RosettaCodeData/Task/Stern-Brocot-sequence/Python/stern-brocot-sequence-3.py

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()