如何从 Scala 列表中删除第 n 个元素?

how to remove every nth element from the Scala list?

是否有任何方法可以从 Scala List 中删除每个 n th element

我希望我们可以通过编写逻辑在 filter 方法和 return 另一个列表中做到这一点。但这是有效的方法吗?

简单:

list.zipWithIndex
    .filter { case (_, i) => (i + 1) % n != 0 }
    .map { case (e, _) => e }

到目前为止最简单,我认为

def removeNth[A](myList: List[A], n: Int): List[A] = 
  myList.zipWithIndex collect { case (x,i) if (i + 1) % n != 0 => x }

collect 是一个经常被遗忘的 gem,它将 partial 函数作为其第二个参数,使用该函数映射元素并忽略那些是不在其域内。

一种没有索引的方法,通过将列表切成每个长度为 nth 的块,

xs.grouped(nth).flatMap(_.take(nth-1)).toList

grouped 交付的每个块中,我们最多提取 nth-1 项。

这种其他方法效率不高(请注意@Alexey Romanov 的评论),通过使用 for comprehension 将糖分转化为 flatMapwithFilterlazy 过滤器),

for (i <- 0 until xs.size if i % nth != nth-1) yield xs(i)

这是一个没有索引的递归实现。

  def drop[A](n: Int, lst: List[A]): List[A] = {
    def dropN(i: Int, lst: List[A]): List[A] = (i, lst) match {
      case (0, _ :: xs) => dropN(n, xs)
      case (_, x :: xs) => x :: dropN(i - 1, xs)
      case (_, x) => x
    }
    dropN(n, lst)
  }

另一种选择,接近@elm 的答案,但考虑到 drop(1) 对于列表比 take 几乎整个列表快得多:

def remove[A](xs: List[A], n: Int) = {
  val (firstPart, rest) = xs.splitAt(n - 1)
  firstPart ++ rest.grouped(n).flatMap(_.drop(1))
}

这是使用累加器的列表的尾递归实现:

  import scala.annotation.tailrec
  def dropNth[A](lst: List[A], n: Int): List[A] = {
    @tailrec
    def dropRec(i: Int, lst: List[A], acc: List[A]): List[A] = (i, lst) match {
      case (_, Nil) => acc
      case (1, x :: xs) => dropRec(n, xs, acc)
      case (i, x :: xs) => dropRec(i - 1, xs, x :: acc)
    }
    dropRec(n, lst, Nil).reverse
  }

更新: 如评论中所述,我已经在大 (1 to 5000000).toList 输入上尝试了其他解决方案。具有 zipWithIndex filter/collect 的那些在 OutOfMemoryError 上失败,并且(非尾)递归在 WhosebugError 上失败。我使用 List cons (::) 和 tailrec 效果很好。

这是因为 zipping-with-index 会创建新的 ListBuffer 并附加元组,从而导致 OOM。 而且recursive干脆就有500万级的递归,对stack来说太多了。

尾递归不会创建不必要的对象并有效地创建输入的两个副本(即 2*5 百万个 :: 实例),都在 O(n) 中。第一个是创建过滤后的元素,它们的顺序是相反的,因为输出是前置 x :: acc(在 O(1) 中,而附加一个 ListO(n))。第二个就是递归输出的逆向

最简单的解决方案

scala> def dropNth[T](list:List[T], n:Int) :List[T] = {
     | list.take(n-1):::list.drop(n)
     | }

另一种方法:为 List 创建一个完全满足您需要的函数。这与 Martin 的 dropNth 函数相同,但不需要 O(n) reverse:

    import scala.collection.mutable.ListBuffer

    implicit class improvedList[A](xs: List[A]) {
      def filterAllWhereIndex(n: Int): List[A] = {
        var i = 1
        var these = xs
        val b = new ListBuffer[A]
        while (these.nonEmpty) {
          if (i !=  n) {
            b += these.head
            i += 1
          } else i = 1
          these = these.tail
        }
        b.result
      }
    }

    (1 to 5000000).toList filterAllWhereIndex 3

如果你想要高效,这个就可以了。另外,它可以用作中缀运算符,如上所示。这是一个很好的模式,可以避免使用 zipWithIndex,这在时间和 space.

上似乎都有些繁重。