2013-04-11 37 views
3

我有这样的事情斯卡拉集合分组,同时维持秩序

case class Job(workId: Int, users: List[String]) 
val jobs = IndexedSeq(Job(1, List("a", "b")), Job(2, List("b", "c")), Job(3, List("a", "c")), Job(4, List("d", "b"))) 

我想将其转换为类似:

Map(c -> Vector(2, 3), a -> Vector(1, 3), d -> Vector(4), b -> Vector(1, 2, 4)) 

我基本上要保持Job.workId的顺序原始序列。因此,由于JobId 1的作业在workId 3的作业之前,因此地图中的条目在JobId 3之前具有JobId 1。

我无法找到这样做的简单方法。现在,我有:

((for (job <- jobs; 
    user <- job.users) 
    yield { (user, job.work) }) groupBy { tuple => tuple._1 }) map { tuple => (tuple._1 -> (tuple._2 map { _._2 })) } 

这首先创建:

Map(c -> Vector((c,2), (c,3)), a -> Vector((a,1), (a,3)), d -> Vector((d,4)), b -> Vector((b,1), (b,2), (b,4))) 

,然后将其转换为:

Map(c -> Vector(2, 3), a -> Vector(1, 3), d -> Vector(4), b -> Vector(1, 2, 4)) 

这似乎相当冗长。我想知道是否有更简单的方法来做到这一点,同时保持秩序。另外我不喜欢它需要多次迭代初始序列。

我还有一个冗长的解决方案:

val mapping = scala.collection.mutable.Map[String, IndexedSeq[Int]]() 

for (job <- jobs; 
     user <- job.users) 
    yield{ 
    if (mapping.contains(user)) { 
     val entry = mapping(user) 
     mapping.put(user, entry :+ job.work) 
    } else { 
     mapping += user -> mutable.IndexedSeq(job.work) 
    } 
    } 

映射现在是:

Map(c -> ArrayBuffer(2, 3), a -> ArrayBuffer(1, 3), d -> ArrayBuffer(4), b -> ArrayBuffer(1, 2, 4)) 

这股最初的推导,但并不需要是来自使用GROUPBY然后地图额外的迭代。

有没有一个更标准的收集方法这样做的地道和简洁的方式?

回答

1

与几乎所有的List处理问题一样,这可以通过折叠来解决!

(for { 
    job <- jobs.view; 
    user <- job.users 
} yield (job, user)).foldLeft (Map[String, Vector[Int]]()) { case (acc, (a,b)) => 
    acc + (b -> (acc.getOrElse(b, Vector()) :+ a.workId)) 
} 

不幸的是,Scala的类型inferencer不能够确定初始“地图”的类型,所以你必须明确指定它。

在初始集合上使用'view'方法会使这个懒惰,并且只会执行初始列表的一次传递。

+0

谢谢。实际上,我用一些不同的方法来写一点性能测试。 foldLeft方法比我之前的命令式方法表现得更好。我修改了命令式方法来使用视图,然后对结果运行foreach。最后,我也有一个方法,只是使用嵌套的foreach。嵌套的foreach表现最好,但折叠方法非常接近。势在必行的方法表现得更糟,视图修改的执行情况稍好一些。 – Rajiv 2013-04-11 21:38:12

+0

我想我最终会使用Guava的ArrayListMultimap。它具有最佳性能,并且与Scala的multimap不同,它也保持插入的顺序。 – Rajiv 2013-04-11 23:53:36