Get all combinations of six players - ruby

I have an array of six players. This means I have fifteen unique games:
players = [1, 2, 3, 4, 5, 6]
games = players.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]]
I want to arrange these games randomly into 5 rounds of 3. Each player should play 1 game each round, and no pair should repeat from any previous round.
In each round, I have tried picking player1 and player2 randomly, using while loop coupled with each, but I always end up in infinite loop. Any suggestions?
So here is the code (sorry for not putting it in eariler). Problem is that sometimes it does work and gives me the result that I want but sometimes it just breaks down and it's caught in the loop.
def pick_pair(players)
player1 = players[rand(players.length)]
players.delete(player1)
player2 = players[rand(players.length)]
players.delete(player2)
pair = [player1, player2]
return pair.sort!
end
def check_round(all_rounds, current_round)
repeat = false
if all_rounds == []
repeat = false
else
repeat = catch :repeat do
k = 0
all_rounds.each do |round|
a_r_l = all_rounds.length
round.each do |pair|
r_l = round.length
k += 1
z = 0
current_round.each do |new_pair|
#p "Comparing: #{pair} to #{new_pair}"
z += 1
if pair == new_pair
repeat = true
throw :repeat, repeat
elsif (k == a_r_l*r_l and z == current_round.length and pair != new_pair)
repeat = false
throw :repeat, repeat
end
end
end
end
end
end
return repeat
end
players = [1,2,3,4,5,6]
all_rounds = []
for i in 1..(players.length-1)
#p "Round: #{i}"
players_d = players.dup
current_round = catch :round do
check = true
while check
current_round = []
for j in 1..(players.length/2)
#p "Game: #{j}"
pair = pick_pair(players_d)
current_round << pair
end
p "Previous rounds: #{all_rounds}"
p "Current round: #{current_round}"
repeat = check_round(all_rounds, current_round)
if repeat == false
throw :round, current_round
else
players_d = players.dup
end
end
end
all_rounds << current_round
end
Hey,
thanks for the help. I rewrote the code and it seems to works. It's also much simpler:
players = [1,2,3,4,5,6]
possible_games = players.combination(2).to_a
all_games = []
for i in 1..(players.length - 1)
round = catch :round do
check = true
while check
round = []
for i in 1..(players.length/2)
pair = possible_games[rand(possible_games.length)]
round << pair
end
if round.flatten.uniq == round.flatten
round.each do |game|
possible_games.delete(game)
end
throw :round, round
else
end
end
end
all_games << round.sort!
end
p all_games

Basically you are trying for random combinations and retrying until the scheme is valid. It will succeed now and then, but it will get in a deadlock sometimes:
Round 1 2 3 4 5
12 14 16 13
34 36 32 5??
56 52 54
In round 4, player 5 must play player 1 or 3, but that is not going to happen because 3 is already playing 1. Your script keeps retrying until the end of times.
Such a tournament is called a round-robin tournament; wikipedia has an algorithm.

Although, strictly speaking, the best (most efficient) solution here would be to use a "round-robin" algorithm as suggested by steenslang.
I would suggest a simpler approach which works great and is quite straight forward.
Basically, the program tries to make each round have each player playing once per round. It loops over a shuffled array of combinations and tries pairing them up. If the combination is not possible, it reshuffles the array and tries again.
It's simple and it works great.
Plus, you can change the number of players if you need to too and it just works.
Hope this helps:
players = [1, 2, 3, 4, 5, 6]
initial_games = players.combination(2).to_a.shuffle
players_sum = players.inject{|sum,x| sum + x }
all_rounds = []
games = initial_games
until games.empty?
current_round = []
games.each do |game|
if (current_round.flatten & game).empty?
current_round << game
end
end
current_round_sum = current_round.flatten.inject{|sum,x| sum + x }
if current_round_sum == players_sum
all_rounds << current_round
games = games - current_round
else
all_rounds = []
games = initial_games.shuffle
end
end
puts all_rounds.inspect # this outputs 5 rounds of 3 games

Related

Ruby "Colorful Number" method for numbers with only three digits - absolute beginner -

