packages feed

sloane 1.9.3 → 2.0.0

raw patch · 9 files changed

+699/−265 lines, 9 files

Files

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