How can i interleave elements of 2 lists in scala
Asked Answered
S

6

8

I'd like to combine two Lists of arbitrary length in such a way that elements from the 2nd List are inserted after every n-th element into the 1st List. If the 1st List length is less than n, no insertion results.

So having

val a = List(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)
val b = List(101,102,103)
val n = 3 

I want the resulting List to look like this:

List(1,2,3,101,4,5,6,102,7,8,9,103,10,11,12,13,14,15)

I have this working using a foldLeft on a, but I'm wondering how the same logic could be accomplished using Scalaz?

Thanks for everyone's answers. They were all helpful to me !

Siberson answered 5/7, 2012 at 18:27 Comment(0)
P
8

Meet my apomorphism friend

def apo[A, B](v: B)(f: B => Option[(A, Either[B, List[A]])]): List[A] = f(v) match {
   case None => Nil
   case Some((a, Left(b)))   => a :: apo(b)(f)
   case Some((a, Right(as))) => a :: as 
}

Your interleave method can be implemented like this

def interleave[A](period: Int, substitutes: List[A], elems: List[A]): List[A] =
  apo((period, substitutes, elems)){
    case (_, _, Nil)       => None
    case (_, Nil, v :: vs) => Some((v, Right(vs)))
    case (0, x :: xs, vs)  => Some((x, Left((period, xs, vs))))
    case (n, xs, v :: vs)  => Some((v, Left((n - 1, xs, vs))))  
  }

This gives:

scala> interleave(3, b, a)
res1: List[Int] = List(1, 2, 3, 101, 4, 5, 6, 102, 7, 8, 9, 103 , 10, 11 , 12, 13, 14, 15)

The good point is the computation ends when a or b are Nil unlike foldLeft. The bad news is interleave is no more tail recursive

Pastille answered 6/7, 2012 at 10:48 Comment(2)
Interesting. If i understand the term correctly, apomorhism and tail recursion are sort of mutually-exclusive ?Siberson
Right. You may have SOE with "thousands sized" lists. Note with your example, apomorphism is more efficient than catamorphism (foldLeft) because it'll stop just after b list turns to NilPastille
P
5

This gets very simple with zipAll. Moreover, you are able to choose the amount of elements of the second array (in this case 1):

val middle = b.grouped(1).toList
val res = a.grouped(n).toList.zipAll(middle, Nil, Nil)
res.filterNot(_._1.isEmpty).flatMap(x => x._1 ++ x._2)

Or if you prefer, one-liner:

a.grouped(n).toList.zipAll(b.map(List(_)), Nil, Nil).filterNot(_._1.isEmpty).flatMap(x => x._1 ++ x._2)

You can also make an implicit class, so you could call a.interleave(b, 3) or with an optional thrid parameter a.interleave(b, 3, 1).

Photographer answered 14/6, 2014 at 16:0 Comment(2)
I have posted one more alternative answer.Photographer
Seems like the cleanest - Are there any pitfalls to this compared to a fold or direct recursion ? I think it might be in between fold and recursion in terms of performanceWattle
C
4

How about this:

 def process[A](xs: List[A], ys: List[A], n: Int): List[A] = 
   if(xs.size <= n || ys.size == 0) xs
   else xs.take(n):::ys.head::process(xs.drop(n),ys.tail,n)

scala> process(a,b,n) 
res8: List[Int] = List(1, 2, 3, 101, 4, 5, 6, 102, 7, 8, 9, 103, 10, 11, 12, 13, 14, 15)

scala> val a = List(1,2,3,4,5,6,7,8,9,10,11) 
a: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

scala> process(a,b,n) 
res9: List[Int] = List(1, 2, 3, 101, 4, 5, 6, 102, 7, 8, 9, 103, 10, 11)

scala> val a = List(1,2,3,4,5,6,7,8,9) 
a: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> process(a,b,n) 
res10: List[Int] = List(1, 2, 3, 101, 4, 5, 6, 102, 7, 8, 9)

scala> val a = List(1,2,3,4,5,6,7,8) 
a: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)

scala> process(a,b,n) 
res11: List[Int] = List(1, 2, 3, 101, 4, 5, 6, 102, 7, 8)

Your request is "If the 1st List length is less than n, no insertion results", then my code should change to:

 def process[A](xs: List[A], ys: List[A], n: Int): List[A] = 
   if(xs.size < n || ys.size == 0) xs
   else xs.take(n):::ys.head::process(xs.drop(n),ys.tail,n)
