Scala:单趟完成列表的map与unzip操作
单趟遍历完成Map+Unzip的Scala函数式实现
嘿,这个需求很实用啊!想要在单次遍历里同时完成元素映射(提取前两个字段)和拆分操作,确实能避免多趟遍历带来的额外开销,尤其是当数据量较大的时候。
你原来的方案是先map再unzip,确实会遍历列表两次——第一次把每个三元组转换成二元组,第二次再把二元组拆分成两个列表。下面给你几种纯函数式的单趟实现方式:
方式一:用foldLeft(最通用的标准库方案)
这是最直接的纯函数式写法,利用foldLeft的单趟遍历特性,同时累积两个结果列表:
val l = List((8, 3, 9), (10, 3, 0), (-37, 4, 1)) val (list1, list2) = l.foldLeft((List.empty[Int], List.empty[Int])) { // 每次遍历元素时,提取前两个值,分别加到累积列表的头部 case ((acc1, acc2), (a, b, _)) => (a :: acc1, b :: acc2) } // 因为::是往列表头部添加元素,最后需要反转来保持原顺序 match { case (a, b) => (a.reverse, b.reverse) }
说明:
foldLeft只会遍历列表一次,每一步都把当前元素的前两个字段分别追加到两个累积列表中- 由于Scala的
List是链表结构,往头部添加元素(::)是O(1)操作,最后反转列表是O(n),但整体还是单趟遍历的总复杂度O(n) - 全程使用不可变数据结构,完全符合函数式编程的风格
方式二:用Iterator优化(适合超大列表)
如果你的列表特别大,不想一次性持有整个反转后的列表,可以用Iterator来延迟计算,同时保证单趟遍历:
val l = List((8, 3, 9), (10, 3, 0), (-37, 4, 1)) val iter = l.iterator.map { case (a, b, _) => (a, b) } val (list1, list2) = iter.unzip
说明:
iterator.map是惰性求值的,不会立即遍历列表;unzip在执行时会一次性遍历这个迭代器,所以整个过程只遍历原列表一次- 这种写法更简洁,而且对于超大列表来说,内存开销更小,因为不会生成中间的二元组列表
对比原来的方案
你原来的代码:
val (list1, list2) = l.map({ el => (el._1, el._2) }).unzip
会先遍历一次列表生成二元组列表,再遍历一次二元组列表完成拆分,总共两趟遍历。而上面的两种方式都只需要单趟遍历,效率更高。
内容的提问来源于stack exchange,提问作者PawkyPenguin
相关产品推荐
相关产品推荐

