Вычитание массива Ruby без удаления элементов более одного раза
Канонический пример разности массивов в Ruby:
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
Какой лучший способ получить следующее поведение?
[ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) #=> [ 1, 2, 3, 3, 5 ]
То есть, только первый экземпляр каждого совпадающего элемента во втором массиве удаляется из первого массива.
Ответы
Ответ 1
Вычитайте значения столько раз, сколько они появляются в другом массиве, или любой Enumerable:
class Array
# Subtract each passed value once:
# %w(1 2 3 1).subtract_once %w(1 1 2) # => ["3"]
# [ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) => [1, 2, 3, 3, 5]
# Time complexity of O(n + m)
def subtract_once(values)
counts = values.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
reject { |e| counts[e] -= 1 unless counts[e].zero? }
end
Вычтите каждое уникальное значение один раз:
require 'set'
class Array
# Subtract each unique value once:
# %w(1 2 2).subtract_once_uniq %w(1 2 2) # => [2]
# Time complexity of O((n + m) * log m)
def subtract_once_uniq(values)
# note that set is implemented
values_set = Set.new values.to_a
reject { |e| values_set.delete(e) if values_set.include?(e) }
end
end
Ответ 2
class Array
def subtract_once(b)
h = b.inject({}) {|memo, v|
memo[v] ||= 0; memo[v] += 1; memo
}
reject { |e| h.include?(e) && (h[e] -= 1) >= 0 }
end
end
Я считаю, что это делает то, что я хочу. Большое спасибо @glebm
Ответ 3
Это все, о чем я могу думать до сих пор:
[1, 2, 4].each { |x| ary.delete_at ary.index(x) }
Ответ 4
Подобно @Jeremy Ruten, но учитывает тот факт, что некоторые элементы могут отсутствовать:
# remove each element of y from x exactly once
def array_difference(x, y)
ret = x.dup
y.each do |element|
if index = ret.index(element)
ret.delete_at(index)
end
end
ret
end
Этот ответ также не будет изменять исходный массив при его работе, поэтому:
x = [1,2,3]
y = [3,4,5]
z = array_difference(x, y) # => [1,2]
x == [1,2,3] # => [1,2,3]