💓

pythonで「ドドスコスコスコ」を文字数を減らして書きたい

2023/02/28に公開

概要

結論のコード

import random as r;c=15
while c%4096^1911:c=c*2+r.randrange(2);print(["ドド","スコ"][c%2])
print("ラブ注入♡")

今回扱ったコードをGitHubで見る↗︎


次の記事 : 未執筆

本題

かなり前に目にした、下の問題

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ビットを記録すれば、上のコードと同じ機能を実装できる。
 つまり、下記のような手順で記録する。

  1. 最初、カウンターは 0 (二進法)
    "スコ"を出力 (乱数)
    カウンターに"スコ"のインデックス(1)を足す
  2. カウンターは 1 (二進法)
    "ドド"を出力 (乱数)
    カウンターを2倍にして 10 (二進法)
    カウンターに"ドド"のインデックス(0)を足す
  3. カウンターは 10 (二進法)
    "スコ"を出力 (乱数)
    カウンターを2倍して 100 (二進法)
    カウンターに"スコ"のインデックス(1)を足す
  4. カウンターは 101 (二進数)
    "スコ"を出力...
  5. カウンターは1011 (二進数)
    "ドド"を出力
  6. カウンターは10110 (二進数)


 結果、カウンターの数字10110(二進法) は、スコ,ドド,スコ,スコ,ドドを記録している。
 上のようなことを繰り返し、二進法で下12桁が011101110111(十進法で1911)に一致した時、ドドスコスコスコを3回出力していることになる。
 カウンターの二進数での下12桁はカウンターを 2^{12}(4096)で割った余りとして取り出せる。

よって、下のコードのようになる。

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の外側に置かれる)
  • 変数countercにする
  • 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^00 となる
  • 0^11 となる
  • 1^01 となる
  • 1^10 となる

上の四つを基本として下のようなことが出来る。

  • 0b100^0b1000 となる
  • 0b1111^0b10100b0101 となる

(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