more_math 1.8.0 → 1.10.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcc2d84dad36dba3b74a49a1de06431135c220cb4b027251c30fb828a8078bb0
4
- data.tar.gz: 075dcb1e3dbdd99870de43f04ae5d5481c8e02fa712fd70dab95c214e0d39da7
3
+ metadata.gz: b375aa122041b022a2c6b6457cbd6ba9a87691e75a816696c004b57824a5c50e
4
+ data.tar.gz: ea3ae210dc593238418ea80b9d8c86b156b7fd53be2971349cbda9985e59aee1
5
5
  SHA512:
6
- metadata.gz: 5a354f264cca954a2f5bb1633131117c533e02375b55281c5a070e60ab64439d9e6a50cf8a6b654004f5d0d565e2cd6179927b6987a8beb118093a70c66a4ee2
7
- data.tar.gz: 00ed6ef336035013ac18608cdcc1b52c4eb11947cc71f9d9bd5dce1c330be91d3669efb4b613930c0d9b74e227c02eef7d0aa501663c883b1f215bfecad6d00d
6
+ metadata.gz: e28aaa3aaf79ab42eac1fbf4c4a678d7676279ebf326fed10e8c094d0040ad1d664b8a5e9b137a68c25077496886036c67fc377cfb0e1f4027d2c3abaf2ccbc8
7
+ data.tar.gz: 8f8f81bd9beb6c9b7f040d58ac6a90acb3fe4a9394896cd4822c148f7ee362e9c0ee5004785953a9f292b7fac2cf23177676cd46ed17f344e19337399b473cd8
data/CHANGES.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-01-19 v1.10.0
4
+
5
+ - Added new `entropy_maximum` method to calculate theoretical maximum entropy
6
+ for a text given an alphabet size
7
+ - Made `size` parameter required in `entropy_ratio` and `entropy_ratio_minimum`
8
+ methods instead of defaulting to `text.size`
9
+ - Updated YARD documentation to clarify that `size` parameter represents
10
+ alphabet size
11
+ - Modified examples to use explicit alphabet sizes for better clarity
12
+ - All entropy methods now consistently return values in bits as expected for
13
+ Shannon entropy
14
+ - Updated documentation examples to use simplified method calls without
15
+ `MoreMath::Entropy` prefix
16
+ - Enhanced `entropy_maximum` method documentation to explain its use in
17
+ determining security strength for tokens
18
+ - Added comprehensive tests for `entropy_maximum` function covering edge cases
19
+ and various alphabet sizes
20
+ - Improved `entropy_maximum` method signature to return `0` for invalid
21
+ alphabet sizes (≤ 1) and use `Math.log2` for calculation
22
+ - Updated existing entropy method documentation to clarify it calculates
23
+ entropy in bits
24
+ - Simplified example code in documentation to use direct method calls instead
25
+ of module prefixes
26
+
27
+ ## 2026-01-16 v1.9.0
28
+
29
+ - Added support for array inputs in entropy calculation methods by checking
30
+ `text.respond_to?(:chars)` and using raw arrays when appropriate
31
+ - Added `MoreMath::Entropy.entropy_ratio_minimum` method to provide
32
+ conservative lower bound accounting for sampling error
33
+ - Updated `entropy_ratio` method to use `text.size` instead of
34
+ `text.each_char.size` for consistency
35
+ - Added comprehensive tests for new minimum entropy
36
+ ratio methods
37
+
3
38
  ## 2026-01-15 v1.8.0
4
39
 
5
40
  - Added tests for `entropy_ratio` and `lambert_w` method inclusion/extension
@@ -15,29 +15,34 @@ module MoreMath
15
15
  #
16
16
  # @example Basic usage
17
17
  # require 'more_math'
18
- # include MoreMath
18
+ # include MoreMath::Functions
19
19
  #
20
20
  # text = "hello world"
21
21
  # puts entropy(text) # => 2.3219280948873626
22
22
  # puts entropy_ratio(text) # => 0.7428571428571429
