Ruby: hash not OK to be sorted - ruby

My hash is grumpy. :(
I want to sort it but it doesn't want, and I can't find the reason of it:
x = [{2 => "two", 1 => "one"}, {4 => "four", 3 => "three"}]
x.each do |y|
if y.is_a?(Hash) then y = y.sort end
end
(the hash has to be sorted in a .each method)
In the end, instead of having this structure:
[{2=>"two", 1=>"one"}, {4=>"four", 3=>"three"}]
I want this structure:
[[[1, "one"], [2, "two"]], [[3, "three"], [4, "four"]]]
After reading some similar questions I tried to replace Hash by ::Hash or convert the hash into an array before sorting it but it still doesn't work...
How can I sort my hash?

arr = [{2=>"two", 1=>"one"}, {4=>"four", 3=> "three"}]
arr.map { |h| h.sort_by(&:first) }
#=> [[[1, "one"], [2, "two"]], [[3, "three"], [4, "four"]]]

Your loop is grumpy not your hash :)
You are sorting the hash, but your loop does not update the array.
Here is a simplified example
array.each do |y|
y = y.sort
end
Above reassigns the sorted hash to the local variable y and does thus not update the value in array. Assignment to the block parameter does not write back to the array that you are enumerating over.
Use map instead
array = array.map do |y|
y.sort
end

The answer of Cary Swoveland is the short one
here I write a longer version
x = [{ 2 => "two", 1 => "one"}, {4 => "four", 3 => "three"}]
y = x.map{ |el| el.to_a.sort }
so the variables should be like
x => [{ 2 => "two", 1 => "one"}, {4 => "four", 3 => "three"}]
y => [[[1, "one"], [2, "two"]], [[3, "three"], [4, "four"]]]

Related

What's the Ruby equivalent of Python `itertools.chain`?

