Turning an array into a hash - arrays

I have a ruby array of hashes:
my_array = [{"apples" => 5}, {"oranges" => 12}]
I would like to turn it into a hash, where the hash keys are equal to the array index values +1, so like this:
my_hash = {"1"=>{"apples"=> 5}, "2"=>{"oranges" => 12}}
Any ideas?

You can also Enumerable#zip with a range, then convert Array#to_h:
(1..my_array.size).zip(my_array).to_h
#=> {1=>{"apples"=>5}, 2=>{"oranges"=>12}}
How it works
my_array.size #=> 2 returns the size of the Array as an Integer.
(1..my_array.size) it's an inclusive Range which enumerates integers form 1 to array size, 2 in this case.
A Range responds to Enumerable#zip, so, for example you can do this obtaining an Array of pairs:
(1..3).zip(:a..:c) #=> [[1, :a], [2, :b], [3, :c]]
Finally, an Array of pairs can be converted into an Hash, see Array#to_h:
[[1, :a], [2, :b], [3, :c]].to_h #=> {1=>:a, 2=>:b, 3=>:c}
Since the Range is made of integer, keys of the Hash are integer. But you can tweak the line of code to obtain strings as keys.

my_array = [{"apples" => 5}, {"oranges" => 12}]
my_hash = my_array.each_with_index.map{|h, i| [(i+1).to_s, h]}.to_h

You can try to use Enumerator#with_index to have index and Enumerator#each_with_object to create new hash
my_array = [{"apples"=> 5}, {"oranges" => 12}]
my_hash = my_array.each.with_index.with_object({}){|(hsh, i), e| e[(i+1).to_s] = hsh}
# => {"1"=>{"apples"=> 5}, "2"=>{"oranges" => 12}}

Related

Sort an array of arrays by the number of same occurencies in Ruby

This question is different from this one.
I have an array of arrays of AR items looking something like:
[[1,2,3], [4,5,6], [7,8,9], [7,8,9], [1,2,3], [7,8,9]]
I would like to sort it by number of same occurences of the second array:
[[7,8,9], [1,2,3], [4,5,6]]
My real data are more complexes, looking something like:
raw_data = {}
raw_data[:grapers] = []
suggested_data = {}
suggested_data[:grapers] = []
varietals = []
similar_vintage.varietals.each do |varietal|
# sub_array
varietals << Graper.new(:name => varietal.grape.name, :grape_id => varietal.grape_id, :percent => varietal.percent)
end
raw_data[:grapers] << varietals
So, I want to sort raw_data[:grapers] by the max occurrencies of each varietals array comparing this value: grape_id inside them.
When I need to sort a classical array of data by max occurencies I do that:
grapers_with_frequency = raw_data[:grapers].inject(Hash.new(0)) { |h,v| h[v] += 1; h }
suggested_data[:grapers] << raw_data[:grapers].max_by { |v| grapers_with_frequency[v] }
This code doesn't work cos there are sub arrays there, including AR models that I need to analyze.
Possible solution:
array.group_by(&:itself) # grouping
.sort_by {|k, v| -v.size } # sorting
.map(&:first) # optional step, depends on your real data
#=> [[7, 8, 9], [1, 2, 3], [4, 5, 6]]
I recommend you take a look at the Ruby documentation for the sort_by method. It allows you to sort an array using anything associated with the elements, rather than the values of the elements.
my_array.sort_by { |elem| -my_array.count(elem) }.uniq
=> [[7, 8, 9], [1, 2, 3], [4, 5, 6]]
This example sorts by the count of each element in the original array. This is preceded with a minus so that the elements with the highest count are first. The uniq is to only have one instance of each element in the final result.
You can include anything you like in the sort_by block.
As Ilya has pointed out, having my_array.count(elem) in each iteration will be costlier than using group_by beforehand. This may or may not be an issue for you.
arr = [[1,2,3], [4,5,6], [7,8,9], [7,8,9], [1,2,3], [7,8,9]]
arr.each_with_object(Hash.new(0)) { |a,h| h[a] += 1 }.
sort_by(&:last).
reverse.
map(&:first)
#=> [[7.8.9]. [1,2,3], [4,5,6]]
This uses the form of Hash::new that takes an argument (here 0) that is the hash's default value.

While converting Array of array in ruby to hash hash does not include all the keys but takes the last one

