192 lines
4.2 KiB
Python
192 lines
4.2 KiB
Python
'''Stern-Brocot sequence'''
|
|
|
|
from itertools import (count, dropwhile, islice, takewhile)
|
|
import operator
|
|
import math
|
|
|
|
|
|
# sternBrocot :: Generator [Int]
|
|
def sternBrocot():
|
|
'''Non-finite list of the Stern-Brocot
|
|
sequence of integers.'''
|
|
|
|
def go(xs):
|
|
x = xs[1]
|
|
return (tail(xs) + [x + head(xs), x])
|
|
return fmapGen(head)(
|
|
iterate(go)([1, 1])
|
|
)
|
|
|
|
|
|
# TESTS ---------------------------------------------------
|
|
|
|
# main :: IO ()
|
|
def main():
|
|
'''Various tests'''
|
|
|
|
[eq, ne, gcd] = map(
|
|
curry,
|
|
[operator.eq, operator.ne, math.gcd]
|
|
)
|
|
|
|
sbs = take(1200)(sternBrocot())
|
|
ixSB = zip(sbs, enumFrom(1))
|
|
|
|
print(unlines(map(str, [
|
|
|
|
# First 15 members of the sequence.
|
|
take(15)(sbs),
|
|
|
|
# Indices of where the numbers [1..10] first appear.
|
|
take(10)(
|
|
nubBy(on(eq)(fst))(
|
|
sorted(
|
|
takewhile(
|
|
compose(ne(12))(fst),
|
|
ixSB
|
|
),
|
|
key=fst
|
|
)
|
|
)
|
|
),
|
|
|
|
# Index of where the number 100 first appears.
|
|
take(1)(dropwhile(compose(ne(100))(fst), ixSB)),
|
|
|
|
# Is the gcd of any two consecutive members,
|
|
# up to the 1000th member, always one ?
|
|
every(compose(eq(1)(gcd)))(
|
|
take(1000)(zip(sbs, tail(sbs)))
|
|
)
|
|
])))
|
|
|
|
|
|
# GENERIC ABSTRACTIONS ------------------------------------
|
|
|
|
|
|
# compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
|
|
def compose(g):
|
|
'''Right to left function composition.'''
|
|
return lambda f: lambda x: g(f(x))
|
|
|
|
|
|
# curry :: ((a, b) -> c) -> a -> b -> c
|
|
def curry(f):
|
|
'''A curried function derived
|
|
from an uncurried function.'''
|
|
return lambda a: lambda b: f(a, b)
|
|
|
|
|
|
# enumFrom :: Enum a => a -> [a]
|
|
def enumFrom(x):
|
|
'''A non-finite stream of enumerable values,
|
|
starting from the given value.'''
|
|
return count(x) if isinstance(x, int) else (
|
|
map(chr, count(ord(x)))
|
|
)
|
|
|
|
|
|
# every :: (a -> Bool) -> [a] -> Bool
|
|
def every(p):
|
|
'''True if p(x) holds for every x in xs'''
|
|
return lambda xs: all(map(p, xs))
|
|
|
|
|
|
# fmapGen <$> :: (a -> b) -> Gen [a] -> Gen [b]
|
|
def fmapGen(f):
|
|
'''A function f mapped over a
|
|
non finite stream of values.'''
|
|
def go(g):
|
|
while True:
|
|
v = next(g, None)
|
|
if None is not v:
|
|
yield f(v)
|
|
else:
|
|
return
|
|
return lambda gen: go(gen)
|
|
|
|
|
|
# fst :: (a, b) -> a
|
|
def fst(tpl):
|
|
'''First member of a pair.'''
|
|
return tpl[0]
|
|
|
|
|
|
# head :: [a] -> a
|
|
def head(xs):
|
|
'''The first element of a non-empty list.'''
|
|
return xs[0]
|
|
|
|
|
|
# iterate :: (a -> a) -> a -> Gen [a]
|
|
def iterate(f):
|
|
'''An infinite list of repeated
|
|
applications of f to x.'''
|
|
def go(x):
|
|
v = x
|
|
while True:
|
|
yield v
|
|
v = f(v)
|
|
return lambda x: go(x)
|
|
|
|
|
|
# nubBy :: (a -> a -> Bool) -> [a] -> [a]
|
|
def nubBy(p):
|
|
'''A sublist of xs from which all duplicates,
|
|
(as defined by the equality predicate p)
|
|
are excluded.'''
|
|
def go(xs):
|
|
if not xs:
|
|
return []
|
|
x = xs[0]
|
|
return [x] + go(
|
|
list(filter(
|
|
lambda y: not p(x)(y),
|
|
xs[1:]
|
|
))
|
|
)
|
|
return lambda xs: go(xs)
|
|
|
|
|
|
# on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
|
|
def on(f):
|
|
'''A function returning the value of applying
|
|
the binary f to g(a) g(b)'''
|
|
return lambda g: lambda a: lambda b: f(g(a))(g(b))
|
|
|
|
|
|
# tail :: [a] -> [a]
|
|
# tail :: Gen [a] -> [a]
|
|
def tail(xs):
|
|
'''The elements following the head of a
|
|
(non-empty) list or generator stream.'''
|
|
if isinstance(xs, list):
|
|
return xs[1:]
|
|
else:
|
|
list(islice(xs, 1)) # First item dropped.
|
|
return xs
|
|
|
|
|
|
# take :: Int -> [a] -> [a]
|
|
# take :: Int -> String -> String
|
|
def take(n):
|
|
'''The prefix of xs of length n,
|
|
or xs itself if n > length xs.'''
|
|
return lambda xs: (
|
|
xs[0:n]
|
|
if isinstance(xs, list)
|
|
else list(islice(xs, n))
|
|
)
|
|
|
|
|
|
# unlines :: [String] -> String
|
|
def unlines(xs):
|
|
'''A single string derived by the intercalation
|
|
of a list of strings with the newline character.'''
|
|
return '\n'.join(xs)
|
|
|
|
|
|
# MAIN ---
|
|
if __name__ == '__main__':
|
|
main()
|