September Morn Update
This commit is contained in:
parent
4e2d22a71d
commit
aac6731f2c
6856 changed files with 141342 additions and 21127 deletions
|
|
@ -0,0 +1,76 @@
|
|||
record Item, name : String, weight : Int32, value : Int32, count : Int32
|
||||
|
||||
record Selection, mask : Array(Int32), cur_index : Int32, total_value : Int32
|
||||
|
||||
class Knapsack
|
||||
@threshold_value = 0
|
||||
@threshold_choice : Selection?
|
||||
getter checked_nodes = 0
|
||||
|
||||
def knapsack_step(taken, items, remaining_weight)
|
||||
if taken.total_value > @threshold_value
|
||||
@threshold_value = taken.total_value
|
||||
@threshold_choice = taken
|
||||
end
|
||||
candidate_index = items.index(taken.cur_index) { |item| item.weight <= remaining_weight }
|
||||
return nil unless candidate_index
|
||||
@checked_nodes += 1
|
||||
candidate = items[candidate_index]
|
||||
# candidate is a best of available items, so if we fill remaining value with
|
||||
# and still don't reach the threshold, the branch is wrong
|
||||
return nil if taken.total_value + 1.0 * candidate.value / candidate.weight * remaining_weight < @threshold_value
|
||||
# now recursively check all variants (from taking maximum count to taking nothing)
|
||||
max_count = {candidate.count, remaining_weight / candidate.weight}.min
|
||||
(0..max_count).reverse_each do |n|
|
||||
mask = taken.mask.clone
|
||||
mask[candidate_index] = n
|
||||
knapsack_step Selection.new(mask, candidate_index + 1, taken.total_value + n*candidate.value), items, remaining_weight - n*candidate.weight
|
||||
end
|
||||
end
|
||||
|
||||
def select(items, max_weight)
|
||||
@checked_variants = 0
|
||||
# sort by descending relative value
|
||||
list = items.sort_by { |item| -1.0 * item.value / item.weight }
|
||||
nothing = Selection.new(Array(Int32).new(items.size, 0), 0, 0)
|
||||
@threshold_value = 0
|
||||
@threshold_choice = nothing
|
||||
knapsack_step(nothing, list, max_weight)
|
||||
selected = @threshold_choice.not_nil!
|
||||
result = Hash(Item, Int32).new(0)
|
||||
selected.mask.each_with_index { |v, i| result[list[i]] = v if v > 0 }
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
possible = [
|
||||
Item.new("map", 9, 150, 1),
|
||||
Item.new("compass", 13, 35, 1),
|
||||
Item.new("water", 153, 200, 2),
|
||||
Item.new("sandwich", 50, 60, 2),
|
||||
Item.new("glucose", 15, 60, 2),
|
||||
Item.new("tin", 68, 45, 3),
|
||||
Item.new("banana", 27, 60, 3),
|
||||
Item.new("apple", 39, 40, 3),
|
||||
Item.new("cheese", 23, 30, 1),
|
||||
Item.new("beer", 52, 10, 3),
|
||||
Item.new("suntan cream", 11, 70, 1),
|
||||
Item.new("camera", 32, 30, 1),
|
||||
Item.new("T-shirt", 24, 15, 2),
|
||||
Item.new("trousers", 48, 10, 2),
|
||||
Item.new("umbrella", 73, 40, 1),
|
||||
Item.new("waterproof trousers", 42, 70, 1),
|
||||
Item.new("waterproof overclothes", 43, 75, 1),
|
||||
Item.new("note-case", 22, 80, 1),
|
||||
Item.new("sunglasses", 7, 20, 1),
|
||||
Item.new("towel", 18, 12, 2),
|
||||
Item.new("socks", 4, 50, 1),
|
||||
Item.new("book", 30, 10, 2),
|
||||
]
|
||||
|
||||
solver = Knapsack.new
|
||||
used = solver.select(possible, 400)
|
||||
puts "optimal choice: #{used.map { |item, count| count == 1 ? item.name : "#{count}x #{item.name}" }.join(", ")}"
|
||||
puts "total weight #{used.sum { |item, count| item.weight*count }}"
|
||||
puts "total value #{used.sum { |item, count| item.value*count }}"
|
||||
puts "checked nodes: #{solver.checked_nodes}"
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Item struct to represent each item in the problem
|
||||
record Item, name : String, weight : Int32, value : Int32, count : Int32
|
||||
|
||||
def choose_item(items, weight, id, cache)
|
||||
return 0, ([] of Int32) if id < 0
|
||||
|
||||
k = {weight, id}
|
||||
cache = cache || {} of Tuple(Int32, Int32) => Tuple(Int32, Array(Int32))
|
||||
return cache[k] if cache[k]?
|
||||
value = items[id].value
|
||||
best_v = 0
|
||||
best_list = [] of Int32
|
||||
(items[id].count + 1).times do |i|
|
||||
wlim = weight - i * items[id].weight
|
||||
break if wlim < 0
|
||||
val, taken = choose_item(items, wlim, id - 1, cache)
|
||||
if val + i * value > best_v
|
||||
best_v = val + i * value
|
||||
best_list = taken + [i]
|
||||
end
|
||||
end
|
||||
cache[k] = {best_v, best_list}
|
||||
return {best_v, best_list}
|
||||
end
|
||||
|
||||
items = [
|
||||
Item.new("map", 9, 150, 1),
|
||||
Item.new("compass", 13, 35, 1),
|
||||
Item.new("water", 153, 200, 2),
|
||||
Item.new("sandwich", 50, 60, 2),
|
||||
Item.new("glucose", 15, 60, 2),
|
||||
Item.new("tin", 68, 45, 3),
|
||||
Item.new("banana", 27, 60, 3),
|
||||
Item.new("apple", 39, 40, 3),
|
||||
Item.new("cheese", 23, 30, 1),
|
||||
Item.new("beer", 52, 10, 3),
|
||||
Item.new("suntan cream", 11, 70, 1),
|
||||
Item.new("camera", 32, 30, 1),
|
||||
Item.new("T-shirt", 24, 15, 2),
|
||||
Item.new("trousers", 48, 10, 2),
|
||||
Item.new("umbrella", 73, 40, 1),
|
||||
Item.new("waterproof trousers", 42, 70, 1),
|
||||
Item.new("waterproof overclothes", 43, 75, 1),
|
||||
Item.new("note-case", 22, 80, 1),
|
||||
Item.new("sunglasses", 7, 20, 1),
|
||||
Item.new("towel", 18, 12, 2),
|
||||
Item.new("socks", 4, 50, 1),
|
||||
Item.new("book", 30, 10, 2),
|
||||
]
|
||||
|
||||
val, list = choose_item(items, 400, items.size - 1, nil)
|
||||
w = 0
|
||||
list.each_with_index do |cnt, i|
|
||||
if cnt > 0
|
||||
print "#{cnt} #{items[i].name}\n"
|
||||
w += items[i].weight * cnt
|
||||
end
|
||||
end
|
||||
|
||||
p "Total weight: #{w}, Value: #{val}"
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
my $raw = qq:to/TABLE/;
|
||||
map 9 150 1
|
||||
compass 13 35 1
|
||||
water 153 200 2
|
||||
sandwich 50 60 2
|
||||
glucose 15 60 2
|
||||
tin 68 45 3
|
||||
banana 27 60 3
|
||||
apple 39 40 3
|
||||
cheese 23 30 1
|
||||
beer 52 10 1
|
||||
suntancream 11 70 1
|
||||
camera 32 30 1
|
||||
T-shirt 24 15 2
|
||||
trousers 48 10 2
|
||||
umbrella 73 40 1
|
||||
w_trousers 42 70 1
|
||||
w_overcoat 43 75 1
|
||||
note-case 22 80 1
|
||||
sunglasses 7 20 1
|
||||
towel 18 12 2
|
||||
socks 4 50 1
|
||||
book 30 10 2
|
||||
TABLE
|
||||
|
||||
my @items;
|
||||
for split(["\n", /\s+/], $raw, :skip-empty) -> $n,$w,$v,$q {
|
||||
@items.push: %{ name => $n, weight => $w, value => $v, quant => $q}
|
||||
}
|
||||
|
||||
my $max_weight = 400;
|
||||
|
||||
sub pick ($weight, $pos) {
|
||||
state %cache;
|
||||
return 0, 0 if $pos < 0 or $weight <= 0;
|
||||
|
||||
my $key = $weight ~ $pos;
|
||||
%cache{$key} or do {
|
||||
my %item = @items[$pos];
|
||||
my ($bv, $bi, $bw, @bp) = (0, 0, 0);
|
||||
|
||||
for 0 .. %item{'quant'} -> $i {
|
||||
last if $i * %item{'weight'} > $weight;
|
||||
my ($v, $w, @p) = pick($weight - $i * %item{'weight'}, $pos - 1);
|
||||
next if ($v += $i * %item{'value'}) <= $bv;
|
||||
|
||||
($bv, $bi, $bw, @bp) = ($v, $i, $w, |@p);
|
||||
}
|
||||
%cache{$key} = $bv, $bw + $bi * %item{'weight'}, |@bp, $bi;
|
||||
}
|
||||
}
|
||||
|
||||
my ($v, $w, @p) = pick($max_weight, @items.end);
|
||||
{ say "{@p[$_]} of @items[$_]{'name'}" if @p[$_] } for 0 .. @p.end;
|
||||
say "Value: $v Weight: $w";
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
from itertools import groupby
|
||||
from collections import namedtuple
|
||||
from pprint import pprint as pp
|
||||
|
||||
def anyvalidcomb(items, val=0, wt=0):
|
||||
def anyvalidcomb(items, maxwt, val=0, wt=0):
|
||||
' All combinations below the maxwt '
|
||||
if not items:
|
||||
yield [], val, wt
|
||||
else:
|
||||
this, *items = items # car, cdr
|
||||
for n in range(this.number+1):
|
||||
v = val + n*this.value
|
||||
w = wt + n*this.weight
|
||||
for n in range(this.number + 1):
|
||||
w = wt + n * this.weight
|
||||
if w > maxwt:
|
||||
break
|
||||
for c in anyvalidcomb(items, v, w):
|
||||
yield [this]*n + c[COMB], c[VAL], c[WT]
|
||||
v = val + n * this.value
|
||||
this_comb = [this] * n
|
||||
for comb, value, weight in anyvalidcomb(items, maxwt, v, w):
|
||||
yield this_comb + comb, value, weight
|
||||
|
||||
maxwt = 400
|
||||
COMB, VAL, WT = range(3)
|
||||
|
|
@ -45,8 +45,7 @@ items = [ Item(*x) for x in
|
|||
("book", 30, 10, 2),
|
||||
) ]
|
||||
|
||||
bagged = max( anyvalidcomb(items), key=lambda c: (c[VAL], -c[WT])) # max val or min wt if values equal
|
||||
print("Bagged the following %i items\n " % len(bagged[COMB]) +
|
||||
'\n '.join('%i off: %s' % (len(list(grp)), item.name)
|
||||
for item,grp in groupby(sorted(bagged[COMB]))))
|
||||
bagged = max( anyvalidcomb(items, maxwt), key=lambda c: (c[VAL], -c[WT])) # max val or min wt if values equal
|
||||
print("Bagged the following %i items" % len(bagged[COMB]))
|
||||
print('\n\t'.join('%i off: %s' % (len(list(grp)), item.name) for item, grp in groupby(sorted(bagged[COMB]))))
|
||||
print("for a total value of %i and a total weight of %i" % bagged[1:])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue