Is there an algorithm to find all the combinations of addends for a sum, from a given range of addends which are greater than 1? - ruby

I'm trying to create a program that takes a given sum and a given range of allowed addends and outputs the unique configurations of those addends which add up to the sum.
The use case is determining the possible combinations of different-sized multi-member districts to divide the members of a legislature into.
In a trivial example, given 15 legislators, and districts of minimum 3 and maximum 5 seats per district, the possible combinations are:
[3, 3, 3, 3, 3]
[4, 4, 4, 3]
[5, 4, 3, 3]
[5, 5, 5]
My initial thought was to start with the largest group of minimum-sized districts possible in a nested array, and add more entries by copying and modifying the previous entry. I don't know how to implement that approach, but I'm also not sure if it's even the right approach to this problem and I'm looking for suggestions.
def multi_member_districts
reps = 19
min = 3
max = 6
quomin, modmin = reps.divmod(min)
quomax, modmax = reps.divmod(max)
groups = Array.new(1) {Array.new}
(quomin - 1).times do groups[0].push(min) end
groups[0].unshift(min + modmin)
# PSEUDOCODE
# copy groups[i], insert copy at groups[i+1]
# remove the smallest element of groups[i+1] and spread it out across the other
# numbers in groups[i+1] in all configurations in which no element exceeds max
# check that there are no duplicate configurations
# repeat
puts "\nThe possible groups of districts are as follows:"
groups.each_index do |i|
(min..max).each do |j|
unless groups[i].count(j) == 0
puts ">> #{groups[i].count(j)} #{j}-member districts"
end
end
puts
puts "o-o-o-o-o-o-o-o-o-o-o-o-o-o"
end
end
multi_member_districts
EDIT_1:
A less trivial example, 19 legislators, 3-6 seats per district --
[4, 3, 3, 3, 3, 3]
[4, 4, 4, 4, 3]
[5, 5, 5, 4]
[5, 4, 4, 3, 3]
[5, 5, 3, 3, 3]
[6, 5, 5, 3]
[6, 4, 3, 3, 3]
[6, 5, 4, 4]
[6, 6, 4, 3]
EDIT_2: Clarified my question, cut down the code, hopefully more suitable

