Фильтровать значения из массива хэшей и удалять их из исходного массива

#arrays #ruby #select #hash

#массивы #ruby #выберите #хэш

Вопрос:

Я изо всех сил пытаюсь найти правильный способ фильтрации массива хэшей, удаляя записи, соответствующие условию фильтра, и возвращая эти удаленные хэши. Вот пример этого magic_function! , который делает то, чего я пытаюсь достичь:

 my_array = [{ id: 1, val: 'foo' }, { id: 2, val: 'bar' }, { id: 3, val: 'baz' }]

extracted_hashes = my_array.magic_function! { |hash| hash[:id] == 1 }
# [{ id: 1, val: 'foo' }]

my_array
# [{ id: 2, val: 'bar' }, { id: 3, val: 'baz' }]

  

Причина, по которой я пытаюсь это сделать, заключается в том, что массив представляет собой огромную коллекцию строк базы данных, и мне нужно их обработать, используя значения из другого массива идентификаторов для обработки. (к сожалению, эта часть не может измениться, ограничение строк из базы данных было бы намного эффективнее)

Использование Array.filter позволяет мне получать правильные хэши, но повторять каждый раз по всей длине массива. Поскольку мне не понадобятся хэши, которые уже были обработаны, я предполагаю, что удаление их из исходного массива будет делать его все меньше и меньше, уменьшая количество итераций, необходимых для фильтрации следующих идентификаторов, пока все идентификаторы не будут обработаны и в исходном массиве ничего не останется.

Комментарии:

1. Вам не нужно удалять их из массива. Если вы можете создать новый массив, который является подмножеством (используя filter ), это нормально. И да, проверьте Enumerable#partition

Ответ №1:

Используйте partition для разделения входного массива на 2 массива в зависимости от условия фильтрации. Вы также можете изменить входной массив на месте, используя его в левой части назначения:

 my_array = [{ id: 1, val: 'foo' } , { id: 2, val: 'bar' } , { id: 3, val: 'baz' }]

# split input array into 2 new arrays, keep input array as is:
selected, other = my_array.partition { |hash| hash[:id] == 1 }

puts "#{selected}"   # [{:id=>1, :val=>"foo"}]
puts "#{other}"      # [{:id=>2, :val=>"bar"}, {:id=>3, :val=>"baz"}]


# remove from the input array selected elements into 1 new array,
# keep the rest in the input array (change the input array in-place):
selected, my_array = my_array.partition { |hash| hash[:id] == 1 }

puts "#{selected}"  # [{:id=>1, :val=>"foo"}]
puts "#{my_array}"  # [{:id=>2, :val=>"bar"}, {:id=>3, :val=>"baz"}]

  

Обратите внимание, что partition можно рассматривать как оба select и reject (или filter и reject ) в одной операции. Вот простой пример, иллюстрирующий, что partition делает:

 x, y = [3, 2, 1, 4].partition { |n| n < 3 }
puts "#{x}; #{y}"   # [2, 1]; [3, 4]
  

Комментарии:

1. Потрясающе, спасибо! Я не знал о partition . Я не мог найти подходящие условия поиска для описания моей проблемы и продолжал видеть примеры с filter и delete_if , которые не достигали того же результата.

Ответ №2:

my_array.magic_function! указывает, что вы хотите создать метод в классе Array . Я бы не советовал этого делать, вместо этого разрешив my_array быть аргументом. Вы могли бы написать свой метод следующим образом.

 def magic_function!(arr)
  (arr.size-1).downto(0).with_object([]) do |i,a|
    a.unshift(arr.delete_at(i)) if yield(arr[i])
  end
end
  
 my_array = [{ id: 1, val: 'foo' }, { id: 2, val: 'bar' }, { id: 3, val: 'baz' }]
  
 magic_function!(my_array) { |h| h[:id] == 2 }
  #=> [{:id=>2, :val=>"bar"}] 
my_array
  #=> [{:id=>1, :val=>"foo"}, {:id=>3, :val=>"baz"}] 
  

Вот второй пример (после установки my_array равного его исходному значению).

 magic_function!(my_array) { |h| h[:id] == 1 || h[:id] == 3 }
  #=> [{:id=>1, :val=>"foo"}, {:id=>3, :val=>"baz"}] 
my_array
  #=> [{:id=>2, :val=>"bar"}] 
  

Смотрите Array#delete_at. Обратите внимание, что необходимо удалять элементы из my_array в обратном порядке, потому что аргумент меняется во время выполнения метода.

Комментарии:

1. Я бы использовал Enumerable#partition , если бы это уже не было занято…

2. partition кажется наиболее адаптированным, но это yield решение также кажется мне очень элегантным. Многому научился из этого, спасибо!