23
23
  #
24
24
  # @example Using with different text samples
25
- # MoreMath::Entropy.entropy("aaaa") # => 0.0 (no entropy)
26
- # MoreMath::Entropy.entropy("abcd") # => 2.0 (maximum entropy)
25
+ # entropy("aaaa") # => 0.0 (no entropy)
26
+ # entropy("abcd") # => 2.0 (actual entropy)
27
27
  module Entropy
28
- # Calculates the Shannon entropy of a text string.
28
+ # Calculates the Shannon entropy in bits of a text string.
29
29
  #
30
30
  # Shannon entropy measures the average amount of information (in bits) needed
31
31
  # to encode characters in the text based on their frequencies.
32
32
  #
33
33
  # @example
34
- # MoreMath::Entropy.entropy("hello") # => 2.3219280948873626
35
- # MoreMath::Entropy.entropy("aaaa") # => 0.0
34
+ # entropy("hello") # => 2.3219280948873626
35
+ # entropy("aaaa") # => 0.0
36
36
  #
37
37
  # @param text [String] The input text to calculate entropy for
38
38
  # @return [Float] The Shannon entropy in bits
39
39
  def entropy(text)
40
- chars = text.chars
40
+ chars = nil
41
+ if text.respond_to?(:chars)
42
+ chars = text.chars
43
+ else
44
+ chars = text
45
+ end
41
46
  size = chars.size
42
47
 
43
48
  chars.each_with_object(Hash.new(0.0)) { |c, h| h[c] += 1 }.
@@ -53,8 +58,8 @@ module MoreMath
53
58
  # alphabet have equal probability of occurrence.
54
59
  #
55
60
  # @example
56
- # MoreMath::Entropy.entropy_ideal(2) # => 1.0
57
- # MoreMath::Entropy.entropy_ideal(256) # => 8.0
61
+ # entropy_ideal(2) # => 1.0
62
+ # entropy_ideal(256) # => 8.0
58
63
  #
59
64
  # @param size [Integer] The number of unique characters in the alphabet
60
65
  # @return [Float] The maximum possible entropy in bits
@@ -64,7 +69,6 @@ module MoreMath
64
69
  -1.0 * size * frequency * Math.log2(frequency)
65
70
  end
66
71
 
67
-
68
72
  # Calculates the normalized entropy ratio of a text string.
69
73
  #
70
74
  # The ratio is calculated as actual entropy divided by ideal entropy,
@@ -76,23 +80,72 @@ module MoreMath
76
80
  # theoretical maximum entropy for that character set.
77
81
  #
78
82
  # @example
79
- # MoreMath::Entropy.entropy_ratio("hello") # => 0.6834
80
- # MoreMath::Entropy.entropy_ratio("aaaaa") # => 0.0
81
- # MoreMath::Entropy.entropy_ratio("abcde") # => 1.0
83
+ # entropy_ratio("hello") # => 0.6834
84
+ # entropy_ratio("aaaaa") # => 0.0
85
+ # entropy_ratio("abcde") # => 1.0
82
86
  #
83
87
  # @example With custom alphabet size
84
88
  # # Normalizing against a 26-letter alphabet (English)
85
- # MoreMath::Entropy.entropy_ratio("hello", size: 26) # => 0.394...
89
+ # entropy_ratio("hello", size: 26) # => 0.394...
86
90
  #
87
91
  # @param text [String] The input text to calculate entropy ratio for
88
- # @param size [Integer] The size of the character set to normalize against.
89
- # Defaults to the total length of the text (`text.each_char.size`), which
90
- # normalizes the entropy relative to the text's own character space.
91
- # This allows comparison of texts with different lengths on the same scale.
92
+ # @param size [Integer] The size of the character set to normalize against (alphabet size).
92
93
  # @return [Float] Normalized entropy ratio between 0 and 1
93
- def entropy_ratio(text, size: text.each_char.size)
94
+ def entropy_ratio(text, size:)
94
95
  size <= 1 and return 0.0
