2
\$\begingroup\$

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]].

Kotlin Playground

asked Dec 11, 2022 at 1:41
\$\endgroup\$
3
  • \$\begingroup\$ Out of curiosity, why "ideally in a functionally style"? \$\endgroup\$ Commented 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\$ Commented 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\$ Commented Dec 12, 2022 at 18:57

1 Answer 1

2
\$\begingroup\$

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 just acc.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.

answered Dec 11, 2022 at 14:11
\$\endgroup\$
3
  • \$\begingroup\$ Thanks, I thought I needed Comparable for elem == delim, but I now realize that equals is defined for all objects. \$\endgroup\$ Commented 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\$ Commented Dec 12, 2022 at 16:20
  • 1
    \$\begingroup\$ @FelixDombek Yup, if possible I would recommend using it as a Sequence instead of a List \$\endgroup\$ Commented Dec 12, 2022 at 17:48

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.