pythonで「ドドスコスコスコ」を文字数を減らして書きたい
概要
結論のコード
import random as r;c=15
while c%4096^1911:c=c*2+r.randrange(2);print(["ドド","スコ"][c%2])
print("ラブ注入♡")
次の記事 : 未執筆
本題
かなり前に目にした、下の問題
twitterより
@sheeeeepla さんのツイート
【問題】配列{"ドド","スコ"}からランダムに要素を標準出力し続け、『その並びが「ドドスコスコスコ」を3回繰り返したもの』に一致したときに「ラブ注入♡」と標準出力して終了するプログラムを作成せよ(配点:5点)
これを最初に見た時は簡単に実装できると考えたが、効率を考えるとかなり難しい。
まず、そのまま実装すると大体こうなる
import random
DOSU = ["ドド", "スコ"]
requested_text = "ドドスコスコスコ"*3
output_text = ""
while True:
#乱数を生成、出力、記録
dosu_selected = random.randrange(2)
print(DOSU[dosu_selected])
output_text += DOSU[dosu_selected]
#条件を満たせばループ終了
if output_text[-24:] == requested_text:
print("ラブ注入♡")
break
出力した文字列を記録し、最後に出力したものが「ドドスコスコスコを3回繰り返したもの」に一致した時、ラブを注入する。
ここから改良を重ねていく。
まずは"ドド"と"スコ"を数字を用いてリストに記録すれば、少しは良くなりそうだ。
import random
DOSU = ["ドド", "スコ"]
requested_pattern = [0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1]
output_pattern = list()
while True:
#乱数を生成、出力、記録
dosu_selected = random.randrange(2)
print(DOSU[dosu_selected])
output_pattern.append(dosu_selected)
#条件を満たせばループ終了
if output_pattern[-12:] == requested_pattern:
print("ラブ注入♡")
break
そして、0か1かを記録するのに必要なのは1ビット。
よって、二進法で1桁に1ビットを記録すれば、上のコードと同じ機能を実装できる。
つまり、下記のような手順で記録する。
- 最初、カウンターは
0
(二進法)
"スコ"を出力 (乱数)
カウンターに"スコ"のインデックス(1)を足す - カウンターは
1
(二進法)
"ドド"を出力 (乱数)
カウンターを2倍にして10
(二進法)
カウンターに"ドド"のインデックス(0)を足す - カウンターは
10
(二進法)
"スコ"を出力 (乱数)
カウンターを2倍して100
(二進法)
カウンターに"スコ"のインデックス(1)を足す - カウンターは
101
(二進数)
"スコ"を出力... - カウンターは
1011
(二進数)
"ドド"を出力 - カウンターは
10110
(二進数)
結果、カウンターの数字10110
(二進法) は、スコ,ドド,スコ,スコ,ドドを記録している。
上のようなことを繰り返し、二進法で下12桁が011101110111
(十進法で1911)に一致した時、ドドスコスコスコを3回出力していることになる。
カウンターの二進数での下12桁はカウンターを
よって、下のコードのようになる。
import random
#注意 正確には、このコードは正しいドドスコを出力しません
DOSU = ["ドド", "スコ"]
requested_pattern = 0b011101110111 #二進法で1911のこと
counter = 0
while True:
#乱数を生成、出力、記録
dosu_selected = random.randrange(2)
print(DOSU[dosu_selected])
counter = counter*2 + dosu_selected
#条件を満たせばラブ注入
if counter%4096 == requested_pattern:
print("ラブ注入♡")
break
しかし、このままでは誤ってラブ注入をする恐れがある。
スコ,スコ,スコ,ドド,スコ,スコ...と始まった時11101110111
(1911)となり、条件を満たさないのにcounter
の数値が 1911
となるため、ラブを注入してしまうのだ。
これを防ぐ方法はいくつかあるが、今回は初期値を1111
(15)にして対策する。
よって、下のコードとなる。
import random
DOSU = ["ドド", "スコ"]
requested_pattern = 0b011101110111
counter = 15
while True:
#乱数を生成、出力、記録
dosu_selected = random.randrange(2)
print(DOSU[dosu_selected])
counter = counter*2 + dosu_selected
#条件を満たせばラブ注入
if counter%4096 == requested_pattern:
print("ラブ注入♡")
break
これで、最初に示したコードとロジックは同じになった。
次に、文字数を減らしていく。
まず、下記の点を修正する。
-
DOSU
,requested_pattern
を変数に代入せず、直接置く -
while True:
をbreak
するのではなく、最初からwhile counter%4096 != 1911:
とす
(その影響でprint("ラブ注入♡")
はwhile
の外側に置かれる) - 変数
counter
をc
にする -
import random as r
としてrandom.randrange(2)
をr.dandrange( 2)
にする
その結果が下のコードである。
import random as r
c = 15
while c%4096 != 1911:
dosu_selected = r.randrange(2))
c = c*2 + dosu_selected
print(["ドド", "スコ"][dosu_selected]
print("ラブ注入♡")
次にdosu_selected
に注目するとc%2
で取り出せることがわかる。
よって、dosu_selected
を下記のように置き換える。
import random as r
c = 15
while c%4096 != 1911:
c = c*2 + r.randrange(2)
print(["ドド", "スコ"][c%2])
print("ラブ注入♡")
かなり簡潔に纏まってきた。
最後に、c%4096 != 1911
を一文字減らす。
pythonにはXOR演算子の^
というものがある。
XOR演算子について
XOR演算子は、0と1が入力された時に1、それ以外は0を出力するビット演算である。
下の図のような説明が多い。
入力1 | 入力2 | 出力 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 0 |
注.この記事ではこれを全て理解する必要はありません。
詳しく知りたい方は「python ビット演算」、「ビット演算」などで調べると良いかもしれません。
XOR演算子を使えば、一文字でその数字が左右で等しいかどうかを検知できる。
-
0^0
で0
となる -
0^1
で1
となる -
1^0
で1
となる -
1^1
で0
となる
上の四つを基本として下のようなことが出来る。
-
0b100^0b100
で0
となる -
0b1111^0b1010
で0b0101
となる
(0bはその後に続く数字を二進法として扱う記号)
そして、a=bの時、a^b
は0となる。
その仕様をwhile
に使うと、a=bの時ループを抜け出すことができるようになる。
よってコードは下のようになる。
import random as r
c = 15
while c%4096 ^ 1911:
c = c*2 + r.randrange(2)
print(["ドド", "スコ"][c%2])
print("ラブ注入♡")
最後に空白を削除し、改行の代わりに;
を使えば、下のコードになる。
import random as r;c=15
while c%4096^1911:c=c*2+r.randrange(2);print(["ドド","スコ"][c%2])
print("ラブ注入♡")
最後に
このコードは、いやぁ含め数人で数日かけて議論し、結果ここまで短くなりました。
問題に出会い、最初から上のような短いコードを書いているわけではありません。
ですので、いいアイデアが出なくても気にせず、一度寝て起きて歯磨きして、じっくり考えてみてください。
そして、私のコードより文字数を減らせたら、ぜひお知らせください。
最後に、協力してくださった皆さんおよび、今回扱ったコードの原案を出してくださったヤマハラワさん、ありがとうございました。
そしてこれからもお世話になります。
それでは、貴方と私に良いドドスコライフを。
執筆者 : いやぁ
次の記事 : 未執筆
Discussion