I'm an absolute coding beginner and have just started to learn ruby. I had a challenge where I was supposed to check if a number was "colorful".
When in a given number, product of every digit of a sub-sequence are different. That number is called Colorful Number.
For example, "263 is a colorful number because [2, 6, 3, 2*6, 6*3, 2*6*3] are all different; whereas 236 is not colorful, because [2, 3, 6, 2*3, 3*6, 2*3*6] have 6 twice.
So take all consecutive subsets of digits, take their product and ensure all the products are different."
In the challenge given we were to only accept numbers up to three digits.
So since I am a beginner I tried to write every product seperately.
I know this is not "good" code, but still I want to understand why it is not working. I think it should... but it doesn't :)
I would be so glad if someone could tell my why this is not working.
I am not looking for a nicer solution, I really just want to know why mine doesn't work.
THANK YOU SO MUCH!
def colorful?(number)
if number.class != Integer
return false
elsif number.to_s.length > 3
return false
elsif number == 0 || 1
return true
end
digits_arr = number.digits.reverse
product_1 = digits_arr[0]*digits_arr[1]
product_2 = digits_arr[1]*digits_arr[2]
product_3 = digits_arr[0]*digits_arr[1]*digits_arr[2]
final_array = digits_arr + product_1 + product_2 + product_3
if final_array.uniq.length == final_array.length
return true
else
return false
end
end
So, your biggest issue is here:
elsif number == 0 || 1
This means "when number is equal to 0 OR when 1 is truthy value". In Ruby, 1 is always a truthy value, so this condition is always satisfied, leading to the execution of return true, terminating your method and returning true for any value.
Once you replace it with either
elsif number == 0 || number == 1
or
elsif [0,1].include? number
you will still have some more issues to fix - next one will be No implicit conversion of Integer into an Array when you try to add number to an Array. I hope you'll figure this one out by yourself. :)
While we're here, few minor notes.
In Ruby, we prefer duck-typing over strong types (mostly becuase we don't have strong typing) so any form of argument type checks like if number.class != Integer is thrown upon (not to mention it won't work very well as you will reject a large number of other Numeric classes - if you have to chack for classes use is_a? or kind_of?). Note that if your method is ever executed with something that is not a number, you do want to throw an exception rather than let the calculation continue - it is much easier to prevent your system from getting into a bad state than debugging the bad state and guessing how you got there.
Might be personal opinion here, but elsif sucks - almost always there is a better way. In this scenario, since you're returning in each case, you could just do:
return false unless number.is_a? Integer # really unnecessary
return false if number.to_s.length > 3
return true if number == 0 || number == 1
require 'set'
def colorful?(n)
digits = n.digits.reverse
products = Set.new
(1..digits.size-1).each do |n|
digits.each_cons(n) { |a| return false unless products.add?(a.reduce(:*)) }
end
true
end
colorful? 263
#=> true
colorful? 236
#=> false
colorful? 3245
#=> true
See Set#add?
I can best show how the method works by adding some puts statements and executing the method for two of the example integers above.
def colorful?(n)
digits = n.digits.reverse
puts "digits = #{digits}"
products = Set.new
(1..digits.size-1).each do |n|
puts "n = #{n}"
puts "digits.each_cons(#{n}).to_a #=> #{digits.each_cons(n).to_a}"
digits.each_cons(n) do |a|
m = a.reduce(:*)
puts " a = #{a}, m = #{m}"
puts " products = #{products}"
puts " products.include?(#{m}) = #{products.include?(m)}"
return false unless products.add?(m)
end
end
puts "true is returned"
true
end
colorful? 263
#=> true
The following is displayed.
digits = [2, 6, 3]
n = 1
digits.each_cons(1).to_a #=> [[2], [6], [3]]
a = [2], m = 2
products = #<Set: {}>
products.include?(2) = false
a = [6], m = 6
products = #<Set: {2}>
products.include?(6) = false
a = [3], m = 3
products = #<Set: {2, 6}>
products.include?(3) = false
n = 2
digits.each_cons(2).to_a #=> [[2, 6], [6, 3]]
a = [2, 6], m = 12
products = #<Set: {2, 6, 3}>
products.include?(12) = false
a = [6, 3], m = 18
products = #<Set: {2, 6, 3, 12}>
products.include?(18) = false
true is returned
colorful? 236
#=> false
The following is displayed.
digits = [2, 3, 6]
n = 1
digits.each_cons(1).to_a #=> [[2], [3], [6]]
a = [2], m = 2
products = #<Set: {}>
products.include?(2) = false
a = [3], m = 3
products = #<Set: {2}>
products.include?(3) = false
a = [6], m = 6
products = #<Set: {2, 3}>
products.include?(6) = false
n = 2
digits.each_cons(2).to_a #=> [[2, 3], [3, 6]]
a = [2, 3], m = 6
products = #<Set: {2, 3, 6}>
products.include?(6) = true
Because products was found to contain m (3), false was returned.
So in the end I've solved it like this (I know there are nicer ways, but still, it now works (as in can tell if a number is colorful or not, if it has no more than 3 digits):
def colorful?(number)
return false unless number.is_a? Integer
digits_arr = number.digits.reverse
return false if digits_arr.length > 3
return true if digits_arr.length == 1
final_array = []
final_array << digits_arr
case digits_arr.length
when 3
product_one = digits_arr[0] * digits_arr[1]
final_array << product_one
product_two = digits_arr[1] * digits_arr[2]
final_array << product_two
product_three = digits_arr[0] * digits_arr[1] * digits_arr[2]
final_array << product_three
when 2
product_one = digits_arr[0] * digits_arr[1]
final_array << product_one
end
if final_array.flatten.uniq == final_array.flatten
return true
else
return false
end
end

