Strip in collect on splitted array in Ruby - ruby

The following code:
str = "1, hello,2"
puts str
arr = str.split(",")
puts arr.inspect
arr.collect { |x| x.strip! }
puts arr.inspect
produces the following result:
1, hello,2
["1", " hello", "2"]
["1", "hello", "2"]
This is as expected. The following code:
str = "1, hello,2"
puts str
arr = (str.split(",")).collect { |x| x.strip! }
puts arr.inspect
Does however produce the following output:
1, hello,2
[nil, "hello", nil]
Why do I get these "nil"? Why can't I do the .collect immediately on the splitted-array?
Thanks for the help!

The #collect method will return an array of the values returned by each block's call. In your first example, you're modifying the actual array contents with #strip! and use those, while you neglect the return value of #collect.
In the second case, you use the #collect result. Your problem is that #strip! will either return a string or nil, depending on its result – especially, it'll return nil if the string wasn't modified.
Therefore, use #strip (without the exclamation mark):
1.9.3-p194 :005 > (str.split(",")).collect { |x| x.strip }
=> ["1", "hello", "2"]

Because #strip! returns nil if the string was not altered.
In your early examples you were not using the result of #collect, just modifying the strings with #strip!. Using #each in that case would have made the non-functional imperative loop a bit more clear. One normally uses #map / #collect only when using the resulting new array.
You last approach looks good, you wrote a functional map but you left the #strip! in ... just take out the !.

Related

How to generate the expected output by using split method used in my code?

Question:
Create a method for Array that returns a hash having 'key' as length of the element and value as an array of all the elements of that length. Make use of Array#each.
Returned Hash should be sorted by key.
I have tried to do it through Hash sorting over length. I have almost resolved it using another method but I want to use split and hash to achieve expected output.
Can anyone suggest any amendments in my code below?
Input argument:
array-hash.rb "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
Expected output:
{1=>["x", "5"], 3=>["abc", "def", "234"], 4=>["1234", "abcd", "mnop", "zZzZ"]}
class String
def key_length(v2)
hash = {}
v2.each do |item|
item_length = item.to_s.length
hash[item_length] ||= []
hash[item_length].push(item)
end
Hash[hash.sort]
end
end
reader = ''
if ARGV.empty?
puts 'Please provide an input'
else
v1 = ARGV[0]
v2 = v1.tr("'[]''",'').split
p reader.key_length(v2)
end
Actual output:
{35=>["abc,def,1234,234,abcd,x,mnop,5,zZzZ"]}
Given the array (converted from string, note integers as string between ""):
ary = str[1..-2].delete('\'').split(',')
ary #=> ["abc", "def", "1234", "234", "abcd", "x", "mnop", "5", "zZzZ"]
The most "idiomatic" way should be using group_by:
ary.group_by(&:size)
If you want to use each, then you could use Enumerable#each_with_object, where the object is an Hash#new with an empty array as default:
ary.each_with_object(Hash.new{ |h,k| h[k] = []}) { |e, h| h[e.size] << e }
Which is the same as
res = Hash.new{ |h,k| h[k] = []}
ary.each { |e| res[e.size] << e }
Not sure why you need to monkeypatch* array here, is this a school exercise or something?
I think your bug is you need to pass in the comma delimiter arg to split.
I would solve the underlying problem as a reduce/inject/fold thing, myself.
s = "['abc','def',1234,234,'abcd','x','mnop',5,'zZzZ']"
splits = s.tr("'[]''",'').split(',') # need to pass in the comma for the split
Hash[splits.inject({}) { |memo,s| memo[s.length] ||= []; memo[s.length] << s; memo }.sort] # doesn't use Array.each but?
{1=>["x", "5"], 3=>["def", "234"], 4=>["1234", "abcd", "mnop"],
5=>["['abc"], 6=>["zZzZ']"]}

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 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"

Ruby using regex in select block

I've been having a lot of trouble sifting out regex matches. I could use scan, but since it only operates over a string, and I dont want to use a join on the array in question, it is much more tedious. I want to be able to do something like this:
array = ["a1d", "6dh","th3"].select{|x| x =~ /\d/}
# => ["1", "6", "3"}
However this never seems to work. Is there a work around or do I just need to use scan?
Try: Array#map
> array = ["a1d", "6dh","th3"].map {|x| x[/\d+/]}
#=> ["1", "6", "3"]
Note:
select
Returns a new array containing all elements of ary for which the given
block returns a true value.
In your case each element contains digit and it returns true, so you are getting original element via select. while map will perform action on each element and return new array with performed action on each element.
You can use grep with a block:
array = ["a1d", "6dh", "th3"]
array.grep(/(\d)/) { $1 }
#=> ["1", "6", "3"]
It passes each matching element to the block and returns an array containing the block's results.
$1 is a special global variable containing the first capture group.
Unlike map, only matching elements are returned:
array = ["a1d", "foo", "6dh", "bar", "th3"]
array.grep(/(\d)/) { $1 }
#=> ["1", "6", "3"]
array.map { |s| s[/\d/] }
#=> ["1", nil, "6", nil, "3"]
Depending on your requirements, you may wish to construct a hash.
arr = ["a1d", "6dh", "th3", "abc", "3for", "rg6", "def"]
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |str,h| h[str[/\d+/]] << str }
#=> {"1"=>["a1d"], "6"=>["6dh", "rg6"], "3"=>["th3", "3for"], nil=>["abc", "def"]}
Hash.new { |h,k| h[k] = [] } creates an empty hash with a default block, represented by the block variable h. That means that if the hash does not have a key k, the block is executed, adding the key value pair k=>[] to the hash, after which h[k] << k is executed.
The above is a condensed (and Ruby-like) way of writing the following.
h = {}
arr.each do |str|
s = str[/\d+/]
h[s] = [] unless h.key?(s)
h[s] << str
end
h
# => {"1"=>["a1d"], "6"=>["6dh", "rg6"], "3"=>["th3", "3for"], nil=>["abc", "def"]}
The expression in the third line could alternatively be written
arr.each_with_object({}) { |str,h| (h[str[/\d+/]] ||= []) << str }
h[str[/\d+/]] ||= [] sets h[str[/\d+/]] to an empty array if the hash h does not have a key str[/\d+/].
See Enumerable#each_with_object and Hash::new.
#Stefan suggests
arr.group_by { |str| str[/\d+/] }
#=> {"1"=>["a1d"], "6"=>["6dh", "rg6"], "3"=>["th3", "3for"], nil=>["abc", "def"]}
What can I say?

How to get index of value in anonymous array inside of iteration

I would like to be able to take an anonymous array, iterate through it and inside of the iterator block find out what the index is of the current element.
For instance, I am trying to output only every third element.
["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"].each do |elem|
if index_of(elem) % 3 == 0 then
puts elem
end
end
(where index_of is a nonexistent method being used as a placeholder here to demonstrate what I'm trying to do)
In theory the output should be:
foo
bang
Hello, Sailor!
This is pretty straightforward when I'm naming the array. But when it is anonymous, I can't very well refer to the array by name. I've tried using self.find_index(elem) as well as self.index(elem) but both fail with the error: NoMethodError: undefined method '(find_)index' for main:Object
What is the proper way to do this?
Use each_with_index:
arr = ["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"]
arr.each_with_index do |elem, index|
puts elem if index % 3 == 0
end
Another way:
arr = ["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"]
arr.each_slice(3) { |a| puts a.first }
#=> foo
# bang
# Hello, Sailor!

Resources