3

To my mind the following three examples are logically equivalent and ought to generate identical results.

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

Here are the results with a capital J.

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

Here are the results with a lower case 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.

For reasons I don't understand, only examples 1 and 3 generate identical results. In example 2, by contrast, only the first item in the brackets (the lower case j) is evaluated.

But why? If example 1 works perfectly, why does example 2 fail simply because the "include?" method is nested within an "if/end"?

And if we need to wrap the code in an "if/elsif/end" conditional, is there a way in which to list multiple values to be assessed in an "include?" method without needing to write them out individually as in example 3? This would be tedious - almost "anti-Ruby" - if there were many values to be evaluated!

In other words, is there a means of making example 2 work correctly?

MrTheWalrus
  • 9,670
  • 2
  • 42
  • 66
TechnoCat
  • 667
  • 8
  • 16
  • 2
    The simplest way to get example 2 working may be `my_string.downcase.include?('j')` – marksiemers Apr 21 '15 at 17:41
  • 1
    The condition really should be on the same line as the `if`. It's also probably time for you to dabble with regular expressions: `my_string.match(/[j]/i)` would be what you want here, where in that you can add other letters as required. – tadman Apr 21 '15 at 18:49
  • 1
    Thanks @Tadman, That's a clear example. It's become obvious that the include? method is far too limiting (e.g. useful only where there is a single value to be checked). I expanded upon your suggestion as follows (as Example 2b), and it gave me exactly what I was looking for: `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` – TechnoCat Apr 21 '15 at 19:38
  • Glad that helped! That translates to: `my_string.match(/[jJyz]/)` if you want to match against arbitrary characters. That's probably a lot more efficient, although regular expressions can be hard to read if you're not familiar with them. – tadman Apr 21 '15 at 21:41

4 Answers4

5

Here's what's happening in each of these examples:

First Example

This example outputs 1. Your string includes a j or J. regardless of the previous line. The my_string.include? check is being ignored as it's not used in a comparison anywhere, so the second line is just a regular puts.

Second Example

The second example is a little more interesting. ("j" or "J") is syntax in Ruby which will output the first of the provided arguments which evaluates to true. "j" evaluates to true because it's not nil or false, so it becomes the argument of the second include? method. include? is case-sensitive, so it will return false – the string Jack does not include a lowercase j.

You can try this out by running irb and entering something like 1 or 2 or false and 1; you'll see pretty quickly that the first true argument is returned (or false if no arguments are true).

There's no good way to make this work as-is, other than updating the include? check to use something like set intersections. An easier solution may be to downcase the input before checking characters.

Avdi Grimm posted a good video on using and and or in Ruby.

Third Example

The third example is calling include? twice on the string, and returning true when it hits the second call, hence the if statement being evaluated.

Update

papirtiger's answer got me thinking, so I did a bit of digging with Ripper using the following script:

require 'ripper'
require 'pp'

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

pp Ripper.sexp(expression)

Here's the result:

[: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]]]

After updating the expression to the following:

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

This was the new output:

[: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]]]

It looks as though Ruby does indeed ignore any whitespace and evaluate the next expression. I don't have enough expertise to dig much deeper, but after trying a few more examples (such as throwing a dozen newlines in after an if statement), I'm convinced.

Community
  • 1
  • 1
