Array combinatorics - arrays

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"]

Related

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)

Limit number of duplicate elements within an array to a given value

In Ruby, if I have an array with duplicates in it and a given value n:
array = [1,56,1,245,56,1,56,1,56,1,56]
n = 2
How could I limit the number of duplicates within the array to n whilst also retaining the positioning of the remaining elements to produce this?
array = [1,56,1,245,56]
def ndup(list, n)
cnts = {}
list.select do |item|
cnts[item] ||= 0
cnts[item] += 1
cnts[item] <= n
end
end
ndup([1,56,1,245,56,1,56,1,56,1,56], 2)
#=> [1, 56, 1, 245, 56]
def ff(array, n)
occurances = Hash.new(0)
array.uniq do |element|
occurances[element] = occurances[element] < n ? occurances[element].next : n
[element, occurances[element]]
end
end
Using a counting hash is the way to go here, but here's an alternative:
array.each_with_index.
group_by(&:first).
values.
flat_map { |a| a.first(n) }.
sort_by(&:last).
map(&:first)
#=> [1, 56, 1, 245, 56]
The steps are as follows.
enum = array.each_with_index
#=> #<Enumerator: [1, 56, 1, 245, 56, 1, 56, 1, 56, 1, 56]:each_with_index>
h = enum.group_by(&:first)
#=> { 1=>[[1, 0], [1, 2], [1, 5], [1, 7], [1, 9]],
# 56=>[[56, 1], [56, 4], [56, 6], [56, 8], [56, 10]],
# 245=>[[245, 3]]}
a = h.values
#=> [[[1, 0], [1, 2], [1, 5], [1, 7], [1, 9]],
# [[56, 1], [56, 4], [56, 6], [56, 8], [56, 10]],
# [[245, 3]]]
b = a.flat_map { |a| a.first(n) }
#=> [[1, 0], [1, 2], [56, 1], [56, 4], [245, 3]]
c = b.sort_by(&:last)
#=> [[1, 0], [56, 1], [1, 2], [245, 3], [56, 4]]
c.map(&:first)
#=> [1, 56, 1, 245, 56]

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.

Find the closest value in multidimensional arrays with ruby

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.

Resources