Ruby 1.8.6 Array#uniq 不删除重复的哈希
Ruby 1.8.6 Array#uniq not removing duplicate hashes
我有这个数组,在 ruby 1.8.6 控制台中:
arr = [{:foo => "bar"}, {:foo => "bar"}]
两个元素彼此相等:
arr[0] == arr[1]
=> true
#just in case there's some "==" vs "===" oddness...
arr[0] === arr[1]
=> true
但是,arr.uniq 不会删除重复项:
arr.uniq
=> [{:foo=>"bar"}, {:foo=>"bar"}]
谁能告诉我这是怎么回事?
编辑:我可以写一个不太聪明的 uniqifier,它使用 include?
如下:
uniqed = []
arr.each do |hash|
unless uniqed.include?(hash)
uniqed << hash
end
end;false
uniqed
=> [{:foo=>"bar"}]
这产生了正确的结果,这使得 uniq
的失败更加神秘。
编辑 2:关于正在发生的事情的一些注释,可能只是为了我自己的清楚。正如 @Ajedi32 在评论中指出的那样,统一化失败是因为这两个元素是不同的对象。一些classes定义了eql?
和hash
方法,用于比较,意思是"are these effectively the same thing, even if they're not the same object in memory"。例如,String 就是这样做的,这就是为什么您可以将两个变量定义为 "foo" 并且据说它们彼此相等,即使它们不是同一个对象。
哈希 class 不会 在 Ruby 1.8.6 中执行此操作,因此当 .eql?
和 .hash
在散列对象上调用(.hash 方法与散列数据类型无关 - 它就像散列的校验和类型)它回退到使用对象基 class 中定义的方法,它简单地说 "Is it the same object in memory".
对于散列对象,==
和===
运算符已经做了我想要的,即如果两个散列的内容相同,则它们是相同的。我已经覆盖 Hash#eql?
来使用这些,如下所示:
class Hash
def eql?(other_hash)
self == other_hash
end
end
但是,我不确定如何处理 Hash#hash
:也就是说,我不知道如何为两个内容相同但总是不同的散列生成相同的校验和对于具有不同内容的两个哈希值。
@Ajedi32 建议我在这里 https://github.com/rubinius/rubinius/blob/master/core/hash.rb#L589 看看 Rubinius 对 Hash#hash
方法的实现,我的 Rubinius 实现版本如下所示:
class Hash
def hash
result = self.size
self.each do |key,value|
result ^= key.hash
result ^= value.hash
end
return result
end
end
这似乎确实有效,尽管我不知道“^=”运算符的作用,这让我有点紧张。此外,它非常慢——根据一些原始基准测试,速度大约是原来的 50 倍。这可能会使它使用起来太慢。
编辑 3:一些研究表明“^”是按位异或运算符。当我们有两个输入时,如果输入不同,则 XOR returns 1(即 returns 0 表示 0,0 和 1,1,1 表示 0,1 和 1,0)。
所以,起初我认为这意味着
result ^= key.hash
是 shorthand 对于
result = result ^ key.hash
换句话说,在result的当前值和其他东西之间进行XOR,然后将其保存在result中。不过,我仍然不太明白其中的逻辑。我认为 ^ 运算符可能与指针有关,因为在变量上调用它有效,而在变量值上调用它不起作用:例如
var = 1
=> 1
var ^= :foo
=> 14904
1 ^= :foo
SyntaxError: compile error
(irb):11: syntax error, unexpected tOP_ASGN, expecting $end
所以,在变量上调用 ^= 没问题,但不是变量的值,这让我觉得这与 referencing/dereferencing.
有关
Ruby 的后期实现也有 Hash#hash 方法的 C 代码,Rubinius 的实现似乎太慢了。有点卡住了...
出于效率原因,Array#uniq
不使用 ==
甚至 ===
比较值。根据 the docs:
It compares values using their hash and eql? methods for efficiency.
(注意我在这里链接了 2.4.2 的文档。虽然 1.8.6 的文档不包含此声明,但我相信它仍然适用于 Ruby 的那个版本。)
在 Ruby 1.8.6 中,neither Hash#hash
nor Hash#eql?
are implemented, so they fallback to using Object#hash
and Object#eql?
:
Equality—At the Object level, ==
returns true only if obj and other are the same object. Typically, this method is overridden in descendent classes to provide class-specific meaning.
[...]
The eql?
method returns true
if obj and anObject have the same value. Used by Hash to test members for equality. For objects of class Object, eql?
is synonymous with ==
.
所以根据 Array#uniq
,这两个哈希值是不同的对象,因此是唯一的。
要解决此问题,您可以尝试定义 Hash#hash
and Hash#eql?
yourself. The details of how to do this are left as an exercise to the reader. You may find it helpful however to refer to Rubinius's implementation of these methods.
如何使用 JSON stringify 并像 Javascript 那样对其进行解析?
require 'json'
arr.map { |x| x.to_json}.uniq.map { |x| JSON.parse(x) }
json 方法可能在 1.8.6
中不受支持,请使用支持的方法。
我有这个数组,在 ruby 1.8.6 控制台中:
arr = [{:foo => "bar"}, {:foo => "bar"}]
两个元素彼此相等:
arr[0] == arr[1]
=> true
#just in case there's some "==" vs "===" oddness...
arr[0] === arr[1]
=> true
但是,arr.uniq 不会删除重复项:
arr.uniq
=> [{:foo=>"bar"}, {:foo=>"bar"}]
谁能告诉我这是怎么回事?
编辑:我可以写一个不太聪明的 uniqifier,它使用 include?
如下:
uniqed = []
arr.each do |hash|
unless uniqed.include?(hash)
uniqed << hash
end
end;false
uniqed
=> [{:foo=>"bar"}]
这产生了正确的结果,这使得 uniq
的失败更加神秘。
编辑 2:关于正在发生的事情的一些注释,可能只是为了我自己的清楚。正如 @Ajedi32 在评论中指出的那样,统一化失败是因为这两个元素是不同的对象。一些classes定义了eql?
和hash
方法,用于比较,意思是"are these effectively the same thing, even if they're not the same object in memory"。例如,String 就是这样做的,这就是为什么您可以将两个变量定义为 "foo" 并且据说它们彼此相等,即使它们不是同一个对象。
哈希 class 不会 在 Ruby 1.8.6 中执行此操作,因此当 .eql?
和 .hash
在散列对象上调用(.hash 方法与散列数据类型无关 - 它就像散列的校验和类型)它回退到使用对象基 class 中定义的方法,它简单地说 "Is it the same object in memory".
对于散列对象,==
和===
运算符已经做了我想要的,即如果两个散列的内容相同,则它们是相同的。我已经覆盖 Hash#eql?
来使用这些,如下所示:
class Hash
def eql?(other_hash)
self == other_hash
end
end
但是,我不确定如何处理 Hash#hash
:也就是说,我不知道如何为两个内容相同但总是不同的散列生成相同的校验和对于具有不同内容的两个哈希值。
@Ajedi32 建议我在这里 https://github.com/rubinius/rubinius/blob/master/core/hash.rb#L589 看看 Rubinius 对 Hash#hash
方法的实现,我的 Rubinius 实现版本如下所示:
class Hash
def hash
result = self.size
self.each do |key,value|
result ^= key.hash
result ^= value.hash
end
return result
end
end
这似乎确实有效,尽管我不知道“^=”运算符的作用,这让我有点紧张。此外,它非常慢——根据一些原始基准测试,速度大约是原来的 50 倍。这可能会使它使用起来太慢。
编辑 3:一些研究表明“^”是按位异或运算符。当我们有两个输入时,如果输入不同,则 XOR returns 1(即 returns 0 表示 0,0 和 1,1,1 表示 0,1 和 1,0)。
所以,起初我认为这意味着
result ^= key.hash
是 shorthand 对于
result = result ^ key.hash
换句话说,在result的当前值和其他东西之间进行XOR,然后将其保存在result中。不过,我仍然不太明白其中的逻辑。我认为 ^ 运算符可能与指针有关,因为在变量上调用它有效,而在变量值上调用它不起作用:例如
var = 1
=> 1
var ^= :foo
=> 14904
1 ^= :foo
SyntaxError: compile error
(irb):11: syntax error, unexpected tOP_ASGN, expecting $end
所以,在变量上调用 ^= 没问题,但不是变量的值,这让我觉得这与 referencing/dereferencing.
有关Ruby 的后期实现也有 Hash#hash 方法的 C 代码,Rubinius 的实现似乎太慢了。有点卡住了...
出于效率原因,Array#uniq
不使用 ==
甚至 ===
比较值。根据 the docs:
It compares values using their hash and eql? methods for efficiency.
(注意我在这里链接了 2.4.2 的文档。虽然 1.8.6 的文档不包含此声明,但我相信它仍然适用于 Ruby 的那个版本。)
在 Ruby 1.8.6 中,neither Hash#hash
nor Hash#eql?
are implemented, so they fallback to using Object#hash
and Object#eql?
:
Equality—At the Object level,
==
returns true only if obj and other are the same object. Typically, this method is overridden in descendent classes to provide class-specific meaning.[...]
The
eql?
method returnstrue
if obj and anObject have the same value. Used by Hash to test members for equality. For objects of class Object,eql?
is synonymous with==
.
所以根据 Array#uniq
,这两个哈希值是不同的对象,因此是唯一的。
要解决此问题,您可以尝试定义 Hash#hash
and Hash#eql?
yourself. The details of how to do this are left as an exercise to the reader. You may find it helpful however to refer to Rubinius's implementation of these methods.
如何使用 JSON stringify 并像 Javascript 那样对其进行解析?
require 'json'
arr.map { |x| x.to_json}.uniq.map { |x| JSON.parse(x) }
json 方法可能在 1.8.6
中不受支持,请使用支持的方法。