I've been working in SQL Server 2008 R2 to develop a stored procedure that can match split quantities. I'm having trouble with the algorithm; the one I developed, which I'll describe, can't scale past 55 items. If an alternate solution is available, the name of the algorithm will do. I can implement it.
Problem:
I have a list of quantities, and the sum of some subset of those numbers needs to equal a target number. Quantities are not unique. For example, assume you have quantities:
1, 2, 3, 8
and the target quantity is 11. Two solutions exist: 1+2+8 and 3+8. It doesn't matter which solution is found, I only need a solution.
This is how I've tried to solve it (keep in mind, this is in SQL). Assign a bitmask to each quantity:
1: 0001
2: 0010
3: 0100
8: 1000
Using a counter from 15 to 1 and using bitwise-and, we get:
15: 1111: 1, 2, 3, 8 total: 14
14: 1110: 2, 3, 8 total: 13
13: 1101: 1, 2, 8 total: 11 (solution found)
...
2: 0010: 2
1: 0001: 1
This works great... when the number of items is less than 56. At that point, 2^57 gets truncated. I'm using decimal(38,0)
as my data type so I have 17 bytes (136 bits). I'm pretty much guaranteed never to have more than 125 items, so this is fine. But at 57, my bitmask strategy fails.
Does this make sense? Please comment if I need to clarify the algorithm.
Question 1: Can this algorithm be made to scale out to 125? If not, or if another solution would be more efficient:
Question 2: What is the name of another algorithm I can implement to solve the problem? Surely this sort of problem is common enough that it has a named algorithm.... right?
My implementation, for those interested
Uncomment the creation and filling of foo to create the test data.
--create table foo
--(
-- ID int primary key identity(1,1),
-- Qty int not null
--)
--go
--create nonclustered index IX_FOO__Qty on foo (Qty) include (ID)
--go
--insert into foo (Qty) values (1)
--insert into foo (Qty) values (1)
--insert into foo (Qty) values (2)
--insert into foo (Qty) values (3)
--insert into foo (Qty) values (7)
declare @seek int = 9,
@total int = 0,
@n int,
@oldn int = 0,
@i int
select @i = SQUARE(count(1))-1 from foo -- start counting down from the top
select
x.ID,
x.Qty,
x.v as flags,
0 isanswer,
convert(varbinary(16), v) hex
into #tmp
from
(
select f.ID, f.Qty, POWER(2, row_number() over(order by f.qty) - 1) v
from foo f
) x
while @i > 0
begin
select @total = sum(Qty), @n = count(ID) from #tmp t where flags & @i != 0
if (@total = @seek and @oldn < @n)
begin
update #tmp set isanswer = case when flags & @i != 0 then 1 else 0 end
select @oldn = @n
end
select @i = @i - 1
end
select * from #tmp where isanswer = 1
drop table #tmp
This has the result set:
ID Qty flags isanswer hex
1 1 1 1 0x00000001
2 1 2 1 0x00000002
5 7 16 1 0x00000010
Any thoughts? I recognize I would have a much easier time doing this in C#, but if a SQL solution is possible...