Расширение метода uniq
-
21-09-2019 - |
Вопрос
Это вопрос Ruby 1.8:
Мы все знаем, как использовать Array#uniq
:
[1,2,3,1].uniq #=> [1,2,3]
Однако мне интересно, можем ли мы обезьяньим способом исправить это для работы со сложными объектами.Текущее поведение выглядит следующим образом:
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
#=> [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}]
Запрошенный является:
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
#=> [{"three"=>"3"}, {"three"=>"4"}]
Решение
Это уже работает у меня в версии 1.8.7.
1:~$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
1:~$ irb -v
irb 0.9.5(05/04/13)
1:~$ irb
>> [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
=> [{"three"=>"3"}, {"three"=>"4"}]
Другие советы
Чтобы заставить массив #uniq работать для Любой объект, который вы должны переопределить двумя методами:хэш и eql?
У всех объектов есть хэш-метод, который вычисляет хэш-значение для этого объекта, поэтому для того, чтобы два объекта были равны, их значения при хэшировании также должны быть равны.
Пример - пользователь уникален, когда его адрес электронной почты уникален:
class User
attr_accessor :name,:email
def hash
@email.hash
end
def eql?(o)
@email == o.email
end
end
>> [User.new('Erin Smith','roo@example.com'),User.new('E. Smith','roo@example.com')].uniq
=> [#<User:0x1015a97e8 @name="Erin Smith", @email="maynurd@example.com"]
Проблема в том, что Hash#hash
и Hash#eql?
оба дают поддельные результаты в Ruby 1.8.6.Это одно из очень редких исправлений monkey, которое я был готов выполнить, потому что эта ошибка серьезно нарушает множество кодов — в частности, функции запоминания.Просто будьте осторожны с патчами monkey, чтобы не переопределить неповрежденное поведение.
Итак:
class Hash
if {}.hash != {}.hash
def hash
# code goes here
end
end
if !{}.eql?({})
def eql?(other)
# code goes here
end
end
end
Но если вы делаете что-то, где вы управляете средой развертывания, просто выдайте сообщение об ошибке, если приложение запускается с версией 1.8.6.
Как насчет этого?
h={}
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].select {|e| need=!h.key?(e) ; h[e]=1 ; need}
#=> [{"three"=>"3"}, {"three"=>"4"}]
Я сам много раз сталкивался с этим.Хэш-равенство в Ruby 1.8.6 нарушено:
require 'test/unit'
class TestHashEquality < Test::Unit::TestCase
def test_that_an_empty_Hash_is_equal_to_another_empty_Hash
assert({}.eql?({}), 'Empty Hashes should be eql.')
end
end
Проходит в Ruby 1.9 и Ruby 1.8.7, завершается неудачей в Ruby 1.8.6.
1.8.7 :039 > [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq {|x|x.values}
=> [{"three"=>"3"}, {"three"=>"4"}]
1.8.7 :040 > [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq {|x|x.keys}
=> [{"three"=>"3"}]
Как насчет чего-нибудь в этом роде?просто uniq_by хэш-значение или хэш-ключ через блок.