3

I'm trying to filter certain items out of a list and merge them in the final list in a specific order. The first code snippet seems inefficient since it creates 2 lists for filtering & then iterates over them however that code works. The second snippet is trying to combine both filterings however the map operator is not adding items to otherNums list

Could someone please help me understand why is this happening?

Snippet 1:

fun main() {
    val favItem = 0
    val list = listOf(11, 12, 13, 2,3,4,5,6,7,10, favItem)
    
    val greaterThan10 = list.filter{item -> item > 10}
    val otherNums = list.asSequence().filter{item -> item != favItem}.filter{item -> item < 10}
    
    println(" $greaterThan10") //the list is filled with proper numbers
    
    println("merged list ${greaterThan10.plus(favItem).plus(otherNums)}")
}

Result:

 [11, 12, 13]
merged list [11, 12, 13, 0, 2, 3, 4, 5, 6, 7]

Snippet 2:

fun main() {
    val favItem = 0
    val list = listOf(11, 12, 13, 2,3,4,5,6,7,10, favItem)
    
    val greaterThan10 = mutableListOf<Int>()
    val otherNums = list.asSequence().filter{item -> item != favItem}.map{
        if(it > 10) {
            greaterThan10.add(it)
        }
        it
    }
    .filter{item -> item != 10}
    
    println("$greaterThan10") // the list is empty
    
    println("merged list ${greaterThan10.plus(favItem).plus(otherNums)}")
}

Result:

 []
merged list [0, 11, 12, 13, 2, 3, 4, 5, 6, 7]
user2498079
  • 2,872
  • 8
  • 32
  • 60
  • 1
    Do you need the `greaterThan10` list or just the final one? – João Dias Nov 29 '21 at 11:57
  • not sure why, but removing `asSequence()` makes it that `greaterThan10` at least gets filled – Ivo Nov 29 '21 at 11:58
  • @JoãoDias I need the final one but `greaterThan10` list helps in debugging by checking if the `greaterThan10` has proper values in it or not – user2498079 Nov 29 '21 at 12:01
  • @IvoBeckers `asSequence` helps avoid creating 2 separate lists for each `filter`. Its a bit more efficient – user2498079 Nov 29 '21 at 12:02
  • Yeah I get that. but for some reason if you leave it out greaterThan10 does get filled. And I don't understand why because in both cases `greaterThan10.add(it)` gets called before the print – Ivo Nov 29 '21 at 12:09
  • Nevermind. because of asSequence it does in fact execute the mapping after the first print – Ivo Nov 29 '21 at 12:12
  • @IvoBeckers that's probably the reason. Is asSequence lazy? – user2498079 Nov 29 '21 at 12:13
  • something like that yeah. see https://stackoverflow.com/questions/52758671/advantage-of-adding-assequence-in-array-in-kotlin – Ivo Nov 29 '21 at 12:14
  • @IvoBeckers that seems kinda odd. Isn't `greaterThan10` being iterated over when we're combining all the lists at the end with `plus`. why is `otherNums` being iterated over then? – user2498079 Nov 29 '21 at 12:20
  • I'm assuming applying plus to it makes it iterate over it. or at least creating the string to be printed definitely iterates over it – Ivo Nov 29 '21 at 12:25

3 Answers3

2

In your second snippet, greaterThan10 list is empty because of the lazy behavior of sequences, a sequence is iterated only when a terminal operation is encountered like toList() or sum().

In your case, the sequence is iterated when you write .plus(otherNums). List + Sequence produces a List. If you print your greaterThan10 list after printing the merged list, you will find it populated.

Btw, you don't need a Sequence here. Two major cases where sequences are more performant than lists are:

  • When you have lot of intermediate operations, like map, filter etc. With Iterable a lot of intermediate Iterables are created which consume more memory,
  • When you have some kind of a short-circuit operation at the end like take(), contains(), first() etc. i.e. when the entire collection needs not be iterated to get the final result.

As per the docs,

The lazy nature of sequences adds some overhead which may be significant when processing smaller collections or doing simpler computations. Hence, you should consider both Sequence and Iterable and decide which one is better for your case.

For the final solution, I think you can use your snippet 1. That looks good to me, just remove the unnecessary asSequence and combine the two filter into one.

fun main() {
    val favItem = 0
    val list = listOf(11, 12, 13, 2, 3, 4, 5, 6, 7, 10, favItem)
    
    val greaterThan10 = list.filter {item -> item > 10}
    val otherNums = list.filter { it != favItem && it <= 10 }
    
    println(" $greaterThan10")
    
    println("merged list ${greaterThan10 + favItem + otherNums}")
}

I think using filter is better than minus on lists as the latter has quadratic worst case time complexity (if I remember correctly). I wrote a small example to demonstrate the difference. Run this a few times to see the difference.

Also, as @IvoBeckers mentioned in the comment, "If the original list doesn't have a favItem this method will also add one to it. And if the list has multiple of the favItem this method will replace it with a single one."

Arpit Shukla
  • 9,612
  • 1
  • 14
  • 40
  • Maybe worth to mention: if the original list doesn't have a favItem this method will also add one to it. And I believe if the list has multiple of the favItem this method will replace it with a single one. – Ivo Nov 30 '21 at 07:03
  • Yes, you are correct. Thanks for pointing out. Added this to the answer. – Arpit Shukla Nov 30 '21 at 07:14
0
val favItem = 0
val list = listOf(11, 12, 13, 2, 3, 4, 5, 6, 7, 10, favItem)

val greaterThan10 = list.filter { it > 10 }
val otherNums = list - greaterThan10.toSet() - favItem

println("greaterThan10: $greaterThan10")   // [11, 12, 13]
println("otherNums: $otherNums")           // [2, 3, 4, 5, 6, 7, 10]
println("merged list: ${greaterThan10 + favItem + otherNums}")

Edit: replaced .minus(...) with -. Thanks to @Ivo Beckers' comment.

lukas.j
  • 6,453
  • 2
  • 5
  • 24
  • I think `list.minus((greaterThan10 + favItem).toSet())` is more complicated than needs to be. I believe a simple `val otherNums = list - greaterThan10 - favItem` works just as good – Ivo Nov 29 '21 at 12:31
  • Edited it. Many thanks for suggesting that. Much easier to capture. – lukas.j Nov 29 '21 at 12:33
0
val favItem = 0
val list = listOf(11, 12, 13, 2, 3, 4, 5, 6, 7, 10, favItem)

val (greaterThan10, otherNums) = list           // destructuring assignment
  .filter { it != favItem }                     // filter out favItem
  .groupBy { it > 10 }                          // create two groups
  .toSortedMap { o1, _ -> if (o1) -1 else 1 }   // sort groups in the order [true, false]
  .map { it.value.toList() }                    // return the two lists

println("greaterThan10: $greaterThan10")
println("otherNums: $otherNums")
println("merged list: ${greaterThan10 + favItem + otherNums}")
lukas.j
  • 6,453
  • 2
  • 5
  • 24