使用 foldLeft 将列表转换为地图

Converting List to Map with foldLeft

我正在尝试使用以下代码生成

 Map(2017-06-03 09:25:30 -> List( ("c",2190.79) , ("d",24.11), ("d",24.11), ("d",24.11) ),
     2017-06-03 09:25:40 -> List( ("b",24.62) , ("b",24.62)) ,
     2017-06-03 09:25:50 -> List( ("a",194.55) , ("a",194.55)) )

来自

val l = List("a,194.55,2017-06-03 09:25:50",
             "b,24.62,2017-06-03 09:25:40",
             "c,2190.79,2017-06-03 09:25:30",
             "d,24.11,2017-06-03 09:25:30",
             "a,194.55,2017-06-03 09:25:50",
             "b,24.62,2017-06-03 09:25:40",
             "c,2190.79,2017-06-03 09:25:30",
             "d,24.11,2017-06-03 09:25:30")

完整代码如下:

object Main extends App {

    val l = List("a,194.55,2017-06-03 09:25:50",
                 "b,24.62,2017-06-03 09:25:40",
                 "c,2190.79,2017-06-03 09:25:30",
                 "d,24.11,2017-06-03 09:25:30",
                 "a,194.55,2017-06-03 09:25:50",
                 "b,24.62,2017-06-03 09:25:40",
                 "c,2190.79,2017-06-03 09:25:30",
                 "d,24.11,2017-06-03 09:25:30")

    case class Details(date : java.util.Date , det : (String , Float))

    val format = new java.text.SimpleDateFormat("yyyy-MM-dd hh:mm:ss")

    val p = l.map(m => new Details(format.parse(m.split(",")(2)), ( m.split(",")(0),m.split(",")(1).toFloat) ))

    val s = p.sortBy(r => (r.date))

val map = s.foldLeft(Map[java.util.Date, List[(String , Float)]]()) { (m, s) => (m , List(s)) }

}

行:

val map = s.foldLeft(Map[java.util.Date, List[(String , Float)]]()) { (m, s) => (m , List(s)) }

导致以下编译错误:

[error] found : (scala.collection.immutable.Map[java.util.Date,List[(String, Float)]], List[Main.Details]) [error] required: scala.collection.immutable.Map[java.util.Date,List[(String, Float)]] [error] val map = s.foldLeft(Mapjava.util.Date, List[(String , Float)]) { (m, s) => (m , List(s)) } [error]
^ [error] one error found [error] (compile:compileIncremental) Compilation failed [error] Total time: 2 s, completed 11-Jun-2017 22:51:46

我没有正确使用 map 吗?

这不是异常,而是编译错误。该错误解释了您的代码有什么问题:

foldLeft 的第二个参数(由错误消息中的 ^ 指向)必须是一个函数 (B, A) ⇒ B。您的代码有一个 (B, A) ⇒ (B, A) 而不是...

修复该行的方法如下:

val map = s.foldLeft(Map[java.util.Date, List[(String , Float)]]()) {
  (m, s) =>
    m +
      (s.date ->
        (s.det :: m.getOrElse(s.date, List[(String , Float)]()))
      )
}

对于 fold 的每次迭代,您需要 return 更新地图 m

为此,您需要检查 m 是否已包含 s.date。如果是,将新的 s.det 添加到现有列表值并将更新的列表放回地图中。

如果这是 s.date 的第一次出现,只需创建一个空列表,将 s.det 放入其中,然后将列表放回 m

请注意,生成的 Map 的值可能是相反的顺序(因为我使用的是 cons (::) 运算符,这比附加 List 更有效。您可以反转结果值使用 map.mapValues(_.reverse)).

我觉得objective可以直接实现一点

val format = new java.text.SimpleDateFormat("yyyy-MM-dd hh:mm:ss")

l.map(_.split(","))
 .groupBy(a => format.parse(a(2)))
 .mapValues(_.map(a => (a(0),a(1).toFloat))) //Map[java.util.Date,List[(String, Float)]]

您面临的问题来自您试图将新元组集成到地图中的匿名函数;你要做的是:

{ (m, s) => (m, List(s)) }

其中 mMap[Date, List[(String , Float)]] 类型,sDetails 类型。

(m, List(s)) 语法意味着您正在创建一个由映射 m 和包含 s.

的单例列表组成的对

您想要实现的是 s 中的两个项目 s 作为一对新的 m,您可以通过执行以下操作实现:

{ (m, s) => m.updated(s.date, s.det :: m.get(s.date).getOrElse(List.empty)) }

让我们看看这里会发生什么:您使用 accumulator 映射 m 并在每次折叠时使用 s.date 作为键更新它,并且然后是一个值。该值是该键先前保存的值(m.get(s.date),以确保我们不会覆盖该键)或者如果仍然没有值,则为空列表,前面加上我们正在查看的值而折叠遍历集合。

这解决了问题,但如您所见,您正在做的是广为人知的分组操作,Scala Collection API 已经为您提供了实现 objective 的基本基础设施.

您可以像下面这样重构您的代码并获得相同的结果:

object Main extends App {

  val l = List("a,194.55,2017-06-03 09:25:50",
    "b,24.62,2017-06-03 09:25:40",
    "c,2190.79,2017-06-03 09:25:30",
    "d,24.11,2017-06-03 09:25:30",
    "a,194.55,2017-06-03 09:25:50",
    "b,24.62,2017-06-03 09:25:40",
    "c,2190.79,2017-06-03 09:25:30",
    "d,24.11,2017-06-03 09:25:30")

  val format = new java.text.SimpleDateFormat("yyyy-MM-dd hh:mm:ss")

  val map = 
    l.groupBy(m => format.parse(m.split(",")(2))).
      mapValues(l => l.map(m => (m.split(",")(0),m.split(",")(1).toFloat)))

}

如您所见,我已将 groupBy 组合子与格式化程序的 parse 方法结合使用。然而,此函数显示为整个项目的结果分组值,而您只需要它的一部分(这就是为什么我进一步使用 mapValues 组合器)。

如果您对地图显示项目的顺序更感兴趣,请记住使用强制执行某种排序的地图(如 SortedMap)。