web-dev-qa-db-fra.com

Algorithme d'aplatissement des gammes de chevauchement

Je cherche une bonne façon d'aplatir (diviser) une liste de gammes numériques potentiellement chevauchant. Le problème est très similaire à celui de cette question: moyen le plus rapide de fragmenter les gammes de date chevauchant , et beaucoup d'autres.

Cependant, les gammes ne sont pas seulement des entiers et je recherche un algorithme décent qui peut être facilement implémenté dans JavaScript ou Python, etc.

Exemple de données:  Example data

Exemple de solution:  enter image description here

Toutes mes excuses s'il s'agit d'un duplicata, mais je dois encore trouver une solution.

16
Jollywatt

Marchez de gauche à droite, en utilisant une pile pour garder une trace de quelle couleur vous allez. Au lieu d'une carte discrète, utilisez les 10 numéros dans votre jeu de données comme points de rupture.

En commençant par une pile vide et réglage start à 0, en boucle jusqu'à ce que nous atteignions la fin:

  • Si la pile est vide:
    • Recherchez la première couleur commençant à ou après start et poussez-la et toutes les couleurs classées plus bas sur la pile. Dans votre liste aplatie, marquez le début de cette couleur.
  • sinon (sinon vide):
    • Trouvez le prochain point de départ pour toute couleur classée supérieure à ou après start et trouvez la fin de la couleur actuelle [.____]
      • Si la couleur suivante commence en premier, poussez-la et autre chose sur le chemin sur la pile. Mettez à jour la fin de la couleur actuelle comme début de celui-ci et ajoutez le début de cette couleur à la liste aplatie.
      • S'il n'y en a aucun et que la couleur actuelle se termine d'abord, définissez start à la fin de cette couleur, allez-la hors de la pile et vérifiez la couleur la plus élevée au plus haut niveau [.____]
        • Si start est dans la plage de couleurs suivante, ajoutez cette couleur à la liste aplatie, à partir de start.
        • Si la pile vide, continuez simplement la boucle (revenir au premier point de balle).

Ceci est une course mentale donnée à vos exemples de données:

# Initial data.
flattened = []
stack = []
start = 0
# Stack is empty.  Look for the next starting point at 0 or later: "b", 0 - Push it and all lower levels onto stack
flattened = [ (b, 0, ?) ]
stack = [ r, b ]
start = 0
# End of "b" is 5.4, next higher-colored start is "g" at 2 - Delimit and continue
flattened = [ (b, 0, 2), (g, 2, ?) ]
stack = [ r, b, g ]
start = 2
# End of "g" is 12, next higher-colored start is "y" at 3.5 - Delimit and continue
flattened = [ (b, 0, 2), (g, 2, 3.5), (y, 3.5, ?) ]
stack = [ r, b, g, y ]
start = 3.5
# End of "y" is 6.7, next higher-colored start is "o" at 6.7 - Delimit and continue
flattened = [ (b, 0, 2), (g, 2, 3.5), (y, 3.5, 6.7), (o, 6.7, ?) ]
stack = [ r, b, g, y, o ]
start = 6.7
# End of "o" is 10, and there is nothing starting at 12 or later in a higher color.  Next off stack, "y", has already ended.  Next off stack, "g", has not ended.  Delimit and continue.
flattened = [ (b, 0, 2), (g, 2, 3.5), (y, 3.5, 6.7), (o, 6.7, 10), (g, 10, ?) ]
stack = [ r, b, g ]
start = 10
# End of "g" is 12, there is nothing starting at 12 or later in a higher color.  Next off stack, "b", is out of range (already ended).  Next off stack, "r", is out of range (not started).  Mark end of current color:
flattened = [ (b, 0, 2), (g, 2, 3.5), (y, 3.5, 6.7), (o, 6.7, 10), (g, 10, 12) ]
stack = []
start = 12
# Stack is empty.  Look for the next starting point at 12 or later: "r", 12.5 - Push onto stack
flattened = [ (b, 0, 2), (g, 2, 3.5), (y, 3.5, 6.7), (o, 6.7, 10), (g, 10, 12), (r, 12.5, ?) ]
stack = [ r ]
start = 12
# End of "r" is 13.8, and there is nothing starting at 12 or higher in a higher color.  Mark end and pop off stack.
flattened = [ (b, 0, 2), (g, 2, 3.5), (y, 3.5, 6.7), (o, 6.7, 10), (g, 10, 12), (r, 12.5, 13.8) ]
stack = []
start = 13.8
# Stack is empty and nothing is past 13.8 - We're done.
10
Izkata

Cette solution semble la plus simple. (Ou au moins, le plus facile à saisir)

Tout ce qui est nécessaire est une fonction de soustraire deux gammes. En d'autres termes, quelque chose qui va donner ceci:

A ------               A     ------           A    ----
B    -------    and    B ------        and    B ---------
=       ----           = ----                 = ---    --

Qui est assez simple. Ensuite, vous pouvez simplement itérale à travers chacune des gammes, à partir du plus bas, et pour chacun, soustrayez-la de toutes les gammes au-dessus de celui-ci, à son tour. et là, vous l'avez.