Carty answered 6/7, 2012 at 3:8 Comment(0)
L
3

What about:

def interleave[A](xs: Seq[A], ys: Seq[A], n: Int): Seq[A] = {
  val iter = xs grouped n
  val coll = iter zip ys.iterator flatMap { case (xs, y) => if (xs.size == n) xs :+ y else xs }
  (coll ++ iter.flatten).toIndexedSeq
}

scala> interleave(a, b, n)
res34: Seq[Int] = Vector(1, 2, 3, 101, 4, 5, 6, 102, 7, 8, 9, 103, 10, 11, 12, 13, 14, 15)

scala> interleave(1 to 2, b, n)
res35: Seq[Int] = Vector(1, 2)

scala> interleave(1 to 6, b, n)
res36: Seq[Int] = Vector(1, 2, 3, 101, 4, 5, 6, 102)

scala> interleave(1 to 7 b, n)
res37: Seq[Int] = Vector(1, 2, 3, 101, 4, 5, 6, 102, 7)

scala> interleave(1 to 7, Nil, n)
res38: Seq[Int] = Vector(1, 2, 3, 4, 5, 6, 7)

scala> interleave(1 to 7, Nil, -3)
java.lang.IllegalArgumentException: requirement failed: size=-3 and step=-3, but both must be positive

It is short, but it is not the most efficient solution. If you call it with Lists for example, the append-operations (:+ and ++) are expensive (O(n)).

EDIT: I'm sorry. I notice now, that you want to have a solution with Scalaz. Nevertheless the answer may be useful therefore I won't delete it.

Laclair answered 5/7, 2012 at 20:50 Comment(3)
And your solution didnot performance correctly if a.size > 3(b.size+1)Carty
@Antoras: Yeah i was looking for a more elegant solution than mine using foldLeft. Yours works for the example. However, and it is my fault for picking a bad example, the lists could be of arbitrary length. So a.grouped(n).length won't necessarily equal b.lengthSiberson
@marcin: Changed my code a little bit. Hopefully, it works now as expected.Laclair
C
3

Without Scalaz and recursion.

scala> a.grouped(n).zip(b.iterator.map{ Some(_) } ++ Iterator.continually(None)).flatMap{ case (as, e) => if (as.size == n) as ++ e else as }.toList
res17: List[Int] = List(1, 2, 3, 101, 4, 5, 6, 102, 7, 8, 9, 103, 10, 11, 12, 13, 14, 15)

Generic way:

def filled[T, A, That](a: A, b: Seq[T], n: Int)(implicit bf: CanBuildFrom[A, T, That], a2seq: A => Seq[T]): That = {
  val builder = bf()
  builder.sizeHint(a, a.length / n)
  builder ++= a.grouped(n).zip(b.iterator.map{ Some(_) } ++ Iterator.continually(None)).flatMap{ case (as, e) => if(as.size == n ) as ++ e else as }
  builder.result()
}

Usage:

scala> filled("abcdefghijklmnopqrstuvwxyz", "1234", 3)
res0: String = abc1def2ghi3jkl4mnopqrstuvwxyz

scala> filled(1 to 15, 101 to 103, 3)
res1: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 101, 4, 5, 6, 102, 7, 8, 9, 103, 10, 11, 12, 13, 14, 15)

scala> filled(1 to 3, 101 to 103, 3)
res70: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 101)

scala> filled(1 to 2, 101 to 103, 3)
res71: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2)
Collective answered 6/7, 2012 at 5:52 Comment(3)
map+flatten is the same as flatMapLaclair
@senia: i like the padding with None idea, clever. However if a.length is less than n, the insertion will occur. I want it to occur only if a.length >= nSiberson
@marcin, It's hard to express such complex logic thru compositionally. It's better to go for recursion/loops in such cases IMO.Shifflett
S
0

here's the one you want:

import scala.annotation.tailrec

@tailrec
final def interleave[A](base: Vector[A], a: List[A], b: List[A]): Vector[A] = a match {
  case elt :: aTail => interleave(base :+ elt, b, aTail)
  case _ => base ++ b
}

...

interleave(Vector.empty, a, b)
Sequential answered 23/3, 2017 at 18:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.