How to Write a Ruby Switch Statement (Case...When) with Regex and Backreferences

How to write a Ruby switch statement (case...when) with regex and backreferences?

The references to the latest regex matching groups are always stored in pseudo variables $1 to $9:

case foo
when /^([0-9][0-9])/
print "the month is #{$1}"
else
print "something else"
end

You can also use the $LAST_MATCH_INFO pseudo variable to get at the whole MatchData object. This can be useful when using named captures:

case foo
when /^(?<number>[0-9][0-9])/
print "the month is #{$LAST_MATCH_INFO['number']}"
else
print "something else"
end

How to change case of letters in string using RegEx in Ruby

@sawa Has the simple answer, and you've edited your question with another mechanism. However, to answer two of your questions:

Is there a way to do this within the regex though?

No, Ruby's regex does not support a case-changing feature as some other regex flavors do. You can "prove" this to yourself by reviewing the official Ruby regex docs for 1.9 and 2.0 and searching for the word "case":

  • https://github.com/ruby/ruby/blob/ruby_1_9_3/doc/re.rdoc
  • https://github.com/ruby/ruby/blob/ruby_2_0_0/doc/re.rdoc

I don't really understand the '\1' '\2' thing. Is that backreferencing? How does that work?

Your use of \1 is a kind of backreference. A backreference can be when you use \1 and such in the search pattern. For example, the regular expression /f(.)\1/ will find the letter f, followed by any character, followed by that same character (e.g. "foo" or "f!!").

In this case, within a replacement string passed to a method like String#gsub, the backreference does refer to the previous capture. From the docs:

"If replacement is a String it will be substituted for the matched text. It may contain back-references to the pattern’s capture groups of the form \d, where d is a group number, or \k<n>, where n is a group name. If it is a double-quoted string, both back-references must be preceded by an additional backslash."

In practice, this means:

"hello world".gsub( /([aeiou])/, '_\1_' )  #=> "h_e_ll_o_ w_o_rld"
"hello world".gsub( /([aeiou])/, "_\1_" ) #=> "h_\u0001_ll_\u0001_ w_\u0001_rld"
"hello world".gsub( /([aeiou])/, "_\\1_" ) #=> "h_e_ll_o_ w_o_rld"

Now, you have to understand when code runs. In your original code…

string.gsub!(/([a-z])([A-Z]+ )/, '\1'.upcase)

…what you are doing is calling upcase on the string '\1' (which has no effect) and then calling the gsub! method, passing in a regex and a string as parameters.

Finally, another way to achieve this same goal is with the block form like so:

# Take your pick of which you prefer:
string.gsub!(/([a-z])([A-Z]+ )/){ $1.upcase << $2.downcase }
string.gsub!(/([a-z])([A-Z]+ )/){ [$1.upcase,$2.downcase].join }
string.gsub!(/([a-z])([A-Z]+ )/){ "#{$1.upcase}#{$2.downcase}" }

In the block form of gsub the captured patterns are set to the global variables $1, $2, etc. and you can use those to construct the replacement string.

Using regex in Ruby/Capybara case statement throws 'Regex matched 0 arguments' error

Your block is taking a parameter page_name but your Regex doesn't have a capture group in it to be used as that parameter. You probably want something like

Given /^I am on the (.+) page$/ do |page_name|

so the .+ portion is captured and passed through as page_name

How to write a case statement in Ruby with multiple statements inside a when clause?

Your question seems to be "how can I put all those statements on one line to have fewer lines".

Generally, you can use ";" in ruby to replace the End-of-Lines. Thus:

case selection
when 1; system "clear"; view_all_entries; main_menu
...

Or

case selection
when 1 then system "clear"; view_all_entries; main_menu
...

Using the ";" in any way is very much not ruby-like and not recommended. See below for a much nicer refactoring which strips the duplicated code.

Implementing the 'case' statement in order to match multiple 'when' conditions

Ruby doesn't have any sort of fall-through for case.

One alternative be a series of if statements using the === method, which is what case uses internally to compare the items.

has_matched? = false

if '2' === var
has_matched? = true
# stuff
end

if '3' === var
has_matched? = true
# other stuff
end

if something_else === var
has_matched? = true
# presumably some even more exciting stuff
end

if !has_matched?
# more stuff
end

This has two glaring issues.

  1. It isn't very DRY: has_matched? = true is littered all over the place.

  2. You always need to remember to place var on the right-hand-side of ===, because that's what case does behind the scenes.

You could create your own class with a matches? method that encapsulates this functionality. It could have a constructor that takes the value you'll be matching against (in this case, var), and it could have an else_do method that only executes its block if its internal @has_matched? instance variable is still false.

Edit:

The === method can mean anything you want it to mean. Generally, it's a more "forgiving" way to test equivalency between two objects. Here's an example from this this page:

class String
def ===(other_str)
self.strip[0, other_str.length].downcase == other_str.downcase
end
end

class Array
def ===(str)
self.any? {|elem| elem.include?(str)}
end
end

class Fixnum
def ===(str)
self == str.to_i
end
end

Essentially, when Ruby encounters case var, it will call === on the objects against which you are comparing var.

Ruby regex - using optional named backreferences


Problem

The reason it does not match the second line is because the second instance of hat does not end with a slash, but the first instance does.

Solution

Specify that there is a slash between the first and second match

Regex

(top_.*)/(\1.*$)|(^.*$)

Replacement

hier = \2\3

Example

Regex101 Permalink


More info on the Alternation token

To explain how the | token works in regex, see the example: abc|def

What this regex means in plain english is:

  • Match either the regex below (attempting the next alternative only if this one fails)
    • Match the characters abc literally
  • Or match the regex below (the entire match attempt fails if this one fails to match)
    • Match the characters def literally

Example

Regex: alpha|alphabet

If we had a phrase "I know the alphabet", only the word alpha would be matched.

However, if we changed the regex to alphabet|alpha, we would match alphabet.

So you can see, alternation works in a left-to-right fashion.

Write a better switch-case function?

If you wanted to use a hash (as per your question) you could do:

def readable_status(status)
readable = { "1" => "go", "2" => "stop", "3" => "die" }
readable[status] || "default value"
end

Using a case statement to assign multiple variable in ruby

Your return values in the case statement are not accepted by the ruby engine. I think you want to return an array... using the [] perhaps?

Like this:

def hsv_to_rgb(h, s, v)
if (h == 0) then return 0, 0, 0 end
c = v * s
hp = h / 60.0
x = c * (1 - (hp % 2 - 1).abs)
r, g, b = case hp
when 0..1
[c, x, 0]
when 1..2
[x, c, 0]
when 2..3
[0, c, x]
when 3..4
[0, x, c]
when 4..5
[x, 0, c]
else
[c, 0, x]
end

m = v - c
return r + m, g + m, b + m
end


Related Topics



Leave a reply



Submit