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}
"e"=>180would mean the resulting hash should be empty?