https://kotlinlang.org logo
#getting-started
Title
# getting-started
l

Lukasz Kalnik

07/18/2022, 3:02 PM
What is the idiomatic way of copying an immutable list with one element removed?
mapNotNull { if (it == removeMe) null else it }
doesn't support lists with nullable elements. In my case the elements have unique IDs, so I could also
filter {}
or
dropWhile {}
based on ID, but the names are not obviously suggesting that only one element gets removed. Do we have to do
toMutableList().remove(removeMe).toList()
?
j

Joffrey

07/18/2022, 3:03 PM
You can use the minus operator:
Copy code
val newList = oldList - elementToRemove
K 3
l

Lukasz Kalnik

07/18/2022, 3:05 PM
Wow, that's so simple I didn't even think about it 😄
Thank you 🙏
👍 1
j

Joffrey

07/18/2022, 3:05 PM
Note that this finds the element in the original list based on
equals()
, which is likely more expensive than filtering based on the ID (unless you overrode
equals()
to use the ID)
l

Lukasz Kalnik

07/18/2022, 3:05 PM
That's true
j

Joffrey

07/18/2022, 3:06 PM
It might not matter much in your case though 🙂
l

Lukasz Kalnik

07/18/2022, 3:06 PM
And when having an ID what do you recommend?
toMutableList().removeIf { it.id == removeMe.id }.toList()
?
j

Joffrey

07/18/2022, 3:07 PM
I would go with filter I think:
Copy code
val newList = oldList.filter { it.id != removeMe.id }
But I agree it doesn't express the intent so well
e

ephemient

07/18/2022, 3:07 PM
`minus`/`-` removes a single instance, `removeIf`/`filter` removes all matching instances
1
l

Lukasz Kalnik

07/18/2022, 3:09 PM
In my case I only have unique IDs, so
filter
should be fine. Especially that it's quite concise.
Thank you both for the help!
y

Youssef Shoaib [MOD]

07/18/2022, 5:11 PM
Careful though because, as ephemient said, filter will remove all instances, and so it won't stop deleting after the first occurence, meaning that, for a sufficiently large list, it will be slower. Maybe something like so would be better:
Copy code
val indexOfRemoveMe = oldList.indexOfFirst { it.id == removeMe.id }
val newList = buildList(oldList.size) {
    addAll(oldList)
    if(indexOfRemoveMe >= 0) removeAt(removeMe)
}
k

Klitos Kyriacou

07/18/2022, 5:19 PM
If the list is sufficiently large to worry about performance overhead of iterating it, shouldn't it be a Map keyed on ID?
y

Youssef Shoaib [MOD]

07/18/2022, 5:23 PM
That is true yeah. It could be that the list is sorted though, and so in that case it is "more performant". But yeah I was bikeshedding honestly
l

Lukasz Kalnik

07/18/2022, 5:27 PM
Yes, the list is short, so performance is not an issue. Thanks for your remarks though, it's always nice to see different aspects.
j

Joffrey

07/18/2022, 5:28 PM
@Youssef Shoaib [MOD] AFAIR, using
removeAt
in an array list would shift all following items to the left and be incredibly inefficient (at least compared to just initializing to the right size and copying items except the one at the given index). That is probably even worse than the
filter
approach, actually.
y

Youssef Shoaib [MOD]

07/18/2022, 5:30 PM
Filter would do it element by element though. AFAIK an arraycopy (which is what shifting left is internally) is much faster (although I'm not sure lol, I think that's also the reasoning as to why bulk modification operations like `addAll`exist). I considered also to addAll the elements before and addAll the elements after, but that, I think, is as performant as a
removeAt
j

Joffrey

07/18/2022, 5:33 PM
I considered also to addAll the elements before and addAll the elements after, but that, I think, is as performant as a
removeAt
I don't think so, it would just do 2 arraycopy for the 2 ranges, instead of copying everything + re-copying the second part. But I agree that this would be better than a
filter
that uses an iterator and adds each element one by one.
y

Youssef Shoaib [MOD]

07/18/2022, 5:38 PM
addAll creates an extra array though (it calls
toArray
on the collection) and so
removeAt
would create one array instead of 2 and then resize within that array. I honestly didn't know if the extra array was worse or not though.
e

ephemient

07/18/2022, 5:45 PM
if you're adding by iterating one-by-one, that involves copies due to array resizing, so addAll is probably a constant factor faster on average
j

Joffrey

07/18/2022, 5:56 PM
Oh right, the filter-like approaches don't know the target size, so they don't set the initial capacity in a smart way
k

Klitos Kyriacou

07/18/2022, 8:04 PM
There is
filterTo
for when you know the target size.