Scala集合:数组groupBy和返回每个组的数组索引

时间:2014-09-04 05:46:27

标签: scala group-by scala-collections

我有一个数组,类似的东西:

val a = Array("a", "c", "c", "z", "c", "b", "a")

我希望得到一个包含该数组的所有不同值的键的映射,以及每个这样的组的相关索引的集合,即对于给定的数组,答案是:

Map(
  "a" -> Array(0, 6),
  "b" -> Array(5),
  "c" -> Array(1, 2, 4),
  "z" -> Array(3)
)

令人惊讶的是,事实证明它比我预想的要复杂一些。到目前为止,我所做的最好的是:

a.zipWithIndex.groupBy {
  case(cnt, idx) => cnt
}.map {
  case(cnt, arr) => (cnt, arr.map {
    case(k, v) => v
  }
}

这既不简洁也不易理解。有更好的想法吗?

4 个答案:

答案 0 :(得分:5)

您的代码可以重写为oneliner,但它看起来很难看。

as.zipWithIndex.groupBy(_._1).mapValues(_.map(_._2))

另一种方法是使用mutable.MultiMap

import collection.mutable.{ HashMap, MultiMap, Set }

val as = Array("a", "c", "c", "z", "c", "b", "a")
val mm = new HashMap[String, Set[Int]] with MultiMap[String, Int]

然后只需添加每个绑定

as.zipWithIndex foreach (mm.addBinding _).tupled    
//mm = Map(z -> Set(3), b -> Set(5), a -> Set(0, 6), c -> Set(1, 2, 4))

如果你想要不可变版本,最后你可以转换它mm.toMap

答案 1 :(得分:2)

这是foldRight的版本。我认为它相当清楚。

val a = Array("a", "c", "c", "z", "c", "b", "a") 
a
 .zipWithIndex
 .foldRight(Map[String, List[Int]]())
            {case ((e,i), m)=> m updated (e, i::m.getOrElse(e, Nil))}
//> res0: scala.collection.immutable.Map[String,List[Int]] = Map(a -> List(0, 6)
//| , b -> List(5), c -> List(1, 2, 4), z -> List(3))

答案 2 :(得分:2)

使用foldLeft的另一个版本和具有默认值的不可变Map

val a = Array("a", "c", "c", "z", "c", "b", "a")
a.zipWithIndex.foldLeft(Map[String, List[Int]]().withDefaultValue(Nil))( (m, p) => m + ((p._1, p._2 +: m(p._1))))

// res6: scala.collection.immutable.Map[String,List[Int]] = Map(a -> List(6, 0), c -> List(4, 2, 1), z -> List(3), b -> List(5))

答案 3 :(得分:1)

Scala 2.13开始,我们可以使用新的groupMap(正如其名称所暗示的那样),它等效于groupBymap的分组项目:

// val a = Array("a", "c", "c", "z", "c", "b", "a")
a.zipWithIndex.groupMap(_._1)(_._2)
// Map("z" -> Array(3), "b" -> Array(5), "a" -> Array(0, 6), "c" -> Array(1, 2, 4))

此:

  • 将每个项目及其索引压缩,得到(item, index)元组

  • group的元素基于它们的第一个元组部分(_._1)(地图的组部分)

  • map将值分组到第二个元组部分(_._2,即它们的索引)(组 Map 的映射部分)