Voici une mise en œuvre du soustracteur de la gamme en Python:

def subtractRanges((As, Ae), (Bs, Be)):
    '''SUBTRACTS A FROM B'''
    # e.g, A =    ------
    #      B =  -----------
    # result =  --      ---
    # Returns list of new range(s)

    if As > Be or Bs > Ae: # All of B visible
        return [[Bs, Be]]
    result = []
    if As > Bs: # Beginning of B visible
        result.append([Bs, As])
    if Ae < Be: # End of B visible
        result.append([Ae, Be])
    return result

Utilisation de cette fonction, le reste peut être fait comme ceci: (une plage "signifie une plage, comme" plage "est un Python)

spans = [["red", [12.5, 13.8]],
["blue", [0.0, 5.4]],
["green", [2.0, 12.0]],
["yellow", [3.5, 6.7]],
["orange", [6.7, 10.0]]]

i = 0 # Start at lowest span
while i < len(spans):
    for superior in spans[i+1:]: # Iterate through all spans above
        result = subtractRanges(superior[1], spans[i][1])
        if not result:      # If span is completely covered
            del spans[i]    # Remove it from list
            i -= 1          # Compensate for list shifting
            break           # Skip to next span
        else:   # If there is at least one resulting span
            spans[i][1] = result[0]
            if len(result) > 1: # If there are two resulting spans
                # Insert another span with the same name
                spans.insert(i+1, [spans[i][0], result[1]])
    i += 1

print spans

Cela donne [['red', [12.5, 13.8]], ['blue', [0.0, 2.0]], ['green', [2.0, 3.5]], ['green', [10.0, 12.0]], ['yellow', [3.5, 6.7]], ['orange', [6.7, 10.0]]], qui est correct.

3
Jollywatt

Voici une solution relativement simple dans Scala. Il ne devrait pas être trop difficile de porter dans une autre langue.

case class Range(name: String, left: Double, right: Double) {
  def overlapsLeft(other: Range) =
    other.left < left && left < other.right

  def overlapsRight(other: Range) =
    other.left < right && right < other.right

  def overlapsCompletely(other: Range) =
    left <= other.left && right >= other.right

  def splitLeft(other: Range) = 
    Range(other.name, other.left, left)

  def splitRight(other: Range) = 
    Range(other.name, right, other.right)
}

def apply(ranges: Set[Range], newRange: Range) = {
  val left     = ranges.filter(newRange.overlapsLeft)
  val right    = ranges.filter(newRange.overlapsRight)
  val overlaps = ranges.filter(newRange.overlapsCompletely)

  val leftSplit  =  left.map(newRange.splitLeft)
  val rightSplit = right.map(newRange.splitRight)

  ranges -- left -- right -- overlaps ++ leftSplit ++ rightSplit + newRange
}

val ranges = Vector(
  Range("red",   12.5, 13.8),
  Range("blue",   0.0,  5.4),
  Range("green",  2.0, 12.0),
  Range("yellow", 3.5,  6.7),
  Range("orange", 6.7, 10.0))

val flattened = ranges.foldLeft(Set.empty[Range])(apply)
val sorted = flattened.toSeq.sortBy(_.left)
sorted foreach println

apply prend un Set de toutes les gammes déjà appliquées, trouve les chevauchements, puis renvoie un nouvel ensemble moins les chevauchements et la nouvelle gamme et les nouvelles gammes récentes. foldLeft appelle à plusieurs reprises apply avec chaque plage d'entrée.

2
Karl Bielefeldt

Si les données représentent réellement une portée similaire à vos données d'échantillonnage, vous pouvez créer une carte comme celle-ci:

map = [0 .. 150]

for each color:
    for loc range start * 10 to range finish * 10:
        map[loc] = color

Puis parcourez cette carte pour générer les gammes

curcolor = none
for loc in map:
    if map[loc] != curcolor:
        if curcolor:
            rangeend = loc / 10
        make new range
        rangecolor = map[loc]
        rangestart = loc / 10

Pour travailler, les valeurs doivent être dans une plage relativement petite que dans votre exemple de données.

Edit: Pour travailler avec de vrais flotteurs, utilisez la carte pour générer un mappage de niveau élevé, puis se référer aux données d'origine pour créer les limites.

map = [0 .. 15]

for each color:
   for loc round(range start) to round(range finish):
        map[loc] = color

curcolor = none
for loc in map
    if map[loc] != curcolor:

        make new range
        if loc = round(range[map[loc]].start)  
             rangestart = range[map[loc]].start
        else
             rangestart = previous rangeend
        rangecolor = map[loc]
        if curcolor:
             if map[loc] == none:
                 last rangeend = range[map[loc]].end
             else
                 last rangeend = rangestart
        curcolor = rangecolor
2
Gort the Robot

Gardez simplement un ensemble de gammes triés par début. Ajoutez une plage qui couvre tout (-oo .. + oo). Pour ajouter une plage R:

let pre = last range that starts before r starts

let post = earliest range that starts before r ends

now iterate from pre to post: split ranges that overlap, remove ranges that are covered, then add r
0
kevin cline