2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

競プロの問題をnibblesで解く③

Posted at

はじめに

nibblesで問題を解くの会 第三回です。
一回目二回目もぜひご覧ください。

今回はmapを扱います。
filterもやろうと思いましたが、思ったより分量が多くなってしまったので、こちらはまた次回で。

この辺りから可読性が終わってきます。
最初はきっと、コードを読んでも何もわからないと思いますが、頑張りましょう。
公式のQuick Refを見ながらだと理解が進みやすいと思います。

ちなみに、nibblesでコメント付けられるの最近まで知らず...今回から活用していきます。
「 # 」以降がコメントになるみたいです。pythonと一緒。

今回はmapの概要を説明した後に問題を解いてみたいと思います。
最終的に、ドキュメントに載っている下記FizzBuzzのコードを解読を目指します!

FizzBuzz 28byte
$?,:^-~%$3"Fizz"^-~%$5"Buzz"

今回もフォーマットはkotatsugameさんのこちらの記事から、丸パクリさせていただきました。(いつもありがとうございます。)

入力形式

解法

Byte数
コード

コメント

mapでできること

連想配列ではないです。
pythonのmapと基本同じ、内包表記も近いです。
そのため、一部解説の中でpythonの疑似コードを使用しています。(pythonわからない方ごめんなさい)

nibblesにはforがないので、多くの問題でmapを使用することになります。
mapの演算をQuick Refで確認します。

演算子 引数1 引数2
. list fn

(「 . 」が演算子なのちょっと不思議な感じしませんか?)
map 、第二引数にfn(関数)を取ります。
文字で説明するのも難しいので、実際にコードを見ていきます。

例1)長さ5、要素が全て10の配列を作成してください

pythonの内包表記と上の表のlist, fnを対応させてみました。

python
map(lambda _: 10, range(5))  # map(fn, list)
[10 for _ in range(5)]  # [fn for _ in list]

nibblesではこちら

nibbles
.    # map
,5  # list ( ,x で 1 〜 x のリストを作成)
10   # fn
# -> [10, 10, 10, 10, 10]

まだ簡単です。

例2)10〜15のリストを作成してください

先ほどと同様に、まずはpythonを見てみます。
rangeでそのまま実現できますが、今回はこちらで。

python
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
.    # 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
.     # map
`,6   # 0 ~ 10 のリストを生成
+$10  # 足し算
# -> [10, 11, 12, 13, 14, 15]

同じ記号でも意味がどんどん変わってくるのでコードを読むのが難しくなってくるわけです。
mapの中でmapをするとさらに1個分押し出され、押し出され、、逆にmapのスコープを外れるとなんと1個分戻ります。

変数迷子にならないために

変数迷子にならないために、 ct を使ってデバッグすることが重要になってきます。
ct をするとエラー出力で先ほどの変数の表を作ってくれます。
色々見てみましょう。

例1) ctするだけ

入力の受け取り方がそのまま見れます。

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

ct
.   # 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

map map map 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 $を出力します。

7byte
>1;;$`*

実際に問題を解いていきます。
ただ、散々mapの話をしたのにいきなり演算子 . が存在しませんね。一旦気にしないでください。

順番に見ていきます。
まず入力を ;;\$ で受け取ります。
;;$ は数値行をまとめて二次元配列としてアクセスできます。
pythonで次のことをしているイメージ。

;;$
[list(map(int, input().split())) for _ in range(XX)]

一行目の$N$は不要ですので、 drop > を使用して読み飛ばします。

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 $に $ でアクセスできるようになります。

map
.>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 `* をすると総積が求められるので、

solve
.>1;;$ `*$

.>1;;$`*$  # 不要なスペース削除
.>1;;$`*   # $ 省略

さらに、こちらのImplicit Opsの欄を見ると、下記の記述があります。

If you don't use the accumulator (@) then it will instead assume you wanted to do a map.

簡単に言うと、引数が $ だけのとき、map演算が省略できる場合があるよってことです。
今回はしっかり省略できるので、最終形は下記となります。

7byte
>1;;$`*

ABC307 A - Weekly Records

$ N $
$ A_1 \quad A_2 \quad \ldots \quad A_{7N} $

$ A $を7要素ずつ区切って、それぞれ $ sum $ を出力します。

9byte
`/7=2;;$+

chunk of を使用して、2行目を7個ずつに分割します。

chunk of
p `/7 =2;;$  # `/x で配列を x個ずつの要素に分解
# 入力例1
# [[1000,2000,3000,4000,5000,6000,7000],
#  [2000,3000,4000,5000,6000,7000,8000]]

あとは map を使ってそれぞれの要素を sum してあげればOKです。

solve
.`/7=2;;$+$  # map して sum
`/7=2;;$+    # 先頭の map と最後の $ を省略して完成

FizzBuzz

※入力なし

1 ~ 100 の各要素に対して、3の倍数のとき Fizz, 5の倍数のとき Buzz, 3の倍数かつ5の倍数のとき FizzBuzz, それ以外のときは数字のまま出力します。

28byte
$?,:^-~%$3"Fizz"^-~%$5"Buzz"

最後にFizzBuzzを扱っていきます。
FizzBuzzは非常に有名な問題ですね。詳細はこちらから。

いままでの問題に対して、かなり難しめですので、気になった方だけ挑戦してみてください!

まずは、「3の倍数に対して、Fizzを出力する」処理だけ抜き出してみます。
これだけでもややこしいので、Quick Refを横に並べてご覧ください!

3の倍数に対して、Fizzを出力
^-~%$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
:  # 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)
?,  # if/else (lazy list)
:^-~%$3"Fizz"^-~%$5"Buzz"

演算結果は
空文字列でない -> true, 空文字列 -> false
となります。

こちらの演算は
「 ?, 条件 trueの出力 falseの出力 」
の形で記載していきます。

if/else (lazy list)
p ?,""     "true" "false"  # "false"
p ?,"1"    "true" "false"  # "true"
p ?,""     "true" 10       # "10"
p ?,"Fizz" $      10       # "Fizz"

一番下の処理、実は if/else では、「 条件 」の値が \$ に入ります!
map のように変数の移動が起こるわけですね。
スコープは 「 trueの出力 」だけです。「 falseの出力 」までくると、参照できません。

ct確認
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出力を行ってくれる部分が作成できました!

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 」が「 \$ 」とできて、下記コードが完成します!

28byte
$  # 1 ~ 100 のリストをmap
?,:^-~%$3"Fizz"^-~%$5"Buzz"

$?,:^-~%$3"Fizz"^-~%$5"Buzz"  # 整理

おわりに

急に難易度が爆上がりしてしまいましたが、いかがでしたでしょうか。
記事のことでもなんでも、わからない箇所があれば気軽にご質問ください。
X宛でも構いません、可能な範囲で対応させていただきます!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?