重複する可能性のある数値範囲のリストを平坦化(分割)する良い方法を探しています。問題は、この質問の問題と非常に似ています。重複する日付範囲を分割する最も速い方法、および他の多くの方法です。
ただし、範囲は整数だけではなく、JavascriptやPythonなどで簡単に実装できる適切なアルゴリズムを探しています。
サンプルデータ:
ソリューション例:
これが重複している場合はおApびしますが、解決策はまだ見つかりません。
重複する可能性のある数値範囲のリストを平坦化(分割)する良い方法を探しています。問題は、この質問の問題と非常に似ています。重複する日付範囲を分割する最も速い方法、および他の多くの方法です。
ただし、範囲は整数だけではなく、JavascriptやPythonなどで簡単に実装できる適切なアルゴリズムを探しています。
サンプルデータ:
ソリューション例:
これが重複している場合はおApびしますが、解決策はまだ見つかりません。
回答:
スタックを使用して、左から右に歩いて、自分の色を追跡します。個別のマップの代わりに、データセット内の10個の数値をブレークポイントとして使用します。
空のスタックで開始し、start
0に設定して、最後に到達するまでループします。
start
それとすべての下位ランクの色をスタックにプッシュします。フラット化されたリストで、その色の始まりをマークします。start
見つけ、現在の色の終わりを見つけます
start
終了する場合、この色の最後に設定し、スタックからそれをポップし、次に高いランクの色を確認します
start
が次の色の範囲内にある場合、この色をから始まるフラット化されたリストに追加しstart
ます。これは、サンプルデータが与えられたメンタルランスルーです。
# 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.
このソリューションは最も簡単に思えます。(または、少なくとも、最も把握しやすい)
必要なのは、2つの範囲を減算する関数だけです。言い換えれば、これを与えるもの:
A ------ A ------ A ----
B ------- and B ------ and B ---------
= ---- = ---- = --- --
どちらも簡単です。次に、各範囲を最も低いものから順に繰り返し、それぞれについて、その上のすべての範囲を順番に減算します。そして、あなたはそれを持っています。
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
この関数を使用すると、残りは次のように実行できます(「範囲」は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
これは[['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]]]
、正しいを与えます。
データのスコープがサンプルデータと本当に似ている場合は、次のようなマップを作成できます。
map = [0 .. 150]
for each color:
for loc range start * 10 to range finish * 10:
map[loc] = color
次に、このマップを歩いて範囲を生成します
curcolor = none
for loc in map:
if map[loc] != curcolor:
if curcolor:
rangeend = loc / 10
make new range
rangecolor = map[loc]
rangestart = loc / 10
動作させるには、サンプルデータのように値が比較的狭い範囲内にある必要があります。
編集:真のフロートを使用するには、マップを使用して高レベルのマッピングを生成し、元のデータを参照して境界を作成します。
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
Scalaでの比較的簡単なソリューションを次に示します。別の言語に移植するのはそれほど難しくないはずです。
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
Set
すでに適用されているすべての範囲を取り込んで重複を見つけ、重複を除いた新しいセットと、新しい範囲と新しく分割された範囲を返します。 各入力範囲でfoldLeft
繰り返し呼び出しapply
ます。