Let's first compute the combinations where each combination corresponds to an array arr where arr[i] equals the number of legislators assigned to district i. If, for example, there are 15 legislators and there must be between 3 and 5 assigned to each district, [3,3,4,5] and [5,3,4,3] would be distinct combinations. We can solve that problem using recursion.
def doit(nbr, rng)
return nil if nbr < rng.begin
recurse(nbr, rng)
end
def recurse(nbr, rng)
(rng.begin..[rng.end, nbr].min).each_with_object([]) do |n,arr|
if n == nbr
arr << [n]
elsif nbr-n >= rng.begin
recurse(nbr-n, rng).each { |a| arr << a.unshift(n) }
end
end
end
doit(15, 3..5)
#=> [[3, 3, 3, 3, 3], [3, 3, 4, 5], [3, 3, 5, 4], [3, 4, 3, 5],
# [3, 4, 4, 4], [3, 4, 5, 3], [3, 5, 3, 4], [3, 5, 4, 3], [4, 3, 3, 5],
# [4, 3, 4, 4], [4, 3, 5, 3], [4, 4, 3, 4], [4, 4, 4, 3], [4, 5, 3, 3],
# [5, 3, 3, 4], [5, 3, 4, 3], [5, 4, 3, 3], [5, 5, 5]]
doit(19, 3..6)
#=> [[3, 3, 3, 3, 3, 4], [3, 3, 3, 3, 4, 3], [3, 3, 3, 4, 3, 3],
# [3, 3, 3, 4, 6], [3, 3, 3, 5, 5], [3, 3, 3, 6, 4], [3, 3, 4, 3, 3, 3],
# ...
# [6, 5, 3, 5], [6, 5, 4, 4], [6, 5, 5, 3], [6, 6, 3, 4], [6, 6, 4, 3]]
doit(19, 3..6).size
#=> 111
The question is not concerned, however, with allocations to specific districts. To obtain the combinations of interest we may therefore write the following.
require 'set'
def really_doit(nbr, rng)
doit(nbr, rng).map(&:tally).uniq.map do |h|
h.flat_map { |k,v| [k]*v }.sort.reverse
end
end
really_doit(15, 3..5)
#=> [[3, 3, 3, 3, 3], [5, 4, 3, 3], [4, 4, 4, 3], [5, 5, 5]]
really_doit(19, 3..6)
#=> [[4, 3, 3, 3, 3, 3], [6, 4, 3, 3, 3], [5, 5, 3, 3, 3],
# [5, 4, 4, 3, 3], [4, 4, 4, 4, 3], [6, 6, 4, 3], [6, 5, 5, 3],
# [6, 5, 4, 4], [5, 5, 5, 4]]
Enumerable#tally made its debut in Ruby v2.7. To support earlier versions replace map(&:tally) with map { |a| a.each_with_object(Hash.new(0)) { |n,h| h[n] += 1 }.
Note that doit(nbr, rng).map(&:tally).uniq in returns
[{3=>5}, {3=>2, 4=>1, 5=>1}, {3=>1, 4=>3}, {5=>3}]
in really_doit(15, 3..5) and
[{3=>5, 4=>1}, {3=>3, 4=>1, 6=>1}, {3=>3, 5=>2}, {3=>2, 4=>2, 5=>1},
{3=>1, 4=>4}, {3=>1, 4=>1, 6=>2}, {3=>1, 5=>2, 6=>1}, {4=>2, 5=>1, 6=>1},
{4=>1, 5=>3}]
in really_doit(19, 3..6).
We can improve on this by constructing sets of hashes (rather than arrays of arrays) in recurse:
require 'set'
def doit(nbr, rng)
return nil if nbr < rng.begin
recurse(nbr, rng).map { |h| h.flat_map { |k,v| [k]*v }.sort.reverse }
end
def recurse(nbr, rng)
(rng.begin..[rng.end, nbr].min).each_with_object(Set.new) do |n,st|
if n == nbr
st << { n=>1 }
elsif nbr-n >= rng.begin
recurse(nbr-n, rng).each { |h| st << h.merge(n=>h[n].to_i+1 ) }
end
end
end
doit(15, 3..5)
#=> [[3, 3, 3, 3, 3], [5, 4, 3, 3], [4, 4, 4, 3], [5, 5, 5]]
doit(19, 3..6)
#=> [[4, 3, 3, 3, 3, 3], [6, 4, 3, 3, 3], [5, 5, 3, 3, 3],
# [5, 4, 4, 3, 3], [4, 4, 4, 4, 3], [6, 6, 4, 3], [6, 5, 5, 3],
# [6, 5, 4, 4], [5, 5, 5, 4]]
Note that here recurse(nbr, rng) in doit returns:
#<Set: {{3=>5}, {5=>1, 4=>1, 3=>2}, {4=>3, 3=>1}, {5=>3}}>
When executing doit(19, 3..6) recurse(nbr, rng) in doit returns:
#<Set: {{4=>1, 3=>5}, {6=>1, 4=>1, 3=>3}, {5=>2, 3=>3},
# {5=>1, 4=>2, 3=>2}, {4=>4, 3=>1}, {6=>2, 4=>1, 3=>1},
# {6=>1, 5=>2, 3=>1}, {6=>1, 5=>1, 4=>2}, {5=>3, 4=>1}}>

Related

How do I generate a nested array that contains all possible 4 digit permutations 6 numbers with repeating values? ruby

