From 654f0efc0ae1b56515f5a7408d10e5bbd0b85ccd Mon Sep 17 00:00:00 2001 From: filifa Date: Wed, 27 Aug 2025 22:37:45 -0400 Subject: [PATCH] add stirling subcommand --- cmd/stirling.go | 91 ++++++++++++++++++++++++++++++++++++++++ internal/lib/stirling.go | 68 ++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 cmd/stirling.go create mode 100644 internal/lib/stirling.go diff --git a/cmd/stirling.go b/cmd/stirling.go new file mode 100644 index 0000000..91dfec5 --- /dev/null +++ b/cmd/stirling.go @@ -0,0 +1,91 @@ +/* +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 cmd + +import ( + "fmt" + "math/big" + + "github.com/spf13/cobra" + "scm.dairydemon.net/filifa/mathtools/internal/lib" +) + +var firstKind bool +var secondKind bool +var stirlingTop string +var stirlingBottom string +var stirlingUnsigned bool + +func stirling(cmd *cobra.Command, args []string) { + n, ok := new(big.Int).SetString(stirlingTop, 10) + if !ok { + cobra.CheckErr("invalid input " + stirlingTop) + } + + k, ok := new(big.Int).SetString(stirlingBottom, 10) + if !ok { + cobra.CheckErr("invalid input " + stirlingBottom) + } + + var result *big.Int + if firstKind { + result = lib.Stirling1(n, k) + } else if secondKind { + result = lib.Stirling2(n, k) + } + + if stirlingUnsigned { + result.Abs(result) + } + + fmt.Println(result) +} + +// stirlingCmd represents the stirling command +var stirlingCmd = &cobra.Command{ + Use: "stirling", + Short: "Compute the Stirling numbers", + Long: `Compute the Stirling numbers.`, + Run: stirling, +} + +func init() { + rootCmd.AddCommand(stirlingCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // stirlingCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // stirlingCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + + stirlingCmd.Flags().BoolVarP(&firstKind, "first", "1", false, "Compute Stirling numbers of the first kind") + stirlingCmd.Flags().BoolVarP(&secondKind, "second", "2", false, "Compute Stirling numbers of the second kind") + + stirlingCmd.MarkFlagsMutuallyExclusive("first", "second") + stirlingCmd.MarkFlagsOneRequired("first", "second") + + stirlingCmd.Flags().StringVarP(&stirlingTop, "n", "n", "", "n") + stirlingCmd.Flags().StringVarP(&stirlingBottom, "k", "k", "", "k") + stirlingCmd.MarkFlagRequired("n") + stirlingCmd.MarkFlagRequired("k") + + stirlingCmd.Flags().BoolVar(&stirlingUnsigned, "unsigned", false, "output the absolute value of the number (only relevant for first kind)") +} diff --git a/internal/lib/stirling.go b/internal/lib/stirling.go new file mode 100644 index 0000000..b3f99ff --- /dev/null +++ b/internal/lib/stirling.go @@ -0,0 +1,68 @@ +/* +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: implement both of these with dynamic programming + +func Stirling1(n, k *big.Int) *big.Int { + if n.Cmp(big.NewInt(0)) == 0 && k.Cmp(big.NewInt(0)) == 0 { + return big.NewInt(1) + } + + if n.Cmp(big.NewInt(0)) == 0 || k.Cmp(big.NewInt(0)) == 0 { + return big.NewInt(0) + } + + newN := new(big.Int).Set(n) + newN.Sub(newN, big.NewInt(1)) + + result := Stirling1(newN, k) + result.Mul(result, newN) + 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 big.NewInt(0) + } + + newN := new(big.Int).Set(n) + newN.Sub(newN, big.NewInt(1)) + + result := Stirling2(newN, k) + result.Mul(result, k) + + newK := new(big.Int).Set(k) + newK.Sub(newK, big.NewInt(1)) + + result.Add(result, Stirling2(newN, newK)) + return result +}