in reply to Challenge: Twist On Bin Packing

The object is to find the smallest combination of items that "fills" each bin, while still using as many of the smaller items as possible. So for 1 1 1 2 2 3 4 6 7 7...

1 1 1 2 4 (all 1's always go in first bin, and 2 4 adds up to 7 and uses a 2)
2 3 (adds up to 5 and uses 2 and 3)
6
7
7

I've tested with various sequences of numbers, and this method seems to always work. You still have to brute-force a small number of combinations to find the smallest value, but the most you'll have to do for each bin is maybe a dozen. EDIT: This assumes the bin size is small. Complexity increases relative to the number of combinations possible for the current bin, which can be fairly large if you have a large bin and a lot of small items. I'm still trying to figure out a way to increase efficiency for that. Anyhow:

use strict; use warnings; my ($binsize, %n, @n, $total, $smallest, $bin, @bin, @bins); $binsize = 50; push @_, ((int rand 10) + 1) for 1..1000; my $c = 0; my $time = time(); $n{$_}++, $total += $_ for @_; while ($n{1} && $n{1} > $binsize) { push @bins, [split //, '1'x$binsize]; $n{1} -= $binsize; $total -= $binsize; } while ($total > $binsize) { @n = (); push @n, [$_, $n{$_}] for sort {$a <=> $b} keys %n; $smallest = $binsize + 1; find(\@n, 0, 0, 0, ''); $n{$_}--, $total -= $_ for @bin = split / /, $bin; push @bins, [@bin]; } $bin = ''; $bin .= "$_ "x$n{$_} for sort {$a <=> $b} keys %n; push @bins, [split / /, $bin]; for (@bins) { print "@$_\n"; } print "$c iterations in " . (time() - $time) . " seconds"; sub find { $c++; my ($n, $p, $stotal, $small, $set) = @_; my ($num, $count) = $n[$p] ? @{$n[$p]} : 0; if ($p > $#$n || $stotal + $num > $binsize) { if ($smallest > $stotal && (!$small || $stotal + $small > $bin +size)) { $bin = $set; $smallest = $stotal; } return; } my $remains = $binsize - $stotal; my $max = $remains < $num * $count ? int($remains / $num) : $count +; for (reverse 0..$max) { find($n, $p+1, $stotal+$num*$_, ($small || $_ == $max ? $small + : $num), $set."$num "x$_); } }
This required 1586177 function calls and 19 seconds for 1000 items between 1 and 10 and a bin size of 50. If you reduce the bin size to 10, then it only takes 15526 iterations and 5 seconds. I'm sure there's a way to significantly improve on this. For instance, if you have a bin size of 50, 20 2's, 10 3's, and a 5, then the minimum number of 2's you'll be using is all of them, since 40 + the largest number is smaller than 50. There's no need to go through the entire range of possibilities.