jusのシェル勉強会4月分

jusのシェル勉強会4月分

SoftwareDesingの2018年7月号にでていた課題やってみました。

こんなの。

素因数分解したときに23より大きい素因数を持たない自然数を1985個抽出
上記で作成した自然数をファイルaに保存し、この中から4つの数字を選んで掛け算したとき、その値がある自然数の4乗になっている組合せを1つ以上探す。

まず上の方から。
これは簡単。とりえあず1985個みつかるまでfactorで素因数分解していけばいい。「23より大きい素因数を持たない」のところはfactorの結果をgrepで判定する。

#!/bin/bash
i=1
count=0
while [ $count -lt 1985 ]
do
        i=$(expr $i + 1)
        factor $i | awk -F: '{print $2}' | grep -vP '( \d{3,}| 2[3-9]| [3-9]\d)' > /dev/null
        if [ $? == 0 ]; then
                echo $i
                count=$(expr $count + 1)
        fi
done

iを1からインクリメント。でもってfactorで素因分解。factorの出力は以下みたいなものなので、

$ factor 100
100: 2 2 5 5

コロンから後ろをawkで抽出して、grepで23より大きい数字が含まれていないか判定する。

次の課題。4つを選んで掛け算ってことでファイル中から4つランダムに選択して掛け算。
でもって、bcのsqrtに二回かけて、小数点以下が全部0になっていれば、求める数字。ってことで正直に工夫もナシに書いたコードがこちら。

#!/bin/bash
i=1
while [ 1 ]
do
        a=$(expr $(($RANDOM % 1985)) + 1)
        b=$(expr $(($RANDOM % 1985)) + 1)
        c=$(expr $(($RANDOM % 1985)) + 1)
        d=$(expr $(($RANDOM % 1985)) + 1)

        aa=`sed -ne "$a"p ./b`
        bb=`sed -ne "$b"p ./b`
        cc=`sed -ne "$c"p ./b`
        dd=`sed -ne "$d"p ./b`

        number=$(expr $aa \* $bb \* $cc \* $dd)
        echo "sqrt(sqrt($number))" | bc -l | grep '.00000000000000000000'
        if [ $? == 0 ]; then
                echo $aa,$bb,$cc,$dd
                exit
        fi
done

1985で割ったあまりに1を足せば1〜1985までのランダムな数字がでる。
sedで該当行の数字をとりだして、すべてかけ合わせる。
でもって、sqrtに二度かけして、結果を0いっぱいでgrep。マッチしたらそれでOK。
ですが、これとっても遅い。そりゃそうで、一旦乗算した数字を再度sqrtしてるんですから計算量すごいことになってる。
1985個から4つ選ぶって組合せは644,937,012,560もあるので、そりゃ時間かかる。

ランダムに選ぶところはいいとして、判定するところをなんとかしたい。
数学的に考えると、任意の自然数の4乗になっているってことは、素因数分解したときに任意の整数が4の倍数だったらいいってこと。
例えば上記の解の一つとして 19773,2662,858,3072 という組みあせがありますが、これは
3の4乗、2の12乗、11の4乗、13の4乗に素因数分解できます。

そういう観点で作り直したのがこちら。

#!/bin/bash
i=1
while [ 1 ]
do
        a=$(expr $(($RANDOM % 1985)) + 1)
        b=$(expr $(($RANDOM % 1985)) + 1)
        c=$(expr $(($RANDOM % 1985)) + 1)
        d=$(expr $(($RANDOM % 1985)) + 1)

        aa=`sed -ne "$a"p ./b`
        bb=`sed -ne "$b"p ./b`
        cc=`sed -ne "$c"p ./b`
        dd=`sed -ne "$d"p ./b`

        lc1=$(factor $aa $bb $cc $dd | awk -F: '{print $2}' | sed -e 's/ /\n/g' | sed -e '/^$/d' | sort | uniq -c | grep -P '^\W+(4|8|12|16)' | wc -l)
        lc2=$(factor $aa $bb $cc $dd | awk -F: '{print $2}' | sed -e 's/ /\n/g' | sed -e '/^$/d' | sort | uniq -c | wc -l)
        if [ $lc1 ==  $lc2 ]; then
                echo $aa,$bb,$cc,$dd
                exit
        fi

done

選択した数字をすべて素因数分解した上で、それぞれの素因数を1行1文字に。sort uniq -c すると素因数の数を数えられます。
44,2662,18816,5292の場合、こんな出力。

$ factor 44 2662 18816 5292 |  awk -F: '{print $2}' | sed -e 's/ /\n/g' | sed -e '/^$/d' | sort | uniq -c
      4 11
     12 2
      4 3
      4 7

でてくる数字部分を4の倍数でgrepした場合と行数が同じならば、それは自然数の4乗。
うーん、速度的にはそれほど変わらないような。組合せを減らす方向か、素因数分解した数を選択する方向かなんだろうなぁ。