What's the Ruby equivalent of map() for strings? - ruby

If you wanted to split a space-separated list of words, you would use
def words(text)
return text.split.map{|word| word.downcase}
end
similarly to Python's list comprehension:
words("get out of here")
which returns ["get", "out", "of", "here"]. How can I apply a block to every character in a string?

Use String#chars:
irb> "asdf".chars.map { |ch| ch.upcase }
=> ["A", "S", "D", "F"]

Are you looking for something like this?
class String
def map
size.times.with_object('') {|i,s| s << yield(self[i])}
end
end
"ABC".map {|c| c.downcase} #=> "abc"
"ABC".map(&:downcase) #=> "abc"
"abcdef".map {|c| (c.ord+1).chr} #=> "bcdefg"
"abcdef".map {|c| c*3} #=> "aaabbbcccdddeeefff"

I think the short answer to your question is "no, there's nothing like map for strings that operates a character at a time." Previous answerer had the cleanest solution in my book; simply create one by adding a function definition to the class.
BTW, there's also String#each_char which is an iterator across each character of a string. In this case String#chars gets you the same result because it returns an Array which also responds to each (or map), but I guess there may be cases where the distinction would be important.

Related

How to split on a character and keep value

I have a string:
"N8383"
I want to split on the character and maintain it to get:
["N", "8383"]
I tried the following:
"N8383".split(/[A-Z]/)
which gives me:
["", "8383"]
I want to match some more example strings like:
N344 344N S555 555S
String#split is a bad fit for this problem for the reasons others have stated. I would approach it like this, using String#scan instead:
str_parts = "N8383".scan(/[[:alpha:]]+/)
num_parts = "N8383".scan(/[[:digit:]]+/)
This will give you something to work with if the strings contain multiple string parts and/or multiple numeric parts.
This expression:
%w[N344 344N S555 555S].map do |str|
next str.scan(/[[:alpha:]]+/), str.scan(/[[:digit:]]+/)
end
Will return:
[
[["N"], ["344"]],
[["N"], ["344"]],
[["S"], ["555"]],
[["S"], ["555"]]
]
Although you are scanning each string twice, I think it's a better solution than 1. trying to come up with a complex regex that backtracks to return the parts in the right order, or 2. reprocessing the results to put the parts in the right order. Especially if the strings are as short as they are in the examples you've provided. That being said, if scanning each string twice really rankles you, here's another way to do it:
str_parts, num_parts = str.scan(/([[:alpha:]]+)|([[:digit:]]+)/).transpose.each(&:compact!)
Okay given the examples you could use the following regex
/(?=[A-Z])|(?<=[A-Z])/
This will look look ahead (?=) for a single character [A-Z] or look behind (?<=) for a single character [A-Z]. Since these are zero length assertions the split is placed between the characters rather than being the character. e.g.
%w{N8383 N344 344N S555 555S}.map {|s| s.split(/(?=[A-Z])|(?<=[A-Z])/) }
#=> [["N", "8383"], ["N", "344"], ["344", "N"], ["S", "555"], ["555", "S"]]
However this regex is specific to the given cases and does not offer any real deviation from the given cases e.g I have no idea of desired output for "N344S" but right now it will be ["N", "344" ,"S"] and worse yet "NSS344S" will be ["N", "S", "S", "344", "S"]
def doit(str)
str.scan(/\d+|\p{L}+/)
end
doit "N123" #=> ["N", "123"]
doit "123N" #=> ["123", "N"]
doit "N123M" #=> ["N", "123", "M"]
doit "N12M3P" #=> ["N", "12", "M", "3", "P"]
doit "123" #=> ["123"]
doit "NMN" #=> ["NMN"]
doit "" #=> []

Ruby: Is it true that #map generally doesn't make sense with bang methods?

