Open5

【Swift 5.6】エラトステネスの櫛を使って素因数分解【おまけもあるよ】

kotan.knkotan.kn
extension BinaryInteger {
    static func gcd(_ x: Self, _ y: Self) -> Self {
        y == 0 ? x : gcd(y, x % y)
    }
    static var Primes: UnfoldSequence<Self, (Self, Array<Self>)> {
        sequence(state: (2, [])) { state in
            while state.1.contains(where: { state.0 % $0 == 0 }) {
                state.0 = state.0 + 1
            }
            state.1.append(state.0)
            return state.0
        }
    }
    var primeFactors: Array<(Self, Self)> {
        var F = Array<(Self, Self)>()
        var N = self
        for p in Self.Primes {
            guard N > 1 else { break }
            guard N >= p * p else {
                F.append((N, 1))
                break
            }
            var C = 0 as Self
            while N % p == 0 {
                N /= p
                C += 1
            }
            if 0 < C {
                F.append((p, C))
            }
        }
        return F
    }
}

Primesで素数を列挙しながら剰余が0になる因数とその個数を配列に詰め込むだけ.
任意の整数に .primeFactors をさせると[(Base, Exponential)] という書式の配列が得られる.
例えば240.primeFactors ならば [(2, 4), (3, 1), (5, 1)] が得られる.240 = 2^43^15^2と等価.

GCDは素因数分解に使わないけれども今後の拡張用おまけ.

kotan.knkotan.kn

BinaryInteger プロトコルを実装すれば任意の整数型で使えるはず.
BigInteger とか BInt とか.
ただNaïve実装すぎて早くはない.

kotan.knkotan.kn

エラトステネスの櫛の最悪計算時間はO\left(x\right)\sim\frac{x\sqrt{x}}{\ln x}.そんなに早くない.

kotan.knkotan.kn

[(Base, Exponential)] where Base: BinaryInteger, Exponential: BinaryIntegerの書式を合成数に戻すコードは以下.

let today = 20220527
let today_pf = today.primeFactors
let today_synth = today_pf.reduce(1) {
    $0 * Array(repeating: $1.0, count: Int($1.1)).reduce(1, *)
}
print(today == today_synth)
kotan.knkotan.kn

素数列を与える

while state.1.contains(where: { state.0 % $0 == 0 }) {
    state.0 = state.0 + 1
}

の部分でテスト範囲に \sqrt{\mathrm{state.0}} の上限を与えたいものの,.lazy.filter { $0 < upperBound } をしても,素数定理で得られた個数n\sim\frac{x}{\ln x}.prefix(n) をしても上限がない時より遅くなって謎.多分最適化の問題とは思う.