Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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...
commit 2ef6b75677dc9190e13665c9195164f42951567d 1 parent 5a2130d
@ashbb ashbb authored
View
87 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
View
6 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
View
148 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
View
6 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
View
96 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
+
View
6 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
Please sign in to comment.
Something went wrong with that request. Please try again.