Зачем использовать символы в качестве хеш-ключей в Ruby?

162

Часто люди используют символы в качестве ключей в хэше Ruby.

В чем преимущество использования строки?

Например:

hash[:name]

против

hash['name']
Максимум
источник

Ответы:

227

TL; DR:

Использование символов не только экономит время при выполнении сравнений, но и экономит память, поскольку они сохраняются только один раз.

Рубиновые символы являются неизменяемыми (не могут быть изменены), что значительно упрощает поиск

Краткий (иш) ответ:

Использование символов не только экономит время при выполнении сравнений, но и экономит память, поскольку они сохраняются только один раз.

Символы в Ruby в основном являются «неизменяемыми строками» .. это означает, что они не могут быть изменены, и это означает, что один и тот же символ при многократном обращении по всему исходному коду всегда сохраняется как одна и та же сущность, например, имеет один и тот же идентификатор объекта ,

С другой стороны, строки изменчивы , их можно изменить в любое время. Это подразумевает, что Ruby необходимо хранить каждую строку, которую вы упоминаете в исходном коде, в отдельной сущности, например, если у вас несколько раз упоминается строка «имя» в вашем исходном коде, Ruby необходимо хранить их все в отдельных объектах String, потому что они может измениться позже (такова природа строки Ruby).

Если вы используете строку в качестве ключа Hash, Ruby необходимо оценить строку и посмотреть ее содержимое (и вычислить хэш-функцию для этого) и сравнить результат со значениями (хэшированными) ключей, которые уже хранятся в Hash. ,

Если вы используете символ в качестве хэш-ключа, это неявно означает, что он неизменен, поэтому Ruby в основном может просто сравнить (hash function of) object-id с (hashed) object-id ключей, которые уже хранятся в Хеш (намного быстрее)

Недостаток: каждый символ занимает слот в таблице символов интерпретатора Ruby, который никогда не освобождается. Символы никогда не собираются мусором. Так что угловой случай - это когда у вас есть большое количество символов (например, автоматически сгенерированных). В этом случае вы должны оценить, как это влияет на размер вашего интерпретатора Ruby.

Ноты:

Если вы выполняете сравнение строк, Ruby может сравнивать символы только по их идентификаторам объектов, без необходимости их оценки. Это намного быстрее, чем сравнение строк, которые необходимо оценить.

Если вы обращаетесь к хешу, Ruby всегда применяет хеш-функцию для вычисления «хеш-ключа» из любого ключа, который вы используете. Вы можете представить что-то вроде MD5-хеша. И тогда Руби сравнивает эти «хешированные ключи» друг с другом.

Длинный ответ:

https://web.archive.org/web/20180709094450/http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net.s3-website-us-east-1.amazonaws.com/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/

Тило
источник
5
Кстати, символы будут GCd в следующей версии Ruby: bugs.ruby-lang.org/issues/9634
Ajedi32
2
Кроме того, строки автоматически замораживаются при использовании в качестве хэш-ключей в Ruby. Так что не совсем верно, что строки изменчивы, когда говорят о них в этом контексте.
Ajedi32
1
Отличное понимание темы & Первая ссылка в разделе «Длинный ответ» удалена или перенесена.
Хбксагар
2
Символы - это мусор, собранный в Ruby 2.2
Marc-André Lafortune
2
Отличный ответ! С троллинговой стороны, ваш «короткий ответ» также достаточно длинный. ;)
технофил
22

Причина в эффективности, с множественным усилением над строкой:

  1. Символы являются неизменными, поэтому вопрос "что произойдет, если ключ изменится?" не нужно спрашивать
  2. Строки дублируются в вашем коде и обычно занимают больше места в памяти.
  3. Хеш-поиск должен вычислять хеш ключей для их сравнения. Это O(n)для строк и константа для символов.

Более того, в Ruby 1.9 введен упрощенный синтаксис только для хеша с символьными ключами (например h.merge(foo: 42, bar: 6)), а в Ruby 2.0 есть аргументы с ключевыми словами, которые работают только для символьных ключей.

Примечания :

1) Вы можете быть удивлены, узнав, что Ruby обрабатывает Stringключи иначе, чем любой другой тип. На самом деле:

s = "foo"
h = {}
h[s] = "bar"
s.upcase!
h.rehash   # must be called whenever a key changes!
h[s]   # => nil, not "bar"
h.keys
h.keys.first.upcase!  # => TypeError: can't modify frozen string

Только для строковых ключей, Ruby будет использовать замороженную копию вместо самого объекта.

2) Буквы «b», «a» и «r» сохраняются только один раз для всех вхождений :barв программе. До Ruby 2.2 было плохой идеей постоянно создавать новые Symbols, которые никогда не использовались повторно, так как они навсегда останутся в глобальной таблице поиска Symbol. Ruby 2.2 будет собирать их мусор, так что не беспокойтесь.

3) На самом деле, вычисление хеша для Symbol не заняло никакого времени в Ruby 1.8.x, поскольку идентификатор объекта использовался напрямую:

:bar.object_id == :bar.hash # => true in Ruby 1.8.7

В Ruby 1.9.x это изменилось по мере того, как хэши переходят от одного сеанса к другому (включая те из них Symbols):

:bar.hash # => some number that will be different next time Ruby 1.9 is ran
Марк-Андре Лафортун
источник
+1 за ваши отличные заметки! Первоначально я не упомянул хеш-функцию в своем ответе, потому что я пытался облегчить чтение :)
Tilo
@Tilo: действительно, именно поэтому я написал свой ответ :-) Я только что отредактировал свой ответ, чтобы упомянуть специальный синтаксис в Ruby 1.9 и обещанные именованные параметры Ruby 2.0
Marc-André Lafortune
Можете ли вы объяснить, как хэш-поиск постоянен для символов и O (n) для строк?
Асад Моосви
7

Re: в чем преимущество перед использованием строки?

  • Стиль: его Рубиновый путь
  • (Очень) поиск значений немного быстрее, поскольку хеширование символа эквивалентно хешированию целого по сравнению с хешированием строки.

  • Недостаток: занимает слот в таблице символов программы, который никогда не освобождается.

Ларри К
источник
4
+1 за упоминание о том, что символ никогда не собирается мусором.
Вортико
символ никогда не сборщиком мусора - не верно , так как рубин 2.2+
Эвдемония
0

Я был бы очень заинтересован в продолжении относительно замороженных строк, представленных в Ruby 2.x.

Когда вы имеете дело с множеством строк, поступающих из текстового ввода (я имею в виду HTTP-параметры или полезную нагрузку, например, через Rack), гораздо проще использовать строки везде.

Когда вы имеете дело с десятками из них, но они никогда не меняются (если они «словарный запас» вашего бизнеса), мне нравится думать, что их замораживание может иметь значение. Я еще не делал никаких тестов, но думаю, это будет близко к производительности символов.

jlecour
источник