95
96
  entropy(text) / entropy_ideal(size)
96
97
  end
98
+
99
+ # Calculates the minimum entropy ratio with confidence interval adjustment
100
+ #
101
+ # This method computes a adjusted entropy ratio that accounts for
102
+ # statistical uncertainty by incorporating the standard error and a
103
+ # confidence level.
104
+ #
105
+ # @param text [String] The input text to calculate entropy ratio for
106
+ # @param size [Integer] The size of the character set to normalize against
107
+ # (alphabet size).
108
+ # @param alpha [Float] The significance level for the confidence interval (default: 0.05)
109
+ # @return [Float] The adjusted entropy ratio within the confidence interval
110
+ # @raise [ArgumentError] When alphabet size is less than 2
111
+ # @raise [ArgumentError] When text is empty
112
+ def entropy_ratio_minimum(text, size:, alpha: 0.05)
113
+ raise ArgumentError, 'alphabet size must be ≥ 2' if size < 2
114
+ raise ArgumentError, 'text must not be empty' if text.empty?
115
+
116
+ n = text.size
117
+ k = size
118
+
119
+ ratio = MoreMath::Functions.entropy_ratio(text, size: k)
120
+
121
+ logk = Math.log2(k)
122
+ diff = logk - 1.0 / Math.log(2)
123
+ var = (diff ** 2) / (logk ** 2) * (1.0 - 1.0 / k) / n
124
+ se = Math.sqrt(var) # standard error
125
+
126
+ z = STD_NORMAL_DISTRIBUTION.inverse_probability(1.0 - alpha / 2.0)
127
+
128
+ (ratio - z * se).clamp(0, 1)
129
+ end
130
+
131
+ # Calculates the maximum possible entropy for a given text and alphabet
132
+ # size.
133
+ #
134
+ # This represents the theoretical maximum entropy that could be achieved if
135
+ # all characters in the text were chosen uniformly at random from the
136
+ # alphabet. It's used to determine the upper bound of security strength for
137
+ # tokens.
138
+ #
139
+ # @example
140
+ # entropy_maximum("hello", size: 26) # => 23
141
+ # entropy_maximum("abc123", size: 64) # => 36
142
+ #
143
+ # @param text [String] The input text to calculate maximum entropy for
144
+ # @param size [Integer] The size of the character set (alphabet size)
145
+ # @return [Integer] The maximum possible entropy in bits, or 0 if size <= 1
146
+ def entropy_maximum(text, size:)
147
+ size > 1 or return 0
148
+ (text.size * Math.log2(size)).floor
149
+ end
97
150
  end
98
151
  end
@@ -1,6 +1,6 @@
1
1
  module MoreMath
2
2
  # MoreMath version
3
- VERSION = '1.8.0'
3
+ VERSION = '1.10.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/more_math.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: more_math 1.8.0 ruby lib
2
+ # stub: more_math 1.10.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "more_math".freeze
6
- s.version = "1.8.0".freeze
6
+ s.version = "1.10.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -12,14 +12,14 @@ Gem::Specification.new do |s|
12
12
  s.description = "Library that provides more mathematical functions/algorithms than standard Ruby.".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.extra_rdoc_files = ["README.md".freeze, "lib/more_math.rb".freeze, "lib/more_math/cantor_pairing_function.rb".freeze, "lib/more_math/constants/functions_constants.rb".freeze, "lib/more_math/continued_fraction.rb".freeze, "lib/more_math/distributions.rb".freeze, "lib/more_math/entropy.rb".freeze, "lib/more_math/exceptions.rb".freeze, "lib/more_math/functions.rb".freeze, "lib/more_math/histogram.rb".freeze, "lib/more_math/lambert.rb".freeze, "lib/more_math/linear_regression.rb".freeze, "lib/more_math/newton_bisection.rb".freeze, "lib/more_math/numberify_string_function.rb".freeze, "lib/more_math/permutation.rb".freeze, "lib/more_math/ranking_common.rb".freeze, "lib/more_math/sequence.rb".freeze, "lib/more_math/sequence/moving_average.rb".freeze, "lib/more_math/sequence/refinement.rb".freeze, "lib/more_math/string_numeral.rb".freeze, "lib/more_math/subset.rb".freeze, "lib/more_math/version.rb".freeze]
