【问题标题】:Split a lists into multiple sub lists with predicate - looking for a nice way to write this code使用谓词将列表拆分为多个子列表 - 寻找编写此代码的好方法
【发布时间】:2021-12-09 02:35:17
【问题描述】:

假设我有一个整数列表:[2, 4, 4, 6, 1, 8, 3, 5, 2, 2, 2],我想将此列表拆分为子列表,以便每个子列表包含与谓词匹配的值,或者如果不匹配则只有不匹配的值t 匹配,并且应该按照原始列表的顺序。

例如,如果谓词是“偶数”,我希望得到以下结果: [[2, 4, 4, 6],[1],[8],[3, 5],[2, 2, 2]]

我有一个有效的代码,但在我看来它很丑陋,我觉得应该有更好的方法来编写它:

private fun <T : Any> toSubLists(values: List<T>, predicate: (T) -> Boolean): List<List<T>> {
  val subLists = mutableListOf<List<T>>()
  var currentList = mutableListOf<T>()
  values.forEach {
    if (!predicate(it)) {
      if (currentList.isNotEmpty()) {
        subLists.add(currentList)
        currentList = mutableListOf()
      }
      subLists.add(listOf(it))
    } else {
      currentList.add(it)
    }
  }
  if (currentList.isNotEmpty()) {
    subLists.add(currentList)
  }
  return subLists
}

【问题讨论】:

标签: algorithm kotlin data-structures


【解决方案1】:

不确定它是否更好,因为它可能更难理解,但这是一种方法

private fun <T : Any> toSubLists(values: List<T>, predicate: (T) -> Boolean): List<List<T>> = 
    values.withIndex().groupBy({
        var index = it.index
        while (index != 0 && predicate(values[index]) && predicate(values[index - 1])) index--
        index
    }){
        it.value
    }.values.toList()

看到它在工作here

