fat_core 6.0.0 → 7.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +54 -0
- data/.rubocop.yml +4 -2
- data/.simplecov +2 -0
- data/CHANGELOG.org +61 -0
- data/Gemfile +5 -4
- data/README.org +1023 -58
- data/Rakefile +16 -3
- data/bin/console +1 -0
- data/fat_core.gemspec +6 -5
- data/lib/fat_core/all.rb +9 -10
- data/lib/fat_core/array.rb +5 -5
- data/lib/fat_core/enumerable.rb +0 -15
- data/lib/fat_core/hash.rb +48 -11
- data/lib/fat_core/numeric.rb +48 -1
- data/lib/fat_core/range.rb +108 -55
- data/lib/fat_core/string.rb +78 -38
- data/lib/fat_core/symbol.rb +1 -1
- data/lib/fat_core/version.rb +3 -3
- data/lib/fat_core.rb +2 -0
- data/spec/lib/array_spec.rb +2 -0
- data/spec/lib/big_decimal_spec.rb +2 -0
- data/spec/lib/enumerable_spec.rb +29 -39
- data/spec/lib/hash_spec.rb +19 -2
- data/spec/lib/nil_class_spec.rb +2 -0
- data/spec/lib/numeric_spec.rb +14 -0
- data/spec/lib/range_spec.rb +10 -0
- data/spec/lib/string_spec.rb +96 -12
- data/spec/lib/symbol_spec.rb +2 -0
- data/spec/spec_helper.rb +3 -1
- metadata +13 -10
- data/lib/fat_core/kernel.rb +0 -26
- data/spec/lib/kernel_spec.rb +0 -13
data/Rakefile
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'bundler/gem_tasks'
|
|
2
4
|
require 'rspec/core/rake_task'
|
|
3
5
|
require 'rdoc/task'
|
|
@@ -19,14 +21,25 @@ YARD::Rake::YardocTask.new do |t|
|
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
RSpec::Core::RakeTask.new(:spec, :tag) do |t|
|
|
22
|
-
t.rspec_opts = '--tag ~online -f
|
|
24
|
+
t.rspec_opts = '--tag ~online -f d'
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
########################################################################
|
|
26
28
|
# Rubocop tasks
|
|
27
29
|
########################################################################
|
|
28
|
-
|
|
30
|
+
# Option A (recommended): Keep using Bundler and run rubocop via `bundle exec`.
|
|
31
|
+
# This wrapper task ensures the rubocop run uses the gems from your Gemfile,
|
|
32
|
+
# even when you invoke `rake rubocop` (no need to remember `bundle exec rake`).
|
|
33
|
+
#
|
|
34
|
+
# You can pass extra RuboCop CLI flags with the RUBOCOP_OPTS environment variable:
|
|
35
|
+
# RUBOCOP_OPTS="--format simple" rake rubocop
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
desc "Run rubocop under `bundle exec`"
|
|
38
|
+
task :rubocop do
|
|
39
|
+
opts = (ENV['RUBOCOP_OPTS'] || '').split
|
|
40
|
+
Bundler.with_unbundled_env do
|
|
41
|
+
sh 'bundle', 'exec', 'rubocop', *opts
|
|
42
|
+
end
|
|
43
|
+
end
|
|
31
44
|
|
|
32
45
|
task :default => [:spec, :rubocop]
|
data/bin/console
CHANGED
data/fat_core.gemspec
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require_relative 'lib/fat_core/version'
|
|
4
5
|
|
|
@@ -9,15 +10,15 @@ Gem::Specification.new do |spec|
|
|
|
9
10
|
spec.email = ['ded@ddoherty.net']
|
|
10
11
|
spec.summary = 'some useful core extensions'
|
|
11
12
|
spec.description = <<~DESC
|
|
12
|
-
Useful extensions to
|
|
13
|
-
including useful Date extensions for dealing with US Federal
|
|
14
|
-
and New York Stock Exchange holidays and working days, a useful
|
|
13
|
+
Useful extensions to Enumerable and String, Range and other classes. A
|
|
15
14
|
Enumerable#each_with_flags for flagging first and last items in the
|
|
16
|
-
iteration,
|
|
15
|
+
iteration, operations on Ranges for testing contiguity, gaps in sets of
|
|
16
|
+
Ranges, and performing set operations on Ranges. String#fuzzy_match
|
|
17
|
+
for user-friendly matchers to test for matching.
|
|
17
18
|
DESC
|
|
18
19
|
spec.homepage = 'https://github.com/ddoherty03/fat_core.git'
|
|
19
20
|
spec.license = 'MIT'
|
|
20
|
-
spec.required_ruby_version = '>=
|
|
21
|
+
spec.required_ruby_version = '>= 3.2.2'
|
|
21
22
|
spec.metadata['yard.run'] = 'yri' # use "yard" to build full HTML docs.
|
|
22
23
|
|
|
23
24
|
spec.files = %x[git ls-files -z].split("\x0")
|
data/lib/fat_core/all.rb
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
require 'fat_core/symbol'
|
|
3
|
+
require_relative 'array'
|
|
4
|
+
require_relative 'bigdecimal'
|
|
5
|
+
require_relative 'enumerable'
|
|
6
|
+
require_relative 'hash'
|
|
7
|
+
require_relative 'nil'
|
|
8
|
+
require_relative 'numeric'
|
|
9
|
+
require_relative 'range'
|
|
10
|
+
require_relative 'string'
|
|
11
|
+
require_relative 'symbol'
|
data/lib/fat_core/array.rb
CHANGED
|
@@ -21,10 +21,10 @@ module FatCore
|
|
|
21
21
|
result
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# Return an Array that is the difference between this Array and +other+,
|
|
25
|
-
# without removing duplicates as the Array#- method does. All items of
|
|
26
|
-
# Array are included in the result unless they also appear in
|
|
27
|
-
#
|
|
24
|
+
# Return an Array that is the difference between this Array and +other+,
|
|
25
|
+
# but without removing duplicates as the Array#- method does. All items of
|
|
26
|
+
# this Array are included in the result unless they also appear in any of
|
|
27
|
+
# the +other+ Arrays.
|
|
28
28
|
def diff_with_dups(*others)
|
|
29
29
|
result = []
|
|
30
30
|
each do |itm|
|
|
@@ -35,7 +35,7 @@ module FatCore
|
|
|
35
35
|
|
|
36
36
|
# Convert this array into a single string by (1) applying #to_s to each
|
|
37
37
|
# element and (2) joining the elements with the string given by the sep:
|
|
38
|
-
#
|
|
38
|
+
# parameter. By default the sep parameter is ', '. You may use a different
|
|
39
39
|
# separation string in the case when there are only two items in the list
|
|
40
40
|
# by supplying a two_sep parameter. You may also supply a difference
|
|
41
41
|
# separation string to separate the second-last and last items in the
|
data/lib/fat_core/enumerable.rb
CHANGED
|
@@ -2,21 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# Useful extensions to the core Enumerable module.
|
|
4
4
|
module Enumerable
|
|
5
|
-
# Yield items in groups of n, for each group yield the group number, starting
|
|
6
|
-
# with zero and an Array of n items, or all remaining items if less than n.
|
|
7
|
-
#
|
|
8
|
-
# ('a'..'z').to_a.groups_of(5) do |k, grp|
|
|
9
|
-
# # On each iteration, grp is an Array of the next 5 items except the
|
|
10
|
-
# # last group, which contains only ['z'].
|
|
11
|
-
# end
|
|
12
|
-
def groups_of(num)
|
|
13
|
-
k = -1
|
|
14
|
-
group_by do
|
|
15
|
-
k += 1
|
|
16
|
-
k.div(num)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
5
|
# Yield each item together with two booleans that indicate whether the item is
|
|
21
6
|
# the first or last item in the Enumerable.
|
|
22
7
|
#
|
data/lib/fat_core/hash.rb
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
# require 'fat_core/hash'
|
|
11
11
|
# ```
|
|
12
12
|
#
|
|
13
|
+
|
|
14
|
+
require_relative 'enumerable'
|
|
15
|
+
|
|
13
16
|
module FatCore
|
|
14
17
|
# It provides a couple of methods for manipulating the keys of a Hash:
|
|
15
18
|
# `#remap_keys` for translating the current set of keys to a new set provided by
|
|
@@ -59,23 +62,50 @@ module FatCore
|
|
|
59
62
|
|
|
60
63
|
# @group Deletion
|
|
61
64
|
#
|
|
62
|
-
# Remove from the hash all keys that have values == to given
|
|
63
|
-
#
|
|
65
|
+
# Remove from the hash in place all keys that have values == to the given
|
|
66
|
+
# value or values.
|
|
64
67
|
#
|
|
65
68
|
# @example
|
|
66
69
|
# h = { a: 1, b: 2, c: 3, d: 2, e: 1 }
|
|
67
|
-
# h.delete_with_value(2)
|
|
68
|
-
# h
|
|
70
|
+
# h.delete_with_value!(2)
|
|
71
|
+
# h #=> { a: 1, c: 3, e: 1 }
|
|
72
|
+
# h.delete_with_value!(1, 3)
|
|
73
|
+
# h #=> { b: 2, d: 2 }
|
|
69
74
|
#
|
|
70
75
|
# @param val [Object, Enumerable<Object>] value to test for
|
|
71
76
|
# @return [Hash] hash having entries === v or including v deleted
|
|
72
|
-
def delete_with_value(val)
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
def delete_with_value!(*val)
|
|
78
|
+
val.each do |v|
|
|
79
|
+
keys_with_value(v).each do |k|
|
|
80
|
+
delete(k)
|
|
81
|
+
end
|
|
75
82
|
end
|
|
76
83
|
self
|
|
77
84
|
end
|
|
78
85
|
|
|
86
|
+
#
|
|
87
|
+
# Return a copy of the Hash a Hash with all keys that have values == to the
|
|
88
|
+
# given value or values.
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# h = { a: 1, b: 2, c: 3, d: 2, e: 1 }
|
|
92
|
+
# h.delete_with_value(2) #=> { a: 1, c: 3, e: 1 }
|
|
93
|
+
# h => { a: 1, b: 2, c: 3, d: 2, e: 1 }
|
|
94
|
+
# h.delete_with_value(1, 3) #=> { b: 2, d: 2 }
|
|
95
|
+
# h => { a: 1, b: 2, c: 3, d: 2, e: 1 }
|
|
96
|
+
#
|
|
97
|
+
# @param val [Object, Enumerable<Object>] value to test for
|
|
98
|
+
# @return [Hash] hash having entries === v or including v deleted
|
|
99
|
+
def delete_with_value(*val)
|
|
100
|
+
hsh = clone
|
|
101
|
+
val.each do |v|
|
|
102
|
+
hsh.keys_with_value(v).each do |k|
|
|
103
|
+
hsh.delete(k)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
hsh
|
|
107
|
+
end
|
|
108
|
+
|
|
79
109
|
# @group Key Manipulation
|
|
80
110
|
#
|
|
81
111
|
# Return all keys in hash that have a value == to the given value or have an
|
|
@@ -88,10 +118,12 @@ module FatCore
|
|
|
88
118
|
#
|
|
89
119
|
# @param val [Object, Enumerable<Object>] value to test for
|
|
90
120
|
# @return [Array<Object>] the keys with value or values v
|
|
91
|
-
def keys_with_value(
|
|
121
|
+
def keys_with_value(*vals)
|
|
92
122
|
keys = []
|
|
93
|
-
|
|
94
|
-
|
|
123
|
+
vals.each do |val|
|
|
124
|
+
each_pair do |k, v|
|
|
125
|
+
keys << k if self[k] == val || (v.respond_to?(:include?) && v.include?(val))
|
|
126
|
+
end
|
|
95
127
|
end
|
|
96
128
|
keys
|
|
97
129
|
end
|
|
@@ -136,7 +168,12 @@ module FatCore
|
|
|
136
168
|
end
|
|
137
169
|
|
|
138
170
|
def <<(other)
|
|
139
|
-
|
|
171
|
+
case other
|
|
172
|
+
when Hash
|
|
173
|
+
merge(other)
|
|
174
|
+
when Enumerable
|
|
175
|
+
merge(other.flatten.each_slice(2).to_h)
|
|
176
|
+
end
|
|
140
177
|
end
|
|
141
178
|
end
|
|
142
179
|
end
|
data/lib/fat_core/numeric.rb
CHANGED
|
@@ -153,7 +153,54 @@ module FatCore
|
|
|
153
153
|
# Quote self for use in TeX documents. Since number components are not
|
|
154
154
|
# special to TeX, this just applies `#to_s`
|
|
155
155
|
def tex_quote
|
|
156
|
-
|
|
156
|
+
case self
|
|
157
|
+
when Float
|
|
158
|
+
if self == Float::INFINITY
|
|
159
|
+
"$\\infty$"
|
|
160
|
+
elsif self == -Float::INFINITY
|
|
161
|
+
"$-\\infty$"
|
|
162
|
+
elsif self == Math::PI
|
|
163
|
+
"$\\pi$"
|
|
164
|
+
elsif self == Math::E
|
|
165
|
+
"$e$"
|
|
166
|
+
else
|
|
167
|
+
to_s.tex_quote
|
|
168
|
+
end
|
|
169
|
+
when Rational
|
|
170
|
+
"$\\frac{#{numerator}}{#{denominator}}$"
|
|
171
|
+
when Complex
|
|
172
|
+
if imaginary.zero?
|
|
173
|
+
real.int_if_whole.tex_quote
|
|
174
|
+
elsif imaginary == 1.0
|
|
175
|
+
if real == Math::PI
|
|
176
|
+
"$\\pi+i$"
|
|
177
|
+
elsif real == Math::E
|
|
178
|
+
"$e+i$"
|
|
179
|
+
else
|
|
180
|
+
"$#{real.int_if_whole}+i$"
|
|
181
|
+
end
|
|
182
|
+
elsif imaginary == Math::PI
|
|
183
|
+
if real == Math::PI
|
|
184
|
+
"$\\pi+\\pi i$"
|
|
185
|
+
elsif real == Math::E
|
|
186
|
+
"$e+\\pi i$"
|
|
187
|
+
else
|
|
188
|
+
"$#{real.int_if_whole}+\\pi i$"
|
|
189
|
+
end
|
|
190
|
+
elsif imaginary == Math::E
|
|
191
|
+
if real == Math::PI
|
|
192
|
+
"$\\pi+e i$"
|
|
193
|
+
elsif real == Math::E
|
|
194
|
+
"$e+e i$"
|
|
195
|
+
else
|
|
196
|
+
"$#{real.int_if_whole}+e i$"
|
|
197
|
+
end
|
|
198
|
+
else
|
|
199
|
+
"$#{real.int_if_whole}+#{imaginary.int_if_whole}i$"
|
|
200
|
+
end
|
|
201
|
+
else
|
|
202
|
+
to_s.tex_quote
|
|
203
|
+
end
|
|
157
204
|
end
|
|
158
205
|
end
|
|
159
206
|
end
|
data/lib/fat_core/range.rb
CHANGED
|
@@ -12,8 +12,13 @@
|
|
|
12
12
|
# coverage,
|
|
13
13
|
# 5. provide a definition for sorting Ranges based on sorting by the min values
|
|
14
14
|
# and sizes of the Ranges.
|
|
15
|
+
|
|
16
|
+
require_relative 'string'
|
|
17
|
+
|
|
15
18
|
module FatCore
|
|
16
19
|
module Range
|
|
20
|
+
using StringPred
|
|
21
|
+
|
|
17
22
|
# @group Operations
|
|
18
23
|
|
|
19
24
|
# Return a range that concatenates this range with other if it is contiguous
|
|
@@ -29,9 +34,9 @@ module FatCore
|
|
|
29
34
|
# @see contiguous? For the definition of "contiguous"
|
|
30
35
|
def join(other)
|
|
31
36
|
if left_contiguous?(other)
|
|
32
|
-
::Range.new(min, other.max)
|
|
33
|
-
elsif right_contiguous?(other)
|
|
34
37
|
::Range.new(other.min, max)
|
|
38
|
+
elsif right_contiguous?(other)
|
|
39
|
+
::Range.new(min, other.max)
|
|
35
40
|
end
|
|
36
41
|
end
|
|
37
42
|
|
|
@@ -53,30 +58,48 @@ module FatCore
|
|
|
53
58
|
# @param ranges [Array<Range>]
|
|
54
59
|
# @return [Array<Range>]
|
|
55
60
|
def gaps(ranges)
|
|
56
|
-
if ranges.empty?
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
61
|
+
return [clone] if ranges.empty?
|
|
62
|
+
|
|
63
|
+
msg = "#{ranges.first.min.class} range incompatible with #{min.class} Range"
|
|
64
|
+
raise ArgumentError, msg unless compatible?(ranges)
|
|
65
|
+
|
|
66
|
+
return [] if spanned_by?(ranges)
|
|
67
|
+
|
|
68
|
+
ranges = ranges.select { |r| r.overlaps?(self) }.sort
|
|
69
|
+
self_is_continuous = ranges.map(&:minmax).flatten.any? { |p| !p.respond_to?(:succ) }
|
|
70
|
+
gaps = []
|
|
71
|
+
cur_point = min
|
|
72
|
+
ranges.each do |rr|
|
|
73
|
+
# Loop Invariant: cur_point is the last element in the self Range
|
|
74
|
+
# NOT covered by the given ranges or the gaps so far.
|
|
75
|
+
break if cur_point == max
|
|
76
|
+
|
|
77
|
+
if (self_is_continuous && rr.min > cur_point) ||
|
|
78
|
+
(!self_is_continuous && rr.min > cur_point)
|
|
79
|
+
# There is a gap between the cur_point within self and the start
|
|
80
|
+
# of this range, rr, so we need to record it.
|
|
81
|
+
start_point = cur_point
|
|
82
|
+
end_point = self_is_continuous ? rr.min : rr.min.pred
|
|
83
|
+
gaps << (start_point..end_point)
|
|
76
84
|
end
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
cur_point =
|
|
86
|
+
if rr.max.is_a?(String) && rr.max[-1].match?(/[Zz9]/)
|
|
87
|
+
# This is a real kludge that stems from the fact that 'z'.succ <
|
|
88
|
+
# 'z', so the test for gaps at the end of the self range
|
|
89
|
+
# believes there is a gap when there is none. This ensures that
|
|
90
|
+
# cur_point is set to something > rr.max when it is one of the
|
|
91
|
+
# problematic strings ending in 'Z', 'z', or '9', all of whose
|
|
92
|
+
# successors sort less than them.
|
|
93
|
+
rr.max + rr.max[-1]
|
|
94
|
+
else
|
|
95
|
+
self_is_continuous ? rr.max : rr.max.succ
|
|
96
|
+
end
|
|
79
97
|
end
|
|
98
|
+
# Add any gap between the last of the ranges and the end of self.
|
|
99
|
+
if cur_point <= max
|
|
100
|
+
gaps << (cur_point..max)
|
|
101
|
+
end
|
|
102
|
+
gaps
|
|
80
103
|
end
|
|
81
104
|
|
|
82
105
|
# Within this range return an Array of Ranges representing the overlaps
|
|
@@ -90,31 +113,34 @@ module FatCore
|
|
|
90
113
|
# @param ranges [Array<Range>] ranges to search for overlaps
|
|
91
114
|
# @return [Array<Range>] overlaps with ranges but inside this Range
|
|
92
115
|
def overlaps(ranges)
|
|
93
|
-
if ranges.empty?
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if rr.min < cur_point
|
|
110
|
-
start_point = rr.min
|
|
111
|
-
end_point = cur_point
|
|
112
|
-
overlaps << (start_point..end_point)
|
|
113
|
-
end
|
|
116
|
+
return [] if ranges.empty?
|
|
117
|
+
|
|
118
|
+
msg = "#{ranges.first.min.class} range incompatible with #{min.class} Range"
|
|
119
|
+
raise ArgumentError, msg unless compatible?(ranges)
|
|
120
|
+
|
|
121
|
+
return [] if spanned_by?(ranges)
|
|
122
|
+
|
|
123
|
+
ranges = ranges.sort_by(&:min)
|
|
124
|
+
overlaps = []
|
|
125
|
+
cur_point = nil
|
|
126
|
+
ranges.each do |rr|
|
|
127
|
+
# Skip ranges outside of self
|
|
128
|
+
next if rr.max < min || rr.min > max
|
|
129
|
+
|
|
130
|
+
# Initialize cur_point to max of first range
|
|
131
|
+
if cur_point.nil?
|
|
114
132
|
cur_point = rr.max
|
|
133
|
+
next
|
|
134
|
+
end
|
|
135
|
+
# We are on the second or later range
|
|
136
|
+
if rr.min < cur_point
|
|
137
|
+
start_point = rr.min
|
|
138
|
+
end_point = cur_point
|
|
139
|
+
overlaps << (start_point..end_point)
|
|
115
140
|
end
|
|
116
|
-
|
|
141
|
+
cur_point = rr.max
|
|
117
142
|
end
|
|
143
|
+
overlaps
|
|
118
144
|
end
|
|
119
145
|
|
|
120
146
|
# Return a Range that represents the intersection between this range and the
|
|
@@ -193,12 +219,24 @@ module FatCore
|
|
|
193
219
|
#
|
|
194
220
|
# @return [String]
|
|
195
221
|
def tex_quote
|
|
196
|
-
|
|
222
|
+
minq =
|
|
223
|
+
if min.respond_to?(:tex_quote)
|
|
224
|
+
min.tex_quote
|
|
225
|
+
else
|
|
226
|
+
min.to_s
|
|
227
|
+
end
|
|
228
|
+
maxq =
|
|
229
|
+
if max.respond_to?(:tex_quote)
|
|
230
|
+
max.tex_quote
|
|
231
|
+
else
|
|
232
|
+
max.to_s
|
|
233
|
+
end
|
|
234
|
+
"(#{minq}..#{maxq})"
|
|
197
235
|
end
|
|
198
236
|
|
|
199
237
|
# @group Queries
|
|
200
238
|
|
|
201
|
-
# Is
|
|
239
|
+
# Is other on the left of and contiguous to self? Whether one range is
|
|
202
240
|
# "contiguous" to another has two cases:
|
|
203
241
|
#
|
|
204
242
|
# 1. If the elements of the Range on the left respond to the #succ method
|
|
@@ -219,14 +257,14 @@ module FatCore
|
|
|
219
257
|
# @param other [Range] other range to test for contiguity
|
|
220
258
|
# @return [Boolean] is self left_contiguous with other
|
|
221
259
|
def left_contiguous?(other)
|
|
222
|
-
if max.respond_to?(:succ)
|
|
223
|
-
max.succ ==
|
|
260
|
+
if other.max.respond_to?(:succ)
|
|
261
|
+
other.max.succ == min
|
|
224
262
|
else
|
|
225
|
-
max ==
|
|
263
|
+
other.max == min
|
|
226
264
|
end
|
|
227
265
|
end
|
|
228
266
|
|
|
229
|
-
# Is
|
|
267
|
+
# Is other on the right of and contiguous to self? Whether one range is
|
|
230
268
|
# "contiguous" to another has two cases:
|
|
231
269
|
#
|
|
232
270
|
# 1. If the elements of the Range on the left respond to the #succ method
|
|
@@ -247,10 +285,10 @@ module FatCore
|
|
|
247
285
|
# @param other [Range] other range to test for contiguity
|
|
248
286
|
# @return [Boolean] is self right_contiguous with other
|
|
249
287
|
def right_contiguous?(other)
|
|
250
|
-
if
|
|
251
|
-
|
|
288
|
+
if max.respond_to?(:succ)
|
|
289
|
+
max.succ == other.min
|
|
252
290
|
else
|
|
253
|
-
|
|
291
|
+
max == other.min
|
|
254
292
|
end
|
|
255
293
|
end
|
|
256
294
|
|
|
@@ -348,6 +386,11 @@ module FatCore
|
|
|
348
386
|
# @param ranges [Array<Range>]
|
|
349
387
|
# @return [Boolean]
|
|
350
388
|
def spanned_by?(ranges)
|
|
389
|
+
return empty? if ranges.empty?
|
|
390
|
+
|
|
391
|
+
msg = "#{ranges.first.min.class} range incompatible with #{min.class} Range"
|
|
392
|
+
raise ArgumentError, msg unless compatible?(ranges)
|
|
393
|
+
|
|
351
394
|
joined_range = nil
|
|
352
395
|
ranges.sort.each do |r|
|
|
353
396
|
unless joined_range
|
|
@@ -385,6 +428,16 @@ module FatCore
|
|
|
385
428
|
[min, max] <=> other.minmax
|
|
386
429
|
end
|
|
387
430
|
|
|
431
|
+
def compatible?(ranges)
|
|
432
|
+
numeric_ok = min.is_a?(Numeric) && max.is_a?(Numeric)
|
|
433
|
+
if numeric_ok
|
|
434
|
+
ranges.map.all? { |r| r.min.is_a?(Numeric) && r.max.is_a?(Numeric) }
|
|
435
|
+
else
|
|
436
|
+
self_class = min.class
|
|
437
|
+
ranges.map.all? { |r| r.min.is_a?(self_class) && r.max.is_a?(self_class) }
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
388
441
|
module ClassMethods
|
|
389
442
|
# Return whether any of the `ranges` overlap one another
|
|
390
443
|
#
|