第一章 顺序编程
文章目录
1.数据类型
Erlang数据类型:原子、整数、浮点数、字符串、列表(list)、元组(tuple)、记录(record)、映射(map)
如:在输入shell命令
%% 整数
1> 1.
1
%% 浮点数
2> 1.1.
1.1
%% 字符串
3> "ABC".
"ABC"
%% 原子
5> abc.
abc
%% 布尔
6> X = true.
true
%% 列表
7> [1,2,3,4,5].
[1,2,3,4,5]
%% 元组
6> {a,"123",[1,2,3]}.
{a,"123",[1,2,3]}
原子以小写字母开头,后接一串字母、 数字、 下划线( _)或at( @)符号
原子还可以放在单引号 '
内。可以用这种引号形式创建以大写字母开头(否则会被解释成变量)或包含字母数字以外字符的原子
2.简单的运算
Erlang中的加减乘除:
%% 加法
1> 1+1.
2
%% 减法
2> 2-1.
1
%% 乘法
3> 2*2.
4
%% 除法
4> 4/2.
2.0
值得注意的是,Erlang中的除法 /
得到的结果是一个浮点数,如果需要得到一个整数,就需要使用div
%% 整除
5> 4 div 2.
2
在java中经常会使用 %
作为取余,但是在Erlang 是不行的,只能使用 rem来做整除
%% 取余数
6> 5 rem 2.
3
3.元组
基础
元组: 如果想把一些数量固定的项目归组成单一的实体,就会使用元组( tuple)。元组会在声明它们时自动创建,不再使用时则被销毁
% 建立一个元组表示X,Y轴
P = {10,45}.
% 元组的嵌套
Persion = {persion,{name,joe},{height,1.82},{footsize,42}}.
1>{persion,{name,joe},{height,1.82},{footsize,42}}
% 如果在构建新元组时用到变量,那么新的元组会共享该变量所引用数据结构的值
2> F = {firstName,joe}.
{firstName,joe}
3> L = {lastName,tom}.
{lastName,tom}
4> P = {persion,F,L}.
{persion,{firstName,joe},{lastName,tom}}
% 元组的提取方式
̀5> Point = {point,10,45}.
{point,10,45}
6> {point,K,Z} = Point.
{point,10,45}
18> K.
10
19> Z.
45
占位符:使用下划线 _ 作为占位符,用于表示不感兴趣的那些变量。符号被称为匿名变量。与正规变量不同,同一模式里的多个_不必绑定相同的值
% 使用 _ 作为占位符提取 joe
{person,{name,joe,armstrong},{footsize,42}}
1> {_,{_,Who,_},_} = Person.
{person,{name,joe,armstrong},{footsize,42}}
2> Who.
joe
修改元组的数据
%% 修改元组的数据,
update_tuple() ->
List1 = {1,2,3},
{X,Y,Z} = List1,
List2 = {X,Y,30}.
%% 也可以使用API的形式
%% setelement(Index,Tuple,Value)
update_tuple() ->
Tuple = {a,b,c},
Tuple2 = setelement(1,Tuple,aa).
4.列表
定义及使用
列表( list)被用来存放任意数量的事物。创建列表的方法是用中括号把列表元素括起来,并用逗号分隔它们
% 创建一个list存储1,2,3,4,5
1> D = [1,2,3,4,5].
[1,2,3,4,5]
同时list也可以存储元组
% list中存储元组
1> Dw = [{name,tom},{height,1.3}].
[{name,tom},{height,1.3}]
也可以混合存储数字或元组
% list存储元组,数字
1> A = [{apple,14},{pear,15},15,16].
[{apple,14},{pear,15},15,16]
可以看到Erlang中的list并不是强类型的存储的,就好比Java中的list,只能存储某一类型的数据。
比如:
//创建一个list
List<Integer> javaList = new ArrayList<>();
该javaList只能存储整形的数据,而Erlang中没有这样的限制。
列表中两个概念:
列表头(head):列表的第一个元素被称为列表头
列表尾(tail) : 假设把列表头去掉,剩下的就被称为列表尾
解释:
如果有一个列表[1,2,3,4,5],那么列表头就是整数1,列表尾则是列表 [2,3,4,5]。
注意列表头可以是任何事物,但列表尾通常仍然是个列表
自定义列表
如果T是一个列表,那么[H | T]也是一个列表, 它的头是H,尾是T。竖线( | ) 把列表的头与尾分隔开。 [ ]是一个空列表 。
用法:
% 定义一个列表T
1> T = [2,3,4,5].
[2,3,4,5]
% 使用自定义列表 [1 | T]
2> T2 = [0,1 | T].
[0,1,2,3,4,5]
可以看到T2的值为[0,1,2,3,4,5]
[A,B,C|T] = [a,b,c,d,e,f] 成功: A = a, B = b, C = c, T = [d,e,f]
例子:
求[1,2,3,4,5,6,7,8,9,10]所有元素的和
%% 求和
sum([A|T]) -> A + sum(T);
sum([]) -> 0.
%% 编译
1> demo_day02:sum([1,2,3,4,5]).
15
提取列表元素
提取列表元素:可以用模式匹配操作来提取某个列表里的元素。
如果有一个非空列表L,那么表达式[X|Y] = L( X和Y都是未绑定变量)会提取列表头作为X,列表尾作为Y。
举例:有一个列表T =[2,3,4,5]
.
% 提取元素,X作为列表头,Y作为列表尾
1> [X|Y] = T.
[2,3,4,5]
2> X.
2
3> Y.
[3,4,5]
可以看到,这里的X就是一个元素,Y是一个列表
**代码:**提取列表中的元素; 比如[1,2,3,4,5]中提取3,如果没有就返回 -1
%% 提取列表中的元素; 比如[1,2,3,4,5]中提取3,如果没有就返回 -1
get_list(Key,[A|T]) ->
if
A =:= Key -> Key;
true -> get_list(Key,T)
end;
get_list(_,[]) -> -1.
元组和列表结合的例子:
% 定义一个T1,存储水果的报价单
1> T1 = [{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,3}].
[{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,3}]
% 提取列表头和列表尾
2> [X|Y] = T1.
[{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,3}]
3> X.
{oranges,4}
4> Y.
[{newspaper,1},{apples,10},{pears,6},{milk,3}]
这里的X、Y可以理解为变量名
列表合并
将两个列表 [1,2,3] 、[4,5,6]合并一起变成 [1,2,3,4,5,6].
%% 列表A与列表B拼接;
merge_list1([A | T], L) ->
[A | merge_list(T, L)];
merge_list([], L) ->
L.
%% 也可以使用API操作
merge_list2(L1,L2) ->
lists:append(L1,L2).
%% 还可以使用++拼接两个列表
merger_list3(L1, L2) ->
L1 ++ L2.
列表推导
列表推导( list comprehension)是无需使用fun、 map或filter就能创建列表的表达式。
有一个列表L =[1,2,3,4,5],要实现列表中的每一个元素平方
%% 原来的方式
1> lists:map(fun(M) -> M*M end,L).
[1,4,9,16,25]
%% 列表推导
2> [X*X || X <-L].
[1,4,9,16,25]
在列表推导的式子中,X来源于 列表L中
实现快速排序:
qsort([]) ->[];
qsort([H | T]) ->
qsort([X || X <- T,X < H])
++ [H] ++
qsort([X || X <- T,X >= H]).
5.模式匹配
Erlang中的 =
并不像我们之前的语言是赋值的意思,这里是模式匹配的意思;
可以看到对应的参数都进行了参数上的绑定。
习题: 假定 X = 1
,对于以下模式匹配,请说明是正确还是错误,并说明为什么;
X = 1.0. %%错误,1.0为浮点数
X = X * 1. %% 正确
{X, abc} = {abc, abc}. %% 错误,X已经模式匹配为1了,此时不能再匹配为abc
[X | _] = [1]. %% 正确,列表头为1
[a | _] = "abc". %% 错误,a为原子,“abc”字符编码可以转换为[97,98,99],但a并没有转为字符编码
%% [97 | _ ] = “abc".则正确
[A, 97 | _] = "abc". %% 错误;“abc”字符编码可以转换为[97,98,99],此时模式匹配不上
[X | _] = "1bc". %% 错误;1的字符编码为 49,X已绑定值1
[A, B | _] = [a, b, c, d]. %% 正确,A匹配为a,B匹配为b
{A, B} = {a, b, c}. %% 错误,数量不匹配
6.函数
- 编写两个函数,实现要求如下:
-
- 函数1:传入整数 X,当X为1时返回true,其余情况返回false;
find(1) -> true;
find(_) -> false.
-
- 函数2:传入整数 X ,经过公式
X / 1
后值为1时返回true, 其余情况返回false;
- 函数2:传入整数 X ,经过公式
find(X) when X / 1 =:= 1 -> true;
find(_) -> false.
注意:
同名函数不能参数数量不一致:
错误写法:
修改:把一个参数的函数名使用.结束
lists类库
lists:map
lists:map(F,L)。这个函数返回的是一个列表,它通过给列表L里的各个元素应用fun F生成;
这里的F指的是一个具体操作的函数,L是列表
如给一个列表的每一个元素都相乘:
1> lists:map(fun(X) -> X*X end,[1,2,3,4,5]).
[1,4,9,16,25]
这里的X是指从列表[1,2,3,4,5]中获取的元素
lists:filter:
lists:filter(F, L),它返回一个新的列表,内含L中所有符合条件的元素
如:给定一个列表,返回能被2整除的数
1> lists:filter(fun(X) -> X rem 2 =:= 0 end,[1,2,3,4,5]).
[2,4]
类似的功能实现:
lists:member:
lists:member(X, L):如果X是L中的成员,就返回true,否则返回false。
判断1是否在列表[1,2,3,4,5]中
1> lists:member(1,[1,2,3,4]).
true
lists:append:
lists:append(L1,L2):合并两个列表
%% 合并两个列表
1> lists:append([1,2,3,4],[5,6,7,8]).
[1,2,3,4,5,6,7,8]
lists:seq:
lists:seq(Start,End).返回一个从Start~End的列表
%% 返回从1 到5的列表
4> lists:seq(1,5).
[1,2,3,4,5]
列表增删改查
列表除了使用自定义列表、递归等形式操作,还能使用其内部函数lists提供的API操作
Erlang_API中文手册:http://erldoc.com/
前置条件:定义名为(role)的record,包含id、name、sex、lev、age、score字段;
定义role实例,存储到list中并实现以下功能:
增删改查role实例;
%% 定义一个实例
1> R1 = #role{id=1,name=r1,lev=1,age=18,score=60}.
#role{id = 1,name = r1,sex = undefined,lev = 1,age = 18,score = 60}
%% 存储在list中
2> L1 = [RoleList].
[#role{id = 1,name = r1,sex = undefined,lev = 1,age = 18,score = 60}]
%% 增加
3> L2 = [R1 | L1].
%% 删除
4> L3 = lists:delete(R1,L2).
[#role{id = 1,name = r1,sex = undefined,lev = 1,age = 18,score = 60}]
%% 查找 ->这里是指查找id为1的数据
5> lists:keyfind(1,2,L3).
#role{id = 1,name = r1,sex = undefined,lev = 1,age = 18,score = 60}
%% 更新,将id为1的更新为记录更新为R2
6> R2 = R1#role{id=2,name=r2}.
#role{id = 2,name = r2,sex = undefined,lev = 1,age = 18,score = 60}
7> L4 = lists:keyreplace(1,2,L3,R2).
[#role{id = 2,name = r2,sex = undefined,lev = 1,age = 18,score = 60}]
keyfind(Key, N, TupleList) :在一个元组列表里查找一个元素
Key是指需要查找的值,N是指位置
如这里:指查找id为1的数据
%% 查找
5> lists:keyfind(1,2,L3).
#role{id = 1,name = r1,sex = undefined,lev = 1,age = 18,score = 60}
%% 查找name = r1的值 ,此处3是指位置:role,id,name,sex,lev,age,socre,name排第3位
5> lists:keyfind(r1,3,L3).
- 根据指定键值排序打印list;
%% 根据id从大到小排序
8> lists:sort(fun(A,B) ->
case (A#role.id > B#role.id) of
true -> true;
false ->false
end
end,L2).
[#role{id = 2,name = r2,sex = undefined,lev = 1,age = 19,score = 61},
#role{id = 1,name = r1,sex = undefined,lev = 1,age = 20,score = 60}]
%% 或者直接使用api
lists:keysort(2,L2)
函数返回
函数的嵌套使用
1> F1 = fun(L) -> ( fun(X) -> lists:member(X,L) end) end.
#Fun<erl_eval.6.118419387>
2> F2 = F1([1,2,3,4,5]).
#Fun<erl_eval.6.118419387>
3> F2(1).
内置函数
list_to_tuple/1 能将一个列表转换成元组
%% 将list转变为tuple
1> list_to_tuple([a,b,c,d]).
{a,b,c,d}
f() 能够清除 erlang Shell 原有绑定
1> f().
ok
time()显示时分秒
6> time().
{14,57,31}
实现for循环
%% 实现一个for循环,生成一个1,10的列表
for(Max,Max,F) -> [F(Max)];
for(I,Max,F) -> [I| for(I+1,Max,F)].
%% 编译测试
1> demo_day02:for(1,10,fun(X) -> X end).
[1,2,3,4,5,6,7,8,9,10]
7.关卡
关卡( guard)是一种结构,可以用它来增加模式匹配的威力。
使用when语句比较X,Y的大小
%% 比较X、Y的大小
max(X,Y) when X > Y -> X;
max(X,Y) -> Y.
关卡里分隔各个条件的逗号(,)的意思是“并且” 的意思
%% 当X>Y且X>10的时候
max(X,Y) when X>Y,X>10 -> X.
关卡里分号( ;)的意思是“或者” 的意思
max(X,Y) when X>Y;X>10 -> X.
%% 使用高阶函数的形式将list分为原子和整数
split(List) ->
F = fun(I, {IntegerList, AtomList}) when is_integer(I) ->
{[I | IntegerList], AtomList};
(Atom, {IntegerList, AtomList}) when is_atom(Atom) ->
{IntegerList, [Atom | AtomList]};
(_, {IntegerList, AtomList}) ->
{IntegerList, AtomList}
end,
lists:foldl(F, {[], []}, List).
%% 测试
1> demo:split([1,2,e,5,4,3,a,b,d]).
{[3,4,5,2,1],[d,b,a,e]}
8. If and case
%% 使用if来判断大小
max_if(X,Y) ->
if
X > Y -> X;
true -> Y
end.
%% 使用case of的形式
max_case_of(X,Y) ->
case X>Y of
true -> X;
false -> Y
end.
9.记录和映射
记录Record
记录是元组的另一种形式,本质上是元组 ,记录的存储与性能特性和元组一样。
应该在下列情形里使用记录:
- 用一些预先确定且数量固定的原子来表示数据时;
- 记录里的元素数量和元素名称是不改变的
列表的定义和操作:records.hrl
%% 定义一个记录
-record(todo,{status=1,who=joe,text}).
%% 编译文件
1> rr("records.hrl").
[todo]
%% 创建新的记录
2> #todo{}.
#todo{status = 1,who = joe,text = undefined}
%% 模式匹配给X1,并更新who、text字段的值
3> X1 = #todo{status=1,who=x1,text=hello}.
#todo{status = 1,who = x1,text = hello}
%% 在第4行复制了X1记录并修改字段status的值为2
4> X2 = X1#todo{status=2}.
#todo{status = 2,who = x1,text = hello}
提取记录字段:
要在一次操作中提取记录的多个字段,可以使用模式匹配
%% 提取X2中的who和text的值
5> #todo{who=W,text=T} = X2.
#todo{status = 2,who = x1,text = hello}
6> W.
x1
7> T.
hello
8>
如果只是想要记录里的单个字段,就可以使用“点语法”来提取该字段。
%% 提取X2的单个字段
9> X2#todo.text.
hello
映射组Map
映射组适合以下的情形:
-
当键不能预先知道时用来表示键值数据结构;
-
当存在大量不同的键时用来表示数据;
-
用来表示键值解析树,例如XML或配置文件;
-
用JSON来和其他编程语言通信
映射组的定义:
%% 定义一个映射组
1> F1 = #{a =>1,b =>2}.
#{a => 1,b => 2}
%% 也可以创建一个非原子键的映射组
2> F2 = #{{wife,fred} =>"sue",{age,fred} => 45}.
#{{age,fred} => 45,{wife,fred} => "sue"}
映射组的更新:使用=>或 =:
%% 使用 =>新增一个数据
3> F3 = F1#{c => 3}.
#{a => 1,b => 2,c => 3}
%% 使用 =:修改数据
4> F4 = F3#{a:=11}.
#{a => 11,b => 2,c => 3}
表达式 =>
- 将现有键K的值更新为新值V
- 另一种是给映射组添加一个全新的键值对。这个操作总是成功的
表达式K :=
- 是将现有键K的值更新为新值V。 如果被更新的映射组不包含键K,这个操作就会失败
常用api:
maps:new()
返回一个新的空映射组
is_map(M)
判断是不是一个映射组
maps:to_list(M)
把映射组M里的所有键和值转换成一个键值列表。键在生成的列表里严格按升序排列
%% 将一个map转为list
1> F1 = #{a => 1,b => 2,c => 3}.
#{a => 1,b => 2,c => 3}
2> maps:to_list(F1).
[{a,1},{b,2},{c,3}]
maps:map_size(Map)
返回映射组里的条目数量
maps:is_key(Key,Map)
如果映射组Map包含Key就返回true,否则就返回false
%% 判断某个key在不在映射组里
1> F1 = #{a => 1,b => 2,c => 3}.
#{a => 1,b => 2,c => 3}
2> maps:is_key(a,F1).
true
maps:get(Key,Map)
从Map中取出Key的值,如果没有就抛出异常错误
1> F1 = #{a => 1,b => 2,c => 3}.
#{a => 1,b => 2,c => 3}
2> maps:get(a,F1).
1
maps:find(key,Map)
从Map中取出Key的值,如果没有就返回error
1> F1 = #{a => 1,b => 2,c => 3}.
#{a => 1,b => 2,c => 3}
2> maps:find(a,F1).
{ok,1}
maps:keys(Map)
返回Map中所有的Key
maps:remove(c,F1).
返回一个新映射组M1,除了键为Key的项(如果有的话)被移除外,其他与M一致
映射组的增删改查
前置条件:定义名为(role)的record,包含id、name、sex、lev、age、score字段;
定义多个role实例,存储到map中,id作为键,role实例作为值:
2> R1 = #role{id=1,name=r1,sex=1,lev=1,age=18,score=61}.
#role{id = 1,name = r1,sex = 1,lev = 1,age = 18,score = 61}
3> R2 = R1#role{id=2,name=r2}.
#role{id = 2,name = r2,sex = 1,lev = 1,age = 18,score = 61}
4> R3 = R1#role{id=3,name=r3}.
#role{id = 3,name = r3,sex = 1,lev = 1,age = 18,score = 61}
- 增删改查role实例;
%5 增加实例
5> M1 = maps:put(R1#role.id,R1,Map).
#{1 =>#role{id = 1,name = r1,sex = 1,lev = 1,age = 18,score = 61}}
6> M2 = maps:put(R2#role.id,R2,M1).
#{2 =>#role{id = 2,name = r2,sex = 1,lev = 1,age = 18,score = 61}}
7> M3 = maps:put(R3#role.id,R3,M2).
#{3 =>#role{id = 3,name = r3,sex = 1,lev = 1,age = 18,score = 61}}
%% 删除一个R3实例
8> M4 = maps:remove(R3#role.id,M3).
#{1 =>#role{id = 1,name = r1,sex = 1,lev = 1,age = 18,score = 61},
2 =>#role{id = 2,name = r2,sex = 1,lev = 1,age = 18,score = 61}}
%% 修改一个实例
9> M5 = maps:update(R2#role.id,R1#role{name=update},M4).
#{1 =>#role{id = 1,name = r1,sex = 1,lev = 1,age = 18,score = 61},
2 => #role{id = 1,name = update,sex = 1,lev = 1,age = 18,score = 61}}
%% 查找R1实例
10> M6 = maps:get(R1#role.id,M5).
#role{id = 1,name = r1,sex = 1,lev = 1,age = 18,score = 61}
10.递归和尾递归
普通递归求和一个列表[1,2,3,4,5]
%% 求和
sum([A|T]) -> A + sum(T);
sum([]) -> 0.
%% 编译
1> demo_day02:sum([1,2,3,4,5]).
15
尾递归优化:
tail_sum([A | T]) ->
tail_sum(A, T).
tail_sum(N, [A | T]) ->
tail_sum(N + A, T);
tail_sum(N, []) ->
N.
尾递归求和一个数字N
%% 尾递归求和1~N
tail_sum2(N) ->
tail_sum2(N, 1).
tail_sum2(1, Result) ->
Result;
tail_sum2(N, Result) ->
tail_sum2(N - 1, Result + N).