ruby remove same value multiple keys in hash - ruby

from following hash
hash ={"a"=>100,"b"=>200,"c"=>100,"d"=>120,"e" => 400, "f"=>430, "g"=>500}
I want to remove all the pairs ("key", "value") having either same "value" or have diff of 50(of "value").
example, a=>100 and c => 100, should be removed as they have same "value". And d=>120 should also be removed along with as the difference between 100 and 120 is 20. 400 and 430 should also be removed as the difference is 30.
I should have only
hash["b"=>200,"g"=>500]
Above just an example, in reality, I have hash of 33,000 keys.

A pair of a hash's key/value pairs, k1=>v1 and k2=>v2, are both to be deleted if (v1-v2).abs <= 50. This includes pairs for which v1 == v2, so we need not consider the latter separately. I would do this by first constructing an array of keys to keep, then create a hash comprised of the corresponding key/value pairs from the original hash.
Code
keys_to_keep = hash.keys -
hash.sort_by { |_,v| v }
.each_cons(2)
.each_with_object([]) {
|((k1,v1),(k2,v2)),a| a << k1 << k2 if (v1-v2).abs <= 50 }
keys_to_keep.zip(hash.values_at(*keys_to_keep)).to_h
Explanation
hash = {"a"=>100,"b"=>200,"c"=>100,"d"=>120}
Sort by hash values:
b = hash.sort_by { |_,v| v }
#=> [["a", 100], ["c", 100], ["d", 120], ["b", 200]]
Next, use Enumerable#each_cons to construct an array of all adjacent pairs of elements of b:
c = b.each_cons(2)
#=> #<Enumerator:
# [["a", 100], ["c", 100], ["d", 120], ["b", 200]]:each_cons(2)>
To view the contents of this enumerator:
c.to_a
#=> [[["a", 100], ["c", 100]],
# [["c", 100], ["d", 120]],
# [["d", 120], ["b", 200]]]
Now build an array consisting of keys to be deleted (duplicates OK)
d = c.each_with_object([]) {
|((k1,v1),(k2,v2)),a| a << k1 << k2 if (v1-v2).abs <= 50 }
#=> ["a", "c", "c", "d"]
To compute d, consider the first value passed to the block by the enumerator c:
k1 => "a"
v1 => 100
k2 => "c"
v2 => 100
Since
(100 - 100).abs <= 50
keys k1 and k2 are added to the array of keys to be deleted (block variable a). The next value passed to the block is:
k1 => "c"
v1 => 100
k2 => "d"
v2 => 120
Since
(100 - 120).abs <= 50
the keys "c" and "d" are also added to a. The third value does not add any keys to a since
(120 - 200).abs > 50
Now construct an array of keys to keep by using set difference:
e = hash.keys
#=> ["a", "b", "c", "d"]
keys_to_keep = e - d
#=> ["b"]
Pull out the values for the keys to keep, using Hash#values_at:
f = hash.values_at(*keys_to_keep)
#=> [200]
Construct an array of key/value pairs for keys to keep:
g = keys_to_keep.zip(f)
#=> [["b", 200]]
Convert to a hash.
g.to_h # Ruby v.2.0+
#=> {"b"=>200}
or
Hash[g]
#=> {"b"=>200}

Try this:
multiple_values = hash.group_by { |k, v| v }.select { |v, i| i.length > 1 }.map { |v, i| v }
hash.delete_if { |k, v| multiple_values.any? { |i| v < i + 50 && v > i - 50 } }
The first line builds a histogram for all the values (groups entries by value), and filters out all the values which have only one entry.
This gives us a list of all the values which have more than one key associated with them.
The second pass removes all keys whose values are close to one of these by less than 50.

Related

How can I find the missing letter in an array

