Ruby bad array to Hash - ruby

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

Related

Looking to convert information from a file into a hash Ruby

Hello I have been doing some research for sometime on this particular project I have been working on and I am at a loss. What I am looking to do is use information from a file and convert that to a hash using some of those components for my key. Within the file I have:1,Foo,20,Smith,40,John,55
An example of what I am looking for I am looking for an output like so {1 =>[Foo,20], 2 =>[Smith,40] 3 => [John,55]}
Here is what I got.
h = {}
people_file = File.open("people.txt") # I am only looking to read here.
until people_file.eof?
i = products_file.gets.chomp.split(",")
end
people_file.close
FName = 'test'
str = "1,Foo,20,Smith, 40,John,55"
File.write(FName, str)
#=> 26
base, *arr = File.read(FName).
split(/\s*,\s*/)
enum = (base.to_i).step
arr.each_slice(2).
with_object({}) {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"],
# 3=>["John", "55"]}
The steps are as follows.
s = File.read(FName)
#=> "1,Foo,20,Smith, 40,John,55"
base, *arr = s.split(/\s*,\s*/)
#=> ["1", "Foo", "20", "Smith", "40", "John", "55"]
base
#=> "1"
arr
#=> ["Foo", "20", "Smith", "40", "John", "55"]
a = base.to_i
#=> 1
I assume the keys are to be sequential integers beginning with a #=> 1.
enum = a.step
#=> (1.step)
enum.next
#=> 1
enum.next
#=> 2
enum.next
#=> 3
Continuing,
enum = a.step
b = arr.each_slice(2)
#=> #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]:each_slice(2)>
Note I needed to redefine enum (or execute enum.rewind) to reinitialize it. We can see the elements that will be generated by this enumerator by converting it to an array.
b.to_a
#=> [["Foo", "20"], ["Smith", "40"], ["John", "55"]]
Continuing,
c = b.with_object({})
#=> #<Enumerator: #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]
# :each_slice(2)>:with_object({})>
c.to_a
#=> [[["Foo", "20"], {}], [["Smith", "40"], {}], [["John", "55"], {}]]
The now-empty hashes will be constructed as calculations progress.
c.each {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"], 3=>["John", "55"]}
To see how the last step is performed, each initially directs the enumerator c to generate the first value, which it passes to the block. The block variables are assigned to that value, and the block calculation is performed.
enum = a.step
b = arr.each_slice(2)
c = b.with_object({})
pair, h = c.next
#=> [["Foo", "20"], {}]
pair
#=> ["Foo", "20"]
h #=> {}
h[enum.next]=pair
#=> ["Foo", "20"]
Now,
h#=> {1=>["Foo", "20"]}
The calculations are similar for the remaining two elements generated by the enumerator c.
See IO::write, IO::read, Numeric#step, Enumerable#each_slice, Enumerator#with_object, Enumerator#next and Enumerator#rewind. write and read respond to File because File is a subclass of IO (File.superclass #=> IO). split's argument, the regular expression, /\s*,\s*/, causes the string to be split on commas together with any spaces that surround the commas. Converting [["Foo", "20"], {}] to pair and h is a product of Array Decompostion.

Searching through two multidimensional arrays and grouping together similar subarrays

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.

Only push specific substring to array in Ruby

I have an array that I am looping through and pushing specific values to a separate array. EX:
first_array = ["Promoter: 8", "Passive: 7"]
I want to push every value that is an integer to a separate array, that would look like this in the end:
final_array = [8,7]
It would be nice for the values in the new array to be integers. I can't think of a way to push all numeric values within a string to a new array, but what would be the best option to do what I am wanting?
first_array.map{|s| s[/\d+/].to_i}
# => [8, 7]
first_array.map{|a| a.match(/\d+/)}.compact.map{|a| a[0].to_i }
Use a regex to grab the integers,
compact the blank spaces from the strings with no integers, and
convert them all to ints
And I have to add this super short but complicated one-liner solution:
a = ["Promoter: 8", "Passive: 7"]
p a.grep(/(\d+)/){$&.to_i} #=> [8,7]
Your question, as formulated, has an easy practical answer, already provided by others. But it seems to me, that your array of strings
a = ["Promoter: 8", "Passive: 7"]
envies being a Hash. So, from broader perspective, I would take freedom of converting it to a Hash first:
require 'pyper' # (type "gem install pyper" in your command line to install it)
hsh = Hash[ a.τBmm2dτ &/(\w+): *(\d+)/.method( :match ) ]
#=> {"Promoter"=>"8", "Passive"=>"7"}
# (The construction of #τBmm2dτ Pyper method will be explained in the appendix.)
Now, having your input data in a hash, you can do things with them more easily, eg.
hsh.τmbtiτ
#=> [8, 7]
APPENDIX: Explanation of the Pyper methods.
Pyper methods are similar to Lisp #car/#cdr methods in that, that a combination of
letters controls the method behavior. In the first method, #τBmm2dτ:
τ - opening and ending character
m - means #map
B - means take a block
2 - means first 3 elements of an array
d - means all elements except the first one (same meaning as in #cdr, btw.)
So, in #τBmm2dτ, Bm applies the block as follows:
x = ["Promoter: 8", "Passive: 7"].map &/(\w+): *(\d+)/.method( :match )
#=> [#<MatchData "Promoter: 8" 1:"Promoter" 2:"8">, #<MatchData "Passive: 7" 1:"Passive" 2:"7">]
# Which results in an array of 2 MatchData objects.
Then, m2d chars map (m) the MatchData objects using 2 and d chars. Character 2 gives
x = x.map { |e| e.to_a.take 3 }
#=> [["Promoter: 8", "Promoter", "8"], ["Passive: 7", "Passive", "7"]]
and d removes the first element from each:
x = x.map { |e| e.drop 1 }
#=> [["Promoter", "8"], ["Passive", "7"]]
In the secon method, #τmbtiτ, m means again #map, b means take the second element, and ti means convert it to Integer:
{"Promoter"=>"8", "Passive"=>"7"}.to_a.map { |e| Integer e[1] }
#=> [8, 7]
If the integer part of each string in (which look like members of a hash) is always preceded by at least one space, and there is no other whitespace (other than possibly at the beginning of the string), you could do this:
first_array = ["Promoter: 8", "Passive: 7"]
Hash[*first_array.map(&:split).flatten].values.map(&:to_i) # => [8,7]
map first_array => [["Promoter:", "8"], ["Passive:", "7"]]
flatten => ["Promoter:", "8", "Passive:", "7"]
convert to hash => {"Promoter:" => "8", "Passive:" => "7"}
get hash values => ["8", "7"]
convert to ints => [8, 7]
Note the need for the splat:
Hash[*["Promoter:", "8", "Passive:", "7"]]
=> Hash["Promoter:", "8", "Passive:", "7"]
=> {"Promoter:" => "8", "Passive:" => "7"}

Array sort not sorting properly

I'm having a problem sorting an array. My expected result is:
#list = [
["2 1/8\"", "23 13/32\"", "stile", "2"],
["2 1/8\"", "11 5/32\"", "rail", "6"],
["2 1/8\"", "7 13/32\"", "stile", "4"]
]
This is just an example since the array is dynamic and data are always changing, but the one thing that is constant is that most if not all single digit measurements end up at the beginning.
And here's the code I'm using:
#list = #list.sort {|a,b| b[1]<=>a[1]}
Here's the actual output from the code.
#list
# => [
# ["2 1/8\"", "7 13/32\"", "stile", "2"],
# ["2 1/8\"", "23 13/32\"", "rail", "6"],
# ["2 1/8\"", "11 5/32\"", "stile", "4"]
# ]
Any ideas why?
If the goal is to sort by the first numeric value of the second value of each array (7, 23, 11) converting the string to integer in the sort could be a solution:
#list = #list.sort {|a,b| b[1].to_i <=> a[1].to_i }
You can also make it shorter by using sort!:
#list.sort! {|a,b| b[1].to_i <=> a[1].to_i }

Array to Hash Ruby

Convert this Array:
a = ["item 1", "item 2", "item 3", "item 4"]
...to a Hash:
{ "item 1" => "item 2", "item 3" => "item 4" }
i.e. elements at even indexes are keys and odd ones are values.
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }
That's it. The * is called the splat operator.
One caveat per #Mike Lewis (in the comments): "Be very careful with this. Ruby expands splats on the stack. If you do this with a large dataset, expect to blow out your stack."
So, for most general use cases this method is great, but use a different method if you want to do the conversion on lots of data. For example, #Łukasz Niemier (also in the comments) offers this method for large data sets:
h = Hash[a.each_slice(2).to_a]
Ruby 2.1.0 introduced a to_h method on Array that does what you require if your original array consists of arrays of key-value pairs: http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-to_h.
[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}
Just use Hash.[] with the values in the array. For example:
arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}
Or if you have an array of [key, value] arrays, you can do:
[[1, 2], [3, 4]].inject({}) do |r, s|
r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }
This is what I was looking for when googling this:
[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v }
=> {:a=>1, :b=>2}
Enumerator includes Enumerable. Since 2.1, Enumerable also has a method #to_h. That's why, we can write :-
a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}
Because #each_slice without block gives us Enumerator, and as per the above explanation, we can call the #to_h method on the Enumerator object.
You could try like this, for single array
irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
=> ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
=> {"item 1"=>"item 2", "item 3"=>"item 4"}
for array of array
irb(main):022:0> a = [[1, 2], [3, 4]]
=> [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
=> {1=>2, 3=>4}
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]
or, if you hate Hash[ ... ]:
a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end
or, if you are a lazy fan of broken functional programming:
h = a.lazy.each_slice( 2 ).tap { |a|
break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"
All answers assume the starting array is unique. OP did not specify how to handle arrays with duplicate entries, which result in duplicate keys.
Let's look at:
a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]
You will lose the item 1 => item 2 pair as it is overridden bij item 1 => item 5:
Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}
All of the methods, including the reduce(&:merge!) result in the same removal.
It could be that this is exactly what you expect, though. But in other cases, you probably want to get a result with an Array for value instead:
{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}
The naïve way would be to create a helper variable, a hash that has a default value, and then fill that in a loop:
result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}
It might be possible to use assoc and reduce to do above in one line, but that becomes much harder to reason about and read.

Resources