How to instantiate a multilevel hash with an arbitrary depth - ruby

I want to create a hash from the result of a database query such that its keys are the column values except the last one, and the value are the last column value (or a default value). For example, if I have rows:
1 2 3 1
1 2 4 9
1 3 2 nil
and a default value of 111, I should get:
{
1 =>
{
2 => { 3 => 1, 4 => 9},
3 => { 2 => 111}
}
}
I want to make the method generic enough to handle an arbitrary number of columns, so the signature could be:
to_lookup(rows, default_value, value_column, *columns)
How would I go about that?
Update: forgot a comma in the output.

[Edit: after reading #cthulhu's answer, I think I may have misinterpreted the question. I assumed that consecutive rows were to be grouped, rather than all rows to be grouped. I will leave my answer for the former interpretation.]
I believe this is what you are looking for:
def hashify(arr)
return arr.first.first if arr.first.size == 1
arr.slice_when { |f,s| f.first != s.first }.
each_with_object({}) do |a,h|
key, *rest = a.transpose
h[key.first] = hashify(rest.transpose)
end
end
hashify [[1, 2, 3, 1], [1, 2, 4, 9], [1, 3, 2, nil]]
#=> {1=>{2=>{3=>1, 4=>9}, 3=>{2=>nil}}}
hashify [[1, 2, 3, 1], [1, 2, 4, 9], [2, 3, 2, nil]]
#=> {1=>{2=>{3=>1, 4=>9}}, 2=>{3=>{2=>nil}}}
Replacing nil with the default can be done before or after the construction of the hash.
Enumerable#slice_when was bestowed upon us in v2.2. For earlier versions, you could replace:
arr.slice_when { |f,s| f.first != s.first }
with
arr.chunk { |row| row.first }.map(&:last)

I simplified things by removing the ability to pass a default,
I also simplified the signature method to have only one parameter.
RSpec.describe "#to_lookup" do
def to_lookup(rows)
return rows.first.first if rows.flatten.size == 1
h = {}
rows.group_by { |e| e.first }.each_entry do |k, v|
v.each &:shift
h[k] = to_lookup(v)
end
h
end
let :input do
[
[1, 2, 3, 1],
[1, 2, 4, 9],
[1, 3, 2, 111],
]
end
let :output do
{
1 => {
2 => {3 => 1, 4 => 9},
3 => {2 => 111}
}
}
end
it { expect(to_lookup(input)).to eq(output) }
end
BTW I wonder what output do you want for following input:
1 2 3 1
1 2 3 2
EDIT: working code snippet: http://rubysandbox.com/#/snippet/566aefa80195f1000c000000

Related

How to modify the implementation of the map function in the Array class in Ruby

Is there a way to modify the implementation of map() in the Array class such that it only affects certain indices of the array?
Example:
a = [1, 2, 3, 4, 5]
a.map(2..4) { |x| x*2 }
Now, a = [1, 2, 6, 8, 10] since the map function was only used on indices 2 and 3.
You could (but really shouldn't) do this:
Array.class_eval do
def map(range = nil)
return super() if range.nil?
return self[range].map unless block_given?
self[range].map { |x| yield x }
end
end
[1, 2, 3, 4, 5].map(2..4) { |x| x * 2 }
# => [6, 8, 10]
Ruby already has a MUCH more normal/nice/better way of "select only certain indices", with arr[2..4]:
arr = [1, 2, 3, 4, 5]
arr[2..4].map { |x| x * 2 }
# => [6, 8, 10]
I've avoided mutation above, but if you must have that as well, you can do something similar to the above, just with map! instead.

How to group adjacent numbers that are the same

I need to pack if there are at least two adjacent numbers which are same in the format <number : number_of_occurrences >.
This is my input:
[2,2,2,3,4,3,3,2,4,4,5]
And the expected output:
"2:3,3,4,3:2,2,4:2,5"
So far I tried:
a = [1, 1, 1, 2, 2, 3, 2, 3, 4, 4, 5]
a.each_cons(2).any? do |s , t|
if s == t
If it's equal try a counter maybe, but thats not working.
You can use Enumerable#chunk_while (if you're on Ruby >= 2.3):
a.chunk_while { |a, b| a == b }
.flat_map { |chunk| chunk.one? ? chunk.first : "#{chunk.first}:#{chunk.size}" }
.join(',')
#=> "2:3,3,4,3:2,2,4:2,5"
You can also use Enumerable#chunk (Ruby ~1.9.3, maybe earlier):
a.chunk(&:itself)
.flat_map { |_, chunk| chunk.one? ? chunk.first : "#{chunk.first}:#{chunk.size}" }
.join(',')
#=> "2:3,3,4,3:2,2,4:2,5"
You could chunk elements together when they're equal, you could also slice the array between elements that are distinct (slice_when has been added in Ruby 2.2 ):
[2, 2, 2, 3, 4, 3, 3, 2, 4, 4, 5].slice_when { |a, b| a != b }.map do |ints|
if ints.size == 1
ints[0]
else
"#{ints[0]}:#{ints.size}"
end
end.join(',')
# "2:3,3,4,3:2,2,4:2,5"
It's mostly a matter of taste, both methods can achieve perfectly similar results, just like select and reject.
arr = [2, 2, 2, 3, 4, 3, 3, 2, 4, 4, 5]
arr.drop(1).each_with_object([[arr.first, 1]]) do |e,a|
a.last.first == e ? a[-1][-1] += 1 : a << [e, 1]
end.map { |a| a.join(':') }.join(',')
#=> "2:3,3:1,4:1,3:2,2:1,4:2,5:1"

Ruby: Is there any use for select/find without a block?

I have a lazy evaluation, where I want the first truthy result resulting from a map operation, and once again I found myself writing .find { |e| e } on the end of my expression.
Here's a simple example; the array and map block are, of course, different in my real life:
[nil, 2, 3, 4].lazy.map{ |e| e }.find { |e| e }
I'm always a little surprised/disappointed when I have to add the block { |e| e } to a select or find, especially if it's a lazy evaluation, because both - redundantly - seem to be identity functions by default:
> [nil, 2, 3, 4].find { |e| e }
=> 2
> [nil, 2, 3, 4].find
=> #<Enumerator: [nil, 2, 3, 4]:find>
> [nil, 2, 3, 4].find.map { |e| e }
=> [nil, 2, 3, 4]
Does this Enumerator practically differ at all from the one obtained from .each?
> [nil, 2, 3, 4].each.map { |e| e }
=> [nil, 2, 3, 4]
Similarly with select, except that's even more unhelpful with lazy:
> [nil, 2, 3, 4].select
=> #<Enumerator: [nil, 2, 3, 4]:select>
> [nil, 2, 3, 4].select { |e| e }
=> [2, 3, 4]
> [nil, 2, 3, 4].select.lazy.force # doing it wrong looks functional!
=> [nil, 2, 3, 4]
> [nil, 2, 3, 4].lazy.select { |e| e }.force
=> [2, 3, 4]
> [nil, 2, 3, 4].lazy.select.force # same without .force
ArgumentError: tried to call lazy select without a block
Are these apparent identities (and ArgumentError!) useful, or just an opportunity for a better default in a future version of Ruby?
First of all - a small remark. If you ever find yourself typing { |e| e }, you can instead use &:itself.
With that out of the way, enumerable methods without a block often times return an enumerator. You can use that to chain with enumerator methods. For example, consider:
[1, 2, 3].map.with_index { |n, i| n + i } # => [1, 3, 5]
[1, 2, 3].each.with_index { |n, i| n + i } # => [1, 2, 3]
[1, 2, 3].select.with_index { |n, i| (n + 2 * i).even? } # => [2]

Get items from Ruby Array that occur 2 or more times

Let's say I have a Ruby array.
[1,2,3,4,4,5,6,6,7,7]
I want to find the values that occur 2 or more times.
[4,6,7]
It will help my process to first determine which items occur only once then remove those. So I'd like to solve this by first finding the items that occur once.
There are probably better ways, but this is one:
> [1,2,3,4,4,5,6,6,7,7].group_by{|i| i}.reject{|k,v| v.size == 1}.keys
=> [4, 6, 7]
Breaking it down:
> a = [1,2,3,4,4,5,6,6,7,7]
=> [1, 2, 3, 4, 4, 5, 6, 6, 7, 7]
> a1 = a.group_by{|i| i}
=> {1=>[1], 2=>[2], 3=>[3], 4=>[4, 4], 5=>[5], 6=>[6, 6], 7=>[7, 7]}
> a2 = a1.reject{|k,v| v.size == 1}
=> {4=>[4, 4], 6=>[6, 6], 7=>[7, 7]}
> a2.keys
=> [4, 6, 7]
Everyone loves a really difficult to follow one liner :)
[1,2,3,4,4,5,6,6,7,7].each_with_object(Hash.new(0)) { |o, h| h[o] += 1 }.select { |_, v| v > 1 }.keys
Add some white space and some comments
[1,2,3,4,4,5,6,6,7,7].each_with_object(Hash.new(0)) { |o, h|
h[o] += 1
}.select { |_, v|
v > 1
}.keys
Enumerate and pass in our memo hash to each iteration the Hash defaults to having 0 for any key
Increment counter for the object
Select only key value pairs where the value is greater than 1
Grab just the keys
This looks quite similar to Phillip's neat answer - in theory this should use slightly less memory as it will not have to build the intermediate arrays to perform counting
Another way:
a = [1,2,3,4,4,5,6,6,7,7]
au = a.uniq
a.reject { |i| au.delete(i) }
#=> [4, 6, 7]
If efficiency is important, you could use a set:
require 'set'
s = Set.new
a.reject { |e| s.add?(e) }
#=> [4, 6, 7]
You can use Array#select to return the elements where Array#count is greater than 1:
2.1.2 :005 > arr = [1,2,3,4,4,5,6,6,7,7]
=> [1, 2, 3, 4, 4, 5, 6, 6, 7, 7]
2.1.2 :006 > arr.select { |e| arr.count(e) > 1 }.uniq
=> [4, 6, 7]
Hope this helps

Finding duplicates in nested arrays

I have a hash, which contains a hash, which contains a number of arrays, like this:
{ "bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 9],
"bar" => [8, 10, 12]
}
}
I would like to compare the arrays against the other arrays, and then alert me if they are duplicates. It is possible for hash["bob"]["foo"] and hash["fred"]["foo"] to have duplicates, but not for hash["bob"]["foo"] and hash["bob"]["bar"]. Same with hash["fred"].
I can't even figure out where to begin with this one. I suspect inject will be involved somewhere, but I could be wrong.
This snippet will return an array of duplicates for each key. Duplicates can only be generated for equal keys.
duplicates = (keys = h.values.map(&:keys).flatten.uniq).map do |key|
{key => h.values.map { |h| h[key] }.inject(&:&)}
end
This will return [{"foo"=>[1]}, {"bar"=>[]}] which indicates that the key foo was the only one containing a duplicate of 1.
The snippet above assume h is the variable name of your hash.
h = {
"bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 9],
"bar" => [1, 10, 12]
}
}
h.each do |k, v|
numbers = v.values.flatten
puts k if numbers.length > numbers.uniq.length
end
There are many ways to do it.
Here's one that should be easy to read.
It works in Ruby 1.9. It uses + to combine two arrays and then uses the uniq! operator to figure out whether there is a duplicate number.
h = { "bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 12],
"bar" => [8, 10, 12]
}
}
h.each do |person|
if (person[1]["foo"] + person[1]["bar"]).uniq! != nil
puts "Duplicate in #{person[1]}"
end
end
I'm not sure what exactly you are looking for. But at look at a possible solution, perhaps you can reuse something.
outer_hash.each do |person, inner_hash|
seen_arrays = Hash.new
inner_hash.each do |inner_key, array|
other = seen_arrays[array]
if other
raise "array #{person}/#{inner_key} is a duplicate of #{other}"
end
seen_arrays[array] = "#{person}/#{inner_key}"
end
end

Resources