This is an array which i want to convert into a hash
a = [[1, 3], [3, 2], [1, 2]]
but the hash i am getting is
2.2.0 :004 > a.to_h
=> {1=>2, 3=>2}
why is it so?
Hashes have unique keys. Array#to_h is effectively doing the following:
h = {}.merge(1=>3).merge(3=>2).merge(1=>2)
#=> { 1=>3 }.merge(3=>2).merge(1=>2)
#=> { 1=>3, 3=>2 }.merge(1=>2)
#=> { 1=>2, 3=>2 }
In the last merge the value of the key 1 (3) is replaced with 2.
Note that
h.merge(k=>v)
is (permitted) shorthand for
h.merge({ k=>v })
The keys of a Hash are basically a Set, so no duplicate keys are allowed.
If two pairs are present in your Array with the same first element, only the last pair will be kept in the Hash.
If you want to keep the whole information, you could define arrays as values :
a = [[1, 3], [3, 2], [1, 2]]
hash = Hash.new{|h,k| h[k] = []}
p a.each_with_object(hash) { |(k, v), h| h[k] << v }
#=> {1=>[3, 2], 3=>[2]}
Here's a shorter but less common way to define it :
hash = a.each_with_object(Hash.new{[]}) { |(k, v), h| h[k] <<= v }
Calling hash[1] returns [3,2], which are all the second elements from the pairs of your array having 1 as first element.

Multiple return values with arrays and hashes

I believe arrays are mostly used for returning multiple values from methods:
def some_method
return [1, 2]
end
[a, b] = some_method # should yield a = 1 and b = 2
I presume this is a kind of syntactic sugar that Ruby provides. Can we get a similar result with hashes, for instance
def some_method
return { "a" => 1, "b" => 2 }
end
{"c", "d"} = some_method() # "c" => 1, "d" => 2
I'm looking for the result { "c" => 1, "d" => 2 }, which obviously does not happen. Is there any other way this can be done? I know that we can return a hash from the method and store it and use it like so
def some_method
return {"a" => 1, "b" => 2}
end
hash = some_method()
Just curious if there is another way similar to the one with arrays but using hashes....
I think a simpler way to put the question would be...
# If we have a hash
hash = {"a" => 1, "b" => 2}
# Is the following possible
hash = {2, 3} # directly assigning values to the hash.
OR
# another example
{"c", "d"} = {2, 3} # c and d would be treated as keys and {2, 3} as respective values.
First of all, you have a syntax error. Instead of this:
[a, b] = [1, 2]
you should use:
a, b = [1, 2]
And if you want to use similar syntax with hashes, you can do:
a, b = { "c" => 1, "d" => 2 }.values # a => 1, b => 2
This is actually the same thing as the array version, beacause Hash#values returns an array of the hash values in the order they were inserted to the hash (because ruby hashes have a nice feature of preserving their order)
What you are asking is syntactically not possible.
What you want to accomplish is possible, but you will have to code it.
One possible way to do that is shown below
hash = {"a" => 1, "b" => 2}
# Assign new keys
new_keys = ["c", "d"]
p [new_keys, hash.values].transpose.to_h
#=> {"c"=>1, "d"=>2}
# Assign new values
new_values = [3, 4]
p [hash.keys, new_values].transpose.to_h
#=> {"a"=>3, "b"=>4}
If you really want some more easier looking way of doing, you could monkey-patch Hash class and define new methods to manipulate the values of keys and values array. Please be cautioned that it may not be really worthwhile to mess with core classes. Anyways, a possible implementation is shown below. Use at your own RISK.
class Hash
def values= new_values
new_values.each_with_index do |v, i|
self[keys[i]] = v if i < self.size
end
end
def keys= new_keys
orig_keys = keys.dup
new_keys.each_with_index do |k, i|
if i < orig_keys.size
v = delete(orig_keys[i])
self[k] = v
rehash
end
end
end
end
hash = {"a" => 1, "b" => 2}
hash.values = [2,3]
p hash
#=> {"a"=>2, "b"=>3}
hash.keys = ["c", "d"]
p hash
#=> {"c"=>2, "d"=>3}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
# *** results can be unpredictable at times ***
hash.keys, hash.values = ["a"], [20, 10]
p hash
#=> {"y"=>20, "a"=>10}

Sort a hash of arrays by one of the arrays in ruby

In Ruby, is there a short and sweet way to sort this hash of arrays by score descending:
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
so it looks like this?:
scored = {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
I couldnt find an example where I can sort arrays within a hash like this? I could do this with some nasty code but I feel like there should be a 1 or 2 liner that does it?
You could use sort_by
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
scored.tap do |s|
s[:id] = s[:id].sort_by.with_index{ |a, i| -s[:score][i] }
s[:score] = s[:score].sort_by{ |a| -a }
end
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
order = scored[:score].each_with_index.sort_by(&:first).map(&:last).reverse
#=> [2,0,1]
scored.update(scored) { |_,a| a.values_at *order }
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
If scored is to not to be mutated, replace update with merge.
Some points:
Computing order makes it easy for the reader to understand what's going on.
The second line uses the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged (which here is all keys). This is a convenient way to modify hash values (generally), in part because the new hash is returned.
I sorted then reversed, rather than sorted by negated values, to make the method more rubust. (That is, the elements of the arrays that are the values can be from any class that implements <=>).
With Ruby 2.2+, another way to sort an array arr in descending order is to use Enumerable#max_by: arr.max_by(arr.size).to_a.
The first line could be replaced with:
arr = scored[:score]
order = arr.each_index.sort_by { |i| arr[i] }.reverse
#=> [2,0,1]
Here is one possible solution. It has an intermediate step, where it utilizes a zipped version of the scores object, but produces the correct output:
s = scored.values.inject(&:zip).sort_by(&:last).reverse
#=> [[3, 10], [1, 8.3], [2, 5]]
result = { id: s.map(&:first), score: s.map(&:last) }
#=> { :id => [3, 1, 2], :score => [10, 8.3, 5] }

Destructive reject from an array returning the values rejected

Is there a sensible way to do the following:
I want to take an array and select specific items from the array according to conditions, removing them from the array as they go.
(I basically want to split the contents of an array into categories).
array = [1,2,3,4,5,6,7,8]
less_than_three = array.reject_destructively{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.reject_destructively{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
I've tried delete_if, select!, reject! and none of them seem to be able to give you the affected items whilst leaving the array with the rest.
Unless I'm going mad, which is entirely possible.
As I understood the question, you do not want to produce two new objects. Here you go:
class Array
def carve!
dup.tap { delete_if &Proc.new } - self
end
end
array = [1,2,3,4,5,6,7,8]
p array.carve! { |v| v < 3 }
#⇒ [1, 2] # returned by Array#carve method
p array
#⇒ [3, 4, 5, 6, 7, 8] # remained in original array
Using this solution, array.__id__ remains the same. And this is the golfiest answer all around :)
You can build your own method for this...
class Array
def extract(&block)
temp = self.select(&block)
self.reject!(&block)
temp
end
end
then...
a = [1, 2, 3, 4, 5]
a.extract{|x| x < 3}
=> [1,2]
p a
=> [3, 4, 5]
EDIT: If you don't want to monkey patch (but monkey patching isn't evil in itself) you can do it with a vanilla method...
def select_from_array(array, &block)
temp = array.select(&block)
array.reject!(&block)
temp
end
array = [1,2,3,4,5,6,7,8]
less_than_three = select_from_array(array){|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = select_from_array(array){|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
In rails 6 there is a method extract!:
a = [1, 2, 3] #=> [1, 2, 3]
a.extract! { |num| num.odd? } #=> [1, 3]
a #=> [2]
irb(main):001:0> array = [1,2,3,4,5,6,7,8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
irb(main):002:0> array.partition{|v| v < 3}
=> [[1, 2], [3, 4, 5, 6, 7, 8]]
is there a specific reason, why this has to be destructive ?
Will this help
class Array
def reject_destructively(&block)
arr = self.select(&block)
arr.each{ |i| self.delete(i) }
arr
end
end
array = [1,2,3,4,5,6,7,8]
p less_than_three = array.reject_destructively{|v| v<3}
#=> [1,2]
p array
#=> [3,4,5,6,7,8]
p more_than_five = array.reject_destructively{|v| v>5}
#=> [6,7,8]
p array
#=> [3,4,5]
The above code can be simplified further to look like:
class Array
def reject_destructively(&block)
self.select(&block).each{ |i| self.delete(i) }
end
end
Ok. This works, avoids monkey patching, keeps it to one line...etc, but it's damn ugly....
less_than_three = array.dup - array.reject!{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.dup - array.reject!{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
module Enumerable
def reject_destructively
array=[]
self.each do |y|
if yield(y)
array<<y
end
end
array.each do |x|
self.delete(x)
end
return array
end
end
array=[10,9,2,1,3,45,52]
print less_than_three = array.reject_destructively{|v| v < 3}
print array
You can use group_by to get all of the elements that satisfy the condition in one group, and all of the rest in the other.
For example
[1,2,3,4,5].group_by{|i| i > 3}
gives
{false=>[1, 2, 3], true=>[4, 5]}
More information is available at http://ruby-doc.org/core-2.1.1/Enumerable.html#method-i-group_by

Resources