combinatorics 0.3.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +8 -0
  3. data/Benchmarks.md +257 -26
  4. data/ChangeLog.md +12 -0
  5. data/LICENSE.txt +1 -2
  6. data/README.md +102 -32
  7. data/Rakefile +13 -2
  8. data/benchmarks/cartesian_product.rb +18 -0
  9. data/benchmarks/choose.rb +19 -0
  10. data/benchmarks/derange.rb +18 -0
  11. data/benchmarks/list_comprehension.rb +2 -5
  12. data/benchmarks/permute.rb +18 -0
  13. data/benchmarks/power_set.rb +18 -0
  14. data/combinatorics.gemspec +124 -7
  15. data/gemspec.yml +11 -6
  16. data/lib/combinatorics.rb +7 -0
  17. data/lib/combinatorics/cartesian_product.rb +3 -0
  18. data/lib/combinatorics/cartesian_product/cardinality.rb +45 -0
  19. data/lib/combinatorics/cartesian_product/extensions.rb +2 -0
  20. data/lib/combinatorics/cartesian_product/extensions/array.rb +7 -0
  21. data/lib/combinatorics/cartesian_product/extensions/set.rb +9 -0
  22. data/lib/combinatorics/cartesian_product/mixin.rb +57 -0
  23. data/lib/combinatorics/choose.rb +3 -0
  24. data/lib/combinatorics/choose/cardinality.rb +99 -0
  25. data/lib/combinatorics/choose/extensions.rb +2 -0
  26. data/lib/combinatorics/choose/extensions/array.rb +5 -0
  27. data/lib/combinatorics/choose/extensions/set.rb +6 -0
  28. data/lib/combinatorics/choose/mixin.rb +53 -0
  29. data/lib/combinatorics/derange.rb +3 -0
  30. data/lib/combinatorics/derange/cardinality.rb +23 -0
  31. data/lib/combinatorics/derange/extensions.rb +1 -0
  32. data/lib/combinatorics/derange/extensions/array.rb +5 -0
  33. data/lib/combinatorics/derange/mixin.rb +47 -0
  34. data/lib/combinatorics/enumerator.rb +2 -0
  35. data/lib/combinatorics/extensions/math.rb +177 -0
  36. data/lib/combinatorics/generator.rb +8 -1
  37. data/lib/combinatorics/permute.rb +3 -0
  38. data/lib/combinatorics/permute/cardinality.rb +98 -0
  39. data/lib/combinatorics/permute/extensions.rb +2 -0
  40. data/lib/combinatorics/permute/extensions/array.rb +7 -0
  41. data/lib/combinatorics/permute/extensions/set.rb +9 -0
  42. data/lib/combinatorics/permute/mixin.rb +48 -0
  43. data/lib/combinatorics/power_set.rb +1 -0
  44. data/lib/combinatorics/power_set/cardinality.rb +36 -0
  45. data/lib/combinatorics/power_set/mixin.rb +19 -22
  46. data/lib/combinatorics/version.rb +2 -2
  47. data/spec/cartesian_product/array_spec.rb +10 -0
  48. data/spec/cartesian_product/cardinality_spec.rb +64 -0
  49. data/spec/cartesian_product/mixin_examples.rb +98 -0
  50. data/spec/cartesian_product/set_spec.rb +10 -0
  51. data/spec/choose/array_spec.rb +9 -0
  52. data/spec/choose/cardinality_spec.rb +132 -0
  53. data/spec/choose/mixin_examples.rb +48 -0
  54. data/spec/choose/set_spec.rb +9 -0
  55. data/spec/derange/array_spec.rb +10 -0
  56. data/spec/derange/cardinality_spec.rb +14 -0
  57. data/spec/derange/mixin_examples.rb +52 -0
  58. data/spec/extensions/math_spec.rb +100 -0
  59. data/spec/extensions/range_spec.rb +1 -1
  60. data/spec/permute/array_spec.rb +10 -0
  61. data/spec/permute/cardinality_spec.rb +146 -0
  62. data/spec/permute/mixin_examples.rb +42 -0
  63. data/spec/permute/set_spec.rb +10 -0
  64. data/spec/power_set/array_spec.rb +3 -2
  65. data/spec/power_set/cardinality_spec.rb +32 -0
  66. data/spec/power_set/mixin_examples.rb +17 -8
  67. data/spec/power_set/set_spec.rb +3 -2
  68. data/spec/spec_helper.rb +5 -3
  69. metadata +114 -95
