Расширение массива хэшей значениями из массива

#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>