使用两个条件过滤列表并创建地图标度

Filter list with two criteria and create a map scala

我有一个案例 class 可以保留交易(假设为资金转账)。为了简单起见,我将使用下面的一个

case class Trans(from: String, to: String, amount: Int)

我有交易清单

val tl = List(
  Trans("a", "b", 30), 
  Trans("a", "c", 40),
  Trans("b", "c", 10),
  Trans("b", "a", 25),
  Trans("c", "a", 15)
)

我想根据 fromto 对列表进行分组。然后需要将组的总和到一个Map。地图将元素保留在下面的结构中

("a" -> (70, 40))
// "a" is the key
// "70" is the sum of values where "a" is 'from' (30 + 40)
// "40" is the sum of values where "a" is 'to' (25 + 15)

例如,关于列表应将输出填充为

Map("a" -> (70, 40), "b" -> (35, 30), "c" -> (15, 50))

我可以用两个语句进行过滤,然后像下面这样组合它们

// out map
val mOut = tl.map(t => (t.from, t.amount)).groupBy(_._1).map(s => (s._1, s._2.sum))

// in map
val mIn = tl.map(t => (t.to, t.amount)).groupBy(_._1).map(s => (s._1, s._2.sum))

// finally I can join these tow maps to have the final output

有什么方法可以用 single/one 声明吗?

这似乎适用于单次遍历列表。

tl.foldLeft(Map.empty[String,(Int,Int)].withDefaultValue((0,0))){
  case (m,Trans(from,to,amount)) =>
    val (fromA, fromB) = m(from)
    val (toA, toB) = m(to)
    m + (from -> (fromA+amount, fromB)) + (to -> (toA, toB+amount))
}
//res0: Map[String,(Int, Int)] = Map(a -> (70,40), b -> (35,30), c -> (15,50))

希望你能为中间变量想出更好的名字。

如果您是 catsscalaz 用户,您可以使用 foldMap:

import cats._, implicits._ // or...
//import scalaz._, Scalaz._


case class Trans(from: String, to: String, amount: Int)

val tl = List(
  Trans("a", "b", 30), 
  Trans("a", "c", 40),
  Trans("b", "c", 10),
  Trans("b", "a", 25),
  Trans("c", "a", 15)
)

tl foldMap {
    case Trans(from, to, am) => Map(from -> (am, 0), to -> (0, am))
}

你不需要 scalaz。 . 我想,你要找的是 flatMap:

   tl
    .flatMap { p => 
       p.from -> (p.amount, 0),
       p.to -> (0, p.amount)
    }.groupBy(_._1)
     .mapValues { tuples => 
        tuples.foldLeft((0,0)) { case ((a,b), (_, (c,d))) => (a+c, b+d) } 
     }