I'm trying to do a stock picker method on Ruby but i have some issue in my code

I'm trying to do a stock picker method that takes in an array of stock prices, one for each hypothetical day. It should return a pair of days representing the best day to buy and the best day to sell. Days start at 0.
def stock_picker stocks
pair = []
if stocks.size < 2
return "Please enter an array with a valid number of stocks"
else
buy_day = 0
sell_day = 0
profit = 0
stocks.each_with_index do |buy, index|
i = index
while (i < stocks[index..-1].size)
if ((buy - stocks[i]) > profit)
profit = buy - stocks[i]
buy_day = stocks.index(buy)
sell_day = i
end
i+= 1
end
end
pair = [buy_day,sell_day]
return pair.inspect
end
end
stock_picker([17,3,6,9,15,8,6,1,10])
It should return [1,4] instead of [0,7]
Another option is to slice the Array while iterating over it for finding the best profit:
res = ary.each_with_index.with_object([]) do |(buy_val, i), res|
highest_val = ary[i..].max
highest_idx = ary[i..].each_with_index.max[1] + i
res << [highest_val - buy_val, i, highest_idx]
end.max_by(&:first)
#=> [12, 1, 4]
Where 12 is the profit, 1 is the buy index and 4 is the sell index.
To understand how it works, run this extended version, it worth more than any written explanation:
res = []
ary.each_with_index do |buy_val, i|
p buy_val
p ary[i..]
p highest_val = ary[i..].max
p highest_idx = ary[i..].each_with_index.max[1] + i
res << [highest_val - buy_val, i, highest_idx]
p '----'
end
res #=> [[0, 0, 0], [12, 1, 4], [9, 2, 4], [6, 3, 4], [0, 4, 4], [2, 5, 8], [4, 6, 8], [9, 7, 8], [0, 8, 8]]
From the Ruby standard library I used Enumerable#each_with_index, Enumerable#each_with_object, Enumerable#max and Enumerable#max_by.
For getting the index of the max I kindly stole from Chuck (https://stackoverflow.com/a/2149874), thanks and +1. I didn't look for any better option.
As per a comment from Cary Swoveland in the linked post:
[..] a.index(a.max) will return the index of the first and
a.each_with_index.max[1] will return the index of the last [..]
So, maybe you want to use the first option to keep the time between buy and sell shorter.
Use Array#combination:
stocks.
each_with_index.
to_a.
combination(2).
select { |(_, idx1), (_, idx2)| idx2 > idx1 }.
reduce([-1, [-1, -1]]) do |(val, acc), ((v1, idx1), (v2, idx2))|
val < v2 - v1 ? [v2 - v1, [idx1, idx2]] : [val, acc]
end
#⇒ [ 12, [1, 4] ]
You can loop through the stock_prices array selecting for days with greatest positive difference. Your while condition needs to be changed.
#steps
#sets value of biggest_profit to 0(biggest_loss if looking for loss)
#sets most_profitable_days to [nil,nil]
#loops through array
#takes buy day
#loops through remainder of array
#if current day-first day>biggest_profit (first_day-current_day for loss)
#make >= for shortest holding period
#reassign biggest_profit
#most_profitable_days.first=buy_day, most_profitable_days.last=sell_day
#sell_day & buy_day are values of indices
#tests
#must accept only array
#must return array
#must return correct array
def stock_picker(arr)
#checks to make sure array inputs only are given
raise 'Only arrays allowed' unless arr.instance_of?(Array)
#sets value of biggest_profit to 0(biggest_loss if looking for loss)
biggest_profit=0
#sets most_profitable_days to [nil,nil]
most_profitable_days=[nil,nil]
#loops through array
arr.each_with_index do |starting_price, buy_day|
#takes buy day
arr.each_with_index do |final_price,sell_day|
#loops through remainder of array
next if sell_day<=buy_day
#if current day-first day>biggest_profit (first_day-current_day for loss)
#make '>=' for shortest holding period
if final_price-starting_price>=biggest_profit
#reassign biggest_profit
biggest_profit=final_price-starting_price
#most_profitable_days.first=buy_day,
most_profitable_days[0]=buy_day#+1 #to make it more user friendly
#most_profitable_days.last=sell_day
most_profitable_days[-1]=sell_day#+1 #to make it more user friendly
end
end
end
#return most_profitable_days
most_profitable_days
end
p stock_picker([3,2,5,4,12,3]) #[1,4]

How to improve algorithm efficiency for nested loop

Given a list of integers and a single sum value, return the first two values (from the left) that add up to form the sum.
For example, given:
sum_pairs([10, 5, 2, 3, 7, 5], 10)
[5, 5] (at indices [1, 5] of [10, 5, 2, 3, 7, 5]) add up to 10, and [3, 7] (at indices [3, 4]) add up to 10. Among them, the entire pair [3, 7] is earlier, and therefore is the correct answer.
Here is my code:
def sum_pairs(ints, s)
result = []
i = 0
while i < ints.length - 1
j = i+1
while j < ints.length
result << [ints[i],ints[j]] if ints[i] + ints[j] == s
j += 1
end
i += 1
end
puts result.to_s
result.min
end
It works, but is too inefficient, taking 12000 ms to run. The nested loop is the problem of inefficiency. How could I improve the algorithm?
Have a Set of numbers you have seen, starting empty
Look at each number in the input list
Calculate which number you would need to add to it to make up the sum
See if that number is in the set
If it is, return it, and the current element
If not, add the current element to the set, and continue the loop
When the loop ends, you are certain there is no such pair; the task does not specify, but returning nil is likely the best option
Should go superfast, as there is only a single loop. It also terminates as soon as it finds the first matching pair, so normally you wouldn't even go through every element before you get your answer.
As a style thing, using while in this way is very unRubyish. In implementing the above, I suggest you use ints.each do |int| ... end rather than while.
EDIT: As Cary Swoveland commented, for a weird reason I thought you needed indices, not the values.
require 'set'
def sum_pairs(arr, target)
s = Set.new
arr.each do |v|
return [target-v, v] if s.include?(target-v)
s << v
end
nil
end
sum_pairs [10, 5, 2, 3, 7, 5], 10
#=> [3, 7]
sum_pairs [10, 5, 2, 3, 7, 5], 99
#=> nil
I've used Set methods to speed include? lookups (and, less important, to save only unique values).
Try below, as it is much more readable.
def sum_pairs(ints, s)
ints.each_with_index.map do |ele, i|
if ele < s
rem_arr = ints.from(i + 1)
rem = s - ele
[ele, rem] if rem_arr.include?(rem)
end
end.compact.last
end
One liner (the fastest?)
ary = [10, 0, 8, 5, 2, 7, 3, 5, 5]
sum = 10
def sum_pairs(ary, sum)
ary.map.with_index { |e, i| [e, i] }.combination(2).to_a.keep_if { |a| a.first.first + a.last.first == sum }.map { |e| [e, e.max { |a, b| a.last <=> b.last }.last] }.min { |a, b| a.last <=> b.last }.first.map{ |e| e.first }
end
Yes, it's not really readable, but if you add methods step by step starting from ary.map.with_index { |e, i| [e, i] } it's easy to understand how it works.

How can I remove duplicates in an array without using `uniq`?

The object of my coding exercise is to get rid of duplicates in an array without using the uniq method. Here is my code:
numbers = [1, 4, 2, 4, 3, 1, 5]
def my_uniq(array)
sorted = array.sort
count = 1
while count <= sorted.length
while true
sorted.delete_if {|i| i = i + count}
count += 1
end
end
return sorted
end
When I run this, I get an infinite loop. What is wrong?
Can I use delete the way that I am doing with count?
How will it execute? Will count continue until the end of the array before the method iterates to the next index?
I did this with each or map, and got the same results. What is the best way to do this using each, delete_if, map, or a while loop (with a second loop that compares against the first one)?
Here is a clearly written example.
numbers = [1, 4, 2, 4, 3, 1, 5]
def remove_duplicates(array)
response = Array.new
array.each do |number|
response << number unless response.include?(number)
end
return response
end
remove_duplicates(numbers)
As others pointed out, your inner loop is infinite. Here's a concise solution with no loops:
numbers.group_by{|n| n}.keys
You can sort it if you want, but this solution doesn't require it.
the problem is that the inner loop is an infinite loop:
while true
sorted.delete_if {|i| i = i + count}
count += 1
end #while
you can probably do what you are doing but it's not eliminating duplicates.
one way to do this would be:
numbers = [1, 4, 2, 4, 3, 1, 5]
target = []
numbers.each {|x| target << x unless target.include?(x) }
puts target.inspect
to add it to the array class:
class ::Array
def my_uniq
target = []
self.each {|x| target << x unless target.include?(x) }
target
end
end
now you can do:
numbers = [1, 4, 2, 4, 3, 1, 5]
numbers.my_uniq
You count use Set that acts like an array with does not allow duplicates:
require 'set'
numbers = [1, 4, 2, 4, 3, 1, 5]
Set.new(numbers).to_a
#=> [1, 4, 2, 3, 5]
Try using Array#& passing the array itself as parameter:
x = [1,2,3,3,3]
x & x #=> [1,2,3]
This is one of the answer. However, I do not know how much of performance issue it takes to return unique
def my_uniq(ints)
i = 0
uniq = []
while i < ints.length
ints.each do |integers|
if integers == i
uniq.push(integers)
end
i += 1
end
end
return uniq
end

Scheduling 15 games into 5 rounds of 3 - random pick not working

I have an array of six players. This means I have fifteen unique games:
players = [1, 2, 3, 4, 5, 6]
games = players.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]]
I want to arrange these games randomly into 5 rounds of 3. Each player should play 1 game each round, and no pair should repeat from any previous round.
In each round, I have tried picking player1 and player2 randomly, using while loop coupled with each, but I always end up in infinite loop. Any suggestions?
First, install y_support gem by typing gem install y_support in the command line. You know, I'm uncomfortable in Ruby without it. And then:
require 'y_support/all'
class Player
include NameMagic
def to_s; "#{name}" end
def inspect; to_s end
end
Calvin, Paul, Willis, Shannon, Randy, Arnold = 6.times.map { Player.new }
Players = Player.instances
Bout = -> player1, player2, score_table do
puts "Higashi, #{player1}! Nishi, #{player2}"
winner = rand() > 0.5 ? player1 : player2
puts "Winner: #{winner}!!!"
puts
score_table[ winner ] = score_table[ winner ] + 1
end
Basho = -> days: days do
combinations = Players.shuffle.combination( 2 ).to_a
first = days.times.map { combinations.shift }
second = combinations.combination( days )
.find { |c| first.zip( c ).all? { |a, b| ( a + b ).uniq.size == 4 } }
return puts "Required tournament not possible!" unless second
puts
puts "Bang, bang, bang! Tournament is starting!"
puts
score = Players >> Players.size.times.map { 0 }
[ first, second ].transpose.map { |a, b| [ a, b, Players - a - b ] }
.each_with_index { |day, idx|
puts
puts "Day #{idx} is starting!"
puts
day.each { |pair| Bout.call *pair, score }
}
puts
puts "Dam ti di dam! Tournament is over!"
puts "Yusho goes to #{Players.max_by &score.method(:fetch)}. Congratulations!"
puts
puts "Full results:"
score.pretty_print_numeric_values precision: 0
end
def Go_fight! days
Basho.call days: days
end
Go_fight! 5 #=> not possible
Go_fight! 3
You can see that you have asked a question which, in its literal form, has no solution. The above code uses brute force search to prove that your question has no solution for 5 tournament days and 4 tournament days, but is possible for 3 and less days.

Resources