knapsack_solver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +2 -0
- data/bin/knapsack_solver +12 -0
- data/knapsack_solver.gemspec +33 -0
- data/lib/knapsack_solver.rb +2 -0
- data/lib/knapsack_solver/cli.rb +49 -0
- data/lib/knapsack_solver/cli_option_checker.rb +83 -0
- data/lib/knapsack_solver/cli_option_parser.rb +68 -0
- data/lib/knapsack_solver/dataset.rb +44 -0
- data/lib/knapsack_solver/graph_printer.rb +115 -0
- data/lib/knapsack_solver/instance.rb +48 -0
- data/lib/knapsack_solver/output_printer.rb +97 -0
- data/lib/knapsack_solver/solver.rb +118 -0
- data/lib/knapsack_solver/solving_methods/branch_and_bound.rb +111 -0
- data/lib/knapsack_solver/solving_methods/dynamic_programming.rb +116 -0
- data/lib/knapsack_solver/solving_methods/fptas.rb +47 -0
- data/lib/knapsack_solver/solving_methods/heuristic_price_weight.rb +56 -0
- data/lib/knapsack_solver/version.rb +5 -0
- data/test/knapsack_solver_matchers.rb +103 -0
- data/test/knapsack_solver_spec.rb +261 -0
- data/test/spec_helper.rb +5 -0
- metadata +141 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module KnapsackSolver
|
2
|
+
# This class represents an instance of a 0/1 knapsack problem.
|
3
|
+
class Instance
|
4
|
+
# Initializes instance of a 0/1 knapsack problem.
|
5
|
+
#
|
6
|
+
# @param capacity [Integer] weight capacity of the knapsack
|
7
|
+
# @param things [Array<Thing>] things which can be put into the knapsack
|
8
|
+
def initialize(capacity, things)
|
9
|
+
@weight_capacity = capacity
|
10
|
+
@things = things
|
11
|
+
end
|
12
|
+
|
13
|
+
# Creates new instance of a 0/1 knapsack problem.
|
14
|
+
#
|
15
|
+
# @param line [String] line that describes an instance of a 0/1 knapsack problem
|
16
|
+
# @return [Instance] instance of the 0/1 knapsack problem
|
17
|
+
def self.parse(line)
|
18
|
+
thing = Struct.new(:price, :weight, :index)
|
19
|
+
# Rozdelit riadok na slova a previest na cisla
|
20
|
+
items = split_line(line)
|
21
|
+
# Inicializacia premennych
|
22
|
+
things = items.drop(1).each_slice(2).with_index.each_with_object([]) do |(s, i), o|
|
23
|
+
o << thing.new(s[0], s[1], i)
|
24
|
+
end
|
25
|
+
Instance.new(items[0], things)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Splits line that describes an instance of a 0/1 knapsack problem to
|
29
|
+
# individual numbers.
|
30
|
+
#
|
31
|
+
# @param line [String] line that describes an instance of a 0/1 knapsack problem
|
32
|
+
# @return [Array<Integer>] integer numbers from the line
|
33
|
+
def self.split_line(line)
|
34
|
+
items = line.split.map! do |i|
|
35
|
+
n = Integer(i)
|
36
|
+
raise StandardError, 'dataset: instance desctiption contains negative number' if n < 0
|
37
|
+
n
|
38
|
+
end
|
39
|
+
raise StandardError, 'dataset: missing knapsack capacity' if items.empty?
|
40
|
+
raise StandardError, 'dataset: missing pairs (price, weight)' if items.size.even?
|
41
|
+
items
|
42
|
+
rescue ArgumentError
|
43
|
+
raise StandardError, 'dataset: instance desctiption does not contain only integers'
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :weight_capacity, :things
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module KnapsackSolver
|
2
|
+
# This class provides support for printing results and statistics of a
|
3
|
+
# dataset solving either to stdout or to a text file.
|
4
|
+
class OutputPrinter
|
5
|
+
# Initializes printer for output log (results, statistics).
|
6
|
+
#
|
7
|
+
# @param dataset_filenames [Array<String>] dataset filenames
|
8
|
+
# @param suffix [String] suffix of the created files
|
9
|
+
# @param results [Hash] results of solving or statistics to print
|
10
|
+
def initialize(dataset_filenames, suffix, results)
|
11
|
+
@dataset_basenames = file_basenames(dataset_filenames)
|
12
|
+
@suffix = suffix
|
13
|
+
@results = results
|
14
|
+
end
|
15
|
+
|
16
|
+
# Prints results or statistics to stdout or to files in output directory.
|
17
|
+
#
|
18
|
+
# @param out_dir [String] path to output directory
|
19
|
+
def print(out_dir = nil)
|
20
|
+
@results.each_value.with_index do |results, index|
|
21
|
+
results.each do |method, res|
|
22
|
+
print_solving_method_results(method, res, out_dir, @dataset_basenames[index])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
# Prints results of solving or statistics.
|
30
|
+
#
|
31
|
+
# @param method [Symbol] symbol for solving method
|
32
|
+
# @param res [Hash] results of the solving method
|
33
|
+
# @param out_dir [String] path to output directory
|
34
|
+
# @param basename [String] basename of dataset input file corresponding to the results
|
35
|
+
def print_solving_method_results(method, res, out_dir, basename)
|
36
|
+
of = output_filename(out_dir, basename, method.to_s)
|
37
|
+
os = output_stream(out_dir, of)
|
38
|
+
print_header(os, of, res)
|
39
|
+
res.each do |r|
|
40
|
+
os.puts r.values.each_with_object([]) { |v, a| a << v.to_s }.join(' ')
|
41
|
+
end
|
42
|
+
os.puts if out_dir.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Opens output file and turns on synchronized writes (this is neede for
|
46
|
+
# testing with Rspec).
|
47
|
+
#
|
48
|
+
# @param fname [String] path to the output file
|
49
|
+
# @return [#puts] output stream
|
50
|
+
def open_output_file(fname)
|
51
|
+
f = File.new(fname, 'w')
|
52
|
+
f.sync = true
|
53
|
+
f
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets output stream to stdout or to a file if path to it was provided.
|
57
|
+
#
|
58
|
+
# @param out_dir [String] directory for output files
|
59
|
+
# @param out_file [String] output file
|
60
|
+
def output_stream(out_dir, out_file)
|
61
|
+
return $stdout if out_dir.nil?
|
62
|
+
open_output_file(out_file)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Prints header of output log file.
|
66
|
+
#
|
67
|
+
# @param out_stream [#puts] stream to which output will be printed
|
68
|
+
# @param out_file [String] name of output file
|
69
|
+
# @param results [Hash] results of solving or statistics
|
70
|
+
def print_header(out_stream, out_file, results)
|
71
|
+
out_stream.puts "# #{out_file}"
|
72
|
+
out_stream.puts "# #{results.first.keys.join(' ')}"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Gets basenames of supplied file paths.
|
76
|
+
#
|
77
|
+
# @param paths [Array<String>] path to files
|
78
|
+
# @return [Array<String>] basenames of the paths
|
79
|
+
def file_basenames(paths)
|
80
|
+
paths.each_with_object([]) do |path, basenames|
|
81
|
+
basenames << File.basename(path, File.extname(path))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Construct filename for output log.
|
86
|
+
#
|
87
|
+
# @param output_dir [String] output directory
|
88
|
+
# @param basename [String] basename of the output file
|
89
|
+
# @param solving_method [String] name of solving method
|
90
|
+
# @return [String] filename for output log
|
91
|
+
def output_filename(output_dir, basename, solving_method)
|
92
|
+
filename = basename + '_' + solving_method + @suffix
|
93
|
+
return filename if output_dir.nil?
|
94
|
+
File.join(output_dir, filename)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
require 'knapsack_solver/dataset'
|
4
|
+
require 'knapsack_solver/solving_methods/heuristic_price_weight'
|
5
|
+
require 'knapsack_solver/solving_methods/branch_and_bound'
|
6
|
+
require 'knapsack_solver/solving_methods/dynamic_programming'
|
7
|
+
require 'knapsack_solver/solving_methods/fptas'
|
8
|
+
|
9
|
+
module KnapsackSolver
|
10
|
+
# This class solves datasets of 0/1 knapsack problem instances using a
|
11
|
+
# requested solving methods. It measures execution time of a solving and
|
12
|
+
# computes relative error if some exact solving method is requested.
|
13
|
+
class Solver
|
14
|
+
# Initializes solver for use of user selected solving methods.
|
15
|
+
#
|
16
|
+
# @param opts [Hash] parser command-line options
|
17
|
+
# @param datasets [Hash] parsed sets of 0/1 knapsack problem instances
|
18
|
+
def initialize(opts, datasets)
|
19
|
+
@opts = opts
|
20
|
+
@datasets = datasets
|
21
|
+
@solver_objects = {}
|
22
|
+
{ branch_and_bound: 'KnapsackSolver::BranchAndBound',
|
23
|
+
dynamic_programming: 'KnapsackSolver::DynamicProgramming',
|
24
|
+
heuristic: 'KnapsackSolver::HeuristicPriceToWeight',
|
25
|
+
fptas: 'KnapsackSolver::Fptas' }.each do |symbol, class_name|
|
26
|
+
@solver_objects[symbol] = Object.const_get(class_name) if opts[symbol]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Solve datasets using all selected method of solving, measure their
|
31
|
+
# execution time and compute relative errors if some exact method was
|
32
|
+
# requested.
|
33
|
+
#
|
34
|
+
# @return [Hash] results of dataset solving
|
35
|
+
def run
|
36
|
+
results = @datasets.each_with_object({}) do |dataset, res|
|
37
|
+
res[dataset.id] = @solver_objects.each_with_object({}) do |(solver, object), r|
|
38
|
+
r[solver] = dataset.instances.each_with_object([]) do |inst, a|
|
39
|
+
o = object.new(inst) unless solver == :fptas
|
40
|
+
o = object.new(inst, @opts[:fptas_epsilon]) if solver == :fptas
|
41
|
+
a << execution_time { o.run }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
add_relative_error(results)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creates statistics (average price, execution times, relative error) from
|
49
|
+
# results of solving.
|
50
|
+
#
|
51
|
+
# @param results [Hash] solving results of datasets and solving methods
|
52
|
+
# @return [Hash] statistics for datasets and solving methods
|
53
|
+
def stats(results)
|
54
|
+
results.each_with_object({}) do |(dataset_id, method_results), q|
|
55
|
+
q[dataset_id] = method_results.each_with_object({}) do |(met, res), p|
|
56
|
+
p[met] = averages(res)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
# Computes average values from the results.
|
64
|
+
#
|
65
|
+
# @param res [Hash] results for one dataset and one method
|
66
|
+
# @return [Array<Hash>] array of computed average values
|
67
|
+
def averages(res)
|
68
|
+
[res.first.keys.reject { |k| k == :config }.each_with_object({}) do |v, o|
|
69
|
+
values = res.map { |i| i[v] }
|
70
|
+
o[('avg_' + v.to_s).to_sym] = values.reduce(:+).to_f / values.size
|
71
|
+
end]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Adds relative error to results of solving if some exact method of
|
75
|
+
# solving was requested.
|
76
|
+
#
|
77
|
+
# @param results [Hash] results of solving using requested methods
|
78
|
+
# @return [Hash] the results with relative error added
|
79
|
+
def add_relative_error(results)
|
80
|
+
return results unless @opts[:branch_and_bound] || @opts[:dynamic_programming]
|
81
|
+
exact_method = @opts[:branch_and_bound] ? :branch_and_bound : :dynamic_programming
|
82
|
+
results.each_value do |method_results|
|
83
|
+
method_results.each_value do |res|
|
84
|
+
res.each_with_index do |r, i|
|
85
|
+
r[:relative_error] = relative_error(method_results[exact_method][i][:price], r[:price])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
results
|
90
|
+
end
|
91
|
+
|
92
|
+
# Measure execution time of provided block so that measured time is non-zero.
|
93
|
+
#
|
94
|
+
# @yieldparam block for which execution time will be measured
|
95
|
+
# @return [Hash] solving results with cpu time and wall clock time of execution
|
96
|
+
def execution_time
|
97
|
+
exec_count = 1
|
98
|
+
result = nil
|
99
|
+
cpu_time = wall_clock_time = 0.0
|
100
|
+
while cpu_time.zero? || wall_clock_time.zero?
|
101
|
+
b = Benchmark.measure { exec_count.times { result = yield } }
|
102
|
+
cpu_time += b.total
|
103
|
+
wall_clock_time += b.real
|
104
|
+
exec_count *= 2
|
105
|
+
end
|
106
|
+
result.merge(cpu_time: cpu_time, wall_clock_time: wall_clock_time)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Computes relative error of approximate solution.
|
110
|
+
#
|
111
|
+
# @param opt [Numeric] Optimal price.
|
112
|
+
# @param apx [Numeric] Approximate price.
|
113
|
+
# @return [Float] Relative error.
|
114
|
+
def relative_error(opt, apx)
|
115
|
+
(opt.to_f - apx.to_f) / opt.to_f
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module KnapsackSolver
|
2
|
+
# This class implements methods for solving 0/1 knapsack problem using
|
3
|
+
# Branch and Bound method.
|
4
|
+
class BranchAndBound
|
5
|
+
# Initializes instance of Brand and Bound 0/1 knapsack problem solver.
|
6
|
+
#
|
7
|
+
# @param instance [Instance] 0/1 knapsack problem instance
|
8
|
+
def initialize(instance)
|
9
|
+
@instance = instance
|
10
|
+
@config = Array.new(instance.things.size)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Solve the instance of 0/1 knapsack problem.
|
14
|
+
#
|
15
|
+
# @return [Hash] resulting price and thing configuration (0 = thing is not in the knapsack, 1 = thing is there)
|
16
|
+
def run
|
17
|
+
solve(0)
|
18
|
+
{ price: @best_price, config: @best_config }
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
# Solve the problem starting at specified thing.
|
24
|
+
#
|
25
|
+
# @param index [Integer] index of thing which will be decided (put in or out from the knapsack) the next
|
26
|
+
def solve(index)
|
27
|
+
@config[index] = 0
|
28
|
+
solve(index + 1) unless stop(index)
|
29
|
+
@config[index] = 1
|
30
|
+
solve(index + 1) unless stop(index)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Determine if solving of current branch should continue.
|
34
|
+
#
|
35
|
+
# @param index [Integer] index of the last decided thing so far
|
36
|
+
# @return [true, false] weather to continue with solving current branch.
|
37
|
+
def stop(index)
|
38
|
+
# Update of the best price so far
|
39
|
+
weight = config_weight(0, index)
|
40
|
+
price = config_price(0, index)
|
41
|
+
update_best_price(price, weight, index)
|
42
|
+
# No more things to put into the knapsack
|
43
|
+
return true if index >= (@instance.things.size - 1)
|
44
|
+
# The knapsack is overloaded, do not continue this branch
|
45
|
+
return true if weight > @instance.weight_capacity
|
46
|
+
if instance_variable_defined?('@best_price') &&
|
47
|
+
((price + get_price_of_remaining_things(index + 1)) <= @best_price)
|
48
|
+
# Adding all the ramining things does not produce better price
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Update the best price achieved so far.
|
55
|
+
#
|
56
|
+
# @param price [Integer] price of the current configuration
|
57
|
+
# @param weight [Integer] weight of the current configuration
|
58
|
+
# @param index [Integer] index of the next thing presence of which will be decided
|
59
|
+
def update_best_price(price, weight, index)
|
60
|
+
if !instance_variable_defined?('@best_price') ||
|
61
|
+
((weight <= @instance.weight_capacity) && (price > @best_price))
|
62
|
+
@best_price = price
|
63
|
+
valid_len = index + 1
|
64
|
+
remaining = @config.size - index - 1
|
65
|
+
# All undecided things will not be put into the knapsack
|
66
|
+
@best_config = @config.slice(0, valid_len).fill(0, valid_len, remaining)
|
67
|
+
@best_config_index = index
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Gets weight of set of things. The set is subset of the things ordered by
|
72
|
+
# their index.
|
73
|
+
#
|
74
|
+
# @param start_index [Integer] index of the first thing included in the set
|
75
|
+
# @param end_index [Integer] index of the last thing included in the set
|
76
|
+
# @return [Integer] weight of the things
|
77
|
+
def config_weight(start_index, end_index)
|
78
|
+
weight = 0
|
79
|
+
@config[start_index..end_index].each_with_index do |presence, index|
|
80
|
+
weight += presence * @instance.things[index].weight
|
81
|
+
end
|
82
|
+
weight
|
83
|
+
end
|
84
|
+
|
85
|
+
# Gets price of set of things. The set is subset of the things ordered by
|
86
|
+
# their index.
|
87
|
+
#
|
88
|
+
# @param start_index [Integer] index of the first thing included in the set
|
89
|
+
# @param end_index [Integer] index of the last thing included in the set
|
90
|
+
# @return [Integer] price of the things
|
91
|
+
def config_price(start_index, end_index)
|
92
|
+
price = 0
|
93
|
+
@config[start_index..end_index].each_with_index do |presence, index|
|
94
|
+
price += presence * @instance.things[index].price
|
95
|
+
end
|
96
|
+
price
|
97
|
+
end
|
98
|
+
|
99
|
+
# Gets sum of prices of things for which their presence in the knapsack
|
100
|
+
# was not decided yet.
|
101
|
+
#
|
102
|
+
# @param from_index [Integer] index of the first undecided thing
|
103
|
+
# @return [Integer] price of the remaining things
|
104
|
+
def get_price_of_remaining_things(from_index)
|
105
|
+
price = 0
|
106
|
+
to_index = @instance.things.size - 1
|
107
|
+
@instance.things[from_index..to_index].each { |t| price += t.price }
|
108
|
+
price
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module KnapsackSolver
|
2
|
+
# This class implements methods for solving 0/1 knapsack problem using
|
3
|
+
# dynamic programming with decomposition by price.
|
4
|
+
class DynamicProgramming
|
5
|
+
# Initializes instance of 0/1 knapsack problem solver based on dynamic
|
6
|
+
# programming with decomposition by price.
|
7
|
+
#
|
8
|
+
# @param instance [Instance] 0/1 knapsack problem instance
|
9
|
+
def initialize(instance)
|
10
|
+
@instance = instance
|
11
|
+
@config = Array.new(instance.things.size)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Solve the instance of 0/1 knapsack problem.
|
15
|
+
#
|
16
|
+
# @return [Hash] resulting price and thing configuration (0 = thing is not in the knapsack, 1 = thing is there)
|
17
|
+
def run
|
18
|
+
solve
|
19
|
+
{ price: @best_price, config: @best_config }
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
# Solve the instance of 0/1 knapsack problem using dynamic programming.
|
25
|
+
def solve
|
26
|
+
# Dynamic programming table
|
27
|
+
c = all_things_price + 1 # height of array from 0 to max. price
|
28
|
+
n = @instance.things.size + 1 # width of array, from 0th thing to Nth
|
29
|
+
# Value used as infinity in the dynamic programming table
|
30
|
+
@infinity = (all_things_weight + 1).freeze
|
31
|
+
@weight_array = Array.new(n) { Array.new(c, @infinity) }
|
32
|
+
@weight_array[0][0] = 0
|
33
|
+
fill_table
|
34
|
+
find_best_price
|
35
|
+
configuration_vector
|
36
|
+
end
|
37
|
+
|
38
|
+
# Fill the dynamic programming table.
|
39
|
+
def fill_table
|
40
|
+
(1..@instance.things.size).each do |ni|
|
41
|
+
(0..all_things_price).each do |ci|
|
42
|
+
@weight_array[ni][ci] = minimum_weight(ni, ci)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Find the value of cell in dynamic programming table.
|
48
|
+
#
|
49
|
+
# @param ni [Integer] X axis coordinate
|
50
|
+
# @param ci [Integer] Y axis coordinate
|
51
|
+
# @return [Integer]
|
52
|
+
def minimum_weight(ni, ci)
|
53
|
+
b = weight_of(ni - 1, ci - @instance.things[ni - 1].price)
|
54
|
+
b += @instance.things[ni - 1].weight
|
55
|
+
[weight_of(ni - 1, ci), b].min
|
56
|
+
end
|
57
|
+
|
58
|
+
# Find the best price from the filled dynamic programming table.
|
59
|
+
def find_best_price
|
60
|
+
@best_price = @weight_array.last[0]
|
61
|
+
(1..all_things_price).each do |i|
|
62
|
+
@best_price = i if @weight_array.last[i] <= @instance.weight_capacity
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Reconstructs configuration vector from dynamic programming table.
|
67
|
+
def configuration_vector
|
68
|
+
@best_config = []
|
69
|
+
ci = @best_price
|
70
|
+
@instance.things.size.downto(1) do |i|
|
71
|
+
ci = determine_config_variable(i, ci)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Determine value of one scalar for the configuration vector.
|
76
|
+
#
|
77
|
+
# return [Integer] next Y index to the dynamic programming table
|
78
|
+
def determine_config_variable(i, ci)
|
79
|
+
if @weight_array[i][ci] == @weight_array[i - 1][ci]
|
80
|
+
@best_config[i - 1] = 0
|
81
|
+
else
|
82
|
+
@best_config[i - 1] = 1
|
83
|
+
ci -= @instance.things[i - 1].price
|
84
|
+
end
|
85
|
+
ci
|
86
|
+
end
|
87
|
+
|
88
|
+
# Gets weight from dynamic programming table.
|
89
|
+
#
|
90
|
+
# @param i [Integer] Y index of dynamic programming table
|
91
|
+
# @param c [Integer] X index of dynamic programming table
|
92
|
+
# @return [Integer] the value from the array
|
93
|
+
def weight_of(i, c)
|
94
|
+
return @infinity if (i < 0) || (c < 0)
|
95
|
+
@weight_array[i][c]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Computes total price of all things of the instance.
|
99
|
+
#
|
100
|
+
# @return [Integer] total price
|
101
|
+
def all_things_price
|
102
|
+
price = 0
|
103
|
+
@instance.things.each { |t| price += t.price }
|
104
|
+
price
|
105
|
+
end
|
106
|
+
|
107
|
+
# Computes total weight of all things of the instance.
|
108
|
+
#
|
109
|
+
# @return [Integer] total weight
|
110
|
+
def all_things_weight
|
111
|
+
weight = 0
|
112
|
+
@instance.things.each { |t| weight += t.weight }
|
113
|
+
weight
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|