This is the input hash:
p Score.periods #{"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}
This is my current code to exchange the keys with the values, while converting the keys to symbols:
periods = Score.periods.inject({}) do |hsh,(k,v)|
hsh[v] = k.to_sym
hsh
end
Here is the result:
p periods #{0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}
It just seems like my code is clunky and it shouldn't take 4 lines to do what I'm doing here. Is there a cleaner way to write this?
You can do this:
Hash[periods.values.zip(periods.keys.map(&:to_sym))]
Or if you're using a version of Ruby where to_h is available for arrays, you can do this:
periods.values.zip(periods.keys.map(&:to_sym)).to_h
What the two examples above do is make arrays of the keys and values of the original hash. Note that the string keys of the hash are mapped to symbols by passing to_sym to map as a Proc:
periods.keys.map(&:to_sym)
# => [:q1, :q2, :q3, :q4, :h1, :h2]
periods.values
# => [0, 1, 2, 3, 4, 5]
Then it zips them up into an array of [value, key] pairs, where each corresponding elements of values is matched with its corresponding key in keys:
periods.values.zip(periods.keys.map(&:to_sym))
# => [[0, :q1], [1, :q2], [2, :q3], [3, :q4], [4, :h1], [5, :h2]]
Then that array can be converted back into a hash using Hash[array] or array.to_h.
The simplest way is:
data = {"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}
Hash[data.invert.collect { |k, v| [ k, v.to_sym ] }]
The Hash[] method converts an array of key/value pairs into an actual Hash. Quite handy for situations like this.
If you're using Ruby on Rails this could be even easier:
data.symbolize_keys.invert
h = {"q1"=>0, "q2"=>1, "q3"=>2, "q4"=>3, "h1"=>4, "h2"=>5}
h.each_with_object({}) { |(k,v),g| g[v] = k.to_sym }
#=> {0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}
The steps are as follows (for the benefit of Ruby newbies).
enum = h.each_with_object({})
#=> #<Enumerator: {0=>"q1", 1=>"q2", 2=>"q3", 3=>"q4",
# 4=>"h1", 5=>"h2"}:each_with_object({})>
The elements that will be generated by the enumerator and passed to the block can be seen by converting the enumerator to an array, using Enumerable#entries or Enumerable#to_a.
enum.entries
#=> [[["q1", 0], {}], [["q2", 1], {}], [["q3", 2], {}],
# [["q4", 3], {}], [["h1", 4], {}], [["h2", 5], {}]]
Continuing,
enum.each { |(k,v),g| g[v] = k.to_sym }
#=> {0=>:q1, 1=>:q2, 2=>:q3, 3=>:q4, 4=>:h1, 5=>:h2}
In the last step, Enumerator#each passes the first element generated by enum to the block and assigns the three block variables. Consider the first element of enum that is passed to the block and the associated calculation of values for the three block variables. (I must first execute enum.rewind to reinitialize enum, as each above took the enumerator to its end. See Enumerator#rewind).
(k, v), g = enum.next
#=> [["q1", 0], {}]
k #=> "q1"
v #=> 0
g #=> {}
See Enumerator#next. The block calculation is therefore
g[v] = k.to_sym
#=> :q1
Hence,
g #=> {0=>:q1}
The next element of enum is passed to the block and similar calculations are performed.
(k, v), g = enum.next
#=> [["q2", 1], {0=>:q1}]
k #=> "q2"
v #=> 1
g #=> {0=>:q1}
g[v] = k.to_sym
#=> :q2
g #=> {0=>:q1, 1=>:q2}
The remaining calculations are similar.
Related
I have an array and I want to create a hash whose keys are the elements of the array and whose values are (an array of) the indices of the array. I want to get something like:
array = [1,3,4,5]
... # => {1=>0, 3=>1, 4=>2, 5=>3}
array = [1,3,4,5,6,6,6]
... # => {1=>0, 3=>1, 4=>2, 5=>3, 6=>[4,5,6]}
This code:
hash = Hash.new 0
array.each_with_index do |x, y|
hash[x] = y
end
works fine only if I don't have duplicate elements. When I have duplicate elements, it does not.
Any idea on how I can get something like this?
You can change the logic to special-case the situation when the key already exists, turning it into an array and pushing the new index:
arr = %i{a a b a c}
result = arr.each.with_object({}).with_index do |(elem, memo), idx|
memo[elem] = memo.key?(elem) ? [*memo[elem], idx] : idx
end
puts result
# => {:a=>[0, 1, 3], :b=>2, :c=>4}
It's worth mentioning, though, that whatever you're trying to do here could possibly be accomplished in a different way ... we have no context. In general, it's a good idea to keep key-val data types uniform, e.g. the fact that values here can be numbers or arrays is a bit of a code smell.
Also note that it doesn't make sense to use Hash.new(0) here unless you're intentionally setting a default value (which there's no reason to do). Use {} instead
I'm adding my two cents:
array = [1,3,4,5,6,6,6,8,8,8,9,7,7,7]
hash = {}
array.map.with_index {|val, idx| [val, idx]}.group_by(&:first).map do |k, v|
hash[k] = v[0][1] if v.size == 1
hash[k] = v.map(&:last) if v.size > 1
end
p hash #=> {1=>0, 3=>1, 4=>2, 5=>3, 6=>[4, 5, 6], 8=>[7, 8, 9], 9=>10, 7=>[11, 12, 13]}
It fails with duplicated element not adjacent, of course.
This is the expanded version, step by step, to show how it works.
The basic idea is to build a temporary array with pairs of value and index, then work on it.
array = [1,3,4,5,6,6,6]
tmp_array = []
array.each_with_index do |val, idx|
tmp_array << [val, idx]
end
p tmp_array #=> [[1, 0], [3, 1], [4, 2], [5, 3], [6, 4], [6, 5], [6, 6]]
tmp_hash = tmp_array.group_by { |e| e[0] }
p tmp_hash #=> {1=>[[1, 0]], 3=>[[3, 1]], 4=>[[4, 2]], 5=>[[5, 3]], 6=>[[6, 4], [6, 5], [6, 6]]}
hash = {}
tmp_hash.map do |k, v|
hash[k] = v[0][0] if v.size == 1
hash[k] = v.map {|e| e[1]} if v.size > 1
end
p hash #=> {1=>1, 3=>3, 4=>4, 5=>5, 6=>[4, 5, 6]}
It can be written as one line as:
hash = {}
array.map.with_index.group_by(&:first).map { |k, v| v.size == 1 ? hash[k] = v[0][1] : hash[k] = v.map(&:last) }
p hash
If you are prepared to accept
{ 1=>[0], 3=>[1], 4=>[2], 5=>[3], 6=>[4,5,6] }
as the return value you may write the following.
array.each_with_index.group_by(&:first).transform_values { |v| v.map(&:last) }
#=> {1=>[0], 3=>[1], 4=>[2], 5=>[3], 6=>[4, 5, 6]}
The first step in this calculation is the following.
array.each_with_index.group_by(&:first)
#=> {1=>[[1, 0]], 3=>[[3, 1]], 4=>[[4, 2]], 5=>[[5, 3]], 6=>[[6, 4], [6, 5], [6, 6]]}
This may help readers to follow the subsequent calculations.
I think you will find this return value generally more convenient to use than the one given in the question.
Here are a couple of examples where it's clearly preferable for all values to be arrays. Let:
h_orig = { 1=>0, 3=>1, 4=>2, 5=>3, 6=>[4,5,6] }
h_mod { 1=>[0], 3=>[1], 4=>[2], 5=>[3], 6=>[4,5,6] }
Create a hash h whose keys are unique elements of array and whose values are the numbers of times the key appears in the array
h_mod.transform_values(&:count)
#=> {1=>1, 3=>1, 4=>1, 5=>1, 6=>3}
h_orig.transform_values { |v| v.is_a?(Array) ? v.count : 1 }
Create a hash h whose keys are unique elements of array and whose values equal the index of the first instance of the element in the array.
h_mod.transform_values(&:min)
#=> {1=>0, 3=>1, 4=>2, 5=>3, 6=>4}
h_orig.transform_values { |v| v.is_a?(Array) ? v.min : v }
In these examples, given h_orig, we could alternatively convert values that are indices to arrays containing a single index.
h_orig.transform_values { |v| [*v].count }
h_orig.transform_values { |v| [*v].min }
This is hardly proof that it is generally more convenient for all values to be arrays, but that has been my experience and the experience of many others.
h = { "a" => 1, "b" => 2 }
Is there a way to reduce a hash and have the key, value and index as block parameters?
As a starting point I can iterate over a hash getting key, value and index:
h.each_with_index { |(k,v), i| puts [k,v,i].inspect }
# => ["a", 1, 0]
# => ["b", 2, 1]
However when I add reduce I seem to loose the ability to have the key and value as separate values and instead they are provided as a two element array:
h.each_with_index.reduce([]) { |memo, (kv,i)| puts [kv,i].inspect }
# => [["a", 1], 0]
# => [["b", 2], 1]
This is okay, I can in the block do kv[0] and kv[1], but I'd like something like this:
h.each_with_index.reduce([]) { |memo, (k,v), i| puts [k,v,i].inspect }
I'd like to do this without monkey-patching.
Maybe something like this?:
h.each_with_index.reduce([]) { |memo, ((k,v), i)| puts [k,v,i].inspect }
#=> ["a", 1, 0]
#=> ["b", 2, 1]
#=> nil
All you need is scoping: ((k,v), i).
Keeping in mind with reduce, we always have to return the object at the end of block. Which is kind of an extra overhead unless last operation isn't on the memo object which returns the object itself.Otherwise it won't return the desired result.
Same thing can be achieved with each_with_index chained with with_object like so:
h.each_with_index.with_object([]) { |((k,v), i), memo| memo << [k,v,i].inspect }
#=> ["a", 1, 0]
#=> ["b", 2, 1]
#=> []
See the array at last line of output? That's our memo object, which isn't same as reduce that we used above.
When in doubt what the block arguments are, create an instance of an Enumerator and call #next on it:
▶ h = {a: 1, b: 2}
#⇒ {:a=>1, :b=>2}
▶ enum = h.each.with_index.with_object([])
#⇒ #<Enumerator: ...>
▶ enum.next
#⇒ [[[:a, 1], 0], []]
The returned value consists of:
array of key and value, joined into:
array with an index, joined into:
array with an accumulator (for reduce it’d go in front, if reduce returned an enumerator when called without a block—credits to #Stefan for nitpicking.)
Hence, the proper parentheses for decomposing it would be:
# ⇓ ⇓ ⇓ ⇓
# [ [ [:a, 1], 0 ], [] ]
{ | ( (k, v), idx ), memo| ...
Enumerable#each_with_index yields two values into the block: the item and its index. When it is invoked for a Hash, the item is an array that contains two elements: the key and the associated value.
When you declare the block arguments |(k,v), i| you, in fact, deconstruct the first block argument (the item) into its two components: the key and the value. Without a block h.each_with_index produces an Enumerator that yields both arguments of the previously used block wrapped into an array.
This array is the second argument of Enumerator#reduce.
You can tell this by running:
irb> h.each_with_index.reduce([]) { |memo, j| p j }
[["a", 1], 0]
[["b", 2], 1]
Now, the answer to your question is easy: just deconstruct j and you get:
irb> h.each_with_index.reduce([]) { |memo, ((k,v), i)| puts [k,v,i].inspect }
["a", 1, 0]
["b", 2, 1]
Of course, you should memo << [k,v,i] or put the values in memo using other other rules and return memo to get your final desired result.
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"}
I have the following:
lumpy_hash = { 1 => ["A", "B"] }
then if I invoke Hash#invert on this hash, I'd like to get:
lumpy_hash = {"A" => 1, "B" => 1}
I don't get that from using Hash#invert. Any ideas on doing this? I'm not sure if I should try Hash#map or Hash#invert.
There are many ways to do this. Here is one:
Hash[lumpy_hash.map { |k,v| v.product([k]) }.first]
#=> {"A"=>1, "B"=>1}
I don't think the method Hash#invert is useful here.
The steps:
enum = lumpy_hash.map
#=> #<Enumerator: {1=>["A", "B"]}:map>
k,v = enum.next
#=> [1, ["A", "B"]]
k #=> 1
v #=> ["A", "B"]
a = v.product([k])
#=> ["A", "B"].product([1])
#=> [["A", 1], ["B", 1]]
Hash[a]
#=> {"A"=>1, "B"=>1}
Here's another way that makes use of a hash's default value. This one is rather interesting:
key,value = lumpy_hash.to_a.first
#=> [1, ["A","B"]]
Hash.new { |h,k| h[k]=key }.tap { |h| h.values_at(*value) }
#=> {"A"=>1,"B"=>1}
Object#tap passes an empty hash to its block, assigning it to the block variable h. The block returns h after adding three key-value pairs, each having a value equal to the hash's default value. It adds the pairs merely by computing the values of keys the hash doesn't have!
Here's another, more pedestrian, method:
lumpy_hash.flat_map{|k,vs| vs.map{|v| {v => k}}}.reduce(&:merge)
=> {"A"=>1, "B"=>1}
I was looking at code regarding how to return a mode from an array and I ran into this code:
def mode(array)
answer = array.inject ({}) { |k, v| k[v]=array.count(v);k}
answer.select { |k,v| v == answer.values.max}.keys
end
I'm trying to conceptualize what the syntax means behind it as I am fairly new to Ruby and don't exactly understand how hashes are being used here. Any help would be greatly appreciated.
Line by line:
answer = array.inject ({}) { |k, v| k[v]=array.count(v);k}
This assembles a hash of counts. I would not have called the variable answer because it is not the answer, it is an intermediary step. The inject() method (also known as reduce()) allows you to iterate over a collection, keeping an accumulator (e.g. a running total or in this case a hash collecting counts). It needs a starting value of {} so that the hash exists when attempting to store a value. Given the array [1,2,2,2,3,4,5,6,6] the counts would look like this: {1=>1, 2=>3, 3=>1, 4=>1, 5=>1, 6=>2}.
answer.select { |k,v| v == answer.values.max}.keys
This selects all elements in the above hash whose value is equal to the maximum value, in other words the highest. Then it identifies the keys associated with the maximum values. Note that it will list multiple values if they share the maximum value.
An alternative:
If you didn't care about returning multiple, you could use group_by as follows:
array.group_by{|x|x}.values.max_by(&:size).first
or, in Ruby 2.2+:
array.group_by{&:itself}.values.max_by(&:size).first
The inject method acts like an accumulator. Here is a simpler example:
sum = [1,2,3].inject(0) { |current_tally, new_value| current_tally + new_value }
The 0 is the starting point.
So after the first line, we have a hash that maps each number to the number of times it appears.
The mode calls for the most frequent element, and that is what the next line does: selects only those who are equal to the maximum.
I believe your question has been answered, and #Mark mentioned different ways to do the calculations. I would like to just focus on other ways to improve the first line of code:
answer = array.inject ({}) { |k, v| k[v] = array.count(v); k }
First, let's create some data:
array = [1,2,1,4,3,2,1]
Use each_with_object instead of inject
My suspicion is that the code might be fairly old, as Enumerable#each_with_object, which was introduced in v. 1.9, is arguably a better choice here than Enumerable#inject (aka reduce). If we were to use each_with_object, the first line would be:
answer = array.each_with_object ({}) { |v,k| k[v] = array.count(v) }
#=> {1=>3, 2=>2, 4=>1, 3=>1}
each_with_object returns the object, a hash held by the block variable v.
As you see, each_with_object is very similar to inject, the only differences being:
it is not necessary to return v from the block to each_with_object, as it is with inject (the reason for that annoying ; v at the end of inject's block);
the block variable for the object (k) follows v with each_with_object, whereas it proceeds v with inject; and
when not given a block, each_with_object returns an enumerator, meaning it can be chained to other other methods (e.g., arr.each_with_object.with_index ....
Don't get me wrong, inject remains an extremely powerful method, and in many situations it has no peer.
Two more improvements
In addition to replacing inject with each_with_object, let me make two other changes:
answer = array.uniq.each_with_object ({}) { |k,h| h[k] = array.count(k) }
#=> {1=>3, 2=>2, 4=>1, 3=>1}
In the original expression, the object returned by inject (sometimes called the "memo") was represented by the block variable k, which I am using to represent a hash key ("k" for "key"). Simlarly, as the object is a hash, I chose to use h for its block variable. Like many others, I prefer to keep the block variables short and use names that indicate object type (e.g., a for array, h for hash, s for string, sym for symbol, and so on).
Now suppose:
array = [1,1]
then inject would pass the first 1 into the block and then compute k[1] = array.count(1) #=> 2, so the hash k returned to inject would be {1=>2}. It would then pass the second 1 into the block, again compute k[1] = array.count(1) #=> 2, overwriting 1=>1 in k with 1=>1; that is, not changing it at all. Doesn't it make more sense to just do this for the unique values of array? That's why I have: array.uniq....
Even better: use a counting hash
This is still quite inefficient--all those counts. Here's a way that reads better and is probably more efficient:
array.each_with_object(Hash.new(0)) { |k,h| h[k] += 1 }
#=> {1=>3, 2=>2, 4=>1, 3=>1}
Let's have a look at this in gory detail. Firstly, the docs for Hash#new read, "If obj is specified [i.e., Hash.new(obj)], this single object will be used for all default values." This means that if:
h = Hash.new('cat')
and h does not have a key dog, then:
h['dog'] #=> 'cat'
Important: The last expression is often misunderstood. It merely returns the default value. str = "It does *not* add the key-value pair 'dog'=>'cat' to the hash." Let me repeat that: puts str.
Now let's see what's happening here:
enum = array.each_with_object(Hash.new(0))
#=> #<Enumerator: [1, 2, 1, 4, 3, 2, 1]:each_with_object({})>
We can see the contents of the enumerator by converting it to an array:
enum.to_a
#=> [[1, {}], [2, {}], [1, {}], [4, {}], [3, {}], [2, {}], [1, {}]]
These seven elements are passed into the block by the method each:
enum.each { |k,h| h[k] += 1 }
=> {1=>3, 2=>2, 4=>1, 3=>1}
Pretty cool, eh?
We can simulate this using Enumerator#next. The first value of enum ([1, {}]) is passed to the block and assigned to the block variables:
k,h = enum.next
#=> [1, {}]
k #=> 1
h #=> {}
and we compute:
h[k] += 1
#=> h[k] = h[k] + 1 (what '+=' means)
# = 0 + 1 = 1 (h[k] on the right equals the default value
# of 1 since `h` has no key `k`)
so now:
h #=> {1=>1}
Next, each passes the second value of enum into the block and similar calculations are performed:
k,h = enum.next
#=> [2, {1=>1}]
k #=> 2
h #=> {1=>1}
h[k] += 1
#=> 1
h #=> {1=>1, 2=>1}
Things are a little different when the third element of enum is passed in, because h now has a key 1:
k,h = enum.next
#=> [1, {1=>1, 2=>1}]
k #=> 1
h #=> {1=>1, 2=>1}
h[k] += 1
#=> h[k] = h[k] + 1
#=> h[1] = h[1] + 1
#=> h[1] = 1 + 1 => 2
h #=> {1=>1, 2=>1}
The remaining calculations are performed similarly.