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
@@ -1,4 +1,9 @@
1
- require 'generator'
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,3 @@
1
+ require 'combinatorics/permute/cardinality'
2
+ require 'combinatorics/permute/mixin'
3
+ require 'combinatorics/permute/extensions'
@@ -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,2 @@
1
+ require 'combinatorics/permute/extensions/array'
2
+ require 'combinatorics/permute/extensions/set'
@@ -0,0 +1,7 @@
1
+ require 'combinatorics/permute/mixin'
2
+
3
+ class Array
4
+
5
+ include Combinatorics::Permute::Mixin
6
+
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'combinatorics/permute/mixin'
2
+
3
+ require 'set'
4
+
5
+ class Set
6
+
7
+ include Combinatorics::Permute::Mixin
8
+
9
+ 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
@@ -1,2 +1,3 @@
1
1
  require 'combinatorics/power_set/mixin'
2
2
  require 'combinatorics/power_set/extensions'
3
+ require 'combinatorics/power_set/cardinality'
@@ -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 [Array]
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: {}>, #<Set: {"123"}>, #<Set: {"xyz"}>,
24
- # #<Set: {"abc"}>, #<Set: {"xyz", "123"}>,
25
- # #<Set: {"abc", "123"}>, #<Set: {"abc", "xyz"}>,
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
- inject([self.class.new]) do |power_set,element|
32
- sub_set = []
32
+ return enum_for(:powerset) unless block_given?
33
33
 
34
- power_set.each do |previous_set|
35
- new_set = previous_set + [element]
36
- yield new_set if block_given?
34
+ elements = self.to_a
35
+ elements.uniq!
37
36
 
38
- sub_set << previous_set
39
- sub_set << new_set
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 cartesian_product powerset
47
-
44
+ alias power_set powerset
48
45
  end
49
46
  end
50
47
  end
@@ -1,4 +1,4 @@
1
1
  module Combinatorics
2
- # combinatorics version
3
- VERSION = '0.3.1'
2
+ # Combinatorics module revision number
3
+ VERSION = '0.4.1'
4
4
  end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'cartesian_product/mixin_examples'
3
+
4
+ require 'combinatorics/cartesian_product/extensions/array'
5
+
6
+ describe Array do
7
+ subject { Array }
8
+
9
+ it_should_behave_like "CartesianProduct::Mixin"
10
+ 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