2012-02-09 8 views
7

bir Listesi'nden sayı-aralıkları örtüşen birleştirmek için:işlevsel tüm örtüşen aralıkları kaybolur o ben çok birleştirmek için gereken menzil-nesnelerin bir dizi var

3 40 
    1 45 
    2 50 
70 75 
75 90 
80 85 
100 200 
: Burada

case class Range(from:Int, to:Int) 

val rangelist = List(Range(3, 40), Range(1, 45), Range(2, 50), etc) 

aralıkları olduğunu

1 50 
70 90 
100 200 

Zorunlu Algoritma:

kez biz alacağı bitmiş

  1. İlk aralıktaki nesneyi Pop() ve diğer tüm aralıklar ile karşılaştırarak listenin geri kalanı boyunca yineleyin.
  2. Üst üste binen bir öğe varsa, bunları birleştirir (Bu yeni bir Aralık örneği verir) ve kaynak listeden 2 birleştirme adayını siler.
  3. Listenin sonunda Range nesnesini (birleştirme yoluyla sayısız kez değişmiş olabilir) son sonuç listesine ekleyin.
  4. Bunu, kalan öğelerin bir sonraki kısmı ile tekrarlayın.
  5. Kaynak listesi boşaltıldıktan sonra işimiz bitti.

imperatively bir daha işlevsel bir yaklaşım olup olmadığını Yani, merak ediyorum

vb geçici değişkenlerin bir sürü endeksli döngüler oluşturmalısınız Bunu yapmak için?

İlk bakışta kaynak koleksiyonunun, pop() PLUS sağlamada bir Yığın gibi davranabilmesi gerekir; bu, öğeleri yineleme sırasında indeksle silme yeteneğini verir, ancak daha sonra bu artık işlevsel olmaz.

cevap

8

deneyin kuyruk özyineleme. (Ek açıklama, kuyruk özyineleme optimizasyonu gerçekleşmezse sizi uyarmak için gereklidir; derleyici, açıklama ekleyip eklemeyebilirse, derleyici bunu yapar.)

import annotation.{tailrec => tco} 
@tco final def collapse(rs: List[Range], sep: List[Range] = Nil): List[Range] = rs match { 
    case x :: y :: rest => 
    if (y.from > x.to) collapse(y :: rest, x :: sep) 
    else collapse(Range(x.from, x.to max y.to) :: rest, sep) 
    case _ => 
    (rs ::: sep).reverse 
} 
def merge(rs: List[Range]): List[Range] = collapse(rs.sortBy(_.from)) 
+0

Bu, 'rs' menzil başlangıç ​​öğelerine göre sıralı olduğunu varsayar. Sadece x'in y.from'u içermesi daha iyi olur. –

+1

"birleştirme", "daraltma" durumuna geçer ve geçer. Bunu yapmazsanız, çalışma zamanınız "O (n log n)' yerine "O (n^2)' dir. Olması gerektiği gibi. –

+0

Duh! 'Birleştirme' yöntemini fark etmedim ... –

8

Ben bulmaca bu tür seviyorum:

case class Range(from:Int, to:Int) { 
    assert(from <= to) 

    /** Returns true if given Range is completely contained in this range */ 
    def contains(rhs: Range) = from <= rhs.from && rhs.to <= to 

    /** Returns true if given value is contained in this range */ 
    def contains(v: Int) = from <= v && v <= to 
} 

def collapse(rangelist: List[Range]) = 
    // sorting the list puts overlapping ranges adjacent to one another in the list 
    // foldLeft runs a function on successive elements. it's a great way to process 
    // a list when the results are not a 1:1 mapping. 
    rangelist.sortBy(_.from).foldLeft(List.empty[Range]) { (acc, r) => 
    acc match { 
     case head :: tail if head.contains(r) => 
     // r completely contained; drop it 
     head :: tail 
     case head :: tail if head.contains(r.from) => 
     // partial overlap; expand head to include both head and r 
     Range(head.from, r.to) :: tail 
     case _ => 
     // no overlap; prepend r to list 
     r :: acc 
    } 
    } 
+1

Mükemmel teşekkürler. Sıralı sipariş fişine geri almak için geri çekmeniz gerekiyor. – monkjack

4

İşte benim çözüm:

def merge(ranges:List[Range]) = ranges 
    .sortWith{(a, b) => a.from < b.from || (a.from == b.from && a.to < b.to)} 
    .foldLeft(List[Range]()){(buildList, range) => buildList match { 
    case Nil => List(range) 
    case head :: tail => if (head.to >= range.from) { 
     Range(head.from, head.to.max(range.to)) :: tail 
    } else { 
     range :: buildList 
    } 
    }} 
    .reverse 

merge(List(Range(1, 3), Range(4, 5), Range(10, 11), Range(1, 6), Range(2, 8))) 
//List[Range] = List(Range(1,8), Range(10,11)) 
İlgili konular