Ruby combinatorics - ruby

I want to generate a soccer football fixture with a list of clubs. Each game is played on Sundays in a random time included in the match_starts_at array. Each club plays only one game each Sunday.
Example:
Having these clubs:
Club Atlético All Boys
Asociación Atlética Argentinos Juniors
Arsenal Fútbol Club
Club Atlético Banfield
Club Atlético Belgrano
Club Atlético Boca Juniors
Club Atlético Colón
Club Estudiantes de La Plata
Club Deportivo Godoy Cruz Antonio Tomba
Asociación Mutual Social y Deportiva Atlético de Rafaela
Club Atlético Independiente
Club Atlético Lanús
Club Atlético Newell's Old Boys
Club Olimpo
Racing Club
Club Atlético San Martín
Club Atlético San Lorenzo de Almagro
Club Atlético Tigre
Club Atlético Unión
Club Atlético Vélez Sarsfield
Result should be similar to what is seen here: http://www.afa.org.ar/index.php?option=com_content&view=article&id=16780%3Afixture-del-torneo-de-primera-division&Itemid=100
Club structure example:
=> # Club #id=1 #name="Example Name"
=> # Club #id=2 #name="Example2 Name"
Fixture structure example:
=> # Fixture #id=1 #datetime='2011-11-19 19:12:49' #home_id=1 #away_id=2
A Fixture object needs the following to be saved to the database:
a home club (:home)
an away club (:away)
and the time of the match (:datetime)
Each club should play only one time with the other clubs and all clubs should play one match at home, the other away , the other at home, etc. There should be 10 matches in a date. How can I create the list of matches?
This is what I've done so far.
competition = Competition.get(1)
clubs = Club.all #20 clubs
#time = Time.now
#count = 0
until #time.sunday? do
#time += (24*60*60) # add 1 day until it's sunday
end
#first_time = #time
#fixture = {1 => []}
clubs.combination(2).each_with_index do |(club1, club2), idx|
Fixture.create(
:home => idx.even? ? club1 : club2,
:away => idx.even? ? club2 : club1,
:datetime => available_fixture_date(club1,club2)
).save
end
def getFecha(club1, club2)
#fixture.keys.each do |fecha|
if (!#fixture[fecha].include?(club1.name) && !#fixture[fecha].include?(club2.name))
#fixture[fecha] << club1.name
#fixture[fecha] << club2.name
#fixture[#fixture.keys.last + 1] = []
return fecha
end
end
end
def available_fixture_date(club1, club2)
fecha = getFecha(club1, club2)
match_starts_at = ['16:00', '17:30', '18:10', '22:00']
match_time = match_starts_at.shuffle.first
#time = #first_time + (24*60*60) * fecha * 7
Time.new(#time.year, #time.month, #time.day, match_time[0,2], match_time[3,2])
end
With my code I get more than 19 dates and I should get 19 dates with 10 matches per date.

You won't get a nice one-liner for that, like you did for the pairing of teams, since it requires consulting the existing data to find out what dates are already taken. But this should work fine. Note that I've used ActiveSupport's time helpers, but you could use something like Chronic if you don't have ActiveSupport available and don't want to include it.
def available_fixture_date(club1, club2)
last_played = (club1.fixtures | club2.fixtures).max(:datetime)
last_played.nil? ? DateTime.now.sunday : last_played + 1.week
end
def create_fixtures(clubs)
clubs.combination(2).each_with_index do |(club1, club2), idx|
Fixture.create(
:home => idx.even? ? club1 : club2,
:away => idx.even? ? club2 : club1,
:datetime => available_fixture_date(club1, club2)
)
end
end

I believe the general algorithm you're looking for here is the Round-Robin. The following gets the dates correctly for me, ending up with 19 dates total, 10 matches per date:
DAY = 24 * 60 * 60
MATCH_START_TIMES = ['16:00', '17:30', '18:10', '22:00']
def fixture_date(fecha)
# select a random start time
match_time = MATCH_START_TIMES.sample
#time = #first_time + DAY * fecha * 7
Time.new(#time.year, #time.month, #time.day, match_time[0,2].to_i, match_time[3,2].to_i)
end
# uses round-robin indexing algorithm described on
# http://en.wikipedia.org/wiki/Round-robin%5Ftournament#Scheduling_algorithm
def round_robin(n, round)
arr = [*0...n]
arr.insert 1, *arr.pop(round)
[arr.slice(0, n/2), arr.slice(n/2, n).reverse]
end
def find_club_combination(clubs, round, pair)
indexes = round_robin(clubs.size, round)
index_a, index_b = indexes.first[pair], indexes.last[pair]
[clubs[index_a], clubs[index_b]]
end
competition = Competition.get(1)
clubs = Club.all #20 clubs
#time = Time.now
#count = 0
#time += DAY until #time.sunday?
#first_time = #time
num_rounds = clubs.size - 1
matches_per_day = clubs.size / 2
(0...num_rounds).collect do |round|
matches_per_day.times do |pair|
club1, club2 = find_club_combination(clubs, round, pair)
Fixture.create(
:home => round.even? ? club1 : club2,
:away => round.even? ? club2 : club1,
:datetime => fixture_date(round)
).save
end
end

Not sure exactly what you are after here, but you seem to want an easier way to do time calculations?
If so, Chronic is pretty cool.
Chronic.parse('next sunday at 4pm')
#=> Sun Nov 20 16:00:00 -0800 2011
match_starts_at = ['16:00', '17:30', '18:10', '22:00']
Chronic.parse("sunday at #{match_starts_at[0]}")
#=> Sun Nov 20 16:00:00 -0800 2011

Related

Ruby Hash: Subtracting quantities

I am trying to create a response that will allow the user to enter the key and value of the inventory to subtract from the inventory that already exists. So if there are 10 apples at the start of the inventory and I respond saying I'm selling 7 apples the remainder in the hash should be represented as 3 apples left.
I am a beginner and a bit lost so any explanation would be helpful. Thank you!
#inventory = {"apples" => 10, "bananas" => 10, "crackers" => 10, "breads" => 10}
def sell_inventory
puts "What food are we selling today?"
product = gets.chomp.downcase
puts "How many #{product} are we selling today?"
quantity = gets.to_i
#inventory.delete(product, quantity)
end
#inventory = { "apples" => 10, "bananas" => 10, "crackers" => 10, "breads" => 10 }
def sell_inventory
puts "What food are we selling today?"
product = gets.chomp.downcase
puts "How many #{product} are we selling today?"
quantity = gets.to_i
if #inventory.key?(product)
#inventory[product] -= quantity
#inventory[product] = 0 if #inventory[product] < 0
else
puts "No inventory product: #{product}"
end
end
At first I check whether product is an inventory product with Hash#key?. Otherwise I print an error.
Then I subtract the quantity. Last I check the total quantity can't be negative.
Hash.delete, which you tried, would remove the key-value-pair from the hash and returns the value. An example:
#inventory.delete("apples")
# => 8
#inventory
# => {"bananas"=>10, "crackers"=>10, "breads"=>10}

How many Friday 13ths in a year?

The only thing that is wrong in this code is the return :P
How would you display how many Friday 13ths there in a year?
def unlucky_days(year)
require 'date'
start_date = Date.new(year)
end_date = Date.new(year+1)
my_fridays = [4]
thirteen = "13"
result = (start_date..end_date).to_a.select {|k| my_fridays.include?(k.wday) && thirteen.include?(k.strftime('%d'))}
result.length
end
I'd write:
require 'date'
(1..12).count { |month| Date.new(year, month, 13).friday? }
+1 to #MarkReed's comments. Also, why call .to_a on a range, and why use variables when the Date class in Ruby already has methods like .day and .friday? Here is how I would do it:
def unlucky_days(year)
s = Date.new(year, 1, 1)
e = Date.new(year, 12, 31)
((s...e).select {|d| d.friday? && d.day == 13 }).count
end
Your code is wrong on a few points.
Friday is weekday number 5, not 4.
Why [4].include?(n) instead of just n==4?
"13".include?("#{n}") is not just strange but incorrect, since it returns true for 1 and 3 as well as 13.
You can go cut down on the level of brute force by just looking at the twelve 13ths and counting how many are Fridays, rather than looking at all 365 or 366 days and seeing which of them are both 13th's and Fridays, as in #tokland's answer, reproduced here:
def unlucky_days(year)
(1..12).count { |month| Date.new(year, month, 13).friday? }
end
Or, since there are only 14 possibilities, you could also just use a prebuilt table:
# number of Friday the 13ths in a given year is given by
# UnluckyDays[weekday of Jan 1][0 if common, 1 if leap]
UnluckyDays = [ [2,3], [2,2], [2,1], [1,2], [3,2], [1,1], [1,1] ]
def unlucky_days(year)
UnluckyDays[Date.new(year,1,1).wday][Date.leap?(year) ? 1 : 0 ]
end
This is a variant of #Tokland's answer.
require 'date'
def count_em(year)
d = Date.new(year, 1, 13) << 1
12.times.count { (d >>= 1).friday? }
end
(2010..2016).each { |y| puts "%d Friday the 13ths in %s" % [count_em(y), y] }
# 1 Friday the 13ths in 2010
# 1 Friday the 13ths in 2011
# 3 Friday the 13ths in 2012
# 2 Friday the 13ths in 2013
# 1 Friday the 13ths in 2014
# 3 Friday the 13ths in 2015
# 1 Friday the 13ths in 2016
If this calculation (or one like it) were done often and performance was important, two hashes could be constructed, one for leap years, the other for non-leap years, with keys the day of week on which the first day of the year falls and the values the number of Friday the 13ths in such years.

reset a count in ruby when putting to screen from parallel arrays

This is home work so I would prefer not to put up my code. I have 2 parallel arrays, 1.names 2. ages. The idea is to puts all ages less than 21. I can do this. The problem is that when I puts "#{count}. #{names[count]}, #{ages[count]}" <---The beginning count prints out the index number or position of element in array. Obviously what I want is for it to start at 1. if there are three names...
name, age
name, age
name, age
NOT
5, name, age
6, name, age
I am using a while loop with an if statement. I don't need code, just would like some feedback to trigger more ideas. Thanks for your time, much appreciated.
names[name1, name2, name3]
ages[age1, age2, age3]
#view all younger than 21
count = 0
while count < names.length
if ages[count] < 21
puts "#{count}. #{names[count]}, #{ages[count]}" #works
end
count += 1
end
pause
You shouldn't have "parallel arrays" in the first place! Data that belongs together should be manipulated together, not separately.
Instead of something like
names = %w[john bob alice liz]
ages = [16, 22, 18, 23 ]
You could, for example, have a map (called Hash in Ruby):
people = { 'john' => 16, 'bob' => 22, 'alice' => 18, 'liz' => 23 }
Then you would have something like:
puts people.select {|_name, age| age > 21 }.map.
with_index(1) {|(name, age), i| "#{i}. #{name}, #{age}" }
# 1. bob, 22
# 2. liz, 23
If you have no control over the creation of those parallel arrays, then it is still better to convert them to a sensible data structure first, and avoid the pain of having to juggle them in your algorithm:
people = Hash[names.zip(ages)]
Even better yet: you should have Person objects. After all, Ruby is object-oriented, not array-oriented or hash-oriented:
class Person < Struct.new(:name, :age)
def to_s
"#{name}, #{age}"
end
def adult?
age > 21
end
end
people = [
Person.new('john', 16),
Person.new('bob', 22),
Person.new('alice', 18),
Person.new('liz', 23)]
puts people.select(&:adult?).map.with_index(1) {|p, i| "#{i}. #{p}" }
Again, if you don't have control of the creation of those two parallel arrays, you can still convert them first:
people = names.zip(ages).map {|name, age| Person.new(name, age) }

Output an Array of objects to terminal, as a table with attributes in fixed-width columns

I am attempting to output a 2D array to the console; I want the information in the array to be nicely formatted as shown in my desired output at the end of this question. My array is created as follows (instances are created of the FamilyMember class):
#family_members.rb
class FamilyMember
attr_accessor :name, :sex, :status, :age
def initialize (name, sex, type, role, age)
#name = name
#sex = sex
#type = type
#role = role
#age = age
end
end
# Below, an array is created called fm; instances of the class are then instantiated within the array elements
fm = {}
fm[1] = FamilyMember.new('Andrew','Male', 'Child', 'Son' , '27' )
fm[2] = FamilyMember.new('Bill','Male', 'Parent', 'Father' , '63' )
fm[3] = FamilyMember.new('Samantha','Female', 'Parent', 'Mother' , '62' )
fm[4] = FamilyMember.new('Thomas','Male', 'Child', 'Dog' , '10' )
fm[5] = FamilyMember.new('Samantha', 'Female', 'Child', 'Dog' , '4' )
I want to be able to output the contents of the array to the console formatted as a string. I need to be able to do this two ways - using each and seperately by using do.
What I have attempted (inspired by a previous SO question):
def eacharray(an_array)
an_array.each do |inner|
puts inner.join(" ")
end
end
eacharray(fm)
However the output from the above is as follows:
1 #<FamilyMember:0x000000027e7d48>
2 #<FamilyMember:0x000000027e7c58>
3 #<FamilyMember:0x000000027e7b68>
4 #<FamilyMember:0x000000027e7a78>
5 #<FamilyMember:0x000000027e7988>
How do I output the 2D array elements nicely formatted using each and do?. Any help appreciated. Thanks.
Ideally, my output would be like this:
Family Member Name Sex Type Role Age
1 Andrew Male Child Son 27
2 Bill Male Parent Father 63
3 Samantha Female Parent Mother 62
4 Thomas Male Child Dog 10
5 Samantha Female Child Dog 4
If you're happy with a fixed format (you could build the format dynamically depending on the maximum data width in each field) you could write something like this.
class FamilyMember
attr_accessor :name, :sex, :type, :role, :age
def initialize (*args)
#name, #sex, #type, #role, #age = args
end
end
fm = []
fm << FamilyMember.new( 'Andrew', 'Male', 'Child', 'Son' , '27' )
fm << FamilyMember.new( 'Bill', 'Male', 'Parent', 'Father', '63' )
fm << FamilyMember.new( 'Samantha', 'Female', 'Parent', 'Mother', '62' )
fm << FamilyMember.new( 'Thomas', 'Male', 'Child', 'Dog' , '10' )
fm << FamilyMember.new( 'Samantha', 'Female', 'Child', 'Dog' , '4' )
format = '%-15s %-8s %-7s %-7s %-7s %s'
puts format % ['Family Member', 'Name', 'Sex', 'Type', 'Role', 'Age']
fm.each_with_index do |member, i|
puts format % [ i+1, member.name, member.sex, member.type, member.role, member.age ]
end
output
Family Member Name Sex Type Role Age
1 Andrew Male Child Son 27
2 Bill Male Parent Father 63
3 Samantha Female Parent Mother 62
4 Thomas Male Child Dog 10
5 Samantha Female Child Dog 4
You can also use for ... in, which actually compiles to pretty much the same loop, using the each iterator.
i = 0
for member in fm do
i += 1
puts format % [ i, member.name, member.sex, member.type, member.role, member.age ]
end
or you can use the primitive while or until loop constructs, which most Ruby programmers forget about. Ruby is much more expressive using its iterators.
i = 0
while member = fm[i] do
i += 1
puts format % [ i, member.name, member.sex, member.type, member.role, member.age ]
end
Note that you can omit the do from both of these last examples. As long as you have a newline (or a semicolon) after the while expression Ruby will understand just fine.
A class like your FamilyMember is most easily constructed with a Struct. The result is just a class, but with some extra features.
FamilyMember = Struct.new(:name, :sex, :type, :status, :age)
fm = []
fm << FamilyMember.new('Andrew','Male', 'Child', 'Son' , '27' )
fm << FamilyMember.new('Bill','Male', 'Parent', 'Father' , '63' )
puts FamilyMember.members.map(&:capitalize).join("\t") #members gives you the names of all methods
fm.each{|fam_member|puts fam_member.values.join("\t")} #another feature.
Output:
Name Sex Type Status Age
Andrew Male Child Son 27
Bill Male Parent Father 63
Only lined out...
I understand that, This may be not relevant now. But Just sharing this so someone can take advantage.
Try This Gem - terminal-table
terminal-table

get no of months, years between two dates in ruby

I'm trying to write web ui tests to choose date from jquery calender based on user input (watir-webdriver), how can find no of months years between two give dates, i searched few solution couldn't get what i want
date1 = Date::strptime("2013-09-19", "%Y-%m-%d")
date2 = Date::strptime("2013-09-25", "%Y-%m-%d")
date3 = Date::strptime("2013-10-01", "%Y-%m-%d")
date4 = Date::strptime("2014-01-20", "%Y-%m-%d")
date5 = Date::strptime("2014-12-01", "%Y-%m-%d")
desired output
diff between date1,date2 -- 0 yrs, 0 month(s)
diff between date1,date3 -- 0 yrs, 1 month(s)
diff between date1,date4 -- 0 yrs, 4 month(s)
diff between date1,date5 -- 1 yrs, 3 month(s)
i checked time_diff gem also
I'd calculate the difference in months (be aware that we ignore day differences here) and then calculate the number of years by dividing that number by 12:
##
# Calculates the difference in years and month between two dates
# Returns an array [year, month]
def date_diff(date1,date2)
month = (date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)
month.divmod(12)
end
date_diff date1, date4 #=> [0, 4]
date_diff date1, date2 #=> [0, 0]
date_diff date1, date3 #=> [0, 1]
date_diff date1, date5 #=> [1, 3]
Here is my attempt. Works in multiple units:
def date_diff(date1, date2, units=:months)
seconds_between = (date2.to_i - date1.to_i).abs
days_between = seconds_between / 60 / 60 / 24
case units
when :days
days_between.floor
when :months
(days_between / 30).floor
when :years
(days_between / 365).floor
else
seconds_between.floor
end
end
Usage:
date_diff(Time.now, 10.years.ago - 77.days, :years) #=> 10
date_diff(10.years.ago - 77.days, Time.now, :months) #=> 124
date_diff(10.years.ago - 77.days, Time.now, :days) #=> 3730
I took this from the TimeDifference gem but it works so nicely that I thought I'd share. If you're using Rails, make a class called TimeDifference with the following code:
class TimeDifference
private_class_method :new
def self.between(start_time:, end_time:)
new(start_time, end_time)
end
def in_seconds
#time_diff
end
def in_minutes
in_component(:minutes)
end
def in_hours
in_component(:hours)
end
def in_days
in_component(:days)
end
def in_weeks
in_component(:weeks)
end
def in_months
(#time_diff / (1.day * 30.42)).round(2)
end
def in_years
in_component(:years)
end
private
def initialize(start_time, end_time)
start_time = time_in_seconds(start_time)
end_time = time_in_seconds(end_time)
#time_diff = (end_time - start_time).abs
end
def time_in_seconds(time)
time.to_time.to_f
end
def in_component(component)
(#time_diff / 1.send(component)).round(2)
end
end
And then simply call:
start_time = DateTime.parse('2 June, 1999 9:00:00')
end_time = DateTime.parse('19 April, 2021 9:00:00')
time_difference = TimeDifference.between(
start_time: start_time,
end_time: end_time
)
time_difference.in_days
=> 7992.0
time_difference.in_months
=> 262.72
time_difference.in_years
=> 21.88
Note: if you're not using Rails you might have to require ActiveSupport.

Resources