sloane 1.9.3 → 2.0.0
raw patch · 9 files changed
+699/−265 lines, 9 files
Files
- LICENSE +1/−1
- README.md +56/−61
- Sloane/Config.hs +2/−2
- Sloane/DB.hs +41/−37
- Sloane/GF.hs +162/−0
- Sloane/Transform.hs +262/−0
- sloane.1 +66/−65
- sloane.cabal +5/−3
- sloane.hs +104/−96
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2012, 2013, 2014, Anders Claesson+Copyright (c) 2012--2015, Anders Claesson All rights reserved.
README.md view
@@ -1,6 +1,6 @@ ----title: SLOANE(1) Sloane User Manual | Version 1.9.3-date: 24 Sep 2014+title: SLOANE(1) Sloane User Manual | Version 2.0.0+date: 2 Jan 2015 --- # NAME@@ -10,103 +10,98 @@ # SYNOPSIS -sloane [lookup | grep] - [-a | --all | -k *keys* | --url] [-n *entries*] *terms* ... -sloane filter [--invert] -sloane update -sloane version +sloane [-a|--all] [-k KEYS] [-n N] [--url] [--local] [TERMS...] -# DESCRIPTION+sloane [--filter] [--invert] -The `sloane lookup` command searches Sloane's On-Line Encyclopedia of-Integer Sequences (OEIS). The search terms are typically the leading-term of a sequence. For example,+sloane [--transform NAME] [--list-transforms] - sloane lookup 1,1,2,5,15,52,203,877,4140+sloane [--update] [--version] [--help] -returns entry A000110 (Bell numbers), and four more entries. If no-command is given sloane will fall back to the lookup command, so the-above query can more simply be given as+# DESCRIPTION +The `sloane` command searches Sloane's On-Line Encyclopedia of Integer+Sequences (OEIS). The search terms are typically the leading term of a+sequence. For example,+ sloane 1,1,2,5,15,52,203,877,4140 -One can also search by sequence id (A-number), or even search for-arbitrary words. See the **EXAMPLES** section.+returns entry A000110 (Bell numbers), and four more entries. One can+also search by sequence id (A-number), or even search for arbitrary+words. See the **EXAMPLES** section. -Alternatively, using the `sloane grep` command, the search can be done-locally against a downloaded list of known sequences. This mode works by-"grepping" for the query in the sequence field.+Alternatively, using the `--local` option, the search can be done+locally against a downloaded local database of known sequences. This+mode works by "grepping" for the query in the sequence field. -To check a large number of sequences one can use the `sloane filter`-command. It reads the standard input line-by-line, if the sequence read-is in the local database, then it is returned to the standard output; if-not, it is ignored. This way onw can quickly filter out the sequences-from the input that are in the local database. In other words, assuming-that *FILE* contains one sequence per line,+To check a large number of sequences one can use `--filter`. When this+option is set, `sloane` reads the standard input line-by-line, if the+sequence read is in the local database, then it is returned to the+standard output; if not, it is ignored. This way one can quickly filter+out the sequences from the input that are in the local database. In+other words, assuming that *FILE* contains one sequence per line, - sloane filter <FILE+ sloane --filter <FILE returns the subset of the sequences in *FILE* that are in the local database. To also look-up the names of those sequences one could, for instance, run - sloane filter <FILE | xargs -L1 --verbose sloane grep+ sloane --filter <FILE | xargs -L1 --verbose sloane --local Sloane normally crops long lines to fit the widths of the terminal. If this is unwanted, pipe the output through cat or less: - sloane lookup -a id:A000110 | less -R+ sloane -a id:A000110 | less -R # OPTIONS ---help-: Display a short help message--# COMMANDS--## `lookup`--Lookup a sequence, or other search term, in OEIS- -a, --all : Print all fields --k *keys*+-k *KEYS* : Keys of fields to print (default: SN) ---url-: Print URLs of found entries (but nothing else)---n *entries*+-n *N* : Fetch at most this many entries (default: 5) --## `grep`--Grep for a sequence in the local database. Same options as for the-`lookup` command apply.+--url+: Print URLs of found entries (but nothing else) -## `filter`+--local+: Grep for a sequence in the local database. -Read sequences from stdin and return those that are in the local-database.+--filter+: Read sequences from stdin and return those that are in the local+ database. --invert-: Return sequences *not* in the database.+: Return sequences *not* in the database. This option has no effect+ unless `--filter` is also set. -## `update`+--transform *NAME*+: Apply the named transform to the input sequence. If the resulting+ sequence is integral print it to stdout; else print nothing.+ Most of the transforms and their names are taken from+ <https://oeis.org/transforms.txt>. -Update the local database.+--list-transforms+: List the names of all transforms. -## `version`+--update+: Update the local database. -Print version information.+--version+: Print version information. +--help+: Briefly describe the available options.+ # EXAMPLES The most common search is for entries matching a sequence of consecutive terms: - sloane lookup 1,3,19,183,2371,38703+ sloane 1,3,19,183,2371,38703 At the time of writing this particular query would return @@ -118,21 +113,21 @@ option. For instance, the following search shows the sequence, name, comments, and formula fields of the sequence whose A-number is A006531: - sloane lookup -k SNCF id:A006531+ sloane -k SNCF id:A006531 The next example returns at most 3 results of a free text search: - sloane lookup -n 3 "(2+2)-free posets"+ sloane -n 3 "(2+2)-free posets" To view the full entries of these 3 results in a browser (e.g., Firefox) one can use the url option: - firefox `sloane lookup --url -n 3 "(2+2)-free posets"`+ firefox `sloane --url -n 3 "(2+2)-free posets"` In the final example the local cache is used to filter out sequences from the standard input that are in OEIS: - sloane filter <<END+ sloane --filter <<END 1,2,3,6,11,23,47,106,235 # Comma separated integers 1 2 444 90 120 # Space separated integers '(3 9 27 88 123) # S-expression
Sloane/Config.hs view
@@ -12,7 +12,7 @@ type URL = String data Config = Config- { name :: String+ { nameVer :: String , home :: FilePath , sloaneDir :: FilePath , sloaneDB :: FilePath@@ -29,7 +29,7 @@ h <- getHomeDirectory let dsloane = h </> ".sloane" return Config- { name = "sloane 1.9.3"+ { nameVer = "sloane 2.0.0" , home = h , sloaneDir = dsloane , sloaneDB = dsloane </> "sloane.db"
Sloane/DB.hs view
@@ -7,10 +7,10 @@ module Sloane.DB ( DB , Reply- , initDB- , readDB- , writeDB- , putDB+ , update+ , read+ , write+ , put , null , insert , lookup@@ -20,21 +20,22 @@ , parseOEISEntries ) where -import Prelude hiding (lookup, null, take)+import Prelude hiding (lookup, null, take, read) import qualified Prelude as P import Data.List (intersect) import Data.ByteString (ByteString)+import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL-import Data.Map (Map, (!))-import qualified Data.Map as M-import Data.Serialize+import Data.Map.Strict (Map, (!))+import qualified Data.Map.Strict as M+import Data.Serialize hiding (put) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as IO import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Control.Monad (forM_, unless)-import qualified Codec.Compression.GZip as GZip-import Network.Curl.Download.Lazy (openLazyURI)+import qualified Codec.Compression.GZip as GZ+import Network.Curl.Download (openURI) import System.Console.ANSI import Sloane.Config import System.Directory@@ -47,50 +48,50 @@ type DB = Map ANumber Reply type DBRaw = Map ByteString (Map Char ByteString) -encodeDB :: DB -> DBRaw-encodeDB = M.mapKeys encodeUtf8 . M.map (M.map encodeUtf8)+encodeUtf8' :: DB -> DBRaw+encodeUtf8'= M.mapKeys encodeUtf8 . M.map (M.map encodeUtf8) -decodeDB :: DBRaw -> DB-decodeDB = M.mapKeys decodeUtf8 . M.map (M.map decodeUtf8)+decodeUtf8' :: DBRaw -> DB+decodeUtf8' = M.mapKeys decodeUtf8 . M.map (M.map decodeUtf8) -compress :: ByteString -> BL.ByteString-compress = GZip.compress . BL.fromStrict+gzCompress :: ByteString -> ByteString+gzCompress = BL.toStrict . GZ.compress . BL.fromStrict -compressDB :: DB -> BL.ByteString-compressDB = compress . encode . encodeDB+compressDB :: DB -> ByteString+compressDB = gzCompress . encode . encodeUtf8' -decompress :: BL.ByteString -> ByteString-decompress = BL.toStrict . GZip.decompress+gzDecompress :: ByteString -> ByteString+gzDecompress = BL.toStrict . GZ.decompress . BL.fromStrict -decompressDB :: BL.ByteString -> Either String DB-decompressDB = fmap decodeDB . decode . decompress+decompressDB :: ByteString -> Either String DB+decompressDB = fmap decodeUtf8' . decode . gzDecompress -initDB :: Config -> IO ()-initDB cfg = do+update :: Config -> IO ()+update cfg = do createDirectoryIfMissing False (sloaneDir cfg) putStrLn $ "Downloading " ++ sURL cfg- dbS <- openLazyURI (sURL cfg) >>= either error (return . mkDB 'S')+ dbS <- openURI (sURL cfg) >>= either error (return . mkDB 'S') putStrLn $ "Downloading " ++ nURL cfg- dbN <- openLazyURI (nURL cfg) >>= either error (return . mkDB 'N')+ dbN <- openURI (nURL cfg) >>= either error (return . mkDB 'N') putStrLn "Building database"- writeDB cfg $ unionDB dbS dbN+ write cfg $ unionDB dbS dbN putStrLn "Done." where unionDB = M.unionWith M.union- mkDB key = mkMap key . decodeUtf8 . decompress+ mkDB key = mkMap key . decodeUtf8 . gzDecompress mkMap key = M.fromList . map (aNumberAndReply key) . drop 4 . T.lines mkReply key = M.singleton key . T.dropWhile (==',') . T.drop 8 aNumberAndReply key line = (T.take 7 line, mkReply key line) -readDB :: Config -> IO DB-readDB cfg = doesFileExist (sloaneDB cfg) >>= \updated ->+read :: Config -> IO DB+read cfg = doesFileExist (sloaneDB cfg) >>= \updated -> if updated- then BL.readFile (sloaneDB cfg) >>= either error return . decompressDB+ then B.readFile (sloaneDB cfg) >>= either error return . decompressDB else error $ "No local database found. " ++- "You need to run \"sloane update\" first."+ "You need to run \"sloane --update\" first." -writeDB :: Config -> DB -> IO ()-writeDB cfg = BL.writeFile (sloaneDB cfg) . compressDB+write :: Config -> DB -> IO ()+write cfg = B.writeFile (sloaneDB cfg) . compressDB null :: DB -> Bool null = M.null@@ -102,7 +103,10 @@ lookup = M.lookup grep :: Text -> DB -> DB-grep q = M.filter $ \reply -> q `T.isInfixOf` (reply ! 'S')+grep q = M.filter $ \reply ->+ case M.lookup 'S' reply of+ Nothing -> False+ Just r -> q `T.isInfixOf` r take :: Int -> DB -> DB take n = M.fromList . P.take n . M.toList@@ -125,8 +129,8 @@ parseWords (key:aNum:rest) = singleton aNum (T.head key) (T.unwords rest) parseWords _ = M.empty -putDB :: Config -> [Key] -> DB -> IO ()-putDB cfg keys db = do+put :: Config -> [Key] -> DB -> IO ()+put cfg keys db = do unless (null db) $ putStrLn "" forM_ (M.toList db) $ \(aNum, reply) -> do forM_ (keys `intersect` M.keys reply) $ \key -> do
+ Sloane/GF.hs view
@@ -0,0 +1,162 @@+-- |+-- Copyright : Anders Claesson 2012-2015+-- Maintainer : Anders Claesson <anders.claesson@gmail.com>+-- License : BSD-3+--+module Sloane.GF+ ( Series (..)+ , GF+ , imap+ , diff+ , squareRoot+ , (^^^)+ , o+ , ogf+ , egf+ , ogfCoeffs+ , egfCoeffs+ )where++import Data.List+import Data.Maybe+import Data.Ratio++-- Reference: M. D. McIlroy, The music of streams,+-- Information Processing Letters 77 (2001) 189-195.++newtype Series a = Series { coeffs :: [a] } deriving (Show, Eq)++type GF = Series Rational++instance Functor Series where+ fmap f = Series . fmap f . coeffs++instance (Eq a, Num a) => Num (Series a) where+ (+) = lift2 add+ (*) = lift2 mul+ fromInteger c = Series [fromInteger c]+ negate = fmap negate+ signum = undefined+ abs = undefined++instance (Eq a, Fractional a) => Fractional (Series a) where+ fromRational c = Series [fromRational c]+ (/) = lift2 divide++degree :: (Num a, Eq a) => Series a -> Int+degree (Series [0]) = -1+degree (Series cs) = length cs - 1+-- degree (Series cs) = length (dropWhile (==0) (reverse cs)) - 1++o :: (Eq a, Fractional a) => Series a -> Series a -> Series a+o = lift2 compose++lift :: ([a] -> [b]) -> Series a -> Series b+lift f (Series as) = Series (f as)++lift2 :: ([a] -> [b] -> [c]) -> Series a -> Series b -> Series c+lift2 op (Series as) (Series bs) = Series (as `op` bs)++add :: Num a => [a] -> [a] -> [a]+add [] ds = ds+add cs [] = cs+add (c:cs) (d:ds) = c+d : add cs ds++sub :: Num a => [a] -> [a] -> [a]+cs `sub` ds = cs `add` map negate ds++(!*!) :: (Eq a, Num a) => a -> a -> a+(!*!) _ 0 = 0+(!*!) 0 _ = 0+(!*!) a b = a*b++mul :: (Eq a, Num a) => [a] -> [a] -> [a]+mul (c:ct) ds@(d:dt) = c!*!d : map (c !*!) dt `add` (ct `mul` ds)+mul _ _ = []++divide :: (Eq a, Fractional a) => [a] -> [a] -> [a]+divide [] (0:_) = undefined+divide [] _ = []+divide (0:ct) (0:dt) = ct `divide` dt+divide (c:ct) ds@(d:dt) = q : (ct `sub` ([q] `mul` dt)) `divide` ds where q = c/d+divide _ [] = undefined++diff :: GF -> GF+diff = lift dF where {dF [] = []; dF (_:ct) = zipWith (*) [1..] ct}++nthRootApprox :: Integer -> GF -> [GF]+nthRootApprox n f@(Series (1:_)) =+ iterateUntilFixed (nthRootNext n f) (Series [1])++nthRootNext :: Integer -> GF -> GF -> GF+nthRootNext n f g = Series (take (1 + 2*degree f) ds)+ where+ Series ds = (Series [n%1-1] * g^n + f) / (Series [n%1] * g^(n-1))++iterateUntilFixed :: Eq a => (a -> a) -> a -> [a]+iterateUntilFixed f x = x : (if x == y then [] else ys)+ where+ ys@(y:_) = iterateUntilFixed f (f x)++saddlePoint :: Eq a => [a] -> Maybe a+saddlePoint [c] = Just c+saddlePoint cs = listToMaybe [ c | (c,d) <- zip cs (drop 1 cs), c == d ]++nthRoot1 :: Integer -> GF -> GF+nthRoot1 n f@(Series (1:_)) =+ let css = transpose (map coeffs (nthRootApprox n f))+ in Series $ map fromJust (takeWhile isJust (map saddlePoint css))++nthRoot :: Integer -> GF -> GF+nthRoot _ (Series []) = Series []+nthRoot n (Series cs@(c:_)) = Series [d] * nthRoot1 n (Series $ map (/c) cs)+ where+ d = toRational (fromRational c ** fromRational (1%n) :: Double)++(^^^) :: GF -> Rational -> GF+(^^^) f r = case (numerator r, denominator r) of+ (n, 1) -> f ^^ n+ (0, _) -> ogf [1]+ (n, k) -> nthRoot k f ^^ n++-- XXX: Seems correct, but isn't producing anything+-- nthRoot :: Integer -> GF -> GF+-- nthRoot n f = sum [ (f-1)^k * fromRational (bin n k) | k<-[0..]]+-- where+-- -- bin n k = {1/n choose k}+-- bin n k = product [(1-i*n+n) % i | i<-[1..k] ] / (n%1)^k++squareRoot :: GF -> GF+squareRoot = Series . map toRational . squareRoot' . map fromRational . coeffs++squareRoot' :: [Double] -> [Double]+squareRoot' [] = []+squareRoot' (c:ct) = ds+ where+ ds = d : ct `divide` ([d] `add` ds)+ d = sqrt c++compose :: (Eq a, Fractional a) => [a] -> [a] -> [a]+[] `compose` _ = []+(c:_) `compose` [] = [c]+(c:ct) `compose` ds@(0:dt) = c : dt `mul` (ct `compose` ds)+(c:ct) `compose` ds = [c] `add` ds `mul` (ct `compose` ds)+ -- ct must be finite++imap :: (Integer -> a -> b) -> Series a -> Series b+imap f = Series . zipWith f [0..] . coeffs++factorial :: Integer -> Integer+factorial n = product [1..n]++ogf :: Real a => [a] -> GF+ogf = Series . map toRational++ogfCoeffs :: GF -> [Rational]+ogfCoeffs = coeffs++egf :: Real a => [a] -> GF+egf = Series . zipWith (\n c -> toRational c * (1 % factorial n)) [0..]++egfCoeffs :: GF -> [Rational]+egfCoeffs = ogfCoeffs . imap (\i -> (* (factorial i % 1)))
+ Sloane/Transform.hs view
@@ -0,0 +1,262 @@+-- |+-- Copyright : Anders Claesson 2012-2015+-- Maintainer : Anders Claesson <anders.claesson@gmail.com>+-- License : BSD-3+--+module Sloane.Transform+ ( NamedTransform (..)+ , ($$)+ , tLEFT+ , tRIGHT+ , tM2+ , tM2i+ , tBINOMIAL+ , tBINOMIALi+ , tBIN1+ , tBISECT0+ , tBISECT1+ , tCONV+ , tCONVi+ , tEXPCONV+ , tDIFF+ , tEULER+ , tEXP+ , tLOG+ , tNEGATE+ , tPRODS+ , tPSUM+ , tPSUMSIGN+ , tSTIRLING+ , tTRISECT0+ , tTRISECT1+ , tTRISECT2+ , tPOINT+ , tWEIGHT+ , tPARTITION+ , transforms+ , lookupTranform+ , applyAllTransforms+ ) where++import Data.List+import Data.Ratio+import Data.Monoid+import Control.Monad+import Sloane.GF++type Transform = [Rational] -> [Rational]++data NamedTransform = NT+ { name :: String+ , eval :: Transform+ }++instance Show NamedTransform where+ show = ('t':) . name++instance Eq NamedTransform where+ t == s = name t == name s++instance Monoid NamedTransform where+ mempty = NT "" id+ mappend f g = NT tname teval+ where+ tname = name f ++ "." ++ name g+ teval = eval f . eval g++infixr 0 $$++isInteger :: Rational -> Bool+isInteger = (1==) . denominator++toIntSeq :: [Rational] -> [Integer]+toIntSeq cs = [ numerator c | c <- cs, all isInteger cs ]++($$) :: NamedTransform -> [Integer] -> [Integer]+f $$ cs = toIntSeq $ take (length cs) (eval f (map toRational cs))++x :: GF+x = ogf [0::Integer, 1]++-- addSeq :: [Integer] -> Transform+-- addSeq seq0 = zipWith (+) seq0++-- mulOGF :: [Integer] -> Transform+-- mulOGF seq0 = \cs -> ogfCoeffs (ogf seq0 * ogf cs)++-- mulEGF :: [Integer] -> Transform+-- mulEGF seq0 = \cs -> egfCoeffs (egf seq0 * egf cs)++geoSeries :: Rational -> GF+geoSeries c = ogf [ c^k | k<-[0::Int ..] ]++expSeries :: Rational -> GF+expSeries c = egf [ c^k | k<-[0::Int ..] ]++bisect0 :: [a] -> [a]+bisect0 [] = []+bisect0 (c:cs) = c : bisect1 cs++bisect1 :: [a] -> [a]+bisect1 [] = []+bisect1 (_:cs) = bisect0 cs++trisect0 :: [a] -> [a]+trisect0 [] = []+trisect0 (c:cs) = c : trisect2 cs++trisect1 :: [a] -> [a]+trisect1 [] = []+trisect1 (_:cs) = trisect0 cs++trisect2 :: [a] -> [a]+trisect2 [] = []+trisect2 (_:cs) = trisect1 cs++signed :: GF -> GF+signed = imap $ \i c -> (-1%1)^i * c++tLEFT :: NamedTransform+tLEFT = NT "LEFT" (drop 1)++tRIGHT :: NamedTransform+tRIGHT = NT "RIGHT" (1:)++tM2 :: NamedTransform+tM2 = NT "M2" f where f [] = []; f (c:cs) = c : map ((2%1)*) cs++tM2i :: NamedTransform+tM2i = NT "M2i" (\cs -> ogfCoeffs (Series (f cs)))+ where+ f [] = []+ f cs = let (d:ds) = map toRational cs in d : map (/(2%1)) ds++tBINOMIAL :: NamedTransform+tBINOMIAL = NT "BINOMIAL" (\cs -> egfCoeffs (expSeries (1%1) * egf cs))++tBINOMIALi :: NamedTransform+tBINOMIALi = NT "BINOMIALi" (\cs -> egfCoeffs (expSeries ((-1)%1) * egf cs))++tBIN1 :: NamedTransform+tBIN1 = NT "BIN1" (\cs ->+ drop 1 . egfCoeffs $ -expSeries (-1%1) * signed (egf (0:cs)))++tBISECT0 :: NamedTransform+tBISECT0 = NT "BISECT0" bisect0++tBISECT1 :: NamedTransform+tBISECT1 = NT "BISECT1" bisect1++tCONV :: NamedTransform+tCONV = NT "CONV" (\cs -> ogfCoeffs (ogf cs ^ (2::Int)))++tCONVi :: NamedTransform+tCONVi = NT "CONVi" (\cs -> ogfCoeffs (squareRoot (ogf cs)))++tEXPCONV :: NamedTransform+tEXPCONV = NT "EXPCONV" (\cs -> egfCoeffs (egf cs ^ (2::Int)))++tDIFF :: NamedTransform+tDIFF = NT "DIFF" (\cs -> zipWith (-) (drop 1 cs) cs)++tEULER :: NamedTransform+tEULER = NT "EULER" (\cs ->+ let f = product $ zipWith (\n c -> (1 - x^n)^^^c) [1::Int ..] cs+ in drop 1 $ ogfCoeffs (1/f))++tEULERi :: NamedTransform+tEULERi = NT "EULERi" undefined++-- EXP converts [a_1, a_2, ...] to [b_1, b_2,...] where+-- 1 + EGF_B (x) = exp EGF_A (x)+tEXP :: NamedTransform+tEXP = NT "EXP" (\cs -> drop 1 . egfCoeffs $ expSeries 1 `o` egf (0:cs))++-- LOG converts [a_1, a_2, ...] to [b_1, b_2,...] where+-- 1 + EGF_A (x) = exp EGF_B (x) i.e. EGF_B (x) = log(1 + EGF_A (x)).+tLOG :: NamedTransform+tLOG = NT "LOG" (\cs -> drop 1 $ egfCoeffs (log1 `o` (-1 * egf (0:cs))))+ where+ log1 = Series (0 : [-1 % n | n <- [1..]])++tNEGATE :: NamedTransform+tNEGATE = NT "NEGATE" f where f [] = []; f (c:cs) = c : map negate cs++tPRODS :: NamedTransform+tPRODS = NT "PRODS" (drop 1 . scanl (*) (1%1))++tPSUM :: NamedTransform+tPSUM = NT "PSUM" (drop 1 . scanl (+) (0%1))++tPSUMSIGN :: NamedTransform+tPSUMSIGN = NT "PSUMSIGN" (ogfCoeffs . (geoSeries (-1%1) *) . ogf)++tSTIRLING :: NamedTransform+tSTIRLING = NT "STIRLING" (\cs ->+ drop 1 . egfCoeffs $ egf (0:cs) `o` (expSeries (1%1) - 1))++tTRISECT0 :: NamedTransform+tTRISECT0 = NT "TRISECT0" trisect0++tTRISECT1 :: NamedTransform+tTRISECT1 = NT "TRISECT1" trisect1++tTRISECT2 :: NamedTransform+tTRISECT2 = NT "TRISECT2" trisect2++tPOINT :: NamedTransform+tPOINT = NT "POINT" (zipWith (*) [0..])++tWEIGHT :: NamedTransform+tWEIGHT = NT "WEIGHT" $+ drop 1 . ogfCoeffs . product . zipWith (\n c -> (1 + x^n)^^^c) [1::Int ..]++increasing :: Ord a => [a] -> Bool+increasing cs = and $ zipWith (<=) cs (drop 1 cs)++tPARTITION :: NamedTransform+tPARTITION = NT "PARTITION" $ \cs -> do+ guard $ not (null cs) && all (>0) cs && increasing cs+ let f = product $ map (\c -> (1 - x^^^c)) (nub cs)+ drop 1 . ogfCoeffs $ 1/f++-- New transform: tSTIELTJES -- continued fraction coefficients+++transforms :: [NamedTransform]+transforms =+ [ tLEFT+ , tRIGHT+ , tM2+ , tM2i+ , tBINOMIAL+ , tBINOMIALi+ , tBIN1+ , tBISECT0+ , tBISECT1+ , tCONV+ , tCONVi+ , tEXPCONV+ , tDIFF+ , tEULER+-- , tEULERi+ , tEXP+ , tLOG+ , tNEGATE+ , tPRODS+ , tPSUM+ , tPSUMSIGN+ , tSTIRLING+ , tTRISECT0+ , tTRISECT1+ , tTRISECT2+ , tPOINT+ , tWEIGHT+ , tPARTITION+ ]++lookupTranform :: String -> Maybe NamedTransform+lookupTranform tname = lookup tname [ (name f, f) | f <- transforms ]++applyAllTransforms :: [Rational] -> [(NamedTransform, [Rational])]+applyAllTransforms cs = [ (f, eval f cs) | f <- transforms ]
sloane.1 view
@@ -1,68 +1,51 @@-.TH "SLOANE" "1" "24 Sep 2014" "Sloane User Manual" "Version 1.9.3"+.TH "SLOANE" "1" "2 Jan 2015" "Sloane User Manual" "Version 2.0.0" .SH NAME .PP sloane \- a command line interface to Sloane\[aq]s On\-Line Encyclopedia of Integer Sequences <http://oeis.org> .SH SYNOPSIS .PP-sloane [lookup | grep] [\-a | \-\-all | \-k \f[I]keys\f[] | \-\-url]-[\-n \f[I]entries\f[]] \f[I]terms\f[] ...-.PD 0-.P-.PD-sloane filter [\-\-invert]-.PD 0-.P-.PD-sloane update-.PD 0-.P-.PD-sloane version+sloane [\-a|\-\-all] [\-k KEYS] [\-n N] [\-\-url] [\-\-local] [TERMS...]+.PP+sloane [\-\-filter] [\-\-invert]+.PP+sloane [\-\-transform NAME] [\-\-list\-transforms]+.PP+sloane [\-\-update] [\-\-version] [\-\-help] .SH DESCRIPTION .PP-The \f[C]sloane\ lookup\f[] command searches Sloane\[aq]s On\-Line-Encyclopedia of Integer Sequences (OEIS).+The \f[C]sloane\f[] command searches Sloane\[aq]s On\-Line Encyclopedia+of Integer Sequences (OEIS). The search terms are typically the leading term of a sequence. For example, .IP .nf \f[C]-sloane\ lookup\ 1,1,2,5,15,52,203,877,4140-\f[]-.fi-.PP-returns entry A000110 (Bell numbers), and four more entries.-If no command is given sloane will fall back to the lookup command, so-the above query can more simply be given as-.IP-.nf-\f[C] sloane\ 1,1,2,5,15,52,203,877,4140 \f[] .fi .PP+returns entry A000110 (Bell numbers), and four more entries. One can also search by sequence id (A\-number), or even search for arbitrary words. See the \f[B]EXAMPLES\f[] section. .PP-Alternatively, using the \f[C]sloane\ grep\f[] command, the search can-be done locally against a downloaded list of known sequences.+Alternatively, using the \f[C]\-\-local\f[] option, the search can be+done locally against a downloaded local database of known sequences. This mode works by "grepping" for the query in the sequence field. .PP-To check a large number of sequences one can use the-\f[C]sloane\ filter\f[] command.-It reads the standard input line\-by\-line, if the sequence read is in-the local database, then it is returned to the standard output; if not,-it is ignored.-This way onw can quickly filter out the sequences from the input that+To check a large number of sequences one can use \f[C]\-\-filter\f[].+When this option is set, \f[C]sloane\f[] reads the standard input+line\-by\-line, if the sequence read is in the local database, then it+is returned to the standard output; if not, it is ignored.+This way one can quickly filter out the sequences from the input that are in the local database. In other words, assuming that \f[I]FILE\f[] contains one sequence per line, .IP .nf \f[C]-sloane\ filter\ <FILE+sloane\ \-\-filter\ <FILE \f[] .fi .PP@@ -73,7 +56,7 @@ .IP .nf \f[C]-sloane\ filter\ <FILE\ |\ xargs\ \-L1\ \-\-verbose\ sloane\ grep+sloane\ \-\-filter\ <FILE\ |\ xargs\ \-L1\ \-\-verbose\ sloane\ \-\-local \f[] .fi .PP@@ -82,58 +65,76 @@ .IP .nf \f[C]-sloane\ lookup\ \-a\ id:A000110\ |\ less\ \-R+sloane\ \-a\ id:A000110\ |\ less\ \-R \f[] .fi .SH OPTIONS .TP-.B \-\-help-Display a short help message-.RS-.RE-.SH COMMANDS-.SS \f[C]lookup\f[]-.PP-Lookup a sequence, or other search term, in OEIS-.TP .B \-a, \-\-all Print all fields .RS .RE .TP-.B \-k \f[I]keys\f[]+.B \-k \f[I]KEYS\f[] Keys of fields to print (default: SN) .RS .RE .TP+.B \-n \f[I]N\f[]+Fetch at most this many entries (default: 5)+.RS+.RE+.TP .B \-\-url Print URLs of found entries (but nothing else) .RS .RE .TP-.B \-n \f[I]entries\f[]-Fetch at most this many entries (default: 5)+.B \-\-local+Grep for a sequence in the local database. .RS .RE-.SS \f[C]grep\f[]-.PP-Grep for a sequence in the local database.-Same options as for the \f[C]lookup\f[] command apply.-.SS \f[C]filter\f[]-.PP+.TP+.B \-\-filter Read sequences from stdin and return those that are in the local database.+.RS+.RE .TP .B \-\-invert Return sequences \f[I]not\f[] in the database.+This option has no effect unless \f[C]\-\-filter\f[] is also set. .RS .RE-.SS \f[C]update\f[]-.PP+.TP+.B \-\-transform \f[I]NAME\f[]+Apply the named transform to the input sequence.+If the resulting sequence is integral print it to stdout; else print+nothing.+Most of the transforms and their names are taken from+<https://oeis.org/transforms.txt>.+.RS+.RE+.TP+.B \-\-list\-transforms+List the names of all transforms.+.RS+.RE+.TP+.B \-\-update Update the local database.-.SS \f[C]version\f[]-.PP+.RS+.RE+.TP+.B \-\-version Print version information.+.RS+.RE+.TP+.B \-\-help+Briefly describe the available options.+.RS+.RE .SH EXAMPLES .PP The most common search is for entries matching a sequence of consecutive@@ -141,7 +142,7 @@ .IP .nf \f[C]-sloane\ lookup\ 1,3,19,183,2371,38703+sloane\ 1,3,19,183,2371,38703 \f[] .fi .PP@@ -162,7 +163,7 @@ .IP .nf \f[C]-sloane\ lookup\ \-k\ SNCF\ id:A006531+sloane\ \-k\ SNCF\ id:A006531 \f[] .fi .PP@@ -170,7 +171,7 @@ .IP .nf \f[C]-sloane\ lookup\ \-n\ 3\ "(2+2)\-free\ posets"+sloane\ \-n\ 3\ "(2+2)\-free\ posets" \f[] .fi .PP@@ -179,7 +180,7 @@ .IP .nf \f[C]-firefox\ `sloane\ lookup\ \-\-url\ \-n\ 3\ "(2+2)\-free\ posets"`+firefox\ `sloane\ \-\-url\ \-n\ 3\ "(2+2)\-free\ posets"` \f[] .fi .PP@@ -188,7 +189,7 @@ .IP .nf \f[C]-sloane\ filter\ <<END+sloane\ \-\-filter\ <<END 1,2,3,6,11,23,47,106,235\ \ \ \ \ \ \ \ \ \ \ #\ Comma\ separated\ integers 1\ 2\ 444\ 90\ 120\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Space\ separated\ integers \[aq](3\ 9\ 27\ 88\ 123)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ S\-expression
sloane.cabal view
@@ -1,5 +1,5 @@ Name: sloane-Version: 1.9.3+Version: 2.0.0 Synopsis: A command line interface to Sloane's On-Line Encyclopedia of Integer Sequences Description: A command line interface to Sloane's On-Line Encyclopedia@@ -22,8 +22,10 @@ Executable sloane Main-is: sloane.hs- Other-Modules: Sloane.Config- Sloane.DB+ Other-Modules: Sloane.DB+ Sloane.Config+ Sloane.GF+ Sloane.Transform ghc-options: -Wall Build-depends: base >=3 && <5, containers >=0.5,
sloane.hs view
@@ -3,6 +3,9 @@ -- Maintainer : Anders Claesson <anders.claesson@gmail.com> -- License : BSD-3 --++import Data.List (intercalate)+import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as IO@@ -11,143 +14,148 @@ import Network.Curl.Download (openURI) import Options.Applicative import Sloane.Config-import Sloane.DB hiding (null)+import Sloane.DB (DB) import qualified Sloane.DB as DB+import Sloane.Transform type URL = String type Seq = Text -data Visibility = Visible | Internal--data Options- = Cmd Command- | IntOpts SearchOpts -- Internal opts for fallback to lookup--data Command- = Lookup SearchOpts- | Grep SearchOpts- | Filter FilterOpts- | Update- | Version--data SearchOpts = SearchOpts- { full :: Bool -- Print all fields?- , keys :: String -- Keys of fields to print- , limit :: Int -- Fetch at most this many entries- , url :: Bool -- Print URLs of found entries- , terms :: [String] -- Search terms- }--data FilterOpts = FilterOpts- { invert :: Bool -- Return sequences NOT in DB+data Options = Options+ { full :: Bool -- Print all fields?+ , keys :: String -- Keys of fields to print+ , limit :: Int -- Fetch at most this many entries+ , url :: Bool -- Print URLs of found entries+ , local :: Bool -- Lookup in local DB+ , filtr :: Bool -- Filter out sequences in local DB+ , invert :: Bool -- Return sequences NOT in DB+ , transform :: String -- Apply the named transform+ , listTransforms :: Bool -- List the names of all transforms+ , update :: Bool -- Updated local DB+ , version :: Bool -- Show version info+ , terms :: [String] -- Search terms } oeisKeys :: String oeisKeys = "ISTUVWXNDHFYAOEeptoKC" -- Valid OEIS keys oeisUrls :: Config -> DB -> [URL]-oeisUrls cfg = map ((oeisHost cfg ++) . T.unpack) . aNumbers+oeisUrls cfg = map ((oeisHost cfg ++) . T.unpack) . DB.aNumbers -oeisLookup :: SearchOpts -> Config -> IO DB+oeisLookup :: Options -> Config -> IO DB oeisLookup opts cfg =- (parseOEISEntries . decodeUtf8 . either error id) <$>+ (DB.parseOEISEntries . decodeUtf8 . either error id) <$> openURI (oeisURL cfg ++ "&" ++ urlEncodeVars [("n", show n), ("q", q)]) where n = limit opts q = unwords $ terms opts -grepDB :: SearchOpts -> DB -> DB+grepDB :: Options -> DB -> DB grepDB opts = DB.take n . DB.grep (T.pack q) where n = limit opts- q = unwords $ terms opts+ q = intercalate "," (terms opts) -filterDB :: FilterOpts -> DB -> IO [Seq]+applyTransform :: Options -> String -> IO ()+applyTransform opts tname =+ case lookupTranform tname of+ Nothing -> error "No transform with that name"+ Just f -> case f $$ input of+ [] -> return ()+ cs -> putStrLn (showSeq cs)+ where+ tr c = if c `elem` ";," then ' ' else c+ input = map read (words (map tr (unwords (terms opts))))+++dropComment :: Text -> Text+dropComment = T.takeWhile (/= '#')++showSeq :: [Integer] -> String+showSeq = intercalate "," . map show++mkSeq :: Text -> Seq+mkSeq = T.intercalate (T.pack ",") . T.words . clean . dropComment+ where+ clean = T.filter (`elem` " 0123456789-") . T.map tr+ tr c = if c `elem` ";," then ' ' else c++filterDB :: Options -> DB -> IO [Seq] filterDB opts db = filter match . parseSeqs <$> IO.getContents where match q = (if invert opts then id else not) (DB.null $ DB.grep q db) parseSeqs = filter (not . T.null) . map mkSeq . T.lines- mkSeq = normalize . dropComment- dropComment = T.takeWhile (/= '#')- normalize = T.intercalate (T.pack ",") . T.words . clean . T.map tr- tr c = if c `elem` ";," then ' ' else c- clean = T.filter (`elem` " 0123456789-") -searchOptionsParser :: Visibility -> Parser SearchOpts-searchOptionsParser visibility = hiddenHelp <*> (SearchOpts+hiddenHelp :: Parser (a -> a)+hiddenHelp = abortOption ShowHelpText $ hidden <> long "help"++optionsParser :: Parser Options+optionsParser = hiddenHelp <*> (Options <$> switch ( short 'a' <> long "all"- <> help "Print all fields"- <> f )+ <> help "Print all fields" ) <*> strOption ( short 'k' <> metavar "KEYS" <> value "SN"- <> help "Keys of fields to print [default: SN]"- <> f )+ <> help "Keys of fields to print [default: SN]" ) <*> option auto ( short 'n' <> metavar "N" <> value 5- <> help "Fetch at most this many entries [default: 5]"- <> f )+ <> help "Fetch at most this many entries [default: 5]" ) <*> switch ( long "url"- <> help "Print URLs of found entries"- <> f )- <*> some (argument str (metavar "TERMS...")))- where- f = case visibility of {Visible -> idm; Internal -> internal}--filterOptionsParser :: Parser FilterOpts-filterOptionsParser = FilterOpts- <$> switch (long "invert" <> help "Return sequences NOT in the database")--commandParser :: Parser Command-commandParser = subparser- ( command "lookup" (info (Lookup <$> searchOptionsParser Visible)- ( progDesc "Lookup a sequence, or other search term, in OEIS" ))- <> command "grep" (info (Grep <$> searchOptionsParser Visible)- ( progDesc "Grep for a sequence in the local database" ))- <> command "filter" (info (Filter <$> filterOptionsParser)- ( progDesc ("Read sequences from stdin and "- ++ "return those that are in the local database")))- <> command "update" (info (pure Update)- ( progDesc "Update the local database" ))- <> command "version" (info (pure Version)- ( progDesc "Show version info" ))- )--optionsParser :: Parser Options-optionsParser =- (Cmd <$> commandParser) <|> (IntOpts <$> searchOptionsParser Internal)--runSearch :: (SearchOpts -> Config -> IO DB) -> SearchOpts -> Config -> IO ()-runSearch f opts cfg = f opts cfg >>=- if url opts- then putStr . unlines . oeisUrls cfg- else putDB cfg (if full opts then oeisKeys else keys opts)--runCmd :: Command -> Config -> IO ()-runCmd (Lookup opts) = runSearch oeisLookup opts-runCmd (Grep opts) = runSearch (\o cfg -> grepDB o <$> readDB cfg) opts-runCmd (Filter opts) = \c -> readDB c >>= filterDB opts >>= mapM_ IO.putStrLn-runCmd Update = initDB-runCmd Version = putStrLn . name+ <> help "Print URLs of found entries" )+ <*> switch+ ( long "local"+ <> help "Use the local database rather than oeis.org" )+ <*> switch+ ( long "filter"+ <> help ("Read sequences from stdin and return"+ ++ " those that are in the local database") )+ <*> switch+ ( long "invert"+ <> help ("Return sequences NOT in the database;"+ ++ " only relevant when used with --filter") )+ <*> strOption+ ( long "transform"+ <> metavar "NAME"+ <> value ""+ <> help ("Apply the named transform to input sequence"))+ <*> switch+ ( long "list-transforms"+ <> help "List the names of all transforms" )+ <*> switch+ ( long "update"+ <> help "Update the local database" )+ <*> switch+ ( long "version"+ <> help "Show version info" )+ <*> many (argument str (metavar "TERMS..."))) -hiddenHelp :: Parser (a -> a)-hiddenHelp = abortOption ShowHelpText $ hidden <> short 'h' <> long "help"+search :: (Options -> Config -> IO DB) -> Options -> Config -> IO ()+search f opts cfg = f opts cfg >>= put+ where+ put | url opts = putStr . unlines . oeisUrls cfg+ | otherwise = DB.put cfg $ if full opts then oeisKeys else keys opts main :: IO () main = do- conf <- defaultConfig- opts <- customExecParser preferences (info parser description)- case opts of- (Cmd cmd) -> runCmd cmd conf- (IntOpts o) -> runCmd (Lookup o) conf -- Fallback to 'lookup'- where- parser = hiddenHelp <*> optionsParser- preferences = prefs showHelpOnError- description = fullDesc <> footer- "Run 'sloane COMMAND --help' for help on a specific command."+ let pprefs = prefs mempty+ let pinfo = info optionsParser fullDesc+ let usage = handleParseResult . Failure+ $ parserFailure pprefs pinfo ShowHelpText mempty+ opts <- customExecParser pprefs pinfo+ let tname = transform opts+ let sloane+ | version opts = putStrLn . nameVer+ | update opts = DB.update+ | listTransforms opts = const $ mapM_ (putStrLn . name) transforms+ | filtr opts = \c -> DB.read c >>= filterDB opts >>= mapM_ IO.putStrLn+ | null (terms opts) = const usage+ | not (null tname) = const $ applyTransform opts tname+ | local opts = search (\o cfg -> grepDB o <$> DB.read cfg) opts+ | otherwise = search oeisLookup opts+ defaultConfig >>= sloane