Ruby 显式指定的方法或块的数组差异
如果我有数组Ruby 显式指定的方法或块的数组差异,ruby,Ruby,如果我有数组a和b,表达式a-b将返回一个数组,其中包含a中不在b中的所有元素。“不在”在这里是指不合格(!=) 在我的例子中,两个数组只包含相同类型的元素(或者,从ducktyping的角度来看,只包含理解“相等”方法的元素f)。 是否有一种简单的方法可以指定此f作为相等的标准,类似地,我可以在执行排序时提供自己的比较器?目前,我明确地实现了这一点: # Get the difference a-b, based on 'f': a.select { |ael| b.all? {|bel| a
a
和b
,表达式a-b
将返回一个数组,其中包含a
中不在b
中的所有元素。“不在”在这里是指不合格(!=
)
在我的例子中,两个数组只包含相同类型的元素(或者,从ducktyping的角度来看,只包含理解“相等”方法的元素f
)。
是否有一种简单的方法可以指定此f
作为相等的标准,类似地,我可以在执行排序时提供自己的比较器?目前,我明确地实现了这一点:
# Get the difference a-b, based on 'f':
a.select { |ael| b.all? {|bel| ael.f != bel.f} }
这是可行的,但我想知道是否有更简单的方法
更新:从对这个问题的评论中,我得到的印象是,如果能举一个具体的例子,我将不胜感激。那么,我们开始吧:
class Dummy; end
# Create an Array of Dummy objects.
a = Array.new(99) { Dummy.new }
# Pick some of them at random
b = Array.new(10) { a.sample }
# Now I want to get those elements from a, which are not in b.
diff = a.select { |ael| b.all? {|bel| ael.object_id != bel.object_id} }
当然,在这种情况下,我也可以说!ael eql?bel
,但在我的一般解决方案中,情况并非如此。例如数组上的哈希和集合操作(例如-
操作)的“正常”对象相等使用包含对象的方法的输出以及a.eql?(b)
比较的语义
这可以用来提高性能。Ruby在这里假设两个对象是eql?
,如果它们各自的hash
方法的返回值相同(因此,假设两个返回不同hash
值的对象不是eql?
)
对于正常的a-b
操作,这可以用于首先计算每个对象的哈希值一次,然后仅比较这些值。这相当快
现在,如果您有一个自定义等式,那么最好覆盖对象的hash
方法,以便它们为这些语义返回合适的值
一种常见的方法是构建一个数组,其中包含参与对象标识的所有数据,并获取其哈希值,例如
class MyObject
#...
attr_accessor :foo, :bar
def hash
[self.class, foo, bar].hash
end
end
在对象的hash
方法中,您将包含f
比较方法当前考虑的所有数据。然后,您不再实际使用f
,而是使用所有Ruby对象的默认语义,并且可以再次实现对象的快速设置操作
然而,如果这是不可行的(例如,因为您需要基于用例的不同平等语义),您可以自己模拟ruby所做的事情
使用f
方法,您可以执行如下设置操作:
def f_difference(a, b)
a_map = a.each_with_object({}) do |a_el, hash|
hash[a_el.f] = a_el
end
b.each do |b_el|
a_map.delete b_el.f
end
a_map.values
end
使用这种方法,只需计算每个对象的f
值一次。我们首先用a
中的所有f
值和元素构建一个哈希映射,并根据它们的f
值从b
中删除匹配的元素。剩下的值就是结果
这种方法使您不必为a
中的每个对象循环b
,如果对象太多,循环速度可能会很慢。但是,如果每个数组上只有几个对象,那么您最初的方法应该已经很好了
让我们看一看基准测试,当我使用标准的hash
方法代替自定义的f
来获得可比较的结果时
require 'benchmark/ips'
def question_diff(a, b)
a.select { |ael| b.all? {|bel| ael.hash != bel.hash} }
end
def answer_diff(a, b)
a_map = a.each_with_object({}) do |a_el, hash|
hash[a_el.hash] = a_el
end
b.each do |b_el|
a_map.delete b_el.hash
end
a_map.values
end
A = Array.new(100) { rand(10_000) }
B = Array.new(10) { A.sample }
Benchmark.ips do |x|
x.report("question") { question_diff(A, B) }
x.report("answer") { answer_diff(A, B) }
x.compare!
end
使用Ruby 2.7.1,我在我的机器上得到以下结果,表明问题的原始方法比我的答案中的优化版本慢5.9倍:
Warming up --------------------------------------
question 1.304k i/100ms
answer 7.504k i/100ms
Calculating -------------------------------------
question 12.779k (± 2.0%) i/s - 63.896k in 5.002006s
answer 74.898k (± 3.3%) i/s - 375.200k in 5.015239s
Comparison:
answer: 74898.0 i/s
question: 12779.3 i/s - 5.86x (± 0.00) slower
为什么不直接使用f
?(1) 创建一个哈希h
,它将ael.f
映射到ael
(2)遍历b
并从h
中删除bel.f
(3)返回h.values
。我假设OP的原始f
方法只比较值。从您的描述中,它看起来好像您假设f
本质上返回了一个类似于我用f\u hash
构建的值。如果是这样,OP可以使用他们的f
方法,就像我在回答中使用的f_hash
一样。在任何情况下,最终的哈希比较都相当快。我仍然用你的方法编辑了我的答案,这确实比我原来的方法快了一点点。尽管如此,它仍然假设一个合适的f_hash
方法bel.f
–这看起来像一个静态值.Hmmm,现在我再次阅读了这个问题,f
确实应该作为我的f\u散列
方法来工作,好的,根据这些新信息再次编辑。一般的方法是一样的:)也许我真的应该阅读实际的问题,而不是假设一个更有趣的问题:)嗯。。我能看看你想处理的数据吗?所有A的F都是一样的吗?@chad\uz:实际上,我的A
和b
中的元素都是非常复杂的类。这就是为什么我把他们漏掉了。如果您需要一个具体的示例,可以将f
简单地看作是方法object\u id
。实际上,在我的程序的早期版本中,这里确实有object\u id
,只是我现在切换到了一些不同的东西,这更适合我。您可以创建一个包装器,将
、eql?
和散列
委托给相应方法的结果。然后map
将所有对象映射到包装器,在包装器上执行所需的计算(差异/排序),然后展开(我的意思是覆盖==数组中对象的类内部…@user1934428不,您需要添加包装器进行排序/差异,然后将其去掉。您的对象将保持不变。