Related
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']"]}
I have data like:
str = "CODEA text for first item CODEB text for next item CODEB2 some"\
"more text CODEC yet more text"
and a list:
arr = ["CODEA", "CODEB", "CODEB2", "CODEC", ... ]
I want to divide this string into a hash. The keys of the hash will be CODEA, CODEB, etc. The values of the hash will be the text that follows, until the next CODE. The output should look like this:
"CODEA" => "text for first item",
"CODEB" => "text for next item",
"CODEB2" => "some more text",
"CODEC" => "yet more text"
We are given a sting and an array.
str = "CODEA text for first item CODEB text for next item " +
"CODEB2 some more text CODEC yet more text"
arr= %w|CODEC CODEB2 CODEA CODEB|
#=> ["CODEC", "CODEB2", "CODEA", "CODEB"]
This is one way to obtain the desired hash.
str.split.
slice_before { |word| arr.include?(word) }.
map { |word, *rest| [word, rest.join(' ')] }.
to_h
#=> {"CODEA" =>"text for first item",
# "CODEB" =>"text for next item",
# "CODEB2"=>"some more text",
# "CODEC" =>"yet more text"}
See Enumerable#slice_before.
The steps are as follows.
a = str.split
#=> ["CODEA", "text", "for", "first", "item", "CODEB",
# "text", "for", "next", "item", "CODEB2", "some",
# "more", "text", "CODEC", "yet", "more", "text"]
b = a.slice_before { |word| arr.include?(word) }
#=> #<Enumerator:
# #<Enumerator::Generator:0x00005cbdec2b5eb0>:each>
We can see the (4) elements (arrays) that will be generated by this enumerator and passed to each_with_object by converting it to an array.
b.to_a
#=> [["CODEA", "text", "for", "first", "item"],
# ["CODEB", "text", "for", "next", "item"],
# ["CODEB2", "some", "more", "text"],
# ["CODEC", "yet", "more", "text"]]
Continuing,
c = b.map { |word, *rest| [word, rest.join(' ')] }
#=> [["CODEA", ["text for first item"]],
# ["CODEB", ["text for next item"]],
# ["CODEB2", ["some more text"]],
# ["CODEC", ["yet more text"]]]
c.to_h
#=> {"CODEA"=>"text for first item",
# "CODEB"=>"text for next item",
# "CODEB2"=>"some more text",
# "CODEC"=>"yet more text"}
The following is perhaps a better way of doing this.
str.split.
slice_before { |word| arr.include?(word) }.
each_with_object({}) { |(word, *rest),h|
h[word] = rest.join(' ') }
When I was a kid this might be done as follows.
last_word = ''
str.split.each_with_object({}) do |word,h|
if arr.include?(word)
h[word]=''
last_word = word
else
h[last_word] << ' ' unless h[last_word].empty?
h[last_word] << word
end
end
last_word must be set to anything outside the block.
Code:
str = 'CODEA text for first item CODEB text for next item ' +
'CODEB2 some more text CODEC yet more text'
puts Hash[str.scan(/(CODE\S*) (.*?(?= CODE|$))/)]
Result:
{"CODEA"=>"text for first item", "CODEB"=>"text for next item", "CODEB2"=>"some more text", "CODEC"=>"yet more text"}
Another option.
string.split.reverse
.slice_when { |word| word.start_with? 'CODE' }
.map{ |(*v, k)| [k, v.reverse.join(' ')] }.to_h
Enumerator#slice_when, in this case returns this array:
[["text", "more", "yet", "CODEC"], ["text", "more", "some", "CODEB2"], ["item", "next", "for", "text", "CODEB"], ["item", "first", "for", "text", "CODEA"]]
Then the array is mapped to build the required hash to get the result (I did not reversed the Hash):
#=> {"CODEC"=>"yet more text", "CODEB2"=>"some more text", "CODEB"=>"text for next item", "CODEA"=>"text for first item"}
Adding parentheses to the pattern in String#split lets you get both the separators and the fields.
str.split(/(#{Regexp.union(*arr)})/).drop(1).each_slice(2).to_h
# =>
# {
# "CODEA"=>" text for first item ",
# "CODEB"=>"2 somemore text ",
# "CODEC"=>" yet more text"
# }
I am trying to search through two multidimensional arrays to find any elements in common in a given subarray and then put the results in a third array where the entire subarrays with similar elements are grouped together (not just the similar elements).
The data is imported from two CSVs:
require 'csv'
array = CSV.read('primary_csv.csv')
#=> [["account_num", "account_name", "primary_phone", "second_phone", "status],
#=> ["11111", "John Smith", "8675309", " ", "active"],
#=> ["11112", "Tina F.", "5551234", "5555678" , "disconnected"],
#=> ["11113", "Troy P.", "9874321", " ", "active"]]
# and so on...
second_array = CSV.read('customer_service.csv')
#=> [["date", "name", "agent", "call_length", "phone", "second_phone", "complaint"],
#=> ["3/1/15", "Mary ?", "Bob X", "5:00", "5551234", " ", "rude"],
#=> ["3/2/15", "Mrs. Smith", "Stew", "1:45", "9995678", "8675309" , "says shes not a customer"]]
# and so on...
If any number is present as an element in a subarray on both primary.csv and customer_service.csv, I want that entire subarray (as opposed to just the common elements), put into a third array, results_array. The desire output based upon the above sample is:
results_array = [["11111", "John Smith", "8675309", " ", "active"],
["3/2/15", "Mrs. Smith", "Stew", "1:45", "9995678", "8675309" , "says shes not a customer"]] # and so on...
I then want to export the array into a new CSV, where each subarray is its own row of the CSV. I intend to iterate over each subarray by joining it with a , to make it comma delimited and then put the results into a new CSV:
results_array.each do {|j| j.join(",")}
File.open("results.csv", "w") {|f| f.puts results_array}
#=> 11111,John Smith,8675309, ,active
#=> 3/2/15,Mrs. Smith,Stew,1:45,9995678,8675309,says shes not a customer
# and so on...
How can I achieve the desired output? I am aware that the final product will look messy because similar data (for example, phone number) will be in different columns. But I need to find a way to generally group the data together.
Suppose a1 and a2 are the two arrays (excluding header rows).
Code
def combine(a1, a2)
h2 = a2.each_with_index
.with_object(Hash.new { |h,k| h[k] = [] }) { |(arr,i),h|
arr.each { |e| es = e.strip; h[es] << i if number?(es) } }
a1.each_with_object([]) do |arr, b|
d = arr.each_with_object([]) do |str, d|
s = str.strip
d.concat(a2.values_at(*h2[s])) if number?(s) && h2.key?(s)
end
b << d.uniq.unshift(arr) if d.any?
end
end
def number?(str)
str =~ /^\d+$/
end
Example
Here is your example, modified somewhat:
a1 = [
["11111", "John Smith", "8675309", "", "active" ],
["11112", "Tina F.", "5551234", "5555678", "disconnected"],
["11113", "Troy P.", "9874321", "", "active" ]
]
a2 = [
["3/1/15", "Mary ?", "Bob X", "5:00", "5551234", "", "rude"],
["3/2/15", "Mrs. Smith", "Stew", "1:45", "9995678", "8675309", "surly"],
["3/7/15", "Cher", "Sonny", "7:45", "9874321", "8675309", "Hey Jude"]
]
combine(a1, a2)
#=> [[["11111", "John Smith", "8675309", "",
# "active"],
# ["3/2/15", "Mrs. Smith", "Stew", "1:45",
# "9995678", "8675309", "surly"],
# ["3/7/15", "Cher", "Sonny", "7:45",
# "9874321", "8675309", "Hey Jude"]
# ],
# [["11112", "Tina F.", "5551234", "5555678",
# "disconnected"],
# ["3/1/15", "Mary ?", "Bob X", "5:00",
# "5551234", "", "rude"]
# ],
# [["11113", "Troy P.", "9874321", "",
# "active"],
# ["3/7/15", "Cher", "Sonny", "7:45",
# "9874321", "8675309", "Hey Jude"]
# ]
# ]
Explanation
First, we define a helper:
def number?(str)
str =~ /^\d+$/
end
For example:
number?("8675309") #=> 0 ("truthy)
number?("3/1/15") #=> nil
Now index a2 on the values that represent numbers:
h2 = a2.each_with_index
.with_object(Hash.new { |h,k| h[k] = [] }) { |(arr,i),h|
arr.each { |e| es = e.strip; h[es] << i if number?(es) } }
#=> {"5551234"=>[0], "9995678"=>[1], "8675309"=>[1, 2], "9874321"=>[2]}
This says, for example, that the "numeric" field "8675309" is contained in elements at offsets 1 and 2 of a2 (i.e, for Mrs. Smith and Cher).
We can now simply run through the elements of a1 looking for matches.
The code:
arr.each_with_object([]) do |str, d|
s = str.strip
d.concat(a2.values_at(*h2[s])) if number?(s) && h2.key?(s)
end
steps through the elements of arr, assigning each to the block variable str. For example, if arr holds the first element of a1 str will in turn equals "11111", "John Smith", and so on. After s = str.strip, this says that if a s has a numerical representation and there is a matching key in h2, the (initially empty) array d is concatenated with the elements of a2 given by the value of h2[s].
After completing this loop we see if d contains any elements of a2:
b << d.uniq.unshift(arr) if d.any?
If it does, we remove duplicates, prepend the array with arr and save it to b.
Note that this allows one element of a2 to match multiple elements of a1.
I have a ugly array that looks like this
["advert 0", "[1404915231, 1404920520]", "advert 4", "[1404915231]", "advert 5", "[1404915231]", "advert 6", "[1404915231]", "advert 7", "[1404915231]", "advert 8", "[1404915231]", "advert 9", "[1404915231]"]
I've tried to get it to look like this but I have been unsuccessful.
{advert1: [1404915231, 1404920520], advert4: [1404915231]}
Thanks
Here is a way(if it helps you) :-
require 'yaml'
arr = [
"advert 0", "[1404915231, 1404920520]",
"advert 4", "[1404915231]", "advert 5",
"[1404915231]", "advert 6", "[1404915231]",
"advert 7", "[1404915231]", "advert 8",
"[1404915231]", "advert 9", "[1404915231]"
]
Hash[arr.each_slice(2).map { |a, b| [a.gsub(/\s+/,'').to_sym, YAML.load(b)] }]
# => {:advert0=>[1404915231, 1404920520],
# :advert4=>[1404915231],
# :advert5=>[1404915231],
# :advert6=>[1404915231],
# :advert7=>[1404915231],
# :advert8=>[1404915231],
# :advert9=>[1404915231]}
Enumerable#each_slice(2) - Is sending 2 items from the collection, to the Enumerable#map block. Now OP wants, string to be converted as symbols. Thus, I used first, to removes the white spaces in between strings. It is done by String#gsub method. I passed the regex, /\s+/ as an Argument to the method #gsub, which as per the regex, will find each white space and replace them with empty strings(''). That means
"advert 0".gsub(/\s+/,'') # => "advert0"
Now as OP wants, all keys to be symbols, I apply String#to_sym.
"advert0".to_sym # => :advert0
So finally, I need to covert all string arrays to array, thus YAML::load will be helpful.
YAML::load "[1404915231, 1404920520]" # => [1404915231, 1404920520]
Till now what I said, will give us -
arr.each_slice(2).map { |a, b| [a.gsub(/\s+/,'').to_sym, YAML.load(b)] }
# => [[:advert0, [1404915231, 1404920520]],
# [:advert4, [1404915231]],
# [:advert5, [1404915231]],
# [:advert6, [1404915231]],
# [:advert7, [1404915231]],
# [:advert8, [1404915231]],
# [:advert9, [1404915231]]]
Now, I will make it a Hash. Look this Hash[ key, value, ... ] → new_hash.
Hash[:a,[1],:b, [2,3]] # => {:a=>[1], :b=>[2, 3]}
Hope it helps.
Arup gives a great answer, but if you're looking for alternatives, here are some thoughts I had:
Break down the problem
Let's break down the problem. We have ugly_array, and we want to use its even elements ("advert 0", "advert 2") as keys in a Hash, and its odd elements as the corresponding values. We want to transform both the keys (into symbols) and the values (into arrays of numbers) on the way.
Create a (temporary) Hash
A useful method in Ruby is Hash[], which will create a Hash using its odd arguments as keys and even arguments as values. For example:
Hash[ :foo, 1, :bar, 2 ]
# => { :foo => 1, :bar => 2 }
In order to use the items in ugly_array as separate arguments, we put * ("splat") before it:
ugly_hash = Hash[ *ugly_array ]
This is equivalent to Hash[ "advert 0", "[1404915231, 1404920520]", "advert 4", ... ], and it gives us this Hash:
{ "advert 0" => "[1404915231, 1404920520]",
"advert 4" => "[1404915231]",
"advert 5" => "[1404915231]",
# ...
}
Transform the keys
Now we need to transform the keys and values. First, the keys. You want to turn the string "advert 0" into the symbol :advert0, which we can do like this:
"advert 0".gsub(" ", "").to_sym
# => :advert0
To clarify gsub(" ", "") replaces all spaces (" ") with nothing, which effectively removes them. We could also use a regular expression like /\s+/, but it looks like the extra flexibility isn't really needed here. Now we have the string "advert0", and to_sym converts it into the symbol :advert0.
Transform the values
We want to turn a string like "[1404915231, 1404920520]" into an array of numbers like [1404915231, 1404920520]. There are a lot of different ways to do this, but one way that doesn't require using a module like JSON or YAML is this:
"[1404915231, 1404920520]".scan(/\d+/).map(&:to_i)
# => [ 1404915231, 1404920520 ]
This does two things. First, scan(/\d+/) uses a regular expression to find sequences of consecutive digits (\d) in the string and returns them as an array, yielding an array of strings: ["1404915231", "1404920520"]. Then we use map(&:to_i) to call the to_i method on each of the strings, yielding an array of numbers.
Transform the temporary Hash
Now that we know how to transform the keys and values, we can take our temporary Hash and build a new, transformed Hash from it. There are other ways to do this, but I'm fond of Enumerable#each_with_object. Suppose we do this:
ugly_hash.each_with_object({}) do |(key, val), hsh|
hsh[key] = val
end
Inside the block, hsh is the new, empty Hash ({}) we gave as the argument to each_with_object, and in each iteration we add val to it with the key key. This yields:
{ "advert 0" => "[1404915231, 1404920520]",
"advert 4" => "[1404915231]",
# ...
}
It looks exactly the same! But you can probably see that since we have key and val inside the block, we can transform them before using them.
Bring it all together
pretty_hash = Hash[ *ugly_array ].each_with_object({}) do |(key, val), hsh|
key = key.tr(" ", "").to_sym
val = val.scan(/\d+/).map(&:to_i)
hsh[key] = val
end
As you can see, this is the same code, except inside the block we're transforming key and val before using them to add an element to the Hash. And it gives us just what we're looking for:
p pretty_hash
# => { :advert0 => [ 1404915231, 1404920520 ],
# :advert4 => [ 1404915231 ],
# :advert5 => [ 1404915231 ],
# ...
# }
Apart from being relatively readable and not requiring external libraries, this method has the distinct advantage of iterating over the original data exactly once and performing only one (composite) operation on each key and value.
If you're so inclined, you can reduce the inside of the block to a single line, but be aware of how it impacts readability:
pretty_hash = Hash[*ugly_array].each_with_object({}) do |(key, val), hsh|
hsh[ key.tr(" ", "").to_sym ] = val.scan(/\d+/).map(&:to_i)
end
I hope that's helpful!
I've assumed you want the result:
{ advert0: [1404915231, 1404920520], advert4: [1404915231] }
That is, I assume you meant advert0 where you wrote advert1; specifically, that you wish to select those pairs "advert x", arr (arr being an array) that correspond to given values of x (here 0 and 4) and convert the collection of those pairs to a hash. If this is not want you want to do, you need read no further.
You could do this as follows:
Code
def doit(arr, *vals_at)
arr.each_slice(2)
.map {|s1,s2|
[s1.split.last.to_i,[s1.tr(' ','').to_sym, s2.scan(/\d+/).map(&:to_i)]]}
.to_h
.values_at(*vals_at)
.to_h
end
Example
arr = ["advert 0", "[1404915231, 1404920520]",
"advert 4", "[1404915231]",
"advert 5", "[1404915231]"
]
(We don't need the rest of arr.)
doit(arr, 0, 4)
#=> {:advert0=>[1404915231, 1404920520], :advert4=>[1404915231]}
Explanation
For the value of arr above, let:
b = a.each_slice(2)
#=> #<Enumerator: ["advert 0", "[1404915231, 1404920520]",
# "advert 4", "[1404915231]",
# "advert 5", "[1404915231]"]:each_slice(2)>
c = b.map
#<Enumerator: #<Enumerator: ["advert 0", "[1404915231, 1404920520]",
# "advert 4", "[1404915231]",
# "advert 5", "[1404915231]"]:each_slice(2)>:map>
c may be thought of as a "compound" enumerator. When we add a block, each will pass each value of the following array into the block:
c.to_a
#=> [["advert 0", "[1404915231, 1404920520]"],
# ["advert 4", "[1404915231]"],
# ["advert 5", "[1404915231]"]]
Note
b.to_a == c.to_a #=> true
Next,
d = c.each { |s1,s2| [s1.split.last.to_i,
[s1.tr(' ','').to_sym, s2.scan(/\d+/).map(&:to_i)]] }
#=> [[0, [:advert0, [1404915231, 1404920520]]],
# [4, [:advert4, [1404915231]]],
# [5, [:advert5, [1404915231]]]]
Note that c.each is equivalent to b.map:
d == b.map { |s1,s2| [s1.split.last.to_i,
[s1.tr(' ','').to_sym, s2.scan(/\d+/).map(&:to_i)]] }
#=> true
Let's pause a moment to see what's going on here. The first value the enumerator c passes into the block (["advert 0", "[1404915231, 1404920520]"]) is split into the two block variables by disambiguation:
s1 #=> "advert 0"
s2 #=> "[1404915231, 1404920520]"
Then:
s1.split.last.to_i
#=> 0
r = s2.scan(/\d+/)
#=> ["1404915231", "1404920520"]
r.map(&:to_i)
#=> [1404915231, 1404920520]
Ergo, the first element passed into the block:
["advert 0", "[1404915231, 1404920520]"]
is mapped to:
[0,[1404915231, 1404920520]]
Similarly, the second and the third elements of c are respectively mapped to:
[4=>[:advert4, [1404915231]]
and
[5=>[:advert5, [1404915231]]
Now that we have d,
e = d.to_h # or Hash[d] for Ruby versions < 2.0
#=> {0=>[:advert0, [1404915231, 1404920520]],
# 4=>[:advert4, [1404915231]],
# 5=>[:advert5, [1404915231]]}
Aha! Now you can see why I constructed e as I did:
f = e.values_at(0,4)
#=> [[:advert0, [1404915231, 1404920520]], [:advert4, [1404915231]]]
f.to_h
#=> {:advert0=>[1404915231, 1404920520], :advert4=>[1404915231]}
I have a hash collection:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
What syntax would I use to replace the first occurrence of the key with hash collection value in a string?
eg my input string:
str = I want a 3
The resulting string would be:
str = I want a cat
My one liner:
hash.each { |k, v| str[k] &&= v }
or using String#sub! method:
hash.each { |k, v| str.sub!(k, v) }
"I want a %{b}" % {c: "apple", b: "bee", a: "cat"}
=> "I want a bee"
Assuming Ruby 1.9 or later:
str.gsub /\d/, my_hash
I didn't understand your problem, but you can try this:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
str = "I want a 3"
str.gsub(/[[:word:]]+/).each do |word|
my_hash[word] || word
end
#=> "I want a cat"
:D
Just to add point free style abuse to fl00r's answer:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
my_hash.default_proc = Proc.new {|hash, key| key}
str = "I want a 3"
str.gsub(/[[:word:]]+/).each(&my_hash.method(:[]))
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
str = "I want a 3"
If there isn't any general pattern for the strings you want to substitute, you can use:
str.sub /#{my_hash.keys.map { |s| Regexp.escape s }.join '|'}/, my_hash
But if there is one, the code becomes much simpler, e.g.:
str.sub /[0-9]+/, my_hash
If you want to substitute all the occurrences, not only the first one, use gsub.
You can use String.sub in ruby 1.9:
string.sub(key, hash[key])
The following code replace the first occurrence of the key with hash collection value in the given string str
str.gsub(/\w+/) { |m| my_hash.fetch(m,m)}
=> "I want a cat"