[Haskell] 为什么列表操作++很昂贵?
博主是haskell新手。学习haskll的时候遇到了一些问题,在寻求答案的过程中产生了一些思考,可能理解存在偏差,希望各位不吝赐教。
提出问题
《Learn you a haskell for great good》里第六章关于函数foldl(左fold)的部分提到,++操作符比:要昂贵很多,所以我们一般用foldr来构造一个list
第一次看这段话的时候我并没有深究(实际上我认为这句话根本就有毛病,因为foldr也得用++,原文作者根本没把核心问题指出来),因为haskell用都没用过几次,自然理解不了内在机制。最近刚好遇上了一个用(++)的机会,我想把一个Char数组转成String数组,实现如下(不重要,大致就是用foldl将剩余数组的内容塞进累加器数组里,用++来连接,不想看的直接跳过代码吧):
chars2String :: [Char] -> [String]
chars2Str chars = foldl (\strs c -> strs ++ [[c]]) [] chars
-- 当时没想到的更简便实现
chars2String :: [Char] -> [String]
chars2Str chars = map (\c -> [c]) chars
突然想到这不正是使用++的最坏情况吗?所以我花了不少时间去SOF等地方看别人的回答,为什么++会显得更加昂贵。
探索过程
++的源码实现是递归式的,并且底层使用了:,大部分的答案都有提到这一点,去看了源码确实是这样没错。(大致的做法就是将 ++ 左边的部分拆成 x1:x2:x3 ... 这样,右边不需要递归遍历)
可是仔细想一想这种递归的开销是O(n)。懂一些数据结构知识就知道,往数组(非链式)底部插入一个(或x个)值,相当于把已经存在的n个值全部抬高一个(或x个)数组位,开销必然是O(n)。也就是说不论你是什么语言,想要用非链式数组数据结构实现这样的功能开销都应该是一样的。
证明开销昂贵
先不论别的语言如何,现在博主来证明++开销的巨大,假如有这样的情况:
-- 从SOF的这个回答里受到很大启发: https://stackoverflow.com/questions/12296694/the-performance-of-with-lazy-evaluation
let a = ( ( ( [1,2] ) ++ [3] ) ++ [4] )
计算步骤(伪代码):
[1,2] ++ [3] => 1 : 2 : [3] => [1,2,3],这里通过递归遍历将[1,2]拆开,大致消耗O(2)[1,2,3] ++ [4] => 1 : 2 : 3 : [4] => [1,2,3,4],又通过递归遍历将[1,2,3]拆开,大致消耗O(3)- ...
很明显在步骤2,重复了递归遍历拆开[1,2]这个操作,也就是说继续这样循环下去,时间复杂度大致为O(1+2+3+4+...+n),似乎可以化简为O(k * n^2) (k代表++左边数组的长度,n代表重复++的次数)
再观察刚才的代码,可以看到这和函数foldl所做的事情差不多,这就是为什么++开销在foldl会很昂贵的原因
也可以很廉价
但是,++也可以很廉价,想象这样的情况:
let a = ( [1] ++ ( [2] ++ [3,4] ) )
相当于1 : 2 : [3,4],时间复杂度是O(n),n是++的次数,这和:操作是一样的。
上面的代码刚好是foldr所做的事情。这就是《Learn you a haskell for great good》作者写下那段话的原因。
对比其他语言
现在回头反观其他语言。假设对一个非链式数组进行如下操作:
noLinkedArray = []
noLinkedArray.prepend(1).prepend(2).prepend(3)
排除该语言对数组操作优化的可能,难道时间复杂度不也是O(0+1+2+3+...+n)吗?
我由此思考得出结论,这是一个普遍存在的问题,和haskell的底层机制没多大关系。
可是一码归一码,现在数组的首选应该都是LinkedArray吧,链式数组无论往头部还是尾部插入元素,单次时间复杂度都是O(1),多次操作时间复杂度则是O(n),不会出现O(k * n^2)这种天文运算
[Haskell] 为什么列表操作++很昂贵?的更多相关文章
- react实例之todo,做一个实时响应的列表操作
react实例之todo, 做一个实时响应的列表操作 在所有的mvc框架中,最常见的例子不是hello world,而是todo,由于reactjs的简单性,在不引用flux和redux的情况下,我们 ...
- 征服 Redis + Jedis + Spring (三)—— 列表操作【转】
一开始以为Spring下操作哈希表,列表,真就是那么土.恍惚间发现“stringRedisTemplate.opsForList()”的强大,抓紧时间恶补下. 相关链接: 征服 Redis 征服 Re ...
- Python中的列表操作
Python的列表操作可谓是功能强大且方便(相对于Java)简单.常规的操作就不说了(这不是一个入门教程),介绍几个很有特点的例子 添加 # 追加到结尾(append) li = [1, 2, 3, ...
- 【Python】Python中的列表操作
Python的列表操作可谓是功能强大且方便(相对于Java)简单.常规的操作就不说了(这不是一个入门教程),介绍几个很有特点的例子 添加 # 追加到结尾(append) li = [1, 2, 3, ...
- MongoDB Java API操作很全的整理
MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写,一般生产上建议以共享分片的形式来部署. 但是MongoDB官方也提供了其它语言的客户端操作API.如下图所示: 提供了C.C++ ...
- python连接redis、redis字符串操作、hash操作、列表操作、其他通用操作、管道、django中使用redis
今日内容概要 python连接redis redis字符串操作 redis之hash操作 redis之列表操作 redis其他 通用操作,管道 django中使用redis 内容详细 1.python ...
- TCL语言笔记:TCL中的列表操作
一.介绍 列表则是具有特殊解释的字符串.Tcl 中的列表操作和其它 Tcl 命令一样具有相同的结构.列表可应用在诸如 foreach 这样的以列表为变元的循环命令中,也应于构建 eval 命令的延迟命 ...
- Python 基础篇:字符串、列表操作
字符串操作 判断是否为数字 string = "200" string.isdigit() >>false 待完善.. 列表操作 列表是我们最以后最常用的数据类型之一, ...
- windows升级到1607后操作很卡顿的解决办法
CPU I5,固态128G,win7主系统,WIN10和WIN7都安装在固态硬盘上. 未升级之前,操作很流畅,以至于把家里的老古董电脑也换固态,系统换WIN10了.自从升级了1607后这个问题就出现了 ...
随机推荐
- org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
异常信息 org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable represen ...
- 前端MVC Vue2学习总结(三)——模板语法、过滤器、计算属性、观察者、Class 与 Style 绑定
Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据.所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解 ...
- asp.net mvc 下拉列表
第一步:新建一个格式化下拉列表的公共类文件 using System; using System.Collections; using System.Collections.Generic; usin ...
- iOS框架搭建(MVC,自定义TabBar)--微博搭建为例
项目搭建 1.新建一个微博的项目,去掉屏幕旋转 2.设置屏幕方向-->只有竖向 3.使用代码构建UI,不使用storyboard 4.配置图标AppIcon和LaunchImage 将微博资料的 ...
- Noip2016换教室(期望+DP)
Description 题目链接:Luogu Solution 这题结合了DP和概率与期望,其实只要稍微知道什么是期望就可以了, 状态的构造很关键,\(F[i][j][0/1]\)表示已经到第\(i\ ...
- Core Erlang:Erlang的Core中间表示
随着erlang的不断发展,它的语法越来越复杂,不便于诸如分析器,调试器此类程序在源码层次直接进行解析,而CORE Erlang旨在为Erlang提供一个人类可读可改的中间表示(Intermediat ...
- 深谈auto变量
1.c++中有一个关键字auto,c语言也有这么一个关键字,但是两者的意义大不相同. 2.c++中用auto定义的变量自动匹配赋值号右边的值的类型,具有自动匹配类型的作用,而c语言中auto只是声明一 ...
- SVO环境搭建
我是装了双系统,实验OS:Ubuntu14.04 Installation: Plain CMake (No ROS) 首先,建立一个工作目录比如:workspace,然后把下面的需要的都在该目录下进 ...
- MobaXterm
MobaXterm又名MobaXVT,是一款增强型终端.X服务器和Unix命令集(GNU/ Cygwin)封装在一个单一的便携式exe文件.MobaXterm可以开启多个终端视窗,以最新的X服务器为基 ...
- 深入理解 Python 异步编程(上)
http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...