【问题标题】:What is an idiomatic Scala way to "remove" one element from an immutable List?从不可变列表中“删除”一个元素的惯用 Scala 方式是什么?
【发布时间】:2011-08-03 22:47:45
【问题描述】:

我有一个列表,其中可能包含比较相等的元素。我想要一个类似的列表,但删除了一个元素。所以从 (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)
}

【问题讨论】:

  • 添加了一个注释,在这种特定情况下,结果列表的顺序无关紧要。
  • 所以这个问题中的List[Card]是玩家的手?
  • @Ken Bloom,是的,这是玩家的手牌。
  • 你知道,我搜索了一段时间这样的问题,然后发布了同样的问题,然后在我浏览并等待人们回答我的时候找到了这个问题。我想我现在应该投票结束我自己的问题作为重复。 ;-)
  • 这个关于 Clojure 的问题:stackoverflow.com/questions/7662447/…

标签: list scala


【解决方案1】:

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

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)

就像一个魅力:-)。

【讨论】:

  • 不错!我会在列表中再添加 2 个,以明确只删除一个元素。
【解决方案2】:

您可以使用filterNot 方法。

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

【讨论】:

  • 这将删除所有等于 "test" 的元素 - 而不是所要求的;)
  • 实际上它会完全满足您的需求。它将返回列表中的所有元素,除了那些不等于“test”的元素。注意它使用 filterNot
  • 最初的问题是如何删除 SINGLE 实例。并非所有实例。
  • @Søren Mathiasen 如果我想过滤出像 val data = Seq("test","a") 这样的序列这样的多个元素,该怎么做?
【解决方案3】:

你可以试试这个:

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

【讨论】:

  • 值得注意的是left ::: right.drop(1)比带有isEmpty的if语句短。
  • 谢谢,是否有任何情况下更喜欢 .drop(1) 而不是 .tail,反之亦然?
  • @James Petry - 如果你在一个空列表上调用tail,你会得到一个例外:scala> List().tail java.lang.UnsupportedOperationException: tail of empty listdrop(1) 在一个空列表上但是返回一个空列表。
  • tail 如果列表为空(即没有head)会抛出异常。 drop(1) 在一个空列表上只会产生另一个空列表。
【解决方案4】:

不幸的是,集合层次结构在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 样式实现,因此做了“错误的事情”并且向您抛出了弃用警告(足够明智,因为它实际上是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)

因此可以说最简单的做法是将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)。我认为这不是你想要的。
  • @Ken Bloom - 确实。这是原始算法中的错误,我没有考虑就复制了它。现已修复。
  • 问题规范中有更多遗漏,因为在我的具体情况下顺序无关紧要。很高兴看到保留订单的版本,谢谢。
  • @Rex:'filterNot 做“错误的事情”'是什么意思?它正在删除所有事件?为什么它会发出弃用警告?谢谢
  • @teo - 它删除所有出现的事件(这不是这里想要的),并且它已被弃用,因为它可能被破坏了(或者可能所需的行为不清楚 - 无论哪种方式,它都已被弃用2.9 并在 2.10 中消失)。
【解决方案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
 }

【讨论】:

    【解决方案6】:

    怎么样

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

    如果你看到return,那就有问题了。

    【讨论】:

    • 这并没有做他想做的事,就是只删除c的第一个实例
    • 这将删除所有卡片c,但只应删除第一个。
    • 我应该更仔细地阅读问题!更正了我的答案。
    • +1 表示“如果您看到返回,则说明有问题。”这本身就是一个非常重要的“惯用 Scala”课程。
    【解决方案7】:
    // 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
    }
    

    【讨论】:

      【解决方案8】:

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

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

      【讨论】:

      • 使用span查看我的答案以做同样的事情。
      【解决方案9】:

      关于如何使用折叠做到这一点的另一个想法:

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

      【讨论】:

        【解决方案10】:

        通用尾递归解决方案:

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

        【讨论】:

          【解决方案11】:
          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)
          

          【讨论】:

            【解决方案12】:
            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)
            
                }
            
            }
            

            【讨论】:

            • 能否请您添加一些解释(cmets、描述)来说明这是如何回答问题的?
            • 1.这个问题在 5 年前被问及并得到了回答。 2. OP 要求使用“惯用的”Scala。使用 2 个 vars 和一个 while 循环不是 Scala 惯用的。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-01-30
            • 1970-01-01
            • 2012-10-03
            • 1970-01-01
            • 1970-01-01
            • 2021-09-23
            相关资源
            最近更新 更多