Зауважте, що весь код запускався з Ruby MRI 2.4.1
, тож не має гарантії, що його поведінка буде ідентичною для інших імплементацій (JRuby, mruby, тощо). Та і навряд ви захочете реалізувати щось подібне на реальному проекті.
Напевно вам, як і кожному користувачу Ruby, доводилося мати справу з вбудованим класом Hash
. Я зробив можливим використання моїх власних класів як ключів, і знаючи про існування декількох методів визначення еквівалентності вирішив зробити власний "дивний екземпляр" класу Hash
.
Дивний хеш №1: Неунікальні ключі
Що ви можете сказати про хеш, який дозволяє мати тільки одне значення для будь-якого ключа Integer
, не дивлячись на його значення?
У документації зазначено, що це просто, як зміна поведінки Integer#hash
так, щоб він завжди повертав одне й те саме значення. Або ж так само як змусити Integer#eql?
повертати true
.
class Integer
def eql?(other)
true
end
def hash
0
end
end
table = {}
table[1] = 'one'
table[5] = 'five'
puts table
Результат:
#=> {1=>"one", 5=>"five"}
Таблиця мала два елементи з ключами [1]
і [5]
навіть я не очікував, що Hash зможе розрізнити ці ключі. Погравшись з деякими з цих модифікованих цілочисельних змінних, можна побачити як вони працюють:
irb(main)> 1 == 2
=> false
irb(main)> 1.eql? 2
=> true
irb(main)> 1.hash
=> 0
irb(main)> 2.hash
=> 0
Я знаю, що я перевизначив eql?
та hash
у моєму класі, і вони працювали так, як описано у документації Ruby. Але якщо модифікувати таким самим чином клас Array
?
class Array
def eql?(other)
true
end
def hash
0
end
end
table = {}
table[[1]] = 'масив з 1'
table[[5]] = 'масив з 5'
puts table
Результат:
#=> {[1]=>"масив з 5"}
Обидва елементи [1]
і [5]
були оброблені начебто вони мають однаковий ключ.
Значення першого елементу масиву table[[1]] = 'масив з 1'
було перезаписане останнім table[[5]] = 'масив з 5'
.
Тож елемент масиву поверне останнє збережене у нього значення table
з ключем.
irb(main)> table[[1]]
=> "масив з 5"
irb(main)> table[[5]]
=> "масив з 5"
irb(main)> table[['hello']]
=> "масив з 5"
Пошук істини
Тож чому модифікований клас Array
працює по-іншому, у той час як Integer
– ні?
Деякі тести вказують на те, що інші класи такі, як Symbol
і String
також вперто продовжують оброблюватися як унікальні навіть після модифікації.
Документація не має спеціальних пояснень як вони мають працювати, у випадку їх використання як ключів хешу.
Якщо зазирнути у вихідний код MRI то можна помітити дещо важливе у hash.c
:
rb_any_cmp(VALUE a, VALUE b) {
// ... скорочення
if (FIXNUM_P(a) && FIXNUM_P(b)) {
return a != b;
}
if (RB_TYPE_P(a, T_STRING) && RBASIC(a)->klass == rb_cString &&
RB_TYPE_P(b, T_STRING) && RBASIC(b)->klass == rb_cString) {
return rb_str_hash_cmp(a, b);
}
// ... скорочення
if (SYMBOL_P(a) && SYMBOL_P(b)) {
return a != b;
}
// ... скорочення
Безумовно, це виглядає як оптимізація обробки строкових, числових і символьних ключів. Також цей код схожий на функцію any_hash
. Знаючи, що це спеціальні випадки, ви можете впоратися зі створенням "дивних" екземплярів хешу.
Дивний хеш №2: Дубльовані ключі
А що про збереження декількох значень з одним ключем? Якщо об'єкт повертає несумісні значення для hash
, тоді його клас не буде розглядати їх як однакові об'єкти:
class Array
@@last_id = 0
def eql?(other)
false
end
def hash
@@last_id = @@last_id + 1
end
end
table = {}
table[[0]] = 'масив з 0'
table[[0]] = 'ще один масив з 0'
Результат:
puts table
#=> {[0]=>"масив з 0", [0]=>"ще один масив з 0"}
puts table.keys
#=> [[0], [0]]
Масив table
має декілька елементів з однаковими ключами:
У будь-якому випадку отримання значень збережених під одним ключем не дасть результатів:
table = {}
table[[0]] = 'масив з 0'
Результат:
puts table[[0]]
#=> nil
Дивний хеш №3: Тимчасові ключі
class Array
def eql?(other)
Time.now.to_i < (@@key_expires_at || 0)
end
def hash
@@key_expires_at ||= Time.now.to_i + 3
end
end
table = {}
table[['expires']] = 'це значення доступне лише 3 секунди'
puts table[['expires']]
Результат:
#=> 'це значення доступне лише 3 секунди'
Результат:
sleep(5)
puts table[['expires']]
#=> nil
Тепер масив може буди використаний для отримання значення з заданим періодом часу. А також, всі ключі масиву стали виглядати як ідентичні у першому прикладі.
Ще немає коментарів