【问题标题】:Scala - modify strings in a list based on their number of occurencesScala - 根据出现次数修改列表中的字符串
【发布时间】:2015-03-05 23:23:00
【问题描述】:

另一个 Scala 新手问题,因为我不知道如何以功能方式实现这一点(主要来自脚本语言背景):

我有一个字符串列表:

val food-list = List("banana-name", "orange-name", "orange-num", "orange-name", "orange-num", "grape-name")

在它们重复的地方,我想在字符串中添加一个递增的数字,并在类似于输入列表的列表中获取它,如下所示:

List("banana-name", "orange1-name", "orange1-num", "orange2-name", "orange2-num", "grape-name")

我已将它们分组以计算它们的数量:

val freqs = list.groupBy(identity).mapValues(v => List.range(1, v.length + 1))

这给了我:

Map(orange-num -> List(1, 2), banana-name -> List(1), grape-name -> List(1), orange-name -> List(1, 2))

列表的顺序很重要(它应该是food-list 的原始顺序)所以我知道此时使用地图对我来说是有问题的。我觉得最接近解决方案的是:

food-list.map{l =>

    if (freqs(l).length > 1){

            freqs(l).map(n => 
                           l.split("-")(0) + n.toString + "-" + l.split("-")(1))

    } else {
        l
    }
}

这当然给了我一个不稳定的输出,因为我正在从freqs 中的单词值映射频率列表

List(banana-name, List(orange1-name, orange2-name), List(orange1-num, orange2-num), List(orange1-name, orange2-name), List(orange1-num, orange2-num), grape-name)

如何以 Scala fp 方式完成此操作,而无需使用笨拙的 for 循环和计数器?

【问题讨论】:

  • 每次给定值的上一次出现都可以计数,还是太慢了(O(n^2))?
  • 就我的目的而言,我不会有任何列表太长以至于会产生明显的差异,因此以前的出现不会成为问题,但试图找出 Scala 中的有效计数即将到来来自 Python 目前对我来说不是很明显
  • count (scala-lang.org/api/current/…) 方法在Seqs 上可用,所以我认为可能与 Python 非常相似。无论如何,我最后的解决方案没有使用它,虽然它需要一个reverse,所以它不是最有效的。

标签: string list scala increment


【解决方案1】:

如果索引很重要,有时最好使用zipWithIndex(非常类似于Python 的enumerate)来明确地跟踪它们:

food-list.zipWithIndex.groupBy(_._1).values.toList.flatMap{
  //if only one entry in this group, don't change the values
  //x is actually a tuple, could write case (str, idx) :: Nil => (str, idx) :: Nil
  case x :: Nil => x :: Nil
  //case where there are duplicate strings
  case xs => xs.zipWithIndex.map {
    //idx is index in the original list, n is index in the new list i.e. count
    case ((str, idx), n) =>
      //destructuring assignment, like python's (fruit, suffix) = ...
      val Array(fruit, suffix) = str.split("-")
      //string interpolation, returning a tuple
      (s"$fruit${n+1}-$suffix", idx)
  }
//We now have our list of (string, index) pairs;
//sort them and map to a list of just strings
}.sortBy(_._2).map(_._1)

【讨论】:

    【解决方案2】:

    高效简单:

    val food = List("banana-name", "orange-name", "orange-num", 
                 "orange-name", "orange-num", "grape-name")
    
    def replaceName(s: String, n: Int) = {
      val tokens = s.split("-")
      tokens(0) + n + "-" + tokens(1)
    }
    
    val indicesMap = scala.collection.mutable.HashMap.empty[String, Int]
    val res = food.map { name =>
      {
        val n = indicesMap.getOrElse(name, 1)
        indicesMap += (name -> (n + 1))
        replaceName(name, n)
      }
    }
    

    【讨论】:

    • 有效,但可变映射并不完全符合习惯。
    • 但是这里很有趣,因为它可以即时完成
    • @JeanLogeart 当列表仅包含一次出现时,您没有处理香蕉名称的情况。您需要事先多做一步。
    • 你是对的。然后我的解决方案是错误的,我不能随便做。即使它没有严格回答 OP 问题,我也将其作为替代方案。
    • 好吧,您可以快速创建一个查找表,使用 groupBy(identity) 检查基数。或者,如果您坚持不创建额外的变量,food.count 可能会降低性能。
    【解决方案3】:

    Here 试图提供您对foldLeft 的期望:

    foodList.foldLeft((List[String](), Map[String, Int]()))//initial value
        ((a/*accumulator, list, map*/, v/*value from the list*/)=>
             if (a._2.isDefinedAt(v))//already seen
                 (s"$v+${a._2(v)}" :: a._1, a._2.updated(v, a._2(v) + 1))
             else
                 (v::a._1, a._2.updated(v, 1)))
        ._1/*select the list*/.reverse/*because we created in the opposite order*/
    

    【讨论】:

    • 是的,这个版本把数字放错了地方,虽然这会不必要地复杂化,其他答案很好地实现了这部分。 :)
    猜你喜欢
    • 2021-10-21
    • 1970-01-01
    • 2018-04-10
    • 1970-01-01
    • 2020-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多