get an array of arrays with unique elements - ruby

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

Related

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?

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}}>

Split an array into arrays

I'm new to Ruby and would like to know if there is a better way to solve the following problem.
I have an array that looks like this:
[6, 1, 3, 6, 2, 4, 1, 3, 2, 3]
I'd like to turn it into this:
[ [1,1], [2,2], [3,3,3], [4], [], [6,6] ]
This is my current solution (again, I'm new to Ruby):
def split_array_into_arrays(array)
max_num = array.max
arrays = Array.new(max_num) { Array.new }
array.each do |num|
arrays[num-1] << num
end
arrays
end
arrays = split_array_into_arrays([6, 1, 3, 6, 2, 4, 1, 3, 2, 3])
puts arrays.inspect
Produces:
[[1, 1], [2, 2], [3, 3, 3], [4], [], [6, 6]]
Note: I realize I am not handling possible errors.
How might an experienced Ruby developer implement this?
ar = [6, 1, 3, 6, 2, 4, 1, 3, 2, 3]
(1..ar.max).map{|n| [n]*ar.count(n)}
# => [[1, 1], [2, 2], [3, 3, 3], [4], [], [6, 6]]

Ruby, merging lazy sequences

Let i have lazy sequences: s1, s2, s3, ..., sN, with non-descending numbers, for example:
s1 = [1, 1, 2, 3, 3, 3, 4, .....]
s2 = [1, 2, 2, 2, 2, 2, 3, 3, 4, ....]
s3 = [1, 2, 3, 3, 3, 3, 4, 4, 4, ....]
what I'd like to do - is to merge it, grouping by similar items and processing it with some function, for example generate list of tuples (number, count)
for my case:
merge(s1, s2, s3) should generate [ [1, 4], [2, 6], [3, 9], [4, 5], .... ]
Are any gems, etc., to process such sequences
If you want to do it lazily, here is some code which would do that:
def merge(*args)
args.map!(&:lazy)
Enumerator.new do |yielder|
while num = args.map(&:peek).min
count = 0
while list = args.find { |l| l.peek == num }
list.next
list.peek rescue args.delete list
count += 1
end
yielder.yield [num, count]
end
end
end
s1 = [1, 1, 2, 3, 3, 3, 4]
s2 = [1, 2, 2, 2, 2, 2, 3, 3, 4]
s3 = [1, 2, 3, 3, 3, 3, 4, 4, 4]
s4 = (0..1.0/0)
merge(s1, s2, s3, s4).take(20)
# => [[0, 1], [1, 5], [2, 8], [3, 10], [4, 6], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1], [10, 1], [11, 1], [12, 1], [13, 1], [14, 1], [15, 1], [16, 1], [17, 1], [18, 1], [19, 1]]

Nested Array Initialization && Assignment Oddity [duplicate]

This question already has an answer here:
Ruby Array Initialization [duplicate]
(1 answer)
Closed 3 years ago.
What is going on in the Array initialization that's causing the disparity in int assignment?
arr = Array.new(3) { Array.new(3) { Array.new(3) } }
3.times do |x|
3.times do |y|
3.times do |z|
arr[x][y][z] = Random.rand(1..9)
end
end
end
puts arr.to_s
#=> [[[3, 3, 1], [4, 9, 6], [2, 4, 7]], [[1, 6, 8], [9, 8, 5], [1, 7, 5]], [[2, 5, 9], [2, 8, 8], [9, 1, 8]]]
#=> [[[2, 4, 4], [6, 8, 9], [6, 2, 7]], [[2, 7, 7], [2, 1, 1], [8, 7, 7]], [[5, 3, 5], [3, 8, 1], [7, 6, 6]]]
#=> [[[4, 9, 1], [1, 6, 8], [9, 2, 5]], [[3, 7, 1], [7, 5, 4], [9, 9, 9]], [[6, 8, 2], [8, 2, 8], [2, 9, 9]]]
arr = Array.new(3, Array.new(3, Array.new(3)))
3.times do |x|
3.times do |y|
3.times do |z|
arr[x][y][z] = Random.rand(1..9)
end
end
end
puts arr.to_s
#=> [[[8, 2, 4], [8, 2, 4], [8, 2, 4]], [[8, 2, 4], [8, 2, 4], [8, 2, 4]], [[8, 2, 4], [8, 2, 4], [8, 2, 4]]]
#=> [[[2, 1, 4], [2, 1, 4], [2, 1, 4]], [[2, 1, 4], [2, 1, 4], [2, 1, 4]], [[2, 1, 4], [2, 1, 4], [2, 1, 4]]]
#=> [[[2, 7, 6], [2, 7, 6], [2, 7, 6]], [[2, 7, 6], [2, 7, 6], [2, 7, 6]], [[2, 7, 6], [2, 7, 6], [2, 7, 6]]]
When you use new(size=0, obj=nil) to initialize the array:
From the doc:
In the first form, if no arguments are sent, the new array will be
empty. When a size and an optional obj are sent, an array is created
with size copies of obj. Take notice that all elements will reference
the same object obj.
If you want multiple copy, then you should use the block version which uses the result of that block each time an element of the array needs to be initialized.

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