겹치는 범위를 평탄화하기위한 알고리즘


16

잠재적으로 겹치는 숫자 범위 목록을 병합 (분할)하는 좋은 방법을 찾고 있습니다. 이 문제는이 질문과 매우 유사합니다. 겹치는 날짜 범위를 분할하는 가장 빠른 방법 및 기타 여러 가지.

그러나 범위는 정수 일뿐 만 아니라 Javascript 또는 Python 등에서 쉽게 구현할 수있는 괜찮은 알고리즘을 찾고 있습니다.

데이터 예 : 데이터 예

솔루션 예 : 여기에 이미지 설명을 입력하십시오

이것이 중복이라면 사과하지만 아직 해결책을 찾지 못했습니다.


녹색이 파란색 위에 있지만 노란색과 오렌지색 아래에 있는지 어떻게 알 수 있습니까? 색상 범위가 순서대로 적용됩니까? 이 경우 알고리즘이 명백해 보입니다. 그냥 ... 음, 색상 범위를 순서대로 적용하십시오.
Robert Harvey

1
예, 순서대로 적용됩니다. 그러나 이것이 문제입니다. 어떻게 범위를 '적용'하겠습니까?
Jollywatt

1
색상을 자주 추가 / 제거하거나 쿼리 속도를 최적화해야합니까? 당신은 보통 얼마나 많은 "범위"를 가지겠습니까? 삼? 3000?
Telastyn

색상을 자주 추가 / 제거하지 않으며, 4 자리 이상의 정밀도로 10-20 범위 사이에 위치합니다. 세트가 1000 개 이상의 아이템이어야하므로 세트 방법이 적합하지 않은 이유입니다. 내가 간 방법은 내가 파이썬에 게시 한 방법입니다.
Jollywatt

답변:


10

스택을 사용하여 왼쪽에서 오른쪽으로 이동하여 현재 사용중인 색상을 추적합니다. 불연속 맵 대신 데이터 세트의 10 개 숫자를 중단 점으로 사용하십시오.

빈 스택으로 시작 start하여 0으로 설정 하면 끝까지 도달합니다.

  • 스택이 비어있는 경우 :
    • 에서 또는 이후에 시작하는 첫 번째 색상을 start찾아서 색상이 낮은 모든 색상을 스택에 밀어 넣습니다. 병합 된 목록에서 해당 색상의 시작을 표시하십시오.
  • else (비어 있지 않은 경우) :
    • 에서 또는 이후에 높은 등급의 색상에 대한 다음 시작점을 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.

"스택으로가는 도중에 다른 것"이란 무엇입니까?
Guillaume07

1
@ Guillaume07 현재와 선택한 다음 시작 사이의 순위. 샘플 데이터에는 표시되지 않지만 녹색이 녹색보다 먼저 시작되도록 이동되었다고 상상하십시오. 노란색이 끝날 때 녹색의 끝이 여전히 스택의 올바른 위치에 있도록 녹색과 노란색을 스택으로 밀어야합니다. 그래서 그것은 여전히 ​​최종 결과에 나타납니다
Izkata

내가 이해하지 못하는 또 다른 생각은 먼저 "스택이 비어있는 경우 : 시작하거나 시작하기 전에 첫 번째 색을 찾으십시오"라고 말한 다음 코드 샘플에서 "# Stack is empty. 다음을 찾으십시오. 시작점은 0 이상 "입니다. 그래서 한 번 전에 그리고 나중에 한 번
Guillaume07

1
@ Guillaume07 네, 오타가 올바른 코드 버전은 코드 블록에 두 번 있습니다 (두 번째는 "Stack is empty"로 시작하는 하단 근처 주석입니다). 그 글 머리 기호를 편집했습니다.
이즈 카타

3

이 솔루션은 가장 단순 해 보입니다. (또는 적어도 이해하기 가장 쉬운 방법)

필요한 것은 두 범위를 빼는 기능입니다. 다시 말해, 이것을 줄 것입니다 :

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

어느 정도 간단합니다. 그런 다음 가장 낮은 범위에서 시작하여 각 범위반복하고 각 범위에서 그 위의 모든 범위를 차례로 뺍니다. 그리고 거기 있습니다.


다음은 파이썬에서 범위 감산기의 구현입니다.

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

이 함수를 사용하면 나머지는 다음과 같이 수행 할 수 있습니다. ''범위 '는 파이썬 키워드이므로'범위 '는 범위를 의미합니다.

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]]]정확합니다.


마지막에 출력이 문제의 예상 출력과 일치하지 않습니다 ...
Izkata

@Izkata Gosh, 나는 부주의했다. 그것은 다른 테스트의 결과물 일 것입니다. 고침
Jollywatt

2

데이터가 실제로 샘플 데이터와 범위가 비슷한 경우 다음과 같은 맵을 만들 수 있습니다.

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

이것은 매우 좋은 해결책입니다. 그러나 임의의 부동 범위를 관리 할 있는보다 일반적인 솔루션을 찾고 있습니다 ... (563.807-770.100과 같은 경우에는 최선이 아닐 것입니다)
Jollywatt

1
값을 반올림하고 맵을 생성하지만 가장자리의 위치를 ​​두 가지 색상으로 표시하여 일반화 할 수 있다고 생각합니다. 그런 다음 두 가지 색상의 위치가 표시되면 원래 데이터로 돌아가 경계를 결정하십시오.
로봇 고트

2

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

applySet이미 적용된 모든 범위 중 하나 를 취하고 겹침을 찾은 다음 겹침을 뺀 새 세트와 겹침을 더한 값과 새 범위 및 새로 분할 된 범위를 반환합니다. 각 입력 범위로 foldLeft반복해서 호출 apply합니다.


0

시작으로 정렬 된 범위 세트를 유지하십시오. 모든 것을 포함하는 범위를 추가하십시오 (-oo .. + oo). 범위 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.