繁体   English   中英

在Scala中,我如何才能完成SQL SUM和GROUP BY的等效操作?

[英]In Scala, how can I do the equivalent of an SQL SUM and GROUP BY?

例如,假设我有

val list: List[(String, Double)]

有价值的

"04-03-1985", 1.5
"05-03-1985", 2.4
"05-03-1985", 1.3

我怎么能产生一个新的清单

"04-03-1985", 1.5
"05-03-1985", 3.7

这是一个单行。 它不是特别易读,除非真正内化这些高阶函数的类型。

val s = Seq(("04-03-1985" -> 1.5),
            ("05-03-1985" -> 2.4),
            ("05-03-1985" -> 1.3))

s.groupBy(_._1).mapValues(_.map(_._2).sum)
// returns: Map(04-03-1985 -> 1.5, 05-03-1985 -> 3.7)

另一种方法是使用折叠逐个添加键值对,

s.foldLeft(Map[String, Double]()) { case (m, (k, v)) =>
  m + (k -> (v + m.getOrElse(k, 0d)))
}

在我看来,相当于理解是最容易获得的,

var m = Map[String, Double]()
for ((k, v) <- s) {
  m += k -> (v + m.getOrElse(k, 0d))
}

也许用Scalaz的Map的monoid类型类可以做得更好。

请注意,您可以使用toSeqtoMap方法在Map[K, V]Seq[(K, V)]进行toMap


更新 在思考了一些之后,我认为自然抽象将是一种类型的“多图”转换,

def seqToMultimap[A, B](s: Seq[(A, B)]): Map[A, Seq[B]]

通过在个人库中进行适当的隐式扩展,可以编写:

s.toMultimap.mapValues(_.sum)

在我看来,这是最清楚的!

使用Scalaz还有另一种可能性。

关键是要注意,如果MMonoid ,那么Map[T, M]也是Monoid 这意味着如果我有2个地图, m1m2我可以添加它们,这样,对于每个相似的键,元素将被添加到一起。

例如, Map[String, List[String]]是一个Monoid,因为List[String]是一个Monoid 因此,在范围内给出适当的Monoid实例,我应该能够做到:

  val m1 = Map("a" -> List(1), "b" -> List(3))
  val m2 = Map("a" -> List(2))

  // |+| "adds" two elements of a Monoid together in Scalaz
  m1 |+| m2 === Map("a" -> List(1, 2), "b" -> List(3))

对于您的问题,我们可以看到Map[String, Int]是一个Monoid因为Int类型有一个Monoid实例。 让我们导入它:

  implicit val mapMonoid = MapMonoid[String, Int]

然后,我需要一个函数reduceMonoid ,它接受任何Traversable并用Monoid “添加”它的元素。 我只是在这里编写reduceMonoid定义,为了完整实现,请参考我关于迭代器模式本质的帖子:

  // T is a "Traversable"
  def reduce[A, M : Monoid](reducer: A => M): T[A] => M

这两个定义在当前的Scalaz库中不存在,但它们并不难添加(基于现有的MonoidTraverse类型类)。 一旦我们拥有它们,您问题的解决方案就非常简单:

  val s = Seq(("04-03-1985" -> 1.5),
              ("05-03-1985" -> 2.4),
              ("05-03-1985" -> 1.3))

   // we just put each pair in its own map and we let the Monoid instance
   // "add" the maps together
   s.reduceMonoid(Map(_)) === Map("04-03-1985" -> 1.5,
                                  "05-03-1985" -> 3.7)

如果你觉得上面的代码有点模糊(但是真的很简洁,对吧?),我建议你检查一下EIP帖子github项目并使用它。 一个示例显示了您的问题的解决方案:

   "I can build a map String->Int" >> {
     val map1 = List("a" -> 1, "a" -> 2, "b" -> 3, "c" -> 4, "b" -> 5)
     implicit val mapMonoid = MapMonoid[String, Int]

     map1.reduceMonoid(Map(_)) must_== Map("a" -> 3, "b" -> 8, "c" -> 4)
   }

我一直使用Kipton的答案中的那个模式s.groupBy(_._1).mapValues(_.map(_._2).sum) 它直接翻译我的思考过程,但遗憾的是并不总是很容易阅读。 我发现尽可能使用case类会让事情变得更好:

case class Data(date: String, amount: Double)
val t = s.map(t => (Data.apply _).tupled(t))
// List(Data(04-03-1985,1.5), Data(05-03-1985,2.4), Data(05-03-1985,1.3))

然后变成:

t.groupBy(_.date).mapValues{ group => group.map(_.amount).sum }
// Map(04-03-1985-> 1.5, 05-03-1985 -> 3.7)

我觉得它是那么比 版本更具可读性。

val s = List ( "04-03-1985" -> 1.5, "05-03-1985" -> 2.4, "05-03-1985" -> 1.3)
for { (key, xs) <- s.groupBy(_._1)
       x = xs.map(_._2).sum
    } yield (key, x)

启动Scala 2.13 ,您可以使用groupMapReduce方法(顾名思义)相当于groupBy后跟mapValuesreduce步骤:

// val l = List(("04-03-1985", 1.5), ("05-03-1985", 2.4), ("05-03-1985", 1.3))
l.groupMapReduce(_._1)(_._2)(_ + _).toList
// List(("04-03-1985", 1.5), ("05-03-1985", 3.7))

这个:

  • group小号元组可以通过第一部分( _._1 )( 的MapReduce的基团部分)

  • map每个分组的元组map到它们的第二部分( _._2 )(映射部分Map Reduce)

  • reduce每个组(内价值观_ + _ )通过对它们求和(减少groupMap的一部分缩小 )。

这是一个可以翻译的单程版本

l.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _)).toList

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM