How to split a list by another list in Scala - scala

I am new to Scala and I need a solution to my problem. Imagine I have these lists:
val list1 = List(1,2,3,4,5,6,7,8,9,10,11)
val list2 = List(6,5)
And my desire is to split the first list in a List of Lists using list2 to map it.
So the result would be something like this:
val result = List(List(1,2,3,4,5,6), List(7,8,9,10,11))
If my list2 was like this:
val list2 = List(4,4,3)
The result would then be:
val result = List(List(1,2,3,4),List(5,6,7,8),List(9,10,11))
What is the best way to do this?

You can use a combination of scanLeft and splitAt:
list2.scanLeft((List.empty[Int], list1)) {
case ((_, remaining), i) => remaining.splitAt(i)
}.unzip._1.tail
Gives:
List(List(1, 2, 3, 4, 5, 6), List(7, 8, 9, 10, 11))
Brief explanation: each step of scanLeft saves each piece of list1 and the remaining elements of list1 in a tuple. The remaining elements are split according to the size i of next chunk, where i is an element of list2. In the end, all the "remainders" are thrown away by unzip._1, and the first empty dummy-element is removed by tail.
Note that since the list structure is immutable & persistent, the intermediate results stored in the second component of the tuple in each step do not take up any extra space, they are mere references to tails of list1.

If what you're doing is using the second list to be the indexes on the first list:
def indexedSplit[A](myList: List[A], indx: List[Int], acc: List[List[A]]): List[List[A]] = indx match{
case Nil => acc.filter(_ != Nil).reverse
case x :: xs =>
val (h, t) = myList.splitAt(x)
indexedSplit(t, xs, h :: acc)
}
wherein you recursively walk the index list and split the list under operation at each of those points. Finally you filter out empty lists and reverse the order since you've accumulated in reverse order.

def foo[A](xs: List[A], ys: List[Int]): List[List[A]] = {
val (result, _) = ys.foldLeft((List.empty[List[A]], xs)) { case ((acc, remaining), i) =>
(remaining.take(i) :: acc, remaining.drop(i))
}
result.reverse
}
test("1") {
val list1 = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
val list2 = List(6, 5)
val result = List(List(1, 2, 3, 4, 5, 6), List(7, 8, 9, 10, 11))
foo(list1, list2) shouldBe result
}
test("2") {
val list1 = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
val list2 = List(4, 4, 3)
val result = List(List(1, 2, 3, 4), List(5, 6, 7, 8), List(9, 10, 11))
foo(list1, list2) shouldBe result
}

Related

Merge two collections by interleaving values

How can I merge two lists / Seqs so it takes 1 element from list 1, then 1 element from list 2, and so on, instead of just appending list 2 at the end of list 1?
E.g
[1,2] + [3,4] = [1,3,2,4]
and not [1,2,3,4]
Any ideas? Most concat methods I've looked at seem to do to the latter and not the former.
Another way:
List(List(1,2), List(3,4)).transpose.flatten
So maybe your collections aren't always the same size. Using zip in that situation would create data loss.
def interleave[A](a :Seq[A], b :Seq[A]) :Seq[A] =
if (a.isEmpty) b else if (b.isEmpty) a
else a.head +: b.head +: interleave(a.tail, b.tail)
interleave(List(1, 2, 17, 27)
,Vector(3, 4)) //res0: Seq[Int] = List(1, 3, 2, 4, 17, 27)
You can do:
val l1 = List(1, 2)
val l2 = List(3, 4)
l1.zip(l2).flatMap { case (a, b) => List(a, b) }
Try
List(1,2)
.zip(List(3,4))
.flatMap(v => List(v._1, v._2))
which outputs
res0: List[Int] = List(1, 3, 2, 4)
Also consider the following implicit class
implicit class ListIntercalate[T](lhs: List[T]) {
def intercalate(rhs: List[T]): List[T] = lhs match {
case head :: tail => head :: (rhs.intercalate(tail))
case _ => rhs
}
}
List(1,2) intercalate List(3,4)
List(1,2,5,6,6,7,8,0) intercalate List(3,4)
which outputs
res2: List[Int] = List(1, 3, 2, 4)
res3: List[Int] = List(1, 3, 2, 4, 5, 6, 6, 7, 8, 0)

Splitting List into List of List

