Ruby 中保留重复元素的数组之间的区别
Difference Between Arrays Preserving Duplicate Elements in Ruby
我是 Ruby 的新手,希望能找出两个数组之间的区别。
我知道常用的方法:
a = [...]
b = [...]
difference = (a-b)+(b-a)
但问题在于这是在计算集合差,因为在ruby中,语句(a-b)
定义了a相对于b的集合补码。
这意味着 [1,2,2,3,4,5,5,5,5] - [5]
= [1,2,2,3,4]
,因为它会剔除第一组中出现的所有 5,而不仅仅是一个,其行为类似于对数据的筛选。
我希望它只删除一次差异,因此例如 [1,2,2,3,4,5,5,5,5]
和 [5]
的差异应该是 [1,2,2,3,4,5,5,5]
,只删除一个 5。
我可以迭代地执行此操作:
a = [...]
b = [...]
complimentAbyB = a.dup
complimentBbyA = b.dup
b.each do |bValue|
complimentAbyB.delete_at(complimentAbyB.index(bValue) || complimentAbyB.length)
end
a.each do |aValue|
complimentBbyA.delete_at(complimentBbyA.index(aValue) || complimentBbyA.length)
end
difference = complimentAbyB + complimentBbyA
但这似乎非常冗长且效率低下。我不得不想象有比这更优雅的解决方案。所以我的问题基本上是,找到两个数组差异的最优雅方法是什么,如果一个数组比另一个数组出现更多单个元素,它们不会全部被删除?
.....
ha = a.group_by(&:itself).map{|k, v| [k, v.length]}.to_h
hb = b.group_by(&:itself).map{|k, v| [k, v.length]}.to_h
ha.merge(hb){|_, va, vb| (va - vb).abs}.inject([]){|a, (k, v)| a + [k] * v}
ha
和hb
是以原数组中的元素为键,出现次数为值的散列。下面的合并将它们放在一起并创建一个散列,其值是两个数组中出现次数的差值。 inject 将其转换为一个数组,该数组的每个元素都按散列中给定的数字重复。
另一种方式:
ha = a.group_by(&:itself)
hb = b.group_by(&:itself)
ha.merge(hb){|k, va, vb| [k] * (va.length - vb.length).abs}.values.flatten
我最近提议将这种方法 Ruby#difference 添加到 Ruby 的核心。对于你的例子,它会被写成:
a = [1,2,2,3,4,5,5,5,5]
b = [5]
a.difference b
#=> [1,2,2,3,4,5,5,5]
我经常举的例子是:
a = [3,1,2,3,4,3,2,2,4]
b = [2,3,4,4,3,4]
a.difference b
#=> [1, 3, 2, 2]
我首先在我的回答中提出了这种方法here。在那里你会找到我建议使用该方法的其他 SO 问题的解释和链接。
如链接所示,方法可以这样写:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
我是 Ruby 的新手,希望能找出两个数组之间的区别。
我知道常用的方法:
a = [...]
b = [...]
difference = (a-b)+(b-a)
但问题在于这是在计算集合差,因为在ruby中,语句(a-b)
定义了a相对于b的集合补码。
这意味着 [1,2,2,3,4,5,5,5,5] - [5]
= [1,2,2,3,4]
,因为它会剔除第一组中出现的所有 5,而不仅仅是一个,其行为类似于对数据的筛选。
我希望它只删除一次差异,因此例如 [1,2,2,3,4,5,5,5,5]
和 [5]
的差异应该是 [1,2,2,3,4,5,5,5]
,只删除一个 5。
我可以迭代地执行此操作:
a = [...]
b = [...]
complimentAbyB = a.dup
complimentBbyA = b.dup
b.each do |bValue|
complimentAbyB.delete_at(complimentAbyB.index(bValue) || complimentAbyB.length)
end
a.each do |aValue|
complimentBbyA.delete_at(complimentBbyA.index(aValue) || complimentBbyA.length)
end
difference = complimentAbyB + complimentBbyA
但这似乎非常冗长且效率低下。我不得不想象有比这更优雅的解决方案。所以我的问题基本上是,找到两个数组差异的最优雅方法是什么,如果一个数组比另一个数组出现更多单个元素,它们不会全部被删除?
.....
ha = a.group_by(&:itself).map{|k, v| [k, v.length]}.to_h
hb = b.group_by(&:itself).map{|k, v| [k, v.length]}.to_h
ha.merge(hb){|_, va, vb| (va - vb).abs}.inject([]){|a, (k, v)| a + [k] * v}
ha
和hb
是以原数组中的元素为键,出现次数为值的散列。下面的合并将它们放在一起并创建一个散列,其值是两个数组中出现次数的差值。 inject 将其转换为一个数组,该数组的每个元素都按散列中给定的数字重复。
另一种方式:
ha = a.group_by(&:itself)
hb = b.group_by(&:itself)
ha.merge(hb){|k, va, vb| [k] * (va.length - vb.length).abs}.values.flatten
我最近提议将这种方法 Ruby#difference 添加到 Ruby 的核心。对于你的例子,它会被写成:
a = [1,2,2,3,4,5,5,5,5]
b = [5]
a.difference b
#=> [1,2,2,3,4,5,5,5]
我经常举的例子是:
a = [3,1,2,3,4,3,2,2,4]
b = [2,3,4,4,3,4]
a.difference b
#=> [1, 3, 2, 2]
我首先在我的回答中提出了这种方法here。在那里你会找到我建议使用该方法的其他 SO 问题的解释和链接。
如链接所示,方法可以这样写:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end