# \[SOLUTION\] Pinewood Derby

**URL:** <https://www.ruby-forum.com/t/solution-pinewood-derby/225891>\
**Category:** Ruby\
**Created:** [November 30, 2005, 10:53pm UTC](https://www.ruby-forum.com/t/solution-pinewood-derby/225891 "2005-11-30T22:53:56Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![kcollins](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/k/45deac/32.png) [@kcollins](https://www.ruby-forum.com/u/kcollins)\
**Post date:** [November 30, 2005, 10:53pm UTC](https://www.ruby-forum.com/t/solution-pinewood-derby/225891/1 "2005-11-30T22:53:56Z")

</div>

#!/usr/bin/env ruby

# 

# Pinewood Derby scheduling program.

# 

class Scheduler

def initialize(car\_count, lane\_count, round\_target)  
# Set up instance variables for basic data  
@cars = (1…car\_count).inject([]) { |a,n| a \<\< n }  
@lanes = (1…lane\_count).inject([]) { |a,n| a \<\< n }  
heat\_count = round\_target \* car\_count  
@heats = (1…heat\_count).inject([]) { |a,n| a \<\< n }

```
# Prepare to track car in lane for heat (SELECTION RESULTS)
@heat_lane_cars = {}
@heats.each do |heat|
  @heat_lane_cars[heat] = {}
  @lanes.each do |lane|
    @heat_lane_cars[heat][lane] = nil
  end
end

# Prepare to track how many runs each car made in each lane 

```

(SELECTION CRITERIA)  
@lane\_car\_runs = {}  
@lanes.each do |lane|  
@lane\_car\_runs[lane] = {}  
@cars.each do |car|  
@lane\_car\_runs[lane][car] = 0  
end  
end

```
# Prepare to track how many runs each car made against each other 

```

car (SELECTION CRITERIA)  
@car\_opponent\_runs = {}  
@cars.each do |car|  
@car\_opponent\_runs[car] = {}  
@cars.each do |opponent|  
next if opponent == car  
@car\_opponent\_runs[car][opponent] = 0  
end  
end

```
# Prepare to track a car's last heat (SELECTION CRITERIA)
@car_last_heats = {}
@cars.each do |car|
  @car_last_heats[car] = 0
end

```

end

# Assign cars to lanes in heats, populating @heat\_lane\_cars;

# also update selection criteria

def assign  
@heats.each do |heat|  
@lanes.each do |lane|  
opponents = @heat\_lane\_cars[heat].values.delete\_if { |car| car  
== nil }  
@heat\_lane\_cars[heat][lane] = choose\_car(lane, opponents)  
increment\_lane\_car\_heats(lane, @heat\_lane\_cars[heat][lane])  
update\_car\_last\_heats(@heat\_lane\_cars[heat][lane], heat)  
end  
increment\_car\_opponent\_heats(@heat\_lane\_cars[heat].values)  
end  
end

# Assign a car to a lane based on selection criteria

def choose\_car(lane, opponents)  
# Narrow list of candidates to cars which have used this lane least  
often  
candidate\_array = @lane\_car\_runs[lane].sort { |a,b| a[1] \<=\> b[1] }  
low\_count = nil  
candidates = []  
candidate\_array.each do |candidate|  
low\_count ||= candidate[1]  
break if candidate[1] \> low\_count  
candidates \<\< candidate[0]  
end  
# Exclude cars already in this heat  
candidates -= opponents  
# Weight remaining candidates based on past heats with opponents in  
this heat  
candidate\_weights = {}  
candidates.each do |candidate|  
candidate\_weights[candidate] = 0  
opponents.each do |opponent|  
candidate\_weights[candidate] +=  
@car\_opponent\_runs[candidate][opponent]  
end  
end  
# Narrow list of candidates to cars with the lowest weight  
candidate\_array = candidate\_weights.sort { |a,b| a[1] \<=\> b[1] }  
low\_weight = nil  
candidates = []  
candidate\_array.each do |candidate|  
low\_weight ||= candidate[1]  
break if candidate[1] \> low\_weight  
candidates \<\< candidate[0]  
end  
# Break tie based on which car ran least recently  
least\_recent\_heat = @heats.length  
least\_recent\_candidate = nil  
candidates.each do |candidate|  
if @car\_last\_heats[candidate] \< least\_recent\_heat  
least\_recent\_heat = @car\_last\_heats[candidate]  
least\_recent\_candidate = candidate  
end  
end  
return least\_recent\_candidate  
end

# Update @lane\_car\_runs selection criteria based on a car assignment

def increment\_lane\_car\_heats(lane, car)  
@lane\_car\_runs[lane][car] += 1  
end

# Update @car\_last\_heats selection criteria based on a car assignment

def update\_car\_last\_heats(car, heat)  
@car\_last\_heats[car] = heat  
end

# Update @car\_opponent\_runs selection criteria based on lane

assignments for a heat  
def increment\_car\_opponent\_heats(opponents)  
opponents.each do |car|  
opponents.each do |opponent|  
next if car == opponent  
@car\_opponent\_runs[car][opponent] += 1  
end  
end  
end

# Print the results of scheduling

def report  
# Header lines  
line = "Heat "  
@lanes.sort.each do |lane|  
line \<\< "Lane #{lane} "  
end  
puts line  
line = "---- "  
@lanes.sort.each do |lane|  
line \<\< “------ "  
end  
puts line  
# Report lines  
@heats.each do |heat|  
line = sprintf(”%3d “, heat)  
@heat\_lane\_cars[heat].keys.sort.each do |lane|  
line \<\< sprintf(”%5d ", @heat\_lane\_cars[heat][lane])  
end  
puts line  
end  
end

end

####################

unless ARGV.length == 3  
puts “Usage: #{$0} \<car\_count\> \<lane\_count\> \<round\_count\>”  
exit  
end  
s = Scheduler.new(ARGV.shift.to\_i, ARGV.shift.to\_i, ARGV.shift.to\_i)  
s.assign  
s.report

---

<div class="post-metadata">

**Author:** ![kcollins](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/k/45deac/32.png) [@kcollins](https://www.ruby-forum.com/u/kcollins)\
**Post date:** [December 1, 2005, 4:58pm UTC](https://www.ruby-forum.com/t/solution-pinewood-derby/225891/2 "2005-12-01T16:58:02Z")

</div>

Kenneth C. wrote:

> #!/usr/bin/env ruby
> 
> # 
> 
> # Pinewood Derby scheduling program.
> 
> # 
> 
> class Scheduler

Sorry, Kenneth, I sent the summary to James before your solution hit, so  
I didn’t include it. It’s much faster than mine and generates excellent  
results! Good job! I’d like to test whether it creates “Perfect-N”  
charts where possible (just curious)