How can I convert:
List(1,1,1,1,4,2,2,2,2)
into:
List(List(1,1,1,1), List(2,2,2,2))
Thought this would be the easiest way to show what I'm looking for. I am having a hard time trying to find the most functional way to do this with a large list that needs to be separated at a specific element. This element does not show up in the new list of lists. Any help would be appreciated!
If you want to support multiple instances of that separator, you can use foldRight with some list "gymnastics":
// more complex example: separator (4) appears multiple times
val l = List(1,1,1,1,4,2,2,2,2,4,5,6,4)
val separator = 4
val result = l.foldRight(List[List[Int]]()) {
case (`separator`, res) => List(Nil) ++ res
case (v, head :: tail) => List(v :: head) ++ tail
case (v, Nil) => List(List(v))
}
// result: List(List(1, 1, 1, 1), List(2, 2, 2, 2), List(5, 6))
This is the cleanest way to do this
val (l, _ :: r) = list.span( _ != 4)
The span function splits the list at the first value not matching the condition, and the de-structuring on the left-hand side removes the matching value from the second list.
This will fail if there is no matching value.
Given a list and a delimiter, in order to split the list in 2:
val list = List(1, 1, 1, 1, 4, 2, 2, 2, 2)
val delimiter = 4
you could use a combination of List.indexOf, List.take and List.drop:
val splitIdx = list.indexOf(delimiter)
List(list.take(splitIdx), list.drop(splitIdx + 1))
you could use List.span which splits the list into a tuple given a predicate:
list.span(_ != delimiter) match { case (l1, l2) => List(l1, l2.tail) }
in order to produce:
List(List(1, 1, 1, 1), List(2, 2, 2, 2))
scala> val l = List(1,1,1,1,4,2,2,2,2)
l: List[Int] = List(1, 1, 1, 1, 4, 2, 2, 2, 2)
scala> l.splitAt(l.indexOf(4))
res0: (List[Int], List[Int]) = (List(1, 1, 1, 1),List(4, 2, 2, 2, 2))
def convert(list: List[Int], separator: Int): List[List[Int]] = {
#scala.annotation.tailrec
def rec(acc: List[List[Int]], listTemp: List[Int]): List[List[Int]] = {
if (listTemp.isEmpty) acc
else {
val (l, _ :: r) = listTemp.span(_ != separator)
rec(acc ++ List(l), r)
}
}
rec(List(), list)
}

How to sum two neighbours in a list in scala

If you have one Integer list in Scala, and you want to iterate through it and sum every two neighbours with the same value and return this as a list, how would one do that ?
So for example:
List(4, 4, 2, 6) => List(8, 2, 6)
I'm completely new to Scala, but I can imagine that pattern match or map could be useful.
def sumSameNeighbours: List[Int] => List[Int] = {
ls match {
case l1::l2:ls => l1 == l2
}
}
This is what I can think of.
EDIT: How would I have to change the code in order to iterate from right to left instead from left to right?
So that f.e. it would be:
List(2, 2, 2, 6, 4) => List(2, 4, 6, 4)
instead of
List(2, 2, 2, 6, 4) => List(4, 2, 6, 4)
This is pretty close to your suggestion and seems basically to work:
import scala.annotation.tailrec
def sumSameNeighbors( ls : List[Int] ) : List[Int] = {
#tailrec
def walk( unsummed : List[Int], reverseAccum : List[Int] ) : List[Int] = {
unsummed match {
case a :: b :: rest if a == b => walk( rest, a + b :: reverseAccum )
case a :: rest => walk( rest, a :: reverseAccum )
case Nil => reverseAccum.reverse
}
}
walk( ls, Nil )
}
Note: Based on final OP's specifications clarification, this answer doesn't exactly fit the question requirements.
Here is a solution using List.grouped(2):
list.grouped(2).toList
.flatMap {
case List(a, b) if a == b => List(a + b)
case l => l
}
The idea is to group consecutive elements by pair. If the pair has the same elements, we return their sum to be flatMaped and otherwise both elements untouched.
List(4, 4, 2, 6) => List(8, 2, 6)
List(2, 4, 4, 2, 6) => List(2, 4, 4, 2, 6)
List(2) => List(2)
List(9, 4, 4, 4, 2, 6) => List(9, 4, 8, 2, 6)
Another way using foldRight, which I find a good default for this sort of traversing a collection while creating a new one:
list.foldRight(List.empty[Int]) {
case (x, y :: tail) if x == y => (x + y) :: tail
case (x, list) => x :: list
}
Output of List(2, 2, 2, 6, 4) is List(2, 4, 6, 4) as requested.
The main thing I wasn't clear on from your examples is what the output should be if summing creates new neighbours: should List(8, 4, 2, 2) turn into List(8, 4, 4) or List(16)? This produces the second.

Remove consecutive identical elements in scala

