読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

拡張ユークリッド互除法も再帰で書いた方が自然に見える

プログラミング アルゴリズム C#3.0 Haskell

最適化のために最大公約数を求める必要があって、
ユークリッド互除法と拡張ユークリッド互除法を書いたので、チラ裏に残しておく*1


ユークリッド互除法

まずはよく見る実装をC#

        public static int Gcd(int a, int b)
        {
            while (b != 0)
            {
                int r = a % b;
                a = b;
                b = r;
            }
            return a;
        }

実にありがちな実装なのですが、どう見てもダサすぎ。


これってつまり、こういうことだよね。

        public static int GcdR(int a, int b)
        {
            if (b == 0) return a;
            return GcdR(b, a % b);
        }

スッキリしました。



何を思ったか、久々にHaskellでも書いてみることに。
って、実はHaskellでは最初からgcd関数が定義されていたりする。
http://www.zvon.org/other/haskell/Outputprelude/gcd_f.html


なので

main :: IO()
main = return (gcd 1029 1071) >>= print

これで、21って答えが出ちゃったりする。


でも、自分で書いてみることに意味があるよね。たぶん。

gcd2 :: Int -> Int -> Int
gcd2 a 0 = a
gcd2 a b = gcd2 b (rem a b)

こんだけ。書くまでもなかった。



拡張ユークリッド互除法

よく見る実装をC#で。

        public static int GcdEx(int a, int b)
        {
            int x1 = 0;
            int x2 = 1;
            int y1 = 1;
            int y2 = 0;
            int r1 = b;
            int r2 = a;

            while (true)
            {
                if (r1 == 0) return r2;

                //Math.DivRemなんてのもあるけど
                int q = (int)Math.Floor((double)(r2 / r1));
                int r = r2 % r1;

                int x = x2 - q * x1;
                int y = y2 - q * y1;

                if (r == 0) break;

                x2 = x1;
                x1 = x;
                y2 = y1;
                y1 = y;
                r2 = r1;
                r1 = r;
            }
            return r1;
        }

うへ。これはひどい・・・。
なんというローカル変数の乱立www


これも再帰で表現したほうがスッキリできそうです。
実際に書いてみると、こんな感じでした。

        private delegate TResult Func<T1, T2, T3, T4, T5, T6,TResult>(T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6);
        public static int GcdExR(int a, int b)
        {
            Func<int, int, int, int, int, int, int> f = null;
            f = (a2, b2, w2, x2, y2, z2) =>
            {
                if (b2 == 0) return a2;
                return f(b2, a2 % b2, x2, (w2 - a2) / b2 * x2, z2, (y2 - a2) / b2 * z2);
            };
            return f(a, b, 1, 0, 0, 1);
        }

いざ書いてみると、引数を6つとるFuncがないことに気付く…w
一見わかりにくそうに見えるかもしれませんが、個人的には上に書いたよく見る実装よりも、
こちらの方が好みです。書き方的に「今風」っぽいというのも含めて。
あと、ラムダ式ばっか書いてると、匿名delegateでの旧式な書き方を忘れ気味であることに気付いた俺ガイル。


締めにHaskellでも

gcdex :: Int -> Int -> Int
gcdex a b = gcdex' a b 1 0 0 1
  where
    gcdex' a 0 _ _ _ _ = a
    gcdex' a b w x y z = gcdex' b r y z (w-y*q) (x-z*q)
      where (q, r) = quotRem a b

こうしてみると、やっぱHaskellはクールだなあ。

*1:負の数に関しては考えていません