@@ -0,0 +1,3 @@
1
+ require 'combinatorics/choose/mixin'
2
+ require 'combinatorics/choose/extensions'
3
+ require 'combinatorics/choose/cardinality'
@@ -0,0 +1,99 @@
1
+ require 'combinatorics/extensions/math'
2
+
3
+ module Combinatorics
4
+ #
5
+ # @author duper <super@manson.vistech.net>
6
+ #
7
+ # @since 0.4.0
8
+ #
9
+ module Choose
10
+ #
11
+ # Compute the number of elements in a subset of given size
12
+ #
13
+ # @param [Fixnum] n
14
+ # The number of elements in the input set
15
+ #
16
+ # @param [Fixnum] r
17
+ # Cardinality of subsets to choose
18
+ #
19
+ # @raise [RangeError]
20
+ # `n` must be non-negative.
21
+ #
22
+ # @raise [RangeError]
23
+ # `r` must be non-negative.
24
+ #
25
+ # @raise [RangeError]
26
+ # `r` must be less than or equal to `n`.
27
+ #
28
+ # @return [Fixnum]
29
+ # The binomial coefficient for "n-choose-r"
30
+ #
31
+ # @example
32
+ # cardinality(6, 4)
33
+ # # => 15
34
+ #
35
+ # @see http://en.wikipedia.org/wiki/Binomial_coefficient
36
+ #
37
+ def self.cardinality(n,r=nil)
38
+ raise(RangeError,"n must be non-negative") if n < 0
39
+
40
+ case r
41
+ when 0 then 0
42
+ when nil then Math.factorial(n)
43
+ else
44
+ Math.factorial(n) / (Math.factorial(r) * Math.factorial(n - r))
45
+ end
46
+ end
47
+
48
+ #
49
+ # Wrapper for combination cardinality method defined above. The letter `C'
50
+ # is "chalkboard" notation for subset cardinality.
51
+ #
52
+ # @note
53
+ # This method's naming convention reflects well-known notation used
54
+ # within fields of academic inquiry such as discrete mathematics and
55
+ # set theory. It represents a function returning an integer value
56
+ # for the cardinality of a k-combination (i.e. binomial coefficient.)
57
+ #
58
+ # @see Choose.cardinality
59
+ #
60
+ def self.C(n,r=nil)
61
+ cardinality(n,r)
62
+ end
63
+
64
+ #
65
+ # @param [Integer] n
66
+ # The total number of choices.
67
+ #
68
+ # @param [Enumerable] c
69
+ # The set of `r` values to choose from `n`.
70
+ #
71
+ # @return [Array]
72
+ # Elements are cardinalities for each subset "1" through "c".
73
+ #
74
+ # @raise [RangeError]
75
+ # `n` must be non-negative.
76
+ #
77
+ # @example
78
+ # cardinality_all(4)
79
+ # # => [4, 6, 4, 1]
80
+ #
81
+ # @example
82
+ # cardinality_all(10, 5..10)
83
+ # # => [252, 210, 120, 45, 10, 1]
84
+ #
85
+ # @note
86
+ # Sum of elements will equal Math.factorial(c)
87
+ #
88
+ # @see cardinality
89
+ # @see http://en.wikipedia.org/wiki/Combinations
90
+ #
91
+ def self.cardinality_all(n,c=(1..n))
92
+ if n < 0
93
+ raise(RangeError,"c must be non-negative")
94
+ end
95
+
96
+ c.map { |r| cardinality(n,r) }
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,2 @@
1
+ require 'combinatorics/choose/extensions/array'
2
+ require 'combinatorics/choose/extensions/set'
@@ -0,0 +1,5 @@
1
+ require 'combinatorics/choose/mixin'
2
+
3
+ class Array
4
+ include Combinatorics::Choose::Mixin
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'set'
2
+ require 'combinatorics/choose/mixin'
3
+
4
+ class Set
5
+ include Combinatorics::Choose::Mixin
6
+ end
@@ -0,0 +1,53 @@
1
+ require 'set'
2
+
3
+ module Combinatorics
4
+ module Choose
5
+ #
6
+ # @author duper <super@manson.vistech.net>
7
+ #
8
+ # @since 0.4.0
9
+ #
10
+ module Mixin
11
+ #
12
+ # Get combinations with a specified number of elements from an input
13
+ # set.
14
+ #
15
+ # @param [Fixnum] k
16
+ # Cardinality of chosen subsets
17
+ #
18
+ # @yield [combo]
19
+ # The given block will be passed each combination.
20
+ #
21
+ # @yieldparam [Array] combo
22
+ # A k-sized combination of elements from the set.
23
+ #
24
+ # @raise [TypeError]
25
+ # `self` must be Enumerable.
26
+ #
27
+ # @return [Enumerator]
28
+ # If no block is given, an Enumerator of the k-sized combinations
29
+ # within the set will be returned.
30
+ #
31
+ # @example
32
+ # [1, 2, 3].choose(2).to_a
33
+ # # => [#<Set: {1, 2}>, #<Set: {1, 3}>, #<Set: {2, 3}>]
34
+ #
35
+ # @see http://rubydoc.info/stdlib/core/Array#combination-instance_method
36
+ #
37
+ def choose(k,&block)
38
+ return enum_for(:choose,k) unless block
39
+
40
+ unless kind_of?(Enumerable)
41
+ raise(TypeError,"#{inspect} must be Enumerable")
42
+ end
43
+
44
+ elements = self.to_a
45
+ elements.uniq!
46
+
47
+ elements.combination(k) do |subset|
48
+ yield Set.new(subset)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ require 'combinatorics/derange/mixin'
2
+ require 'combinatorics/derange/extensions'
3
+ require 'combinatorics/derange/cardinality'
@@ -0,0 +1,23 @@
1
+ require 'combinatorics/extensions/math'
2
+
3
+ module Combinatorics
4
+ module Derange
5
+ #
6
+ # Compute the number of derangements for a data structure of given size
7
+ #
8
+ # @see Math.subfactorial
9
+ #
10
+ def self.cardinality(n)
11
+ Math.subfactorial(n)
12
+ end
13
+
14
+ #
15
+ # Wrapper for derangement cardinality method defined above
16
+ #
17
+ # @note The letter `D' is academic representation for derangements
18
+ #
19
+ def self.D(n)
20
+ cardinality(n)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1 @@
1
+ require 'combinatorics/derange/extensions/array'
@@ -0,0 +1,5 @@
1
+ require 'combinatorics/derange/mixin'
2
+
3
+ class Array
4
+ include Combinatorics::Derange::Mixin
5
+ end
@@ -0,0 +1,47 @@
1
+ module Combinatorics
2
+ module Derange
3
+ #
4
+ # @author duper <super@manson.vistech.net>
5
+ #
6
+ # @since 0.4.0
7
+ #
8
+ module Mixin
9
+ #
10
+ # Calculate all derangements for an Enumerable object.
11
+ #
12
+ # @yield [derangement]
13
+ # If a block is given, it will be passed an Array representing
14
+ # an individual derangement from the full calculation.
15
+ #
16
+ # @yieldparam [Array] derangement
17
+ # One of the calculated derangements.
18
+ #
19
+ # @return [Enumerator]
20
+ # If no block is given, an Enumerator of all derangements will be
21
+ # returned.
22
+ #
23
+ # @example Produce the derangements of a three-element Array
24
+ # [1, 2, 3].derange.to_a
25
+ # # => [[2, 3, 1], [3, 1, 2]]
26
+ #
27
+ # @see http://en.wikipedia.org/wiki/Derangements
28
+ # @see http://mathworld.wolfram.com/Derangement.html
29
+ #
30
+ def derange
31
+ return enum_for(:derange) unless block_given?
32
+
33
+ if size <= 1
34
+ yield []
35
+ else
36
+ elements = self.to_a
37
+
38
+ elements.permutation do |x|
39
+ unless elements.each_with_index.any? { |e,i| e == x[i] }
40
+ yield x
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -6,5 +6,7 @@ module Combinatorics
6
6
  ::Enumerator
