massively improve performance with dynamic programming

This commit is contained in:
filifa 2025-09-16 22:23:39 -04:00
parent 4d8f12e1a2
commit ddbb892748
2 changed files with 45 additions and 36 deletions

View File

@ -19,6 +19,7 @@ package cmd
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"scm.dairydemon.net/filifa/mathtools/internal/lib" "scm.dairydemon.net/filifa/mathtools/internal/lib"
@ -31,13 +32,13 @@ var stirlingBottom string
var stirlingUnsigned bool var stirlingUnsigned bool
func stirling(cmd *cobra.Command, args []string) { func stirling(cmd *cobra.Command, args []string) {
n, ok := new(big.Int).SetString(stirlingTop, 10) n, err := strconv.Atoi(stirlingTop)
if !ok { if err != nil {
cobra.CheckErr("invalid input " + stirlingTop) cobra.CheckErr("invalid input " + stirlingTop)
} }
k, ok := new(big.Int).SetString(stirlingBottom, 10) k, err := strconv.Atoi(stirlingBottom)
if !ok { if err != nil {
cobra.CheckErr("invalid input " + stirlingBottom) cobra.CheckErr("invalid input " + stirlingBottom)
} }

View File

@ -20,49 +20,57 @@ import (
"math/big" "math/big"
) )
// TODO: implement both of these with dynamic programming // given a slice where vals[i] = Stirling1(i+k-1, k-1) for a given k, nextStirling1 updates the slice so vals[i] = Stirling1(i+k, k) using the property that Stirling1(n, k) = -(n-1)*Stirling1(n-1, k) + Stirling1(n-1, k-1)
func nextStirling1(k int, vals []*big.Int) {
func Stirling1(n, k *big.Int) *big.Int { for i := 1; i < len(vals); i++ {
if n.Cmp(big.NewInt(0)) == 0 && k.Cmp(big.NewInt(0)) == 0 { n := int64(k + i - 1)
return big.NewInt(1) v := big.NewInt(-n)
v.Mul(v, vals[i-1])
vals[i].Add(vals[i], v)
} }
}
if n.Cmp(big.NewInt(0)) == 0 || k.Cmp(big.NewInt(0)) == 0 { func Stirling1(n, k int) *big.Int {
if k > n {
return big.NewInt(0) return big.NewInt(0)
} }
newN := new(big.Int).Set(n) vals := make([]*big.Int, n-k+1)
newN.Sub(newN, big.NewInt(1)) for i := range vals {
vals[i] = big.NewInt(0)
}
vals[0] = big.NewInt(1)
result := Stirling1(newN, k) for i := 1; i <= k; i++ {
result.Mul(result, newN) nextStirling1(i, vals)
result.Neg(result)
newK := new(big.Int).Set(k)
newK.Sub(newK, big.NewInt(1))
result.Add(result, Stirling1(newN, newK))
return result
}
func Stirling2(n, k *big.Int) *big.Int {
if n.Cmp(k) == 0 {
return big.NewInt(1)
} }
if n.Cmp(big.NewInt(0)) == 0 || k.Cmp(big.NewInt(0)) == 0 { return vals[n-k]
}
// given a slice where vals[i] = Stirling2(i+k-1, k-1) for a given k, nextStirling2 updates the slice so vals[i] = Stirling2(i+k, k) using the property that Stirling2(n, k) = k*Stirling2(n-1, k) + Stirling2(n-1, k-1)
func nextStirling2(k int64, vals []*big.Int) {
for i := 1; i < len(vals); i++ {
v := big.NewInt(k)
v.Mul(v, vals[i-1])
vals[i].Add(vals[i], v)
}
}
func Stirling2(n, k int) *big.Int {
if k > n {
return big.NewInt(0) return big.NewInt(0)
} }
newN := new(big.Int).Set(n) vals := make([]*big.Int, n-k+1)
newN.Sub(newN, big.NewInt(1)) for i := range vals {
vals[i] = big.NewInt(0)
}
vals[0] = big.NewInt(1)
result := Stirling2(newN, k) for i := 1; i <= k; i++ {
result.Mul(result, k) nextStirling2(int64(i), vals)
}
newK := new(big.Int).Set(k) return vals[n-k]
newK.Sub(newK, big.NewInt(1))
result.Add(result, Stirling2(newN, newK))
return result
} }