This question was inspired by this one:
Ruby: Why does this way of using map throw an error?
Someone pointed out the following:
map doesn't make much sense when used with ! methods.
You should either:
use map with gsub
or use each with gsub!
Can someone explain why that is?
Base object
Here's an array with strings as element :
words = ['hello', 'world']
New array
If you want a new array with modified strings, you can use map with gsub :
new_words = words.map{|word| word.gsub('o','#') }
p new_words
#=> ["hell#", "w#rld"]
p words
#=> ["hello", "world"]
p new_words == words
#=> false
The original strings and the original array aren't modified.
Strings modified in place
If you want to modify the strings in place, you can use :
words.each{|word| word.gsub!('o','#') }
p words
#=> ["hell#", "w#rld"]
map and gsub!
new_words = words.map{|word| word.gsub!('o','#') }
p words
#=> ["hell#", "w#rld"]
p new_words
#=> ["hell#", "w#rld"]
p words == new_words
#=> true
p new_words.object_id
#=> 12704900
p words.object_id
#=> 12704920
Here, a new array is created, but the elements are the exact same ones!
It doesn't bring anything more than the previous examples. It creates a new Array for nothing. It also might confuse people reading your code by sending opposite signals :
gsub! will indicate that you want to modifiy existing objects
map will indicate that you don't want to modify existing objects.
Map is for building a new array without mutating the original. Each is for performing some action on each element of an array. Doing both at once is surprising.
>> arr = ["foo bar", "baz", "quux"]
=> ["foo bar", "baz", "quux"]
>> arr.map{|x| x.gsub!(' ', '-')}
=> ["foo-bar", nil, nil]
>> arr
=> ["foo-bar", "baz", "quux"]
Since !-methods generally have side effects (and only incidentally might return a value), each should be preferred to map when invoking a !-method.
An exception might be when you have a list of actions to perform. The method to perform the action might sensibly be named with a !, but you wish to collect the results in order to report which ones succeeded or failed.

Ruby select method selecting values that do not meet criteria

Have the following code which should select every other character of a string and make a new string out of them:
def bits(string)
string.chars.each_with_index.select {|m, index| m if index % 2 == 0}.join
end
However, select returns this output with test case "hello":
"h0l2o4"
When using map instead I get the desired result:
"hlo"
Is there a reason why select would not work in this case? In what scenarios would it be better to use map over select and vice versa
If you still want to use select, try this.
irb(main):005:0> "hello".chars.select.with_index {|m, index| m if index % 2 == 0}.join
=> "hlo"
each_with_index does not work because it is selecting both the character and the index and then joining all of that.
The reason that select does not work in this case is that select "Returns an array containing all elements of enum for which the given block returns a true value" (see the doc here), so what you get in your case is an array of arrays [['h',0],['l',2],['o',4]] which you then join to get "h0l2o4".
So select returns a subset of an enumerable. map returns a one to one mapping of the provided enumerable. For example the following would "fix" your problem by using map to extract character from each value returned by select.
def bits(string)
string.chars.each_with_index.select {|m, index| m if index % 2 == 0}.map { |pair| pair.first }.join
end
puts(bits "hello")
=> hlo
For lots of reasons this is not a good way to get every other character from a string however.
Here is another example using map. In this case each index is mapped to either the character or nil then joined.
def bits(string)
string.chars.each_index.map {|i| string[i] if i.even? }.join
end
If you use Enumerable#map, you will return an array having one element for each character in the string.
arr = "try this".each_char.map.with_index { |c,i| i.even? ? c : nil }
#=> ["t", nil, "y", nil, "t", nil, "i", nil]
which is the same as
arr = "try this".each_char.map.with_index { |c,i| c if i.even? }
#=> ["t", nil, "y", nil, "t", nil, "i", nil]
My initial answer suggested using Array#compact to remove the nils before joining:
arr.compact.join
#=> "tyti"
but as #npn notes, compact is not necessary because Array#join applies NilClass.to_s to the nil's, converting them to empty strings. Ergo, you may simply write
arr.join
#=> "tyti"
Another way you could use map is to first apply Enumerable#each_cons to pass pairs of characters and then return the first character of each pair:
"try this".each_char.each_cons(2).map(&:first).join
#=> "tyti"
Even so, Array#select is preferable, as it returns only the characters of interest:
"try this".each_char.select.with_index { |c,i| i.even? }.join
#=> "tyti"
A variant of this is:
even = [true, false].cycle
#=> #<Enumerator: [true, false]:cycle>
"try this".each_char.select { |c| even.next }.join
#=> "tyti"
which uses Array#cycle to create the enumerator and Enumerator#next to generate its elements.
One small thing: String#each_char is more memory-efficient than String#chars, as the former returns an enumerator whereas the latter creates a temporary array.
In general, when the receiver is an array,
use map when you want to return an array containing one element for each element of the receiver.
use Enumerable#find when you want to return just one element of the receiver.
use Array#select or Array#reject (or Enumerable#select or Enumerable#reject if the receiver is an enumerator).
Me, I'd use a simple regular expression:
"Now is the time to have fun.".scan(/(.)./).join
#=> "Nwi h iet aefn"

