#ruby
#ruby
Вопрос:
У меня есть этот массив
types = ['first', 'second', 'third']
и этот массив хэшей
data = [{query: "A"}, {query: "B"}, {query:"C", type: 'first'}]
Теперь я должен «расширить» каждый хэш данных с каждым типом, если он еще не существует. Все существующие ключи хэша также должны быть скопированы (например. :запрос).
Таким образом, конечный результат должен быть:
results = [
{query: "A", type: 'first'}, {query: "A", type: "second"}, {query: "A", type: "third"},
{query: "B", type: 'first'}, {query: "B", type: "second"}, {query: "D", type: "third"},
{query: "C", type: 'first'}, {query: "C", type: "second"}, {query: "C", type: "third"}
]
массив данных довольно большой для вопросов производительности.
Комментарии:
1. Как насчет этого,
type: 'first'
изdata
последнего элемента массива?2. Иногда тип : может уже присутствовать, и его не следует включать снова
3. «не должен включаться снова» — Тем не менее, он все равно перезаписывается?
4. Да, его можно было бы перезаписать, если бы он был быстрее
5. Что случилось с
query: "D"
, откуда это взялось?
Ответ №1:
Вы можете использовать Array#product
для объединения обоих массивов и Hash#merge
для добавления :type
ключа:
data.product(types).map { |h, t| h.merge(type: t) }
#=> [
# {:query=>"A", :type=>"first"}, {:query=>"A", :type=>"second"}, {:query=>"A", :type=>"third"},
# {:query=>"B", :type=>"first"}, {:query=>"B", :type=>"second"}, {:query=>"B", :type=>"third"},
# {:query=>"C", :type=>"first"}, {:query=>"C", :type=>"second"}, {:query=>"C", :type=>"third"}
# ]
Обратите внимание, что вышеизложенное заменит существующие значения для :type
на значения из types
массива. (на каждый хэш может быть только один :type
)
Если вам нужна более сложная логика, вы можете передать блок merge
, который обрабатывает существующие / конфликтующие ключи, например:
h = { query: 'C', type: 'first' }
t = 'third'
h.merge(type: t) { |h, v1, v2| v1 } # preserve existing value
#=> {:query=>"C", :type=>"first"}
h.merge(type: t) { |h, v1, v2| [v1, v2] } # put both values in an array
#=> {:query=>"C", :type=>["first", "third"]}
Ответ №2:
Мы видим, что каждый хэш в data
сопоставляется массиву из трех хэшей, и результирующий массив из трех массивов затем должен быть выровнен, предлагая пропустить шаг, используя метод Enumerable#flat_map на data
. Конструкция выглядит следующим образом.
n = types.size
#=> 3
data.flat_map { |h| n.times.map { |i| ... } }
где ...
создается хэш, такой как
{:query=>"A", :type=>"second"}
Далее мы видим, что значение :type
в возвращаемом массиве хэшей равно :first
then :second
, then :third
, then :first
и так далее. То есть значение циклически изменяется между элементами types
. Кроме того, тот факт, что один из хэшей в data
имеет ключ :type
, не имеет значения, поскольку он будет перезаписан. Поэтому для каждого значения i
( 0
, 1
или 2
) в map
приведенном выше блоке мы хотим объединить h
с { type: types[i%n] }
:
n = types.size
data.flat_map { |h| n.times.map { |i| h.merge(type: types[i%n]) } }
#=> [{:query=>"A", :type=>"first"}, {:query=>"A", :type=>"second"},
# {:query=>"A", :type=>"third"},
# {:query=>"B", :type=>"first"}, {:query=>"B", :type=>"second"},
# {:query=>"B", :type=>"third"},
# {:query=>"C", :type=>"first"}, {:query=>"C", :type=>"second"},
# {:query=>"C", :type=>"third"}]
В качестве альтернативы мы можем использовать метод Array#cycle.
enum = types.cycle
#=> #<Enumerator: ["first", "second", "third"]:cycle>
Как следует из названия метода,
enum.next
#=> "first"
enum.next
#=> "second"
enum.next
#=> "third"
enum.next
#=> "first"
enum.next
#=> "second"
...
ad infinitum. Прежде чем продолжить, позвольте мне сбросить счетчик.
enum.rewind
Смотрите Перечислитель#следующий и Перечислитель #перемотка.
n = types.size
data.flat_map { |h| n.times.map { h.merge(type: enum.next) } }
#=> <as above>