Switch Round Robins/Americano Algorithm in Ruby - ruby

Just as part of my hobby, I'm try to build a scheduler where you enter N number of players in the system, and it will generate an array of combinations. For example
players = ['A','B','C','D','E']
players.combination(2).to_a
# ["A", "B"], ["A", "C"], ["A", "D"], ["A", "E"], ["B", "C"], ["B", "D"], ["B", "E"], ["C", "D"], ["C", "E"], ["D", "E"]]
Then I would like my generate_matches functions to take this array of pairs, and turn this into matches where teach pair only plays once AND players of course cannot play themselves. This should work on a minimum 4 players, and work for every number above 4 whether it's odd or even.
I thought of it to be a simple issue to solve, but I've been just in a rabbit hole.
See pseudocode & code below:
# select random team
# find team that does not contain one of the players
# create match between team 1 and team 2
# delete team 1 and team 2 from original array
# repeat until no team left
def generate_matches(player_combinations)
matches = []
while !player_combinations.empty?
team_1 = player_combinations.shift
# if team doesn't include any of the team 1 player, select them
filtered_teams = player_combinations.select do |team|
(team & team_1).empty?
end
team_2 = filtered_teams.pop
player_combinations.delete(team_2)
matches << [team_1, team_2]
end
matches
end
Expected Result:
[
[['C','B'], ['A','E']],
[['E','D'], ['B','A']],
[['A','C'], ['D','B']],
[['B','E'], ['C','D']],
[['D','A'], ['E','C']],
]
Actual Result:
[
[["A", "C"], ["D", "E"]],
[["C", "D"], ["B", "E"]],
[["A", "B"], ["C", "E"]],
[["B", "C"], ["A", "E"]]
]
This should result in equal amount of matches for players, however it doesn't. I'm really confused why. See results after running it a few times:
Key Represents Player
Value represents # games they played
{"A"=>4, "C"=>3, "B"=>2, "E"=>4, "D"=>3}
But it should actually be:
{"A"=>4, "C"=>4, "B"=>4, "E"=>4, "D"=>4}
Sometimes it's just dumb luck, and I do get the correct answer. Most of the times, I don't. I guess I think too simple about this problem

Related

Ruby: How to count the number of times a string appears in another string?

