public
Description: Simple Subset Sum Solver with C and Pure Ruby Versions
Homepage: http://subset-sum.rubyforge.org/
Clone URL: git://github.com/jeremyevans/ruby-subset_sum.git
ruby-subset_sum / subset_sum.rb
100644 73 lines (65 sloc) 2.811 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# This module provides both a C and pure ruby simple subset sum problem solver.
# The subset sum problem is, given a set of numbers, can the sum of any subset
# of those numbers equal a given number. This problem is NP-complete.
#
# Both the C and pure ruby versions implement a fairly simple
# meet-in-the-middle algorithm. The C version uses an AVL tree to store the
# data, while the pure ruby version uses a ruby hash. For the C version to be
# used, the sum of the positive numbers and the sum of the negative numbers in
# the set, as well as the wanted number, must all be Fixnums. Additionally,
# max_seconds should be nil or a Fixnum.
module SubsetSum
  # Exception raised when timeout expires
  class TimeoutError < StandardError
  end
  
  # Return first subset of values that sum to want, using the meet in the
  # middle algorithm (O(n * 2^(n/2)).
  def self.subset_sum(values, want, max_seconds=nil)
    raise(TypeError, "values must be an array of Integers") unless values.is_a?(Array)
    raise(TypeError, "want must be an Integer") unless want.is_a?(Integer)
    
    # Optimization by removing 0 values and doing some simple checks
    values = values.reject{|x| x == 0}
    values.each{|value| return [value] if value == want}
    return values if sum(values) == want
    pos, neg = values.partition{|x| x > 0}
    sp, sn = sum(pos), sum(neg)
    return pos if sp == want
    return neg if sn == want
    
    # Use the C version if it exists and all values will be inside the machine
    # limits
    return _subset_sum(values, want, max_seconds.to_i) if \
      respond_to?(:_subset_sum, true) and want.is_a?(Fixnum) and \
      sum(pos).is_a?(Fixnum) and sum(neg).is_a?(Fixnum) and \
      max_seconds.to_i.is_a?(Fixnum)
 
    # The pure ruby version
    sums = {}
    start_time = Time.now if max_seconds
    l = values.length/2
    subsets(values[0...l]) do |subset|
      raise(TimeoutError, "timeout expired") if max_seconds and Time.now - start_time > max_seconds
      sums[sum(subset)] = subset
    end
    subsets(values[l..-1]) do |subset|
      raise(TimeoutError, "timeout expired") if max_seconds and Time.now - start_time > max_seconds
      if subset2 = sums[want - sum(subset)]
        return subset2 + subset
      end
    end
    nil
  end
 
  # Yield all subsets of the array to the block.
  def self.subsets(array, skip = 0, &block)
    yield(array)
    (array.length-1).downto(skip){|i| subsets(array[0...i] + array[i+1..-1], i, &block)}
  end
  
  # Return the sum of the values.
  def self.sum(values)
    values.inject(0){|x,y| x+=y}
  end
  
  private_class_method :subsets, :sum
end
 
begin
  require 'subset_sum.so'
  SubsetSum.send(:private_class_method, :_subset_sum)
rescue LoadError
end