Ruby - hash to array - ruby

I have:
a = [{"_id"=>BSON::ObjectId('569a58b8e301d083c300000c')}]
and I want it to be:
[BSON::ObjectId('569a58bee301d083c3000752')]
I was experimenting with
a.map{|e| e.map{|k, v| v }}
but it gives me nested array:
[[BSON::ObjectId('569a58b8e301d083c300000c')]]
I'll appreciate any help.

If you just had a hash:
h = {"_id"=>BSON::ObjectId('569a58b8e301d083c300000c')}
and you wanted to fetch the BSON::ObectId, you would call Hash#[]:
h["_id"] #=> BSON::ObjectId('569a58b8e301d083c300000c')
The same works with map:
a.map { |h| h["_id"] }
#=> [BSON::ObjectId('569a58b8e301d083c300000c')]

A cleaner solution.
a = [{"id"=>"1234"},{"id"=>"9876"}]
a.flat_map(&:values)
=> ["1234", "9876"]

I'd use values, then call flatten.
a.collect(:&values).flatten
Or, if there'll only ever be one value
a[0].values[0]

Related

How to reduce a Ruby hash by a given array of keys?

I'm looking for a simple way to copy/reduce a hash, but only include the keys/values specified in an array of keys.
original_hash = { one: 1, two: 'too', three: 3 }
wanted_keys = [:one, :three]
new_hash = # do something with the hash
expect(new_hash).to eq({ one: 1, three: 3 })
If the hash has a very large number of keys and/or the array of wanted keys is very large (improbable as that may be),
original_hash.select { |k, v| wanted_keys.include?(k) }
would be relatively inefficient because a linear search of wanted_keys is required for each of original_hash's keys. Here are two ways to speed things up. (#Lucas' solution is a third way.)
Convert wanted_keys to a set
require 'set'
wanted_keys_set = wanted_keys.to_set
original_hash.select { |k, v| wanted_keys_set.include?(k) }
#=> {:one=>1, :three=>3}
Match wanted_keys with the values of those keys in original_hash and then convert the resulting array to a hash
wanted_keys.zip(original_hash.values_at(*wanted_keys)).to_h
#=> {:one=>1, :three=>3}
Prior to Ruby v2.0, when Array#to_h made its debut, this would be written
Hash[wanted_keys.zip(original_hash.values_at(*wanted_keys))]
this is for your spec passing :)
original_hash.slice(*wanted_keys)
If you happen to be using Rails, you could use Hash#slice
require "active_support/core_ext/hash"
original_hash = { one: 1, two: 'too', three: 3 }
wanted_keys = [:one, :three]
new_hash = original_hash.slice *wanted_keys
#=> {:one=>1, :three=>3}
Implementation of Hash#slice method is present in Active support core extensions code rails/activesupport/lib/active_support/core_ext/hash/slice.rb
if you dont wanna to iterate whole Hash, you can use each_with_object
original_hash = {one: 1, two: 'too', three: 3}
wanted_keys = [:one, :three]
# iterate only array of keys
new_hash = wanted_keys.each_with_object({}) do |key, exp|
exp[key] = original_hash[key] if original_hash[key]
end
You could try something like (inefficient solution below)
original_hash.select{|k,v| wanted_keys.include? k }
I'm not entirely up on my Ruby-foo so I'm not sure if this returns a list or a Hash.

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

Ruby: cleaner returns from loop iteration methods

I find that I frequently have methods that iterate through an enumerable in order to return a different enumerable or a hash. These methods almost always look like this simplistic example:
def build_hash(array)
hash = {}
array.each do |item|
hash[ item[:id] ]= item
end
hash
end
This approach works works, but I've often wondered if there's a cleaner way to do this, specifically without having to wrap the loop in a temporary object so that the return is correct.
Does anyone know of an improved and/or cleaner and/or faster way to do this, or is this pretty much the best way?
Here are a few ways, considering your specific example
arr = [{:id => 1, :name => :foo}, {:id => 2, :name => :bar}]
Hash[arr.map{ |o| [o[:id], o] }]
arr.each_with_object({}){ |o, h| h[o[:id]] = o }
arr.reduce({}){ |h, o| h[o[:id]] = o; h }
arr.reduce({}){ |h, o| h.merge o[:id] => o }
# each of these return the same Hash
# {1=>{:id=>1, :name=>:foo}, 2=>{:id=>2, :name=>:bar}}
Well in this case, you can use inject and do something like this :
def build_hash(array)
array.inject({}) { |init, item| init[item[:id]] = item; init }
end
{}.tap { |h| array.each { |a| h[a[:id]] = a } }
Here is also a way how to convert Array into Hash.
list_items = ["1", "Foo", "2", "Bar", "3" , "Baz"]
hss = Hash[*list_items]
parameters must be even, otherwise a fatal error is raised, because an odd
number of arguments can’t be mapped to a series of key/value pairs.
{"1"=>"Foo", "2"=>"Bar", "3"=>"Baz"}
You can use ActiveSupport's index_by.
Your example becomes trivial:
def build_hash(array)
array.index_by{|item| item[:id]}
end
There is no really great way to build a hash in Ruby currently, even in Ruby 2.0.
You can use Hash[], although I find that very ugly:
def build_hash(array)
Hash[array.map{|item| [item[:id], item]}]
end
If we can convince Matz, you could at least:
def build_hash(array)
array.map{|item| [item[:id], item]}.to_h
end
There are other requests for new ways to create hashes.

Collect a hash of arrays in ruby

h = {1=>[1,2,3], 2=>[4,5,6]}
new_arr = []
h.each_value {|arr|
new_arr.concat(arr)
}
This works, but what's a more ruby-like way to do it?
All values are arrays but the elements of each array should not be modified.
How's about this?
h.values.flatten
You can use reduce:
h.values.reduce(&:+)
Slightly cryptic
h.flat_map(&:last)
Slightly verbose
h.flat_map{|_, value| value}
If you want to get the array of hash value, use Hash#values.
new_arr = h.values

How can I reject values from one array based on a parallel array?

Given
number_strings = ["ichi", "ni", "san", "ku"]
fixnums = [1, 2, 3, 9]
how would I get a list of number_strings where the corresponding fixnum is not even?
number_strings.reject.each_with_index do |string, index|
fixnums.fetch(index).even?
end
works, as does
pairs = number_strings.zip(fixnums)
pairs.reject{|_, fixnum| fixnum.even?}.map(&:first)
but both are a bit verbose.
I think they all come out quite verbose really.
Hash[number_strings.zip(fixnums)].select { |k, v| v.odd? }.keys
I actually think your solutions are fine, the only alternative I can come up with is this and that's as verbose:
number_strings.zip(fixnums).map { |str, fn| str if fn.odd? }.compact
Use fix and select:
fixnums.zip(number_strings).select { |k, v| k.odd? }.map(&:last)
=> ["ichi", "san", "ku"]
In Ruby 1.9 you can do:
number_strings = ["ichi", "ni", "san", "ku"]
fixnums = [1, 2, 3, 9]
enum = fixnums.each
number_strings.select{ enum.next.odd? }
You are right to think it's a bit verbose, and that's because Ruby has no syntax for list-comprehensions. A simple abstracion like Enumerable#comprehend -if not as beautiful- comes pretty close (comprehend = map+compact):
number_strings.zip(fixnums).comprehend { |s, n| s if n.odd? }

Resources