Clojure
零基礎
學習筆記
map
編程應該是有趣的
這次我們要完成一個“極具實用性”的功能 --- 顯示乘法口訣表!
(哦。真的是“太”實用的功能了。)
這表示我們的人工智能智力水平已經達到小學程度了?。ㄕ`。)
除此之外,我們還請到了我另一個人格 --- 括號菌--- 作為我們的評論嘉賓,希望你會喜歡他的風格(沒錯就是我。)
首先,讓我們仔細思考一下乘法口訣表的真正意義,(這玩意兒還能有啥意義?)
它其實是兩列數字的乘積,就像這樣:
* 1 2 3 4 5 6 7 8 9
1
2
3
4
5
6
7
8
9
我們只要讓它們相乘,這個表就出來了~(嗯,那要怎么讓程序去乘呢。)
首先,我們拿 1 去和 1 相乘,再拿 1 和 2 相乘 ……這樣就產生了第一列,然后再拿 2 去乘,得到第二列。(那么我們就要用一個循環咯?)
沒錯,循環是可以的,但是,我們有一個更為強大的東西,循環什么的,太落后了。(那是?)
下面有請(可能是)函數世界里最常用的高階函數:
map (名氣大,字都跟著變大了呢)
下面請看前方記者帶回的報道
map 先生,請問為什么他們都說你很強大呢?
嗯,除了英俊的外表之外,鄙人可以使用函數來一次性的處理一大批數據,這個功能很強吧。
呃,這個,能用淺顯的話給我們解釋一下么?
當然。你告訴我一個函數,然后告訴我一個列表,我就會把那個列表里面的值,依次作為你告訴我的那個函數的參數來執行,最后,把每次執行的結果構成一個列表來返回。
好像沒聽懂啊……
沒關系,我來演示一下
=> (map * [4 2 5] [1 6 3])
(4 12 15)
你看,我現在有一個乘法函數,以及一批參數,我現在拿出這批參數的第一組,4 和 1,然后把它們傳遞給乘法函數作為參數,乘法函數返回的結果為 4,于是我就把 4 放在結果列表的第一個位置。接下來我繼續取出這批參數的第二組,2 和 6,經過乘法函數的處理,結果是12,于是我就把 12 放在結果列表的第二個位置……
哦!我明白了 map 先生!不過,如果這批參數的數量并不相等,那怎么辦呢?
唔,這個嘛,我只能盡量保證能被處理的內容被處理了,多出的部分我只能簡單拋棄了。你看。
=> (map * [4 2] [1 6 3])
(4 12)
也就是說,依賴于前面那個參數列表對么?
你總結的不錯!不過,這個乘法啊,一次接受兩個參數,所以后面才有兩組參數列表。如果有個函數一次接受一個參數,那后面就只能跟著一個參數列表咯,也就是參數列表的數量要和函數一致,而且我也是會按次序把每個參數列表里的內容傳遞到函數中去的。比如 inc
函數,它就只能接受一個參數,返回這個參數加一之后的值:
=> (map inc [-10 11])
(-9 12)
好的!感謝在百忙之中接受我們的采訪,下面交給演播室。
好的,感謝前方記者和 map 先生的介紹,我們這就來試試看,怎么使用它來產生乘法口訣的第一列吧!
=> (map * [1 1 1 1 1 1 1 1 1] [1 2 3 4 5 6 7 8 9])
(1 2 3 4 5 6 7 8 9)
(額,這個……有點……雖然是成功了吧……)
嗯,手動輸入的確有點麻煩啊,沒關系,我們還有重復某一元素的函數 repeat
和 可以生成數字序列的 range
~
我們來試試看:
=> (repeat 10 3) ;產生 10 個 3 的序列
(3 3 3 3 3 3 3 3 3 3)
=> (repeat 5 "*") ;產生 5 個 "*" 的序列
("*" "*" "*" "*" "*")
=> (range 10) ;從 0 開始產生不大于 10 的數字序列
(0 1 2 3 4 5 6 7 8 9)
=> (range 3 10) ;從 3 開始產生不大于 10 的數字序列
(3 4 5 6 7 8 9)
(這么方便啊。這下好辦了。)
好了,下面我們就改進一下剛才生成第一行乘法口訣的表達式吧~
(map * (repeat 10 1) (range 1 10))
=> (1 2 3 4 5 6 7 8 9)
那么第二行怎么生成呢?大家應該能寫出來了吧~
(map * (repeat 10 2) (range 1 10))
=> (2 4 6 8 10 12 14 16 18)
(嗯哪,只需要把重復 10 個 1,改成重復 10 個 2 就行啦~)
所以,我們把它寫成一個函數,這樣,我們就可以生成任意的一行了。
(defn multi-table-one-line
[num]
(map * (repeat 10 num) (range 1 10)))
這樣以來,第三行就可以這樣寫了:
=> (multi-table-one-line 3)
(3 6 9 12 15 18 21 24 27)
(那,怎么來一次性生成呢?1 2 3 4……這樣依次輸入太麻煩了。對了!我們再來一次 map?。?br> 哈哈,變聰明了嘛另一個我。(那可不,我可是你。)
=> (map multi-table-one-line (range 1 10))
((1 2 3 4 5 6 7 8 9)
(2 4 6 8 10 12 14 16 18)
(3 6 9 12 15 18 21 24 27)
(4 8 12 16 20 24 28 32 36)
(5 10 15 20 25 30 35 40 45)
(6 12 18 24 30 36 42 48 54)
(7 14 21 28 35 42 49 56 63)
(8 16 24 32 40 48 56 64 72)
(9 18 27 36 45 54 63 72 81))
(這個,和我們平??吹降某朔谠E不一樣啊,你看,有一半是重復的可以去掉啊,因為乘法是對稱的嘛。)
嗯,對啊,我們仔細想想,第一次的時候,我們并不需要拿 1 1 1 1 1 1 1 1 1
去乘啊,只需一個 1 就可以了,2 的時候 只需兩個 2,3 的時候只需三個 3……所以,我們在寫 repeat 的時候,應該這么寫:(repeat num num)
,所以生成每行的函數應該改成:
(defn multi-table-one-line
[num]
(map * (repeat num num) (range 1 10)))
(對啊!這樣,由于 map
以第一組參數的數量為準,后面多余的就不會再乘了。)
我們來看一下效果:
=> (map multi-table-one-line (range 1 10))
((1)
(2 4)
(3 6 9)
(4 8 12 16)
(5 10 15 20 25)
(6 12 18 24 30 36)
(7 14 21 28 35 42 49)
(8 16 24 32 40 48 56 64)
(9 18 27 36 45 54 63 72 81))
(不錯!成功了!但是……平常的乘法口訣,前面都有一個什么 2*3=6 什么的。)
我們生成的乘法口訣可是真正可以用的一組數據啊!不比加上什么 2*3=6 什么的花哨玩意兒實用多了!(喂喂喂。這些數據有毛線用啊。)
好吧,那我們就得把乘法函數改造一番了!乘法函數僅僅只是返回乘法的結果,現在,我們要在乘法的基礎上,加上前綴,比如 2*3,本來只返回 6,現在應該返回 2*3=6。這顯然是個字符串了,那我們就要使用字符串拼接函數 str
,它可以把它接受的參數拼接成一個字符串。
現在我們來寫一個“乘法口訣專用乘法函數”:
(defn special-use-multi
[x y]
(str x "乘" y "得" (* x y)))
看看效果:
=> (special-use-multi 2 3)
"2乘3得6"
現在我們再來改裝我們生成每行的函數:
(defn multi-table-one-line
[num]
(map special-use-multi (repeat num num) (range 1 10)))
看看最后效果:
=> (map multi-table-one-line (range 1 10))
(("1乘1得1")
("2乘1得2" "2乘2得4")
("3乘1得3" "3乘2得6" "3乘3得9")
("4乘1得4" "4乘2得8" "4乘3得12" "4乘4得16")
("5乘1得5" "5乘2得10" "5乘3得15" "5乘4得20" "5乘5得25")
("6乘1得6" "6乘2得12" "6乘3得18" "6乘4得24" "6乘5得30" "6乘6得36")
("7乘1得7" "7乘2得14" "7乘3得21" "7乘4得28" "7乘5得35" "7乘6得42" "7乘7得49")
("8乘1得8" "8乘2得16" "8乘3得24" "8乘4得32" "8乘5得40" "8乘6得48" "8乘7得56" "8乘8得64")
("9乘1得9" "9乘2得18" "9乘3得27" "9乘4得36" "9乘5得45" "9乘6得54" "9乘7得63" "9乘8得72" "9乘9得81"))
大功告成啦可喜可賀
好啦,我們下次再見~
(別別別別啊!你這怎么有這么多括號?。。?br>
什么啊,我們這可是一個活的可用的數據?。”饶切┧赖妮斎氲狡聊簧蠜]屁用的東西強一萬倍??!
唉,真難伺候,好吧,我們可以使用 print
和 println
函數來輸出我們的二維表,也就是兩層嵌套的列表,也就是我們的乘法口訣。
首先,我們再來介紹一個高階函數:
apply (怎么字兒又大了!又要采訪了么?。?/h4>
由于 apply 先生不在家,我們有機會再來采訪他?。悄愀氵@么隆重干啥?。?/p>
我們現在認為它可以把列表的括號去掉:
=> (print [1 2 3])
[1 2 3] nil
=> (apply print [1 2 3])
1 2 3 nil
好了,我們來完成我們輸出二維表的函數:
(defn print-each
"輸出每一行" ; 我們可以在函數名和參數之間另起一行,使用字符串作為函數的說明。
[some-coll]
(apply print some-coll)
(println))
(defn print-coll-2d
"輸出整體"
[some-coll]
(map print-each some-coll))
print-coll-2d
不僅能輸出乘法口訣了,它可以輸出任何一個二維的表。
下面我們就進行最后的輸出工作了。
=> (print-coll-2d (map multi-table-one-line (range 1 10)))
1乘1得1
2乘1得2 2乘2得4
3乘1得3 3乘2得6 3乘3得9
4乘1得4 4乘2得8 4乘3得12 4乘4得16
5乘1得5 5乘2得10 5乘3得15 5乘4得20 5乘5得25
6乘1得6 6乘2得12 6乘3得18 6乘4得24 6乘5得30 6乘6得36
7乘1得7 7乘2得14 7乘3得21 7乘4得28 7乘5得35 7乘6得42 7乘7得49
8乘1得8 8乘2得16 8乘3得24 8乘4得32 8乘5得40 8乘6得48 8乘7得56 8乘8得64
9乘1得9 9乘2得18 9乘3得27 9乘4得36 9乘5得45 9乘6得54 9乘7得63 9乘8得72 9乘9得81
(nil nil nil nil nil nil nil nil nil)
這下好了吧~
(嗯……對齊效果不行,而且最后怎么多了一行。)
對齊可以使用格式化輸出,最后的是 map
函數返回的 print-each
的值,不可以消除返回值但是……你屁事兒咋這么多呢!下次不叫你了!
最后我們完整的看一下我們的程序:
(defn special-use-multi
"特制乘法"
[x y]
(str x "乘" y "得" (* x y)))
(defn multi-table-one-line
"計算一行的值"
[num]
(map special-use-multi (repeat num num) (range 1 10)))
(defn print-each
"去掉括號輸出一行"
[some-coll]
(apply print some-coll)
(println))
(defn print-coll-2d
"輸出整體"
[some-coll]
(map print-each some-coll))
;main
(print-coll-2d (map multi-table-one-line (range 1 10)))