combinatorics 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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