Array of objects or hash in Ruby [closed] - ruby

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
While working with a hash today in Ruby, I kept coming up against a few complications when sorting and accessing it. In my mind, I needed just a simple array.pop method to do what I needed. being that I just learned about class definitions, I had an idea and wanted to see if there was a reason not to do it this way.
hash = {"a" => 1, "b" => 2, "c" => 3}
Could I not do the same thing, but leave it open to more sorting methods and extensible data if I create this an array of objects containing data values?
Something like this
class Key
attr_accessor :value
def initialize (value)
#value = value
end
end
I'd then create an array of Key objects. I can then sort the array easier than the hash and still get the data from the inside the Keys. I figure this keeps things open for a more extensible bit of code if I find several bits of data need to be held together and sorted through.
Is this bad practice? Can you see a situation this would bite me? Am I solving a problem with a hammer because I just got one?

This ability is built into the Hash and Array data structures.
If you are using key-based access, get the keys and sort them:
hsh = {'a' => 1, 'c' => 3, 'b' => 2}
keys = hsh.keys.sort # keys: ['a', 'b', 'c']
If you need the values sorted, get the values and sort:
values = hsh.values.sort # values: [1, 2, 3]

Hash includes the Enumerable module which gives you all kinds of nifty ways of enumerating and sorting the hash.
irb(main):006:0> h = {'a' => 1, 'c' => 3, 'b' => 2}
=> {"a"=>1, "c"=>3, "b"=>2}
irb(main):007:0> h.sort { |a,b| b<=> a }
=> [["c", 3], ["b", 2], ["a", 1]]
irb(main):010:0> h = { 'three' => 3, 'one' => 1, 'two' => 2 }
=> {"three"=>3, "one"=>1, "two"=>2}
irb(main):011:0> h.sort { |a,b| a[0] <=> b[0] }
=> [["one", 1], ["three", 3], ["two", 2]]
irb(main):012:0> h.sort { |a,b| a[1] <=> b[1] }
=> [["one", 1], ["two", 2], ["three", 3]]
If you are looking for an array of hash values based on some key sorting:
irb(main):016:0> h.keys.sort.map { |key| h[key] }
=> [1, 3, 2]

Related

Create a Hash from two arrays of different sizes and iterate until none of the keys are empty

Having two arrays of different sizes, I'd like to get the longer array as keys and the shorter one as values. However, I don't want any keys to remain empty, so that is why I need to keep iterating on the shorter array until all keys have a value.
EDIT: I want to keep array longer intact, but without empty values, that means keep iterating on shorter until all keys have a value.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
Hash[longer.zip(shorter)]
#=> {1=>"a", 2=>"b", 3=>"c", 4=>nil, 5=>nil, 6=>nil, 7=>nil}
Expected Result
#=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's an elegant one. You can "loop" the short array
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
longer.zip(shorter.cycle).to_h # => {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
A crude way until you find something more elegant:
Slice the longer array as per length of shorter one, and iterate over it to re-map the values.
mapped = longer.each_slice(shorter.length).to_a.map do |slice|
Hash[slice.zip(shorter)]
end
=> [{1=>"a", 2=>"b", 3=>"c"}, {4=>"a", 5=>"b", 6=>"c"}, {7=>"a"}]
Merge all hashes withing the mapped array into a single hash
final = mapped.reduce Hash.new, :merge
=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's a fun answer.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
h = Hash.new do |h,k|
idx = longer.index(k)
idx ? shorter[idx % shorter.size] : nil
end
#=> {}
h[1] #=> a
h[2] #=> b
h[3] #=> c
h[4] #=> a
h[5] #=> b
h[6] #=> c
h[7] #=> a
h[8] #=> nil
h #=> {}
h.values_at(3,5) #=> ["c", "b"]
If this is not good enough (e.g., if you wish to use Hash methods such as keys, key?, merge, to_a and so on), you could create the associated hash quite easily:
longer.each { |n| h[n] = h[n] }
h #=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}

Ruby hash order, uniq method