Rubyish way to invert a regular expression

Suppose a regex comes from calling code, outside of the current context, and then is passed on to another call implemented outside of the current project:
["1", "2"].grep(/1/) #=> ["1"]
Is there a simple, Rubyish way to achieve the following behavior when the call is being made?
["1", "2"].grep(/1/.negate) #=> ["2"]
This behavior is similar to switching the =~ operator with the !~ operator. It is possible to use #select or #reject, of course, or to open up or subclass Regexp. But I'm curious whether there is a way already available in Ruby to negate the matches returned by a regular expression in the manner above. Also, I don't care whether false or nil or true or the position of a match are involved in accomplishing this effect.
There is a theoretical question which is relevant but which goes beyond the simple considerations here.
EDIT: I get that iterators are the general way to go in Ruby for filtering a list, but people are overlooking the constraints of the question. Also, I think there is something nicely functional about the way the regex is being inverted. I don't see it as being overwrought or too-clever by half; it's plain-old object-oriented programming and the kind of thing that Ruby excels at doing.
["1", "2"].reject { |e| /1/ === e }
You can do something like this:
class NegatedRegex < Regexp
def ===(other)
!super
end
end
class Regexp
def negate
NegatedRegex.new self
end
end
There are probably other methods to reimplement, but for grep this is enough:
["1", "2"].grep(/1/.negate) #=> ["2"]
You can do them both in one go:
re = /1/
matches, non_matches = ["1", "2", "1", "3"].partition { |el| re =~ el }
p matches #=> ["1", "1"]
p non_matches #=> ["2", "3"]
this could be one way of doing this
["1", "2", 3].select {|i| i !~ /1/ }
=> ["2", 3]
Grep has a dark brother who does everything the same as grep, but vice versa.
["1", "2"].grep(/1/) #=> ["1"]
["1", "2"].grep_v(/1/) #=> ["2"]
arr=["1","2"]
arr-arr.grep("1") # ["2"]
:)

Ruby Hash/Array delete_if without a block

According to Ruby Hash/Array documentation, the delete_if method returns an enumerator if no block is given. How is this useful? Can someone give an example to demonstrate this pattern?
There are some methods defined on Enumerator that give flexibility to iterators. One such method I often use is with_index.
p %w[a b c d e f].delete_if.with_index{|_, i| i.even?}
# => ["b", "d", "f"]
If this was to be done without Enumerator class, all kinds of methods have to be defined, including delete_if_with_index, and that is not a good thing.
The enumerator will just allow you to run the block later. For example, if you had a method that specifically handled the delete if for several different objects, you could pass it the enumerator.
In the example below, it will print 1, 3, 5
arr = [0,1,2,3,4,5]
enumerator = arr.delete_if
enumerator.each { |el| el.even? }
puts arr.join(', ')

Resources