I have a list which may contain certain consecutive identical elements.I want to replace many consecutive identical elements with one. How to do it in scala
Lets say my list is
List(5, 7, 2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)
I want output list as
List(5, 7, 2, 3, 5, 3, 2)
It can be done pretty cleanly using sliding:
myList.head :: myList.sliding(2).collect { case Seq(a,b) if a != b => b }.toList
It looks at all the pairs, and for every non-matching pair (a,b), it gives you back b. But then it has to stick the original a on the front of the list.
One way is this.
I'm sure there is a better way.
list.tail.foldLeft(List[Int](list.head))((prev, next) => {
if (prev.last != next) prev +: next
else prev
})
foldLeft takes a parameter (in the first application) and goes from left to right through your list, applying prev and next to the two parameter function it is given, where prev is the result of the function so far and next is the next element in your list.
another way:
list.zipWithIndex.filter(l => (l._2 == 0) || (l._1 != list(l._2-1))).map(_._1)
In general, list.zip(otherList) returns a list of tuples of corresponding elements. For example List(1,2,3).zip(List(4,5,6)) will result in List((1,4), (2,5), (3,6)). zipWithIndex is a specific function which attaches each list element with its index, resulting in a list where each element is of the form (original_list_element, index).
list.filter(function_returning_boolean) returns a list with only the elements that returned true for function_returning_boolean. The function I gave simply checks if this element is equal to the previous in the original list (or the index is 0).
The last part, .map(_._1) just removes the indices.
val myList = List(5, 7, 2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)
myList.foldRight[List[Int]](Nil) { case (x, xs) =>
if (xs.isEmpty || xs.head != x) x :: xs else xs }
// res: List[Int] = List(5, 7, 2, 3, 5, 3, 2)
(answer moved from this duplicate)
Here is a variant that is
tail-recursive
does not use any methods from the library (for better or worse)
Code:
def compress[A](xs: List[A]): List[A] = {
#annotation.tailrec
def rec(rest: List[A], stack: List[A]): List[A] = {
(rest, stack) match {
case (Nil, s) => s
case (h :: t, Nil) => rec(t, List(h))
case (h :: t, a :: b) =>
if (h == a) rec(t, stack)
else rec(t, h :: stack)
}
}
rec(xs, Nil).reverse
}
Example
println(compress(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)))
produces the following output:
List('a, 'b, 'c, 'a, 'd, 'e)
val l = List(5, 7,2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)
def f(l: List[Int]): List[Int] = l match {
case Nil => Nil
case x :: y :: tail if x == y => f(y::tail)
case x :: tail => x :: f(tail)
}
println(f(l)) //List(5, 7, 2, 3, 5, 3, 2)
Of course you can make it tail recursive
import scala.collection.mutable.ListBuffer
object HelloWorld {
def main(args:Array[String]) {
val lst=List(5, 7, 2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)
val lstBf=ListBuffer[Int](lst.head)
for(i<-0 to lst.length-2){
if(lst(i)!=lst(i+1)){
lstBf+=lst(i+1)
}
}
println(lstBf.toList)
}
}
Try this,
val y = list.sliding(2).toList
val x =y.filter(x=> (x.head != x.tail.head)).map(_.head) :+ (y.reverse.filter(x=> x.head !=x.tail.head)).head.tail.head
Yet another variant
val is = List(5, 7,2, 3, 3, 3, 5, 5, 3, 3, 2, 2, 2)
val ps = is.head::((is zip is.tail) collect { case (a,b) if a != b => b })
//> ps : List[Int] = List(5, 7, 2, 3, 5, 3, 2)
(the is zip is.tail is doing something similar to .sliding(2))

create pairs from sets

If I have unknown number of Set[Int] (or List[Int]) as an input and want to combine
i don't know size of input List[Int] for which I need to produce these tuples as a final result, what's the best way to achieve this? My code looks like below.
Ok. Since combine(xs) yields a List[List[Any]] and you have a :: combine(xs) you just insert a into the the List of all combinations. You want to combine a with each element of the possible combinations. That lead me to this solution.
You can also generalize it to lists:List[List[T]] because when you combine from lists:List[List[Int]] you will get a List[List[Int]].
def combine[T](lists: List[List[T]]): List[List[T]] = lists match {
case Nil => lists
case x :: Nil => for(a <- x) yield List(a) //needed extra case because if comb(xs) is Nil in the for loop, it won't yield anything
case x :: xs => {
val comb = combine(xs) //since all further combinations are constant, you should keep it in a val
for{
a <- x
b <- comb
} yield a :: b
}
}
Tests:
val first = List(7, 3, 1)
val second = List(2, 8)
val third = List("a","b")
combine(List(first, second))
//yields List(List(7, 2), List(7, 8), List(3, 2), List(3, 8), List(1, 2), List(1, 8))
combine(List(first, second, third))
//yields List(List(7, 2, a), List(7, 2, b), List(7, 8, a), List(7, 8, b), List(3, 2, a), List(3, 2, b), List(3, 8, a), List(3, 8, b), List(1, 2, a), List(1, 2, b), List(1, 8, a), List(1, 8, b))
I think you can also generalize this to work with other collections than List, but then you can't use pattern-match this easily and you have to work via iterators.