前言
之前,我们已经看过Quick: An Introduction to Racket with Pictures。距离我们可以使用Racket编程,还有一点距离。
我们先来看看《C++ Primer》是如何介绍C++的。看过它的目录索引,或许,我们可以总结出,命令式编程语言的如下规律。
然而,Racket是个通用、多范型,属于Lisp家族的函数式程序设计语言。它的设计目之一是为了提供一种用于创造设计与实现其它编程语言的平台。Racket被用于脚本程序设计、通用程序设计、计算机科学教育和学术研究等不同领域。
我无法理解What is difference between functional and imperative programming languages?。所以,我暂时按照,变量-数据结构、分支/循环语句、类、模块这几部分去尝试了解Racket。目的是编程,编程,编程。所以,最后我们需要使用Racket进行简单的编程。
PS1:本文参考Racket语言入门-Racket语言概要和The Racket Guide。下面的一些示例来自于这两个链接,使用的时候,不再单独说明。
PS2:在使用的过程中,理解函数式编程。
绑定-内置数据类型
绑定
Racket中似乎没有变量这一概念。我们可以使用2.2.1Definitions和2.2.8 Local Binding with define, let, and let*来达到我们的目的。
(define pi 3.14) ;defines pie to be 3
> pi
3.14
如果我们想一次绑定多个标识符,并进行运算,我们可以使用let
。let
绑定的范围仅仅在其作用域之内。define
则是全局绑定。
(let ([a 3]
[b (list-ref '(1 2 3 4) 3)]) ;list-ref返回位置pos处的lst元素
(sqr (+ a b)))
49
> a
. . a: undefined;
cannot reference an identifier before its definition
如果在绑定的过程中,需要互相引用,可以使用 let*
。
(let* ([x 1]
[y (+ x 1)])
(sqr (+ x y)))
9
内置数据类型
常见的类型包含:布尔值,数值,字符,字符串,列表,向量,哈希表等等。
布尔值
详见:3.1 Booleans
Racket 有两个不同的常量来表示布尔值: #t
表示真,# f
表示假。大写 #T
和#F
被解析为相同的值,但首选小写形式。boolean?
可以识别这两个布尔常量。
在 if
、 cond
、 and
这样的语句中,任何其他值,而不知#f
均为true。
(>= 2 (+ 1 1)) ;#t
(boolean? #t) ;#t
(boolean? #f)
数值
详见:3.2 Numbers
Racket的数值类型可以分为两种:精确和不精确。
精确的数字:任意大小的整数、一个正好是两个任意大小的整数之比的有理数、具有精确实部和虚部(其中虚部不为零)的复数
不精确的数字:数字的 IEEE 浮点表示、具有实部和虚部的复数是 IEEE 浮点表示
(sin 1/2)
0.479425538604203
语句
条件语句
详见:2.2.5 Conditionals with if, and, or, and cond和4.7 Conditionals
(if (> 2 3)
"2 bigger than 3"
"2 not bigger than 3")
"2 not bigger than 3"
Racket的 if
没有多个分支,但是可以通过嵌套来完成相同的功能:
(define score 55)
(if (>= score 90) "A"
(if (>= score 80) "B"
(if(>= score 70) "C"
(if(>= score 60) "D"
"Not Pass"))))
"Not Pass"
同样的功能可以 cond 来完成:
(define score 55)
(cond [(>= score 90) "A"]
[(>= score 80) "B"]
[(>= score 70) "C"]
[(>= score 60) "D"]
[else "Not Pass"])
"Not Pass"
循环语句
详见:2.3Lists, Iteration, and Recursion和11 Iterations and Comprehensions
对列表中每个元素平方刷出。
(for ([i '(1 2 3)])
(let ([tmp (* i i)])
(printf "~a " tmp)))
1 4 9
同时遍历多个列表。
(for ([i '(1 2 3 4)]
[name '("goodbye" "farewell" "so long")]) ; 同时遍历,一个停止,全部停止
(printf "~a: ~a\n" i name))
1: goodbye
2: farewell
3: so long
双层循环
;; 双层循环
(for* ([i '(1 2 3 4)]
[name '("goodbye" "farewell" "so long")])
(printf "~a: ~a\n" i name))
1: goodbye
1: farewell
1: so long
2: goodbye
2: farewell
2: so long
3: goodbye
3: farewell
3: so long
4: goodbye
4: farewell
4: so long
函数
详见:2.2.4 Function Calls (Procedure Applications)、2.2.7 Anonymous Functions with lambda和4.5.1 Function Shorthand
调用string-append
函数,进行字符串拼接。
(string-append "hello " "world")
"hello world"
使用lambda表达式,创建匿名函数。
; (map proc lst ...+) → list? ;对list中的每个元素,应用proc函数
(map
(lambda (num) (+ 1 num))
'(1 2 3))
'(2 3 4)
创建一个函数。
(define (salutation)
(list-ref '("Hi" "Hello") (random 2)))
(define (greet name)
(string-append (salutation) "," name))
> (greet "Tom and jerry")
"Hello,Tom and jerry"
> (greet "Tom and jerry")
"Hi,Tom and jerry"
类和对象
; 创建一个 fish% 类(%是给类绑定用的)
(define fish%
(class object% ; 其他类都是从object%派生的
(init size) ; 初始化的参数
(super-new) ; 父类的初始化
;; 域(fields)
(define current-size size)
;; 公共方法
(define/public (get-size)
current-size)
(define/public (grow amt)
(set! current-size (+ amt current-size)))
(define/public (eat other-fish)
(grow (send other-fish get-size)))))
;; 创建一个 fish% 类的示例
(define charlie
(new fish% [size 10]))
;; 使用 `send' 调用一个对象的方法
(send charlie get-size) ; => 10
(send charlie grow 6)
(send charlie get-size) ; => 16
模块
详见:6 Modules
将下面内容保存在"cack.rkt"中。
#lang racket
(provide print-cake)
; draws a cake with n candles
(define (print-cake n)
(show " ~a " n #\.)
(show " .-~a-. " n #\|)
(show " | ~a | " n #\space)
(show "---~a---" n #\-))
(define (show fmt n ch)
(printf fmt (make-string n ch))
(newline))
在另一个文件中,调用print-cake
函数。
(require "cake.rkt")
(print-cake (random 6))
杨辉三角
题目:118. 杨辉三角
参考题解:杨辉三角-力扣官方题解
C++ 实现
class Solution {
public:
vector<vector<int>> generate(int numRows) {
// 除去边缘数字,满足公式dp[i][j] = dp[i-1][j] + dp[i-1][j-1]
vector<vector<int>> dp(numRows);
for(int i=0; i<numRows; i++){
int n = i+1; // 该行有i+1个数字
dp[i].resize(n);
dp[i][0] = 1; // 边缘数字初始化为零
dp[i][n-1] = 1;
for(int j=1; j<n-1; j++){ // 非边缘数字满足上面公式
dp[i][j] = dp[i-1][j] + dp[i-1][j-1];
}
}
return dp;
}
};
Racket实现一
代码主体来源:Racket-wiki。下面代码可以在leetcode上通过。
#lang racket
; 在leetcode中实现上面的代码
(require racket/contract)
(define (next-row row) ;从杨辉三角的一行生成下一行:当前行左边添加一个0,成为新的序列A;当前行右边添加一个0,生成新的序列B;A+B为下一行的杨辉三角
(map + (cons 0 row) (append row '(0))))
(define (triangle row rows)
(if (= rows 0)
'()
(cons row (triangle (next-row row) (- rows 1))))) ; 用递归实现循环
(define/contract (generate numRows)
(-> exact-integer? (listof (listof exact-integer?))) ;定义域(接收)和值域(产生)为,确切的正整数,二维确切正整数列表
(triangle (list 1) numRows))
> (generate 5)
'((1) (1 1) (1 2 1) (1 3 3 1) (1 4 6 4 1))
如果不明白上面的递归展开过程。自行尝试展开,即可明白。
; 第一次展开
(define (triangle (1) 5)
(if (= 5 0)
'()
(cons (1) (triangle (next-row (1)) (- 5 1))))) ;第一组(1)
(define (next-row (1))
(map + (cons 0 (1)) (append (1) '(0)))) ;(1 1)
; 第二次展开
(define (triangle (1 1) 4)
(if (= 4 0)
'()
(cons (1 1) (triangle (next-row (1 1)) (- 4 1))))) ;第二组(1 1)
(define (next-row (1 1))
(map + (cons 0 (1 1)) (append (1 1) '(0)))) ;(1 2 1)
Racket实现二
我尝试使用for循环代替上面的递归过程。
#lang racket
; 在leetcode中实现上面的代码
(require racket/contract)
;(require flomat)
(define (next-row row) ;从杨辉三角的一行生成下一行:当前行左边添加一个0,成为新的序列A;当前行右边添加一个0,生成新的序列B;A+B为下一行的杨辉三角
(map + (cons 0 row) (append row '(0))))
(define (triangle rows)
(let ([ret '((1))] [tmp '(1)])
(for ([i (- rows 1)])
(set! tmp (next-row tmp))
(set! ret (append ret tmp)))
ret))
(define/contract (generate numRows)
(-> exact-integer? (listof (listof exact-integer?))) ;定义域(接收)和值域(产生)为,确切的正整数,二维确切正整数列表
(triangle numRows))
这个代码是有问题的。问题在于,racket中如何在二维数组中追加一个一维数组作为元素。
> (triangle 5)
'((1) 1 1 1 2 1 1 3 3 1 1 4 6 4 1)
看完4.10Pairs and Lists和4.12
Vectors,我没有看到这两个内置数据结构的方法,可以实现上面的功能。Racket中有矩阵库flomat,类似于numpy,不知道能不能解决这个问题?我用了一上午没有解决这个问题,暂时搁置。
问题总结:Racket中,如何将一维的列表,追加到二维列表后面?