7
7
  elsif defined?(::Enumerable::Enumerator) # 1.8.7
8
8
  ::Enumerable::Enumerator
9
+ else
10
+ raise("unable to find the Enumerator class")
9
11
  end
10
12
  end
@@ -0,0 +1,177 @@
1
+ #
2
+ # @author duper <super@manson.vistech.net>
3
+ #
4
+ module Math
5
+ #
6
+ # Mathematical summation (invokes a block for k = 1 until ++k = n).
7
+ #
8
+ # @param [Range] r
9
+ # Range containing the number of times to yield.
10
+ #
11
+ # @yield [i]
12
+ # The given block is expected to return an Integer, which is added to the
13
+ # total sum.
14
+ #
15
+ # @yieldparam [Integer] i
16
+ # An element from `r`.
17
+ #
18
+ # @return [Integer]
19
+ # Total sum after yielding for each element in `r`.
20
+ #
21
+ # @raise [TypeError]
22
+ # `r` must be a {Range}.
23
+ #
24
+ # @example
25
+ # sigma(1..4) { |i| i }
26
+ # # => 10
27
+ #
28
+ # @note
29
+ # "chalkboard" notation for summation is the capital Greek letter Sigma.
30
+ #
31
+ # @see http://en.wikipedia.org/wiki/Summation
32
+ #
33
+ # @since 0.4.0
34
+ #
35
+ def Math.sigma(r)
36
+ unless r.kind_of?(Range)
37
+ raise(TypeError,"r must be a Range")
38
+ end
39
+
40
+ k = 0
41
+
42
+ if block_given?
43
+ r.each { |n| k += yield n }
44
+ else
45
+ r.each { |n| k += n }
46
+ end
47
+
48
+ k
49
+ end
50
+
51
+ #
52
+ # CamelCase alias for sigma (defined above)
53
+ #
54
+ # @see Math.sigma
55
+ #
56
+ # @since 0.4.0
57
+ #
58
+ def Math.Sigma(r)
59
+ Math.sigma(r)
60
+ end
61
+
62
+ #
63
+ # Pi notation for iterative product computations.
64
+ #
65
+ # @param [Range<Integer>] r
66
+ # Inclusive range of Integers.
67
+ #
68
+ # @yield [i]
69
+ # The given block will be passed elements of `r`. The return value from
70
+ # the block will be combined with the product.
71
+ #
72
+ # @yield [Integer] i
73
+ # An element from `r`.
74
+ #
75
+ # @return [Integer]
76
+ # The total product after iterating over each element in `r`.
77
+ #
78
+ # @raise [TypeError]
79
+ # `r` must be a Range.
80
+ #
81
+ # @example
82
+ # Math.pi(1..4)
83
+ # # => 24
84
+ #
85
+ # @see http://en.wikipedia.org/wiki/Pi_notation#Capital_Pi_notation
86
+ #
87
+ # @since 0.4.0
88
+ #
89
+ def Math.pi(r)
90
+ unless r.kind_of?(Range)
91
+ raise(TypeError,"r must be a Range")
92
+ end
93
+
94
+ k = 1
95
+
96
+ if block_given?
97
+ r.each { |n| k *= yield n }
98
+ else
99
+ r.each { |n| k *= n }
100
+ end
101
+
102
+ k
103
+ end
104
+
105
+ #
106
+ # CamelCase alias for pi (defined above)
107
+ #
108
+ # @see Math.pi
109
+ #
110
+ # @since 0.4.0
111
+ #
112
+ def Math.Pi(r)
113
+ Math.pi(r)
114
+ end
115
+
116
+
117
+ #
118
+ # Subfactorial function for calculation of derangement cardinalities.
119
+ #
120
+ # @param [Fixnum] n
121
+ # The length of sequence.
122
+ #
123
+ # @raise [RangeError]
124
+ # `n` must be non-negative.
125
+ #
126
+ # @return [Integer]
127
+ # Cardinality of derangements set.
128
+ #
129
+ # @example
130
+ # subfactorial([1, 2, 3].size)
131
+ # # => 2
132
+ #
133
+ # @note The notation used in academia for subfactorial notation is "!n"
134
+ #
135
+ # @see http://mathworld.wolfram.com/Subfactorial.html
136
+ # @see Derange.cardinality
137
+ #
138
+ # @since 0.4.0
139
+ #
140
+ def Math.subfactorial(n)
141
+ if n >= 1 then ((Math.factorial(n) + 1) / Math::E).floor
142
+ elsif n == 0 then 1
143
+ else
144
+ raise(RangeError,"n must be non-negative")
145
+ end
146
+ end
147
+
148
+ #
149
+ # Apply the well-known factorial function to the given Integer.
150
+ #
151
+ # @param [Fixnum] x
152
+ # Positive integer to apply algebraic factorial function to.
153
+ #
154
+ # @return [Integer]
155
+ # Solution to factorial function as a whole number.
156
+ #
157
+ # @raise [RangeError]
158
+ # The given number must be non-negative.
159
+ #
160
+ # @example
161
+ # factorial(4)
162
+ # # => 24
163
+ #
164
+ # @note The factorial of zero equals one!
165
+ #
166
+ # @see http://en.wikipedia.org/wiki/Factorial
167
+ #
168
+ # @since 0.4.0
169
+ #
170
+ def Math.factorial(x=1)
171
+ if x >= 1 then pi(1..x)
172
+ elsif x == 0 then 1
173
+ else
174
+ raise(RangeError,"x must be non-negative")
175
+ end
176
+ end
177
+ end