Ruby using regex in select block - ruby

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?

Related

how to convert special string data to hash in ruby?

Hi i have some data that system send to me alternative like this:
"Screw:1,Bound:5,Hing:3"
"Bound:5,Screw:3,Hing:1"
"Bound:2,Screw:2"
how can i make this Hash?
{"Screw"=>6 ,"Bound"=>12, "Hing"=>4}
its probably add other key and value later , i hop solve this for me.
arr = [
"Screw:1,Bound:5,Hing:3",
"Bound:5,Screw:3,Hing:1",
"Bound:2,Screw:2"
]
arr.flat_map { |s| s.split(',') }
.each_with_object(Hash.new(0)) do |s,h|
k, v = s.split(':')
h[k] += v.to_i
end
#=> {"Screw"=>6, "Bound"=>12, "Hing"=>4}
Step 1
arr.flat_map { |s| s.split(',') }
#=>["Screw:1", "Bound:5", "Hing:3", "Bound:5", "Screw:3", "Hing:1",
# "Bound:2", "Screw:2"]
See the form of Hash::new that takes an argument and no block. The argument is called the default value, which is here zero. If h has been defined h = Hash.new(0), and h does not have a key k, h[k] returns the default value (and does not modify the hash). h[k] += v.to_i expands to
h[k] = h[k] + v.to_i
so if h does not have a key k this becomes
h[k] = 0 + v.to_i
Alternatively, one could write the following.
arr.flat_map { |s| s.split(/:|,/) }
.each_slice(2)
.with_object(Hash.new(0)) { |(k,v),h| h[k] += v.to_i }
#=> {"Screw"=>6, "Bound"=>12, "Hing"=>4}
Steps 1 and 2
a = arr.flat_map { |s| s.split(/:|,/) }
#=> ["Screw", "1", "Bound", "5", "Hing", "3", "Bound", "5",
# "Screw", "3", "Hing", "1", "Bound", "2", "Screw", "2"]
e = a.each_slice(2)
#=> #<Enumerator: ["Screw", "1", "Bound", "5", "Hing", "3",
# "Bound", "5", "Screw", "3", "Hing", "1",
# "Bound", "2", "Screw", "2"]:each_slice(2)>
The elements generated by the enumerator e can be seen as follows:
e.entries
#=> [["Screw", "1"], ["Bound", "5"], ["Hing", "3"], ["Bound", "5"],
# ["Screw", "3"], ["Hing", "1"], ["Bound", "2"], ["Screw", "2"]]
A good way would be too loop through all of the entries and update the hash depending on the entries that get found.
The following will do it for you.
str = "Screw:1,Bound:5,Hing:3"
output = Hash.new(0)
str.split(",").each do |entry|
key = entry.split(":")
output[key[0]] += key[1].to_i
end
Just modify it so that it handles multiple strings correctly, depending on how they are fed to you in the system.
Looks like the data is CSV so I'd opt to use a CSV parser to avoid possible encoding issues
require 'csv'
def parse input
Hash[CSV.parse_line(input).map { |pair| pair.split(":") }]
end

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']"]}

Ruby Hash: type casting

I’m trying to get a better grasp on writing in Ruby and working with Hash tables and their values.
1. Say you have a hash:
‘FOO’= {‘baz’ => [1,2,3,4,5]}
Goal: convert each value into a string in the ‘Ruby’ way.
I’ve come across multiple examples of using .each eg.
FOO.each = { |k,v| FOO[k] = v.to_s }
However this renders an array encapsulated in a string. Eg. "[1,2,3,4,5]" where it should be ["1", "2", "3", "4", "5"].
2. When type casting is performed on a Hash that’s holds an array of values, is the result a new array? Or simply a change in type of value (eg. 1 becomes “1” when .to_s is applied (say the value was placed through a each enumerator like above).
An explanation is greatly appreciated. New to Ruby.
In the each block, k and v are the key value pair. In your case, 'baz' is key and [1,2,3,4,5] is value. Since you're doing v.to_s, it converts the whole array to string and not the individual values.
You can do something like this to achieve what you want.
foo = { 'baz' => [1,2,3,4,5] }
foo.each { |k, v| foo[k] = v.map(&:to_s) }
You can use Hash#transform_values:
foo = { 'baz' => [1, 2, 3, 4, 5] }
foo.transform_values { |v| v.map(&:to_s) } #=> {"baz"=>["1", "2", "3", "4", "5"]}

How to merge array index values and create a hash

I'm trying to convert an array into a hash by using some matching. Before converting the array into a hash, I want to merge the values like this
"Desc,X1XXSC,C,CCCC4524,xxxs,xswd"
and create a hash from it. The rule is that, first value of the array is the key in Hash, in array there are repeating keys, for those keys I need to merge values and place it under one key. "Desc:" are keys. My program looks like this.
p 'test sample application'
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
arr = Array.new
arr = str.split(":")
p arr
test_hash = Hash[*arr]
p test_hash
I could not find a way to figure it out. If any one can guide me, It will be thankful.
Functional approach with Facets:
require 'facets'
str.split(":").each_slice(2).map_by { |k, v| [k, v] }.mash { |k, vs| [k, vs.join] }
#=> {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
Not that you cannot do it without Facets, but it's longer because of some basic abstractions missing in the core:
Hash[str.split(":").each_slice(2).group_by(&:first).map { |k, gs| [k, gs.map(&:last).join] }]
#=> {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
A small variation on #Sergio Tulentsev's solution:
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
str.split(':').each_slice(2).each_with_object(Hash.new{""}){|(k,v),h| h[k] += v}
# => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
str.split(':') results in an array; there is no need for initializing with arr = Array.new
each_slice(2) feeds the elements of this array two by two to a block or to the method following it, like in this case.
each_with_object takes those two elements (as an array) and passes them on to a block, together with an object, specified by:
(Hash.new{""}) This object is an empty Hash with special behaviour: when a key is not found then it will respond with a value of "" (instead of the usual nil).
{|(k,v),h| h[k] += v} This is the block of code which does all the work. It takes the array with the two elements and deconstructs it into two strings, assigned to k and v; the special hash is assigned to h. h[k] asks the hash for the value of key "Desc". It responds with "", to which "X1" is added. This is repeated until all elements are processed.
I believe you're looking for each_slice and each_with_object here
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
hash = str.split(':').each_slice(2).each_with_object({}) do |(key, value), memo|
memo[key] ||= ''
memo[key] += value
end
hash # => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
Enumerable#slice_before is a good way to go.
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
a = ["Desc","C","xxxs"] # collect the keys in a separate collection.
str.split(":").slice_before(""){|i| a.include? i}
# => [["Desc", "X1"], ["C", "CCCC"], ["Desc", "XXSC"], ["xxxs", "xswd"], ["C", "4524"]]
hsh = str.split(":").slice_before(""){|i| a.include? i}.each_with_object(Hash.new("")) do |i,h|
h[i[0]] += i[1]
end
hsh
# => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}

Strip in collect on splitted array in 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 !.

Resources