はじめに
nibblesで問題を解くの会 第三回です。
一回目、二回目もぜひご覧ください。
今回はmapを扱います。
filterもやろうと思いましたが、思ったより分量が多くなってしまったので、こちらはまた次回で。
この辺りから可読性が終わってきます。
最初はきっと、コードを読んでも何もわからないと思いますが、頑張りましょう。
公式のQuick Refを見ながらだと理解が進みやすいと思います。
ちなみに、nibblesでコメント付けられるの最近まで知らず...今回から活用していきます。
「 # 」以降がコメントになるみたいです。pythonと一緒。
今回はmapの概要を説明した後に問題を解いてみたいと思います。
最終的に、ドキュメントに載っている下記FizzBuzzのコードを解読を目指します!
$?,:^-~%$3"Fizz"^-~%$5"Buzz"
今回もフォーマットはkotatsugameさんのこちらの記事から、丸パクリさせていただきました。(いつもありがとうございます。)
入力形式
解法
コード
コメント
mapでできること
連想配列ではないです。
pythonのmapと基本同じ、内包表記も近いです。
そのため、一部解説の中でpythonの疑似コードを使用しています。(pythonわからない方ごめんなさい)
nibblesにはforがないので、多くの問題でmapを使用することになります。
mapの演算をQuick Refで確認します。
演算子 | 引数1 | 引数2 |
---|---|---|
. | list | fn |
(「 . 」が演算子なのちょっと不思議な感じしませんか?)
map 、第二引数にfn(関数)を取ります。
文字で説明するのも難しいので、実際にコードを見ていきます。
例1)長さ5、要素が全て10の配列を作成してください
pythonの内包表記と上の表のlist, fnを対応させてみました。
map(lambda _: 10, range(5)) # map(fn, list)
[10 for _ in range(5)] # [fn for _ in list]
nibblesではこちら
. # map
,5 # list ( ,x で 1 〜 x のリストを作成)
10 # fn
# -> [10, 10, 10, 10, 10]
まだ簡単です。
例2)10〜15のリストを作成してください
先ほどと同様に、まずはpythonを見てみます。
rangeでそのまま実現できますが、今回はこちらで。
map(lambda x: x + 10, range(6)) # map(fn, list)
[x + 10 for x in range(6)] # [fn for _ in list]
nibblesでこの x をどうやって扱うかがポイントです。
nibblesでは、下記の通り入力を受け取れました。
Arg | 名前 | 型 |
---|---|---|
$ | fstInt | Integer |
@ | fstLine | String |
_ | ints | [Integer] |
;$ | sndInt | Integer |
;@ | allLines | [String] |
;_ | allInput | String |
;;$ | intMatrix | [[Integer]] |
;;@ | sndLine | String |
これに対してmapをしてみます。
. # map
`,6 # 0 ~ 6 のリストを生成 ( , を `, にすると 0 ~ x - 1のリストが作れます)
実はこのmapで先ほどの表がアップデートされます。
Arg | New | map前 |
---|---|---|
$ | x(listの中身) | fstInt |
@ | fstInt | fstLine |
_ | fstLine | ints |
;$ | ints | sndInt |
;@ | sndInt | allLines |
;_ | allLines | allInput |
;;$ | allInput | intMatrix |
;;@ | intMatrix | sndLine |
;;_ | sndLine | - |
一個ずつ押し出されるイメージです。
そんなわけで、x + 10 は + $ 10 をすれば良いです。
. # map
`,6 # 0 ~ 10 のリストを生成
+$10 # 足し算
# -> [10, 11, 12, 13, 14, 15]
同じ記号でも意味がどんどん変わってくるのでコードを読むのが難しくなってくるわけです。
mapの中でmapをするとさらに1個分押し出され、押し出され、、逆にmapのスコープを外れるとなんと1個分戻ります。
変数迷子にならないために
変数迷子にならないために、 ct を使ってデバッグすることが重要になってきます。
ct をするとエラー出力で先ほどの変数の表を作ってくれます。
色々見てみましょう。
例1) ctするだけ
入力の受け取り方がそのまま見れます。
ct
nibbles:
Context:
LetArg inputs
$ fstInt :: Integer
@ fstLine :: String
_ ints :: [Integer]
;$ sndInt :: Integer
;@ allLines :: [String]
;_ allInput :: String
;;$ intMatrix :: [[Integer]]
;;@ sndLine :: String
at line: 1, char: 1
v
ct
^
例2) stringをmap
. # map
"abcde" # stringもOK
ct # ctを設置した時点での変数の状況を出力
nibbles:
Context:
LambdaArg .
$ :: Char (unused so far, prioritized for implicit args)
LetArg inputs
@ fstInt :: Integer
_ fstLine :: String
;$ ints :: [Integer]
;@ sndInt :: Integer
;_ allLines :: [String]
;;$ allInput :: String
;;@ intMatrix :: [[Integer]]
;;_ sndLine :: String
at line: 3, char: 1
v
ct
^
例3) たくさんmap
.,10 .,10 .,10 ."abcde" .,10 # たくさんmapしてみる
ct # この時点での状況を出力
.,10 .,10 # 出力には影響しない部分
nibbles:
Context:
LambdaArg .
$ :: Integer (unused so far, prioritized for implicit args)
LambdaArg .
@ :: Char (unused so far, prioritized for implicit args)
LambdaArg .
_ :: Integer (unused so far, prioritized for implicit args)
LambdaArg .
;$ :: Integer (unused so far, prioritized for implicit args)
LambdaArg .
;@ :: Integer (unused so far, prioritized for implicit args)
LetArg inputs
;_ fstInt :: Integer
;;$ fstLine :: String
;;@ ints :: [Integer]
;;_ sndInt :: Integer
;;;$ allLines :: [String]
;;;@ allInput :: String
;;;_ intMatrix :: [[Integer]]
;;;;$ sndLine :: String
at line: 2, char: 1
v
ct
^
問題
Chokudai SpeedRun 002 A - 長方形 α
$ N $
$ A_1 \quad B_1 $
$ A_2 \quad B_2 $
$ \vdots $
$ A_N \quad B_N $
各$ A_i, B_i (i = 1, 2 \cdots N)$に対して、 $ A_i \times B_i $を出力します。
>1;;$`*
実際に問題を解いていきます。
ただ、散々mapの話をしたのにいきなり演算子 . が存在しませんね。一旦気にしないでください。
順番に見ていきます。
まず入力を ;;\$ で受け取ります。
;;$ は数値行をまとめて二次元配列としてアクセスできます。
pythonで次のことをしているイメージ。
[list(map(int, input().split())) for _ in range(XX)]
一行目の$N$は不要ですので、 drop > を使用して読み飛ばします。
# > x で 配列を先頭から x 個分進める
# 例 (pでデバッグできます)
p >3 ,5 # [4,5]
p >6 ,5 # [] 要素数より多くてもOK
# 二次元配列に対するdrop
p .,3 ,3 # mapで二次元配列作成 [[1,2,3],[1,2,3],[1,2,3]]
p >1 .,3 ,3 # [[1,2,3],[1,2,3]]
>1;;\$で$ N $を読み飛ばして、 map することで、各$ A_i \quad B_i $に $ でアクセスできるようになります。
.>1;;$ ct
# nibbles:
# Context:
# LambdaArg .
# $ :: [Integer] (unused so far, prioritized for implicit args)
#
# LetArg inputs
# @ fstInt :: Integer
# _ fstLine :: String
# ;$ ints :: [Integer]
# ;@ sndInt :: Integer
# ;_ allLines :: [String]
# ;;$ allInput :: String
# ;;@ intMatrix :: [[Integer]]
# ;;_ sndLine :: String
#
#
# at line: 1, char: 8
# v
# .>1;;$ ct
# ^
# 入力例1
.>1;;$ p$
# [3,4]
# [1000000000,1]
# [111111111,111111111]
ctで型を確認すると、しっかりと$がIntegerの配列になっていますね。
Integer配列に対して product `* をすると総積が求められるので、
.>1;;$ `*$
.>1;;$`*$ # 不要なスペース削除
.>1;;$`* # $ 省略
さらに、こちらのImplicit Opsの欄を見ると、下記の記述があります。
If you don't use the accumulator (@) then it will instead assume you wanted to do a map.
簡単に言うと、引数が $ だけのとき、map演算が省略できる場合があるよってことです。
今回はしっかり省略できるので、最終形は下記となります。
>1;;$`*
ABC307 A - Weekly Records
$ N $
$ A_1 \quad A_2 \quad \ldots \quad A_{7N} $
$ A $を7要素ずつ区切って、それぞれ $ sum $ を出力します。
`/7=2;;$+
chunk of を使用して、2行目を7個ずつに分割します。
p `/7 =2;;$ # `/x で配列を x個ずつの要素に分解
# 入力例1
# [[1000,2000,3000,4000,5000,6000,7000],
# [2000,3000,4000,5000,6000,7000,8000]]
あとは map を使ってそれぞれの要素を sum してあげればOKです。
.`/7=2;;$+$ # map して sum
`/7=2;;$+ # 先頭の map と最後の $ を省略して完成
FizzBuzz
※入力なし
1 ~ 100 の各要素に対して、3の倍数のとき Fizz, 5の倍数のとき Buzz, 3の倍数かつ5の倍数のとき FizzBuzz, それ以外のときは数字のまま出力します。
$?,:^-~%$3"Fizz"^-~%$5"Buzz"
最後にFizzBuzzを扱っていきます。
FizzBuzzは非常に有名な問題ですね。詳細はこちらから。
いままでの問題に対して、かなり難しめですので、気になった方だけ挑戦してみてください!
まずは、「3の倍数に対して、Fizzを出力する」処理だけ抜き出してみます。
これだけでもややこしいので、Quick Refを横に並べてご覧ください!
^-~%$3"Fizz"
# さらに処理を分解
^ # replicate 指定回数文字列を繰り返す
# 例) ^3"ab" → "ababab"
# 回数が0以下のときは空文字列
-~ %$3 # - 1 %$3 と同義。
# 3の倍数のときのみ、 ^ の回数を1とする処理
"Fizz" # 繰り返し対象の文字列
# テスト
p ^-~%$3"Fizz"
# 入力 出力
# 1 ""
# 2 ""
# 3 "Fizz"
-~ %$3 の補足
普通の引き算をしているだけなんですが、 ~ を使用しています。
~ を使用すると、Quick RefのAutos列の値が使用されます。
- (subtract) のAutosを確認すると 「$1 \quad 1$」 とあります。
第一引数、第二引数それぞれで ~ を使うと 1 として処理されますよ、という意味です。
「 -1\$ 」のように書くと、「-1」というかたまりで扱われてしまうので、- と1の間にスペースが必要なんですね。
~を使用することで「 -~$ 」と短縮できるわけです。
Buzzでも「 ^-~%$5"Buzz" 」と、同様の処理が行われています。
その後、append を使用して、FizzBuzzの結果を連結しています。
: # append 文字列連結
^-~%$3"Fizz"
^-~%$5"Buzz"
# append テスト
p :"" "" # ""
p :"Fizz" "" # "Fizz"
p :"Fizz" "Buzz" # "FizzBuzz"
ここまでの「 :^-~%\$3"Fizz"^-~%\$5"Buzz" 」で
3の倍数のとき Fizz
5の倍数のとき Buzz
3の倍数かつ5の倍数のとき FizzBuzz
の実装ができています。適当な入力で確かめてみてください。
続いて、FizzBuzz以外の数字出力処理を作成していきます。
if/else (lazy list) ?, を使用してFizzBuzzの結果が空文字かどうか、場合分けをしていきます。
?, # if/else (lazy list)
:^-~%$3"Fizz"^-~%$5"Buzz"
演算結果は
空文字列でない -> true, 空文字列 -> false
となります。
こちらの演算は
「 ?, 条件 trueの出力 falseの出力 」
の形で記載していきます。
p ?,"" "true" "false" # "false"
p ?,"1" "true" "false" # "true"
p ?,"" "true" 10 # "10"
p ?,"Fizz" $ 10 # "Fizz"
一番下の処理、実は if/else では、「 条件 」の値が \$ に入ります!
map のように変数の移動が起こるわけですね。
スコープは 「 trueの出力 」だけです。「 falseの出力 」までくると、参照できません。
nibbles:
Context:
LambdaArg ?,
$ :: String
LetArg inputs
@ fstInt :: Integer
_ fstLine :: String
;$ ints :: [Integer]
;@ sndInt :: Integer
;_ allLines :: [String]
;;$ allInput :: String
;;@ intMatrix :: [[Integer]]
;;_ sndLine :: String
at line: 1, char: 13
v
p ?, "Fizz" ct
^
「 ?,:^-~%\$3"Fizz"^-~%\$5"Buzz" 」の解読に戻ります。
実はこれ、$$が最後に省略されています。
省略分を表示しながら、整理するとこんな感じになります。
?, # if/else
: # append
^-~%$3"Fizz" # Fizz処理
^-~%$5"Buzz" # Buzz処理
$ # true時の処理 -> FizzBuzz 文字列を表示($:条件部分の値)
$ # false時の処理 -> 数字を表示($:元々の数字)
これで、入力に対して、FizzBuzz出力を行ってくれる部分が作成できました!
p ?,:^-~%$3"Fizz"^-~%$5"Buzz"
# 入力 出力
# 1 "1"
# 2 "2"
# 3 "Fizz"
# 5 "Buzz"
# 15 "FizzBuzz"
# 100 "Buzz"
最後に、1 ~ 100までの数字をmapで処理していけばOKです!
.,100 # 1 ~ 100 のリストをmap
?,:^-~%$3"Fizz"^-~%$5"Buzz"
.,100?,:^-~%$3"Fizz"^-~%$5"Buzz" # 整理
$?,:^-~%$3"Fizz"^-~%$5"Buzz" # 公式のコード
なんか先頭だけ違いますね。最後に「 .,100 」と「 $ 」について見ていきます。
こちらのImplicit Opsの欄を再度見ていきましょう。
These rules also apply if the first value is an integer, except that it does a "range from 1 to n" to generate a list first.
数字を先頭に書いたとき、range として扱うよって言ってます(例外アリ)。
そんなわけで、map に加えて range まで省略できるので「 .,100 」が 「 100 」となります!
最後に、「 $ 」と書ける理由についてです。
Quick RefのInputs:欄を見てみます。
defaultという列がありますね。
対象の入力がない場合、入力の値を使用する代わりにdefaultの値が使用されるんですね。
「 $ 」のdefaultをみると、ちょうど100になっております!
そんなわけで、「 100 」が「 \$ 」とできて、下記コードが完成します!
$ # 1 ~ 100 のリストをmap
?,:^-~%$3"Fizz"^-~%$5"Buzz"
$?,:^-~%$3"Fizz"^-~%$5"Buzz" # 整理
おわりに
急に難易度が爆上がりしてしまいましたが、いかがでしたでしょうか。
記事のことでもなんでも、わからない箇所があれば気軽にご質問ください。
X宛でも構いません、可能な範囲で対応させていただきます!