Scala 使用foldLeft将列表转换为地图

Scala 使用foldLeft将列表转换为地图,scala,fold,Scala,Fold,使用下面的代码,我试图产生 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)) ) 从 以下是完整

使用下面的代码,我试图产生

 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)) )

以下是完整的代码:

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)) }

}
行:

正在导致以下编译错误:

找到[错误]: (scala.collection.immutable.Map[java.util.Date,List[(String,Float)], 需要列出[Main.Details])[error]: scala.collection.immutable.Map[java.util.Date,List[(字符串,浮点)]] [错误]val map=s.foldLeft(Mapjava.util.Date,List[(字符串, 浮动){(m,s)=>(m,列表))}[错误]
^[错误]发现一个错误[错误](编译:编译增量) 编译失败[错误]总时间:2秒,已于2017年6月11日完成 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)]()))
      )
}
对于
折叠的每次迭代
您需要返回更新的Map
m

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

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


注意,结果映射的值可能是相反的顺序(因为我使用cons(
)操作符,这比append for
List
更有效。您可以使用
Map.mapValues(u.reverse)
)反转结果值。

我认为可以更直接地实现目标

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)) }
其中
m
类型为
Map[Date,List[(String,Float)]
s
类型为
Details

(m,List(s))
语法意味着您正在创建一对由map
m
和包含
s
的单例列表组成的列表

相反,您想要实现的是将这两个项目作为一对新的
m
放入
s
,您可以通过执行以下操作来实现:

{ (m, s) => m.updated(s.date, s.det :: m.get(s.date).getOrElse(List.empty)) }
让我们看看这里发生了什么:您获取累加器映射
m
,并在每次折叠时使用
s.date
作为键和一个值来更新它。该值是该键以前保留的值(
m.get(s.date)
,以确保我们没有覆盖该键),或者如果仍然没有值,则为空列表,并在折叠遍历集合时以我们现在查看的值作为前缀

这就解决了问题,但正如您所看到的,您所做的是一个众所周知的分组操作,Scala Collection API已经为您提供了实现目标的基本基础设施

您可以按如下方式重构代码并获得相同的结果:

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)))

}
如您所见,我使用了格式化程序的
parse
方法和
groupBy
combinator。但是,该函数将整个项作为结果分组的值显示,而您只需要它的一部分(这就是我进一步使用
mapValues
combinator的原因)

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

{ (m, s) => m.updated(s.date, s.det :: m.get(s.date).getOrElse(List.empty)) }
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)))

}