I want to split a list (as if splitting a string by a delimiter), ideally in a functional style, and was surprised how hard it seems to be. Or maybe my code is just unnecessarily complicated?
fun <ElemType : Comparable<ElemType>> Iterable<ElemType>.split(delim: ElemType):
List<List<ElemType>> {
return this.fold(emptyList<List<ElemType>>()) {
acc: List<List<ElemType>>, elem: ElemType ->
if (elem == delim)
acc + listOf(emptyList<ElemType>())
else if (acc.isEmpty())
listOf<List<ElemType>>(listOf<ElemType>(elem))
else
acc.take(acc.size - 1) + listOf(acc.last() + elem)
}
}
fun main() {
val l = listOf("1", "", "2", "3", "", "", "4")
println(l.split(""))
val l2 = listOf(1, 0, 2, 3, 0, 0, 4)
println(l2.split(0))
}
The result for both cases is [[1], [2, 3], [], [4]]
.
-
\$\begingroup\$ Out of curiosity, why "ideally in a functionally style"? \$\endgroup\$Simon Forsberg– Simon Forsberg2022年12月12日 16:18:53 +00:00Commented Dec 12, 2022 at 16:18
-
\$\begingroup\$ Because I can write procedural code in a lot of languages, but am learning functional idioms in Kotlin. \$\endgroup\$Felix Dombek– Felix Dombek2022年12月12日 16:22:00 +00:00Commented Dec 12, 2022 at 16:22
-
\$\begingroup\$ Well you chose a tough assignment to learn functional idioms with xD But possibly if you would have handled it list-by-list by searching for the delimiters instead of element-by-element it might have been easier. \$\endgroup\$Simon Forsberg– Simon Forsberg2022年12月12日 18:57:43 +00:00Commented Dec 12, 2022 at 18:57
1 Answer 1
It's complicated because you are making it unnecessarily complicated.
I wouldn't use .fold
to accomplish this.
Think about how many list operations this line does:
acc.take(acc.size - 1) + listOf(acc.last() + elem)
acc.take(acc.size - 1)
copies the current list of sublists except the last.listOf(acc.last() + elem)
(I believe this could be justacc.last() + elem
?) creates a new list with one element appended to the last list.
We know that once we've seen a delimiter, we don't need to touch any previous lists, we can just continue looking at what's ahead of us.
This makes it a perfect choice for a sequence
builder.
fun <ElemType> Iterable<ElemType>.split(delim: ElemType): List<List<ElemType>> {
return sequence {
val currentList = mutableListOf<ElemType>()
for (elem in this@split) {
if (elem == delim) {
yield(currentList.toList())
currentList.clear()
} else {
currentList.add(elem)
}
}
yield(currentList.toList())
}.toList()
}
Other notes: Your function requires ElemType
to extend Comparable<ElemType>
. I do not see any reason for this. You can remove that restriction.
-
\$\begingroup\$ Thanks, I thought I needed
Comparable
forelem == delim
, but I now realize thatequals
is defined for all objects. \$\endgroup\$Felix Dombek– Felix Dombek2022年12月12日 16:14:41 +00:00Commented Dec 12, 2022 at 16:14 -
\$\begingroup\$ And maybe I don't even need the
toList()
at the end ... this could even be lazy. \$\endgroup\$Felix Dombek– Felix Dombek2022年12月12日 16:20:31 +00:00Commented Dec 12, 2022 at 16:20 -
1\$\begingroup\$ @FelixDombek Yup, if possible I would recommend using it as a
Sequence
instead of aList
\$\endgroup\$Simon Forsberg– Simon Forsberg2022年12月12日 17:48:05 +00:00Commented Dec 12, 2022 at 17:48