Reputation: 237
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
Upvotes: 0
Views: 925
Reputation: 110685
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 nil
s 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,
map
when you want to return an array containing one element for each element of the receiver.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"
Upvotes: 0
Reputation: 16748
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
Upvotes: 0
Reputation: 1733
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.
Upvotes: 1