Mike F
Mike F

Reputation: 309

ruby regex scan vs .split method

I was trying to build a method that you take the first letter of every word and would capitalize it. I wrote it as

def titleize(name)
   name.scan(/\w+/) { |x| x.capitalize! }
end

and it just wouldn't work properly. It wouldn't capitalize and letters. I did some searching and found the answer here Capitalizing titles eventually. It was written as

def titleize(name)
   name.split(" ").each { |x| x.capitalize! }.join(" ")
end

How come my code didn't capitalize at all though? If I added a put statement and wrote it as

def titleize(name)
   name.scan(/\w+/) { |x| puts x.capitalize! }
end

It would output "hi there" with capitals but the => would still be just "hi there" What did I miss?

Upvotes: 0

Views: 3169

Answers (4)

Arup Rakshit
Arup Rakshit

Reputation: 118289

Corrected code:

def titleize(name)
   name.scan(/\w+/).each { |x| x.capitalize! }.join(' ')
end

p titleize("ayan roy") #=>"Ayan Roy"

Let's see why your one not worked:

def titleize(name)
   name.scan(/\w+/) 
end

p titleize("ayan roy") #=>["ayan", "roy"]

Now your line name.scan(/\w+/) { |x| x.capitalize! } , x is passed as "ayan", "roy". Now look at the below:

def titleize(name)
   name.scan(/\w+/) { |x| p x.capitalize!  } 
end

p titleize("ayan roy")

Output:

"Ayan"
"Roy"
"ayan roy"

As String#scan says:

scan(pattern) {|match, ...| block } → str - if block is given,scan will return the receiver on which it is called. Both forms iterate through str, matching the pattern (which may be a Regexp or a String). For each match, a result is generated and either added to the result array or passed to the block.

Upvotes: 2

Agis
Agis

Reputation: 33646

Your code doesn't work because #scan returns new String objects which are the results of the Regexp and passes them to the block. So in your method you essentially took these new objects, mutated them by calling #capitalize! but never used them anywhere afterwards.

You should do instead:

def titleize(name)
  name.scan(/\w+/).each { |x| x.capitalize! }.join(' ')
end

But this seems more readable to me:

def titleize2(name)
  name.split(' ').each { |w| w.capitalize! }.join(' ')
end

Note however these methods do not mutate the original argument passed.

Upvotes: 1

The block form of scan returns the original string, regardless of what you do in the block. (I think you may be able to alter the original string in the block by referring directly to it, but it's not recommended to alter the thing you're iterating over.) Instead, do your split variation, but instead of each, do collect followed by join:

name.split(" ").collect { |x| x.capitalize }.join(" ")

This works for titles containing numerals and punctuation, as well.

Upvotes: 0

dbenhur
dbenhur

Reputation: 20398

scan returns/yields new strings and will never modify the source string. Perhaps you want gsub.

def titleize(name)
  name.gsub(/\w+/) {|x| x.capitalize }
end

Or perhaps better to use a likely more correct implementation from the titleize gem.

Upvotes: 2

Related Questions