Ruby select method selecting values that do not meet criteria - ruby

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"

Related

About the method "select" and "join"

so I posted a question earlier about displaying factors of non-prime numbers and got this as a solution.
Below is a part of the code, but I have a little bit trouble understanding a few terms in them (since I am relatively new to ruby).
def factors(n)
(1..n/2).select{|e| (n%e).zero?}.push(n)
end
For instance,
What does the method select do in general?
What does .zero? do?
Then,
puts "#{n} is not a prime number =>#{factors(n).join(',')}"
What does .join(',') do?
It would be greatly appreciated if anyone can explain these terms to me in basic terms or simple concepts.
select method filters a collection. You specify the condition (the predicate) in the block and select returns items who match that condition.
[1,2,3,4].select(&:even?)
=> [2, 4]
i.zero? is another way to write i == 0
[0,2,3,0].select(&:zero?)
=> [0, 0]
join method concatenate a collection as a string with the parameter as a separators between items
[0,2,3,0].select(&:zero?).join(' and ')
=> "0 and 0"
Note
[1,2,3].select(&:even?)
is a simpler way to write
[1,2,3].select { |item| item.even? }
i will try explain by following links and text from documentation
join()
Returns a string created by converting each element of the array to a string, separated by the given separator. If the separator is nil, it uses current $,. If both the separator and $, are nil, it uses empty string.
[ "a", "b", "c" ].join #=> "abc"
[ "a", "b", "c" ].join("-") #=> "a-b-c"
select
Returns a new array containing all elements of ary for which the given block returns a true value.
If no block is given, an Enumerator is returned instead.
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
a = %w{ a b c d e f }
a.select { |v| v =~ /[aeiou]/ } #=> ["a", "e"]
zero?
zero? → true or false
Returns true if num has a zero value.
Ruby html documentation: ruby-doc.org

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.

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

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.

How do `Hash#reject!` and `Hash#reject` differ from `Hash#delete_if`?

How do reject! and reject differ from delete_if for a Hash in Ruby? Can anyone explain the differences between them with simple code snippets?
Since the other answers are referring to Array#delete_if and not Hash#delete_if, which seems to be what you are asking, I thought I should clarify.
As others have pointed out, reject and reject! differ in that reject! version modifies the hash in-place, while reject creates a new hash. Meanwhile delete_if is almost the same as reject!.
In fact, for an Array, reject! and delete_if are exactly the same.
However, for a Hash, they are slightly different. reject! returns nil if no changes were made, or the hash if changes were made. delete_if always returns the hash.
hash = {a: 1, b: 2, c: 3}
return_value = hash.delete_if {|k, v| v > 100}
# hash is unchanged, return_value is {a: 1, b: 2, c: 3}
return_value = hash.reject! {|k, v| v > 100}
# hash is unchanged, return_value is nil
So if you wanted to check whether changes were made to the hash after deleting the elements, you could use reject! and check the return value.
If you read the docs it tells you that reject! is "Equivalent to Array#delete_if"
reject and reject! differ in that the bang (reject!) causes the changes to happen directly on the array you're working with, whereas reject will leave the array you're working with untouched, but will return a new array.
a = [ "a", "b", "c" ]
b = a.reject {|x| x >= "b" } #=> a is untouched, but b is ["a"]
a.reject! {|x| x >= "b" } #=> a is now modified and is ["a"]
I suppose you could read this from the docs:
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-reject-21 :
Equivalent to Array#delete_if, deleting elements from self for which the block evaluates to true, but returns nil if no changes were made. The array is changed instantly every time the block is called and not after the iteration is over. See also Enumerable#reject and Array#delete_if.
If no block is given, an enumerator is returned instead.
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-reject:
Returns a new array containing the items in self for which the block is not true. See also Array#delete_if
If no block is given, an enumerator is returned instead.

How to uniq an array case insensitive

As far as i know, the result of
["a", "A"].uniq
is
["a", "A"]
My question is:
How do I make ["a", "A"].uniq give me either ["a"] or ["A"]
There is another way you can do this. You can actually pass a block to uniq or uniq! that can be used to evaluate each element.
["A", "a"].uniq { |elem| elem.downcase } #=> ["A"]
or
["A", "a"].uniq { |elem| elem.upcase } #=> ["A"]
In this case though, everything will be case insensitive so it will always return the array ["A"]
Just make the case consistent first.
e.g:
["a","A"].map{|i| i.downcase}.uniq
Edit: If as mikej suggests, the elements returned must be exactly the same as in the original array, then this will do that for you:
a.inject([]) { |result,h| result << h unless result.map{|i| i.downcase}.include?(h.downcase); result }
Edit2 Solution which should satisfy mikej :-)
downcased = []
a.inject([]) { |result,h|
unless downcased.include?(h.downcase);
result << h
downcased << h.downcase
end;
result}
you may build a mapping (Hash) between the case-normalized (e.g. downcased) values and the actual value and then take just the values from the hash:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element ; h }\
.values
selects the last occurrence of a given word (case insensitive):
["A", "b", "C"]
if you want the first occurrence:
["a", "b", "A", "C"]\
.inject(Hash.new){ |h,element| h[element.downcase] = element unless h[element.downcase] ; h }\
.values
["a", "A"].map{|x| x.downcase}.uniq
=> ["a"]
or
["a", "A"].map{|x| x.upcase}.uniq
=> ["A"]
If you are using ActiveSupport, you can use uniq_by.
It doesn't affect the case of the final output.
['A','a'].uniq_by(&:downcase) # => ['A']
A bit more efficient and way is to make use of uniq keys in hashes, so check this:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] = j; hash}.values
will return the last element, in this case
["A"]
whereas using ||= as assign operator:
["a", "A"].inject(Hash.new){ |hash,j| hash[j.upcase] ||= j; hash}.values
will return first element, in this case
["a"]
especially for big Arrays this should be faster as we don't search the array each time using include?
cheers...
A more general solution (though not the most efficient):
class EqualityWrapper
attr_reader :obj
def initialize(obj, eq, hash)
#obj = obj
#eq = eq
#hash = hash
end
def ==(other)
#eq[#obj, other.obj]
end
alias :eql? :==
def hash
#hash[#obj]
end
end
class Array
def uniq_by(eq, hash = lambda{|x| 0 })
map {|x| EqualityWrapper.new(x, eq, hash) }.
uniq.
map {|x| x.obj }
end
def uniq_ci
eq = lambda{|x, y| x.casecmp(y) == 0 }
hash = lambda{|x| x.downcase.hash }
uniq_by(eq, hash)
end
end
The uniq_by method takes a lambda that checks the equality, and a lambda that returns a hash, and removes duplicate objects as defined by those data.
Implemented on top of that, the uniq_ci method removes string duplicates using case insensitive comparisons.

Resources