Skip to content

Commit

Permalink
Added three participants' solutions (06-08).
Browse files Browse the repository at this point in the history
- 06 and 07 with Ruby 1.9.1
  The test_basic1, 2, 3, 5 were okay. But test_basic4 was no good.
  They both crashed my Windows XP. :(

- 08 with Ruby 1.8.6
  It worked all test_basic1-5.
  • Loading branch information
ashbb committed Jan 31, 2010
1 parent 5a2130d commit 2ef6b75
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 0 deletions.
87 changes: 87 additions & 0 deletions 06_BradOConnor/fair_distribution.rb
@@ -0,0 +1,87 @@
class FairDistribution
def initialize(jobs, number_of_presses)
@jobs, @number_of_presses = jobs, number_of_presses
@massive = generate_massive_array
end

def time_required
make_array_of_times(@massive).min
end

def distribution
#massive = generate_massive_array
var = variance(@massive)
@massive[var.find_index(var.min)] #returns the first item of vararray that has the lowest variance
end

protected

def generate_massive_array
#Takes the array of jobs in @jobs and produces an array containing every possible combination of jobs
#spread across the number of presses available
#Achieves this by calling the split_array method for each item in the array repeatedly until
#enough splitting has been done (@number_of_presses-1 times)
massive_array = @jobs.dup
(@number_of_presses-1).times do
if massive_array[0].class == Array #Will be true after the first iteration
working_array = []
massive_array.each do |subarray| #Iterate over each item(subarray) in the massive_array
new_array = split_array(subarray.pop) #pass the last element of current array to split_array (removes element from subarray)
new_array.each {|item| working_array << (subarray + item)} #Combine the remainder of subarray with the resulting new_array
end
massive_array = working_array
else
massive_array = split_array(massive_array) #Applied during the first iteration when massive_array contains fixnums rather than arrays
end
end
massive_array
end

def split_array(array)
#Takes an array and produces an array containing every possible combination of the original array split into 2
#Does this using a binary process and disregards any result containing an empty array
big_array = []
(2**array.length).times do |binary_index|
extracted, left = [],[]
array.length.times do |index|
if (2**index & binary_index != 0)
extracted << array[index]
else
left << array[index]
end
end
big_array << [extracted,left] unless extracted == [] || left == []
end
big_array
end

def make_array_of_times(bigarray)
#Takes an array of arrays and sums the total of each item of the subarray storing the highest sum in the returned array
#(man, this is getting confusing, sorry my documentation is so poor - at least I'm trying to document it)
newarr = []
bigarray.each do |x|
y = []
x.each do |item|
y << item.inject(:+)
end
newarr << y.max
end
newarr
end

def variance(big_array) #Calculate variance rather than sd to save CPU effort
#Takes array in form [[[1,2,3],[4,5,6]],[[1,2,3],[4,5,6]]]
#Sums individual values of each subarray (e.g. [[1,2,3],[4,5,6]] becomes [6,15])
#Then calculates the variance of these numbers in each subarray storing them in an array corresponding to the original big_array
var = [] #Empty array to store variances for each element
big_array.each_with_index do |item,index|
dupitem = item.dup #duplicate array so as not to alter array in caller
dupitem.map! {|t| t.inject(:+)} #Sum contents of each array and convert to fixnum
mean = dupitem.inject(:+).to_f / dupitem.length
dupitem.map! {|x| (x - mean) ** 2} #Calculate squares of deviations of each item from mean
var[index] = dupitem.inject(:+) / (dupitem.length - 1) #Calculates variance and stores in var array
end
var
end

end
6 changes: 6 additions & 0 deletions 06_BradOConnor/result.txt
@@ -0,0 +1,6 @@
Loaded suite test_solution_acceptance
Started
....
Finished in 0.828125 seconds.

4 tests, 7 assertions, 0 failures, 0 errors, 0 skips
148 changes: 148 additions & 0 deletions 07_MartinLinkhorst/fair_distribution.rb
@@ -0,0 +1,148 @@
#
# generates all possible distributions by splitting job list in distinct non empty subcollections with the help of Array's combination method
# then finds the best (see Array#chunk and FairDistribution#find_best_distribution for detailed explanation)
#

class Array
#
# returns the sum of all elements
#
# note: works best with homogenous numeric collections
#
def sum
inject(:+)
end

#
# calls array's combination method for each value in range and passes the block to it
#
# note: block must be given, unlike Array#combination
#
def combination_with_range(range, &block)
raise ArgumentError.new('please provide a block') unless block_given?
range.each do |chunk_size|
combination(chunk_size, &block)
end
end

#
# deletes each element of items only once in self
#
# unlike Array#- that deletes multiple items if eql? is true
# that is especially bad when dealing with duplicate integer values in a collection, like in this challenge
#
def delete_first(items)
items = [items] unless items.respond_to?(:each)
items.each do |item|
delete_at(index(item))
end
self
end

#
# returns all possible combinations of distinct non empty subcollections that when unioned equal self
# if more than two subcollections are requested, calls itself recursive on the second chunk
#
# huh?
#
# computes all subcollections with minimum length of one and maximum length of all elements minus the number of the remaining chunks,
# so that every chunk can have at least one element
#
# that collection forms all possible combinations for the first chunk (order is ignored)
#
# for each of those combinations computes the collection of the remaining elements, that is the difference of self and that combination
# (duplicate items are only removed once per item in the second collection)
#
# both collections are combined and form one possible result which gets collected and finally returned
#
# if more than two chunks are requested the second subcollection gets recursively chunked in (chunks - 1) chunks
# the two collections are then combined by prepending the first collection to each element of the second collection
#
def chunk(chunks)
raise ArgumentError.new('please choose at least two chunks') if chunks < 2

results = []

min_chunk_size, max_chunk_size = 1, size - (chunks - 1)

combination_with_range(min_chunk_size..max_chunk_size) do |taken_items|
remaining_items = dup.delete_first(taken_items)

if chunks == 2
results << [taken_items, remaining_items]
else
remaining_items.chunk(chunks - 1).each do |remaining_piece|
results << remaining_piece.unshift(taken_items)
end
end
end

results
end
end

class FairDistribution
def initialize(jobs, number_of_presses)
@jobs = jobs
@number_of_presses = number_of_presses
end

#
# required time is the time of the slowest press
#
def time_required
@time_required ||= distribution.map(&:sum).max
end

#
# returns the distribution considered as best
#
def distribution
@distribution ||= find_best_distribution
end

private

#
# computes and returns best distribution
#
# that is the distribution with the lowest required time
# if more than one distribution is found, returns the first one with the lowest standard deviation
#
# how:
# gets all possible distributions,
# for each one it computes the required time and standard deviation if needed,
# remembers the best, returns the best
#
# Array#chunk does the hard work
#
def find_best_distribution
required_time, best_distribution = nil, nil

all_distributions = @jobs.chunk(@number_of_presses)

all_distributions.each do |distribution|
time_required_by_distribution = distribution.map(&:sum).max
if required_time.nil? || time_required_by_distribution < required_time
best_distribution = distribution
required_time = time_required_by_distribution
else
if time_required_by_distribution == required_time
best_distribution = distribution if standard_deviation(distribution) < standard_deviation(best_distribution)
end
end
end

best_distribution
end

#
# returns the standard deviation of the given distribution
#
# (thanks for finally making me understand what standard deviation is all about :)
#
def standard_deviation(distribution)
u = distribution.map(&:sum).sum / distribution.size
Math.sqrt(distribution.map(&:sum).map{|x| (x-u)**2}.sum / distribution.size)
end
end
6 changes: 6 additions & 0 deletions 07_MartinLinkhorst/result.txt
@@ -0,0 +1,6 @@
Loaded suite test_solution_acceptance
Started
....
Finished in 0.437500 seconds.

4 tests, 7 assertions, 0 failures, 0 errors, 0 skips
96 changes: 96 additions & 0 deletions 08_IlyaErmolin/fair_distribution.rb
@@ -0,0 +1,96 @@
# vim: ts=3 sw=3

class FairDistribution
def initialize( jobs, groups)
@jobs = jobs.sort {|x,y| y <=> x }
@o_c = jobs.length
@g_c = groups
@result = nil
end
def each
# simplifications for 1 printer
# ( over code don't catch well
# case when first and the last
# printer is the same )
if @g_c == 1 then
yield @jobs
return
end
st = (0...@o_c).map{-1} # stack to work without recursion
sp = 0 # stack pointer
# Optimization 1:
# first order is always on first printer
# second order is on first or second printer and etc.
# We exclude full simetric results from output
stm = (1..@o_c).map{|i| [i, @g_c].min} # hi bound of variants for each job (for optimisation)
sta = (0..@o_c).map{ Array.new(@g_c) } # pre culc weigths of jobs of printers
sta[0] = (0...@g_c).map{0}
stf = (0...@o_c).map{1} # flags for stack
stv = (0...@o_c).map{ Array.new(@g_c) }
stvi = (0...@o_c).map{ -1 }
mean = @jobs.inject{|s,i| s + i} / @g_c
while true do
rp = st[sp]
if stf[sp] == 1 then
stf[sp] = 0
sta[sp + 1] = sta[sp].clone
sta[sp + 1][rp] += @jobs[sp]
# Optimization 2:
# If we skip overload - it's not
# optinum combinations anyway
stv[sp] = (0...stm[sp]).find_all{|i| sta[sp][i] < mean }.push(-1)
stvi[sp] = 0
end

r = st[sp] = stv[sp][ stvi[sp] ]
stvi[sp] += 1

if r == -1 then
stf[sp] = 1
sp -= 1
else
sta[sp + 1][r] += @jobs[sp]
sta[sp + 1][rp] -= @jobs[sp]
sp += 1
end
if sp == @o_c then
sp -= 1
dist = self.jobs_distribution(st)
yield [st, # distribution
sta[sp + 1].map{|v| (v - mean)**2}.inject{|s,i| s+ i}, # function to get minimum
dist.map{|v| v.inject(0){|s,i| s + i} }.max, # work time
dist # distribution with substitution of values
]
elsif sp == -1 then
return
end
end
end

def jobs_distribution(st)
r = Array.new(@g_c){[]}
for g, i in st.zip( (0...@o_c).to_a ) do
r[g].push( @jobs[i] )
end
return r
end

def culc_distribution
if @result != nil then
return @result
end
min = nil
for d in self do
min = d if min == nil || min[1] > d[1]
end
@result = min
end

def time_required
self.culc_distribution()[2]
end
def distribution
self.culc_distribution()[3]
end
end

6 changes: 6 additions & 0 deletions 08_IlyaErmolin/result.txt
@@ -0,0 +1,6 @@
Loaded suite test_solution_acceptance
Started
.....
Finished in 1.625 seconds.

5 tests, 8 assertions, 0 failures, 0 errors

0 comments on commit 2ef6b75

Please sign in to comment.