2013-09-30 18 views
14

ich für einen Ansatz suchen, um mehrere Listen auf folgende Weise zu verbinden:Kombinieren mehrerer Listen von beliebiger Länge

ListA a b c 
ListB 1 2 3 4 
ListC + # * § % 
.. 
.. 
.. 

Resulting List: a 1 + b 2 # c 3 * 4 § % 

In Worten: Die Elemente in der angegebenen Reihenfolge auf den ersten Liste beginnen, sich in der Ergebnisliste kombiniert. Eine willkürliche Menge von Eingabelisten könnte dort in der Länge variieren.

Ich habe mehrere Ansätze mit Varianten von Zip, Schiebe-Iteratoren verwendet, aber keine funktionierte und vor allem für unterschiedliche Listenlängen gesorgt. Es hat eine elegante Weise, in scala sein;)

+2

Zip ist eine natürliche dafür. Was hast du versucht, das hat nicht funktioniert? – itsbruce

+0

Zip erlaubt mir 2 Listen zu kombinieren. mehrere Listen zu haben, macht die Verwendung für mich zumindest komplizierter (begginer in scala) und es erlaubt auch nicht, Listen mit keiner passenden Länge zu kombinieren. Sobald eine Liste endet, werden weitere Elemente aus den anderen Listen nicht gezippt. –

+5

@itsbruce: Das ist überhaupt nicht trivial mit 'zip' und sogar mit' zipAll', 'zipWith', etc. wäre es ein wenig knifflig. –

Antwort

19
val lists = List(ListA, ListB, ListC) 

lists.flatMap(_.zipWithIndex).sortBy(_._2).map(_._1) 

Es ist ziemlich selbsterklärend. Es reißt einfach jeden Wert mit seiner Position auf seiner jeweiligen Liste, sortiert nach Index und zieht dann die Werte zurück.

+0

Ich habe immer das Gefühl, dass ich mich konzentrieren muss, bevor Luigi kommt ... – itsbruce

+1

Ich habe gerade gehört. – joescii

+1

Ist sortBy garantiert stabil? –

5

Hier ist, wie ich es tun würde:

class ListTests extends FunSuite { 
    test("The three lists from his example") { 
    val l1 = List("a", "b", "c") 
    val l2 = List(1, 2, 3, 4) 
    val l3 = List("+", "#", "*", "§", "%") 

    // All lists together 
    val l = List(l1, l2, l3) 

    // Max length of a list (to pad the shorter ones) 
    val maxLen = l.map(_.size).max 

    // Wrap the elements in Option and pad with None 
    val padded = l.map { list => list.map(Option(_)) ++ Stream.continually(None).take(maxLen - list.size) } 

    // Transpose 
    val trans = padded.transpose 

    // Flatten the lists then flatten the options 
    val result = trans.flatten.flatten 

    // Viola 
    assert(List("a", 1, "+", "b", 2, "#", "c", 3, "*", 4, "§", "%") === result) 
    } 
} 
+1

Sie könnten interessiert sein an der integrierten 'PadTo'-Methode, die Sie ermöglicht simply: 'val gepolstert = l.map (_. map (Option (_)). padTo (maxLen, None))' –

+0

Sehr cool, @LuigiPlinge. Danke für den Tipp! – joescii

1

Hier ist eine kleine rekursive Lösung. Werde später einen Strom Ansatz zeigen ...

def flatList(lists: List[List[Any]]) = { 
    def loop(output: List[Any], xss: List[List[Any]]): List[Any] = (xss collect { case x :: xs => x }) match { 
    case Nil => output 
    case heads => loop(output ::: heads, xss.collect({ case x :: xs => xs })) 
    } 
    loop(List[Any](), lists) 
} 

Und hier ist ein einfacher Strom Ansatz, der mit einer beliebigen Abfolge von Sequenzen bewältigen kann jeder potentiell unendlich lange.

def flatSeqs[A](ssa: Seq[Seq[A]]): Stream[A] = { 
    def seqs(xss: Seq[Seq[A]]): Stream[Seq[A]] = xss collect { case xs if !xs.isEmpty => xs } match { 
    case Nil => Stream.empty 
    case heads => heads #:: seqs(xss collect { case xs if !xs.isEmpty => xs.tail }) 
    } 
    seqs(ssa).flatten 
} 

Ich bin sicher, Luigi dies zu einem Einzeiler schrumpfen könnte;) Ich habe es etwa so klein, wie ich bekommen kann.

0

Hier ist etwas kurz, aber nicht sehr effizient:

def heads[A](xss: List[List[A]]) = xss.map(_.splitAt(1)).unzip 
def interleave[A](xss: List[List[A]]) = Iterator. 
    iterate(heads(xss)){ case (_, tails) => heads(tails) }. 
    map(_._1.flatten). 
    takeWhile(! _.isEmpty). 
    flatten.toList 
1

Hier ist eine zwingende Lösung, wenn Effizienz an erster Stelle steht:

def combine[T](xss: List[List[T]]): List[T] = { 
    val b = List.newBuilder[T] 
    var its = xss.map(_.iterator) 
    while (!its.isEmpty) { 
    its = its.filter(_.hasNext) 
    its.foreach(b += _.next) 
    } 
    b.result 
} 
1

Sie padTo verwenden können, transpose und flatten um eine gute Wirkung hier:

lists.map(_.map(Some(_)).padTo(lists.map(_.length).max, None)).transpose.flatten.flatten 
Verwandte Themen