scala集合:映射列表并携带一些状态?

scala集合:映射列表并携带一些状态?,scala,collections,Scala,Collections,我似乎总是遇到这个问题。我想修改列表中的一些元素,但我需要在这样做时保持一些状态,所以map不起作用 以下是一个例子: scala> val l1 = List("a","b","c","d","e","f","b","c","e","b","a") l1: List[String] = List(a, b, c, d, e, f, b, c, e, b, a) 我想更改任何副本的名称。所以我想以这个结束: List(a1, b1, c1, d, e1, f, b2, c2, e2, b

我似乎总是遇到这个问题。我想修改列表中的一些元素,但我需要在这样做时保持一些状态,所以map不起作用

以下是一个例子:

scala> val l1 = List("a","b","c","d","e","f","b","c","e","b","a")
l1: List[String] = List(a, b, c, d, e, f, b, c, e, b, a)
我想更改任何副本的名称。所以我想以这个结束:

List(a1, b1, c1, d, e1, f, b2, c2, e2, b3, a2)
获得复制品很容易:

scala> val d = l1.diff(l1.distinct).distinct
d: List[String] = List(b, c, e, a)
现在我被卡住了。我将d转换为HashMap w/a count,并编写一个函数来迭代l1并更新它&在递归之前更新hash。这很好,但在我看来有点难看

但我一直认为应该有一种方法来完成收集课程

以下是我不喜欢的其余解决方案:

  val m = d.map( _ -> 1).toMap

  def makeIt(ms: Map[String, Int], ol: Iterator[String], res: List[String]=List[String]()) :List[String] = {
    if( !ol.hasNext) return res
    val no = ol.next()
    val (p,nm) = ms.get(no) match {
      case Some(v) => (s"$no$v", ms.updated(no,v+1))
      case None => (no,ms)
    }

    makeIt(nm,ol,res :+ p)
  }

  makeIt(m,l1.iterator)
这就是我想要的

res2: List[String] = List(a1, b1, c1, d, e1, f, b2, c2, e2, b3, a2)
我觉得我想要“mapWithState”,在那里我可以传递一些东西。像褶皱一样。也许它存在,而我只是还没有找到它

谢谢

-------更新---------

@阿伦·哈达德的评论为我指明了这个方向。这破坏了秩序,这对我来说很好。但“国家”由zipWithIndex承担。我正在寻找一种更一般的情况,在这种情况下,状态需要在每个元素上进行一些计算。但对于这个简单的例子,我喜欢:

l1.groupBy(x=>x).values.flatMap( v =>{
    if( v.length <= 1 ) v else {
      v.zipWithIndex.map{ case (s,i) => s"$s${i+1}"}
    }
  })
res7: Iterable[String] = List(e1, e2, f, a1, a2, b1, b2, b3, c1, c2, d)
l1.groupBy(x=>x).values.flatMap(v=>{
如果(v.length s“$s${i+1}”}
}
})
res7:Iterable[String]=列表(e1、e2、f、a1、a2、b1、b2、b3、c1、c2、d)

棘手的部分是
“d”
“f”
元素没有得到修改

这就是我想到的。它更简洁,更符合代码,但确实涉及到多次遍历

val l1: List[String] = List("a","b","c","d","e","f","b","c","e","b","a")

l1.reverse.tails.foldLeft(List[String]()){
  case (res, Nil) => res
  case (res, hd::tl) =>
    val count = tl.count(_ == hd)
    if (count > 0) s"$hd${count+1}" +: res
    else if (res.contains(hd+2)) (hd+1) +: res
    else hd +: res
}
//res0: List[String] = List(a1, b1, c1, d, e1, f, b2, c2, e2, b3, a2)

通过使用
tails
,每个元素,
hd
,都能够看到未来,
tl
,以及过去,
res
,这是一个简单但缓慢的版本

l1.zipWithIndex.map{ case (elem, i) =>
  if (l1.count(_ == elem) == 1) {
    elem
  } else {
    val n = {l1.take(i+1).count(_ == elem)}
    s"$elem$n"
  }
}
下一个版本比较长,不太好看,也不太实用,但是在处理一个很长的列表的情况下应该更快

def makeUniq(in: Seq[String]): Seq[String] = {
  // Count occurrence of each element
  val m = mutable.Map.empty[String, Int]

  for (elem <- in) {
    m.update(elem, m.getOrElseUpdate(elem, 0) + 1)
  }

  // Remove elements with a single occurrence
  val dupes = m.filter(_._2 > 1)

  // Apply numbering to duplicate elements
  in.reverse.map(e => {
    val idx = dupes.get(e) match {
      case Some(i) =>
        dupes.update(e, i - 1)
        i.toString
      case _ =>
        ""
    }

    s"$e$idx"
    }).reverse
  }

听起来你想要
groupBy
@aluan我想我可以使用
groupBy
只访问需要更改的元素(并跳过
匹配
),但我不知道它如何帮助我在更改名称时保持状态?我应该说我建议将
groupBy
作为跟踪状态的替代方法。因此,您可以编写类似
values.groupBy(x=>x).map((g=>g.zipWithIndex.map)的内容(…
我认为这对这种情况很有效,而且比我现有的更好。但我实际上对我的“状态”更一般的情况感兴趣比递增的int更复杂。如果我读对了,这只在相同的不超过3个的情况下才有效?但我喜欢这个模式。不是这样。它可以计算任何重复次数。确实如此。抱歉。很好。与其从
0
中进行
切片
,为什么不:
val n=1+{l1.take(I)。count(==elem)}
@jwvh谢谢你的建议,我已经修改了答案。
def makeUniq(in: Seq[String]): Seq[String] = {
  val m = mutable.Map.empty[String, Int]

  in.map{ e =>
    val i = m.getOrElseUpdate(e, 0) + 1
    m.update(e, i)

    s"$e$i"
  }
}