Related
Imagine the following List[Int] in Scala:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
I want to apply kind of a dynamic filter to it such that towards head/tail less data is filtered compared to the middle of the list:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
_ ____ __________ ___________ ______ __
[0, 1, 3, 7, 11, 13] // result
To do this, from both ends an index is increased by the next power of 2 and from the middle onwards the powers of 2 decrease again:
0, 0 + 2**0 = 1, 1 + 2**1 = 3, 3 + 2^2 = 7, etc.
To achieve this with an imperative approach, code similar to this can be used:
var log = 0
var idx = 0
val mask: ListBuffer[Int] = mutable.ListBuffer()
while (idx < buffer.size) {
mask += idx
if (idx + (2 ** log) < buffer.size / 2) {
idx += 2 ** log
log += 1
} else {
idx = buffer.size - (2 ** log) + 1
log -= 1
}
}
This produces a mask array that can then be used to filter the original list like mask.flatMap(list.lift)
Can somebody help me do this in a more concise, functional way?
What I basically need is a way to filter the list using some changing state from the outside.
Thanks in advance.
The usual approach to iterating with state is tail recursion (or you often do the same thing with reduceLeft with simple enough cases).
This is better than the other answer, because it is linear (accessing list elements by index makes the whole thing quadratic), and tail-recursive (no extra space on stack). Also, I think, the other version reverses the order of filtered elements.
You can do it with recursively in one go (this is better than the other answer, because it is tail-recursive, and linear (accessing list elements by index makes the implementation quadratic).
I didn't check the logic, which the other answer suggested was incorrect, just used it as is from your snippet, but here is the idea:
#tailrec
def filter(
in: List[Int],
midpoint: Int,
out: List[Int]=Nil,
idx: Int = 0,
next: Int = 0,
log: Int = 0
): List[Int] = in match {
case Nil => out.reverse
case head::tail if (idx == next) =>
filter(tail, midpoint, head::out, idx+1, idx + pow(2, log).toInt, if (idx < midpoint) log + 1 else log-1)
case head::tail => filter(tail, midpoint, out, idx+1, next, log)
}
Note, that this may seem less efficient than your "mask" idea because it looks at every element in the list, rather than jumping over indices being filtered out, but in fact, as long as you are working with List, it is actually more efficient: first, yours is (at least) O(N) anyway, because you have to traverse the whole list to figure out the size, and secondly, list.lift(idx) is O(idx), so towards the end of the list, this will be require multiple traversals of almost entire list.
Now, if you had an indexed container instead of a list, the whole "masking" idea would indeed improve things:
def filter(list: IndexedSeq[Int]) = {
val size = list.size
Iterator.iterate((0, 0)) { case (idx, log) =>
(idx + math.pow(2, log).toInt, if idx < size/2 log+1 else log-1)
}.map(_._1).takeWhile(_ < size).map(list)
}
You code snippet does not work very well, I had to tweak it a bit to make it output the result you want:
var log = 0
var idx = 0
val resultList: mutable.ListBuffer[Int] = mutable.ListBuffer()
// Fill the result until the middle, increasing the jump size
while (idx < list.size / 2) {
resultList += list(idx)
idx += math.pow(2, log).toInt
log += 1
}
// Fill the result from the middle until the end, decreasing the jump size again
while (idx < list.size && log >= 0) {
resultList += list(idx)
log -= 1
idx += math.pow(2, log).toInt
}
With your example it works:
val list = (0 to 13).toList -> ListBuffer(0, 1, 3, 7, 11, 13)
However with another example I got that:
val list = (0 to 22).toList -> ListBuffer(0, 1, 3, 7, 15)
I don't think this is really what you want, do you?
Anyway here is a more functionnal version of it:
def filter(list: List[Int]) = {
// recursive function to traverse the list
def recursive(l: List[Int], log: Int, idx: Int, size: Int, halfSize: Int): List[Int] = {
if (idx >= l.size || log < 0) // terminal case: end of the list
Nil
else if (idx < l.size / 2) // first half of the list: increase the jump size
l(idx) :: recursive(l, log + 1, idx + math.pow(2, log).toInt, size, halfSize)
else // second half of the list: decrease the jump size
l(idx) :: recursive(l, log - 1, idx + math.pow(2, log-1).toInt, size, halfSize)
}
// call the recursive function with initial parameters
recursive(list, 0, 0, list.size, list.size / 2)
}
However, jumping by powers if 2 is too aggressive. If you are near the middle of the list, the next jump will ends at the very end, and you will not be able to get a progressive jump decay.
Another solution could be to increase the jump size by one each time instead of working with powers of 2. You can also keep a constant jump size when you are near the middle of the list to avoid skipping too much values in the second half before starting to reduce the jump size:
def filter2(list: List[Int]) = {
def recursive(l: List[Int], jumpsize: Int, idx: Int, size: Int, halfSize: Int): List[Int] = {
if (idx >= l.size) // terminal case: end of the list
Nil
else if (idx + jumpsize < l.size/2) // first half of the list: increase the jump size
l(idx) :: recursive(l, jumpsize+1, idx + jumpsize, size, halfSize)
else if (idx < l.size/2) // around the middle of the list: keep the jump size
l(idx) :: recursive(l, jumpsize, idx + jumpsize, size, halfSize)
else { // second half of the list: decrease the jump size
val nextJumpSize = jumpsize - 1
l(idx) :: recursive(l, nextJumpSize, idx + nextJumpSize, size, halfSize)
}
}
// call the recursive function with initial parameters
recursive(list, 1, 0, list.size, list.size / 2)
}
In my opinion, the results with this version are a bit better:
val list = (0 to 22).toList -> List(0, 1, 3, 6, 10, 15, 19, 22)
Your question is not so clear for some corner case, here is my solution:
scala> def filter[A](seq: Seq[A], n: Int = 1): Seq[A] = seq match {
| case Nil => Nil
| case Seq(x) => Seq(x)
| case _ => seq.head +: filter(seq.drop(n).dropRight(2*n), 2*n) :+ seq.last
| }
def filter[A](seq: Seq[A], n: Int): Seq[A]
scala> filter(0 to 13)
val res0: Seq[Int] = List(0, 1, 3, 7, 11, 13)
scala> filter(0 to 100)
val res1: Seq[Int] = List(0, 1, 3, 7, 15, 31, 38, 70, 86, 94, 98, 100) // I am not sure if 38 should in the result
I have three lists inside a list.
val l = List(List(1, 0, 0), List(1, 1, 0), List(1, 0, 1))
Now the problem is to add the values inside list with condition check, basically I need to check each element wise values and if they are equal do some calculation if not do another calculation. I tired to group same element positions using transpose, then map the result and make comparison of current element with previous element in the list, I think the last part is where I am not clear.
EDIT:
Some more explanation, let say we have list like this
List(List(i1, j1, k1), List(i2, j2, k2), List(in, jn, kn))
if i1 is equal to i2 then 1+i1+ i2*0.5 and j1 equal to j2 then 1+j1+j2*.0.5 and it goes upto kn
and the second condition is if they are not equal
i1 is not equal to i2 then it is a decrease, it will be i1+i2*0.5
hope this is explanation helps
Output I looking for
List[Double] = List(2.5, 0.5, 0.5)
Your description is still very confusing but I think maybe something like this is what you're after.
val lst = List(List(1, 0, 0), List(1, 1, 0), List(1, 0, 1))
lst.transpose
.map(sublst => sublst.sum*0.5 + (if (sublst.forall(_==sublst.head)) 1 else 0))
//res0: List[Double] = List(2.5, 0.5, 0.5)
If all the elements of the sub-list are the same, add 1 to the calculation, else add 0.
lst.transpose.map(v=>if(v.forall(_==v.head)) v.sum*0.5+1 else v.sum*0.5))
or
lst.transpose.map(v=>if(v.exists(_!=v.head)) v.sum*0.5 else v.sum*0.5+1)
or
lst.transpose.map(v=>if(v.find(_!=v.head)!=None) v.sum*0.5 else v.sum*0.5+1)
These are in general, can be used for two different expressions to be evaluated for the two different cases ie., all elements equal or not equal in the lists after calling transpose function.ie.,
lst.transpose.map(v=>if(v.exists(_!=v.head))
(Expression1 in terms of elements in v)
else (Expression2 in terms of elements in v) )
In Scala REPL:
for,
val lst = List(List(1, 0, 0), List(1, 1, 0), List(1, 0, 1))
scala> lst.transpose.map(v=>if(v.forall(_==v.head)) v.sum*0.5+1 else v.sum*0.5)
res23: List[Double] = List(2.5, 0.5, 0.5)
scala> lst.transpose.map(v=>if(v.exists(_!=v.head)) v.sum*0.5 else v.sum*0.5+1)
res24: List[Double] = List(2.5, 0.5, 0.5)
scala> lst.transpose.map(v=>if(v.find(_!=v.head)!=None) v.sum*0.5 else v.sum*0.5+1)
res25: List[Double] = List(2.5, 0.5, 0.5)
You can use map and pattern matching
val list = List(List(1, 0, 0), List(1, 1, 0), List(1, 0, 1))
val result = list.transpose.map {
case sublist if sublist.forall(sublist.head == _) => 1+0.5*sublist.sum
case sublist => 0.5*sublist.sum
}
Trying to remove the first element of a list if it is zero (not really, but for the purpose of an example).
Given a list:
val ns = List(0, 1, 2)
Deleting the first zero could be done by dropping the first matches for zero:
List(0, 1, 2).dropWhile(_ == 0)
res1: List[Int] = List(1, 2)
Or you could delete everything that's not a zero.
List(0, 1, 2).filter(_ > 0)
res2: List[Int] = List(1, 2)
The problem with these is when the list has multiple zeroes. The previous solutions don't work, because they delete too many zeroes:
List(0, 0, 1, 2, 0).filter(_ > 0)
res3: List[Int] = List(1, 2)
List(0, 0, 1, 2, 0).dropWhile(_ == 0)
res4: List[Int] = List(1, 2, 0)
Is there an existing function for this?
I also think pattern matching is the best option for readability and performance (I tested and the pattern matching code from OP actually performs better than simple if ... else ....).
List(0, 0, 1, 2, 0) match {
case 0 :: xs => xs
case xs => xs
}
res10: List[Int] = List(0, 1, 2, 0)
And, no, there's no simple built in function for this.
If you only want to drop the first element conditionally, then as jwvh commented, an if/else comprehension is probably the simplest:
if (ns.nonEmpty && ns.head == 0) {
ns.tail
} else {
ns
}
You could then of course wrap this into a function.
You could look for a sequence of one zero, and drop it:
if (ns.startsWith(List(0))) {
ns.drop(1)
} else {
ns
}
Also known as returning the tail:
if (ns.startsWith(List(0))) {
ns.tail
} else {
ns
}
A neat generalized solution would be explicitly add information to your elements.
Example:
How to drop by condition and limit the amount from left to right?
List(0,0,0,1,2,2,3).zipWithIndex.dropWhile({case (elem,index) => elem == 0 && index < 2})
Result:
res0: List[(Int, Int)] = List((0,2), (1,3), (2,4), (2,5), (3,6))
You can get your previous representation with:
res0.map.{_._1}
To do everything in N, you can use lazy evaluation + the force method.
List(0,0,0,1,2,2,3).view.zipWithIndex.dropWhile({case (elem,index) => elem == 0 && index < 2}).map {_._1}.force
This will basically do all the operations on your initial collection in one single iteration. See scaladoc for more info to Scala views..
Modifying your condition on the right size you can choose how far the drop condition will reach inside your collection.
Here's a generalised variant (drop up to K elements matching the predicate) which does not process the rest of the list
def dropWhileLimit[A](xs: List[A], f: A => Boolean, k: Int): List[A] = {
if (k <= 0 || xs.isEmpty || !f(xs.head)) xs
else dropWhileLimit(xs.tail, f, k - 1)
}
and some test cases:
dropWhileLimit(List(0,1,2,3,4), { x:Int => x == 0}, 1)
//> res0: List[Int] = List(1, 2, 3, 4)
dropWhileLimit(List(0,1,2,3,4), { x:Int => x == 0}, 2)
//> res1: List[Int] = List(1, 2, 3, 4)
dropWhileLimit(List(0,0,0,0,0), { x:Int => x == 0}, 1)
//> res2: List[Int] = List(0, 0, 0, 0)
dropWhileLimit(List(0,0,0,0,0), { x:Int => x == 0}, 3)
//> res3: List[Int] = List(0, 0)
You can zip the list with an index:
ns.zipWithIndex.filter( x =>( x._1 != 0 || x._2 != 0)).map(_._1)
Here's a similar solution using dropWhile:
ns.zipWithIndex.dropWhile {
case (x, idx) => x == 0 && idx == 0
} map(_._1)
This could also be a for-comprehension
for {
(x, idx) <- ns.zipWithIndex
if (x != 0 || idx != 0) )
} yield {
x
}
But as Paul mentioned, it will unnecessarily iterate over the entire list.
That's a good option if you wanna drop the first element according to the filter but the value isn't in the first place.
def dropFirstElement( seq: Seq[Int], filterValue: Int ): Seq[Int] = {
seq.headOption match {
case None => seq
case Some( `filterValue` ) => seq.tail
case Some( value ) => value +: dropFirstElement( seq.tail, filterValue )
}
}
Imagine I have an unsorted list of positive & negative ints. I want to return a list containing all the positive ints, and the first negative number, but then ignore all subsequent negative numbers from the list, while preserving the ordering.
Imperatively I could do:
l = [1, 2, -4, 5, -6, -1, 3]
out = []
first = true
for n in l:
if n >= 0:
out.push(n)
else if first:
out.push(n)
first = false
// out = [1, 2, -4, 5, 3]
How could I do this with FP in Scala? I was thinking (probably won't compile...):
val l = List(1, 2, -4, 5, -6, -1, 3)
val posl = l.map(_ >= 0)
val negl = l.zipWithIndex.map((n, i) => if (n < 0) (i, n) else (None, None)).head
// now split posl at negl._1, and create a new list of leftSlice :: negl._2 :: rightSlice?
Is this the right approach, or is there a more elegant, succinct way?
It wouldn't be a proper functional programming question without a slightly-too-clever recursion+pattern matching answer.
def firstNegAllPos(l:List[Int]):List[Int] = {
l match{
case x::xs if x>=0 => x::firstNegAllPos(xs)
case x::xs if x<0 => x::xs.filter(_>=0)
case Nil => Nil
}
}
Here is a tail-recursive way. Compared to m-z's answer, it iterates your list only one time, compared to Dimas answer, it does not use mutable state, so it is pure functional.
def firstNegAllPos(list: List[Int]) : List[Int] = {
def loop(in: List[Int], out: List[Int], negfound: Boolean) : List [Int] = {
in match {
case Nil => out
case head :: tail =>
if (negfound)
loop(tail, if (head < 0) out else head :: out, true)
else
loop(tail, head :: out, head < 0)
}
}
loop(list, Nil, false)
}
firstNegAllPos(List(1, 2, -4, 5, -6, -1, 3)) // List(3, 5, -4, 2, 1)
Edit:
The above implementation provides a reversed result. In order to preserve order you can do following:
def firstNegAllPos(list: List[Int]) : List[Int] = {
def loop(in: List[Int], out: List[Int], negfound: Boolean) : List [Int] = {
in match {
case Nil => out
case head :: tail =>
if (negfound)
loop(tail, if (head < 0) out else head :: out, true)
else
loop(tail, head :: out, head < 0)
}
}
loop(list, Nil, false).reverse
}
firstNegAllPos(List(1, 2, -4, 5, -6, -1, 3)) // List(1, 2, -4, 5, 3)
You can do it in one pass provided you don't mind keeping a bit of state around - mind the var neg:
var neg = false
list.filter {
case x if x > 0 => true
case _ if !neg => neg = true
}
A direct translation of the requirement is pretty clear, takes one pass over the list, and is functional:
val l = List(1, 2, -4, 5, -6, -1, 3)
// split into any initial positive numbers, and the rest of the list
val (pos, firstneg) = l.span(_ >= 0)
// if there were no negative numbers, return the original list.
// otherwise, it's the initial positives, the first negative, and
// the positive numbers from the rest of the list.
if (firstNeg.isEmpty) l else pos:::List(firstneg.head):::firstneg.tail.filter(_>=0)
//> res0: List[Int] = List(1, 2, -4, 5, 3)
(The List around firstneg.head is just for the symmetry of ::: both sides)
val l = List(1, 2, -4, 5, -6, -1, 3)
val (left, right) = l.span(_ > 0)
val result = right.headOption match {
case Some(n) => (left :+ n) ++ right.tail.filter(_ > 0)
case None => left
}
This is an obvious work for fold operation!
val l = List(1, 2, -4, 5, -6, -1, 3)
var result = l.foldLeft((true, Vector.empty[Int])) {
case ((f, r), x) if x >= 0 => (f, r :+ x)
case ((f, r), x) if f => (false, r :+ x)
case ((f, r), x) => (f, r)
}._2
println(result) // Vector(1, 2, -4, 5, 3)
I used a vector as an intermediate structure; you can convert it to a list with toList, if you need it. Or you can use it instead of the Vector, but you will have to reverse the addition order (r :+ x => x :: r) and then reverse the list in the end with reverse method.
If you want to maintain the order (i.e., the position of the negative), you could do something like this:
val list = List(1, 2, -4, 5, -6, -1, 3)
val negIndex = list.indexWhere(_ < 0)
val positive = list.zipWithIndex.filter { case (num, index) =>
num >= 0 || index == negIndex
}.map(_._1)
The negative requirement makes it hard to keep it more succinct than that. My strategy is to just grab the index of the first negative with indexWhere (will be -1 if there is none), then filter all the negatives out of the list, except for the index of the first negative. If the index was -1, no harm, no foul.
Here is tail recursive solution preserving order:
def posNeg(xs: List[Int]): List[Int] = {
#tailrec
def go(tail: List[Int], res: List[Int]): List[Int] = {
tail match {
case h :: t =>
if (h >= 0) go(t, h :: res)
else (h :: res).reverse ::: t.filter(_ > 0)
case _ => res
}
}
go(xs, Nil)
}
There are a lot of tail-recursive solutions, but they're all longer than they need to be.
def oneNeg(xs: List[Int]): List[Int] = {
def loop(in: List[Int], out: List[Int], neg: Int): List[Int] = in match {
case Nil => out
case x :: rest =>
if (neg < 0 && x < 0) loop(rest, out, neg)
else loop(rest, x :: out, x min neg)
}
loop(xs, Nil, 0).reverse
}
If this is not a public-facing API, you can make it shorter yet by just exposing the inner method alone:
def oneNeg(in: List[Int], out: List[Int] = Nil, neg: Int = 0): List[Int] =
in match {
case Nil => out.reverse
case x :: rest =>
if (neg < 0 && x < 0) oneNeg(rest, out, neg)
else oneNeg(rest, x :: out, x min neg)
}
Here is an improvement on the most concise answer I saw, by #sschaef in a comment:
val l = List(1, 2, -4, 5, -6, -1, 3)
l.span(_>=0) match { case (left, Nil) => left
case (left, x::right) => left ++ (x +: right.filter(_>=0)) }
You can try:
val list = List(1, 2, -4, 5, -6, -1, 3)
val index = list.indexWhere(_ < 0) + 1
list.take(index) ++ list.drop(index).filter(_ > 0)
Lets assume we have a Scala list:
val l1 = List(1, 2, 3, 1, 1, 3, 2, 5, 1)
We can easily remove duplicates using the following code:
l1.distinct
or
l1.toSet.toList
But what if we want to remove duplicates only if there are more than 2 of them? So if there are more than 2 elements with the same value we remain only two and remove the rest of them.
I could achieve it with following code:
l1.groupBy(identity).mapValues(_.take(2)).values.toList.flatten
that gave me the result:
List(2, 2, 5, 1, 1, 3, 3)
Elements are removed but the order of remaining elements is different from how these elements appeared in the initial list. How to do this operation and remain the order from original list?
So the result for l1 should be:
List(1, 2, 3, 1, 3, 2, 5)
Not the most efficient.
scala> val l1 = List(1, 2, 3, 1, 1, 3, 2, 5, 1)
l1: List[Int] = List(1, 2, 3, 1, 1, 3, 2, 5, 1)
scala> l1.zipWithIndex.groupBy( _._1 ).map(_._2.take(2)).flatten.toList.sortBy(_._2).unzip._1
res10: List[Int] = List(1, 2, 3, 1, 3, 2, 5)
My humble answer:
def distinctOrder[A](x:List[A]):List[A] = {
#scala.annotation.tailrec
def distinctOrderRec(list: List[A], covered: List[A]): List[A] = {
(list, covered) match {
case (Nil, _) => covered.reverse
case (lst, c) if c.count(_ == lst.head) >= 2 => distinctOrderRec(list.tail, covered)
case _ => distinctOrderRec(list.tail, list.head :: covered)
}
}
distinctOrderRec(x, Nil)
}
With the results:
scala> val l1 = List(1, 2, 3, 1, 1, 3, 2, 5, 1)
l1: List[Int] = List(1, 2, 3, 1, 1, 3, 2, 5, 1)
scala> distinctOrder(l1)
res1: List[Int] = List(1, 2, 3, 1, 3, 2, 5)
On Edit: Right before I went to bed I came up with this!
l1.foldLeft(List[Int]())((total, next) => if (total.count(_ == next) >= 2) total else total :+ next)
With an answer of:
res9: List[Int] = List(1, 2, 3, 1, 3, 2, 5)
Not the prettiest. I look forward to seeing the other solutions.
def noMoreThan(xs: List[Int], max: Int) =
{
def op(m: Map[Int, Int], a: Int) = {
m updated (a, m(a) + 1)
}
xs.scanLeft( Map[Int,Int]().withDefaultValue(0) ) (op).tail
.zip(xs)
.filter{ case (m, a) => m(a) <= max }
.map(_._2)
}
scala> noMoreThan(l1, 2)
res0: List[Int] = List(1, 2, 3, 1, 3, 2, 5)
More straightforward version using foldLeft:
l1.foldLeft(List[Int]()){(acc, el) =>
if (acc.count(_ == el) >= 2) acc else el::acc}.reverse
Similar to how distinct is implemeted, with a multiset instead of a set:
def noMoreThan[T](list : List[T], max : Int) = {
val b = List.newBuilder[T]
val seen = collection.mutable.Map[T,Int]().withDefaultValue(0)
for (x <- list) {
if (seen(x) < max) {
b += x
seen(x) += 1
}
}
b.result()
}
Based on experquisite's answer, but using foldLeft:
def noMoreThanBis(xs: List[Int], max: Int) = {
val initialState: (Map[Int, Int], List[Int]) = (Map().withDefaultValue(0), Nil)
val (_, result) = xs.foldLeft(initialState) { case ((count, res), x) =>
if (count(x) >= max)
(count, res)
else
(count.updated(x, count(x) + 1), x :: res)
}
result.reverse
}
distinct is defined for SeqLike as
/** Builds a new $coll from this $coll without any duplicate elements.
* $willNotTerminateInf
*
* #return A new $coll which contains the first occurrence of every element of this $coll.
*/
def distinct: Repr = {
val b = newBuilder
val seen = mutable.HashSet[A]()
for (x <- this) {
if (!seen(x)) {
b += x
seen += x
}
}
b.result()
}
We can define our function in very similar fashion:
def distinct2[A](ls: List[A]): List[A] = {
val b = List.newBuilder[A]
val seen1 = mutable.HashSet[A]()
val seen2 = mutable.HashSet[A]()
for (x <- ls) {
if (!seen2(x)) {
b += x
if (!seen1(x)) {
seen1 += x
} else {
seen2 += x
}
}
}
b.result()
}
scala> distinct2(l1)
res4: List[Int] = List(1, 2, 3, 1, 3, 2, 5)
This version uses internal state, but is still pure. It is also quite easy to generalise for arbitrary n (currently 2), but specific version is more performant.
You can implement the same function with folds carrying the "what is seen once and twice" state with you. Yet the for loop and mutable state does the same job.
How about this:
list
.zipWithIndex
.groupBy(_._1)
.toSeq
.flatMap { _._2.take(2) }
.sortBy(_._2)
.map(_._1)
Its a bit ugly, but its relatively faster
val l1 = List(1, 2, 3, 1, 1, 3, 2, 5, 1)
l1.foldLeft((Map[Int, Int](), List[Int]())) { case ((m, ls), x) => {
val z = m + ((x, m.getOrElse(x, 0) + 1))
(z, if (z(x) <= 2) x :: ls else ls)
}}._2.reverse
Gives: List(1, 2, 3, 1, 3, 2, 5)
Here is a recursive solution (it will stack overflow for large lists):
def filterAfter[T](l: List[T], max: Int): List[T] = {
require(max > 1)
//keep the state of seen values
val seen = Map[T, Int]().withDefaultValue(0)//init to 0
def filterAfter(l: List[T], seen: Map[T, Int]): (List[T], Map[T, Int]) = {
l match {
case x :: xs =>
if (seen(x) < max) {
//Update the state and pass to next
val pair = filterAfter(xs, seen updated (x, seen(x) + 1))
(x::pair._1, pair._2)
} else {
//already seen more than max
filterAfter(xs, seen)
}
case _ => (l, seen)//empty, terminate recursion
}
}
//call inner recursive function
filterAfter(l, seen, 2)._1
}
Here is canonical Scala code to do reduce three or more in a row to two in a row:
def checkForTwo(candidate: List[Int]): List[Int] = {
candidate match {
case x :: y :: z :: tail if x == y && y == z =>
checkForTwo(y :: z :: tail)
case x :: tail =>
x :: checkForTwo(tail)
case Nil =>
Nil
}
}
It looks at the first three elements of the list, and if they are the same, drops the first one and repeats the process. Otherwise, it passes items on through.
Solution with groupBy and filter, without any sorting (so it's O(N), sorting will give you additional O(Nlog(N)) in typical case):
val li = l1.zipWithIndex
val pred = li.groupBy(_._1).flatMap(_._2.lift(1)) //1 is your "2", but - 1
for ((x, i) <- li if !pred.get(x).exists(_ < i)) yield x
I prefer approach with immutable Map:
def noMoreThan[T](list: List[T], max: Int): List[T] = {
def go(tail: List[T], freq: Map[T, Int]): List[T] = {
tail match {
case h :: t =>
if (freq(h) < max)
h :: go(t, freq + (h -> (freq(h) + 1)))
else go(t, freq)
case _ => Nil
}
}
go(list, Map[T, Int]().withDefaultValue(0))
}