Related
I'm trying to build a pair generator. It takes a list of six names, and generates pairs for the week (5 days), with as few replications as possible.
The minimum I've gotten my replicated pairs down to is 2 (so I have found 5 days of pairs, i.e. 15 total pair combinations, with only 2 identical sets).
My method:
# Start with individuals in an array
[1, 2, 3, 4, 5, 6]
# Bisect the array
[1, 2, 3]
[4, 5, 6] => yields pair combinations [1, 4], [2, 5], [3, 6]
# Move the lower of the bisected arrays along
[1, 2, 3]
[6, 4, 5] => yields pair combinations [1, 6], [2, 4], [3, 5]
# Move along once more
[1, 2, 3]
[5, 6, 4] => yields pair combinations [1, 5], [2, 6], [3, 4]
# Since there are no more unique pair combinations, bisect each array again
(Array 1) [1, 2]
(Array 1) [3] => yields pair combination [1, 3] with 2 'spare'
(Array 2) [4, 5]
(Array 2) [6] => yields pair combination [4, 6] with 6 'spare'
=> 'spare' pair combination [2, 6] is a replication
# Move the lower of the bisected arrays along
(Array 1) [1, 2]
(Array 1) [3] => yields pair combination [2, 3] with 1 'spare'
(Array 2) [4, 5]
(Array 2) [6] => yields pair combination [5, 6] with 4 'spare'
=> 'spare' pair combination [1, 4] is a replication
This process above gives us 13 unique pairs, and then 2 that are non-unique. Every day of the week is covered, but we replicate.
Is there any way to do this more efficiently/to avoid the replication?
This is a round robin tournament where every player plays every other player. Line up the players like below to form the pairs 1 4, 2 5 and 3 6:
123
456
fix player 1, rotate the remaining players:
142
563
to produce pairs 1 5, 4 6 and 2 3. Keep rotating:
154
632
165
324
136
245
I think you're simply after the built-in combination method which returns an enumerator. You can use .to_a to turn it into an array of unique combinations.
[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]]
This is called a 1-factorization. One 1-factorization of the complete graph on 6 vertices {0,1,2,3,4,oo} is to let the schedule on day i be {{oo,i},{i+1,i+4},{i+2,i+3}} where all of the numbers i+j are reduced mod 5.
I couldn't find a way to build an array such as
[ [1,2,3] , [1,2,3] , [1,2,3] , [1,2,3] , [1,2,3] ]
given [1,2,3] and the number 5. I guess there are some kind of operators on arrays, such as product of mult, but none in the doc does it. Please tell me. I missed something very simple.
Array.new(5, [1, 2, 3]) or Array.new(5) { [1, 2, 3] }
Array.new(size, default_object) creates an array with an initial size, filled with the default object you specify. Keep in mind that if you mutate any of the nested arrays, you'll mutate all of them, since each element is a reference to the same object.
array = Array.new(5, [1, 2, 3])
array.first << 4
array # => [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
Array.new(size) { default_object } lets you create an array with separate objects.
array = Array.new(5) { [1, 2, 3] }
array.first << 4
array #=> [[1, 2, 3, 4], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
Look up at the very top of the page you linked to, under the section entitled "Creating Arrays" for some more ways to create arrays.
Why not just use:
[[1, 2, 3]] * 5
# => [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
The documentation for Array.* says:
...returns a new array built by concatenating the int copies of self.
Typically we'd want to create a repeating single-level array:
[1, 2, 3] * 2
# => [1, 2, 3, 1, 2, 3]
but there's nothing to say we can't use a sub-array like I did above.
It looks like mutating one of the subarrays mutates all of them, but that may be what someone wants.
It's like Array.new(5, [1,2,3]):
foo = [[1, 2, 3]] * 2
foo[0][0] = 4
foo # => [[4, 2, 3], [4, 2, 3]]
foo = Array.new(2, [1,2,3])
foo[0][0] = 4
foo # => [[4, 2, 3], [4, 2, 3]]
A work-around, if that's not the behavior wanted is:
foo = ([[1, 2, 3]] * 2).map { |a| [*a] }
foo[0][0] = 4
foo # => [[4, 2, 3], [1, 2, 3]]
But, at that point it's not as convenient, so I'd use the default Array.new(n) {…} behavior.
here is the problem
I have an array:
a = [1, 2, 3, 4]
and want to get:
b = [[1, 2, 3], [1, 3, 4], [1, 2, 4], [2, 3, 4]]
what is the best way? thanks!
You are looking for all unique sets of 3 elements out of a set of 4.
Use Array#combination method:
a = [1, 2, 3, 4]
b = a.combination(3).to_a
output:
=> [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]
More info:
Array#combination
Wikipedia Combination
Here would be my first implementation. (But performance suckz i guess)
array = [1,2,3,4]
b = []
array.each{|e| c = array.clone; c.delete(e); b << c}
# b.sort!
How can I go from this:
for number in [1,2] do
puts 1+number
puts 2+number
puts 3+number
end
which will return 2,3,4 then 3,4,5 -> 2,3,4,3,4,5. This is just an example, and clearly not the real use.
Instead, I would like it to return 2,3 3,4 4,5 -> 2,3,3,4,4,5. I would like each of the puts to be iterated for each of the possible values of number; In this case 1 and 2 are the two possible values of 'number', before moving on to the next puts.
One way to do this is to create two lists, [2,3,4] and [3,4,5] and then use the zip method to combine them like [2,3,4].zip([3,4,5]) -> [2,3,3,4,4,5].
zip is good. You should also look at each_cons:
1.9.2p290 :006 > [2,3,4].each_cons(2).to_a
=> [[2, 3], [3, 4]]
1.9.2p290 :007 > [2,3,4,5,6].each_cons(2).to_a
=> [[2, 3], [3, 4], [4, 5], [5, 6]]
1.9.2p290 :008 > [2,3,4,5,6].each_cons(3).to_a
=> [[2, 3, 4], [3, 4, 5], [4, 5, 6]]
Because each_cons returns an Enumerator, you can use a block with it, as mentioned in the documentation for it, or convert it to an array using to_a like I did above. That returns the array of arrays, which can be flattened to get a single array:
[2,3,4,5].each_cons(2).to_a.flatten
=> [2, 3, 3, 4, 4, 5]
From the ri docs:
Iterates the given block for each array of consecutive elements. If no
block is given, returns an enumerator.
e.g.:
(1..10).each_cons(3) {|a| p a}
# outputs below
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
Maybe not the most readable code but you could use inject on the first range to create an array based on the summed up second range.
(1..3).inject([]){|m,n| (1..2).each{|i| m<<n+i }; m }
=> [2, 3, 3, 4, 4, 5]
This might be a little more readable
res=[]
(1..3).each{|r1| (1..2).each{|r2| res<<r1+r2 } }
[1, 2, 3].each { |i| [1, 2].each { |y| puts i + y } }
Can anybody explain why Array in Ruby doesn't have a drop! method?
Is there a way to drop or slice an array in place?
There's slice!. It can take an index (just like drop, so slice!(index) is the in-place drop you're looking for), a range, or two parameters for start and end.
You can use slice! with a range to accomplish this:
a = [1, 2, 3, 4]
a.drop(2)
=> [3, 4]
a
=> [1, 2, 3, 4]
vs
a = [1, 2, 3, 4]
a.slice!(0..1)
=> [1, 2]
a
=> [3, 4]