Given a number of digits n = 1, 2, 3, 4, 5, 6.
I wanted to generate a nested array S that will contain all possible 4 digit permutations of n.
since 6^4 = 1296, there will be 1296 possible permutations.
Example:
S = [[1,1,1,1],[1,1,1,2],[1,1,2,2]...[6,6,6,6]]
I started the nested loop with the first index with value of [1,1,1,1]
Then used a for in loop with range 0..1295 and tried to carry over the value of S[i] to S[i+1]
then increment the value of S[i+1][x], where x always starts at 3 then is decremented until it reaches 0 then it becomes 3 again. The problem with my procedure is when i try to increment the S[i+1][x], S[i] also increments its S[i][x].
In the code below S is instead called 'all_possible_combinations'
all_possible_combinations = Array.new(1296) {Array.new(4)}
all_possible_combinations[0] = [1, 1 ,1 ,1]
x = 3
for i in 0..1295
if i + 1 == 1296
break
else
all_possible_combinations[i+1] = all_possible_combinations[i]
all_possible_combinations[i+1][x] += 1
x -= 1
if x == 0
x = 3
end
end
end
[Attached image shows debugging process where Si][x] also gets incremented
You may compute that array as follows.
a = [1, 2, 3, 4, 5, 6]
b = a.repeated_permutation(4).to_a
#=> [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 1, 3], [1, 1, 1, 4], [1, 1, 1, 5],
# [1, 1, 1, 6], [1, 1, 2, 1], [1, 1, 2, 2], [1, 1, 2, 3], [1, 1, 2, 4],
# ...
# [6, 6, 5, 3], [6, 6, 5, 4], [6, 6, 5, 5], [6, 6, 5, 6], [6, 6, 6, 1],
# [6, 6, 6, 2], [6, 6, 6, 3], [6, 6, 6, 4], [6, 6, 6, 5], [6, 6, 6, 6]]
b.size
#=> 1296
See Array#repeated_permutation
If the array a may contain duplicates and you wish to remove duplicate permutations you may wish to tack on Array#uniq.
a = [1, 1, 3, 1, 1, 6]
b = a.repeated_permutation(4).to_a.uniq
#=> [[1, 1, 1, 1], [1, 1, 1, 3], [1, 1, 1, 6], [1, 1, 3, 1],
# [1, 1, 3, 3], [1, 1, 3, 6], [1, 1, 6, 1], [1, 1, 6, 3],
# [1, 1, 6, 6], [1, 3, 1, 1], [1, 3, 1, 3], [1, 3, 1, 6],
# [1, 3, 3, 1], [1, 3, 3, 3], [1, 3, 3, 6], [1, 3, 6, 1],
# [1, 3, 6, 3], [1, 3, 6, 6], [1, 6, 1, 1], [1, 6, 1, 3],
# [1, 6, 1, 6], [1, 6, 3, 1], [1, 6, 3, 3], [1, 6, 3, 6],
# [1, 6, 6, 1], [1, 6, 6, 3], [1, 6, 6, 6], [3, 1, 1, 1],
# [3, 1, 1, 3], [3, 1, 1, 6], [3, 1, 3, 1], [3, 1, 3, 3],
# [3, 1, 3, 6], [3, 1, 6, 1], [3, 1, 6, 3], [3, 1, 6, 6],
# [3, 3, 1, 1], [3, 3, 1, 3], [3, 3, 1, 6], [3, 3, 3, 1],
# [3, 3, 3, 3], [3, 3, 3, 6], [3, 3, 6, 1], [3, 3, 6, 3],
# [3, 3, 6, 6], [3, 6, 1, 1], [3, 6, 1, 3], [3, 6, 1, 6],
# [3, 6, 3, 1], [3, 6, 3, 3], [3, 6, 3, 6], [3, 6, 6, 1],
# [3, 6, 6, 3], [3, 6, 6, 6], [6, 1, 1, 1], [6, 1, 1, 3],
# [6, 1, 1, 6], [6, 1, 3, 1], [6, 1, 3, 3], [6, 1, 3, 6],
# [6, 1, 6, 1], [6, 1, 6, 3], [6, 1, 6, 6], [6, 3, 1, 1],
# [6, 3, 1, 3], [6, 3, 1, 6], [6, 3, 3, 1], [6, 3, 3, 3],
# [6, 3, 3, 6], [6, 3, 6, 1], [6, 3, 6, 3], [6, 3, 6, 6],
# [6, 6, 1, 1], [6, 6, 1, 3], [6, 6, 1, 6], [6, 6, 3, 1],
# [6, 6, 3, 3], [6, 6, 3, 6], [6, 6, 6, 1], [6, 6, 6, 3],
# [6, 6, 6, 6]]
b.size
#=> 81
To create a sequence where each element is generated based on the previous one, there's Enumerator.produce, e.g.:
enum = Enumerator.produce([1, 1, 1, 1]) do |a, b, c, d|
d += 1 # ^^^^^^^^^^^^
# initial value
if d > 6
d = 1
c += 1
end
if c > 6
c = 1
b += 1
end
if b > 6
b = 1
a += 1
end
if a > 6
raise StopIteration # <- stops enumeration
end
[a, b, c, d] # <- return value = next value
end
I've kept the example intentionally simple, using an explicit variable for each of the four digits. You could of course also have an array and use a little loop to handle the increment / carry.
The above gives you:
enum.count #=> 1296
enum.first(3) #=> [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 1, 3]]
enum.to_a.last(3) #=> [[6, 6, 6, 4], [6, 6, 6, 5], [6, 6, 6, 6]]

