What is the idiomatic way of copying an immutable ...
# getting-started
l
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
You can use the minus operator:
Copy code
val newList = oldList - elementToRemove
K 3
l
Wow, that's so simple I didn't even think about it 😄
Thank you 🙏
👍 1
j
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
That's true
j
It might not matter much in your case though 🙂
l
And when having an ID what do you recommend?
toMutableList().removeIf { it.id == removeMe.id }.toList()
?
j
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
`minus`/`-` removes a single instance, `removeIf`/`filter` removes all matching instances
1
l
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
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
If the list is sufficiently large to worry about performance overhead of iterating it, shouldn't it be a Map keyed on ID?
y
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
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
@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
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
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
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
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
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
There is
filterTo
for when you know the target size.