subset_sum 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Jeremy Evans
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/extconf.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile("subset_sum")
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env spec
2
+ require 'subset_sum'
3
+
4
+ context "SubsetSum.subset_sum" do
5
+ it "should raise TypeError unless the first argument is an array" do
6
+ proc{SubsetSum.subset_sum(1, 1)}.should raise_error(TypeError)
7
+ proc{SubsetSum.subset_sum(nil, 1)}.should raise_error(TypeError)
8
+ end
9
+
10
+ it "should raise TypeError unless the second argument is an Integer" do
11
+ proc{SubsetSum.subset_sum([1,2,3], [1,2,3])}.should raise_error(TypeError)
12
+ proc{SubsetSum.subset_sum([1,2,3], nil)}.should raise_error(TypeError)
13
+ end
14
+
15
+ it "should return nil if no subset sums to the given amount" do
16
+ SubsetSum.subset_sum([1,2,3], -1).should == nil
17
+ SubsetSum.subset_sum([1,2,3], 7).should == nil
18
+ end
19
+
20
+ it "should return a subset of the given array summing to the given amount" do
21
+ SubsetSum.subset_sum([1,2,3], 0).should == []
22
+ SubsetSum.subset_sum([1,2,3], 1).should == [1]
23
+ SubsetSum.subset_sum([1,2,3], 2).should == [2]
24
+ [[1,2], [3]].should include(SubsetSum.subset_sum([1,2,3], 3))
25
+ SubsetSum.subset_sum([1,2,3], 4).should == [1,3]
26
+ SubsetSum.subset_sum([1,2,3], 5).should == [2,3]
27
+ SubsetSum.subset_sum([1,2,3], 6).should == [1,2,3]
28
+
29
+ a = [355104, 476077, 476303, 224658, -17532, -183480, -286788, 238271, 231845, -227454, 226199, 105438, 316870, 353652, 173563, 244958, 367896, 105046, 495797, 447209, 397810, -394348, 242527, 17532, -57224, -38084, 82375, 445376, -297793, 368660, -65413, 96325, -472195, -23826, -113982, -355574, 331821]
30
+ b = [-17532, 226199, 105438, 353652, 173563, 244958, 397810, 242527, 17532, -38084, 82375, 445376, 368660, -65413, -23826, 331821]
31
+ s = b.inject(0){|x,y|y+x}
32
+ SubsetSum.subset_sum(a, s).inject(0){|x,y|y+x}.should == s
33
+ end
34
+
35
+ it "should raise a SubsetSum::TimeoutError if the Timeout expires" do
36
+ a = [333471, -81424, 168803, -14874, 269559, 276470, -112006, -73874, -122941, 137631, -28130, -158579, -422171, 27107, 320112, -36290, -285551, 8352, 148555, -191195, -378065, -98153, 446370, 364645, -410213, -18978, 473135, -86692, 220362, -131996, -350007, -151015, -406022, -357901, -180250, 234239, -342453, -355331, -288241, 14474, 389351, 98297, -241761, 96174, 21747, 231199, -473800, -172155, 65848, -36045, -449758, 255701, 366189, -181547, -334067, 61902, -369357, -444588, 196073, 173373, 348303, 445476, 233108, 146771, -124962, 426707, -443489, -147630, -40380, 462470, -460874, -488721, -316681, 58097, 374972, -35163, 101042, -143586, 176358, -443511, 262241, 79581, -251333, 432832, 216996, 290187, -216371, 412922, -250978, 282206, -379257, 240712, 245726, -478854, 185713, 187485, -213379, -185167, -475251, -127179, -158661]
37
+ b = [333471, -14874, 276470, 137631, -28130, -158579, 27107, -36290, 148555, -191195, -378065, 473135, -86692, -350007, -151015, -406022, 234239, -355331, 21747, 231199, -172155, -36045, -449758, 366189, -334067, -369357, -444588, 348303, 445476, -124962, 426707, 462470, -460874, 58097, 374972, -35163, 101042, 79581, -216371, 240712, -213379, -185167]
38
+ s = b.inject(0){|x,y|y+x}
39
+ proc{SubsetSum.subset_sum(a, s, 1).inject(0){|x,y|y+x}}.should raise_error(SubsetSum::TimeoutError)
40
+ end
41
+
42
+ it "should not use the C version if the integers are Bignums" do
43
+ SubsetSum.should_not_receive(:_subset_sum)
44
+ SubsetSum.subset_sum([2**100, 1, 2,], 1+2**100).should == [2**100, 1]
45
+ end
46
+ end
47
+
data/subset_sum.c ADDED
@@ -0,0 +1,347 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+ #include <string.h>
4
+ #include <time.h>
5
+
6
+ #include "ruby.h"
7
+
8
+ static VALUE eTimeoutError;
9
+
10
+ typedef struct _sum_node {
11
+ long sum;
12
+ long length;
13
+ long height;
14
+ long* numbers;
15
+ struct _sum_node *left;
16
+ struct _sum_node *right;
17
+ } sum_node;
18
+
19
+ static long rbss_sum(long* numbers, long length) {
20
+ long i;
21
+ long sum = 0;
22
+ for(i=0; i<length; i++) {
23
+ sum += numbers[i];
24
+ }
25
+ return sum;
26
+ }
27
+
28
+ /*
29
+ static void rbss_print(long* numbers, long length) {
30
+ long i;
31
+ long sum = 0;
32
+ printf("[");
33
+ for(i=0; i<length; i++) {
34
+ printf("%ld,", numbers[i]);
35
+ }
36
+ printf("]\n");
37
+ return;
38
+ }
39
+ */
40
+
41
+ static void rbss_free_nodes(sum_node* node) {
42
+ if(node != NULL) {
43
+ rbss_free_nodes(node->left);
44
+ node->left = NULL;
45
+ rbss_free_nodes(node->right);
46
+ node->right = NULL;
47
+ free(node->numbers);
48
+ node->numbers = NULL;
49
+ free(node);
50
+ node = NULL;
51
+ }
52
+ }
53
+
54
+ static sum_node* rbss_new_node(long* numbers, long length) {
55
+ sum_node* node;
56
+
57
+ if((node = (sum_node*)calloc(1, sizeof(sum_node))) == NULL) {
58
+ return NULL;
59
+ }
60
+ if((node->numbers = (long*)calloc(length, sizeof(long))) == NULL) {
61
+ free(node);
62
+ return NULL;
63
+ }
64
+ node->sum = rbss_sum(numbers, length);
65
+ node->length = length;
66
+ node->height = 0;
67
+ memcpy(node->numbers, numbers, length*sizeof(long));
68
+ node->left = NULL;
69
+ node->right = NULL;
70
+ return node;
71
+ }
72
+
73
+ static long rbss_height(sum_node* node) {
74
+ return node == NULL ? -1 : node->height;
75
+ }
76
+
77
+ static long rbss_max(long h1, long h2) {
78
+ return h1 > h2 ? h1 : h2;
79
+ }
80
+
81
+ static void rbss_reset_height(sum_node* node) {
82
+ node->height = rbss_max(rbss_height(node->left), rbss_height(node->right)) + 1;
83
+ }
84
+
85
+ static sum_node* rbss_rotate_left_1(sum_node* node2) {
86
+ sum_node* node1;
87
+
88
+ node1 = node2->left;
89
+ node2->left = node1->right;
90
+ node1->right = node2;
91
+
92
+ rbss_reset_height(node2);
93
+ rbss_reset_height(node1);
94
+ return node1;
95
+ }
96
+
97
+ static sum_node* rbss_rotate_right_1(sum_node* node1) {
98
+ sum_node* node2;
99
+
100
+ node2 = node1->right;
101
+ node1->right = node2->left;
102
+ node2->left = node1;
103
+
104
+ rbss_reset_height(node1);
105
+ rbss_reset_height(node2);
106
+ return node2;
107
+ }
108
+
109
+ static sum_node* rbss_rotate_left_2(sum_node* node) {
110
+ node->left = rbss_rotate_right_1(node->left);
111
+ return rbss_rotate_left_1(node);
112
+ }
113
+
114
+ static sum_node* rbss_rotate_right_2(sum_node* node) {
115
+ node->right = rbss_rotate_left_1(node->right);
116
+ return rbss_rotate_right_1(node);
117
+ }
118
+
119
+ /* Insert a new node in the AVL tree */
120
+ static sum_node* rbss_insert(sum_node* node, long sum, long* numbers, long length) {
121
+ sum_node* t;
122
+
123
+ if(node == NULL) {
124
+ node = rbss_new_node(numbers, length);
125
+ return node;
126
+ }
127
+ if(sum < node->sum) {
128
+ if((t = rbss_insert(node->left, sum, numbers, length)) == NULL) {
129
+ return NULL;
130
+ }
131
+ node->left = t;
132
+ if(rbss_height(node->left) - rbss_height(node->right) == 2) {
133
+ if(sum < node->left->sum) {
134
+ node = rbss_rotate_left_1(node);
135
+ } else {
136
+ node = rbss_rotate_left_2(node);
137
+ }
138
+ }
139
+ } else if (sum > node->sum) {
140
+ if((t = rbss_insert(node->right, sum, numbers, length)) == NULL) {
141
+ return NULL;
142
+ }
143
+ node->right = t;
144
+ if(rbss_height(node->right) - rbss_height(node->left) == 2) {
145
+ if(sum > node->right->sum) {
146
+ node = rbss_rotate_right_1(node);
147
+ } else {
148
+ node = rbss_rotate_right_2(node);
149
+ }
150
+ }
151
+ }
152
+ rbss_reset_height(node);
153
+ return node;
154
+ }
155
+
156
+ /* Look in the tree to see if there is a node with the matching sum. */
157
+ static sum_node* rbss_lookup(sum_node* node, long sum) {
158
+ if(node == NULL) {
159
+ return NULL;
160
+ }
161
+ return sum == node->sum ? node : rbss_lookup(sum < node->sum ? node->left : node->right, sum);
162
+ }
163
+
164
+ /* Add a node to the tree associating the sum of the numbers with the numbers */
165
+ static int rbss_add(sum_node** table, long* numbers, long length, long* temp, long want) {
166
+ sum_node* t;
167
+ t = rbss_insert(*table, rbss_sum(numbers, length), numbers, length);
168
+ if(t == NULL) {
169
+ return -1;
170
+ }
171
+ *table = t;
172
+ return 0;
173
+ }
174
+
175
+ /* Check and see if the difference between the sum of the numbers and the
176
+ number we want is in the tree. If so, update temp to be the current
177
+ array of numbers plus the matching value in the tree. */
178
+ static int rbss_check(sum_node** table, long* numbers, long length, long* temp, long want) {
179
+ long sum;
180
+ long diff;
181
+ sum_node* node;
182
+
183
+ sum = rbss_sum(numbers, length);
184
+ diff = want - sum;
185
+ if((node = rbss_lookup(*table, diff)) == NULL) {
186
+ return 0;
187
+ }
188
+ memcpy(temp, node->numbers, node->length*sizeof(long));
189
+ memcpy(temp+node->length, numbers, length*sizeof(long));
190
+ return 1;
191
+ }
192
+
193
+ /* For each subset of the array of numbers, execute the function, passing
194
+ the table, subset, length of subset, and the temp and want variables to
195
+ the function. */
196
+ static int rbss_subsets(int(*func)(sum_node**, long*, long, long*, long), sum_node** table, long* numbers, long length, long skip, long* temp, long want, time_t start_time, time_t max_seconds) {
197
+ int result;
198
+ time_t current_time;
199
+ long i;
200
+ long limit;
201
+ long* temp_numbers;
202
+
203
+ limit = length - 1;
204
+ if((result = (*func)(table, numbers, length, temp, want)) != 0) {
205
+ return result;
206
+ }
207
+ if(limit > 0) {
208
+ if(max_seconds){
209
+ if((current_time = time(NULL)) == -1) {
210
+ return -3;
211
+ }
212
+ if(current_time - start_time > max_seconds) {
213
+ return -2;
214
+ }
215
+ }
216
+ if((temp_numbers = (long*)calloc(limit, sizeof(long))) == NULL) {
217
+ rb_raise(rb_eNoMemError, "calloc");
218
+ }
219
+ for(i=limit; i>=skip; i--) {
220
+ memcpy(temp_numbers, numbers, i*sizeof(long));
221
+ memcpy(temp_numbers+i, numbers+i+1, (limit-i)*sizeof(long));
222
+ if((result = rbss_subsets(func, table, temp_numbers, limit, i, temp, want, start_time, max_seconds)) != 0) {
223
+ free(temp_numbers);
224
+ return result;
225
+ }
226
+ }
227
+ free(temp_numbers);
228
+ }
229
+ return 0;
230
+ }
231
+
232
+ /* Build a ruby array of Fixnums from the C array of longs */
233
+ static VALUE rbss_array(long* numbers, long length) {
234
+ long i;
235
+ VALUE array;
236
+
237
+ array = rb_ary_new();
238
+ for(i=0;i<length;i++) {
239
+ if(numbers[i] != 0) {
240
+ rb_ary_push(array, LONG2NUM(numbers[i]));
241
+ }
242
+ }
243
+ return array;
244
+ }
245
+
246
+ /* Raise a ruby exception based on the result of res.
247
+ Should only be run after the all memory has been cleaned up. */
248
+ static void rbss_raise_error(int res) {
249
+ if(res == -1) {
250
+ rb_raise(rb_eNoMemError, "calloc");
251
+ }
252
+ if(res == -2) {
253
+ rb_raise(eTimeoutError, "timeout expired");
254
+ }
255
+ if(res == -3) {
256
+ rb_raise(rb_eStandardError, "can't get current time");
257
+ }
258
+ }
259
+
260
+ /*
261
+ Document-method: subset_sum
262
+
263
+ call-seq:
264
+ SubsetSum.subset_sum(array, num) -> subset_of_array || nil
265
+
266
+ If a subset of the numbers in array sum to num, the subset is returned.
267
+ Otherwise, nil is returned.
268
+ */
269
+ static VALUE rbss_main(VALUE self, VALUE numbers, VALUE result, VALUE max_seconds) {
270
+ int res;
271
+ time_t start_time = 0;
272
+ time_t c_max_seconds;
273
+ long i;
274
+ long length;
275
+ long half_length;
276
+ long c_result;
277
+ long* c_numbers;
278
+ long* c_numbers_tmp;
279
+ sum_node* table = NULL;
280
+ VALUE answer;
281
+
282
+ Check_Type(numbers, T_ARRAY);
283
+
284
+ c_result = NUM2LONG(result);
285
+ c_max_seconds = NUM2INT(max_seconds);
286
+ length = RARRAY(numbers)->len;
287
+ half_length = length/2;
288
+
289
+ if(c_max_seconds < 0) {
290
+ c_max_seconds = 0;
291
+ }
292
+ if(c_max_seconds) {
293
+ if((start_time = time(NULL)) == -1) {
294
+ rb_raise(rb_eStandardError, "can't get current time");
295
+ }
296
+ }
297
+
298
+ if((c_numbers = (long*)calloc(length*2, sizeof(long))) == NULL) {
299
+ rb_raise(rb_eNoMemError, "calloc");
300
+ }
301
+ c_numbers_tmp = c_numbers+length;
302
+
303
+ for(i=0; i<length; i++) {
304
+ c_numbers[i] = NUM2LONG(rb_ary_entry(numbers, i));
305
+ }
306
+
307
+ /* Initialize the tree with an empty subset */
308
+ table = rbss_insert(table, 0, NULL, 0);
309
+
310
+ /* Add a node to the tree for each subset of the first half of the set */
311
+ if((res = rbss_subsets(&rbss_add, &table, c_numbers, half_length, 0, \
312
+ c_numbers_tmp, c_result, start_time, c_max_seconds)) != 0) {
313
+ rbss_free_nodes(table);
314
+ free(c_numbers);
315
+ rbss_raise_error(res);
316
+ }
317
+
318
+ /* See if one of the subsets in the first half sums to the result */
319
+ if((res = rbss_check(&table, NULL, 0, c_numbers_tmp, c_result)) == 0) {
320
+ /* Check all subsets in the second half, and see if the difference
321
+ between their sum and the result is a sum of one of the subsets
322
+ in the first half */
323
+ res = rbss_subsets(&rbss_check, &table, c_numbers+half_length, \
324
+ length-half_length, 0, c_numbers_tmp, c_result, start_time, \
325
+ c_max_seconds);
326
+ }
327
+ if(res < 1) {
328
+ rbss_free_nodes(table);
329
+ free(c_numbers);
330
+ if(res < 0) {
331
+ rbss_raise_error(res);
332
+ }
333
+ return Qnil;
334
+ }
335
+
336
+ rbss_free_nodes(table);
337
+ answer = rbss_array(c_numbers_tmp, length);
338
+ free(c_numbers);
339
+ return answer;
340
+ }
341
+
342
+ void Init_subset_sum() {
343
+ VALUE SubsetSum;
344
+ SubsetSum = rb_define_module("SubsetSum");
345
+ rb_define_module_function(SubsetSum, "_subset_sum", rbss_main, 3);
346
+ eTimeoutError = rb_define_class_under(SubsetSum, "TimeoutError", rb_eStandardError);
347
+ }
data/subset_sum.rb ADDED
@@ -0,0 +1,73 @@
1
+ # This module provides both a C and pure ruby simple subset sum problem solver.
2
+ # The subset sum problem is, given a set of numbers, can the sum of any subset
3
+ # of those numbers equal a given number. This problem is NP-complete.
4
+ #
5
+ # Both the C and pure ruby versions implement a fairly simple
6
+ # meet-in-the-middle algorithm. The C version uses an AVL tree to store the
7
+ # data, while the pure ruby version uses a ruby hash. For the C version to be
8
+ # used, the sum of the positive numbers and the sum of the negative numbers in
9
+ # the set, as well as the wanted number, must all be Fixnums. Additionally,
10
+ # max_seconds should be nil or a Fixnum.
11
+ module SubsetSum
12
+ # Exception raised when timeout expires
13
+ class TimeoutError < StandardError
14
+ end
15
+
16
+ # Return first subset of values that sum to want, using the meet in the
17
+ # middle algorithm (O(n * 2^(n/2)).
18
+ def self.subset_sum(values, want, max_seconds=nil)
19
+ raise(TypeError, "values must be an array of Integers") unless values.is_a?(Array)
20
+ raise(TypeError, "want must be an Integer") unless want.is_a?(Integer)
21
+
22
+ # Optimization by removing 0 values and doing some simple checks
23
+ values = values.reject{|x| x == 0}
24
+ values.each{|value| return [value] if value == want}
25
+ return values if sum(values) == want
26
+ pos, neg = values.partition{|x| x > 0}
27
+ sp, sn = sum(pos), sum(neg)
28
+ return pos if sp == want
29
+ return neg if sn == want
30
+
31
+ # Use the C version if it exists and all values will be inside the machine
32
+ # limits
33
+ return _subset_sum(values, want, max_seconds.to_i) if \
34
+ respond_to?(:_subset_sum, true) and want.is_a?(Fixnum) and \
35
+ sum(pos).is_a?(Fixnum) and sum(neg).is_a?(Fixnum) and \
36
+ max_seconds.to_i.is_a?(Fixnum)
37
+
38
+ # The pure ruby version
39
+ sums = {}
40
+ start_time = Time.now if max_seconds
41
+ l = values.length/2
42
+ subsets(values[0...l]) do |subset|
43
+ raise(TimeoutError, "timeout expired") if max_seconds and Time.now - start_time > max_seconds
44
+ sums[sum(subset)] = subset
45
+ end
46
+ subsets(values[l..-1]) do |subset|
47
+ raise(TimeoutError, "timeout expired") if max_seconds and Time.now - start_time > max_seconds
48
+ if subset2 = sums[want - sum(subset)]
49
+ return subset2 + subset
50
+ end
51
+ end
52
+ nil
53
+ end
54
+
55
+ # Yield all subsets of the array to the block.
56
+ def self.subsets(array, skip = 0, &block)
57
+ yield(array)
58
+ (array.length-1).downto(skip){|i| subsets(array[0...i] + array[i+1..-1], i, &block)}
59
+ end
60
+
61
+ # Return the sum of the values.
62
+ def self.sum(values)
63
+ values.inject(0){|x,y| x+=y}
64
+ end
65
+
66
+ private_class_method :subsets, :sum
67
+ end
68
+
69
+ begin
70
+ require 'subset_sum.so'
71
+ SubsetSum.send(:private_class_method, :_subset_sum)
72
+ rescue LoadError
73
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: subset_sum
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Evans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-08 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: code@jeremyevans.net
18
+ executables: []
19
+
20
+ extensions:
21
+ - extconf.rb
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - LICENSE
26
+ - extconf.rb
27
+ - subset_sum.c
28
+ - subset_sum.rb
29
+ - spec/subset_sum_spec.rb
30
+ has_rdoc: true
31
+ homepage: http://subset-sum.rubyforge.org/
32
+ post_install_message:
33
+ rdoc_options: []
34
+
35
+ require_paths:
36
+ - .
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ version:
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ requirements: []
50
+
51
+ rubyforge_project: subset-sum
52
+ rubygems_version: 1.2.0
53
+ signing_key:
54
+ specification_version: 2
55
+ summary: Simple Subset Sum Solver in C
56
+ test_files:
57
+ - spec/subset_sum_spec.rb