Рубин: пересечение двух диапазонов - PullRequest
10 голосов
/ 07 декабря 2011

В рубине, учитывая два диапазона дат, я хочу диапазон, представляющий пересечение двух диапазонов дат, или ноль, если пересечения нет. Например:

(Date.new(2011,1,1)..Date.new(2011,1,15)) & (Date.new(2011,1,10)..Date.new(2011,2,15))
=> Mon, 10 Jan 2011..Sat, 15 Jan 2011

Редактировать: Надо было сказать, что я хочу, чтобы он работал и для DateTime, поэтому интервал может быть уменьшен до минут и секунд:

(DateTime.new(2011,1,1,22,45)..Date.new(2011,2,15)) & (Date.new(2011,1,1)..Date.new(2011,2,15))
=> Sat, 01 Jan 2011 22:45:00 +0000..Tue, 15 Feb 2011

Ответы [ 8 ]

23 голосов
/ 07 декабря 2011
require 'date'

class Range
  def intersection(other)
    return nil if (self.max < other.begin or other.max < self.begin) 
    [self.begin, other.begin].max..[self.max, other.max].min
  end
  alias_method :&, :intersection
end

p (Date.new(2011,1,1)..Date.new(2011,1,15)) & (Date.new(2011,1,10)..Date.new(2011,2,15))
#<Date: 2011-01-10 ((2455572j,0s,0n),+0s,2299161j)>..#<Date: 2011-01-15 ((2455577j,0s,0n),+0s,2299161j)>
7 голосов
/ 07 декабря 2011

Вы можете попробовать это, чтобы получить диапазон, представляющий пересечение

range1 = Date.new(2011,12,1)..Date.new(2011,12,10)
range2 = Date.new(2011,12,4)..Date.new(2011,12,12)

inters = range1.to_a & range2.to_a

intersected_range = inters.min..inters.max

Преобразование вашего примера:

class Range  
  def intersection(other)  
    raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)  

    inters = self.to_a & other.to_a

    inters.empty? ? nil : inters.min..inters.max 
  end  

  alias_method :&, :intersection  
end
2 голосов
/ 07 марта 2013

Я испек это решение для восходящих диапазонов, также заботясь о конечных ситуациях исключения:

intersect_ranges = ->(r1, r2) do
  new_end = [r1.end, r2.end].min
  new_begin = [r1.begin, r2.begin].max
  exclude_end = (r2.exclude_end? && new_end == r2.end) || (r1.exclude_end? && new_end == r1.end)

  valid = (new_begin <= new_end && !exclude_end) 
  valid ||= (new_begin < new_end && exclude_end))
  valid ? Range.new(new_begin, new_end, exclude_end) : nil
end

Меня также немного беспокоит, ребята, добавив его в сам класс Range, поскольку поведениепересекающихся диапазонов не определены однозначно.(Как насчет пересечения 1 ... 4 и 4 ... 1? Почему ноль, когда пересечения нет; мы могли бы также сказать, что это пустой диапазон: 1 ... 1)

1 голос
/ 07 декабря 2011

Я нашел это: http://www.postal -code.com / binarycode / 2009/06/06 / better-range-intersection-in-ruby / , который является довольно хорошим началом, но не работает для свиданий. Я немного подправил это:

class Range  
  def intersection(other)  
    raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)  

    new_min = self.cover?(other.min) ? other.min : other.cover?(min) ? min : nil  
    new_max = self.cover?(other.max) ? other.max : other.cover?(max) ? max : nil  

    new_min && new_max ? new_min..new_max : nil  
  end  

  alias_method :&, :intersection  
end

Я пропустил тесты, но в основном это тесты из поста, измененного для дат. Это работает для ruby ​​1.9.2.

Кто-нибудь получил лучшее решение?

0 голосов
/ 25 марта 2018

Поскольку этот вопрос относится к Как объединить перекрывающиеся временные диапазоны (объединение временных диапазонов) , я также хотел бы опубликовать свой вывод о геме range_operators здесь, потому что, если бы мне помог в той же ситуации.

0 голосов
/ 23 ноября 2016

У меня есть времена как [[start, end], ...], и я хочу удалить некоторые временные диапазоны из каждого начального временного диапазона, вот что я сделал:

def exclude_intersecting_time_ranges(initial_times, other_times)
  initial_times.map { |initial_time|
    other_times.each do |other_time|
      next unless initial_time
      # Other started after initial ended
      next if other_time.first >= initial_time.last
      # Other ended before initial started
      next if other_time.last <= initial_time.first

      # if other time started before and ended after after, no hour is counted
      if other_time.first <= initial_time.first && other_time.last >= initial_time.last
        initial_time = nil
      # if other time range is inside initial time range, split in two time ranges
      elsif initial_time.first < other_time.first && initial_time.last > other_time.last
        initial_times.push([other_time.last, initial_time.last])
        initial_time = [initial_time.first, other_time.first]
      # if start time of other time range is before initial time range
      elsif other_time.first <= initial_time.first
        initial_time = [other_time.last, initial_time.last]
      # if end time of other time range if after initial time range
      elsif other_time.last >= initial_time.last
        initial_time = [initial_time.first, other_time.first]
      end
    end

    initial_time
  }.compact
end
0 голосов
/ 07 декабря 2011

Попробуйте что-то вроде этого

require 'date'
sample = Date.parse('2011-01-01')
sample1 = Date.parse('2011-01-15')
sample2 = Date.parse('2010-12-19')
sample3 = Date.parse('2011-01-11')

puts (sample..sample1).to_a & (sample2..sample3).to_a

Что это даст вам - массив дат пересечения !!

0 голосов
/ 07 декабря 2011

Я бы перевел их в массив, так как массивы знают операцию пересечения:

(Date.new(2011,1,1)..Date.new(2011,1,15)).to_a & (Date.new(2011,1,10)..Date.new(2011,2,15)).to_a

Конечно, это возвращает массив. Поэтому, если вам нужен Enumerator (диапазон кажется невозможным, поскольку они больше не являются последовательными значениями), просто бросьте to_enum в конце.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...