Я представил два метода ниже. Оба являются чисто Ruby и работают на произвольных конечных диапазонах. 1 . Первый - более эффективный, но более сложный.
Эффективно, но сложно
Код
def subtract_range(r1, r2)
return [r1] if r2.end < r1.begin || r2.begin > r1.end
return [] if r2.begin <= r1.begin && r2.end >= r1.end
if r2.begin <= r1.begin
r2.end < r1.begin ? [r1] : [r2.end.succ..r1.end]
else # r1.begin < r2.begin <= r1.end
e = pred(r1.begin, r2.begin)
r2.end >= r1.end ? [(r1.begin)..e] :
[r1.begin..e, (r2.end.succ)..r1.end]
end
end
def pred(start_at, e)
(return e-1) if e.kind_of? Numeric
loop do
break start_at if start_at.succ == e
start_at = start_at.succ
end
end
Все элементы диапазона (независимо от их класса) имеют метод succ
(например, String # succ ), но в целом не имеют сопоставимого метода предшественника. Числовые классы являются исключением: предшественником n
является n-1
. Для нечисловых элементов необходимо вычислить значение предшественника, начиная с некоторого известного меньшего значения, и последовательно применять succ
, пока не будет найдено значение предшественника.
Примеры
subtract_range 15..25, 20..30 #=> [15..19]
subtract_range 15..25, 10..20 #=> [21..25]
subtract_range 15..25, 17..23 #=> [15..16, 24..25]
subtract_range 15..25, 5..10 #=> [15..25]
subtract_range 15..25, 30..35 #=> [15..25]
subtract_range 15..25, 25..30 #=> [15..24]
subtract_range 15..25, 16..30 #=> [15..15]
subtract_range 15..25, 10..30 #=> []
subtract_range 'd'..'j', 'g'..'m' #=> ["d".."f"]
subtract_range 'd'..'j', 'f'..'h' #=> ["d".."e", "i".."j"]
Менее эффективно, но проще
Код
def subtract_range(r1, r2)
arr = r1.to_a - r2.to_a
return [] if arr.empty?
return [arr.first..arr.first] if arr.size == 1
arr.slice_when { |m,n| m.succ < n }.
map { |a| a.size == 1 ? a.first..a.first : a.first..a[-1] }
end
Примеры
Этот метод выдает те же возвращаемые значения для примеров, приведенных выше.
Объяснение
Предположим (из последнего примера)
r1 = 'd'..'j'
r2 = 'f'..'h'
Тогда
arr = r1.to_a - r2.to_a
#=> ["d", "e", "f", "g", "h", "i", "j"] - ["f", "g", "h"]
#=> ["d", "e", "i", "j"]
arr.empty?
#=> false (so do not return [])
arr.size == 1
#=> false (so do not return [arr.first..arr.first])
enum = arr.slice_when { |m,n| m.succ < n }
#=> #<Enumerator: #<Enumerator::Generator:0x00000001d2c228>:each>
Мы можем преобразовать enum
в массив, чтобы увидеть элементы, которые будут переданы в map
.
enum.to_a
#=> [["d", "e"], ["i", "j"]]
Наконец,
enum.map { |a| a.size == 1 ? a.first..a.first : a.first..a[-1] }
#=> ["d".."e", "i".."j"]
1 2.0..6.0 - 4.0..8.0 #=> 2.0...4.0
, не проблема (обратите внимание на три точки). Однако 2.0..4.0
- 1.0..3.0 is a problem, as the resulting range,
3.0 .. 4.0
, исключая 3.0
, нельзя представить как экземпляр Range
.