TRUNCATE TABLE dbo.packages INSERT INTO dbo.packages (size) SELECT (ABS(CHECKSUM(NEWID()))%20) + (ABS(CHECKSUM(NEWID()))%30) +20 FROM dbo.numbers WHERE n BETWEEN 1 AND 100000
solution number_of_bins minimum percentage -------- -------------- --------------------- ------------------------------------- 1 56766 43985 129.057633284074116175969 2 45016 43985 102.343980902580425144935 3 45016 43985 102.343980902580425144935 4 45472 43985 103.380697965215414345799 5 45482 43985 103.403432988518813231783There are two things very obvious in these results:
CREATE PROCEDURE dbo.update_packages_and_bins (@solution tinyint ,@size smallint) AS Begin UPDATE dbo.packages SET bin_no = ( SELECT bno FROM #bins JOIN #packages on pid=bid WHERE pno=package_no ) WHERE EXISTS ( SELECT * FROM #packages WHERE pno = package_no ) UPDATE dbo.bins SET space_left = space_left - @size WHERE solution = @solution AND EXISTS ( SELECT * FROM #bins WHERE bno = bin_no ) End
CREATE PROCEDURE dbo.assign_packages (@solution tinyint ,@debug bit ,@count int ,@number_of_values tinyint ,@val1 smallint ,@val2 smallint = null ,@val3 smallint = null) AS Begin If @count > 0 Begin Declare @size smallint Set @size=@val1 If @number_of_values > 1 Set @size=@size+@val2 If @number_of_values > 2 Set @size=@size+@val3 TRUNCATE TABLE #bins INSERT INTO #bins (bno) SELECT TOP (@count) bin_no FROM dbo.bins WHERE solution = @solution AND space_left = @size TRUNCATE TABLE #packages INSERT INTO #packages (pno) SELECT TOP (@count) package_no FROM dbo.packages WHERE bin_no IS NULL AND size = @val1 EXEC dbo.update_packages_and_bins @solution,@val1 If @number_of_values = 1 AND @debug=1 select @count,'('+cast(@size as varchar(12))+')',@val1 If @number_of_values >1 Begin TRUNCATE TABLE #packages INSERT INTO #packages (pno) SELECT TOP (@count) package_no FROM dbo.packages WHERE bin_no IS NULL AND size = @val2 EXEC dbo.update_packages_and_bins @solution,@val2 End If @number_of_values = 2 AND @debug=1 select @count,'('+cast(@size as varchar(12))+')',@val1,@val2 If @number_of_values >2 Begin TRUNCATE TABLE #packages INSERT INTO #packages (pno) SELECT TOP (@count) package_no FROM dbo.packages WHERE bin_no IS NULL AND size = @val3 EXEC dbo.update_packages_and_bins @solution,@val3 End If @number_of_values = 3 AND @debug=1 select @count,'('+cast(@size as varchar(12))+')',@val1,@val2,@val3 End End
CREATE PROCEDURE dbo.current_state_of_affairs (@solution tinyint ,@debug bit ) AS Begin If @debug=1 Begin SELECT space_left,count(*) FROM dbo.bins WHERE solution=@solution GROUP BY space_left ORDER BY space_left SELECT size,count(*) FROM dbo.packages WHERE bin_no IS NULL GROUP BY size ORDER BY size DESC End End
CREATE PROCEDURE dbo.update_ranking (@bin_size smallint) AS Begin -- Calculate the packages that would result in the most waste -- when not combined with packages of different sizes. -- (@bin_size % size) is the waste for that size. -- It is weighted because the COUNT(*) is taken into account TRUNCATE TABLE #ranking INSERT INTO #ranking SELECT size, (COUNT(*)/(@bin_size/size))*(@bin_size%size) FROM dbo.packages WHERE bin_no IS NULL GROUP BY size HAVING COUNT(*) >= (@bin_size/size) End
CREATE PROCEDURE dbo.count_packages (@number_of_values tinyint ,@val1 smallint ,@val2 smallint = null ,@val3 smallint = null )AS Begin Declare @count int If @number_of_values = 1 Begin Set @count = ( SELECT COUNT(*) FROM dbo.packages WHERE size = @val1 AND bin_no IS NULL ) End If @number_of_values = 2 Begin If @val1=@val2 Set @count=( SELECT COUNT(*)/2 FROM dbo.packages WHERE size = @val1 AND bin_no IS NULL ) Else Set @count=( SELECT MIN(cnt) FROM ( SELECT COUNT(*) AS cnt FROM dbo.packages WHERE size = @val1 AND bin_no IS NULL UNION ALL SELECT COUNT(*) FROM dbo.packages WHERE size = @val2 AND bin_no IS NULL ) AS T ) End If @number_of_values = 3 Begin If @val1 = @val2 AND @val1 = @val3 Set @count=( SELECT COUNT(*)/3 FROM dbo.packages WHERE size = @val1 AND bin_no IS NULL ) Else If @val1 <> @val2 AND @val1 <> @val3 AND @val2 <> @val3 Set @count=( SELECT MIN(cnt) FROM ( SELECT COUNT(*) AS cnt FROM dbo.packages WHERE size = @val1 AND bin_no IS NULL UNION ALL SELECT COUNT(*) FROM dbo.packages WHERE size = @val2 AND bin_no IS NULL UNION ALL SELECT COUNT(*) FROM dbo.packages WHERE size = @val3 AND bin_no IS NULL ) AS T ) Else SELECT 'Sorry, dbo.count_packages doesn''t support this combination (yet)' End Return @count End
-- Solution 6 UPDATE dbo.packages SET bin_no = NULL DELETE FROM dbo.bins WHERE solution=6 Declare @debug bit Set @debug=1 Declare @loop bit Declare @iteration int Declare @space_count int Declare @count int Declare @solution tinyint Declare @bin_size smallint Declare @size smallint Declare @min_bins int Declare @extra_bins int Declare @number_of_values tinyint Declare @val1 smallint Declare @val2 smallint Declare @val3 smallint Set @solution = 6 Set @bin_size = 100 -- Calculate minimal number of bins -- Either determines by total size Set @min_bins = ( SELECT CEILING(SUM(size)*1.0/@bin_size) FROM dbo.packages ) -- Or the number of packages that cover more than half a bin Set @extra_bins = ( SELECT COUNT(*) FROM dbo.packages WHERE size > (@bin_size/2) ) + ( SELECT COUNT(*)/2 FROM dbo.packages WHERE size = (@bin_size/2) ) If @extra_bins > @min_bins Begin If @debug=1 select @min_bins as min_bins,@extra_bins as correction Set @min_bins=@extra_bins End INSERT INTO dbo.bins (solution, bin_no, space_left) SELECT @solution, n, @bin_size FROM dbo.numbers WHERE n BETWEEN 1 AND @min_bins CREATE TABLE #bins (bid int IDENTITY PRIMARY KEY,bno int NOT NULL) CREATE TABLE #packages (pid int IDENTITY PRIMARY KEY,pno int NOT NULL) CREATE TABLE #ranking (size smallint, weight int, constraint pk primary key (weight,size)) SET NOCOUNT ON -- Check for packages > @bin_size/2 While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL AND size > (@bin_size/2) ) Begin Set @size=( SELECT MAX(size) FROM dbo.packages WHERE bin_no IS NULL ) EXEC @count = dbo.count_packages 1,@size TRUNCATE TABLE #bins INSERT INTO #bins (bno) SELECT TOP (@count) bin_no FROM dbo.bins WHERE solution = @solution AND space_left >= @size TRUNCATE TABLE #packages INSERT INTO #packages (pno) SELECT TOP (@count) package_no FROM dbo.packages WHERE bin_no IS NULL AND size = @size EXEC dbo.update_packages_and_bins @solution,@size If @debug=1 select @count,'('+cast(@size as varchar(12))+')',@size End -- Check for packages = @bin_size/2 If EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL AND size = (@bin_size/2) ) Begin Set @size=@bin_size/2 EXEC @count = dbo.count_packages 2,@size,@size EXEC dbo.assign_packages @solution,@debug,@count,2,@size,@size End -- global loop Set @iteration=0 While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) Begin Set @iteration=@iteration+1 If @debug=1 select @iteration as Iteration -- fill up, combi=1 Set @size = 0 While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) AND @size IS NOT NULL Begin Set @size = ( SELECT MIN(space_left) FROM dbo.bins WHERE solution=@solution AND space_left>@size AND space_left<@bin_size/2 ) If @size IS NOT NULL Begin Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = @size ) EXEC @count = dbo.count_packages 1,@size If @count > @space_count Set @count = @space_count EXEC dbo.assign_packages @solution,@debug,@count,1,@size End End EXEC dbo.current_state_of_affairs @solution,@debug -- combi=2, #ranking=2 EXEC dbo.update_ranking @bin_size While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) AND EXISTS ( SELECT * FROM #ranking ) Begin Set @size = ( SELECT TOP 1 size FROM #ranking ORDER BY weight DESC, size DESC ) Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = 2*@size ) If @space_count>0 Begin EXEC @count = dbo.count_packages 2,@size,@size If @count > @space_count Set @count = @space_count If @count > 0 Begin EXEC dbo.assign_packages @solution,@debug,@count,2,@size,@size EXEC dbo.update_ranking @bin_size End End Else Begin DELETE FROM #ranking WHERE size=@size Set @val2=NULL Set @val1=@size Set @val2=( SELECT TOP 1 size FROM dbo.bins JOIN #ranking ON size+@size = space_left WHERE solution = @solution ORDER BY weight DESC ) If @val2 IS NOT NULL Begin EXEC @count=dbo.count_packages 2,@val1,@val2 Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = @val1+@val2 ) If @space_count < @count Set @count=@space_count If @count > 0 Begin EXEC dbo.assign_packages @solution,@debug,@count,2,@val1,@val2 EXEC dbo.update_ranking @bin_size End End End End -- combi=2, #ranking=0 EXEC dbo.update_ranking @bin_size Set @size=@bin_size While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) AND @size>0 Begin Set @val1 = ( SELECT MAX(size) FROM dbo.packages P1 WHERE bin_no IS NULL AND size < @size AND NOT EXISTS ( SELECT * FROM #ranking R1 WHERE R1.size=P1.size ) ) Set @size=@val1 Set @val2=NULL Set @val2=( SELECT TOP 1 size FROM dbo.bins JOIN dbo.packages P1 ON size+@size = space_left WHERE solution = @solution AND P1.bin_no IS NULL ORDER BY size DESC ) If @val2 IS NOT NULL Begin EXEC @count=dbo.count_packages 2,@val1,@val2 Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = @val1+@val2 ) If @space_count < @count Set @count=@space_count If @count > 0 EXEC dbo.assign_packages @solution,@debug,@count,2,@val1,@val2 End End -- combi=3, #ranking=3 EXEC dbo.update_ranking @bin_size While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) AND EXISTS ( SELECT * FROM #ranking ) Begin Set @size = ( SELECT TOP 1 size FROM #ranking ORDER BY weight DESC, size DESC ) Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = 3*@size ) If @space_count>0 Begin EXEC @count = dbo.count_packages 3,@size,@size,@size If @count > @space_count Set @count = @space_count If @count > 0 Begin EXEC dbo.assign_packages @solution,@debug,@count,3,@size,@size,@size EXEC dbo.update_ranking @bin_size End End Else Begin DELETE FROM #ranking WHERE size=@size Set @val3=( SELECT MIN(size) FROM #ranking ) Set @val2=NULL Set @val1=@size Set @val2=( SELECT TOP 1 size FROM dbo.bins JOIN #ranking ON size+@val1 + @val3 <= space_left WHERE solution = @solution ORDER BY weight DESC ) If @val2 IS NOT NULL Set @val3=( SELECT TOP 1 size FROM dbo.bins JOIN #ranking ON size+@val1 + @val2 = space_left WHERE solution = @solution AND size <> @val2 ORDER BY weight DESC ) If @val2 IS NOT NULL AND @val3 IS NOT NULL Begin EXEC @count=dbo.count_packages 3,@val1,@val2,@val3 Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = @val1+@val2+@val3 ) If @space_count < @count Set @count=@space_count If @count > 0 Begin EXEC dbo.assign_packages @solution,@debug,@count,3,@val1,@val2,@val3 EXEC dbo.update_ranking @bin_size End End End End -- combi=3, #ranking=2 EXEC dbo.update_ranking @bin_size While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) AND EXISTS ( SELECT * FROM #ranking ) Begin Set @size = ( SELECT TOP 1 size FROM #ranking ORDER BY weight DESC, size DESC ) DELETE FROM #ranking WHERE size=@size Set @val3=( SELECT MIN(size) FROM dbo.packages WHERE bin_no IS NULL ) Set @val2=NULL Set @val1=@size Set @val2=( SELECT TOP 1 size FROM dbo.bins JOIN #ranking ON size+@val1 + @val3 <= space_left WHERE solution = @solution ORDER BY weight DESC ) If @val2 IS NOT NULL Set @val3=( SELECT TOP 1 size FROM dbo.bins JOIN dbo.packages P1 ON size+@val1+@val2 = space_left WHERE solution = @solution AND P1.bin_no IS NULL AND size NOT IN (@val1,@val2) ORDER BY size DESC ) If @val2 IS NOT NULL AND @val3 IS NOT NULL Begin EXEC @count=dbo.count_packages 3,@val1,@val2,@val3 Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = @val1+@val2+@val3 ) If @space_count < @count Set @count=@space_count If @count > 0 Begin EXEC dbo.assign_packages @solution,@debug,@count,3,@val1,@val2,@val3 EXEC dbo.update_ranking @bin_size End End End -- combi=3, #ranking=1 EXEC dbo.update_ranking @bin_size While EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) AND EXISTS ( SELECT * FROM #ranking ) Begin Set @size=( SELECT MAX(space_left) FROM dbo.bins WHERE solution = @solution ) Set @val3=( SELECT MIN(size) FROM dbo.packages WHERE bin_no IS NULL ) DELETE FROM #ranking WHERE size > (@size - @val3 - @val3) Set @val1=NULL Set @val1 = ( SELECT TOP 1 size FROM #ranking ORDER BY weight DESC, size DESC ) DELETE FROM #ranking WHERE size=@val1 Set @val2=NULL Set @val2=( SELECT MAX(size) FROM dbo.packages WHERE bin_no IS NULL AND size <= @size-@val1-@val3 AND size <> @val1 ) If @val2 IS NOT NULL Set @val3=( SELECT TOP 1 size FROM dbo.bins JOIN dbo.packages P1 ON size+@val1+@val2 = space_left WHERE solution = @solution AND P1.bin_no IS NULL AND size NOT IN (@val1,@val2) ORDER BY size DESC ) If @val2 IS NOT NULL AND @val3 IS NOT NULL Begin EXEC @count=dbo.count_packages 3,@val1,@val2,@val3 Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left = @val1+@val2+@val3 ) If @space_count < @count Set @count=@space_count If @count > 0 Begin EXEC dbo.assign_packages @solution,@debug,@count,3,@val1,@val2,@val3 EXEC dbo.update_ranking @bin_size End End End -- fillers Set @loop=1 While @loop=1 Begin If @debug=1 print 'fillers' Set @loop=0 Set @val3=( SELECT MIN(size) FROM dbo.packages WHERE bin_no IS NULL ) If @val3 IS NOT NULL Begin -- No combination of values can fit the space Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left < @val3+@val3 AND space_left >= @val3 ) If @space_count > 0 Begin Set @size=( SELECT MIN(space_left) FROM dbo.bins WHERE solution = @solution AND space_left < @val3+@val3 AND space_left >= @val3 ) If @size IS NOT NULL Begin Set @val1=( SELECT MAX(size) FROM dbo.packages WHERE bin_no IS NULL AND size <= @size ) EXEC @count=dbo.count_packages 1,@val1 If @space_count < @count Set @count=@space_count If @count > 0 Begin TRUNCATE TABLE #bins INSERT INTO #bins (bno) SELECT TOP (@count) bin_no FROM dbo.bins WHERE solution = @solution AND space_left < @val3+@val3 AND space_left >= @val3 TRUNCATE TABLE #packages INSERT INTO #packages (pno) SELECT TOP (@count) package_no FROM dbo.packages WHERE bin_no IS NULL AND size = @val1 EXEC dbo.update_packages_and_bins @solution,@val1 If @debug=1 select @count,'('+cast(@size as varchar(12))+')',@val1 Set @loop=1 End End End End End -- no combi=4, so simply assign the biggest remaining package Set @size=( SELECT MAX(size) FROM dbo.packages WHERE bin_no IS NULL ) Set @space_count = ( SELECT COUNT(*) FROM dbo.bins WHERE solution = @solution AND space_left >= @size ) If @space_count > 0 Begin If @debug=1 print 'no combi=4' Set @val1=@size EXEC @count=dbo.count_packages 1,@val1 If @space_count < @count Set @count=@space_count If @count > 0 Begin TRUNCATE TABLE #bins INSERT INTO #bins (bno) SELECT TOP (@count) bin_no FROM dbo.bins WHERE solution = @solution AND space_left >= @size TRUNCATE TABLE #packages INSERT INTO #packages (pno) SELECT TOP (@count) package_no FROM dbo.packages WHERE bin_no IS NULL AND size = @val1 EXEC dbo.update_packages_and_bins @solution,@val1 If @debug=1 select @count,'('+cast(@size as varchar(12))+')',@val1 End End Else Begin If EXISTS ( SELECT * FROM dbo.packages WHERE bin_no IS NULL ) Begin -- The biggest package doesn't fit, so extra bins are required If @debug=1 print 'need to add bins' EXEC dbo.current_state_of_affairs @solution,@debug Set @count=CEILING(( SELECT COUNT(*) FROM dbo.packages WHERE bin_no IS NULL AND size=@size )*1.0 / (@bin_size / @size) ) Set @extra_bins = CEILING(( SELECT SUM(size) FROM dbo.packages WHERE bin_no IS NULL ) * 1.0 / @bin_size) If @debug=1 select @count,@extra_bins as extra_bins If @count > @extra_bins Set @extra_bins=@count INSERT INTO dbo.bins (solution, bin_no, space_left) SELECT @solution, n, @bin_size FROM dbo.numbers WHERE n BETWEEN @min_bins+1 AND @min_bins+@extra_bins Set @min_bins = @min_bins + @extra_bins End End End If @debug=1 select getdate(),@iteration as Iteration SET NOCOUNT OFF DROP TABLE #ranking DROP TABLE #packages DROP TABLE #bins
Declare @bin_size smallint Set @bin_size=100 SELECT solution , COUNT(bin_no) AS number_of_bins , CEILING(SUM(@bin_size-space_left)*1.0/@bin_size) AS minimum , COUNT(bin_no) * 100.0 / CEILING(SUM(@bin_size-space_left)*1.0/@bin_size) AS percentage FROM dbo.bins GROUP BY solution ORDER BY solution
solution number_of_bins minimum percentage -------- -------------- --------------------- ------------------------------------- 1 56766 43985 129.057633284074116175969 2 45016 43985 102.343980902580425144935 3 45016 43985 102.343980902580425144935 4 45472 43985 103.380697965215414345799 5 45482 43985 103.403432988518813231783 6 44299 43985 100.713879731726725019893Fortunately, this result is much better than solution 3. The code ran for 29 seconds.
Part 5 - Solution 5 | Part 7 - Brute Force discovery
Back to SQL Server main menu. Mail your comments to gertjans@xs4all.nl.