Scala 从不可变 List 中“移除”一个元素的惯用方法是什么?

我有一个 List,它可能包含相等的比较元素。我想要一个类似的列表,但删除了一个元素。因此,从(A,B,C,B,D)我希望能够“删除”只有一个 B 得到例如(A,C,B,D)。结果中元素的顺序并不重要。

我有一些工作代码,是用 Scala 的 Lisp 编写的 做这个?

上下文是一个纸牌游戏,其中有两副标准牌,所以可能 是复制的牌,但是仍然一张一张地打。

def removeOne(c: Card, left: List[Card], right: List[Card]): List[Card] = {
if (Nil == right) {
return left
}
if (c == right.head) {
return left ::: right.tail
}
return removeOne(c, right.head :: left, right.tail)
}


def removeCard(c: Card, cards: List[Card]): List[Card] = {
return removeOne(c, Nil, cards)
}
111331 次浏览

Unfortunately, the collections hierarchy got itself into a bit of a mess with - on List. For ArrayBuffer it works just like you might hope:

scala> collection.mutable.ArrayBuffer(1,2,3,2,4) - 2
res0: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 3, 2, 4)

but, sadly, List ended up with a filterNot-style implementation and thus does the "wrong thing" and throws a deprecation warning at you (sensible enough, since it is actually filterNoting):

scala> List(1,2,3,2,4) - 2
warning: there were deprecation warnings; re-run with -deprecation for details
res1: List[Int] = List(1, 3, 4)

So arguably the easiest thing to do is convert List into a collection that does this right, and then convert back again:

import collection.mutable.ArrayBuffer._
scala> ((ArrayBuffer() ++ List(1,2,3,2,4)) - 2).toList
res2: List[Int] = List(1, 3, 2, 4)

Alternatively, you could keep the logic of the code you've got but make the style more idiomatic:

def removeInt(i: Int, li: List[Int]) = {
def removeOne(i: Int, left: List[Int], right: List[Int]): List[Int] = right match {
case r :: rest =>
if (r == i) left.reverse ::: rest
else removeOne(i, r :: left, rest)
case Nil => left.reverse
}
removeOne(i, Nil, li)
}


scala> removeInt(2, List(1,2,3,2,4))
res3: List[Int] = List(1, 3, 2, 4)

How about

def removeCard(c: Card, cards: List[Card]) = {
val (head, tail) = cards span {c!=}
head :::
(tail match {
case x :: xs => xs
case Nil => Nil
})
}

If you see return, there's something wrong.

// throws a MatchError exception if i isn't found in li
def remove[A](i:A, li:List[A]) = {
val (head,_::tail) = li.span(i != _)
head ::: tail
}

As one possible solutions you can find index of the first suitable element and then remove element at this index:

def removeOne(l: List[Card], c: Card) = l indexOf c match {
case -1 => l
case n => (l take n) ++ (l drop (n + 1))
}

You could try this:

scala> val (left,right) = List(1,2,3,2,4).span(_ != 2)
left: List[Int] = List(1)
right: List[Int] = List(2, 3, 2, 4)


scala> left ::: right.tail
res7: List[Int] = List(1, 3, 2, 4)

And as method:

def removeInt(i: Int, li: List[Int]) = {
val (left, right) = li.span(_ != i)
left ::: right.drop(1)
}

I haven't seen this possibility in the answers above, so:

scala> def remove(num: Int, list: List[Int]) = list diff List(num)
remove: (num: Int,list: List[Int])List[Int]


scala> remove(2,List(1,2,3,4,5))
res2: List[Int] = List(1, 3, 4, 5)

Edit:

scala> remove(2,List(2,2,2))
res0: List[Int] = List(2, 2)

Like a charm :-).

Just another thought on how to do this using a fold:

def remove[A](item : A, lst : List[A]) : List[A] = {
lst.:\[List[A]](Nil)((lst, lstItem) =>
if (lstItem == item) lst else lstItem::lst )
}

You could use the filterNot method.

val data = "test"
list = List("this", "is", "a", "test")
list.filterNot(elm => elm == data)
 def removeAtIdx[T](idx: Int, listToRemoveFrom: List[T]): List[T] = {
assert(listToRemoveFrom.length > idx && idx >= 0)
val (left, _ :: right) = listToRemoveFrom.splitAt(idx)
left ++ right
}
val list : Array[Int] = Array(6, 5, 3, 1, 8, 7, 2)
val test2 = list.splitAt(list.length / 2)._2
val res = test2.patch(1, Nil, 1)
object HelloWorld {


def main(args: Array[String]) {


var months: List[String] = List("December","November","October","September","August", "July","June","May","April","March","February","January")


println("Deleting the reverse list one by one")


var i = 0


while (i < (months.length)){


println("Deleting "+months.apply(i))


months = (months.drop(1))


}


println(months)


}


}

Generic Tail Recursion Solution:

def removeElement[T](list: List[T], ele: T): List[T] = {
@tailrec
def removeElementHelper(list: List[T],
accumList: List[T] = List[T]()): List[T] = {
if (list.length == 1) {
if (list.head == ele) accumList.reverse
else accumList.reverse ::: list
} else {
list match {
case head :: tail if (head != ele) =>
removeElementHelper(tail, head :: accumList)
case head :: tail if (head == ele) => (accumList.reverse ::: tail)
case _                             => accumList
}
}
}
removeElementHelper(list)
}