F#入门学习(十)---元组

这篇博客开始讲解元组、列表、序列和选项类型。

  1. 元组
    元组是一些未命名但经过排序的值的分组,这些值可能具有不同的类型。
  • 未命名,就是找不到这个东东,它本身就是常量。
  • 排序,就是先写什么,后写什么,决定了元组就是什么,和为命名有着相同是意味儿。
  • 不同的类型,就是元组本身就是一个常量,这个常量的类型是我们自己写的常量组合体,不同的类型组合成一种类型。
    看完这三个特点,我们可以把元组理解为我们自己定义的一种超级常量类型,最原始的常量组合,叫:元组。
  1. 语法结构
  • 元组里面只有一个常量,还不如直接写这个常量,所以不考虑一元的元组。
  • 二元元组,就是里面有两个常量的元组:let x1 = (1,"hello")
  • 三元元组,就是里面有三个常量的元组:let x2 = (1,"hello",true)
    他们的函数特征是:
    val x1 : int * string = (1, “hello”)
    val x2 : int * string * bool = (1, “hello”, true)
  • 元组嵌套元组
    let a = 1
    let b = 2
    let c = "hello"
    let x = (a,(b,c))
    
    函数特征:
    val a : int = 1
    val b : int = 2
    val c : string = “hello”
    val x : int * (int * string) = (1, (2, “hello”))
  1. 元组分解
    先来一个简单的分解:

    let x = (1,"hello")  //待分解元组
    let a1,b1 = x  //第一种分解方式
    let (a2,b2) = x  //第二种分解方式
    let  a3,_ = x  //第三种分解方式
    

    val x : int * string = (1, “hello”)
    val b1 : string = “hello”
    val a1 : int = 1
    val b2 : string = “hello”
    val a2 : int = 1
    val a3 : int = 1
    第一种分解方式比较好看,把元组里面的每一个元素分别赋a1和b1。
    第二种分解方式相比之下,多了一对括号,这个括号其实代表着原元组里面的括号。
    第三种分解方式只取a3的值,其余的值都被赋给了通配符_了,我们不使用它。通配符_只能对应着一个位置。
    我学到这里就有很多疑问,如果元组里面有三个元素我们依然这么取值,会出现什么问题?

    let x = (1,"hello",a)  //待分解元组
    
    let a1,b1 = x  //第一种分解方式
    let (a2,b2) = x  //第二种分解方式
    let  a3,_ = x  //第三种分解方式
    

    我们来看看报错信息:
    myfsharp1.fs(3,13): error FS0001: 类型不匹配。应为
    “int * string”
    而给定的是
    “int * string * int”
    元组具有不同长度的 2 和 3
    接下来我给元组里面添了一个常量,变成这样:

    let x = (1,"hello","a")  //待分解元组
    
    let a1,b1,c1 = x  //第一种分解方式
    let (a2,b2,c2) = x  //第二种分解方式
    let  a3,_ ,_= x  //第三种分解方式
    

    结果成功了:
    val x : int * string * string = (1, “hello”, “a”)
    val c1 : string = “a”
    val b1 : string = “hello”
    val a1 : int = 1
    val c2 : string = “a”
    val b2 : string = “hello”
    val a2 : int = 1
    val a3 : int = 1
    原来元组的长度必须和我们分解的时候对应才可以,这说明,我们必须在心里默认已知元组的长度才可以,其实想想也对,元组是个常量,常量我们本来就是知道的量,为什么不能提前知道呢?
    接下里还有个问题,这个括号加着有什么意思?
    当待分解元组里面没有括号的时候可以随便加括号吗?

    let x = (1,"hello","a")  //待分解元组
    
    let a1,b1,c1 = x  //第一种分解方式
    let a2,(b2,c2) = x  //第二种分解方式
    let  a3,_ ,_= x  //第三种分解方式
    

    报错:
    myfsharp1.fs(4,18): error FS0001: 类型不匹配。应为
    “int * ('a * 'b)”
    而给定的是
    “int * string * string”
    类型“'a * 'b”与类型“string”不匹配
    显然,我们第二种分解方式出现了问题,系统认为加了括号的是一个量,不能当成两个,所以依然不匹配。和上面讲的一样,我们必须默认已知元组长度,现在分解的时候长度不对,结果肯定也不对。
    同理

    let x = ((1,"hello"),"a")  //待分解元组
    
    let a1,b1,c1 = x  //第一种分解方式
    let (a2,b2,c2) = x  //第二种分解方式
    let  a3,_ ,_= x  //第三种分解方式
    

    这段代码肯定也不对,因为待分解元组长度为2,分解方法的长度却是3。
    myfsharp1.fs(3,16): error FS0001: 类型不匹配。应为
    “(int * string) * string * 'a”
    而给定的是
    “(int * string) * string”
    元组具有不同长度的 3 和 2
    我们一定要记得,所有的长度必须一一对应,按照这个标准来写一定不会出错。

    let x = ((1,"hello"),"a")  //待分解元组
    
    let (a1,b1),c1 = x  //第一种分解方式
    let ((a2,b2),c2) = x  //第二种分解方式
    let  (a3,_ ),_= x  //第三种分解方式
    

    val x : (int * string) * string = ((1, “hello”), “a”)
    val c1 : string = “a”
    val b1 : string = “hello”
    val a1 : int = 1
    val c2 : string = “a”
    val b2 : string = “hello”
    val a2 : int = 1
    val a3 : int = 1
    这样来看的话,分解方法二就是把所有的常量放在一起当成一个元组,也就是长度为1的元组而已,理解不同,但是也能取到值。

  2. 单独取值
    上面的方法必须把长度都写完整才能正确取值,有没有不要这么麻烦的?
    我们可以使用元组运算符fst和snd取元组的第一个和第二个元素。

    let x = ((1,"hello"),"a")  //待分解元组
    
    let a1 = fst x
    let b1 = snd x 
    

    val x : (int * string) * string = ((1, “hello”), “a”)
    val a1 : int * string = (1, “hello”)
    val b1 : string = “a”
    此时的a1显然是一个二元的元组。
    书上的例子和现在写的这个例子都不太好,因为它没有说清楚fst和snd的运用范围。要记住我们知道的长度法则,想用fst和snd运算符必须保证元组只有两个常量。

    let x = ((1,"hello"),"a",3)  //待分解元组
    
    let a1 = fst x
    let b1 = snd a1
    

    myfsharp1.fs(3,14): error FS0001: 类型不匹配。应为
    “(int * string) * string”
    而给定的是
    “(int * string) * string * int”
    元组具有不同长度的 2 和 3
    我们也可以通过fst和snd的函数特征发现这个问题。
    fst函数特征述
    snd函数特征

  3. 用模式匹配表达式match访问各个元组元素
    使用元组作为参数进行运算

    let add ( a,b ) = a + b
    let y = add (5,7)
    

    val add : a:int * b:int -> int
    val y : int = 12
    看第一个结果:add的函数特征,a:int * b:int是指int类型的参数和int类型的参数整体作为一个参数(这样的话我们是不能使用函数部分应用特性的,必须两个值都传入),-> int 代表返回值是int类型。
    我们改写一下这个函数作为对比:

    let add ( a,b ) c = a + b + c
    let y = add (5,7) 1
    

    结果为:
    val add : a:int * b:int -> c:int -> int
    val y : int = 13
    现在a:int * b:int 是一个元组类型的参数, -> c:int是另外一个参数,-> int是返回结果类型。

  4. 使用元组模式匹配和尾递归方式求阶乘x!

    //尾递归方法求阶乘
    let fact x = 
        let rec tailfact (x,n) = 
            match (x,n) with
            |(0,_)->n
            |(_,_)->tailfact (x-1,x * n)
        tailfact (x,1)
    

    val fact : x:int -> int
    val y : int = 120
    复习一下尾递归,用rec定义一个递归函数,接收一个二元的元组类型参数,匹配这个参数,如果x这个元素是0,就返回n值,如果不是0,就进入下一次调用。
    元组里的第一个元素参数是我们要开始计算的值,第二个参数我们用来存放运算结果,递归结束条件就是返回这个结果,也就是(0,)->n返回n,递归体是(,_)->tailfact (x-1,x * n),x-1控制方向,x * n是递归内容。
    递归函数定义好了是不先使用的,等到了需要调用的时候才使用。fact 5,把x带入5,递归函数先掠过,到了 tailfact (x,1),我们现在得到了这个元组参数,1只用来占位置,也叫初始化。开始递归, tailfact (5,1)— tailfact (4,51)— tailfact (3,451)— tailfact (2,3451)—tailfact (1,23451)—tailfact (0,123451)—123451=120,递归结束。
    我们fact的返回值是看tailfact的,tailfact的返回值是看模式匹配->后面值的,所以返回int类型的值结束。

  5. 练习:输入两个实数,构造一个复数元组为返回类型

    let complex (r:double) (i:double) = (r,i)
    let x = complex 5. 7.
    

    val complex : r:double -> i:double -> double * double
    val x : double * double = (5.0, 7.0)
    没有想象的那么复杂,直接加上括号就是元组类型。

  6. 元组模式匹配
    设:有二元元组(学分,课程名),学分为2每周上2节课,学分为3每周上3节课,学分为4每周上4节课,学分为5每周上5节课,其余学分不存在。
    求:输入二元元组,求对应课时。

    let detectTuple  course = 
        match course with
        | (2,val1)->printfn"《%s》学分为2,每周课时为2."val1
        | (3,val1)->printfn"《%s》学分为3,每周课时为3."val1
        | (4,val1)->printfn"《%s》学分为4,每周课时为4."val1
        | (5,val1)->printfn"《%s》学分为5,每周课时为5."val1
        | _->printfn" 不存在."
    
    detectTuple (2,"数据库")
    detectTuple (4,"数据结构")
    detectTuple (10,"信号系统")
    

    《数据库》学分为2,每周课时为2.
    《数据结构》学分为4,每周课时为4.
    不存在.
    val detectTuple : int * string -> unit
    val it : unit = ()
    course是一个二元元组常量,就用一个单词表示就行,val1是个正儿八经的的占位符,只要前面的数字对,这个里面传来什么都无所谓,我们打印的时候原样输出。

  7. OR模式的模式匹配
    其实之前的学习我们见过这个模式。OR就是的意思,写的条件有一个成立这个模式就成立。在F#语言中用符号| 表示。OR模式要求运算符两侧模式类型必须兼容。
    设:有二元元组(学分,课程名称)。
    求:若学分在2-6之间的整数,则输出课程设置合理,否则输出不合理。

    let detectSoreOR  (course:int * string) = 
        match course with
        | (2,_) | (3,_) | (4,_) | (5,_) | (6,_) ->printfn"课程设置合理。"
        | _->printfn"课程设置不合理。"
    
    detectSoreOR (2,"数据库")
    detectSoreOR (4,"数据结构")
    detectSoreOR (10,"信号系统")
    

    课程设置合理。
    课程设置合理。
    课程设置不合理。
    val detectSoreOR : int * string -> unit
    val it : unit = ()

  8. AND模式的模式匹配
    和OR模式类似,AND就是的意思,写的条件必须全部成立这个模式才成立。在F#语言中用符号& 表示。AND模式也要求运算符两侧模式类型兼容。
    输入一个int * int 的二元元组,判断元组里面是否有0元素

    let detectZeroAND  point = 
        match point with
        | (0,0) -> printfn "两个元素全为0."
        | (val1,val2) & (0,_) -> printfn "元组(%d,%d)第一个元素为0."val1 val2
        | (val1,val2) & (_,0) -> printfn "元组(%d,%d)第二个元素为0."val1 val2
        | _->printfn"两个元素均不为0。"
    
    detectZeroAND (0,0)
    detectZeroAND (1,0)
    detectZeroAND (0,1)
    detectZeroAND (1,1)
    

    两个元素全为0.
    元组(1,0)第二个元素为0.
    元组(0,1)第一个元素为0.
    两个元素均不为0。
    val detectZeroAND : int * int -> unit
    val it : unit = ()
    这个程序没有意思,就是为了运用&故意用的,不使用时照样能实现一样的功能,注意看不同点。但是我们要学习这个占位符的意义,它和&在一起也是为了达到取值的效果。

    let detectZeroAND  point = 
        match point with
        | (0,0) -> printfn "两个元素全为0."
        | (0,_) -> printfn "第一个元素为0."
        | (_,0) -> printfn "第二个元素为0."
        | _->printfn"两个元素均不为0。"
    
    detectZeroAND (0,0)
    detectZeroAND (1,0)
    detectZeroAND (0,1)
    detectZeroAND (1,1)
    

    两个元素全为0.
    第二个元素为0.
    第一个元素为0.
    两个元素均不为0。
    val detectZeroAND : int * int -> unit
    val it : unit = ()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值