3

In many other programming languages, there is a function which takes as a parameter a regular expression and returns an array of string values. This is true of Javascript and Ruby. The .match in crystal, however, does 1) not seem to accept the global flag and 2) it does not return an array but rather a struct of type Regex::MatchData. (https://crystal-lang.org/api/0.25.1/Regex/MatchData.html)

As an example the following code:

str = "Happy days"
re = /[a-z]+/i
matches = str.match(re)
puts matches

returns Regex::MatchData("Happy")

I am unsure how to convert this result into a string or why this is not the default as it is in the inspiration language (Ruby). I understand this question probably results from my inexperience dealing with structs and compiled languages but I would appreciate an answer in hopes that it might also help someone else coming from a JS/Ruby background.

Shadow43375
  • 514
  • 7
  • 20
  • It seems you want `matches.string`, but with [`scan`](https://crystal-lang.org/api/0.20.1/String.html#scan%28pattern%3ARegex%29-instance-method), you will get all matches. – Wiktor Stribiżew Jul 18 '18 at 00:51

3 Answers3

6

What if I want to convert to a string merely the first match?

puts "Happy days"[/[a-z]+/i]?
puts "Happy days".match(/[a-z]+/i).try &.[0]

It will try to match a string against /[a-z]+/i regex and if there is a match, Group 0, i.e. the whole match, will be output. Note that the ? after [...] will make it fail gracefully if there is no match found. If you just use puts "??!!"[/[a-z]+/i], an exception will be thrown.

See this online demo.

If you want the functionality similar to String#scan that returns all matches found in the input, you may use (shortened version only left as per @Amadan's remark):

str = "Happy days"
re = /[a-z]+/i
matches = [] of String
str.scan(re) do |match|
      matches << match[0]
    end
puts matches

Output of the code above:

["Happy", "days"]

Note that String::scan will return an array of Regex::MatchData for each match. To get the text of the match, just access the first item in the match object.

A shorter way is to use

matches = str.scan(re).map(&.to_a).flatten

See the online demo.

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
1

Actually the posted example returns a #<MatchData "Happy"> in Ruby, which also has no "global" flag – thats what String#scan(Regex) is for as mentioned by others.

If you want only a single match without going through Regex::MatchData, you can use String#[](Regex):

str = "Happy days"
p str[/[a-z]+/i] # => "Happy"
felixbuenemann
  • 579
  • 3
  • 18
0

As mentioned in comment by @Shadow43375, the accepted answer only works if you need the first result.

If one want to obtain all matches found in the input you need to use the following code:

matches = str.scan(re).map{ |x| x[0] }

Output:

["Happy", "days"]

Wiktor Stribiżew was wrong Regex::MatchData.string doesn't return the matched string but return the original string.

As he demonstrated himself, the output of

matches = str.scan(re).map(&.string)

is

["Happy days", "Happy days"]

cf. https://crystal-lang.org/api/1.7.2/Regex/MatchData.html#string%3AString-instance-method

noraj
  • 3,964
  • 1
  • 30
  • 38