【发布时间】:2012-03-24 13:33:26
【问题描述】:
Lists、Maps等中的groupBy方法,在函数之后生成一个Map。
有没有办法使用 groupBy 生成保留插入顺序的 Map(例如 LinkedHashMap)?
我正在使用 for 循环手动插入,但我想知道其中一个有用的已定义函数是否可以帮助我。
提前致谢。
【问题讨论】:
标签: scala collections map hashmap
Lists、Maps等中的groupBy方法,在函数之后生成一个Map。
有没有办法使用 groupBy 生成保留插入顺序的 Map(例如 LinkedHashMap)?
我正在使用 for 循环手动插入,但我想知道其中一个有用的已定义函数是否可以帮助我。
提前致谢。
【问题讨论】:
标签: scala collections map hashmap
TraversableLike 上定义的groupBy 会生成immutable.Map,因此您不能让此方法生成其他内容。
已经保留了每个条目中元素的顺序,但没有保留键的顺序。键是提供的函数的结果,因此它们实际上没有顺序。
如果您想根据特定键的第一次出现来下订单,这里有一个关于如何做的草图。假设我们想按值 / 2 对整数进行分组:
val m = List(4, 0, 5, 1, 2, 6, 3).zipWithIndex groupBy (_._1 / 2)
val lhm = LinkedHashMap(m.toSeq sortBy (_._2.head._2): _*)
lhm mapValues (_ map (_._1))
// Map(2 -> List(4, 5), 0 -> List(0, 1), 1 -> List(2, 3), 3 -> List(6))
// Note order of keys is same as first occurrence in original list
【讨论】:
Seqs 有意义,而这个方法对所有Traversables 都是通用的,但是由于实现使用了一个for 表达式来遍历元素,所以对于Seqs 总是如此.
以下将为您提供一个 groupByOrderedUnique 方法,该方法的行为与您所寻求的一样。它还添加了一个 groupByOrdered 来保留其他人在 cmets 中要求的重复项。
import collection.immutable.ListSet
import collection.mutable.{LinkedHashMap => MMap, Builder}
implicit class GroupByOrderedImplicitImpl[A](val t: Traversable[A]) extends AnyVal {
def groupByOrderedUnique[K](f: A => K): Map[K, ListSet[A]] =
groupByGen(ListSet.newBuilder[A])(f)
def groupByOrdered[K](f: A => K): Map[K, List[A]] =
groupByGen(List.newBuilder[A])(f)
def groupByGen[K, C[_]](makeBuilder: => Builder[A, C[A]])(f: A => K): Map[K, C[A]] = {
val map = MMap[K, Builder[A, C[A]]]()
for (i <- t) {
val key = f(i)
val builder = map.get(key) match {
case Some(existing) => existing
case None =>
val newBuilder = makeBuilder
map(key) = newBuilder
newBuilder
}
builder += i
}
map.mapValues(_.result).toMap
}
}
当我使用该代码时:
import GroupByOrderedImplicit._
val range = 0.until(40)
val in = range ++ range.reverse
println("With dupes:")
in.groupByOrdered(_ % 10).toList.sortBy(_._1).foreach(println)
println("\nUnique:")
in.groupByOrderedUnique(_ % 10).toList.sortBy(_._1).foreach(println)
我得到以下输出:
With dupes:
(0,List(0, 10, 20, 30, 30, 20, 10, 0))
(1,List(1, 11, 21, 31, 31, 21, 11, 1))
(2,List(2, 12, 22, 32, 32, 22, 12, 2))
(3,List(3, 13, 23, 33, 33, 23, 13, 3))
(4,List(4, 14, 24, 34, 34, 24, 14, 4))
(5,List(5, 15, 25, 35, 35, 25, 15, 5))
(6,List(6, 16, 26, 36, 36, 26, 16, 6))
(7,List(7, 17, 27, 37, 37, 27, 17, 7))
(8,List(8, 18, 28, 38, 38, 28, 18, 8))
(9,List(9, 19, 29, 39, 39, 29, 19, 9))
Unique:
(0,ListSet(0, 10, 20, 30))
(1,ListSet(1, 11, 21, 31))
(2,ListSet(2, 12, 22, 32))
(3,ListSet(3, 13, 23, 33))
(4,ListSet(4, 14, 24, 34))
(5,ListSet(5, 15, 25, 35))
(6,ListSet(6, 16, 26, 36))
(7,ListSet(7, 17, 27, 37))
(8,ListSet(8, 18, 28, 38))
(9,ListSet(9, 19, 29, 39))
【讨论】:
AnyVal?
AnyVal,以便在不分配的情况下调用隐式包装类的方法。这个页面很好地描述了“类型丰富”的基本原理。 ivanyu.me/blog/2014/12/14/value-classes-in-scala
ListBuffer 代替LinkedHashSet - 使用map(key) = map(key) :+ i 进行应用。根据groupBy 的标准行为,这允许结果中出现重复值
这是一个没有地图的:
def orderedGroupBy[T, P](seq: Traversable[T])(f: T => P): Seq[(P, Traversable[T])] = {
@tailrec
def accumulator(seq: Traversable[T], f: T => P, res: List[(P, Traversable[T])]): Seq[(P, Traversable[T])] = seq.headOption match {
case None => res.reverse
case Some(h) => {
val key = f(h)
val subseq = seq.takeWhile(f(_) == key)
accumulator(seq.drop(subseq.size), f, (key -> subseq) :: res)
}
}
accumulator(seq, f, Nil)
}
如果您只需要按顺序访问结果(无随机访问)并且希望避免创建和使用 Map 对象的开销,这可能会很有用。注意:我没有将性能与其他选项进行比较,实际上可能更糟。
编辑:为了清楚起见;这假设您的输入已经按组键排序。我的用例是SELECT ... ORDER BY。
【讨论】:
这会在 ScalaMeter 上产生更好的结果,尽管该解决方案与实际的 scala groupBy 非常相似
::Benchmark Range.GroupBy::
cores: 8
hostname: xxxxx-MacBook-Pro.local
name: Java HotSpot(TM) 64-Bit Server VM
osArch: x86_64
osName: Mac OS X
vendor: Oracle Corporation
version: 25.131-b11
Parameters(size -> 300000): 6.500884
Parameters(size -> 600000): 13.019679
Parameters(size -> 900000): 22.756615
Parameters(size -> 1200000): 25.481007
Parameters(size -> 1500000): 33.129888
与产生的 zipWithIndex 方法相比
:Benchmark Range.GroupBy::
cores: 8
hostname: xxxxx-MacBook-Pro.local
name: Java HotSpot(TM) 64-Bit Server VM
osArch: x86_64
osName: Mac OS X
vendor: Oracle Corporation
version: 25.131-b11
Parameters(size -> 300000): 9.57414
Parameters(size -> 600000): 18.569085
Parameters(size -> 900000): 28.233822
Parameters(size -> 1200000): 36.975254
Parameters(size -> 1500000): 47.447057
代码:
implicit class GroupBy[A](val t: TraversableOnce[A]) {
def sortedGroupBy[K](f: A => K)(implicit ordering: Ordering[K]): immutable.SortedMap[K, ArrayBuffer[A]] = {
val m = mutable.SortedMap.empty[K, ArrayBuffer[A]]
for (elem <- t) {
val key = f(elem)
val bldr = m.getOrElseUpdate(key, mutable.ArrayBuffer[A]())
bldr += elem
}
val b = immutable.SortedMap.newBuilder[K, ArrayBuffer[A]]
for ((k, v) <- m) {
b += ((k, v.result))
}
b.result
}
}
示例:val sizes = Gen.range("size")(300000, 1500000, 300000) 和
groupByOrdered(_ % 10)
【讨论】: