【问题标题】:Why does Ruby "include?" behave differently when nested within an if/end conditional?为什么 Ruby “包含”?当嵌套在 if/end 条件中时表现不同?
【发布时间】:2015-04-21 16:55:57
【问题描述】:

在我看来,以下三个示例在逻辑上是等价的,应该会产生相同的结果。

print "Enter your string: "
my_string = gets.chomp

#Example 1
my_string.include? ("j" or "J")
puts "1. Your string includes a j or J." 

#Example 2
if
  my_string.include? ("j" or "J")
  puts "2. Your string includes a j or J." 
end

#Example 3
if
  my_string.include? ("j") or my_string.include? ("J")
  puts "3. Your string includes a j or J." 
end

这是带有大写 J 的结果。

Enter your string: Jack
1. Your string includes a j or J.
3. Your string includes a j or J. 

这里是小写 j 的结果。

Enter your string: jack
1. Your string includes a j or J.
2. Your string includes a j or J.
3. Your string includes a j or J.

由于我不明白的原因,只有示例 1 和 3 产生了相同的结果。相比之下,在示例 2 中,仅评估括号中的第一项(小写 j)。

但是为什么呢?如果示例 1 完美运行,为什么示例 2 仅仅因为“包含”而失败?方法嵌套在“if/end”中?

如果我们需要将代码包装在“if/elsif/end”条件中,有没有办法在“包含”中列出要评估的多个值?不需要像示例 3 中那样单独写出它们的方法?如果要评估许多值,这将是乏味的 - 几乎是“反 Ruby”!

换句话说,有没有办法让示例 2 正常工作?

【问题讨论】:

  • 让示例 2 工作的最简单方法可能是 my_string.downcase.include?('j')
  • 条件确实应该和if在同一行。也可能是您涉足正则表达式的时候了:my_string.match(/[j]/i) 将是您想要的,您可以在其中根据需要添加其他字母。
  • 感谢@Tadman,这是一个明显的例子。很明显,包括?方法的局限性太大(例如,仅在要检查单个值的情况下才有用)。我扩展了您的建议如下(如示例 2b),它给了我我想要的东西:if (my_string.chars & %w[ j J x y z]).any? puts "2b. Your string includes a j or J or x or y or z." end
  • 很高兴有帮助!如果您想匹配任意字符,则转换为:my_string.match(/[jJyz]/)。这可能效率更高,尽管如果您不熟悉正则表达式可能很难阅读。

标签: ruby-on-rails ruby


【解决方案1】:

以下是每个示例中发生的情况:

第一个例子

此示例输出1. Your string includes a j or J.,与前一行无关。 my_string.include? 检查被忽略,因为它没有用于任何地方的比较,所以第二行只是一个普通的puts

第二个例子

第二个例子更有趣一点。 ("j" or "J") 是 Ruby 中的语法,它将输出提供的第一个参数,其计算结果为 true"j" 计算结果为 true,因为它不是 nil 或 false,所以它成为第二个 include? 方法的参数。 include? 区分大小写,因此它将返回 false - 字符串 Jack 不包含小写字母 j

您可以通过运行irb 并输入类似1 or 2false and 1 的内容来尝试此操作;您会很快看到返回第一个 true 参数(如果没有参数为 true,则返回 false)。

除了更新include? 检查以使用set intersections 之类的东西之外,没有任何好的方法可以使这项工作保持原样。一个更简单的解决方案可能是在检查字符之前downcase 输入。

Avdi Grimm posted a good video 在 Ruby 中使用 andor

第三个例子

第三个示例在字符串上调用include? 两次,并在第二次调用时返回 true,因此正在评估 if 语句。

更新

papirtiger 的回答让我开始思考,所以我使用以下脚本对 Ripper 进行了一些挖掘:

require 'ripper'
require 'pp'

expression = <<-FOO
if true
  puts 'Hello'
end
FOO

pp Ripper.sexp(expression)

结果如下:

[:program,
 [[:if,
   [:var_ref, [:@kw, "true", [1, 3]]],
   [[:command,
     [:@ident, "puts", [2, 2]],
     [:args_add_block,
      [[:string_literal,
        [:string_content, [:@tstring_content, "Hello", [2, 8]]]]],
      false]]],
   nil]]]

将表达式更新为以下内容后:

expression = <<-FOO
if
  true
  puts 'Hello'
end
FOO

这是新的输出:

[:program,
 [[:if,
   [:var_ref, [:@kw, "true", [2, 2]]],
   [[:command,
     [:@ident, "puts", [3, 2]],
     [:args_add_block,
      [[:string_literal,
        [:string_content, [:@tstring_content, "Hello", [3, 8]]]]],
      false]]],
   nil]]]

看起来 Ruby 确实忽略了任何空格并计算下一个表达式。我没有足够的专业知识来深入挖掘,但在尝试了更多示例(例如在 if 语句后添加十几个换行符)之后,我确信了。

【讨论】:

  • 不 - 由于 'if' 之后的换行,2 和 3 中的条件永远不会计算
  • 有趣——如果你输入jack作为输入,第二个条件通过。如果输入Jack 大写J,则会失败。
  • papertiger,我删除了空格并没有改变结果。换句话说,不管有没有空格,如果我想让代码正常工作,那么我必须按照示例 3 使用长格式、完整编写的版本,而不是能够使用我在示例中尝试使用的简洁版本2.
  • 非常感谢 samsymons, 对 Avdi Grimm 视频的引用非常有用。我还将进一步探索您设置的交叉点示例。我一直希望对我的问题有一个简单的答案,期望有一种简单的方法可以将一组值插入包含?方法 - 例如include?(a, b, c, .... - 但它似乎是 include? 方法的一个限制,它在括号内只接受一个值,因此对于涉及需要的情况显然需要一种替代方法测试许多可能的匹配项。
  • @GingerCat 如果您想针对多个值检查字符串,您可以使用正则表达式,然后使用if my_string[/J|j/]。如果您想要更复杂的检查,您可以使用正则表达式提供的所有内容。
【解决方案2】:

Ruby 解释器逐行评估所有内容,并在调用方法之前首先评估参数。因此,例如,如果您有一个读取foo(bar('test')) 的代码,那么解释器将首先评估'test',这将返回字符串test,然后它将作为参数传递给bar,并且将传递bar 返回的任何内容到foo

让我们对每个示例应用相同的分析。

# Example 1
my_string.include? ("j" or "J")
puts "1. Your string includes a j or J." 

解释器将在my_string.include? ("j" or "J") 线上评估"j" or "J"。如果您输入 irb "j" or "J",您将看到输出为 "j"。所以那行变成my_string.include?("j")my_string 的计算结果为 "Jack",所以整个表达式变成了 "Jack".include?("j"),它返回 false,我们不对这些信息做任何事情,我们只是移动到下一行并打印一条语句到屏幕 puts "1. Your string includes a j or J."。所以无论你输入什么字符串,你总是会从示例 1 中得到相同的答案。

让我们来看例子2。如果你删除if之后不必要的新行,你可以用更容易理解的方式重写它:

#Example 2
if my_string.include?("j" or "J")
  puts "2. Your string includes a j or J." 
end

不,我们已经分析了my_string.include?("j" or "J"),我们知道它将与"Jack".include?("j") 相同,后者将返回false。所以我们得到if false ...,因此if 语句中的puts 语句将不会被执行,并且不会在屏幕上打印任何内容。

最后一个例子可以通过删除if之后的额外新行以更清晰的方式编写

if my_string.include? ("j") or my_string.include? ("J")
  puts "3. Your string includes a j or J." 
end

它将评估为if false or true..,即if true,因此它将打印该语句。

【讨论】:

  • 我很高兴你摸到它@GingerCat
【解决方案3】:
#Example 2
if
  my_string.include? ("j" or "J")
  puts "2. Your string includes a j or J." 
end

在这种情况下,"j" or "J" 是一个计算结果为 "j" 的表达式。你可以在 irb 中运行它来自己测试。表达式总是使用从右到左的方式进行评估(参见http://www.cs.uml.edu/~canning/101/RLWM.pdf)。 Ruby 在这方面有点模糊,因为最后一个方法不需要括号,所以你必须想象它们在那里

因此,"Jack" 的输入不会导致它通过,因为它不包含 "j"

另外值得注意的是,or 运算符与|| 不同,如果使用不当,它可能会咬你。

它实际上更像是控制流的 Perl 主义。在此处查看 Avdi 的截屏视频:http://devblog.avdi.org/2014/08/26/how-to-use-rubys-english-andor-operators-without-going-nuts/

【讨论】:

    【解决方案4】:

    您可以通过使用 || 运算符而不是 or 来解决此问题 - 它们不可互换。比如2,试试my_string.include?('j') || my_string.include?("J")或者更好的my_string.downcase.include?('j')

    【讨论】:

    • 感谢 mdkirbym,我同意这些解决方案中的任何一个都可以。但是,我一直在寻找一种非常简洁的方法来编写表达式来检查字符串中是否包含某些值,特别是如果我有很多值要检查。 (J 和 j 的例子只是一个简化。)上面几个人的优秀答案证明了 include?方法仅限于要检查的单个值 - include?(x) - 并且永远不会允许 include?(x, y, z)。这表明正则表达式解决方案(根据上面的 luri G cmets)是比包含更好的方法?方法。
    • 确实,问题的最后一句我太关注了,其他的部分都没注意。两个主要的事情是 1. 使用or 进行控制流,使用|| 进行布尔检查。 2.对字符串/子字符串使用正则表达式检查,对数组中的对象使用include?(或Hash中的键,或Enumberable中的对象等)
    猜你喜欢
    • 1970-01-01
    • 2012-12-12
    • 2021-09-11
    • 2019-04-11
    • 1970-01-01
    • 2021-12-31
    • 2021-02-09
    • 1970-01-01
    • 2012-02-17
    相关资源
    最近更新 更多