Проверка наличия символа в массиве с помощью include? - PullRequest
3 голосов
/ 26 ноября 2011

Попытка Ruby с помощью Ruby Koans .Там есть следующий тест:

def test_method_names_become_symbols
  symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s }
  assert_equal __, symbols_as_strings.include?("test_method_names_become_symbols")
end

# THINK ABOUT IT:
#
# Why do we convert the list of symbols to strings and then compare
# against the string value rather than against symbols?

Я попытался сделать то же самое в консоли irb, и он возвратил false для неопределенного метода.Но затем я попробовал то же самое в каком-то файле test.rb, и true был возвращен как существующим, так и несуществующим методам.

Пример кода:

def test_method
end
symbols = Symbol.all_symbols.map { |x| x }
puts symbols.include?(:test_method) # returns true in both cases
puts symbols.include?(:test_method_nonexistant) # returns false in irb, true if executed directly

Вопросы: почему мыконвертировать символы в строки в таком случае и почему в irb и обычном файле разные результаты?

Спасибо!

Ответы [ 2 ]

7 голосов
/ 27 ноября 2011

Давайте пройдемся по слегка измененной версии вашего тестового кода, как это видно по irb и как отдельный скрипт:

def test_method;end
symbols = Symbol.all_symbols # This is already a "fixed" array, no need for map
puts symbols.include?(:test_method)
puts symbols.include?('test_method_nonexistent'.to_sym)
puts symbols.include?(:test_method_nonexistent)
eval 'puts symbols.include?(:really_not_there)'

Когда вы попробуете это в irb, каждая строкабудет проанализирован и оценен до следующей строки.Когда вы нажмете вторую строку, symbols будет содержать :test_method, потому что def test_method;end уже был оценен.Но символ :test_method_nonexistent нигде не был замечен, когда мы добрались до строки 2, поэтому строки 4 и 5 скажут «ложь».Строка 6, конечно, даст нам еще одно ложное значение, потому что :really_not_there не существует до тех пор, пока не вернется eval.Итак, irb говорит следующее:

true
false
false
false

Если вы запускаете это как скрипт на Ruby, все происходит в несколько ином порядке.Сначала Ruby проанализирует сценарий во внутреннем формате, который понимает виртуальная машина Ruby, а затем вернется к первой строке и начнет выполнение сценария.Когда анализируется скрипт, символ :test_method будет существовать после разбора первой строки, а :test_method_nonexistent будет существовать после разбора пятой строки;Итак, перед запуском скрипта известны два символа, которые нас интересуют.Когда мы добрались до шестой строки, Руби просто видит eval и строку, но еще не знает, что eval вызывает появление символа.

Теперь у нас есть два наших символа (:test_method и :test_method_nonexistent) и простая строка, которая при подаче на eval создаст символ (:really_not_there).Затем мы возвращаемся к началу, и виртуальная машина начинает выполнять код.Когда мы запускаем строку 2 и кешируем наш массив символов, оба :test_method и :test_method_nonexistent будут существовать и появляться в массиве symbols, потому что анализатор их создал.Таким образом, строки с 3 по 5:

puts symbols.include?(:test_method)
puts symbols.include?('test_method_nonexistent'.to_sym)
puts symbols.include?(:test_method_nonexistent)

выведут «true».Затем мы нажимаем строку 6:

eval 'puts symbols.include?(:really_not_there)'

и выводится «false», потому что :really_not_there создается eval во время выполнения, а не во время синтаксического анализа.В результате Ruby говорит:

true
true
true
false

Если мы добавим это в конце:

symbols = Symbol.all_symbols
puts symbols.include?('really_not_there'.to_sym)

Тогда мы получим еще одно «истинное» из irb иавтономный скрипт, потому что eval создаст :really_not_there и мы получим свежую копию списка символов.

2 голосов
/ 27 ноября 2011

Причина, по которой вы должны преобразовывать символы в строки при проверке существования символа, заключается в том, что в противном случае он всегда будет возвращать true.Аргумент, передаваемый методу include?, вычисляется первым, поэтому, если вы передадите ему символ, создается новый символ и добавляется в кучу, поэтому у Symbol.all_symbols фактически есть копия символа.

Symbol.all_symbols.include? :the_crow_flies_at_midnight #=> true

Однако преобразование тысяч символов в строки для сравнения (что намного быстрее с символами) является плохим решением.Лучшим способом было бы изменить порядок вычисления этих операторов:

symbols = Symbol.all_symbols
symbols.include? :the_crow_flies_at_midnight #=> false 

Этот «снимок» символов в словаре берется до того, как наш проверенный символ вставляется в кучу, поэтому даже еслинаш аргумент существует в куче во время вызова метода include?, мы получаем ожидаемый результат.

Я не знаю, почему он не работает в вашей консоли IRB.Возможно, вы опечатаны.

...