I'm trying to count the number of times a string appears in another string.
I know you can count the number of times a letter appears in a string:
string = "aabbccddbb"
string.count('a')
=> 2
But if I search for how many times 'aa' appears in this string, I also get two.
string.count('aa')
=> 2
I don't understand this. I put the value in quotation marks, so I'm searching for the number of times the exact string appears, not just the letters.
Here are two ways to count the numbers of times a given substring appears in a string (the first being my preference). Note (as confirmed by the OP) the substring 'aa' appears twice in the string 'aaa', and therefore five times in:
str = "aaabbccaaaaddbab"
1. Use String#scan with a regex that contains a positive lookahead that looks for the given substring
def count_em(str, substr)
str.scan(/(?=#{substr})/).count
end
count_em(str,"aa")
#=> 5
count_em(str,"ab")
#=> 2
Note:
"aaabbccaaaaddbab".scan(/(?=aa)/)
#=> ["", "", "", "", ""]
A positive lookbehind produces the same result:
"aaabbccaaaaddbab".scan(/(?<=aa)/)
#=> ["", "", "", "", ""]
As well, String#scan could be replaced with the form of String#gsub that takes one argument (here the same regular expression) and no block, and returns an enumerator. That form of gsub in unusual in that has nothing to do with character replacement; it simply generates matches of the regular expression.
2. Convert given string to an array of characters, apply String#each_char then Enumerable#each_cons, then Enumerable#count
def count_em(str, substr)
subarr = substr.chars
str.each_char
.each_cons(substr.size)
.count(subarr)
end
count_em(str,"aa")
#=> 5
count_em(str,"ab")
#=> 2
We have:
subarr = "aa".chars
#=> ["a", "a"]
enum0 = "aaabbccaaaaddbab".each_char
#=> #<Enumerator: "aaabbccaaaaddbab":each_char>
We can see the elements that will generated by this enumerator by converting it to an array:
enum0.to_a
#=> ["a", "a", "a", "b", "b", "c", "c", "a", "a", "a",
# "a", "d", "d", "b", "a", "b"]
enum1 = enum0.each_cons("aa".size)
#=> #<Enumerator: #<Enumerator:
# "aaabbccaaaaddbab":each_char>:each_cons(2)>
Convert enum1 to an array to see what values the enumerator will pass on to map:
enum1.to_a
#=> [["a", "a"], ["a", "a"], ["a", "b"], ["b", "b"], ["b", "c"],
# ["c", "c"], ["c", "a"], ["a", "a"], ["a", "a"], ["a", "a"],
# ["a", "d"], ["d", "d"], ["d", "b"], ["b", "a"],
# ["a", "b"]]
enum1.count(subarr)
#=> enum1.count(["a", "a"])
#=> 5
It's because the count counts characters, not instances of strings. In this case 'aa' means the same thing as 'a', it's considered a set of characters to count.
To count the number of times aa appears in the string:
string = "aabbccddbb"
string.scan(/aa/).length
# => 1
string.scan(/bb/).length
# => 2
string.scan(/ff/).length
# => 0
try to use
string.split('a').count - 1

delete similar elements in an array in ruby

I have the following array
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"]]
I want to get the following output [["a","b"], ["a","c"], ["b","c"]] and delete ["b","a"] and ["c","b"]
I have the following code
a3.each do |ary3|
x = ary3[0]
y = ary3[1]
x = ary3[1]
y = ary3[0]
if a3.include?([x,y])
a3 - [y,x]
end
end
print a3
I tried using the swap, but no luck!
Thanks for the help.
Two arrays are considered to be equal if they contain the same elements and these elements are in the same order:
["a", "b"] == ["b", "a"]
#=> false
["a", "b"] == ["a", "b"]
#=> true
So you need to sort the inner arrays first and then you can use Array#uniq to ensure that each element in the outer array will only appear once:
arr = [["a", "b"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
arr.map(&:sort).uniq
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
This will leave arr untouched, however:
arr
#=> [["a", "b"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
You will need to use mutator methods (with a !) to edit the array in place:
arr = [["a", "b"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
arr.map!(&:sort).uniq!
arr
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
Edit
As a follow-up to #sawa's comment, who was concerned that it may not be desirable to change the ordering of the inner arrays, i looked a bit deeper into Array#uniq. Consider the following array:
arr = [["b", "a"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
I figured out that Array#uniq actually takes a block that lets you specify how the elements should be be compared:
arr.uniq!{|x| x.sort }
arr
#=> [["b", "a"], ["a", "c"], ["b", "c"]]
Cool thing is, this also works with Symbol#to_proc (the &: notation) and actually looks even more elegant than my original answer:
arr.uniq!(&:sort)
arr
#=> [["b", "a"], ["a", "c"], ["b", "c"]]
You can still use Array#sort! if you want the inner arrays to be sorted afterwards:
arr.uniq!(&:sort!)
arr
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
My last observation on this is though, that the order probably isn't important or else two arrays with different order would not be considered equal. This got me thinking (again) and i posed myself the question: Why not use a Set? It would work like this:
require 'set'
sets = [Set["a", "b"], Set["a", "c"], Set["b", "c"], Set["b", "a"], Set["c", "b"]]
sets.uniq!
sets
#=> [#<Set: {"a", "b"}>, #<Set: {"a", "c"}>, #<Set: {"b", "c"}>]
Just keep in mind that a Set will not allow you to add the same element multiple times, whereas an array does:
[%w[a b b b c], %w[a b b b c], %w[a b c]].uniq(&:sort)
#=> [["a", "b", "b", "b", "c"], ["a", "b", "c"]]
[Set.new(%w[a b b b c]), Set.new(%w[a b b b c]), Set.new(%w[a b c])].uniq
#=> [#<Set: {"a", "b", "c"}>]
check this one: #delete_if as below:
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"]]
p a3.delete_if{|x| [["b", "a"], ["c","b"]].include? x}
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
As per your comment and description post :
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"],["b", "a"]]
p a3.each {|x| a3.delete(x.reverse) if a3.include? x.reverse}
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
BenchMark:
require 'benchmark'
N = 10000
Benchmark.bm(20) do | x |
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"],["b", "a"]]
x.report('Mine') do
N.times { a3.each {|x| a3.delete(x.reverse) if a3.include? x.reverse} }
end
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"],["b", "a"]]
x.report('padde') do
N.times { a3.uniq!(&:sort!) }
end
end
Output:
user system total real
Mine 0.172000 0.000000 0.172000 ( 0.361021)
padde 0.203000 0.000000 0.203000 ( 0.460026)

Merging arrays without losing original order, ruby

I have an array structure that looks like:
a=[
[['a','A'],['b','B'],['c','C']],
[['d','D'],['e','E'],['f','F']]
]
How to merge inner two arrays so the new structure will be Array of arrays
[
['a','A'],['b','B'],['c','C'],['d','D'],['e','E'],['f','F']
]
Tried
a.inject([]){|k,v| v | k} # but order gets changed
=> [["d", "D"], ["e", "E"], ["f", "F"], ["a", "A"], ["b", "B"], ["c", "C"]]
How can i get desired result without loosing the order.
Tips, comments, suggestions, please?
Thnx.
array.flatten takes a parameter:
a.flatten(1) #[["a", "A"], ["b", "B"], ["c", "C"], ["d", "D"], ["e", "E"], ["f", "F"]]
Try this:
a.inject([]){|k,v| k|v}

Checking through Array subset

I have a challenge, im trying to write a method that takes in an array and returns the subset and permutation of twos, including the initial array. How do I check for particular patterns in the array. For example, given this array:
[a,b,c]
subset return will be:
[a,b,c,], [a,b], [b,c], [c,a]
and I also need to check if each subset contains a particular letter. Here's my code:
def conflict_free?(a)
return a.permutation(2).to_a
end
Here's how to get the subsets you're looking for:
def subsets(a)
2.upto(a.length).flat_map {|n| a.combination(n).to_a}
end
irb(main):023:0> subsets(["a", "b", "c"])
=> [["a", "b"], ["a", "c"], ["b", "c"], ["a", "b", "c"]]
Anything else you want, you'll have to edit your question and provide more detail.
Here is a very compact and fast solution :
def conflict(a)
a.combination(2).to_a << a
end
>> [["a", "b"], ["a", "c"], ["b", "c"], ["a", "b", "c"]]
If you did want the initial array at the beginning you sacrificing a fair bit of speed. Nevertheless the best way to do it :
def conflict(a)
temp = [a]
a.combination(2).each { |com| temp << com}
temp
end
>> [["a", "b", "c"], ["a", "b"], ["a", "c"], ["b", "c"]]
If the input is not 3 then this will work :
def conflict(a)
temp = []
2.upto(a.size-1) {|i| temp += a.combination(i).to_a}
temp << a
end
The initial array can be added at the beginning or end. Above it's at the end.

Ruby Combinations with array elements

Ok, i've searched the internet for answers and also searched for hours in my ruby programmer but i cant sort this out. I'm writing a script for making all sorts of combinations from elements in an array.
ar = ["a","b","c","d"]
At this point I am able to make these combinations:
["a"],["a","b"],["a","b","c"],["a","b","c","d"],["b"],["b","c"],["b","c","d"],["c"],["c","d"],["d"]
This is OK, but I can't find a way for searching these combinations, for example ["a","c"] or ["a","c","d"] or ["a","d"], etc...
For now my code looks like:
def combinaties(array)
combinaties = []
i=0
while i <= array.length-1
combinaties << array[i]
unless i == array.length-1
array[(i+1)..(array.length-1)].each{|volgend_element|
combinaties<<(combinaties.last.dup<<volgend_element)
}
end
i+=1
end
end
Functional approach (needs Ruby >= 1.9) to create the powerset of an array (except for the empty element you don't seem to need):
xs = ["a", "b", "c", "d"]
yss = 1.upto(xs.size).flat_map do |n|
xs.combination(n).to_a
end
#[
# ["a"], ["b"], ["c"], ["d"],
# ["a", "b"], ["a", "c"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"],
# ["a", "b", "c"], ["a", "b", "d"], ["a", "c", "d"], ["b", "c", "d"],
# ["a", "b", "c", "d"],
#]
There is a trivial correspondence (bijection) between such combinations and the numbers in [1..(2^m - 1)] (m being the array length).
Consider such a number n. It's binary representation has m digits (including leading zeros). The positions of the digits that are 1 are the indices of the elements in the corresponding combination.
The code would be:
def combinations(array)
m = array.length
(1...2**m).map do | n |
(0...m).select { | i | n[i] == 1 }.map { | i | array[i] }
end
end
Or in ruby 1.9
%w(a b c d e).combination(3).to_a
will give you all the combinations of size 3.

Resources