SHARE
TWEET

intervals_to_disjoint_intervals

OPiMedia Feb 18th, 2013 91 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!python
  2. # -*- coding: latin-1 -*-
  3.  
  4. #
  5. # Suite à l'article "Union d'un ensemble d'intervalles" du site "Sam & Max" :
  6. # http://sametmax.com/union-dun-ensemble-dintervalles/
  7. #
  8. # Olivier Pirson --- http://www.opimedia.be/
  9. # 18 février 2013
  10. #
  11. # Testé sur Python 2.7.3 et Python 3.3.0
  12. #
  13.  
  14. from __future__ import print_function
  15.  
  16.  
  17.  
  18.  
  19. import bisect
  20. import collections
  21.  
  22.  
  23.  
  24. def intervals_to_disjoint_intervals(intervals,
  25.                                     closed = False,
  26.                                     container = list):
  27.     """
  28.    Renvoie container(suite d'intervalles)
  29.      avec les intervalles non vides,
  30.      strictements ordonnés sur leur premier élément
  31.      et strictement disjoints.
  32.  
  33.    Si closed
  34.    alors les intervalles sont considérés comme fermés,
  35.    sinon ils sont considérés comme ouverts ou semi-ouverts tous du même côté
  36.          et les intervalles (a, a) sont éliminés
  37.  
  38.    Complexité O(n*lg(n)) par rapport à la longueur de intervals
  39.  
  40.    Pre: intervals: Iterable fini de couples d'éléments deux à deux comparables
  41.         closed: bool
  42.         container: classe dont le constructeur peut recevoir l'itérable
  43.                    résultat de ordered_intervals_to_disjoint_intervals_iter()
  44.  
  45.    Return: list de tuples de 2 éléments
  46.    """
  47.     assert isinstance(intervals, collections.Iterable), type(intervals)
  48.     assert isinstance(closed, bool), type(closed)
  49.     assert isinstance(container, type), type(container)
  50.  
  51.     return container(ordered_intervals_to_disjoint_intervals_iter(
  52.             intervals_to_ordered_intervals(intervals,
  53.                                            closed = closed),
  54.             closed = closed))
  55.  
  56.  
  57. def intervals_to_ordered_intervals(intervals,
  58.                                    closed = False):
  59.     """
  60.    Renvoie une list
  61.      avec les intervalles non vides,
  62.      strictements ordonnés sur leur premier élément
  63.      et avec les intervalles contigues rassemblés en un seul intervalle
  64.      (mais les intervalles peuvent être strictement disjoints
  65.      ou se chevaucher).
  66.  
  67.    Si closed
  68.    alors les intervalles sont considérés comme fermés,
  69.    sinon ils sont considérés comme ouverts ou semi-ouverts tous du même côté
  70.          et les intervalles (a, a) sont éliminés.
  71.  
  72.    Complexité O(n*lg(n)) par rapport à la longueur de intervals
  73.  
  74.    Pre: intervals: Iterable fini de couples d'éléments deux à deux comparables
  75.         closed: bool
  76.  
  77.    Return: list de tuples de 2 éléments
  78.    """
  79.     assert isinstance(intervals, collections.Iterable), type(intervals)
  80.     assert isinstance(closed, bool), type(closed)
  81.  
  82.     begins = []
  83.     ends = []
  84.  
  85.     len_begins = 0
  86.  
  87.     for (a, b) in intervals:
  88.         if (a > b) or ((not closed) and (a >= b)):  # interval vide
  89.             continue
  90.  
  91.  
  92.         # Est-ce qu'il y a déjà un intervalle qui commence par a ?
  93.         i_begin = bisect.bisect(begins, (a, b))
  94.  
  95.         len_begins = len(begins)
  96.  
  97.         if (i_begin + 1 < len(begins)) and (begins[i_begin + 1][0] == a):
  98.             # Déja un (a, > b) dans begins
  99.             assert begins[i_begin - 1][1] > b, (begins[i_begin + 1], (a, b))
  100.  
  101.             continue
  102.  
  103.         if (0 < i_begin) and (begins[i_begin - 1][0] == a):
  104.             # Déjà un (a, <= b) dans begins
  105.             assert begins[i_begin - 1][1] <= b, (begins[i_begin - 1], (a, b))
  106.  
  107.             if begins[i_begin - 1][1] != b:
  108.                 # Déjà un (a, < b) dans begins,
  109.                 #   on remplace dans begins et dans ends
  110.                 del ends[bisect.bisect(ends, (begins[i_begin - 1][1],
  111.                                               begins[i_begin - 1][0])) - 1]
  112.  
  113.                 bisect.insort(ends, (b, a))
  114.  
  115.                 begins[i_begin - 1] = (a, b)
  116.  
  117.             continue
  118.  
  119.  
  120.         # Est-ce qu'il y a déjà un intervalle qui commence par b ?
  121.         i_end = bisect.bisect(ends, (b, a))
  122.  
  123.         assert len_begins == len(ends)
  124.  
  125.         if (i_end + 1 < len_begins) and (ends[i_end + 1][0] == b):
  126.             # Déja un (b, > a) dans ends
  127.             assert ends[i_end - 1][1] > a, (ends[i_end + 1], (b, a))
  128.  
  129.             continue
  130.  
  131.         if (0 < i_end) and (ends[i_end - 1][0] == b):
  132.             # Déjà un (b, < a) dans ends, on remplace dans begins et dans ends
  133.             assert ends[i_end - 1][1] < a, (ends[i_end - 1], (b, a))
  134.  
  135.             del begins[bisect.bisect(begins, (ends[i_end - 1][0],
  136.                                               ends[i_end - 1][0])) - 1]
  137.  
  138.             bisect.insort(begins, (a, b))
  139.  
  140.             ends[i_end - 1] = (b, a)
  141.  
  142.             continue
  143.  
  144.  
  145.         # Aucun intervalle commençant par a ou terminant par b
  146.         #   n'a encore été rencontré
  147.         begins.insert(i_begin, (a, b))
  148.         ends.insert(i_end, (b, a))
  149.  
  150.         len_begins += 1
  151.  
  152.     del ends
  153.  
  154.     return begins
  155.  
  156.  
  157. def ordered_intervals_to_disjoint_intervals_iter(ordered_intervals,
  158.                                                  closed = False):
  159.     """
  160.    Renvoie un itérateur,
  161.    qui renvoie un intervalle après l'autre
  162.                de façon strictement ordonné sur leur premier élément
  163.                et disjoints entre eux.
  164.  
  165.    Si closed
  166.    alors les intervalles sont considérés comme fermés,
  167.    sinon ils sont considérés comme ouverts ou semi-ouverts tous du même côté
  168.          et les intervalles (a, a) sont éliminés.
  169.  
  170.    Complexité O(n) par rapport à la longueur de ordered_intervals
  171.  
  172.    Pre: ordered_intervals: Iterable de couples d'éléments
  173.                              deux à deux comparables
  174.                              strictement ordonné sur leur premier élément
  175.         closed: bool
  176.  
  177.    Return: Iterator sur des tuple de 2 éléments
  178.    """
  179.     assert isinstance(ordered_intervals, collections.Iterable), \
  180.         type(ordered_intervals)
  181.     assert isinstance(closed, bool), type(closed)
  182.  
  183.     i = iter(ordered_intervals)
  184.     while True:  # prend le premier intervalle non vide (a, b)
  185.         (a, b) = next(i)
  186.         if (a <= b) and (closed or (a < b)):
  187.             break
  188.  
  189.     while True:  # compare progressivement les intervalles deux à deux
  190.         while True:  # prend l'intervalle non vide suivant (c, d)
  191.             try:
  192.                 (c, d) = next(i)
  193.             except StopIteration:
  194.                 yield (a, b)  # il restait cette intervalle
  195.  
  196.                 raise StopIteration
  197.  
  198.             if (c <= d) and (closed or (c < d)):
  199.                 break
  200.  
  201.         assert a < c, ((a, b), (c, d))
  202.  
  203.         if b >= c:  # (a, b) et (c, d) se chevauchent ou sont contigus
  204.             a, b = a, max(b, d)
  205.         else:       # (a, b) et (c, d) sont séparés
  206.             yield (a, b)
  207.  
  208.             a, b = c, d
  209.  
  210.  
  211.  
  212. ########
  213. # Main #
  214. ########
  215. if __name__ == '__main__':
  216.     from datetime import datetime
  217.  
  218.     for i, ex in enumerate((((2, 7),
  219.                              (3, 7),
  220.                              (1, 5),
  221.                              (9, 10),
  222.                              (8, 9)),
  223.                             ((10, 210), )*1000000,
  224.                             ((-7.2, -6.9),
  225.                              (-8.4, -5.3),
  226.                              (-10.5, -5.25),
  227.                              (-1.7, 0.01),
  228.                              (0.0, 4.0),
  229.                              (12.125, 13.9),
  230.                              (13.9, 15.0)),
  231.                             ((datetime(2012, 12, 21, 16, 54),
  232.                               datetime(2013, 2, 16, 12, 59)),
  233.                              (datetime(2013, 2, 14, 2, 0),
  234.                               datetime(2069, 1, 1, 13, 37))))):
  235.         result = intervals_to_disjoint_intervals(ex)
  236.  
  237.         print(result)
  238.  
  239.         solution = ([(1, 7),
  240.                      (8, 10)],
  241.                     [(10, 210)],
  242.                     [(-10.5, -5.25),
  243.                      (-1.7, 4.0),
  244.                      (12.125, 15.0)],
  245.                     [(datetime(2012, 12, 21, 16, 54),
  246.                       datetime(2069, 1, 1, 13, 37))])[i]
  247.  
  248.         assert result == solution
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top