如何从 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 将糖分转化为 flatMap
和 withFilter
(lazy 过滤器),
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)
中,而附加一个 List
是 O(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.
上似乎都有些繁重。
是否有任何方法可以从 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 将糖分转化为 flatMap
和 withFilter
(lazy 过滤器),
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)
中,而附加一个 List
是 O(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.