Sort hash by key in descending order - ruby

I have the following hash:
{"2013-08-12"=> 10, "2013-08-13"=> 20, "2013-11-11"=>30, "2013-11-14"=> 40}
What I want to do is to sort it by key (dates in format yyyy-mm-dd) in descending order:
{"2013-11-14"=> 40, "2013-11-11"=>30, "2013-08-13"=> 20, "2013-08-12"=> 10}
Is this possible?

It is possible.
Hash[
{"2013-08-12"=> 10, "2013-08-13"=> 20, "2013-11-11"=>30, "2013-11-14"=> 40}
.sort_by{|k, _| k}.reverse
]
# => {
"2013-11-14" => 40,
"2013-11-11" => 30,
"2013-08-13" => 20,
"2013-08-12" => 10
}

Related

Array deducting 1 from previous element when repeated more than once

I have the following array:
Input:
array = [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
Ouput:
array = [211, 200, 199, 198, 197, 196 ... ]
I've tried each_with_index but couldn't get the desired result.
I don't understand what are to be done with nils, so I haven't addressed that. Let arr be array or array.sort.reverse, depending on requirements. I think this is want you want to do? (See my comment on the question.)
def change_em(arr)
dup_indices = arr.each_index
.group_by { |i| arr[i] }
.values
.flat_map { |a| a.drop(1) }
puts "dup_indices = #{dup_indices}"
last = 0 # anything '-' responds to
arr.each_index.map { |i| last = dup_indices.include?(i) ? last-1 : arr[i] }
end
I've included the puts just to clarify what I'm doing here.
change_em [10, 8, 5, 5, 7]
#=> dup_indices = [3]
#=> [10, 8, 5, 4, 7]
change_em [10, 8, 7, 5, 5]
#=> dup_indices = [4]
#=> [10, 8, 7, 5, 4]
change_em [10, 9, 9, 8, 8, 8]
#=> dup_indices = [2, 4, 5]
#=> [10, 9, 8, 8, 7, 6]
change_em [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 196]
#=> dup_indices = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
#=> [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 196]
Notice that the statement
last = dup_indices.include?(i) ? last-1 : arr[i]
is doing double-duty: it updates the value of last and returns the mapped value for the index i. Note also that dup_indices cannot contain 0.
Not sure I fully understand your requirements, but here's my attempt:
# Transforms an array of numbers into a sorted array of the same length, where
# each successive element is always smaller than the preceding element.
def force_descending(array)
array.sort.reverse.each_with_object([]) do |element, collection|
collection << if collection.empty? || element < collection.last
element
else
collection.last-1
end
end
end
Sample inputs/outputs:
force_descending [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
#=> [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190]
force_descending [10, 8, 5, 5, 7]
#=> [10, 8, 7, 5, 4]
force_descending [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 196]
#=> [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 189]
Wrote this is a more functional style.
def f(arr, dup_element = nil, dup_count = 0)
return generate_dup_array(dup_element, dup_count) if arr.empty?
if arr.head != arr.tail.head # Not duplicates
if dup_count == 0 # No duplicates to insert
[arr.head] + f(arr.tail)
else # There are duplicates to insert
generate_dup_array(dup_element, dup_count) + f(arr.tail)
end
else # Duplicate found, continue with tail of array and increase dup_count
f(arr.tail, arr.head, dup_count + 1)
end
end
def generate_dup_array(dup_element, dup_count)
return [] if dup_count == 0
(dup_element - dup_count..dup_element).to_a.reverse
end
class Array
def head; self.first; end
def tail; self[1..-1]; end
end
p f [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
# => [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190]
p f [10, 8, 5, 5, 7].sort.reverse
# => [10, 8, 7, 5, 4]
p f [9, 6, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, 1]
# => [9, 6, 5, 5, 4, 4, 3, 3, 2, 1, 2, 1, 1]
its already in decsending order
For the one-liner crowd:
results = numbers.chunk {|num| num}.flat_map {|num, group| (group.length == 1) ? num : ((num - (group.length-1))..num).to_a.reverse}
For sane programmers:
numbers = [211, 200, 200, 200]
start_of_dups = "_START_" #Something not in the array
dup_count = 0
results = numbers.map do |num|
if start_of_dups == num
dup_count += 1
num - dup_count
else
dup_count = 0
start_of_dups = num
end
end
p results
--output:--
[211, 200, 199, 198]
But if:
array = [10, 10, 10, 9]
--output:--
[10, 9, 8, 9]

How to find some arrays with certain value and sum their values?

I have an array of arrays with a lot of value. Where always the first and third element is repeated and I need to sum only the second element, because this is a number that I want totalizing.
Then make a new array with all values totalizing.
Example
# My array
=> [ ['abc', 13, 40 ], [ 'abc', 20, 40 ], [ 'abc', 2, 40 ], [ 'cde', 90, 20 ], [ 'cde', 60, 20 ], [ 'fgh', 20, 50 ] ]
# My Expected Result
=> [ ['abc', 35, 40 ], ['cde', 150, 20], ['fgh', 20, 50] ]
What would be the optimal way to do that? What functions could be used to reach that result?
You can do that like this:
arr = [[ 'abc', 13, 40 ], [ 'abc', 20, 40 ], [ 'abc', 2, 40 ],
[ 'cde', 90, 20 ], [ 'cde', 60, 20 ], [ 'fgh', 20, 50 ] ]
arr.group_by { |a,_,c| [a,c] }
.map { |(a,b),arr| [a,arr.reduce(0) { |t,(_,e,_)| t + e },b] }
#=> [["abc", 35, 40],
# ["cde", 150, 20],
# ["fgh", 20, 50]]
This is how this works:
f = arr.group_by { |a,_,c| [a,c] }
#=> {["abc", 40]=>[["abc", 13, 40], ["abc", 20, 40], ["abc", 2, 40]],
# ["cde", 20]=>[["cde", 90, 20], ["cde", 60, 20]],
# ["fgh", 50]=>[["fgh", 20, 50]]}
map passes the first key-value pair of the hash f into its block, assigning the block variables (using decomposition) as follows:
a,b = ["abc", 40]
arr = [["abc", 13, 40], ["abc", 20, 40], ["abc", 2, 40]]
and then computes:
[a,g,b]
#=> ["abc", g, 40]
where
g = arr.reduce(0) { |t,(_,e,_)| t + e }
#=> 35
so
["abc", 40]=>[["abc", 13, 40], ["abc", 20, 40], ["abc", 2, 40]]
is mapped to:
["abc", 35, 40]
The two other elements of the hash f are computed similarly.
arr.group_by { |a,_,c| [a,c] }.values.map{|x|[x[0][0],x.map{|n|n[1]}.reduce(&:+),x[0][2]]}

How to insert elements into an array

I have an array consisting of unknown elements:
myary = [100, "hello", 20, 40, "hi"]
I want to put integer 10 after each element to make it into this:
myary = [100, 10, "hello", 10, 20, 10, 40, 10, "hi", 10]
Is there a way or a method to do it?
Another problem is that I need to add integer 10 before a string "hello".
myary = [100, 10,"hello", 20, 40, "hi"]
Is this what you want ?
myary = [100, "hello", 20, 40, "hi"]
myary.flat_map { |i| [i, 10] }
# => [100, 10, "hello", 10, 20, 10, 40, 10, "hi", 10]
myary.flat_map { |i| i == 'hello' ? [10, i] : i }
# => [100, 10,"hello", 20, 40, "hi"]
Read #flat_map method.

Combining two hashes with a common key

I have been trying to combine two hashes in Ruby. For example:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
The output I would like is:
h{"a"=> 10, 11, "b"=>20,21, "c"=> 34, "d"=>3,15}
Each hash has the same key, except the second hash might be missing some. I would like the two values then to be represented by the same key.
This is my unsuccessful code:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h3= h1.update(h2){|key1, key2, val1, val2 |key1,h2_val=h2}
It gives:
{"a"=>{"a"=>11, "b"=>21, "d"=>15}, "b"=>{"a"=>11, "b"=>21, "d"=>15}, "c"=>34, "d"=>{"a"=>11, "b"=>21, "d"=>15}}
I am just new to Ruby so I assume I am missing something very basic here. I would appreciate any help.
What about:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
p h1.merge(h2){|key, old, new| Array(old).push(new) } #=> {"a"=>[10, 11], "b"=>[20, 21], "c"=>34, "d"=>[3, 15]}
And this is how I would write it to combine more than 2 Hashes:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h3 = { "a" => 11, "b" => 21, "c"=> 1, "d"=>15}
merge_to_array = -> x,y { x.merge(y){|key, old, new| Array(old).push(new)} }
p [h1,h2,h3].reduce &merge_to_array #=> {"a"=>[10, 11, 11], "b"=>[20, 21, 21], "c"=>[34, 1], "d"=>[3, 15, 15]}
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
arr= []
arr << h1 << h2
data= arr.map(&:to_a).flatten(1).reduce({}) {|h,(k,v)| (h[k] ||= []) << v; h}
and it gives
{"a"=>[10, 11], "b"=>[20, 21], "c"=>[34], "d"=>[3, 15]}
As example use #reduce:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h2.reduce(h1.dup) {|h,(k,v)| h[k] = (h[k] && [h[k], v] || v); h}
# => {"a"=>[10, 11], "b"=>[20, 21], "c"=>34, "d"=>[3, 15]}
It's not entirely clear what you're looking for, because your example output is invalid, but here's what I'd do to merge the two hashes without stomping on the keys:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
new_hash = Hash.new{ |h, k| h[k] = [] }
[*h1, *h2].each { |k, v| new_hash[k] << v }
Which results in:
new_hash # => {"a"=>[10, 11], "b"=>[20, 21], "c"=>[34], "d"=>[3, 15]}

Sort array using values from separate hash

I have an array of IDs and a separate hash that dictates how those IDs should be ordered. Something like this:
Array:
[
7,
3,
2,
]
Sort ranks:
{
1 => 50,
2 => 70,
3 => 10,
4 => 80,
5 => 60,
6 => 20,
7 => 0,
}
I want to elegantly reverse sort the first array by using the values of the hash. So the result should be this:
[
2,
3,
7,
]
I know there are brute force methods, but is there an easier and more performant way to do this?
assuming you have order_hash and object_array defined, you can sort using following code:
object_array.sort{ |a, b| order_hash[b[:id]] <=> order_hash[a[:id]] }
you can use some constant for instead of order_hash if you want.
I'm not sure about more performant but here's a relatively simple way
arr = [
{
:id => 7,
},
{
:id => 3,
},
{
:id => 2,
}
]
ranks = {
1 => 50,
2 => 70,
3 => 10,
4 => 80,
5 => 60,
6 => 20,
7 => 0,
}
arr2 = arr.sort_by{|elem| -ranks[elem[:id]]}
arr2 # => [{:id=>2}, {:id=>3}, {:id=>7}]
Use Enumerable#sort_by:
input = [
7,
3,
2,
]
ranks = {
1 => 50,
2 => 70,
3 => 10,
4 => 80,
5 => 60,
6 => 20,
7 => 0,
}
result = input.sort_by{|x| ranks[x]} #=> [7, 3, 2]

Resources