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


84

我有一个列表,其中可能包含将进行相等比较的元素。我想要一个类似的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)
}

添加了一条注释,即在此特定情况下结果列表的顺序无关紧要。
Gavilan Comun

所以List[Card]这个问题是玩家的手吗?
肯·布鲁姆

@肯·布鲁姆(Ken Bloom),是的,这很正确。
Gavilan Comun

您知道,我搜索了一段时间这样的问题,然后发布了相同的问题,然后在浏览并等待人们回答我的问题时找到了这个问题。猜猜我现在应该重复做一遍以结束我自己的问题。;-)
乔·卡纳汉

:这个问题对于Clojure的stackoverflow.com/questions/7662447/...
加维兰Comun

Answers:


144

我在上面的答案中没有看到这种可能性,所以:

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)

编辑:

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

就像一个魅力:-)。


18
真好!我会在列表中添加另外2个,以表明仅删除了一个元素。
Frank S. Thomas

38

您可以使用该filterNot方法。

val data = "test"
list = List("this", "is", "a", "test")
list.filterNot(elm => elm == data)

21
这将删除所有等于“ test”的元素-不是要求的内容;)
yǝsʞǝla2013年

1
实际上,它将完全满足您的需求。它将返回列表中的所有元素,但不等于“ test”的元素除外。请注意,它使用filterNot
btbvoy 2014年

14
最初的问题是如何删除单个实例。并非所有实例。
ty1824 2015年

18

您可以尝试以下方法:

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)

并作为方法:

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

3
值得注意的是,left ::: right.drop(1)它比带if的语句短isEmpty
Rex Kerr

2
谢谢,是否有任何情况比.tail更喜欢.drop(1),反之亦然?
加维兰·库姆

8
@James Petry-如果呼叫tail空白清单,则会产生例外:scala> List().tail java.lang.UnsupportedOperationException: tail of empty listdrop(1)在一个空列表上但是返回一个空列表。
Frank S. Thomas

3
tail如果列表为空(即没有head),则引发异常。 drop(1)在一个空列表上只会产生另一个空列表。
Rex Kerr

8

不幸的是,集合层次结构使-on陷入混乱List。因为ArrayBuffer它的工作就像您可能希望的那样:

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

但是,可悲的是,List最终得到一个filterNot-style实现,因此做了“错误的事情”,并向您抛出了弃用警告(足够明智,因为它实际上正在执行filterNot):

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)

因此可以说,最简单的方法是将其转换List为执行此操作的集合,然后再次转换回:

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

另外,您可以保留已有代码的逻辑,但使样式更惯用:

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)

removeInt(5,List(1,2,6,4,5,3,6,4,6,5,1))产量List(4, 6, 2, 1, 3, 6, 4, 6, 5, 1)。我认为这不是您想要的。
肯·布鲁姆

@肯·布鲁姆-的确如此。这是原始算法中的一个错误,我没有充分考虑就将其复制。立即修复。
Rex Kerr

问题规范中的遗漏之处在于,在我的具体情况下顺序无关紧要。很高兴看到保留订单的版本,谢谢。
Gavilan Comun

@Rex:“ filterNot是“错误的东西””是什么意思?它消除了所有情况?为什么会引发弃用警告?谢谢
teo

1
@teo-它将删除所有出现的内容(此处不是所需的内容),并且已弃用,因为可以说它已损坏(或者可能所需的行为尚不清楚-两种方式都已弃用,而2.9中已弃用)。
Rex Kerr 2013年

5
 def removeAtIdx[T](idx: Int, listToRemoveFrom: List[T]): List[T] = {
    assert(listToRemoveFrom.length > idx && idx >= 0)
    val (left, _ :: right) = listToRemoveFrom.splitAt(idx)
    left ++ right
 }

2

怎么样

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

如果看到return,则说明有问题。


1
这并没有满足他的要求,这只是删除了c
Ken Bloom

1
这将删除所有卡c,但仅应删除第一张。
tenshi 2011年

我应该更仔细地阅读问题!更正了我的答案。
尤金·横田

+1表示“如果您看到回报,那说明您出了点问题。” 就其本身而言,这是非常重要的“惯用Scala”课程。
乔·卡纳汉

2
// 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
}

1

作为一种可能的解决方案,您可以找到第一个合适元素的索引,然后在该索引处删除元素:

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

看到我的回答span用来做同样的事情。
肯·布鲁姆

0

关于如何使用折叠的另一个想法:

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

0

通用尾递归解决方案:

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)
  }


-4
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)

    }

}

您能否添加一些关于此答案的解释(注释,描述)?
rjp

4
1.这个问题是在5年前提出并回答的。2. OP要求提供“惯用的” Scala。使用2 vars和一个while循环不是惯用的Scala。
jwvh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.