Find the closest value in multidimensional arrays with ruby - arrays

I need to find the closest values from a set multidimensional arrays, here is the example:
a = [[a,b,1,2,3],[a,b,5,6,7],[a,b,8,9,10],[c,d,1,2,9],[c,d,1,7,8]]
I want to search the value closest to 1.8 from the overall arrays that contain the same elements in the first 2 index. My expected result is this:
a = [[a,b,1,2,3],[c,d,1,2,9]]
Any ideas? Thanks!

a = [[:a, :b, 1, 2, 3], [:a, :b, 5, 6, 7], [:a, :b, 8, 9, 10], [:c, :d, 1, 2, 9], [:c, :d, 1, 7, 8]]
a.
group_by {|a, b, *_| [a, b] }.
map {|_k, v|
v.min_by {|_, _, *nums|
nums.map {|num| (1.8 - num).abs }.min }}
# => [[:a, :b, 1, 2, 3], [:c, :d, 1, 2, 9]]
Group the arrays by their first two elements
We are not interested in the keys, so we map to v
We want to find some sort of minimum
The criterion is: we want to find the array which contains the element which has the minimal absolute difference from 1.8, so we only have to consider the minimal distance of the three

Assumption
In view of your comment, my understanding is that if:
a = [[:a, :b, 2], [:a, :b, 3], [:c, :d, 1], [:c, :d, 4]]
and the target value is 1.8, the desired result is:
[[:a, :b, 2]]
and not:
[[:a, :b, 2], [:c, :d, 1]]
as the "closest value" is 2 and
[[:c, :d, 1], [:c, :d, 4]].flatten.include?(2) #=> false
(At the end, however, I offer a solution for the case where "closest "value" is to be obtained for every group of elements having the same first two elements.)
Code
If my assumption is correct, obtaining the desired result is simple:
def extract_closest(a, target)
closest = a.flatten.
select { |e| e.respond_to?(:abs) }.
min_by { |e| (e.to_f-target).abs }
a.select { |e| e.include?(closest) }.uniq { |e| e[0,2] }
end
Examples
target = 1.8
a1 = [[:a, :b, 1, 2, 3], [:a, :b, 5, 6, 7], [:a, :b, 8, 9, 2],
[:c, :d, 1, 2, 9], [:c, :d, 1, 7, 8]]
(Notice that I've changed the last element of a1[2] from the example.)
extract_closest(a1, target)
#=> [[:a, :b, 1, 2, 3], [:c, :d, 1, 2, 9]]
a2 = [[:a, :b, 1, 2, 3], [:a, :b, 5, 6, 7], [:a, :b, 8, 9, 2],
[:c, :d, 1, 3, 9], [:c, :d, 1, 7, 8]]
extract_closest(a2, target)
#=> [[:a, :b, 1, 2, 3]]
Explanation
For the first example above, the steps are as follows:
b = a1.flatten
#=> [:a, :b, 1, 2, 3, :a, :b, 5, 6, 7, :a, :b, 8, 9, 10,
# :c, :d, 1, 2, 9, :c, :d, 1, 7, 8]
c = b.select { |e| e.respond_to?(:-) }
#=> [1, 2, 3, 5, 6, 7, 8, 9, 10, 1, 2, 9, 1, 7, 8]
closest = c.min_by { |e| (e.to_f-target).abs }
#=> 2
enum = a.select
#=> #<Enumerator: [[:a, :b, 1, 2, 3], [:a, :b, 5, 6, 7],
# [:a, :b, 8, 9, 10], [:c, :d, 1, 2, 9], [:c, :d, 1, 7, 8]]
# :select>
e = enum.next
#=> [:a, :b, 1, 2, 3]
e.include?(closest)
#=> [:a, :b, 1, 2, 3].include?(2) => true
so [:a, :b, 1, 2, 3] is selected.
e = enum.next
#=> [:a, :b, 5, 6, 7]
e.include?(closest)
#=> [:a, :b, 5, 6, 7].include?(2) #=> false
so [:a, :b, 5, 6, 7] is not select. The remaining elements of enum are processed similarly, resulting in a return value of:
f = [[:a, :b, 1, 2, 3], [:a, :b, 8, 9, 2], [:c, :d, 1, 2, 9]]
Lastly, we need only one of the first two elements of f, so:
f.uniq { |e| e[0,2] }
#=> [[:a, :b, 1, 2, 3], [:c, :d, 1, 2, 9]]
Alternative assumption
If a "closest "value" is to be obtained for every group of elements having the same first two elements. In addition, to respond to your comment, I'll permit any of the elements of each array to be any object:
def extract_closest(a, target)
a.each_with_object({}) do |e,h|
min_diff = e[2..-1].select { |n| n.respond_to?(:abs) }.
map { |n| (n.to_f-target).abs }.min
h.update(e[0,2]=>[min_diff, e]) do |_,(omin,oe),(nmin,ne)|
(nmin < omin) ? [nmin, ne] : [omin, oe]
end
end.values.map(&:last)
end
target = 1.8
a3 = [[:a, :b, 1, 2, :c], [:a, :b, 5, "c", 7], [:a, :b, 8, 9, [1,2]],
[:c, :d, 1, 3, {e: 1.8}], [:c, :d, 1, 7, "8"]]
extract_closest(a3, target)
#=> [[:a, :b, 1, 2, :c], [:c, :d, 1, 3, {:e=>1.8}]]
This uses the form of Hash#update (aka merge!) that uses a block to determine the value of keys that are present in both hashes being merged.
Explanation for alternative assumption
For the example given:
enum0 = a3.each_with_object({})
#=> #<Enumerator: [[:a, :b, 1, 2, :c], [:a, :b, 5, "c", 7],
# [:a, :b, 8, 9, [1, 2]], [:c, :d, 1, 3, {:e=>1.8}],
# [:c, :d, 1, 7, "8"]]:each_with_object({})>
e,h = enum0.next
#=> [[:a, :b, 1, 2, :c], {}]
e #=> [:a, :b, 1, 2, :c]
h #=> {}
b = e[2..-1].select { |n| n.respond_to?(:abs) }
#=> [1, 2]
c = b.map { |n| (n.to_f-target).abs }
#=> [0.8, 0.2]
min_diff = c.min
#=> 0.2
h.update(e[0,2]=>[min_diff, e]) do |_,(omin,oe),(nmin,ne)|
(nmin < omin) ? [nmin, ne] : [omin, oe]
end
#=> {}.update([:a, :b]=>[0.2, [:a, :b, 1, 2, :c]])
#=> {[:a, :b]=>[0.2, [:a, :b, 1, 2, :c]]}
as {} does not contain the key [:a, :b], update's block is not called upon. Note that update returns the updated value of h.
e,h = enum0.next
#=> [[:a, :b, 5, "c", 7],
# {[:a, :b]=>[0.2, [:a, :b, 1, 2, :c]]}]
b = e[2..-1].select { |n| n.respond_to?(:abs) }
#=> [5, 7]
c = b.map { |n| (n.to_f-target).abs }
#=> [3.2, 5.2]
min_diff = c.min
#=> 3.2
h.update(e[0,2]=>[min_diff, e]) do |_,(omin,oe),(nmin,ne)|
(nmin < omin) ? [nmin, ne] : [omin, oe]
end
update first considers:
h.update(e[0,2]=>[min_diff, e])
#=> {[:a, :b]=>[0.2, [:a, :b, 1, 2, :c]]}.
# update([:a, :b]=> [3.2, [:a, :b, 5, "c", 7]])
As both hashes being merged are found to have the key [:a, :b], update's block is called upon to determine the value of that key in the merged hash:
# do |_,(0.2, [:a, :b, 1, 2, :c]), (3.2, [:a, :b, 5, "c", 7])|
# (3.2 < 0.2) ? [3.2, [:a, :b, 5, "c", 7]] : [0.2, [:a, :b, 1, 2, :c]]
# end
As 3.2 < 0.2 #=> false, the block returns:
[0.2, [:a, :b, 1, 2, :c]]
for the value of the key [:a, :b]; that is, the value is unchanged.
The remaining calculations are similar.

Related

Array combinatorics

I am working in Ruby 2.7.
I have an Array, whose elements are one of three things: either a String or an Integer, or occasionally another Array with a pair of elements (this time, only Strings or Integers). There are no further levels of nesting for Arrays, by the definition of my problem.
Examples:
w = [1, 2, 'b', 4]
x = [[2, 'r'],[2, 'g']]
y = [[2, 'w']]
z = ['u', 6, [2, 'r']]
The context of the problem is that the inner arrays represent selectable alternatives for use in the outer array. I am looking for all possible ways to choose an element from each inner array, if present, and substitute that element into the outer array.
Examples:
f(w) = [ [1, 2, 'b', 4] ]
f(x) = [ [2, 2], [2, 'g'], ['r', 2], ['r', 'g'] ]
f(y) = [ [2], ['w'] ]
f(z) = [ ['u', 6, 2], ['u', 6, 'r'] ]
Order of elements in each selection (inner array of each solution) does not matter. Order of solutions in output also does not matter.
I can brute force an answer selecting/rejecting, transforming, and using Array#product; but I am looking for an approach that is as brief and elegant as possible.
The rules given in the question are unprecise and incomplete, so I've done my best to infer what they are.
Code
def doit(arr)
if arr.size == 1
e = arr.first
return e.is_a?(Array) ? (arr.map { |f| [f] }) : arr
end
(first, *rest), other = arr.partition { |e| e.is_a?(Array) }
return [arr] if first.nil?
prod = first.product(*rest)
return prod if other.empty?
prod.flat_map { |a| other.map { |e| [e, *a] } }
end
See Enumerable#partition, Array#product and Enumerable#flat_map.
Examples
doit [1, 2, 'b', 4]
#=> [[1, 2, "b", 4]]
doit [[2, 'r'],[2, 'g']]
#=> [[2, 2], [2, "g"], ["r", 2], ["r", "g"]]
doit [:a, [2, 'r'], :b, [2, 'g']]
#=> [[:a, 2, 2], [:b, 2, 2], [:a, 2, "g"], [:b, 2, "g"],
# [:a, "r", 2], [:b, "r", 2], [:a, "r", "g"], [:b, "r", "g"]]
doit [[2, 'w']]
#=> [[[2, "w"]]]
doit ['u', 6, [2, 'r']]
#=> [["u", 2], [6, 2], ["u", "r"], [6, "r"]]
doit [:a, [2, 3], :b, :c, [4, 5], :d, [6, 7]]
#=> [[:a, 2, 4, 6], [:b, 2, 4, 6], [:c, 2, 4, 6], [:d, 2, 4, 6],
# [:a, 2, 4, 7], [:b, 2, 4, 7], [:c, 2, 4, 7], [:d, 2, 4, 7],
# [:a, 2, 5, 6], [:b, 2, 5, 6], [:c, 2, 5, 6], [:d, 2, 5, 6],
# [:a, 2, 5, 7], [:b, 2, 5, 7], [:c, 2, 5, 7], [:d, 2, 5, 7],
# [:a, 3, 4, 6], [:b, 3, 4, 6], [:c, 3, 4, 6], [:d, 3, 4, 6],
# [:a, 3, 4, 7], [:b, 3, 4, 7], [:c, 3, 4, 7], [:d, 3, 4, 7],
# [:a, 3, 5, 6], [:b, 3, 5, 6], [:c, 3, 5, 6], [:d, 3, 5, 6],
# [:a, 3, 5, 7], [:b, 3, 5, 7], [:c, 3, 5, 7], [:d, 3, 5, 7]]
Note that the return value for doit [[2, 'w']] is different than that given in the question.
Explanation
The steps are as follows.
arr = [:a, [2, 'r'], :b, [2, 'g']]
(first, *rest), other = arr.partition { |e| e.is_a?(Array) }
#=> [[[2, "r"], [2, "g"]], [:a, :b]]
Ruby applies array decomposition to the above expression to obtain values for first, rest and other:
first
#=> [2, "r"]
rest
#=> [[2, "g"]]
other
#=> [:a, :b]
Continuing, because first.nil? #=> false in return [arr] if first.nil?, we do not return. Next:
prod = first.product(*rest)
#=> [[2, 2], [2, "g"], ["r", 2], ["r", "g"]]
other.empty? #=> false in return prod if other.empty? so we do not return.
I can best explain the remaining calculations by inserting puts statement in the code and running it.
prod.flat_map do |a|
puts "a = #{a}"
other.map do |e|
puts " e = :#{e}"
puts " [e, *a] = #{[e, *a]}"
[e, *a]
end
end
#=> [[:a, 2, 2], [:b, 2, 2], [:a, 2, "g"], [:b, 2, "g"],
# [:a, "r", 2], [:b, "r", 2], [:a, "r", "g"], [:b, "r", "g"]]
The following is displayed.
a = [2, 2]
e = :a
[e, *a] = [:a, 2, 2]
e = :b
[e, *a] = [:b, 2, 2]
a = [2, "g"]
e = :a
[e, *a] = [:a, 2, "g"]
e = :b
[e, *a] = [:b, 2, "g"]
a = ["r", 2]
e = :a
[e, *a] = [:a, "r", 2]
e = :b
[e, *a] = [:b, "r", 2]
a = ["r", "g"]
e = :a
[e, *a] = [:a, "r", "g"]
e = :b
[e, *a] = [:b, "r", "g"]

How can I arrange k elements by frequency in Decreasing Order in Ruby?

Given the array (array) [1, 1, 2, 2, 2, 3] this method should return (new_array)
[2, 2, 2, 1, 1, 3]
Heres what I have tried so far
Converted array into hash
Key being the element and value being the count
How do I recreate the array again to match new_array?
Here's one way:
array = [1,1,2,2,2,3]
array.tally # This is the bit you did already. Note that this uses the new ruby 2.7 method. You get: {1=>2, 2=>3, 3=>1}
.sort_by {|k, v| -v} # Now we have: [[2, 3], [1, 2], [3, 1]]
.flat_map { |element, count| Array.new(count, element) }
# And that gives the final desired result of:
[2, 2, 2, 1, 1, 3]
Or another variant, along the same lines:
array.tally # {1=>2, 2=>3, 3=>1}
.invert # {2=>1, 3=>2, 1=>3}
.sort # [[1, 3], [2, 1], [3, 2]]
.reverse # [[3, 2], [2, 1], [1, 3]]
.flat_map { |element, count| [element] * count }
Or, here's something completely different:
array.sort_by { |x| -array.count(x) }
Here is another one:
array = [1,1,2,2,2,3]
p array.group_by(&:itself).values.sort_by(&:size).flatten
def sort_chunks_by_length(arr)
arr.slice_when(&:!=).sort_by { |a| -a.size }.flatten
end
sort_chunks_by_length [1,1,2,2,2,3]
#=> [2, 2, 2, 1, 1, 3]
sort_chunks_by_length [1,1,2,2,2,1,3,3]
#=> [2, 2, 2, 1, 1, 3, 3, 1]
I have assumed that for the second example the desired return value is as shown, as opposed to:
#=> [2, 2, 2, 1, 1, 1, 3, 3]
The steps for that example are as follows.
arr = [1,1,2,2,2,1,3,3]
enum = arr.slice_when(&:!=)
#=> #<Enumerator: #<Enumerator::Generator:0x00007ffd1a9740b8>:each>
This is shorthand for:
enum = arr.slice_when { |x,y| x!=y }
We can see the elements that will be generated by this enumerator by converting it to an array:
enum.to_a
#=> [[1, 1], [2, 2, 2], [1], [3, 3]]
Continuing,
a = enum.sort_by { |a| -a.size }
#=> [[2, 2, 2], [1, 1], [3, 3], [1]]
a.flatten
#=> [2, 2, 2, 1, 1, 3, 3, 1]
The operative line could be replaced by either of the following.
arr.chunk(&:itself).map(&:last).sort_by { |a| -a.size }.flatten
arr.chunk_while(&:==).sort_by { |a| -a.size }.flatten
See Enumerable#slice_when, Enumerable#sort_by, Enumerable#chunk and Enumerable#chunk_while.

Group every n elements

I have an array:
a = [1,2,3,4,5,6]
I want an array of arrays taking the elements up to a certain amount like:
b = group(a,4) = [[1,2,3,4],[2,3,4,5],[3,4,5,6],[4,5,6],[5,6],[6]]
I tried:
a.each_cons(4) {|x1,x2,x3,x4| b[a.index(x1) = [x1,x2,x3,x4]}
but the resulting array doesn't have the last three elements:
[[1, 2, 3, 4],[2, 3, 4, 5],[3, 4, 5, 6]]
a = [1,2,3,4,5,6]
a.map.with_index { |e, i| a.slice(i, 4) }
#⇒ [1, 2, 3, 4]
#⇒ [2, 3, 4, 5]
#⇒ [3, 4, 5, 6]
#⇒ [4, 5, 6]
#⇒ [5, 6]
#⇒ [6]
a = [1,2,3,4,5,6]
asize = a.size
#=> 6
a.each_index.map { |i| a[i, [4, asize-i].min] }
#=> [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6], [5, 6], [6]]
The steps are as follows.
asize = a.size
#=> 6
enum0 = a.each_index
#=> #<Enumerator: [1, 2, 3, 4, 5, 6]:each_index>
enum1 = enum0.map
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5, 6]:each_index>:map>
Have a close look at the return value for enum.map. You will see that it can be thought of as a compound enumerator.
We can see the elements that will be generated by enum1 by converting it to an array.
enum1.to_a
#=> [0, 1, 2, 3, 4, 5]
We can now pass each element generated by enum1 to the block for the mapping.
i = enum1.next
#=> 0
a[i, [4, asize-i].min]
#=> a[0, [4, 6].min]
#=> a[0, 4]
#=> [1,2,3,4]
so the first element of a, 1, is mapped to [1,2,3,4].
The remaining steps are as follows.
i = enum1.next #=> 1
a[i, [4, asize-i].min] #=> [2, 3, 4, 5]
i = enum1.next #=> 2
a[i, [4, asize-i].min] #=> [3, 4, 5, 6]
i = enum1.next #=> 3
a[i, [4, asize-i].min] #=> [4, 5, 6]
i = enum1.next #=> 4
a[i, [4, asize-i].min] #=> [5, 6]
i = enum1.next #=> 5
a[i, [4, asize-i].min] #=> [6]
i = enum1.next #=> StopIteration: iteration reached an end

A method for an array

What is the most concise and explicit way to write a method for this?
Given an array a of numbers and a number n, find the n consecutive elements of a whose sum is the largest.
Return the largest sum and the index of the first element in the group.
For example, with a = [1, 1, 1, 1, 1, 1, 1, 2] and n = 2, the
result would be a sum 3 and position 6.
arr = [1,3,2,4,3,5,2,1,3,4,2,5,1]
size = 3
Inefficient but pretty
arr.each_cons(size).with_index.map { |a,i| [a.inject(:+), i] }.max_by(&:first)
#=> [12, 3]
Efficient but whupped with an ugly stick1
tot = arr[0,size].inject(:+)
(1..arr.size-size).each_with_object([tot, 0]) do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
Steps performed by the pretty one
enum0 = arr.each_cons(size)
#=> #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:each_cons(3)>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:
# each_cons(3)>:with_index>
Carefully examine the above return value for enum1. You will see it is effectively a "compound" enumerator. We can see the values that enum1 will generate and pass to map by converting it to an array:
enum1.to_a
#=> [[[1, 3, 2], 0], [[3, 2, 4], 1], [[2, 4, 3], 2], [[4, 3, 5], 3],
# [[3, 5, 2], 4], [[5, 2, 1], 5], [[2, 1, 3], 6], [[1, 3, 4], 7],
# [[3, 4, 2], 8], [[4, 2, 5], 9], [[2, 5, 1], 10]]
Continuing:
b = enum1.map { |a,i| [a.inject(:+), i] }
#=> [[6, 0], [9, 1], [9, 2], [12, 3], [10, 4], [8, 5],
# [6, 6], [8, 7], [9, 8], [11, 9], [8, 10]]
Note the since the first element of enum1 that map passes to the block is [[1, 3, 2], 0], the two block variables are assigned as follows (using parallel or multiple assignment):
a, i = [[1, 3, 2], 0]
#=> [[1, 3, 2], 0]
a #=> [1, 3, 2]
i #=> 0
and the block calculation is performed:
[a.inject(:+), i]
#=> [6, 0]
Lastly,
b.max_by(&:first)
#=> [12, 3]
Enumerable#max_by determines the largest value among
b.map(&:first)
#=> [6, 9, 9, 12, 10, 8, 6, 8, 9, 11, 8]
Steps performed by the less pretty one
a = arr[0,size]
#=> [1, 3, 2]
tot = a.inject(:+)
#=> 6
enum = (1..arr.size-size).each_with_object([tot, 0])
#=> (1..13-3).each_with_object([6, 0])
#=> #<Enumerator: 1..10:each_with_object([6, 0])>
enum.to_a
#=> [[1, [6, 0]], [2, [6, 0]], [3, [6, 0]], [4, [6, 0]], [5, [6, 0]],
# [6, [6, 0]], [7, [6, 0]], [8, [6, 0]], [9, [6, 0]], [10, [6, 0]]]
enum.each do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
The first element of enum, [1, [6, 0]], is passed to the block, assigned to the block variables and the block calculation is performed:
i, best = [1, [6, 0]]
#=> [1, [6, 0]]
i #=> 1
best
#=> [6, 0]
tot += arr[i+size-1] - arr[i-1]
# tot = 6 + arr[1+3-1] - arr[1-1]
# = 6 + 4 - 1
# = 9
best.replace([tot, i]) if tot > best.first
#=> best.replace([9, 1]) if 9 > 6
#=> [9, 1]
best
#=> [9, 1]
The remaining calculations are similar.
1 Credit to Bo Diddley (at 2:51)

How do you replace repeating elements in an array?

I have an array:
[1, 4, 4, 4, 2, 9, 0, 4, 3, 3, 3, 3, 4]
and want to replace the repeating values with a string "repeat". The repeated 4 at indices 1, 2, 3 and 3 at indices 8, 9, 10, 11 should be replaced. I should get:
[1, "repeat", 2, 9, 0, 4, "repeat", 4]
How is this accomplished?
Here are two ways you could do that.
#1 Use Enumerable#chunk:
arr = [1,4,4,4,2,9,0,4,3,3,3,3,4]
arr.chunk(&:itself).map { |f,a| a.size==1 ? f : "repeat" }
#=> [1, "repeat", 2, 9, 0, 4, "repeat", 4]
The steps:
enum = arr.chunk(&:itself)
#=> #<Enumerator: #<Enumerator::Generator:0x007febc99fb160>:each>
We can view the elements of this enumerator by converting it to an array:
enum.to_a
#=> [[1, [1]], [4, [4, 4, 4]], [2, [2]], [9, [9]], [0, [0]],
# [4, [4]], [3, [3, 3, 3, 3]], [4, [4]]]
Object#itself was added in Ruby v2.2. For earlier version you would use
enum = arr.chunk { |e| e }
It is now a simple matter to map the elements of enum as required:
enum.map { |f,a| a.size==1 ? f : "repeat" }
#=> [1, "repeat", 2, 9, 0, 4, "repeat", 4]
#2 Use Enumerable#slice_when
arr.slice_when { |e,f| e !=f }.map { |a| a.size==1 ? a.first : "repeat" }
The steps:
enum = arr.slice_when { |e,f| e !=f }
#=> #<Enumerator: #<Enumerator::Generator:0x007febc99b8cc0>:each>
a = enum.to_a
#=> [[1], [4, 4, 4], [2], [9], [0], [4], [3, 3, 3, 3], [4]]
a.map { |a| a.size==1 ? a.first : "repeat" }
#=> [1, "repeat", 2, 9, 0, 4, "repeat", 4]
slice_when was introduced in Ruby v.2.2.

Resources