Ruby hash permutation - ruby

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)

Related

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.

Array of objects or hash in Ruby [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 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]

Compare values from multiple hashes within an array

I know for-loops should be avoided and i guess the better way to iterate through an array is instead of doing
for i in 0..array.size-1 do
puts array[i]
end
do
array.each{ |x|
puts x
}
But what if i had an array of hashes like
array = [{:value0 => 1, :value1 => 0}, {:value0 => 2, :value1 => 1}, {:value0 => 1, :value1 => 2}]
and wanted to check if :value0 is unique in all hashes.. intuitively I would do something like
for i in 0..array.size-1 do
_value_buffer = array[i][:value0]
for j in i+1..array.size-1 do
if _value_buffer == array[j][:value0]
puts "whatever"
end
end
end
Is there a better way to do this?
Why not just get all the values in question and see if they’re unique?
!array.map { |h| h[:value0] }.uniq!
(uniq! returns nil when there are no duplicates)
As what Andrew Marshall says, you can use uniq!, but with a more concise way:
!array.uniq!{|a| a[:value0]}
Here is how I would do it:
2.0.0p195 :001 > array = [{:value0 => 1, :value2 => 0}, {:value0 => 2, :value2 => 1}, {:value0 => 1, :value2 => 2}]
=> [{:value0=>1, :value2=>0}, {:value0=>2, :value2=>1}, {:value0=>1, :value2=>2}]
2.0.0p195 :002 > val0 = array.map { |hash| hash[:value0] }
=> [1, 2, 1]
2.0.0p195 :003 > puts val0.uniq == val0
false
=> nil
I would collect the values of :value0 and then compare them to the array of unique values.

ruby noob: are hashes speedy and optimal for storage or should I make a tuple?

This is a pretty simple problem im working on in Ruby, but im a total noob so I want to learn the most correct solution. I have a list of data with a name and a value. I need to remember all those (obvious answer: hash). But, i also need to remember the order of this data. SO it looks like:
x=1
y=2
z=3
I was thinking about making an array of 1 element hashes:
[0 => {'x' => 1},
1 => {'y' => 2},
2 => {'z' => 3}]
Are hashes the best choice in this situation? Is there some reason they would be slow, or not optimal?
Use Ruby 1.9. Hashes are ordered here.
You could try OrderedHash from ActiveSupport or Dictionary using Ruby Facets.
Performance permitting, an associate array would work. An associative array is an array of arrays, with each sub-array containing two elements:
a = [
[:x, 1],
[:y, 2],
[:z, 3],
]
You can use assoc to find a sub-array by its first element:
p a.assoc(:x) # [:x, 1]
p a.assoc(:x).last # 1
Or rassoc to find a sub-array by its last element:
p a.rassoc(2) # [:y, 2]
p a.rassoc(2).first # :y
Whether or not this approach will work for you depend upon the size of the list and how often you have to search it. On my machine, finding the last element in a 1000 element list takes about 100 microseconds.
Another approach is to use a plain, unordered (in Ruby <= 1.8.7) hash:
h = {
:x => 1,
:y => 2,
:z => 3,
}
And order it at the time you do an operation where order matters:
sorted_h = h.sort_by do |key, value|
key.to_s
end.each do |key, value|
p [key, value]
end
# [:x, 1]
# [:y, 2]
# [:z, 3]
This approach is good for alphabetical or numerical ordering (for example). It's not so good for order of insertion.

Comparing hash of hashes in Ruby

I am having two structures similar to the one below.
[1 => [{'pc'=>1,'id'=>0},{'pc'=>4,'id'=>0},{'pc'=>2,'id'=>1]]
But both of them need not contain the inside array in the exact same order. How to compare in such case?
If the order isn't important, you should consider other structures instead of Array there, Set, for example.
You can use Set for comparing as well, by converting Arrays to Sets before comparison:
require 'set'
a = [{:a => 2}, {:b => 3}]
b = [{:b => 3}, {:a => 2}]
sa = Set.new a
#=> #<Set: {{:a=>2}, {:b=>3}}>
sb = Set.new b
#=> #<Set: {{:b=>3}, {:a=>2}}>
a == b
#=> false
sa == sb
#=> true
It seems a simple compare works:
data = {
1 => [{'pc'=>1,'id'=>0},{'pc'=>4,'id'=>0},{'pc'=>2,'id'=>1}],
2 => [{'pc'=>1,'id'=>0},{'pc'=>4,'id'=>0},{'pc'=>2,'id'=>1}],
3 => [{'pc'=>1,'id'=>0},{'pc'=>2,'id'=>1},{'pc'=>4,'id'=>0}]
}
data[1] == data[2]
#> true
data[2] == data[3]
#> false
If both the nested hashes having same keys & order, we can write a recursive method to do the comparison. I am not sure whether we have inbuilt method to do so.

Resources