https://kotlinlang.org logo
#codereview
Title
# codereview
e

Ellen Spertus

11/27/2023, 1:39 AM
What's the best way to check if all elements of a
Collection
are the same, if nothing is known about the concrete type? Here are some ideas:
Copy code
val c: Collection<Int> = ??

// This may do more work than necessary.
println(c.toSet().size == 1)

// This requires converting the collection to a list.
val list = c.toList()
println(list.all { it == list[0] })
e

ephemient

11/27/2023, 2:16 AM
this will stop as soon as a duplicate is encountered
Copy code
val set = mutableSetOf<Any?>()
for (element in list) {
    set.add(element)
    if (set.size > 1) break
}
set.size == 1
there's some ways to shorten that, such as
Copy code
buildSet { list.asSequence().takeWhile { size <= 1 }.forEach(::add) }.size == 1
but it's not really clearer
your last solution could be made to work for any
Iterable
though,
Copy code
val sample = c.iterator().next()
list.all { it == sample }
assuming
c.isNotEmpty()
and also assuming the collection can be iterated more than once (true for
Collection
, but not necessarily
Sequence
for example)
e

Ellen Spertus

11/27/2023, 2:35 AM
Thanks so much!
k

Klitos Kyriacou

11/27/2023, 10:00 AM
I would define it the old-style procedural way:
Copy code
fun <T> Iterable<T>.allEqual(): Boolean {
    val iterator = iterator()
    val first = iterator.next()
    while (iterator.hasNext())
        if (iterator.next() != first)
            return false
    return true
}
👍 1
e

ephemient

11/27/2023, 11:45 AM
if you're going to do that, might as well handle the empty case with
Copy code
fun Iterable<*>.allEqual(): Boolean {
    val iterator = iterator()
    if (!iterator.hasNext()) {
        return false
    }
    val first = iterator.next()
    for (next in iterator) {
        if (next != first) {
            return false
        }
    }
    return true
}
or
Copy code
private object EmptySentinel
fun Iterable<*>.allEqual(): Boolean {
    return fold(EmptySentinel) { acc, elem ->
        when (acc) {
            EmptySentinel, elem -> elem
            else -> return false
        }
    } != EmptySentinel
}
p

Paul Woitaschek

11/28/2023, 5:51 PM
If this is not a performance problem you benchmarked, I'd go with the set solution
👍 1
m

Mark

12/08/2023, 2:40 PM
Bit late but here’s another one but taking advantage of return value from `Set.add`:
Copy code
fun Iterable<*>.allEqual(): Boolean {
    val firstItem = firstOrNull() ?: return true
    val set = mutableSetOf<Any?>(firstItem)
    return none(set::add)
}
🙏 1
Actually there was this more general case from a while back: https://kotlinlang.slack.com/archives/C0B8MA7FA/p1650514918864419
Oh, and here’s an ugly version of the above in one line:
Copy code
fun Iterable<*>.allEqual(): Boolean {
    return none(mutableSetOf<Any?>(firstOrNull() ?: return true)::add)
}
p

Paul Woitaschek

12/10/2023, 9:55 AM
That is not readable. I’d keep it a multiple lines 🤯
m

Mark

12/11/2023, 5:51 AM
Of course, no one would actually use something like that. Just for fun!
k

Klitos Kyriacou

12/11/2023, 11:12 AM
This wouldn't work for collections containing
null
.
firstOrNull()
returning
null
doesn't mean the collection is empty.
m

Mark

12/11/2023, 11:32 AM
Well spotted, actually we don’t need the check at all, though this means we always end up creating the mutable set:
Copy code
fun Iterable<*>.allEqual(): Boolean = none(mutableSetOf<Any?>(firstOrNull())::add)
Actually, this makes me think
firstOrNull
should only be on non-null iterables.
4 Views