September Morn Update

This commit is contained in:
Ingy döt Net 2019-09-12 10:33:56 -07:00
parent 4e2d22a71d
commit aac6731f2c
6856 changed files with 141342 additions and 21127 deletions

View file

@ -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}"

View file

@ -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}"

View file

@ -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";

View file

@ -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:])