Variable fav_food can be either "pie", "cake", or "cookie", which will be inputed by the users. However, I want my food_qty Hash to list the fav_food as the first key.
Thus, I come out with
food_order = ([fav_food] + food_qty.keys).uniq
However, is there a better way to do this?
food_qty = {"pie" => 0, "cake" => 0, "cookie" => 0}
# make the fav_food listed first in the hash
food_order = ([fav_food] + food_qty.keys).uniq
Why do you want a particular key/value pair to be first in a hash? Hashes don't need to be ordered, because you can directly access any element at any time without any extra cost.
If you need to retrieve elements in an order, then get the keys and sort that list, then iterate over that list, or use values_at:
foo = {
'z' => 1,
'a' => 2
}
foo_keys = foo.keys.sort # => ["a", "z"]
foo_keys.map{ |k| foo[k] } # => [2, 1]
foo.values_at(*foo_keys) # => [2, 1]
Hashes remember their insertion order, but you shouldn't rely on that; Ordering a hash doesn't help if you insert something later, and other languages don't support it. Instead, order the keys however you want, and use that list to retrieve the values.
If you want to force a key to be first so its value is retrieved first, then consider this:
foo = {
'z' => 1,
'a' => 2,
'w' => 3,
}
foo_keys = foo.keys # => ["z", "a", "w"]
foo_keys.unshift(foo_keys.delete('w')) # => ["w", "z", "a"]
foo_keys.map{ |k| foo[k] } # => [3, 1, 2]
foo.values_at(*foo_keys) # => [3, 1, 2]
If you want a sorted list of keys with one forced to a position:
foo_keys = foo.keys.sort # => ["a", "w", "z"]
foo_keys.unshift(foo_keys.delete('w')) # => ["w", "a", "z"]
foo_keys.map{ |k| foo[k] } # => [3, 2, 1]
foo.values_at(*foo_keys) # => [3, 2, 1]
RE your first paragraph: Hashes are ordered though, specifically because this is such a common requirement and hashes fill so many roles in Ruby. There is no harm relying on hashes being ordered in Ruby, even if other languages don't support this behavior.
Not ordered, as in sorted, instead they remember their insertion order. From the documentation:
Hashes enumerate their values in the order that the corresponding keys were inserted.
This is easily tested/proven:
foo = {z:0, a:-1} # => {:z=>0, :a=>-1}
foo.to_a # => [[:z, 0], [:a, -1]]
foo[:b] = 3
foo.merge!({w:2})
foo # => {:z=>0, :a=>-1, :b=>3, :w=>2}
foo.to_a # => [[:z, 0], [:a, -1], [:b, 3], [:w, 2]]
foo.keys # => [:z, :a, :b, :w]
foo.values # => [0, -1, 3, 2]
If a hash was ordered foo.to_a would be collated somehow, even after adding additional key/value pairs. Instead, it remains in its insertion order. An ordered hash based on keys would move a:-1 to be the first element, just as an ordered hash based on the values would do.
If hashes were ordered, and, if it was important, we'd have some way of telling a hash what its ordering is, ascending or descending or of having some sort of special order based on the keys or values. Instead we have none of those things, and only have the sort and sort_by methods inherited from Enumerable, both of which convert the hash into an array and sort it and return the array, because Arrays can benefit from having an order.
Perhaps you are thinking of Java, which has SortedMap, and provides those sort of capabilities:
A Map that further provides a total ordering on its keys. The map is ordered according to the natural ordering of its keys, or by a Comparator typically provided at sorted map creation time. This order is reflected when iterating over the sorted map's collection views (returned by the entrySet, keySet and values methods). Several additional operations are provided to take advantage of the ordering.
Again, because Ruby's Hash does not sort ordering beyond its insertion order, we have none of those capabilities.
You could use Hash#merge:
food_qty = { "pie" => 0, "cake" => 0, "cookie" => 0 }
fav_food = "cookie"
{ fav_food => nil }.merge(food_qty)
# => { "cookie" => 0, "pie" => 0, "cake" => 0 }
This works because Hash#merge first duplicates the original Hash and then, for keys that already exist (like "cookie"), updates the values—which preserves the order of existing keys. In case it's not clear, the above is equivalent to this:
{ "cookie" => nil }.merge("pie" => 0, "cake" => 0, "cookie" => 0)
Edit: The below is my original answer, before I realized that I had also stumbled upon the "real" answer above.
I don't really advocate this (the Tin Man's advice should be taken instead), but if you're using Ruby 2.0+, I present the following Stupid Ruby Trick:
food_qty = { :pie => 0, :cake => 0, :cookie => 0}
fav_food = :cookie
{ fav_food => nil, **food_qty }
# => { :cookie => 0, :pie => 0, :cake => 0 }
This works because Ruby 2.0 added the "double splat" or "keyword splat" operator, as an analogue to the splat in an Array:
arr = [ 1, 2, *[3, 4] ] # => [ 1, 2, 3, 4 ]
hsh = { a: 1, b: 2, **{ c: 3 } } # => { :a => 1, :b => 2, :c => 3 }
...but it appears to do a reverse merge (a la ActiveSupport's Hash#reverse_merge), merging the "outer" hash into the "inner."
{ a: 1, b: 2, **{ a: 3 } } # => { :a => 1, :b => 2 }
# ...is equivalent to:
{ a: 3 }.merge( { a: 1, b: 2 } ) # => { :a => 1, :b => 2 }
The double splat was implemented to support keyword arguments in Ruby 2.0, which is presumably the reason why it only works if the "inner" Hash's keys are all Symbols.
Like I said, I don't recommend actually doing it, but I find it interesting nonetheless.

Ruby: combine two-dimensional arrays into a single array of hashes [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
what is the cleanest way to turn these two arrays of arrays:
[[1, 'a'], [2, 'b']]
and
[[1, 'c'], [2, 'd']]
into a single array of hashes with arbitrary keys like this:
[{:id => 1, :foo => 'a', :bar => 'c'}, {:id => 2, :foo => 'b', :bar => 'd'}]
to elaborate, position 0 of every internal array should map to the :id key, position 1 of the internal arrays of the first array should map to the :foo key, and position 1 of the internal arrays of the second array should map to the :bar key. further, the :id key of each hash should not repeat, and each hash should have an :id, :foo, and :bar key.
Clarify by decomposing the block variables:
a = [[1, 'a'], [2, 'b']]
b = [[1, 'c'], [2, 'd']]
a.zip(b).map { |(id,foo),(_,bar)| {id: id, foo: foo, bar: bar } }
#=> [{:id=>1, :foo=>"a", :bar=>"c"}, {:id=>2, :foo=>"b", :bar=>"d"}]

Ruby hash permutation

Is there any quick way to get a (random) permutation of a given hash? For example with arrays I can use the sample method as in
ruby-1.9.2-p180 :031 > a = (1..5).to_a
=> [1, 2, 3, 4, 5]
ruby-1.9.2-p180 :032 > a.sample(a.length)
=> [3, 5, 1, 2, 4]
For hashes I can use the same method on hash keys and build a new hash with
ruby-1.9.2-p180 :036 > h = { 1 => 'a', 2 => 'b', 3 => 'c' }
=> {1=>"a", 2=>"b", 3=>"c"}
ruby-1.9.2-p180 :037 > h.keys.sample(h.length).inject({}) { |h2, k| h2[k] = h[k]; h2 }
=> {3=>"c", 2=>"b", 1=>"a"}
but this is so ugly. Is there any 'sample' method for hashes which can avoid all that code?
Update As pointed out by #Michael Kohl in comments, this question is meaningful only for ruby 1.9.x. Since in 1.8.x Hash are unordered there is no way to do that.
A slight refinement of mu is too short's answer:
h = Hash[h.to_a.shuffle]
Just add a to_a and Hash[] to your array version to get a Hash version:
h = Hash[h.to_a.sample(h.length)]
For example:
>> h = { 1 => 'a', 2 => 'b', 3 => 'c' }
=> {1=>"a", 2=>"b", 3=>"c"}
>> h = Hash[h.to_a.sample(h.length)]
=> {2=>"b", 1=>"a", 3=>"c"}
Do you really need to shuffle or do you just need a way to access/iterate on a random key ?
Otherwise, a maybe less expensive solution would be to shuffle the hash keys and access your items based on the permutation of those hash keys
h = your_hash
shuffled_hash_keys = hash.keys.shuffle
shuffled_hash_keys.each do |key|
# do something with h[key]
end
I believe (but would need a proof with a benchmark) that this avoids the need/cost to build a brand new hash and is probably more efficient if you have big hashes (you only need to pay the cost of an array permutation)

Converting an array of keys and an array of values into a hash in Ruby

I have two arrays like this:
keys = ['a', 'b', 'c']
values = [1, 2, 3]
Is there a simple way in Ruby to convert those arrays into the following hash?
{ 'a' => 1, 'b' => 2, 'c' => 3 }
Here is my way of doing it, but I feel like there should be a built-in method to easily do this.
def arrays2hash(keys, values)
hash = {}
0.upto(keys.length - 1) do |i|
hash[keys[i]] = values[i]
end
hash
end
The following works in 1.8.7:
keys = ["a", "b", "c"]
values = [1, 2, 3]
zipped = keys.zip(values)
=> [["a", 1], ["b", 2], ["c", 3]]
Hash[zipped]
=> {"a"=>1, "b"=>2, "c"=>3}
This appears not to work in older versions of Ruby (1.8.6). The following should be backwards compatible:
Hash[*keys.zip(values).flatten]
Another way is to use each_with_index:
hash = {}
keys.each_with_index { |key, index| hash[key] = values[index] }
hash # => {"a"=>1, "b"=>2, "c"=>3}
The same can be done using Array#transpose method. If you are using Ruby version >= 2.1, you can take the advantage of the method Array#to_h, otherwise use your old friend, Hash::[]
keys = ['a', 'b', 'c']
values = [1, 2, 3]
[keys, values].transpose.to_h
# => {"a"=>1, "b"=>2, "c"=>3}
Hash[[keys, values].transpose]
# => {"a"=>1, "b"=>2, "c"=>3}
Try this, this way the latter one d will overwrite the former one c
irb(main):001:0> hash = Hash[[[1,2,3,3], ['a','b','c','d']].transpose]
=> {1=>"a", 2=>"b", 3=>"d"}
irb(main):002:0>

Resources