Scala - 根据出现次数修改列表中的字符串
Scala - modify strings in a list based on their number of occurences
另一个 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 循环和计数器?
如果索引很重要,有时最好使用 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)
高效简单:
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)
}
}
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*/
另一个 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 循环和计数器?
如果索引很重要,有时最好使用 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)
高效简单:
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)
}
}
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*/