Array values is not passed properly in backtracking

I have written the following program to find the permutation of all the elements in an array. The values are created properly but the problem occurs when I try to assign the generated sequence into a new array. The old values will get cleared and the new values are copied as per the array size
def find_perm(nums, answer, set)
if nums.empty?
p set
answer.push(set)
p answer.object_id
p answer
return true
end
for i in (0..nums.length - 1) do
new_nums = nums.clone
new_nums.delete_at(i)
set.push(nums[i])
find_perm(new_nums, answer, set)
set.pop
end
end
def permute(nums)
answer = []
set = []
element = find_perm(nums, answer, set)
return element
end
permute([1,2,3])
This are the observations that I have found out while debugging:
[1, 2, 3]
47167191669680
[[1, 2, 3]]
[1, 3, 2]
47167191669680
[[1, 3, 2], [1, 3, 2]]
[2, 1, 3]
47167191669680
[[2, 1, 3], [2, 1, 3], [2, 1, 3]]
[2, 3, 1]
47167191669680
[[2, 3, 1], [2, 3, 1], [2, 3, 1], [2, 3, 1]]
[3, 1, 2]
47167191669680
[[3, 1, 2], [3, 1, 2], [3, 1, 2], [3, 1, 2], [3, 1, 2]]
[3, 2, 1]
47167191669680
[[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1]]
The problem was each time I was pushing the same set array into the answer array so each element in the answer array will have same reference (same object_id).
Solution: Is to clone the set array during the each push to the answer array so that each element have different reference.
The solution:
def fact(n)
return 1 if n == 1
n*fact(n-1)
end
def find_perm(nums, answer, set)
if nums.empty?
answer.push(set.clone)
end
for i in (0..nums.length - 1) do
new_nums = nums.clone
new_nums.delete_at(i)
set.push(nums[i])
find_perm(new_nums, answer, set)
set.pop
return answer if fact(nums.count) == answer.count
end
end
def permute(nums)
answer = []
set = []
element = find_perm(nums, answer, set)
return element
end
p permute([1,2,3])
I believe the approach you are taking is similar to the following.
Suppose we wish to obtain the permutations of the elements of the array
arr = [1, 2, 3, 4]
Begin with the array
[4]
This array has only a single perumutation:
perms3 = [[4]]
(3 in perms3 denotes the index of 4 in arr.) Now obtain the permuations of
[3, 4]
We see that is
perms2 = [[3, 4], [4, 3]]
We simply take each element of perms3 ([4] is the only one) and create two permuations by inserting 3 before 4 and then 3 after 4;
Now suppose the array were
[2, 3, 4]
Then
perms1 = [[2, 3, 4], [3, 2, 4], [3, 4, 2], [2, 4, 3], [4, 2, 3], [4, 3, 2]]
We create three 3-element arrays from [3, 4], one by inserting 2 before 3, one by inserting 2 between 3 and 4 and one by inserting 2 after 4. Simlarly, three 3-element arrays are generated from [4, 3] in a simlar way. This generates six arrays. (Indeed, 3! #=> 6).
Lastly we generating the 4! #=> 24 permuations of [1, 2, 3, 4] by inserting 1 in four locations of each element of perm1, the first four derived from perms1[0] #=> [2, 3, 4]:
[[1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1]]
We can do this in code as follows.
def my_permutations(arr)
perms = [[arr.last]]
(arr.size-2).downto(0) do |i|
x = arr[i]
perms = perms.flat_map do |perm|
(0..(perm.size)).map { |i| perm.dup.insert(i, x) }
end
end
perms
end
my_permutations(arr)
#=> [[1, 2, 3, 4], [2, 1, 3, 4], [2, 3, 1, 4], [2, 3, 4, 1], [1, 3, 2, 4],
# [3, 1, 2, 4], [3, 2, 1, 4], [3, 2, 4, 1], [1, 3, 4, 2], [3, 1, 4, 2],
# [3, 4, 1, 2], [3, 4, 2, 1], [1, 2, 4, 3], [2, 1, 4, 3], [2, 4, 1, 3],
# [2, 4, 3, 1], [1, 4, 2, 3], [4, 1, 2, 3], [4, 2, 1, 3], [4, 2, 3, 1],
# [1, 4, 3, 2], [4, 1, 3, 2], [4, 3, 1, 2], [4, 3, 2, 1]]
See Enumerable#flat_map and Array#insert. Note that we need to make a copy of the array perm before invoking insert.
We could of course have gone "forward" in arr (starting with [[1]]), rather than "backward", though the elements of the array of permutations would be ordered differently.

How to get 'fair combination' from an array of n elements?

Using combination method on Ruby,
[1, 2, 3, 4, 5, 6].combination(2).to_a
#=> [[1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 3],
# [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6],
# [4, 5], [4, 6], [5, 6]]
we can get a 2-dimensional array having 15 (6C2) elements.
I would like to create a fair_combination method that returns an array like this:
arr = [[1, 2], [3, 5], [4, 6],
[3, 4], [5, 1], [6, 2],
[5, 6], [1, 3], [2, 4],
[2, 3], [4, 5], [6, 1],
[1, 4], [2, 5], [3, 6]]
So that every three sub-arrays (half of 6) contain all the given elements:
arr.each_slice(3).map { |a| a.flatten.sort }
#=> [[1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6]]
This makes it kind of "fair", by using as different elements as possible as arrays go on.
To make it more general, what it needs to satisfy is as follows:
(1) As you follow the arrays from start and count how many times each number appears, at any point it should be as flat as possible;
(1..7).to_a.fair_combination(3)
#=> [[1, 2, 3], [4, 5, 6], [7, 1, 4], [2, 5, 3], [6, 7, 2], ...]
The first 7 numbers make [1,2,...,7] and so do the following 7 numbers.
(2) Once number A comes in the same array with B, A does not want to be in the same array with B if possible.
(1..10).to_a.fair_combination(4)
#=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 1, 5], [2, 6, 9, 3], [4, 7, 10, 8], ...]
Is there any good algorithm that creates a "fair combination" like this ?
It's not guaranteed to give the best solution, but it gives a good enough one.
At each step, it chooses a minimal subpool which is the set of items of minimal height, for which there is still a combination to choose from (height is the number of times the items have been used before).
For instance, let the enumerator be
my_enum = FairPermuter.new('abcdef'.chars, 4).each
The first iteration may return
my_enum.next # => ['a', 'b', 'c', 'd']
At this point those letters have height 1, but there is not enough letters of height 0 to make a combination, so take just all of them for the next:
my_enum.next # => ['a', 'b', 'c', 'e'] for instance
Now the heights are 2 for a, b and c, 1 for d and e, and 0 for f, and still the optimal pool is the full initial set.
So this is not really optimized for combinations of large size. On the other side, if the size of the combination is at most half of the size of the initial set, then the algorithm is pretty decent.
class FairPermuter
def initialize(pool, size)
#pool = pool
#size = size
#all = Array(pool).combination(size)
#used = []
#counts = Hash.new(0)
#max_count = 0
end
def find_valid_combination
[*0..#max_count].each do |height|
candidates = #pool.select { |item| #counts[item] <= height }
next if candidates.size < #size
cand_comb = [*candidates.combination(#size)] - #used
comb = cand_comb.sample
return comb if comb
end
nil
end
def each
return enum_for(:each) unless block_given?
while combination = find_valid_combination
#used << combination
combination.each { |k| #counts[k] += 1 }
#max_count = #counts.values.max
yield combination
return if #used.size >= [*1..#pool.size].inject(1, :*)
end
end
end
Results for fair combinations of 4 over 6
[[1, 2, 4, 6], [3, 4, 5, 6], [1, 2, 3, 5],
[2, 4, 5, 6], [2, 3, 5, 6], [1, 3, 5, 6],
[1, 2, 3, 4], [1, 3, 4, 6], [1, 2, 4, 5],
[1, 2, 3, 6], [2, 3, 4, 6], [1, 2, 5, 6],
[1, 3, 4, 5], [1, 4, 5, 6], [2, 3, 4, 5]]
Results of fair combination of 2 over 6
[[4, 6], [1, 3], [2, 5],
[3, 5], [1, 4], [2, 6],
[4, 5], [3, 6], [1, 2],
[2, 3], [5, 6], [1, 6],
[3, 4], [1, 5], [2, 4]]
Results of fair combinations of 2 over 5
[[4, 5], [2, 3], [3, 5],
[1, 2], [1, 4], [1, 5],
[2, 4], [3, 4], [1, 3],
[2, 5]]
Time to get combinations of 5 over 12:
1.19 real 1.15 user 0.03 sys
Naïve implementation would be:
class Integer
# naïve factorial implementation; no checks
def !
(1..self).inject(:*)
end
end
class Range
# constant Proc instance for tests; not needed
C_N_R = -> (n, r) { n.! / ( r.! * (n - r).! ) }
def fair_combination(n)
to_a.permutation
.map { |a| a.each_slice(n).to_a }
.each_with_object([]) do |e, memo|
e.map!(&:sort)
memo << e if memo.all? { |me| (me & e).empty? }
end
end
end
▶ (1..6).fair_combination(2)
#⇒ [
# [[1, 2], [3, 4], [5, 6]],
# [[1, 3], [2, 5], [4, 6]],
# [[1, 4], [2, 6], [3, 5]],
# [[1, 5], [2, 4], [3, 6]],
# [[1, 6], [2, 3], [4, 5]]]
▶ (1..6).fair_combination(3)
#⇒ [
# [[1, 2, 3], [4, 5, 6]],
# [[1, 2, 4], [3, 5, 6]],
# [[1, 2, 5], [3, 4, 6]],
# [[1, 2, 6], [3, 4, 5]],
# [[1, 3, 4], [2, 5, 6]],
# [[1, 3, 5], [2, 4, 6]],
# [[1, 3, 6], [2, 4, 5]],
# [[1, 4, 5], [2, 3, 6]],
# [[1, 4, 6], [2, 3, 5]],
# [[1, 5, 6], [2, 3, 4]]]
▶ Range::C_N_R[6, 3]
#⇒ 20
Frankly, I do not understand how this function should behave for 10 and 4, but anyway this implementation is too memory consuming to work properly on big ranges (on my machine it gets stuck on ranges of size > 8.)
To adjust this to more robust solution one needs to get rid of permutation there in favor of “smart concatenate permuted arrays.”
Hope this is good for starters.

get an array of arrays with unique elements

I have an array like this:
[1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
I want to know if there's a method to get this:
[[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
I know there is Array.uniq but this removes the duplicate elements, and I would like to keep them.
Not sure about performance, but this works:
Code:
$ cat foo.rb
require 'pp'
array = [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
result = []
values = array.group_by{|e| e}.values
while !values.empty?
result << values.map{|e| e.slice!(0,1)}.flatten
values = values.reject!{|e| e.empty?}
end
pp result
Output:
$ ruby foo.rb
[[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
A simple solution, but I'm sure it will not have the best performance:
def array_groups(arr)
result = []
arr.uniq.each do |elem|
arr.count(elem).times do |n|
result[n] ||= []
result[n] << elem
end
end
result
end
array_groups [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
# [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
[1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
.each.with_object([]){|e, a| (a.find{|b| !b.include?(e)} || a.push([]).last).push(e)}
# => [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
On ruby you could add a method to the class Array. Like this:
class Array
def uniqA (acc)
return acc if self.empty?
# return self.replace acc if self.empty? #to change the object itself
acc << self.uniq
self.uniq.each { |x| self.delete_at(self.index(x)) }
uniqA(acc)
end
end
b = [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
print b.uniqA([])
#=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
print b
#=> []
Or you could do this, to keep the elements on b:
b = b.uniqA([])
#=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
print b
#=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
Here are a couple of ways of doing it.
arr = [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
#1
b = []
a = arr.dup
while a.any?
u = a.uniq
b << u
a = a.difference u
end
b
#=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
The helper Array#difference is defined in my answer here.
#2
arr.map { |n| [n, arr.count(n)] }
.each_with_object({}) { |(n,cnt),h|
(1..cnt).each { |i| (h[i] ||= []) << n } }
.values
.map(&:uniq)
#=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]
The steps, for:
arr = [1, 2, 3, 3, 6, 6, 6, 7]
a = arr.map { |n| [n, arr.count(n)] }
#=> [[1, 1], [2, 1], [3, 2], [3, 2], [4, 2], [4, 2],
# [5, 1], [6, 3], [6, 3], [6, 3], [7, 1]]
enum = a.each_with_object({})
#=> #<Enumerator: [[1, 1], [2, 1], [3, 2], [3, 2], [4, 2], [4, 2],
# [5, 1], [6, 3], [6, 3], [6, 3], [7, 1]]:each_with_object({})>
To view the elements of enum:
enum.to_a
#=> [[[1, 1], {}], [[2, 1], {}],...[[7, 1], {}]]
Now step through the enumerator and examine the hash after each step:
(n,cnt),h = enum.next
#=> [[1, 1], {}]
n #=> 1
cnt #=> 1
h #=> {}
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1]}
(n,cnt),h = enum.next
#=> [[2, 1], {1=>[1]}]
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2]}
(n,cnt),h = enum.next
#=> [[3, 2], {1=>[1, 2]}]
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3], 2=>[3]}
(n,cnt),h = enum.next
#=> [[3, 2], {1=>[1, 2, 3], 2=>[3]}]
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3], 2=>[3, 3]}
(n,cnt),h = enum.next
#=> [[6, 3], {1=>[1, 2, 3, 3], 2=>[3, 3]}]
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6], 2=>[3, 3, 6], 3=>[6]}
(n,cnt),h = enum.next
#=> [[6, 3], {1=>[1, 2, 3, 3, 6], 2=>[3, 3, 6], 3=>[6]}]
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6, 6], 2=>[3, 3, 6, 6], 3=>[6, 6]}
(n,cnt),h = enum.next
#=> [[6, 3], {1=>[1, 2, 3, 3, 6, 6], 2=>[3, 3, 6, 6], 3=>[6, 6]}]
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6, 6, 6], 2=>[3, 3, 6, 6, 6], 3=>[6, 6, 6]}
(n,cnt),h = enum.next
#=> [[7, 1], {1=>[1, 2, 3, 3, 6, 6, 6], 2=>[3, 3, 6, 6, 6], 3=>[6, 6, 6]}]
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6, 6, 6, 7], 2=>[3, 3, 6, 6, 6], 3=>[6, 6, 6]}
Lastly, extract and uniqify the values of the hash:
b = h.values
#=> [[1, 2, 3, 3, 6, 6, 6, 7], [3, 3, 6, 6, 6], [6, 6, 6]]
b.map(&:uniq)
#=> [[1, 2, 3, 6, 7], [3, 6], [6]]

Average of several Ruby arrays

I have three Ruby arrays:
[1, 2, 3, 4]
[2, 3, 4, 5]
[3, 4, 5, 6]
How can I take the average of all three numbers in position 0, then position 1, etc. and store them in a new array called 'Average'?
a = [1, 2, 3, 4]
b = [2, 3, 4, 5]
c = [3, 4, 5, 6]
a.zip(b,c)
# [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
.map {|array| array.reduce(:+) / array.size }
# => [ 2,3,4,5]
Try this:
arr = ([1, 2, 3, 4] + [3, 4, 5, 6] + [2, 3, 4, 5])
arr.inject(0.0) { |sum, el| sum + el } / arr.size
The concatenation could be done in several ways, depends on how you store your arrays.
As a syntactic sugar, you could do it like this too:
arr.inject(:+).to_f / arr.size

Resources