15
- s.files = ["CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "lib/more_math.rb".freeze, "lib/more_math/cantor_pairing_function.rb".freeze, "lib/more_math/constants/functions_constants.rb".freeze, "lib/more_math/continued_fraction.rb".freeze, "lib/more_math/distributions.rb".freeze, "lib/more_math/entropy.rb".freeze, "lib/more_math/exceptions.rb".freeze, "lib/more_math/functions.rb".freeze, "lib/more_math/histogram.rb".freeze, "lib/more_math/lambert.rb".freeze, "lib/more_math/linear_regression.rb".freeze, "lib/more_math/newton_bisection.rb".freeze, "lib/more_math/numberify_string_function.rb".freeze, "lib/more_math/permutation.rb".freeze, "lib/more_math/ranking_common.rb".freeze, "lib/more_math/sequence.rb".freeze, "lib/more_math/sequence/moving_average.rb".freeze, "lib/more_math/sequence/refinement.rb".freeze, "lib/more_math/string_numeral.rb".freeze, "lib/more_math/subset.rb".freeze, "lib/more_math/version.rb".freeze, "more_math.gemspec".freeze, "tests/cantor_pairing_function_test.rb".freeze, "tests/continued_fraction_test.rb".freeze, "tests/distribution_test.rb".freeze, "tests/entropy_test.rb".freeze, "tests/functions_test.rb".freeze, "tests/histogram_test.rb".freeze, "tests/lambert_test.rb".freeze, "tests/newton_bisection_test.rb".freeze, "tests/numberify_string_function_test.rb".freeze, "tests/permutation_test.rb".freeze, "tests/sequence/refinement_test.rb".freeze, "tests/sequence_moving_average_test.rb".freeze, "tests/sequence_test.rb".freeze, "tests/string_numeral_test.rb".freeze, "tests/subset_test.rb".freeze, "tests/test_helper.rb".freeze]
15
+ s.files = ["CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "lib/more_math.rb".freeze, "lib/more_math/cantor_pairing_function.rb".freeze, "lib/more_math/constants/functions_constants.rb".freeze, "lib/more_math/continued_fraction.rb".freeze, "lib/more_math/distributions.rb".freeze, "lib/more_math/entropy.rb".freeze, "lib/more_math/exceptions.rb".freeze, "lib/more_math/functions.rb".freeze, "lib/more_math/histogram.rb".freeze, "lib/more_math/lambert.rb".freeze, "lib/more_math/linear_regression.rb".freeze, "lib/more_math/newton_bisection.rb".freeze, "lib/more_math/numberify_string_function.rb".freeze, "lib/more_math/permutation.rb".freeze, "lib/more_math/ranking_common.rb".freeze, "lib/more_math/sequence.rb".freeze, "lib/more_math/sequence/moving_average.rb".freeze, "lib/more_math/sequence/refinement.rb".freeze, "lib/more_math/string_numeral.rb".freeze, "lib/more_math/subset.rb".freeze, "lib/more_math/version.rb".freeze, "more_math.gemspec".freeze, "tests/cantor_pairing_function_test.rb".freeze, "tests/continued_fraction_test.rb".freeze, "tests/distribution_test.rb".freeze, "tests/entropy_test.rb".freeze, "tests/functions_test.rb".freeze, "tests/histogram_test.rb".freeze, "tests/lambert_test.rb".freeze, "tests/newton_bisection_test.rb".freeze, "tests/numberify_string_function_test.rb".freeze, "tests/permutation_test.rb".freeze, "tests/sequence_moving_average_test.rb".freeze, "tests/sequence_refinement_test.rb".freeze, "tests/sequence_test.rb".freeze, "tests/string_numeral_test.rb".freeze, "tests/subset_test.rb".freeze, "tests/test_helper.rb".freeze]
16
16
  s.homepage = "https://github.com/flori/more_math".freeze
17
17
  s.licenses = ["MIT".freeze]
18
18
  s.rdoc_options = ["--title".freeze, "MoreMath -- More Math in Ruby".freeze, "--main".freeze, "README.md".freeze]
19
19
  s.required_ruby_version = Gem::Requirement.new(">= 2.0".freeze)
20
20
  s.rubygems_version = "4.0.3".freeze
21
21
  s.summary = "Library that provides more mathematics.".freeze
22
- s.test_files = ["tests/cantor_pairing_function_test.rb".freeze, "tests/continued_fraction_test.rb".freeze, "tests/distribution_test.rb".freeze, "tests/entropy_test.rb".freeze, "tests/functions_test.rb".freeze, "tests/histogram_test.rb".freeze, "tests/lambert_test.rb".freeze, "tests/newton_bisection_test.rb".freeze, "tests/numberify_string_function_test.rb".freeze, "tests/permutation_test.rb".freeze, "tests/sequence/refinement_test.rb".freeze, "tests/sequence_moving_average_test.rb".freeze, "tests/sequence_test.rb".freeze, "tests/string_numeral_test.rb".freeze, "tests/subset_test.rb".freeze, "tests/test_helper.rb".freeze]
22
+ s.test_files = ["tests/cantor_pairing_function_test.rb".freeze, "tests/continued_fraction_test.rb".freeze, "tests/distribution_test.rb".freeze, "tests/entropy_test.rb".freeze, "tests/functions_test.rb".freeze, "tests/histogram_test.rb".freeze, "tests/lambert_test.rb".freeze, "tests/newton_bisection_test.rb".freeze, "tests/numberify_string_function_test.rb".freeze, "tests/permutation_test.rb".freeze, "tests/sequence_moving_average_test.rb".freeze, "tests/sequence_refinement_test.rb".freeze, "tests/sequence_test.rb".freeze, "tests/string_numeral_test.rb".freeze, "tests/subset_test.rb".freeze, "tests/test_helper.rb".freeze]
23
23
 
24
24
  s.specification_version = 4
25
25
 
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'test_helper'
4
+ require 'tins'
4
5
 
5
6
  class EntropyTest < Test::Unit::TestCase
6
7
  include MoreMath::Functions
@@ -11,14 +12,16 @@ class EntropyTest < Test::Unit::TestCase
11
12
  @string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
12
13
  @high = 'The quick brown fox jumps over the lazy dog'
13
14
  @random = "\xAC-\x8A\xF5\xA8\xF7\\\e\xB5\x8CI\x06\xA7"
15
+ @hi = "こんにちは世界"
14
16
  end
15
17
 
16
18
  def test_entropy
17
19
  assert_equal 0, entropy(@empty)
18
20
  assert_equal 0, entropy(@low)
19
- assert_in_delta 3.9514, entropy(@string), 1E-3
20
- assert_in_delta 4.4319, entropy(@high), 1E-3
21
+ assert_in_delta 3.951, entropy(@string), 1E-3
22
+ assert_in_delta 4.431, entropy(@high), 1E-3
21
23
  assert_in_delta 3.700, entropy(@random), 1E-3
24
+ assert_in_delta 2.807, entropy(@hi), 1E-3
22
25
  end
23
26
 
24
27
  def test_entropy_ideal
@@ -26,18 +29,52 @@ class EntropyTest < Test::Unit::TestCase
26
29
  assert_equal 0, entropy_ideal(0)
27
30
  assert_equal 0, entropy_ideal(0.5)
28
31
  assert_equal 0, entropy_ideal(1)
29
- assert_in_delta 1, entropy_ideal(2), 1E-3
32
+ assert_in_delta 1, entropy_ideal(2), 1E-3
30
33
  assert_in_delta 1.584, entropy_ideal(3), 1E-3
31
- assert_in_delta 3, entropy_ideal(8), 1E-3
34
+ assert_in_delta 3, entropy_ideal(8), 1E-3
32
35
  assert_in_delta 3.321, entropy_ideal(10), 1E-3
33
- assert_in_delta 4, entropy_ideal(16), 1E-3
36
+ assert_in_delta 4, entropy_ideal(16), 1E-3
37
+ end
38
+
39
+ def test_entropy_mamxium
40
+ text = 'A' * 64
41
+ assert_equal 0, entropy_maximum(text, size: -1)
42
+ assert_equal 0, entropy_maximum(text, size: 0)
43
+ assert_equal 0, entropy_maximum(text, size: 1)
44
+ assert_equal 64, entropy_maximum(text, size: 2)
45
+ assert_equal 256, entropy_maximum(text, size: 16)
46
+ assert_equal 128, entropy_maximum(text[0, 32], size: 16)
34
47
  end
35
48
 
36
49
  def test_entropy_ratio
37
- assert_equal 0, entropy_ratio(@empty)
38
- assert_equal 0, entropy_ratio(@low)
39
- assert_in_delta 0.6834, entropy_ratio(@string), 1E-3
40
- assert_in_delta 0.8167, entropy_ratio(@high), 1E-3
41
- assert_in_delta 1.0, entropy_ratio(@random), 1E-3
50
+ assert_equal 0, entropy_ratio(@empty, size: 128)
51
+ assert_equal 0, entropy_ratio(@low, size: 128)
52
+ assert_in_delta 0.564, entropy_ratio(@string, size: 128), 1E-3
53
+ assert_in_delta 0.633, entropy_ratio(@high, size: 128), 1E-3
54
+ assert_in_delta 1.0, entropy_ratio(@random, size: @random.size), 1E-3
55
+ assert_in_delta 0.462, entropy_ratio(@random, size: 256), 1E-3
56
+ assert_in_delta 0.253, entropy_ratio(@hi, size: 2_136), 1E-3
57
+ end
58
+
59
+ def test_entropy_ratio_minimum_basic
60
+ # A fairly long random token over a 16‑symbol alphabet
61
+ token = Tins::Token.new(length: 128, alphabet: Tins::Token::BASE16_LOWERCASE_ALPHABET)
62
+
63
+ limit = entropy_ratio_minimum(token, size: 16)
64
+
65
+ # Bounds must be ≧ 0
66
+ assert_operator limit, :>=, 0.0
67
+
68
+ # The observed ratio should be ≧ limit
69
+ ratio = entropy_ratio(token, size: 16)
70
+ assert_operator ratio, :>=, limit
71
+ end
72
+
73
+ def test_entropy_ratio_minimum_small
74
+ # Very short string – the interval will stay below 1.0
75
+ str = 'a' # alphabet size 2 (binary)
76
+ limit = entropy_ratio_minimum(str, size: 2)
77
+
78
+ assert_equal 0.0, limit
42
79
  end
43
80
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: more_math
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -201,8 +201,8 @@ files:
201
201
  - tests/newton_bisection_test.rb
202
202
  - tests/numberify_string_function_test.rb
203
203
  - tests/permutation_test.rb
204
- - tests/sequence/refinement_test.rb
205
204
  - tests/sequence_moving_average_test.rb
205
+ - tests/sequence_refinement_test.rb
206
206
  - tests/sequence_test.rb
207
207
  - tests/string_numeral_test.rb
208
208
  - tests/subset_test.rb
@@ -243,8 +243,8 @@ test_files:
243
243
  - tests/newton_bisection_test.rb
244
244
  - tests/numberify_string_function_test.rb
245
245
  - tests/permutation_test.rb
246
- - tests/sequence/refinement_test.rb
247
246
  - tests/sequence_moving_average_test.rb
247
+ - tests/sequence_refinement_test.rb
248
248
  - tests/sequence_test.rb
249
249
  - tests/string_numeral_test.rb
250
250
  - tests/subset_test.rb