使用 foldLeft 将列表转换为地图
Posted
技术标签:
【中文标题】使用 foldLeft 将列表转换为地图【英文标题】:Converting List to Map with foldLeft 【发布时间】:2017-06-11 21:56:15 【问题描述】:使用下面的代码我正在尝试生成
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))
导致以下编译错误:
[错误] 发现: (scala.collection.immutable.Map[java.util.Date,List[(String, Float)]], 列表 [Main.Details]) [错误] 必需: scala.collection.immutable.Map[java.util.Date,List[(String, Float)]] [错误] val map = s.foldLeft(Mapjava.util.Date, List[(String , Float)]) (m, s) => (m , List(s)) [错误] ^ [错误] 发现一个错误 [错误] (compile:compileIncremental) 编译失败 [错误] 总时间:2 s,完成 11-Jun-2017 22:51:46
我没有正确使用map
吗?
【问题讨论】:
【参考方案1】:这不是异常,而是编译错误。该错误说明了您的代码有什么问题:
foldLeft
的第二个参数(由错误消息中的^
指向)必须是函数(B, A) ⇒ B
。您的代码有一个 (B, A) ⇒ (B, A)
代替...
【讨论】:
【参考方案2】:以下是修复该行的方法:
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
的每次迭代,您需要返回更新后的映射m
。
为此,您需要检查m
是否已经包含s.date
。如果是,请将新的s.det
添加到现有列表值并将更新的列表放回地图中。
如果这是第一次出现s.date
,只需创建一个空列表,将s.det
放入其中,然后将列表放回m
。
请注意,生成的 Map 的值可能是相反的顺序(因为我正在使用 cons (::
) 运算符,这比 List
的 append 更有效。您可以使用 map.mapValues(_.reverse)
反转结果值)。
【讨论】:
【参考方案3】:我认为目标可以更直接一点。
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)]]
【讨论】:
【参考方案4】:您面临的问题来自您试图将新元组集成到地图中的匿名函数;你要做的是:
(m, s) => (m, List(s))
其中m
的类型为Map[Date, List[(String , Float)]]
,s
的类型为Details
。
(m, List(s))
语法意味着您正在创建一个由映射 m
和一个包含 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)
,以确保我们不会覆盖该键)或者如果仍然没有值,则为一个空列表,前面加上我们现在正在查看的值,而fold 遍历集合。
这解决了问题,但正如您所见,您所做的是广为人知的分组操作,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)))
如您所见,我已将 groupBy
组合器与格式化程序的 parse
方法结合使用。但是,此函数显示为对整个项目进行分组的结果值,而您只需要其中的一部分(这就是我进一步使用 mapValues
组合器的原因)。
如果您对地图显示项目的顺序更感兴趣,请记住使用强制某种排序的地图(例如 SortedMap
)。
【讨论】:
以上是关于使用 foldLeft 将列表转换为地图的主要内容,如果未能解决你的问题,请参考以下文章