อัลกอริทึมสำหรับช่วงการทับซ้อนของแบนราบ


16

ฉันกำลังมองหาวิธีที่ดีในการทำให้แบน (แยก) รายการของช่วงตัวเลขที่อาจทับซ้อนกัน ปัญหาคล้ายกันมากกับคำถามนี้: วิธีที่เร็วที่สุดในการแยกช่วงวันที่ซ้อนทับกันและอื่น ๆ อีกมากมาย

อย่างไรก็ตามช่วงนั้นไม่ได้เป็นจำนวนเต็มเท่านั้นและฉันกำลังมองหาอัลกอริทึมที่เหมาะสมซึ่งสามารถนำไปใช้งานได้อย่างง่ายดายใน Javascript หรือ Python เป็นต้น

ข้อมูลตัวอย่าง: ตัวอย่างข้อมูล

ตัวอย่างการแก้ไข: ป้อนคำอธิบายรูปภาพที่นี่

ขออภัยหากนี่เป็นสิ่งที่ซ้ำกัน แต่ฉันยังหาวิธีแก้ปัญหาไม่ได้


คุณทราบได้อย่างไรว่าสีเขียวนั้นอยู่ด้านบนของสีน้ำเงิน แต่อยู่ภายใต้สีเหลืองและสีส้ม มีการใช้ช่วงสีตามลำดับหรือไม่ หากเป็นเช่นนั้นอัลกอริทึมจะดูเหมือนชัดเจน เพียงแค่ ... ก่อนอื่นให้ใช้ช่วงสีตามลำดับ
Robert Harvey

1
ใช่พวกเขาถูกนำไปใช้ในการสั่งซื้อ แต่นั่นเป็นปัญหา - คุณจะใช้ 'ช่วง' อย่างไร
Jollywatt

1
คุณมักจะเพิ่ม / ลบสีหรือคุณต้องการเพิ่มประสิทธิภาพสำหรับความเร็วการสืบค้นหรือไม่? โดยปกติคุณจะมี "ช่วง" จำนวนเท่าใด 3 หรือไม่? 3000?
Telastyn

จะไม่เพิ่ม / ลบสีบ่อยมากและจะมีที่ใดก็ได้ระหว่าง 10-20 ช่วงโดยมีความแม่นยำ 4 หลัก นั่นเป็นเหตุผลที่วิธีการตั้งค่านั้นไม่เหมาะสมเนื่องจากชุดจะต้องมีความยาว 1,000+ รายการ วิธีที่ฉันไปด้วยคือวิธีที่ฉันโพสต์ใน Python
Jollywatt

คำตอบ:


10

เดินจากซ้ายไปขวาใช้สแต็คเพื่อติดตามสีที่คุณใช้ แทนที่จะเป็นแผนที่แบบไม่ต่อเนื่องใช้ตัวเลข 10 ตัวในชุดข้อมูลของคุณเป็นจุดพัก

เริ่มต้นด้วยสแต็กเปล่าและตั้งค่าstartเป็น 0 วนซ้ำจนกว่าเราจะถึงจุดสิ้นสุด:

  • หากสแต็กว่างเปล่า:
    • มองหาสีแรกเริ่มที่หรือหลังจากstartนั้นกดและสีที่มีอันดับต่ำกว่าทั้งหมดลงบนสแต็ก ในรายการที่แบนให้ทำเครื่องหมายจุดเริ่มต้นของสีนั้น
  • อื่น (ถ้าไม่ว่าง):
    • ค้นหาจุดเริ่มต้นถัดไปสำหรับสีอันดับที่สูงขึ้นในหรือหลัง startและค้นหาจุดสิ้นสุดของสีปัจจุบัน
      • หากสีถัดไปเริ่มขึ้นก่อนให้ดันสีและสิ่งอื่น ๆ ตามขวางลงบนสแต็ก อัพเดตจุดสิ้นสุดของสีปัจจุบันเป็นจุดเริ่มต้นของสีนี้และเพิ่มจุดเริ่มต้นของสีนี้ลงในรายการที่แบน
      • หากไม่มีและสีปัจจุบันสิ้นสุดลงก่อนให้ตั้งค่า startเป็นสีสุดท้ายแล้วนำออกมาจากสแต็กและตรวจสอบสีที่มีอันดับสูงสุดถัดไป
        • หากอยู่ในช่วงสีต่อไปเพิ่มสีนี้ลงในรายการบี้เริ่มต้นที่startstart
        • หากสแต็กเปล่าให้ทำต่อไปเรื่อย ๆ (ย้อนกลับไปที่สัญลักษณ์แรก)

นี่คือการใช้งานที่ได้รับจากข้อมูลตัวอย่างของคุณ:

# 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 ว่างเปล่ามองหาสิ่งต่อไป จุดเริ่มต้นที่ 0 หรือใหม่กว่า " ดังนั้นเมื่อมันมาก่อนและอีกครั้งหลังจากนั้น
Guillaume07

1
@ Guillaume07 ใช่พิมพ์ผิดรุ่นที่ถูกต้องอยู่ในบล็อกรหัสสองครั้ง (ที่สองคือความคิดเห็นที่อยู่ใกล้ด้านล่างที่เริ่มต้น "สแต็กว่างเปล่า") ฉันแก้ไขจุด bullet แล้ว
Izkata

3

วิธีนี้ดูเหมือนง่ายที่สุด (หรืออย่างน้อยก็ง่ายที่สุดที่จะเข้าใจ)

สิ่งที่ต้องการคือฟังก์ชั่นในการลบสองช่วง กล่าวอีกนัยหนึ่งคือสิ่งที่จะทำให้:

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

การใช้ฟังก์ชั่นนี้ส่วนที่เหลือสามารถทำได้ดังนี้: (A 'span' หมายถึงช่วงเนื่องจาก 'range' เป็นคำสำคัญ 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]]]ซึ่งถูกต้อง


ผลลัพธ์ของคุณในตอนท้ายไม่ตรงกับผลลัพธ์ที่คาดหวังในคำถาม ...
Izkata

@ อิซกาตะเอ้ยฉันประมาท นั่นจะต้องเป็นผลลัพธ์จากการทดสอบอื่น แก้ไขแล้วขอบคุณ
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
ฉันคิดว่าคุณสามารถสรุปได้โดยการปัดเศษค่าและสร้างแผนที่ แต่ทำเครื่องหมายตำแหน่งบนขอบว่ามีสองสี จากนั้นเมื่อคุณเห็นตำแหน่งที่มีสองสีให้กลับไปที่ข้อมูลต้นฉบับเพื่อกำหนดขอบเขต
Gort the Robot

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

applyใช้เวลาในช่วงSetทั้งหมดที่มีการใช้ไปแล้วค้นหาการทับซ้อนจากนั้นส่งคืนชุดใหม่ลบด้วยการทับซ้อนและบวกช่วงใหม่และช่วงแยกใหม่ 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.