knapsack_solver 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|