Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added three participants' solutions (06-08).
- 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
Showing
6 changed files
with
349 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Loaded suite test_solution_acceptance | ||
Started | ||
..... | ||
Finished in 1.625 seconds. | ||
|
||
5 tests, 8 assertions, 0 failures, 0 errors |