Sam
  • 839
  • 10
  • 16
  • Nope - the conditionals in 2 & 3 will never evaluate due to the linebreak after ´if´ – max Apr 21 '15 at 17:14
  • Interesting – if you enter `jack` as input, the second conditional passes. If you enter `Jack` with a capital `J`, it fails. – Sam Apr 21 '15 at 17:16
  • papertiger, I removed the white spaces and it did not change the result. In other words, with or without space, if I wanted the code to work then I had to use the long form, fully-written out version as per Example 3, instead of being able to use the succinct version I tried to use in Example 2. – TechnoCat Apr 21 '15 at 18:03
  • 1
    Many thanks samsymons, The reference to the Avdi Grimm video is extremely useful. I'll also further explore your set intersections example. I'd been hoping for an easy answer to my question, expecting that there would be a simple way to poke a set of values into the include? method - e.g. include?(a, b, c, .... - but it seems to be a limitation of the include? method that it takes only a single value inside the brackets, so an alternative approach is obviously required for cases which involve the need to test for many possible matches. – TechnoCat Apr 21 '15 at 18:19
  • 1
    @GingerCat if you want to check string against multiple values, you can use regex and then use `if my_string[/J|j/]`. if you want more sophisticated checks you can use everything regex has to offer. – Iuri G. Apr 21 '15 at 18:50
  • The most Ruby-like adaptation of #2 is: `(my_string.chars & %w[ j J ]).any?` – tadman Apr 21 '15 at 18:52
3

Ruby interpreter evaluates everything line by line and arguments are evaluated first before calling the method. so for example if you have a code that reads foo(bar('test')), then interpreter will evaluate 'test' first that will return string test, then it will pass that as argument to bar and whatever bar returns will be passed to foo.

Lets apply same analysis to each example.

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

interpreter will evaluate "j" or "J" on line my_string.include? ("j" or "J"). If you type in irb "j" or "J", you will see the output is "j". So that line becomes my_string.include?("j"). my_string evaluates to "Jack" so whole expression becomes "Jack".include?("j") which returns false and we dont do anything with that information, we just move to next line and print a statement to screen puts "1. Your string includes a j or J.". So no matter what you enter for string, you will always get same answer from Example 1.

Lets go to example 2. If you remove unnecessary new line after if, you can rewrite it in more understandable way:

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

No we already analyzed my_string.include?("j" or "J") and we know it will be same as "Jack".include?("j") which will return false. so we get if false ..., therefore puts statement in if statement will not be executed and nothing is printed to screen.

Last example can be written in clearer way by removing extra new line after if

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

It will evaluate to if false or true.. which will be if true so it will print the statement.

Iuri G.
  • 10,460
  • 4
  • 22
  • 39
1
#Example 2
if
  my_string.include? ("j" or "J")
  puts "2. Your string includes a j or J." 
end

In this case "j" or "J" is an expression which evaluates to "j". You can run this in irb to test for yourself. Expressions are always evaluated using a right-left style walk (see http://www.cs.uml.edu/~canning/101/RLWM.pdf). Ruby is a little fuzzy on this as the last method doesn't need parentheses, so you have to just imagine them being there

Thus, an input of "Jack" doesn't cause this to pass because it doesn't include "j"

It's also worth noting that the or operator is not the same as the ||, and it will likely bite you if you don't use it correctly.

It is really more of a Perl-ism for control flow. See Avdi's screencast on it here: http://devblog.avdi.org/2014/08/26/how-to-use-rubys-english-andor-operators-without-going-nuts/

Josh Bodah
  • 1,223
  • 6
  • 17
0

You may solve this by using the || operator, instead of or - they are not interchangeable. For example 2, try my_string.include?('j') || my_string.include?("J") or even better, my_string.downcase.include?('j')

marksiemers
  • 631
  • 8
  • 14
  • Thanks mdkirbym, I agree that either of these solutions would work. However, I was looking for a really succinct way of writing an expression to check if certain values were included in a string, especially if I had many values to check. (The J and j examples were just a simplification.) The excellent answers above from several people demonstrate that the include? method is limited to a single value to check - include?(x) - and will never allow include?(x, y, z). This suggests that a regex solution (as per the luri G comments above) is a better way to go than the include? method. – TechnoCat Apr 21 '15 at 19:22
  • Indeed, I focused too much on the last sentence in the question, and didn't pay attention to the other parts. The two major things are 1. Use ```or``` for control flow, use ```||``` for boolean checks. 2. Use regex checks for strings/substrings, use ```include?``` for objects in an array (or keys in a Hash, or Objects in an Enumberable, etc.) – marksiemers Apr 23 '15 at 14:54