Given an array of letters arr = ["a","b","c","d","f"], I would like to construct an array containing all letters between the smallest and largest letters (by ASCII value) in arr that are not contained in arr. Here that would be ["e"]. How can I to that?
a = %w[a b c d f]
(a.min..a.max).to_a - a # => ["e"]
Assuming that the letters in the array are within "a" to "z" and ordered alphabetically, you could fetch the array's first and last element:
given_letters = ["a", "b", "c", "d", "f"]
first_letter = given_letters.first #=> "a"
last_letter = given_letters.last #=> "f"
and call String#upto and Enumerable#to_a to retrieve an array of all successive letters:
all_letters = first_letter.upto(last_letter).to_a
#=> ["a", "b", "c", "d", "e", "f"]
which can be used to calculate the difference to the original array via Array#-:
all_letters - given_letters
#=> ["e"]
Let
ascii = arr.uniq.map(&:ord)
#=> [97, 98, 99, 100, 102]
sm, lg = ascii.minmax
#=> [97, 102]
If it is known that there is exactly one letter e for which sm < e.ord < lg and e is not an element of arr, we can write the following.
((sm+lg)*(lg-sm+1)/2 - ascii.sum).chr
#=> (597 - 496).chr => 101.chr => "e"
That is because sm..lg represents an arithmetic progression; hence, the sum of its elements equals
(sm+lg)*(lg-sm+1)/2
#=> 597

Sum of same values in ruby hash

