Çakışan aralıkları düzleştirmek için algoritma


16

Potansiyel olarak çakışan sayısal aralıkların bir listesini düzleştirmek (bölmek) için güzel bir yol arıyorum. Sorun bu sorununkine çok benzer: Çakışan tarih aralıklarını ve diğerlerini bölmenin en hızlı yolu .

Ancak, aralıklar sadece tamsayılar değildir ve Javascript veya Python, vb. İçinde kolayca uygulanabilecek iyi bir algoritma arıyorum.

Örnek Veriler: Örnek veriler

Örnek Çözüm: resim açıklamasını buraya girin

Bu bir kopya ise özür dilerim, ama henüz bir çözüm bulamıyorum.


Yeşilin mavinin üstünde, sarı ve turuncu altında olduğunu nasıl belirlersiniz? Renk aralıkları sırayla uygulanıyor mu? Durum buysa, algoritma bariz görünür; sadece ... erm, renk aralıklarını sırayla uygulayın.
Robert Harvey

1
Evet, sırayla uygulanır. Ama sorun bu; aralıkları nasıl 'uygularsınız'?
Jollywatt

1
Sıklıkla renk ekliyor / kaldırıyor musunuz veya sorgu hızı için optimizasyon yapmanız mı gerekiyor? Genellikle kaç "aralığınız" olur? 3? 3000?
Telastyn

Çok sık renk eklemeyecek / kaldıramayacaksınız ve 4-20 basamaklı hassasiyetle 10-20 aralık arasında herhangi bir yer olacak. Bu yüzden set yöntemi oldukça uygun değildir, çünkü setlerin 1000+ öğe uzunluğunda olması gerekir. Gittiğim yöntem Python'da gönderdiğim yöntem.
Jollywatt

Yanıtlar:


10

Hangi rengi kullandığınızı takip etmek için bir yığın kullanarak soldan sağa doğru yürüyün. Ayrık bir harita yerine, veri kümenizdeki 10 sayıyı kesme noktası olarak kullanın.

Boş bir yığınla başlayıp start0 olarak ayarlayarak , sonuna kadar döngü yapalım:

  • Yığın boşsa:
    • İlk renkte veya daha sonra başlayan ilk rengi arayın startve alt sıradaki tüm renkleri yığının üzerine itin. Düzleştirilmiş listenizde, o rengin başlangıcını işaretleyin.
  • başka (Boş değilse):
    • Daha yüksek dereceli renkler için veya daha sonra bir sonraki başlangıç ​​noktasını bulun start ve geçerli rengin sonunu bulun
      • Bir sonraki renk önce başlarsa, onu ve yığına giden yolda başka bir şeyi itin. Geçerli rengin sonunu bunun başlangıcı olarak güncelleyin ve bu rengin başlangıcını düzleştirilmiş listeye ekleyin.
      • Hiçbiri yoksa ve geçerli renk önce biterse, start , bu rengin sonuna , yığından çıkarın ve bir sonraki en yüksek sıradaki rengi kontrol edin
        • Bir startsonraki rengin aralığındaysa, bu rengi şuradan başlayarak düzleştirilmiş listeye ekleyin:start .
        • Yığın boşalırsa, döngüye devam edin (ilk madde işaret noktasına geri dönün).

Bu, örnek verileriniz göz önüne alındığında zihinsel bir çalışmadır:

# 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.

"Yığına giden yolda başka bir şey" ile ne demek istiyorsun?
Guillaume07

1
@ Guillaume07 Geçerli ve seçilen sonraki başlangıç ​​arasında herhangi bir sıralama. Örnek veriler bunu göstermez, ancak sarı yeşilden önce başlamak için kaydırıldığını hayal edin - hem yeşil hem de sarıyı yığının üzerine itmeniz gerekir, böylece sarı sona erdiğinde, yeşilin ucu hala yığınta doğru yerde olur. sonuçta hala görünüyor
Izkata

Anlamadığım başka bir düşünce, lütfen, neden öncelikle "Yığın boşsa: Başlamadan önce veya başlamadan önce ilk rengi arayın," sonra yorumladığınız kod örneğinde "# Yığın boş. msgstr "% 0 başlangıç ​​noktası". Bir kez önce ve sonra bir kez
Guillaume07

1
@ Guillaume07 Evet, bir yazım hatası, doğru sürüm iki kez kod bloğundadır (ikincisi, "Yığın boş." Bu mermi noktasını düzenledim.
Izkata

3

Bu çözüm en basit gibi görünüyor. (Veya en azından kavraması en kolay olanı)

Tek gereken iki aralığı çıkarmak için bir fonksiyondur. Başka bir deyişle, bunu verecek bir şey:

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

Bu yeterince basit. Daha sonra , en alttan başlayarak her bir aralıkta tekrarlayabilirsiniz ve her biri için, üstündeki tüm aralıkları sırayla çıkarabilirsiniz. İşte buyur.


İşte Python'daki menzil çıkarıcısının bir uygulaması:

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

Bu işlevi kullanarak, geri kalanı şu şekilde yapılabilir: ('aralık' bir aralık anlamına gelir, çünkü 'aralık' bir Python anahtar kelimesidir)

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

Bu [['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]]]doğru olanı verir .



@Izkata Gosh, dikkatsizdim. Bu başka bir testten çıktı olmalıydı. Şimdi düzeltildi, teşekkürler
Jollywatt

2

Veriler gerçekte örnek verilerinizle gerçekten benzerse, şöyle bir harita oluşturabilirsiniz:

map = [0 .. 150]

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

Ardından aralıkları oluşturmak için bu haritayı inceleyin

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

Çalışmak için, değerler örnek verilerinizde olduğu gibi nispeten küçük bir aralıkta olmalıdır.

Düzenleme: gerçek kayan noktalarla çalışmak için haritayı kullanarak yüksek düzeyde bir eşleme oluşturun ve ardından sınırları oluşturmak için orijinal verilere bakın.

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

Bu çok güzel bir çözüm, daha önce de karşılaştım. Ancak, ben bir daha genel bir çözüm arıyorum olabilir (bu 563.807 gibi bir şey için iyi olmaz - 770,100) herhangi bir keyfi şamandıra aralıkları ... yönetmek
Jollywatt

1
Değerleri yuvarlayarak ve haritayı oluşturarak genelleştirebilirsiniz, ancak kenarlardaki bir konumu iki renge sahip olarak işaretleyebilirsiniz. Ardından iki renkli bir konum gördüğünüzde, sınırı belirlemek için orijinal verilere geri dönün.
Robot Gort

2

İşte Scala'da nispeten basit bir çözüm. Başka bir dile geçmek çok zor olmamalı.

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

applySetzaten uygulanmış olan tüm aralıkları alır , çakışmaları bulur, daha sonra çakışmaları ve yeni aralığı ve yeni bölünmüş aralıkları ekleyerek yeni bir küme döndürür. her giriş aralığını foldLefttekrar tekrar arar apply.


0

Sadece başlangıca göre sıralanmış bir dizi aralık tutun. Her şeyi kapsayan aralık ekleyin (-oo .. + oo). Aralık r eklemek için:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.