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
I have an array:
a = [[1,[2,3]], [2,[3,4]], [3,[4,5]]]
I want to convert it to:
h = {1 => {2 => 3}, 2 => {3 => 4}, 3 => {4 => 5}}
I m new to Ruby, can anybody help me to solve this?
If you mean:
a = [[1,[2,3]], [2,[3,4]], [3,[4,5]]]
Hash[a.map { |k0, (k, v)| [k0, {k => v}] }]
# => {1=>{2=>3}, 2=>{3=>4}, 3=>{4=>5}}
a.map { |k0, (k, v)| [k0, {k => v}] }.to_h # Ruby 2.1+
# => {1=>{2=>3}, 2=>{3=>4}, 3=>{4=>5}}
Another way:
a = [[1, [2, 3]], [2, [3, 4]], [3,[4, 5]]]
a.each_with_object({}) { |(i, (k, v)), h| h[i] = { k => v } }
#=> {1=>{2=>3}, 2=>{3=>4}, 3=>{4=>5}}
The nested values are extracted using Ruby's array decomposition:
[1, [2, 3]]
(i, (k, v))
This can be implemented nicely as a map/reduce:
a = [[1, [2, 3]], [2, [3, 4]], [3,[4, 5]]]
a.map{|k,v| {k=>v}}.reduce(:merge)
# => {1=>{2=>3}, 2=>{3=>4}, 3=>{4=>5}}
The map turns each sub-array into a tiny hash, and the reduce iteratively applies Hash#merge to combine them into one big hash.
This is more concise than other implementations I've seen, and also more readable/idiomatic (at least to my eye).
Here's another way, which I'm suggesting because a) the standard approaches have already been mentioned in answers posted earlier; and b) you might find it instructive, if not a bit bizarre.
What we will do is create an empty hash h and then execute:
a.each { |k| h[k] }
and presto!
h => {1=>{2=>3}, 2=>{3=>4}, 3=>{4=>5}}
"How can that possibly be?", you probably are thinking. The trick is the way we define the hash h; namely, we use the form of the class method Hash::new that takes a block:
h = Hash.new { |h,(x,(y,z))| h[x] = { y=>z } }
It's normally used like this:
h = Hash.new { |h,k| h[k] = ... } }
This says that when h is passed a key k that is not already in the hash, the block is to be executed to compute the value associated with the new key. We do not, however, need to use k as the key. Here I am passing an element of a, which I have written in its decomposed (or "disambiguted") form, (x,(y,z)), and then adding the key-value pair x => { y=>z } to the hash.
This could instead be written:
h = Hash.new { |h,k| h[k.first] = { k.last.first=>k.last.last } }
but I hope you will agree that the decomposed form is a lot easier to read.
Rather than:
h = Hash.new { |h,(x,(y,z))| h[x] = { y=>z } }
a.each { |k| h[k] }
h
the more Ruby-like way is:
a.each_with_object(Hash.new { |h,(x,(y,z))| h[x] = { y=>z } }) { |k,h| h[k] }
Related
I have an array I want to turn into a hash map keyed by the item and with an array of indices as the value. For example
arr = ["a", "b", "c", "a"]
would become
hsh = {"a": [0,3], "b": [1], "c": [2]}
I would like to do this in a functional way (rather than a big old for loop), but am a little stuck
lst = arr.collect.with_index { |item, i| [item, i] }
produces
[["a", 0], ["b", 1], ["c", 2], ["a", 3]]
I then tried Hash[lst], but I don't get the array in the value and lose index 0
{"a"=>3, "b"=>1, "c"=>2}
How can I get my desired output in a functional way? I feel like it's something like
Hash[arr.collect.with_index { |item, i| [item, item[i] << i || [i] }]
But that doesn't yield anything.
Note: Trying to not do it this way
hsh = {}
arr.each.with_index do |item, index|
if hsh.has_key?(item)
hsh[item] << index
else
hsh[item] = [index]
end
end
hsh
Input
arr = ["a", "b", "c", "a"]
Code
p arr.map
.with_index
.group_by(&:first)
.transform_values { |arr| arr.map(&:last) }
Output
{"a"=>[0, 3], "b"=>[1], "c"=>[2]}
I would like to do this in a functional way (rather than a big old for loop), but am a little stuck
lst = arr.collect.with_index { |item, i| [item, i] }
produces
[["a", 0], ["b", 1], ["c", 2], ["a", 3]]
This is very close. The first thing I would do is change the inner arrays to hashes:
arr.collect.with_index { |item, i| { item => i }}
#=> [{ "a" => 0 }, { "b" => 1 }, { "c" => 2 }, { "a" => 3 }]
This is one step closer. Now, actually we want the indices in arrays:
arr.collect.with_index { |item, i| { item => [i] }}
#=> [{ "a" => [0] }, { "b" => [1] }, { "c" => [2] }, { "a" => [3] }]
This is even closer. Now, all we need to do is to merge those hashes into one single hash. There is a method for that, which is called Hash#merge. It takes an optional block for deconflicting duplicate keys, and all we need to do is concatenate the arrays:
arr.collect.with_index { |item, i| { item => [i] }}.inject({}) {|acc, h| acc.merge(h) {|_, a, b| a + b } }
#=> { "a" => [0, 3], "b" => [1], "c" => [2] }
And we're done!
How can I get my desired output in a functional way? I feel like it's something like
Hash[arr.collect.with_index { |item, i| [item, item[i] << i || [i] }]
But that doesn't yield anything.
Well, it has a SyntaxError, so obviously if it cannot even be parsed, then it cannot run, and if it doesn't even run, then it cannot possibly yield anything.
However, not that even if it worked, it would still violate your constraint that it should be done "in a functional way", because Array#<< mutates its receiver and is thus not functional.
arr.map.with_index.each_with_object({}){ |(a, i), h| h[a] ? h[a] << i : (h[a] = [i]) }
#=> {"a"=>[0, 3], "b"=>[1], "c"=>[2]}
arr.map.with_index => gives enumeration of each element with it's index
each_with_object => lets you reduce the enumeration on a provided object(represented by h in above)
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]
I want to get the coordinates of every occurrence of an object stored in an array of arrays. If I have an array:
array = [["foo", "bar", "lobster"], ["camel", "trombone", "foo"]]
and an object "foo", I want to get:
[[0,0], [1,2]]
The following will do this, but it's elaborate and ugly:
array.map
.with_index{
|row,row_index| row.map.with_index {
|v,col_index| v=="foo" ? [row_index,col_index] : v
}
}
.flatten(1).find_all {|x| x.class==Array}
Is there a more straightforward way to do this? This was asked before, and produced a similarly inelegant solution.
Here's a slightly more elegant solution. I have:
Used flat_map instead of flattening at the end
Used .each_index.select instead of .map.with_index and then having to strip non-arrays at the end, which is really ugly
Added indentation
array.flat_map.with_index {|row, row_idx|
row.each_index.select{|i| row[i] == 'foo' }.map{|col_idx| [row_idx, col_idx] }
}
Another way:
array = [["foo", "bar", "lobster"], ["camel", "trombone", "foo"],
["goat", "car", "hog"], ["foo", "harp", "foo"]]
array.each_with_index.with_object([]) { |(a,i),b|
a.each_with_index { |s,j| b << [i,j] if s == "foo" } }
#=> [[0,0], [1,2], [3,0], [3,2]
It's better to work with flat arrays.
cycle = array.first.length
#=> 3
array.flatten.to_enum.with_index
.select{|e, i| e == "foo"}
.map{|e, i| i.divmod(cycle)}
#=> [[0, 0], [1, 2]]
or
cycle = array.first.length
#=> 3
array = array.flatten
array.each_index.select{|i| array[i] == "foo"}.map{|e, i| i.divmod(cycle)}
#=> [[0, 0], [1, 2]]
How do I turn an Array into a Hash with values of 0 without an each loop.
For example, given this array:
[1, 2, 3, 4]
I want to get this hash:
{"1"=>0, "2"=>0, "3"=>0, "4"=>0}
The standard approach is Hash[...]:
Hash[xs.map { |x| [x.to_s, 0] }]
Or Enumerable#mash if you happen to use Facets. I cannot think of something more concise and declarative:
xs.mash { |x| [x.to_s, 0] }
array.inject({}) { | a, e | a[e.to_s] = 0; a }
or in a more clean way (thanks to tokland, see the discussion in the comments)
array.inject({}) { | a, e | a.update(e.to_s => 0) }
I'm a fan of simple, and I can never remember exactly how crazy things #inject or Hash constructor arguments work.
array = [1, 2, 3, 4]
hash = {}
array.each do |obj|
hash[obj.to_s] = 0
end
puts hash.inspect # {"1"=>0, "2"=>0, "3"=>0, "4"=>0}
Okay, in reality, I'd use each_with_object, but posting this since it's more fun.
ary = *1..4
hash = Hash[ary.zip ary.dup.fill 0]
hash # => {1=>0, 2=>0, 3=>0, 4=>0}
I have an array of hashes. Each hash has an uses key. Multiple hashes can share the same uses value.
[{uses => 0},{uses => 1},{uses => 2},{uses => 1},{uses => 0},{uses => 1},{uses => 3}]
How can I generate an array of the most frequent uses values, in a descending order?
[1,0,2,3]
Referencing this discussion of frequency of items in a list, we can easily modify this for your task.
> unsorted = [{:uses=>0}, {:uses=>1}, {:uses=>2}, {:uses=>1}, {:uses=>0}, {:uses=>1}, {:uses=>3}].map{|h| h[:uses]}
> sorted = unsorted.uniq.sort_by{|u| unsorted.grep(u).size}.reverse
=> [1, 0, 2, 3]
hs.inject({}) do |histogram, h|
histogram.merge(h[:uses] => (histogram[h[:uses]] || 0) + 1)
end.sort_by { |k, v| -v }.map { |k, v| k }
# => [1, 0, 2, 3]
I always recommend to use Facets, though:
http://rubyworks.github.com/facets/doc/api/core/Enumerable.html
hs.frequency.sort_by { |k, v| -v }.map { |k, v| k }
# => [1, 0, 2, 3]
Here is a one pass solution:
a = [{:uses => 0},{:uses => 1},{:uses => 2},{:uses => 1},{:uses => 0},
{:uses => 1},{:uses => 3}]
# A hash with the frequency count is formed in one iteration of the array
# followed by the reverse sort and extraction
a.inject(Hash.new(0)) { |h, v| h[v[:uses]] += 1;h}.
sort{|x, y| x <=> y}.map{|kv| kv[0]}