everybody.
I have hash for example
{-2=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
There can be equal values. My task is to sum keys where values are equal. Result:
{51=>"a", -1=>"c", -38=>"ab"}
How can I do this?
hash.group_by{|key,val| val}
Gives awful result.
hash = {-2=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
hash.reduce({}) do |memo, (k,v)|
memo[v] ||= 0
memo[v] += k
memo
end.invert
# => {51=>"a", -1=>"c", -38=>"ab"}
reduce - lets you build up a new value by iterating over the values of a collection, in this case hash. See the docs for more.
invert - swaps the keys and values of a hash. See the docs for more.
Other ways to do this:
hash.reduce(Hash.new(0)) { |memo, (k,v)| memo[v] += k; memo }.invert
h = {-2=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
then
h.group_by(&:last).each_with_object({}) { |(k,v),h| h[v.map(&:first).sum] = k }
#=> {51=>"a", -1=>"c", -38=>"ab"}
but that would be crazy as it relies on the sums being unique. (Recall that hashes have unique keys.) Suppose
h = {-54=>"a", -1=>"c", 1=>"a", 3=>"a", 49=>"a", -43=>"ab", 5=>"ab"}
then
h.group_by(&:last).each_with_object({}) { |(k,v),h| h[v.map(&:first).sum] = k }
#=> {-1=>"c", -38=>"ab"}
as -1=>"a" is overwritten by -1=<"c". I doubt that this is wanted.
It would be better to save the contents of h in an array:
a = [[-2, "a"], [-1, "c"], [-1, "a"], [49, "a"], [-43, "ab"], [5, "ab"]]
(as it permits duplicate values of the integers--here -1) and then compute
a.group_by(&:last).each_with_object({}) { |(e,ar),h| h[e] = ar.map(&:first).sum }
#=> {"a"=>46, "c"=>-1, "ab"=>-38}
Note that (for the original value of h)
h.group_by(&:last)
#=> {"a"=>[[-2, "a"], [1, "a"], [3, "a"], [49, "a"]],
# "c"=>[[-1, "c"]], "ab"=>[[-43, "ab"], [5, "ab"]]}
and v.map(&:first).sum could be replaced with
v.reduce(0) { |t,(n,_)| t+n }

Inverting a hash value (that's an array) into new individual keys

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}

create date range array from a list of dates using ruby

What is the best way to create an array of hashes with date ranges from a list of dates. For example: If I have a list of dates in an array as below:
['12/11/2014','13/11/2014','14/11/2014','24/11/2014','25/11/2014','26/11/2014','27/11/2014','28/11/2014','29/11/2014','04/12/2014','05/12/2014','06/12/2014','07/12/2014','24/12/2014','25/12/2014','26/12/2014', '28/12/2014', '30/12/2014']
I am trying to get array of date ranges hashes something like below:
[{:from => '12/11/2014', :to => '14/11/2014'}, {:from => '24/11/2014', :to => '29/11/2014'}, {:from => '04/12/2014', :to => '07/12/2014'}, {:from => '24/12/2014', :to => '26/12/1014'}, {:from => '28/12/2014', :to => '28/12/2014'}, {:from => '30/12/2014', :to => '30/12/2014'}]
Anything available in Ruby to generate something like this ? Thanks.
Enumerable has the slice_before method which can do what you want, with the addition of some added code. This is from the documentation:
If the block needs to maintain state over multiple elements, local variables can be used. For example, three or more consecutive increasing numbers can be squashed as follows:
a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")
#=> "0,2-4,6,7,9"
Obviously it isn't the complete answer, but I am not at my computer which has code that will do exactly what you want.
require 'date'
array = ['12/11/2014','13/11/2014','14/11/2014','24/11/2014','25/11/2014','26/11/2014','27/11/2014','28/11/2014','29/11/2014','04/12/2014','05/12/2014','06/12/2014','07/12/2014','24/12/2014','25/12/2014','26/12/2014', '28/12/2014', '30/12/2014']
dates = array.map { |datestr| [datestr, Date.parse(datestr)] }
prev = nil
ranges = dates.slice_before { |datestr, date|
((prev ? date - prev : 1) != 1).tap { prev = date }
}.map { |dates|
{ from: dates[0][0], to: dates[-1][0] }
}
It is a bit more complicated than it needs to be because you're using date strings instead of dates. Also, it might be better to use an array of Range objects than of hashes:
dates = array.map(&Date.method(:parse))
prev = nil
pp dates.slice_before { |date|
((prev ? date - prev : 1) != 1).tap { prev = date }
}.map { |dates| dates[0]..dates[-1] }
One way to do this is to use an enumerator. Suppose you have an array:
a = %w[a b d e f i k l m n o t]
#=> ["a", "b", "d", "e", "f", "i", "k", "l", "m", "n", "o", "t"]
then
arr = [[a.first]]
enum = a[1..-1].to_enum
loop do
e = enum.next
last = arr.last
if last.last.succ == e
last << e
else
arr << [e]
end
end
arr
#=> [["a", "b"], ["d", "e", "f"], ["i"], ["k", "l", "m", "n", "o"], ["t"]]
Enumerator#next raises a StopIteraration exception when an attempt is made to go beyond the last element of the enumerator enum. Kernel#loop handles the exception by breaking out of the loop.
In your application, a will be a sorted array of Date objects. You can use Enumerable#map and the class method Date::parse to convert your strings to Date objects (and sort if necessary). Note that this works with any collection of objects that respond to succ, Date objects being one (Date#succ).

Reorder Ruby array based on the first element of each nested array

My goal is to convert a into b:
a = [["a","b"], ["d", "c"], ["a", "o"], ["d", "g"], ["c", "a"]]
b = [[["a","b"], ["a", "o"]], ["c", "a"], [["d", "c"], ["d", "g"]]
They are grouped by the first element in each nested array. So far I have:
def letter_frequency(c)
d = Hash.new(0)
c.each do |v|
d[v] += 1
end
d.each do |k, v|
end
end
def separate_arrays(arry)
arry2 = []
arry3 = []
big_arry = []
y = 0
while y < arry.length
arry2.push(arry[y][0])
arry3.push(arry[y][1])
y += 1
end
freq = letter_frequency(arry2)
front = arry.slice!(0..(freq["a"] - 1))
end
separate_arrays(a)
Not only does this seem like overkill, but there are now guarantees that "a" will be a legit Hash key, so the last part doesn't work. Thanks for any help.
You can try to do something like this:
a.group_by(&:first).values.map {|e| e.length > 1 ? e : e.flatten}
# => [[["a", "b"], ["a", "o"]], [["d", "c"], ["d", "g"]], ["c", "a"]]
I use the following methods:
Enumerable#group_by (by first element of an array, like in your question):
Returns a hash, which keys are evaluated result from the block, and
values are arrays of elements in enum corresponding to the key.
Hash#values:
Returns a new array populated with the values from hsh. See also Hash#keys.
Enumerable#map (required because you don't want to get nested array when there are only one match, like for c letter):
Returns a new array with the results of running block once for every element in enum.
Enumerable#flatten:
Returns a new array that is a one-dimensional flattening of this array
(recursively). That is, for every element that is an array, extract
its elements into the new array. If the optional level argument
determines the level of recursion to flatten

Resources