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
@@ -1,4 +1,9 @@
|
|
1
|
-
require '
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'generator' # 1.8.7
|
5
|
+
rescue LoadError
|
6
|
+
end
|
2
7
|
|
3
8
|
module Combinatorics
|
4
9
|
# auto-detects the `Generator` class.
|
@@ -6,5 +11,7 @@ module Combinatorics
|
|
6
11
|
::Enumerator::Generator
|
7
12
|
elsif defined?(::Generator) # 1.8.7
|
8
13
|
::Generator
|
14
|
+
else
|
15
|
+
raise("unable to find the Generator class")
|
9
16
|
end
|
10
17
|
end
|
@@ -0,0 +1,98 @@
|
|
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 Permute
|
10
|
+
#
|
11
|
+
# Mathematically determine the number of elements in a r-permutations
|
12
|
+
# set.
|
13
|
+
#
|
14
|
+
# @param [Fixnum] n
|
15
|
+
# The number of elements in the input set.
|
16
|
+
#
|
17
|
+
# @param [Fixnum] r
|
18
|
+
# Cardinality of permuted subsets.
|
19
|
+
#
|
20
|
+
# @raise [RangeError]
|
21
|
+
# `n` must be non-negative.
|
22
|
+
#
|
23
|
+
# @raise [RangeError]
|
24
|
+
# `r` must be non-negative.
|
25
|
+
#
|
26
|
+
# @raise [RangeError]
|
27
|
+
# `r` must be less than or equal to `n`.
|
28
|
+
#
|
29
|
+
# @return [Fixnum]
|
30
|
+
# The product of the first `r` factors of `n`.
|
31
|
+
#
|
32
|
+
# @example Calculate total 4-permutations for a set whose cardinality is 6
|
33
|
+
# cardinality(6, 4)
|
34
|
+
# # => 360
|
35
|
+
#
|
36
|
+
# @see http://en.wikipedia.org/wiki/Permutations
|
37
|
+
#
|
38
|
+
# @note
|
39
|
+
# This function is well-known within fields of academic inquiry such as
|
40
|
+
# discrete mathematics and set theory. It is represented in "chalkboard"
|
41
|
+
# notation by the letter "P."
|
42
|
+
#
|
43
|
+
def self.cardinality(n,r=nil)
|
44
|
+
raise(RangeError,"n must be non-negative") if n < 0
|
45
|
+
|
46
|
+
case r
|
47
|
+
when 0 then 0
|
48
|
+
when nil then Math.factorial(n)
|
49
|
+
else
|
50
|
+
raise(RangeError,"r must be non-negative") if r < 0
|
51
|
+
raise(RangeError,"r must be less than or equal to n") if r > n
|
52
|
+
|
53
|
+
Math.factorial(n) / Math.factorial(n - r)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# @see cardinality
|
59
|
+
#
|
60
|
+
# @note In the study of set theory, permutations are often referenced by
|
61
|
+
# the name of an associated algorithm called "n-choose-r."
|
62
|
+
#
|
63
|
+
def self.N(n,r=nil); cardinality(n,r); end
|
64
|
+
def self.NR(n,r=nil); cardinality(n,r); end
|
65
|
+
def self.R(n,r=nil); cardinality(n,r); end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Compute cardinality of all r-permutations for a set with cardinality c
|
69
|
+
#
|
70
|
+
# @param [Fixnum] c
|
71
|
+
# Input set cardinality.
|
72
|
+
#
|
73
|
+
# @return [Array]
|
74
|
+
# Elements are cardinalities for each subset `1 .. c`.
|
75
|
+
#
|
76
|
+
# @raise [RangeError]
|
77
|
+
# `c` must be non-negative.
|
78
|
+
#
|
79
|
+
# @example cardinality_all(4)
|
80
|
+
# # => [4, 3, 10, 1]
|
81
|
+
#
|
82
|
+
# @note sum of elements will equal `factorial(c)`
|
83
|
+
#
|
84
|
+
# @see http://en.wikipedia.org/wiki/Permutations
|
85
|
+
#
|
86
|
+
def self.cardinality_all(n,c=(1..n))
|
87
|
+
if n < 0
|
88
|
+
raise(RangeError,"n must be non-negative")
|
89
|
+
end
|
90
|
+
|
91
|
+
c.map { |r| cardinality(n,r) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.N_all(c); cardinality_all(c); end
|
95
|
+
def self.NR_all(c); cardinality_all(c); end
|
96
|
+
def self.R_all(c); cardinality_all(c); end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Combinatorics
|
2
|
+
module Permute
|
3
|
+
#
|
4
|
+
# @author duper <super@manson.vistech.net>
|
5
|
+
#
|
6
|
+
# @since 0.4.0
|
7
|
+
#
|
8
|
+
module Mixin
|
9
|
+
#
|
10
|
+
# Enumerate distinct r-permutations for a particular sequence of
|
11
|
+
# elements.
|
12
|
+
#
|
13
|
+
# @param [Fixnum] r
|
14
|
+
# Length of permuted subsets to return.
|
15
|
+
#
|
16
|
+
# @yield [permutation]
|
17
|
+
# If a block is given, it will be passed each k-permutation.
|
18
|
+
#
|
19
|
+
# @yieldparam [Array] permutation
|
20
|
+
# A k-permutation of the elements from `self`.
|
21
|
+
#
|
22
|
+
# @return [Enumerator]
|
23
|
+
# If no block is given, an Enumerator of the k-permutations of
|
24
|
+
# elements from `self` is returned.
|
25
|
+
#
|
26
|
+
# @raise [TypeError]
|
27
|
+
# `self` must be Enumerable.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# [1, 2, 3].permute(2).to_a
|
31
|
+
# # => [[1, 2], [1, 3],
|
32
|
+
# # [2, 1], [2, 3],
|
33
|
+
# # [3, 1], [3, 2]]
|
34
|
+
#
|
35
|
+
# @see http://rubydoc.info/stdlib/core/Array#permutation-instance_method
|
36
|
+
#
|
37
|
+
def permute(r,&block)
|
38
|
+
return enum_for(:permute,r) unless block
|
39
|
+
|
40
|
+
unless kind_of?(Enumerable)
|
41
|
+
raise(TypeError,"#{inspect} must be Enumerable")
|
42
|
+
end
|
43
|
+
|
44
|
+
self.to_a.permutation(r,&block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'combinatorics/extensions/math'
|
2
|
+
|
3
|
+
module Combinatorics
|
4
|
+
module PowerSet
|
5
|
+
#
|
6
|
+
# Get number of elements in power set from number of elements in input
|
7
|
+
# set.
|
8
|
+
#
|
9
|
+
# @param [Fixnum] n
|
10
|
+
# Number of elements input set.
|
11
|
+
#
|
12
|
+
# @return [Fixnum]
|
13
|
+
# Number of elements in power set.
|
14
|
+
#
|
15
|
+
# @see Math::factorial
|
16
|
+
# @see http://en.wikipedia.org/wiki/Cardinality
|
17
|
+
#
|
18
|
+
# @note
|
19
|
+
# Cardinality of power set on an empty set equals `factorial(0)`
|
20
|
+
# equals 1.
|
21
|
+
#
|
22
|
+
def self.cardinality(n)
|
23
|
+
Math.factorial(n)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Wrapper function for power set cardinality method defined above
|
28
|
+
#
|
29
|
+
# @note The letter `P' stands for the power set function in the context of
|
30
|
+
# statements regarding discrete mathematics.
|
31
|
+
#
|
32
|
+
def self.P(n)
|
33
|
+
cardinality(n)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module Combinatorics
|
2
2
|
module PowerSet
|
3
|
+
#
|
4
|
+
# @author postmodern <postmodern.mod3@gmail.com>
|
5
|
+
#
|
3
6
|
module Mixin
|
4
7
|
#
|
5
8
|
# Calculates the power-set of an Enumerable object.
|
@@ -11,40 +14,34 @@ module Combinatorics
|
|
11
14
|
# @yieldparam [Array] subset
|
12
15
|
# A sub-set from the power-set.
|
13
16
|
#
|
14
|
-
# @return [
|
17
|
+
# @return [Enumerator]
|
15
18
|
# The power set.
|
16
19
|
#
|
17
|
-
# @example Power-set of an Array.
|
18
|
-
# [1,2,3].powerset
|
19
|
-
# # => [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
|
20
|
-
#
|
21
20
|
# @example Power-set on a Set of strings.
|
22
|
-
# Set['abc', 'xyz', '123'].powerset
|
23
|
-
# # => [#<Set: {}>,
|
24
|
-
# #<Set: {"
|
25
|
-
# #<Set: {"
|
21
|
+
# Set['abc', 'xyz', '123'].powerset.to_a
|
22
|
+
# # => [#<Set: {}>,
|
23
|
+
# #<Set: {"123"}>,
|
24
|
+
# #<Set: {"xyz"}>,
|
25
|
+
# #<Set: {"abc"}>,
|
26
|
+
# #<Set: {"xyz", "123"}>,
|
27
|
+
# #<Set: {"abc", "123"}>,
|
28
|
+
# #<Set: {"abc", "xyz"}>,
|
26
29
|
# #<Set: {"abc", "xyz", "123"}>]
|
27
30
|
#
|
28
|
-
# @see http://johncarrino.net/blog/2006/08/11/powerset-in-ruby/
|
29
|
-
#
|
30
31
|
def powerset
|
31
|
-
|
32
|
-
sub_set = []
|
32
|
+
return enum_for(:powerset) unless block_given?
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
yield new_set if block_given?
|
34
|
+
elements = self.to_a
|
35
|
+
elements.uniq!
|
37
36
|
|
38
|
-
|
39
|
-
|
37
|
+
0.upto(elements.length) do |k|
|
38
|
+
elements.combination(k) do |subset|
|
39
|
+
yield Set.new(subset)
|
40
40
|
end
|
41
|
-
|
42
|
-
sub_set
|
43
41
|
end
|
44
42
|
end
|
45
43
|
|
46
|
-
alias
|
47
|
-
|
44
|
+
alias power_set powerset
|
48
45
|
end
|
49
46
|
end
|
50
47
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'combinatorics/cartesian_product'
|
3
|
+
|
4
|
+
describe CartesianProduct do
|
5
|
+
subject { CartesianProduct }
|
6
|
+
|
7
|
+
describe "cardinality" do
|
8
|
+
it "should return 1 for cardinality(1, 1)" do
|
9
|
+
subject.cardinality(1, 1).should == 1
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return 2 for cardinality(1, 2)" do
|
13
|
+
subject.cardinality(1, 2).should == 2
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return 2 for cardinality(2, 1)" do
|
17
|
+
subject.cardinality(2, 1).should == 2
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return 4 for cardinality(2, 2)" do
|
21
|
+
subject.cardinality(2, 2).should == 4
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return 3 for cardinality(3, 1)" do
|
25
|
+
subject.cardinality(3, 1).should == 3
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return 3 for cardinality(1, 3)" do
|
29
|
+
subject.cardinality(1, 3).should == 3
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return 6 for cardinality(2, 3)" do
|
33
|
+
subject.cardinality(2, 3).should == 6
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return 6 for cardinality(3, 2)" do
|
37
|
+
subject.cardinality(3, 2).should == 6
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return 9 for cardinality(3, 3)" do
|
41
|
+
subject.cardinality(3, 3).should == 9
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise RangeError if c1 is negative" do
|
45
|
+
lambda { subject.cardinality(-1, 1) }.should raise_error(RangeError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should raise RangeError if c2 is negative" do
|
49
|
+
lambda { subject.cardinality(1, -1) }.should raise_error(RangeError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should raise RangeError if c1 is zero" do
|
53
|
+
lambda { subject.cardinality(0, 1) }.should raise_error(RangeError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise RangeError if c2 is zero" do
|
57
|
+
lambda { subject.cardinality(1, 0) }.should raise_error(RangeError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should wrap cardinality with CartesianProduct.X" do
|
62
|
+
should respond_to(:X)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'combinatorics/cartesian_product/mixin'
|
4
|
+
|
5
|
+
shared_examples_for "CartesianProduct::Mixin" do
|
6
|
+
it "the cartprod of any two Set's should return an Enumerator" do
|
7
|
+
set = subject[1]
|
8
|
+
results = set.cartprod(set)
|
9
|
+
|
10
|
+
results.should be_kind_of(Enumerator)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "the cartprod of two empty Set's should return an empty Set" do
|
14
|
+
set = subject[]
|
15
|
+
results = set.cartprod([]).to_a
|
16
|
+
|
17
|
+
results.should be_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
it "the cartprod of a single empty set should return an empty Set" do
|
21
|
+
set = subject[1,2]
|
22
|
+
results = set.cartprod([2,3],[]).to_a
|
23
|
+
|
24
|
+
results.should be_empty
|
25
|
+
end
|
26
|
+
|
27
|
+
it "the cartprod of another empty set should also return an empty Set" do
|
28
|
+
set = subject[]
|
29
|
+
results = set.cartprod([1]).to_a
|
30
|
+
|
31
|
+
results.should be_empty
|
32
|
+
end
|
33
|
+
|
34
|
+
it "the cartprod of [1] and [1] should be [[1, 1]]" do
|
35
|
+
set = subject[1]
|
36
|
+
results = set.cartprod([1]).to_a
|
37
|
+
|
38
|
+
results.should == [[1, 1]]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "the cartprod of [1, 2] and [3] should be [[1, 3], [2, 3]]" do
|
42
|
+
set = subject[1, 2]
|
43
|
+
results = set.cartprod([3]).to_a
|
44
|
+
|
45
|
+
results.should =~ [[1, 3], [2, 3]]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "the cartprod of [1, 2] and [3, 4] should be [[1, 3], [1, 4], [2, 3], [2, 4]]" do
|
49
|
+
set = subject[1, 2]
|
50
|
+
results = set.cartprod([3, 4]).to_a
|
51
|
+
|
52
|
+
results.should =~ [
|
53
|
+
[1, 3], [1, 4],
|
54
|
+
[2, 3], [2, 4]
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "the cartprod of ['a'].cartprod(['b', 'c', 'd']) should be [['a', 'b'], ['a', 'c'], ['a', 'd']]" do
|
59
|
+
set1 = subject['a']
|
60
|
+
set2 = subject['b', 'c', 'd']
|
61
|
+
results = set1.cartprod(set2).to_a
|
62
|
+
|
63
|
+
results.should =~ [['a', 'b'], ['a', 'c'], ['a', 'd']]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "the cartprod of [0, 1] and [[2, 3], [4, 5]] should be [[0, 2, 4], [1, 2, 4], [0, 3, 4], [1, 3, 4], [0, 2, 5], [1, 2, 5], [0, 3, 5], [1, 3, 5]]" do
|
67
|
+
set1 = subject[0, 1]
|
68
|
+
set2 = subject[2, 3]
|
69
|
+
set3 = subject[4, 5]
|
70
|
+
results = set1.cartprod(set2, set3).to_a
|
71
|
+
|
72
|
+
results.should =~ [
|
73
|
+
[0, 2, 4], [0, 2, 5], [0, 3, 4], [0, 3, 5],
|
74
|
+
[1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5],
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should take an optional block argument" do
|
79
|
+
set = subject[1]
|
80
|
+
results = []
|
81
|
+
|
82
|
+
set.cartprod(set) { |result| results << result }
|
83
|
+
|
84
|
+
results.should == [[1, 1]]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should alias cartprod to cartesian_product" do
|
88
|
+
aset = subject[1]
|
89
|
+
|
90
|
+
aset.should respond_to(:cartesian_product)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should alias cartprod to cartesian" do
|
94
|
+
aset = subject[1]
|
95
|
+
|
96
|
+
aset.should respond_to(:cartesian)
|
97
|
+
end
|
98
|
+
end
|