LoginSignup
1
1

More than 1 year has passed since last update.

小道具:素因数分解

Last updated at Posted at 2022-08-16

素因数分解して結果を出力します。

simple.py
#!/usr/bin/env python3

# 整数の平方根
def lsqrt1(x: int) -> tuple:
    if x < 0:
        raise ArithmeticError
    l, r, s = x.bit_length() >> 1, 0, 0
    for b in range(l, -1, -1):
        r <<= 1
        t = 1 << b
        u = s + t
        v = u << b
        if x < v:
            continue
        x -= v
        r |= 1
        s = u + t
    return (r, x)


# 整数の平方根(Python 3.8 以上)
def lsqrt2(x: int) -> tuple:
    a = isqrt(x)
    return (a, x - a * a)


try:
    from math import isqrt
    lsqrt = lsqrt2
except:
    lsqrt = lsqrt1


# 素因数分解
def factor(N: int) -> tuple:
    f = []

    # 負は正にする
    if N < 0:
        f.append(-1)
        N = -N

    # 2^n で割って奇数にする
    while (N & 1) == 0:
        f.append(2)
        N >>= 1

    # 奇数の素因数分解
    def odd(N: int, ds: int) -> list:
        # 7 以下の場合は素数
        if N <= 7:
            return [N] if N != 1 else []

        # 平方数と除数の上限
        q, r = lsqrt(N)
        if r == 0:
            return odd(q, ds) * 2

        # 奇数の試し割
        for d in range(ds, q + 2, 2):
            q, r = divmod(N, d)
            if r != 0:
                continue

            # 指数を調べる
            e = 1
            while True:
                t, r = divmod(q, d)
                if r != 0:
                    break
                q = t
                e += 1
            return [d] * e + odd(q, d + 2)

        # 素数
        return [N]

    f += odd(N, 3)
    if not f:
        f.append(1)

    # ((素数,指数), ...) の形式に整える
    return tuple([(d, f.count(d)) for d in sorted(set(f))])


# 素因数分解(検算あり)
def factor_debug(N: int) -> tuple:
    f = factor(N)
    c = 1
    for t in [p ** e for p, e in f]:
        c *= t
    if N != c:
        raise Exception(f'Bug:{N} != {c}, {f}')
    return f


def main():
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('N', type=int)
    args = parser.parse_args()
    print(args.N, '=', ' * '.join(e != 1 and f'{p}^{e}' or f'{p}'
                                  for p, e in factor_debug(args.N)))


if __name__ == '__main__':
    main()
実行結果
$ python3 simple.py 1234567890
1234567890 = 2 * 3^2 * 5 * 3607 * 3803

頭の体操

説明が面倒臭い方法で素因数分解します。できるというだけで、アルゴリズム的にはとんでもなく遅いです。

sample.py
#!/usr/bin/env python3

# 素因数分解
def factor(N: int) -> tuple:
    r = []

    if N < 0:
        r.append(-1)
        N = -N

    # 2^n で割って奇数にする
    while (N & 1) == 0:
        r.append(2)
        N >>= 1

    r += factor_odd(N)
    if not r:
        r.append(1)

    # ((素数,指数), ...) の形式に整える
    return tuple([(d, r.count(d)) for d in sorted(set(r))])


# 奇数の素因数分解
def factor_odd(N: int) -> list:
    if N <= 7:
        # 7 以下で 1 以外は素数
        if N == 1:
            return []
        return [N]
    q, r = lsqrt(N)
    if r == 0:
        t = factor_odd(q)
        return t + t
    if (N & 2) == 0:
        return factor_odd_4n1(N, q)
    else:
        return factor_odd_4n3(N, q)


# N = (4n + 1) の場合
def factor_odd_4n1(N: int, q: int) -> list:
    z = N >> 2            # = (N - 1) >> 2
    xmin = (q + 1) >> 1   # = ((q - 1) >> 1) + 1
    xmax = N >> 2         # : (N >> 2) - 1

    for o in range(3):
        for x in range(xmin + o, xmax, 4):
            t = x * x + x - z
            if (t & 2) != 0:  # y^2 = (2k + 1)^2 = 4(k^2 + k) + 1
                continue
            y, r = lsqrt(t)
            if r == 0:
                a = (x + y) * 2 + 1
                b = (x - y) * 2 + 1
                return factor_odd(a) + factor_odd(b)

    # x = 4k + 3
    # x^2 + x = 4(4k^2 + 7k + 3)
    if (z & 2) == 0:
        for x in range(xmin + 3, xmax, 4):
            t = x * x + x - z
            y, r = lsqrt(t)
            if r == 0:               # a = 2x + 1, b = 2y
                m = (x + y) * 2 + 1  # a + b = (2x + 1) + (2y)
                n = (x - y) * 2 + 1  # a - b = (2x + 1) - (2y)
                return factor_odd(m) + factor_odd(n)

    return [N]


# N = (4n + 3) の場合
def factor_odd_4n3(N: int, q: int) -> list:
    z = (N + 1) >> 2
    xmin = (q >> 1) + 1  # = (q >> 1) + 1
    xmax = z             # = (N - 1) >> 2
    for x in range(xmin, xmax):
        t = x * x - z
        s, r = lsqrt(t * 4 + 1)
        if r == 0:
            y = (s - 1) >> 1     # a = 2x, b = 2y + 1
            m = (x + y) * 2 + 1  # a + b = (2x) + (2y + 1)
            n = (x - y) * 2 - 1  # a - b = (2x) - (2y + 1)
            return factor_odd(m) + factor_odd(n)
    return [N]


# 整数の平方根
def lsqrt(x: int) -> tuple:
    if x < 0:
        raise ArithmeticError
    l, r, s = x.bit_length() >> 1, 0, 0
    for b in range(l, -1, -1):
        r <<= 1
        t = 1 << b
        u = s + t
        v = u << b
        if x < v:
            continue
        x -= v
        r |= 1
        s = u + t
    return (r, x)



def main():
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('N', type=int)
    args = parser.parse_args()

    print(args.N, '=', ' * '.join('%d^%d' % q for q in factor(args.N)))


if __name__ == '__main__':
    main()
実行結果
$ python3 sample.py 1234567890
1234567890 = 2^1 * 3^2 * 5^1 * 3607^1 * 3803^1
1
1
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
1
1