diff --git a/cmd/discreteLog.go b/cmd/discreteLog.go index 1da619d..6d4e8eb 100644 --- a/cmd/discreteLog.go +++ b/cmd/discreteLog.go @@ -17,11 +17,11 @@ along with this program. If not, see . package cmd import ( - "errors" "fmt" "math/big" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) var discreteLogModulus string @@ -29,62 +29,6 @@ var discreteLogBase string var discreteLogElement string var discreteLogOrder string -// whyyyy doesn't math/big have a ceil functionnnn -func ceilSqrt(x *big.Int) *big.Int { - z := new(big.Int).Sqrt(x) - s := new(big.Int).Exp(z, big.NewInt(2), nil) - if s.Cmp(x) != 0 { - z.Add(z, big.NewInt(1)) - } - - return z -} - -// TODO: this can be extended to work with n, b not coprime -// https://cp-algorithms.com/algebra/discrete-log.html -func babyStepGiantStep(n, b, x, order *big.Int) (*big.Int, error) { - z := new(big.Int).GCD(nil, nil, b, n) - if z.Cmp(big.NewInt(1)) != 0 { - return nil, fmt.Errorf("base %v and modulus %v are not coprime", b, n) - } - - var m *big.Int - if order == nil { - // m = ceil(sqrt(n - 1)) - z := big.NewInt(1) - z.Sub(n, z) - m = ceilSqrt(z) - } else { - m = ceilSqrt(order) - } - - table := make(map[string]*big.Int) - for j := big.NewInt(1); j.Cmp(m) <= 0; j.Add(j, big.NewInt(1)) { - a := new(big.Int).Exp(b, j, n) - table[a.String()] = new(big.Int).Set(j) - } - - // p = b^-m modulo n - p := new(big.Int).Neg(m) - p.Exp(b, p, n) - - gamma := new(big.Int).Set(x) - - for i := big.NewInt(0); i.Cmp(m) == -1; i.Add(i, big.NewInt(1)) { - j, ok := table[gamma.String()] - if ok { - i.Mul(i, m) - i.Add(i, j) - return i, nil - } - - gamma.Mul(gamma, p) - gamma.Mod(gamma, n) - } - - return nil, errors.New("no solution") -} - func discreteLog(cmd *cobra.Command, args []string) { m, ok := new(big.Int).SetString(discreteLogModulus, 10) if !ok { @@ -109,7 +53,7 @@ func discreteLog(cmd *cobra.Command, args []string) { } } - k, err := babyStepGiantStep(m, b, x, order) + k, err := lib.BabyStepGiantStep(m, b, x, order) if err != nil { cobra.CheckErr(err) } diff --git a/cmd/divisor.go b/cmd/divisor.go index c64dd0b..15fd0e0 100644 --- a/cmd/divisor.go +++ b/cmd/divisor.go @@ -21,27 +21,9 @@ import ( "math/big" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) -// TODO: expand to work for different sigmas -func divisorSummatory(n *big.Int) *big.Int { - // employing Dirichlet's hyperbola method - sqrt := new(big.Int).Sqrt(n) - - total := big.NewInt(0) - for x := big.NewInt(1); x.Cmp(sqrt) <= 0; x.Add(x, big.NewInt(1)) { - z := new(big.Int).Div(n, x) - total.Add(total, z) - } - - total.Mul(total, big.NewInt(2)) - - sqrt.Exp(sqrt, big.NewInt(2), nil) - total.Sub(total, sqrt) - - return total -} - func divisorSum(cmd *cobra.Command, args []string) { for _, arg := range args { n, ok := new(big.Int).SetString(arg, 10) @@ -50,7 +32,7 @@ func divisorSum(cmd *cobra.Command, args []string) { continue } - d := divisorSummatory(n) + d := lib.DivisorSummatory(n) fmt.Println(d) } } diff --git a/cmd/divisors.go b/cmd/divisors.go index cfa358a..9b0873f 100644 --- a/cmd/divisors.go +++ b/cmd/divisors.go @@ -20,71 +20,14 @@ import ( "fmt" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) var divisorsN uint var divisorsE uint -func pow(base uint, exp uint) uint { - result := uint(1) - for exp > 0 { - if exp%2 == 1 { - result *= base - } - - exp >>= 1 - base *= base - } - - return result -} - -func updateMultiples(sieve []uint, x uint, p uint, n uint) { - for q := p; ; q *= p { - // sigma_x(a*b) = sigma_x(a) * sigma_x(b) if gcd(a,b) = 1 - for i := 2 * q; i < n; i += q { - if i%(p*q) != 0 { - sieve[i] *= sieve[q] - } - } - - if p*q >= n { - break - } - - // sigma_x(p^k) = p^(kx) + sigma_x(p^(k-1)) - sieve[p*q] = pow(p*q, x) + sieve[q] - } -} - -func divisorsSieve(n uint, x uint) chan uint { - sieve := make([]uint, n) - sieve[0] = 0 - for i := uint(1); i < n; i++ { - sieve[i] = 1 - } - - ch := make(chan uint) - go func() { - for i := uint(0); i < n; i++ { - if i == 0 || i == 1 || sieve[i] != 1 { - ch <- sieve[i] - continue - } - - sieve[i] = pow(i, x) + 1 - updateMultiples(sieve, x, i, n) - ch <- sieve[i] - } - - close(ch) - }() - - return ch -} - func divisors(cmd *cobra.Command, args []string) { - ch := divisorsSieve(divisorsN, divisorsE) + ch := lib.DivisorsSieve(divisorsN, divisorsE) for i := 0; ; i++ { v, ok := <-ch if !ok { diff --git a/cmd/mobius.go b/cmd/mobius.go index 2fabafa..20798b0 100644 --- a/cmd/mobius.go +++ b/cmd/mobius.go @@ -20,43 +20,13 @@ import ( "fmt" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) var mobiusN uint -func mobiusSieve(n uint) chan int { - sieve := make([]int, n) - for i := 0; i < int(n); i++ { - sieve[i] = i - } - - ch := make(chan int) - go func() { - for i := 0; i < int(n); i++ { - if i == 0 || i == 1 || sieve[i] != i { - ch <- sieve[i] - continue - } - - ch <- -1 - - for j := 2 * i; j < int(n); j += i { - if j%(i*i) == 0 { - sieve[j] = 0 - } - - sieve[j] /= -i - } - } - - close(ch) - }() - - return ch -} - func mobius(cmd *cobra.Command, args []string) { - ch := mobiusSieve(mobiusN) + ch := lib.MobiusSieve(mobiusN) for i := 0; ; i++ { v, ok := <-ch if !ok { diff --git a/cmd/partitions.go b/cmd/partitions.go index edfad54..09e4184 100644 --- a/cmd/partitions.go +++ b/cmd/partitions.go @@ -18,46 +18,15 @@ package cmd import ( "fmt" - "math/big" "strconv" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) var partitionsN string var partitionsK string -// given a slice where vals[i] = p_{k-1}(i) for a given k, nextPartition updates the slice so vals[i] = p_k(i) using the property that p_k(n) = p_k(n-k) + p_{k-1}(n) -func nextPartition(k int, vals []*big.Int) { - for i := 1; i < len(vals); i++ { - if i-k >= 0 { - vals[i].Add(vals[i], vals[i-k]) - } - } -} - -func p(n, k int) *big.Int { - if n < 0 { - return big.NewInt(0) - } - - if k > n { - k = n - } - - vals := make([]*big.Int, n+1) - for i := range vals { - vals[i] = big.NewInt(0) - } - vals[0] = big.NewInt(1) - - for i := 1; i <= k; i++ { - nextPartition(i, vals) - } - - return vals[n] -} - func partitions(cmd *cobra.Command, args []string) { n, err := strconv.Atoi(partitionsN) if err != nil { @@ -83,7 +52,7 @@ func partitions(cmd *cobra.Command, args []string) { cobra.CheckErr("k must be nonnegative") } - z := p(n, k) + z := lib.Partitions(n, k) fmt.Println(z) } diff --git a/cmd/primeOmega.go b/cmd/primeOmega.go index 1b5f162..839d130 100644 --- a/cmd/primeOmega.go +++ b/cmd/primeOmega.go @@ -20,57 +20,14 @@ import ( "fmt" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) var primeOmegaN uint var primeOmegaMul bool -func primeOmegaUpdateMultiples(sieve []uint, p uint, n uint, multiplicity bool) { - for q := p; ; q *= p { - // omega(a*b) = omega(a) + omega(b) if gcd(a,b) = 1 - for i := 2 * q; i < n; i += q { - if i%(p*q) != 0 { - sieve[i] += sieve[q] - } - } - - if p*q >= n { - break - } - - if multiplicity { - sieve[p*q] = 1 + sieve[q] - } - } -} - -func primeOmegaSieve(n uint) chan uint { - sieve := make([]uint, n) - for i := uint(0); i < n; i++ { - sieve[i] = 0 - } - - ch := make(chan uint) - go func() { - for i := uint(0); i < n; i++ { - if i == 0 || i == 1 || sieve[i] != 0 { - ch <- sieve[i] - continue - } - - sieve[i] = 1 - primeOmegaUpdateMultiples(sieve, i, n, primeOmegaMul) - ch <- sieve[i] - } - - close(ch) - }() - - return ch -} - func primeOmega(cmd *cobra.Command, args []string) { - ch := primeOmegaSieve(primeOmegaN) + ch := lib.PrimeOmegaSieve(primeOmegaN, primeOmegaMul) for i := 0; ; i++ { v, ok := <-ch if !ok { diff --git a/cmd/shoelace.go b/cmd/shoelace.go index a31add2..6652267 100644 --- a/cmd/shoelace.go +++ b/cmd/shoelace.go @@ -24,15 +24,11 @@ import ( "strings" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) var shoelaceFile string -type point struct { - x float64 - y float64 -} - func splitLines(file *os.File) []string { slice := make([]string, 0) scanner := bufio.NewScanner(file) @@ -43,8 +39,8 @@ func splitLines(file *os.File) []string { return slice } -func readFromFile(filepath string) ([]point, error) { - points := make([]point, 0) +func readFromFile(filepath string) ([]lib.Point, error) { + points := make([]lib.Point, 0) file, err := os.Open(filepath) if err != nil { return points, err @@ -65,25 +61,14 @@ func readFromFile(filepath string) ([]point, error) { return points, err } - points = append(points, point{x, y}) + points = append(points, lib.Point{x, y}) } return points, nil } -func area(points []point) float64 { - total := float64(0) - n := len(points) - for i, p := range points { - q := points[(i+1)%n] - total += (p.y + q.y) * (p.x - q.x) - } - - return total / 2 -} - func shoelace(cmd *cobra.Command, args []string) { - var coordinates []point + var coordinates []lib.Point var err error if shoelaceFile != "" { coordinates, err = readFromFile(shoelaceFile) @@ -94,7 +79,7 @@ func shoelace(cmd *cobra.Command, args []string) { cobra.CheckErr("filename required") } - fmt.Println(area(coordinates)) + fmt.Println(lib.Area(coordinates)) } // shoelaceCmd represents the shoelace command diff --git a/cmd/totient.go b/cmd/totient.go index 890b164..9b76cbe 100644 --- a/cmd/totient.go +++ b/cmd/totient.go @@ -20,39 +20,13 @@ import ( "fmt" "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" ) var totientN uint -func totientSieve(n uint) chan uint { - totients := make([]uint, n) - totients[0] = 0 - totients[1] = 1 - for i := uint(2); i < n; i++ { - totients[i] = i - 1 - } - - ch := make(chan uint) - go func() { - for i := uint(0); i < n; i++ { - ch <- totients[i] - if i == 0 || i == 1 || totients[i] != i-1 { - continue - } - - for j := uint(2 * i); j < n; j += i { - totients[j] -= totients[j] / i - } - } - - close(ch) - }() - - return ch -} - func totient(cmd *cobra.Command, args []string) { - for v := range totientSieve(totientN) { + for v := range lib.TotientSieve(totientN) { if v == 0 { continue } diff --git a/internal/lib/discreteLog.go b/internal/lib/discreteLog.go new file mode 100644 index 0000000..9452e7a --- /dev/null +++ b/internal/lib/discreteLog.go @@ -0,0 +1,79 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +import ( + "errors" + "fmt" + "math/big" +) + +// whyyyy doesn't math/big have a ceil functionnnn +func ceilSqrt(x *big.Int) *big.Int { + z := new(big.Int).Sqrt(x) + s := new(big.Int).Exp(z, big.NewInt(2), nil) + if s.Cmp(x) != 0 { + z.Add(z, big.NewInt(1)) + } + + return z +} + +// TODO: this can be extended to work with n, b not coprime +// https://cp-algorithms.com/algebra/discrete-log.html +func BabyStepGiantStep(n, b, x, order *big.Int) (*big.Int, error) { + z := new(big.Int).GCD(nil, nil, b, n) + if z.Cmp(big.NewInt(1)) != 0 { + return nil, fmt.Errorf("base %v and modulus %v are not coprime", b, n) + } + + var m *big.Int + if order == nil { + // m = ceil(sqrt(n - 1)) + z := big.NewInt(1) + z.Sub(n, z) + m = ceilSqrt(z) + } else { + m = ceilSqrt(order) + } + + table := make(map[string]*big.Int) + for j := big.NewInt(1); j.Cmp(m) <= 0; j.Add(j, big.NewInt(1)) { + a := new(big.Int).Exp(b, j, n) + table[a.String()] = new(big.Int).Set(j) + } + + // p = b^-m modulo n + p := new(big.Int).Neg(m) + p.Exp(b, p, n) + + gamma := new(big.Int).Set(x) + + for i := big.NewInt(0); i.Cmp(m) == -1; i.Add(i, big.NewInt(1)) { + j, ok := table[gamma.String()] + if ok { + i.Mul(i, m) + i.Add(i, j) + return i, nil + } + + gamma.Mul(gamma, p) + gamma.Mod(gamma, n) + } + + return nil, errors.New("no solution") +} diff --git a/internal/lib/divisor.go b/internal/lib/divisor.go new file mode 100644 index 0000000..7a4967c --- /dev/null +++ b/internal/lib/divisor.go @@ -0,0 +1,40 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +import ( + "math/big" +) + +// TODO: expand to work for different sigmas +func DivisorSummatory(n *big.Int) *big.Int { + // employing Dirichlet's hyperbola method + sqrt := new(big.Int).Sqrt(n) + + total := big.NewInt(0) + for x := big.NewInt(1); x.Cmp(sqrt) <= 0; x.Add(x, big.NewInt(1)) { + z := new(big.Int).Div(n, x) + total.Add(total, z) + } + + total.Mul(total, big.NewInt(2)) + + sqrt.Exp(sqrt, big.NewInt(2), nil) + total.Sub(total, sqrt) + + return total +} diff --git a/internal/lib/divisors.go b/internal/lib/divisors.go new file mode 100644 index 0000000..074f120 --- /dev/null +++ b/internal/lib/divisors.go @@ -0,0 +1,75 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +func pow(base uint, exp uint) uint { + result := uint(1) + for exp > 0 { + if exp%2 == 1 { + result *= base + } + + exp >>= 1 + base *= base + } + + return result +} + +func updateMultiples(sieve []uint, x uint, p uint, n uint) { + for q := p; ; q *= p { + // sigma_x(a*b) = sigma_x(a) * sigma_x(b) if gcd(a,b) = 1 + for i := 2 * q; i < n; i += q { + if i%(p*q) != 0 { + sieve[i] *= sieve[q] + } + } + + if p*q >= n { + break + } + + // sigma_x(p^k) = p^(kx) + sigma_x(p^(k-1)) + sieve[p*q] = pow(p*q, x) + sieve[q] + } +} + +func DivisorsSieve(n uint, x uint) chan uint { + sieve := make([]uint, n) + sieve[0] = 0 + for i := uint(1); i < n; i++ { + sieve[i] = 1 + } + + ch := make(chan uint) + go func() { + for i := uint(0); i < n; i++ { + if i == 0 || i == 1 || sieve[i] != 1 { + ch <- sieve[i] + continue + } + + sieve[i] = pow(i, x) + 1 + updateMultiples(sieve, x, i, n) + ch <- sieve[i] + } + + close(ch) + }() + + return ch +} diff --git a/internal/lib/mobius.go b/internal/lib/mobius.go new file mode 100644 index 0000000..6425fbf --- /dev/null +++ b/internal/lib/mobius.go @@ -0,0 +1,48 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +func MobiusSieve(n uint) chan int { + sieve := make([]int, n) + for i := 0; i < int(n); i++ { + sieve[i] = i + } + + ch := make(chan int) + go func() { + for i := 0; i < int(n); i++ { + if i == 0 || i == 1 || sieve[i] != i { + ch <- sieve[i] + continue + } + + ch <- -1 + + for j := 2 * i; j < int(n); j += i { + if j%(i*i) == 0 { + sieve[j] = 0 + } + + sieve[j] /= -i + } + } + + close(ch) + }() + + return ch +} diff --git a/internal/lib/partitions.go b/internal/lib/partitions.go new file mode 100644 index 0000000..3063f9a --- /dev/null +++ b/internal/lib/partitions.go @@ -0,0 +1,52 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +import ( + "math/big" +) + +// given a slice where vals[i] = p_{k-1}(i) for a given k, nextPartition updates the slice so vals[i] = p_k(i) using the property that p_k(n) = p_k(n-k) + p_{k-1}(n) +func nextPartition(k int, vals []*big.Int) { + for i := 1; i < len(vals); i++ { + if i-k >= 0 { + vals[i].Add(vals[i], vals[i-k]) + } + } +} + +func Partitions(n, k int) *big.Int { + if n < 0 { + return big.NewInt(0) + } + + if k > n { + k = n + } + + vals := make([]*big.Int, n+1) + for i := range vals { + vals[i] = big.NewInt(0) + } + vals[0] = big.NewInt(1) + + for i := 1; i <= k; i++ { + nextPartition(i, vals) + } + + return vals[n] +} diff --git a/internal/lib/primeOmega.go b/internal/lib/primeOmega.go new file mode 100644 index 0000000..4ab53bd --- /dev/null +++ b/internal/lib/primeOmega.go @@ -0,0 +1,61 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +func primeOmegaUpdateMultiples(sieve []uint, p uint, n uint, multiplicity bool) { + for q := p; ; q *= p { + // omega(a*b) = omega(a) + omega(b) if gcd(a,b) = 1 + for i := 2 * q; i < n; i += q { + if i%(p*q) != 0 { + sieve[i] += sieve[q] + } + } + + if p*q >= n { + break + } + + if multiplicity { + sieve[p*q] = 1 + sieve[q] + } + } +} + +func PrimeOmegaSieve(n uint, multiplicity bool) chan uint { + sieve := make([]uint, n) + for i := uint(0); i < n; i++ { + sieve[i] = 0 + } + + ch := make(chan uint) + go func() { + for i := uint(0); i < n; i++ { + if i == 0 || i == 1 || sieve[i] != 0 { + ch <- sieve[i] + continue + } + + sieve[i] = 1 + primeOmegaUpdateMultiples(sieve, i, n, multiplicity) + ch <- sieve[i] + } + + close(ch) + }() + + return ch +} diff --git a/internal/lib/shoelace.go b/internal/lib/shoelace.go new file mode 100644 index 0000000..2b943bf --- /dev/null +++ b/internal/lib/shoelace.go @@ -0,0 +1,33 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +type Point struct { + X float64 + Y float64 +} + +func Area(points []Point) float64 { + total := float64(0) + n := len(points) + for i, p := range points { + q := points[(i+1)%n] + total += (p.Y + q.Y) * (p.X - q.X) + } + + return total / 2 +} diff --git a/internal/lib/totient.go b/internal/lib/totient.go new file mode 100644 index 0000000..fd97f24 --- /dev/null +++ b/internal/lib/totient.go @@ -0,0 +1,44 @@ +/* +Copyright © 2025 filifa + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package lib + +func TotientSieve(n uint) chan uint { + totients := make([]uint, n) + totients[0] = 0 + totients[1] = 1 + for i := uint(2); i < n; i++ { + totients[i] = i - 1 + } + + ch := make(chan uint) + go func() { + for i := uint(0); i < n; i++ { + ch <- totients[i] + if i == 0 || i == 1 || totients[i] != i-1 { + continue + } + + for j := uint(2 * i); j < n; j += i { + totients[j] -= totients[j] / i + } + } + + close(ch) + }() + + return ch +}