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.
- data/.gemtest +0 -0
- data/.gitignore +8 -0
- data/Benchmarks.md +257 -26
- data/ChangeLog.md +12 -0
- data/LICENSE.txt +1 -2
- data/README.md +102 -32
- data/Rakefile +13 -2
- data/benchmarks/cartesian_product.rb +18 -0
- data/benchmarks/choose.rb +19 -0
- data/benchmarks/derange.rb +18 -0
- data/benchmarks/list_comprehension.rb +2 -5
- data/benchmarks/permute.rb +18 -0
- data/benchmarks/power_set.rb +18 -0
- data/combinatorics.gemspec +124 -7
- data/gemspec.yml +11 -6
- data/lib/combinatorics.rb +7 -0
- data/lib/combinatorics/cartesian_product.rb +3 -0
- data/lib/combinatorics/cartesian_product/cardinality.rb +45 -0
- data/lib/combinatorics/cartesian_product/extensions.rb +2 -0
- data/lib/combinatorics/cartesian_product/extensions/array.rb +7 -0
- data/lib/combinatorics/cartesian_product/extensions/set.rb +9 -0
- data/lib/combinatorics/cartesian_product/mixin.rb +57 -0
- data/lib/combinatorics/choose.rb +3 -0
- data/lib/combinatorics/choose/cardinality.rb +99 -0
- data/lib/combinatorics/choose/extensions.rb +2 -0
- data/lib/combinatorics/choose/extensions/array.rb +5 -0
- data/lib/combinatorics/choose/extensions/set.rb +6 -0
- data/lib/combinatorics/choose/mixin.rb +53 -0
- data/lib/combinatorics/derange.rb +3 -0
- data/lib/combinatorics/derange/cardinality.rb +23 -0
- data/lib/combinatorics/derange/extensions.rb +1 -0
- data/lib/combinatorics/derange/extensions/array.rb +5 -0
- data/lib/combinatorics/derange/mixin.rb +47 -0
- data/lib/combinatorics/enumerator.rb +2 -0
- data/lib/combinatorics/extensions/math.rb +177 -0
- data/lib/combinatorics/generator.rb +8 -1
- data/lib/combinatorics/permute.rb +3 -0
- data/lib/combinatorics/permute/cardinality.rb +98 -0
- data/lib/combinatorics/permute/extensions.rb +2 -0
- data/lib/combinatorics/permute/extensions/array.rb +7 -0
- data/lib/combinatorics/permute/extensions/set.rb +9 -0
- data/lib/combinatorics/permute/mixin.rb +48 -0
- data/lib/combinatorics/power_set.rb +1 -0
- data/lib/combinatorics/power_set/cardinality.rb +36 -0
- data/lib/combinatorics/power_set/mixin.rb +19 -22
- data/lib/combinatorics/version.rb +2 -2
- data/spec/cartesian_product/array_spec.rb +10 -0
- data/spec/cartesian_product/cardinality_spec.rb +64 -0
- data/spec/cartesian_product/mixin_examples.rb +98 -0
- data/spec/cartesian_product/set_spec.rb +10 -0
- data/spec/choose/array_spec.rb +9 -0
- data/spec/choose/cardinality_spec.rb +132 -0
- data/spec/choose/mixin_examples.rb +48 -0
- data/spec/choose/set_spec.rb +9 -0
- data/spec/derange/array_spec.rb +10 -0
- data/spec/derange/cardinality_spec.rb +14 -0
- data/spec/derange/mixin_examples.rb +52 -0
- data/spec/extensions/math_spec.rb +100 -0
- data/spec/extensions/range_spec.rb +1 -1
- data/spec/permute/array_spec.rb +10 -0
- data/spec/permute/cardinality_spec.rb +146 -0
- data/spec/permute/mixin_examples.rb +42 -0
- data/spec/permute/set_spec.rb +10 -0
- data/spec/power_set/array_spec.rb +3 -2
- data/spec/power_set/cardinality_spec.rb +32 -0
- data/spec/power_set/mixin_examples.rb +17 -8
- data/spec/power_set/set_spec.rb +3 -2
- data/spec/spec_helper.rb +5 -3
- metadata +114 -95
@@ -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,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,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,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
|
@@ -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
|