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:
Exemple de solution:
Toutes mes excuses s'il s'agit d'un duplicata, mais je dois encore trouver une solution.
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:
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.start
et trouvez la fin de la couleur actuelle [.____]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 [.____]start
est dans la plage de couleurs suivante, ajoutez cette couleur à la liste aplatie, à partir de start
.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.
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.
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.
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
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