Python's itertools module provides a lots of goodies with respect to processing an iterable/iterator by use of generators. What's the Ruby equivalent of Python itertools.chain?
I am not a Ruby programmer, but I think something like this should do it:
def chain(*iterables)
for it in iterables
if it.instance_of? String
it.split("").each do |i|
yield i
end
else
for elem in it
yield elem
end
end
end
end
chain([1, 2, 3], [4, 5, 6, [7, 8, 9]], 'abc') do |x|
print x
puts
end
Output:
1
2
3
4
5
6
[7, 8, 9]
a
b
c
If you don't want to flatten strings, then using Array#flatten this can be shortened to:
def chain(*iterables)
return iterables.flatten(1) # not an iterator though
end
print chain([1, 2, 3], [4, 5, 6, [7, 8, 9]], 'abc')
#[1, 2, 3, 4, 5, 6, [7, 8, 9], "abc"]
The Ruby equivalent of a Python iterator is an Enumerator. There is no method for chaining two Enumerators, but one can easily be written like so:
class Enumerator
def chain(*others)
self.class.new do |y|
[clone, *others.map(&:clone)].each do |e|
loop do y << e.next end
end
end
end
def +(other)
chain(other)
end
end
e1 = %w[one two].each
e2 = %w[three four].each
e3 = %w[five six].each
e = e1.chain(e2, e3)
e.map(&:upcase)
# => ['ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX']
From the Python docs for itertools.chain:
Make an iterator that returns elements from the first iterable until
it is exhausted, then proceeds to the next iterable, until all of the
iterables are exhausted. Used for treating consecutive sequences as a
single sequence.
First, an example in Python
from itertools import chain
# nested arrays
iterables = [
["one", "two"],
["three", "four"],
["five", "six", "6", ["eight", "nine", "ten"]]
]
list(chain(*iterables))
Output:
['one', 'two', 'three', 'four', 'five', 'six', '6', ['eight', 'nine', 'ten']]
I am learning Ruby, so I tried to replicate the behavior using the code example from the Python docs:
# taken from Python docs as a guide
def chain(*iterables):
# chain('ABC', 'DEF') --> A B C D E F
for it in iterables:
for element in it:
yield element # NOTE! `yield` in Python is not `yield` in Ruby.
# for simplicity's sake think of this `yield` as `return`
My Ruby code:
def chain(*iterables)
items = []
iterables.each do |it|
it.each do |item|
items << item
end
end
items
end
nested_iterables = [%w[one two], %w[three four], %W[five six #{3 * 2}]]
nested_iterables[2].insert(-1, %w[eight nine ten])
puts chain(*nested_iterables)
# and to enumerate
chain(*nested_iterables).each do |it|
puts it
end
Both output:
["one", "two", "three", "four", "five", "six", "6", ["eight", "nine", "ten"]]
Code
def chain(*aaa)
aaa.each { |aa| (aa.class == String ? aa.split(//) : aa).each { |a| yield a } }
end
Example
chain([0, 1], (2..3), [[4, 5]], {6 => 7, 8 => 9}, 'abc') { |e| print e, ',' }
Output
0,1,2,3,[4, 5],[6, 7],[8, 9],a,b,c,
Since Ruby 2.6 Enumerables have a chain method. It does not split up strings in characters.

Grouping an array on the basis of its first element, without duplication in Ruby

I'm executing an active record command Product.pluck(:category_id, :price), which returns an array of 2 element arrays:
[
[1, 500],
[1, 100],
[2, 300]
]
I want to group on the basis of the first element, creating a hash that looks like:
{1 => [500, 100], 2 => [300]}
group_by seems logical, but replicates the entire array. I.e. a.group_by(&:first) produces:
{1=>[[1, 500], [1, 100]], 2=>[[2, 300]]}
You can do a secondary transform to it:
Hash[
array.group_by(&:first).collect do |key, values|
[ key, values.collect { |v| v[1] } ]
end
]
Alternatively just map out the logic directly:
array.each_with_object({ }) do |item, result|
(result[item[0]] ||= [ ]) << item[1]
end
This one-liner seemed to work for me.
array.group_by(&:first).map { |k, v| [k, v.each(&:shift)] }.to_h
Since you're grouping by the first element, just remove it with shift and turn the result into a hash:
array.group_by(&:first).map do |key, value|
value = value.flat_map { |x| x.shift; x }
[key, value]
end #=> {1=>[500, 100], 2=>[300]}
I do not like the destructive operation.
array.group_by(&:first).map { |id, a| [id, a.map(&:last)] }.to_h
Used this functionality several times in my app, added extension to an array:
# config/initializers/array_ext.rb
class Array
# given an array of two-element arrays groups second element by first element, eg:
# [[1, 2], [1, 3], [2, 4]].group_second_by_first #=> {1 => [2, 3], 2 => [4]}
def group_second_by_first
each_with_object({}) { |(first, second), h| (h[first] ||= []) << second }
end
end

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

Create a hash using two arrays

I need to create a new Hash object using two arrays.
But, the conditions is first array value should be a key value for the Hash and second array value should be the Hash value.
a = ["x", "y"]
b = [2, 4]
Result should be: c = {"x" => 2, "y" => 4}
irb(main):001:0> a = ["x", "y"]; b = [2, 4]
=> [2, 4]
irb(main):002:0> Hash[a.zip(b)]
=> {"x"=>2, "y"=>4}

Ruby: Building a hash from a string and two array values at a time

I'm trying to build a hash with:
hash = {}
strings = ["one", "two", "three"]
array = [1, 2, 3, 4, 5, 6]
so that I end up with:
hash = { "one" => [1, 2] ,
"two" => [3, 4] ,
"three" => [5, 6] }
I have tried:
strings.each do |string|
array.each_slice(2) do |numbers|
hash[string] = [numbers[0], numbers[1]]
end
end
But that yields:
hash = { "one" => [5,6] , "two" => [5,6], "three" => [5,6] }
I know why it does this (nested loops) but I don't know how to achieve what I'm looking for.
If you want a one-liner:
hash = Hash[strings.zip(array.each_slice(2))]
For example:
>> strings = ["one", "two", "three"]
>> array = [1, 2, 3, 4, 5, 6]
>> hash = Hash[strings.zip(array.each_slice(2))]
=> {"one"=>[1, 2], "two"=>[3, 4], "three"=>[5, 6]}
hash = {}
strings.each { |string| hash[string] = array.slice!(0..1) }
This is a solution using methods and techniques you seem familiar with. It is not a 'one liner' solution but if you are new might be more understandable for you. The first answer is very elegant though.
As Mu says, Zip method is the best choose:
Converts any arguments to arrays, then merges elements of self with corresponding elements from each argument. This generates a sequence of self.size n-element arrays, where n is one more that the count of arguments. If the size of any argument is less than enumObj.size, nil values are supplied. If a block is given, it is invoked for each output array, otherwise an array of arrays is returned.

Resources