【问题标题】:Scala for loop Replace on ListScala for循环替换列表
【发布时间】:2013-09-26 08:09:41
【问题描述】:

也许这可能很容易解决,但您能帮我解决问题或指导我找到解决方案吗?我有一个删除函数,它通过一个元组列表“List [(String,Any)]”,当列表被循环时,我试图用 Nil 替换值的 1 索引。

但是当我尝试用 Nil 替换当前的 v 时,它说 v 被分配给“val”。现在我明白 scala 列表是不可变的。所以也许这就是问题所在?

我尝试了尾递归实现,但是当我退出 def 时,类型不匹配。即:是单位但必填:Option[Any]

// remove(k) removes one value v associated with key k
// from the dictionary, if any, and returns it as Some(v).  
// It returns None if k is associated to no value.  
def remove(key:String):Option[Any] = {
    for((k,v) <- d){
        if(k == key){
            var temp:Option[Any] = Some(v)
            v = Nil
            return temp
        } 
    }; None
}

这是试图弄清楚的另一种方法

  def remove(key:String):Option[Any] = {
    def removeHelper(l:List[(String,Any)]):List[(String,Any)] =
      l match {
        case Nil => Nil
        case (k,v)::t => if (key == k) t else (k,v)::removeHelper(t)
      }
    d = removeHelper(d)
  }

有什么建议吗?这是学校的家庭作业/项目,我想我可以为那些不喜欢帮助家庭作业的人添加它。

【问题讨论】:

  • 请注意,在评论中它说该函数应该“从字典中删除与键关联的值”,然后您使用的是List .也许您应该考虑使用Map,以便代码符合意图? :)

标签: list scala type-mismatch


【解决方案1】:

嗯,有很多方法可以回答这个问题。我将在这里用我自己的实现概述我能想到的那些,但这个列表绝不是详尽无遗的(也可能是最佳的实现)。

首先,您可以尝试使用现有的组合器 - 通常的组合器是 mapflatMapfoldLeftfoldRight

def remove_flatMap(key: String, list: List[(String, Any)]): List[(String, Any)] =
  // The Java developer in me rebels against creating that many "useless" instances.
  list.flatMap {a => if(a._1 == key) Nil else List(a)}

def remove_foldLeft(key: String, list: List[(String, Any)]): List[(String, Any)] =
  list.foldLeft(List[(String, Any)]()) {(acc, a) =>
    if(a._1 == key) acc
    else            a :: acc
  // Note the call to reverse here.
  }.reverse

// This is more obviously correct than the foldLeft version, but is not tail-recursive.
def remove_foldRight(key: String, list: List[(String, Any)]): List[(String, Any)] =
  list.foldRight(List[(String, Any)]()) {(a, acc) =>
    if(a._1 == key) acc
    else            a :: acc
  }

这些问题在于,据我所知,一旦达到特定条件,您就无法阻止它们:我认为它们不会直接解决您的问题,因为它们会删除 all em> key 的实例而不是第一个实例。

您还需要注意:

  • foldLeft 完成后必须反转列表,因为它以“错误”的顺序附加元素。
  • foldRight 没有这个缺陷,但不是尾递归:它会导致大型列表出现内存问题。
  • map 不能用于解决您的问题,因为它只能让我们修改列表的值,但不能修改其结构。