【讨论】:

    【解决方案2】:

    a feature request 是一个通用版本,它接受除谓词之外的其他键选择器。

    同时,对于您的用例,我认为我们可以简化一下:

    fun <T> List<T>.groupRunsBy(predicate: (T) -> Boolean): List<List<T>> {
        val result = mutableListOf<MutableList<T>>()
        var currentlyMatching: Boolean? = null
        forEach {
            val itemMatches = predicate(it)
            if (itemMatches == currentlyMatching) {
                result.last().add(it)
            } else {
                currentlyMatching = itemMatches
                result.add(mutableListOf(it))
            }
        }
        return result
    }
    
    fun main() {
        val list = listOf(2, 4, 4, 6, 1, 8, 3, 5, 2, 2, 2)
        
        println(list.groupRunsBy { it % 2 == 0 }) // [[2, 4, 4, 6], [1], [8], [3, 5], [2, 2, 2]]
    }
    

    在这里测试:https://pl.kotl.in/OHVr7S6lX

    可以轻松更改此版本以支持任何返回非空键的选择器。

    【讨论】:

    • 恕我直言,此解决方案不正确(尽管它给出的结果与 OP 状态相同,但该解决方案不是 OP 代码提供的解决方案)。除了这个答案,您将得到相同的结果 { it % 2 == 0 } 和 { it % 2 != 0 }。
    • 我可能误解了这个要求,我是基于 OP 的例子,但你说得对,描述确实似乎不匹配。
    【解决方案3】:
    fun <T : Any> toSubLists(values: List<T>, predicate: (T) -> Boolean): List<List<T>> {
      val items = values.map { predicate(it) to listOf(it) }.toMutableList()
      for (i in items.indices.last downTo 1) {
        if (items[i - 1].first && items[i].first) {
          items[i - 1] = items[i - 1].first to items[i - 1].second + items[i].second
          items.removeAt(i)
        }
      }
      return items.map { it.second }
    }
    
    val list1 = listOf(2, 4, 4, 6, 1, 8, 3, 5, 2, 2, 2)
    val isOdd: (Int) -> Boolean = { it % 2 != 0 }
    val isEven: (Int) -> Boolean = { it % 2 == 0 }
    
    println(toSubLists(list1, isOdd))   // [[2], [4], [4], [6], [1], [8], [3, 5], [2], [2], [2]]
    println(toSubLists(list1, isEven))  // [[2, 4, 4, 6], [1], [8], [3], [5], [2, 2, 2]]
    

    【讨论】:

    • 这不符合 OP 的要求。与谓词不匹配的连续元素都在单项列表中,而不是在一起
    • 当您查阅 OP 的建议结果时,您是对的。但是当您运行 OP 的代码时,我提供的解决方案是正确的([3] 和 [5] 对于 isEven 是分开的)。
    【解决方案4】:
    fun <T : Any> toSubLists(values: List<T>, predicate: (T) -> Boolean): List<List<T>> {
      return values
        .map { listOf(predicate(it) to listOf(it)) }
        .reduce { acc, item ->
          if (acc.last().first && item.first().first)
            acc.subList(0, acc.size - 1) + listOf(acc.last().first to acc.last().second + item.first().second)
          else
            acc + item
        }
        .map { it.second }
    }
    
    val list1 = listOf(2, 4, 4, 6, 1, 8, 3, 5, 2, 2, 2)
    val isOdd: (Int) -> Boolean = { it % 2 != 0 }
    val isEven: (Int) -> Boolean = { it % 2 == 0 }
    
    println(toSubLists(list1, isOdd))   // [[2], [4], [4], [6], [1], [8], [3, 5], [2], [2], [2]]
    println(toSubLists(list1, isEven))  // [[2, 4, 4, 6], [1], [8], [3], [5], [2, 2, 2]]
    

    【讨论】:

    • 这不符合 OP 的要求。与谓词不匹配的连续元素都在单项列表中,而不是在一起
    • 当您查阅 OP 的建议结果时,您是对的。但是当您运行 OP 的代码时,我提供的解决方案是正确的([3] 和 [5] 对于 isEven 是分开的)。
    【解决方案5】:

    这是一个简单的递归解决方案:

    fun <E> toSubListsRecursive(
      list: List<E>,
      startIndex: Int = 0,
      accMainList: MutableList<List<E>> = mutableListOf(),
      accSubList: MutableList<E> = mutableListOf(),
      newAccSubListRequired: (Boolean) -> Boolean = { true },
      predicate: (E) -> Boolean
    ): List<List<E>> =
      if (list.lastIndex < startIndex) {
        accMainList
      } else {
        val currElement = list[startIndex]
        val currPredicateResult = predicate(currElement)
        val currAccSubList =
          if (newAccSubListRequired(currPredicateResult)) mutableListOf(currElement).also(accMainList::add)
          else accSubList.apply { add(currElement) }
        toSubListsRecursive(list, startIndex + 1, accMainList, currAccSubList, { x -> x != currPredicateResult }, predicate)
      }
    

    适用于折叠的类似解决方案:

    data class Accumulator<E>(
      var mainList: MutableList<List<E>>,
      var subList: MutableList<E>,
      var newAccSubListRequired: (Boolean) -> Boolean
    )
    
    fun <E> toSubListsWithFold(list: List<E>, predicate: (E) -> Boolean): List<List<E>> =
      list.fold<E, Accumulator<E>>(Accumulator(mutableListOf(), mutableListOf()) { true }) { acc, currElement ->
        val currPredicateResult = predicate(currElement)
        val currSubAcc =
          if (acc.newAccSubListRequired(currPredicateResult)) mutableListOf(currElement).also(acc.mainList::add)
          else acc.subList.apply { add(currElement) }
        acc.apply {
          subList = currSubAcc;
          newAccSubListRequired = { x -> x != currPredicateResult }
        }
      }.mainList
    

    如果谓词不昂贵,您可以简化(改编自this answer):

    fun <E> anotherToSubListWithFold(list: List<E>, predicate: (E) -> Boolean): List<List<E>> =
      list.fold(mutableListOf<MutableList<E>>()) { acc, curr ->
        if (acc.isEmpty() || acc.last().first().let(predicate) != curr.let(predicate)) {
          acc.apply { add(mutableListOf(curr)) }
        } else {
          acc.apply { last().add(curr) }
        }
      }
    

    【讨论】:

      猜你喜欢
      • 2010-10-09
      • 1970-01-01
      • 2011-01-05
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-06
      相关资源
      最近更新 更多