您也可以使用自己的实现。我已经包含了两个版本,一个是尾递归的,一个不是。尾递归显然更好,但也更冗长(我责怪使用 List[(String, Any)] 而不是 Map[String, Any] 的丑陋:

def remove_nonTailRec(key: String, list: List[(String, Any)]): List[(String, Any)] = list match {
  case h :: t if h._1 == key => t
  // This line is the reason our function is not tail-recursive.
  case h :: t                => h :: remove_nonTailRec(key, t)
  case Nil                   => Nil
}

def remove_tailRec(key: String, list: List[(String, Any)]): List[(String, Any)] = {
  @scala.annotation.tailrec
  def run(list: List[(String, Any)], acc: List[(String, Any)]): List[(String, Any)] = list match {
    // We've been aggregating in the "wrong" order again...
    case h :: t if h._1 == key => acc.reverse ::: t
    case h :: t                => run(t, h :: acc)
    case Nil                   => acc.reverse
  }

  run(list, Nil)

}

更好的解决方案当然是使用正确的工具来完成这项工作:Map[String, Any]

请注意,我认为我没有完全回答您的问题:我的示例 删除 key,而您想将其设置为 Nil。既然这是你的作业,我会让你弄清楚如何更改我的代码以满足你的要求。

【讨论】:

【解决方案2】:

List 如果任何键只应存在一次,则使用错误的集合。你应该使用Map[String,Any]。有一个列表,

  1. 您必须做额外的工作以防止重复条目。
  2. 检索密钥会更慢,它显示在列表的下方。尝试检索不存在的键将与列表的大小成比例地变慢。

我猜第 2 点可能是您尝试将其替换为 Nil 而不仅仅是从列表中删除密钥的原因。 Nil 真的不适合在这里使用。如果您尝试检索不存在的密钥与已删除的密钥相比,您将得到不同的东西。这真的是你想要的吗?返回Some(Nil) 有多大意义?

这里有一些方法适用于可变或不可变列表,但它们并不假设您已成功阻止重复出现...

val l1: List[(String, Any)] = List(("apple", 1), ("pear", "violin"), ("banana", Unit))
val l2: List[(Int, Any)] = List((3, 1), (4, "violin"), (7, Unit))

def remove[A,B](key: A, xs: List[(A,B)]) = (
  xs collect { case x if x._1 == key => x._2 }, 
    xs map { case x if x._1 != key => x; case _ => (key, Nil) }
)

scala>  remove("apple", l1)
res0: (List[(String, Any)], List[(String, Any)]) = (List((1)),List((apple, List()),(pear,violin), (banana,object scala.Unit)))

scala> remove(4, l2)
res1: (List[(Int, Any)], List[(Int, Any)]) = (List((violin)),List((3,1), (4, List()), (7,object scala.Unit)))

scala> remove("snark", l1)
res2: (List[Any], List[(String, Any)]) = (List(),List((apple,1), (pear,violin), (banana,object scala.Unit)))

返回匹配值的列表(如果不匹配,则返回一个空列表而不是None)和剩余的列表,在一个元组中。如果您想要一个完全删除不需要的密钥的版本,请执行此操作...

def remove[A,B](key: A, xs: List[(A,B)]) = (
  xs collect { case x if x._1 == key => x._2 }, 
  xs filter { _._1 != key }
)

但也要看看这个:

scala> l1 groupBy {
         case (k, _) if k == "apple" => "removed",
         case _ => "kept"
       }
res3: scala.collection.immutable.Map[String,List[(String, Any)]] = Map(removed -> List((apple,1)), kept -> List((pear,violin), (banana,object scala.Unit)))

这是你可以开发的东西。您需要做的就是将("apple", Nil) 添加到“保留”列表中并从“删除”列表中提取值。

请注意,我使用的是 List 组合函数,而不是编写自己的递归代码;这通常会使代码更清晰,并且通常与手动递归函数一样快或更快。

还请注意,我不会更改原始列表。这意味着我的函数适用于可变和不可变列表。如果您有一个可变列表,请随时将我返回的列表分配为您的可变 var 的新值。赢了,赢了。

但是请为此使用地图。看看事情变得多么简单:

val m1: Map[String, Any] = Map(("apple", 1), ("pear", "violin"), ("banana", Unit))
val m2: Map[Int, Any] = Map((3, 1), (4, "violin"), (7, Unit))

def remove[A,B](key: A, m: Map[A,B]) = (m.get(key), m - key)

scala> remove("apple", m1)
res0: (Option[Any], scala.collection.immutable.Map[String,Any]) = (Some(1),Map(pear -> violin, banana -> object scala.Unit))

scala> remove(4, m2)
res1: (Option[Any], scala.collection.immutable.Map[Int,Any]) = (Some(violin),Map(3 -> 1, 7 -> object scala.Unit))

scala> remove("snark", m1)
res2: res26: (Option[Any], scala.collection.immutable.Map[String,Any]) = (None,Map(apple -> 1, pear -> violin, banana -> object scala.Unit))

组合函数使事情变得更容易,但是当你使用正确的集合时,它变得如此简单,以至于几乎不值得编写一个特殊的函数。当然,除非您试图隐藏数据结构——在这种情况下,您应该将其真正隐藏在对象中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-16
    • 2017-09-13
    • 1970-01-01
    • 2022-01-14
    • 2011-09-22
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    相关资源
    最近更新 更多