148 lines
3.4 KiB
Python
148 lines
3.4 KiB
Python
'''Yellowstone permutation OEIS A098550'''
|
|
|
|
from itertools import chain, count, islice
|
|
from operator import itemgetter
|
|
from math import gcd
|
|
|
|
from matplotlib import pyplot
|
|
|
|
|
|
# yellowstone :: [Int]
|
|
def yellowstone():
|
|
'''A non-finite stream of terms from
|
|
the Yellowstone permutation.
|
|
OEIS A098550.
|
|
'''
|
|
# relativelyPrime :: Int -> Int -> Bool
|
|
def relativelyPrime(a):
|
|
return lambda b: 1 == gcd(a, b)
|
|
|
|
# nextWindow :: (Int, Int, [Int]) -> (Int, Int, [Int])
|
|
def nextWindow(triple):
|
|
p2, p1, rest = triple
|
|
[rp2, rp1] = map(relativelyPrime, [p2, p1])
|
|
|
|
# match :: [Int] -> (Int, [Int])
|
|
def match(xxs):
|
|
x, xs = uncons(xxs)['Just']
|
|
return (x, xs) if rp1(x) and not rp2(x) else (
|
|
second(cons(x))(
|
|
match(xs)
|
|
)
|
|
)
|
|
n, residue = match(rest)
|
|
return (p1, n, residue)
|
|
|
|
return chain(
|
|
range(1, 3),
|
|
map(
|
|
itemgetter(1),
|
|
iterate(nextWindow)(
|
|
(2, 3, count(4))
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
# TEST ----------------------------------------------------
|
|
# main :: IO ()
|
|
def main():
|
|
'''Terms of the Yellowstone permutation.'''
|
|
|
|
print(showList(
|
|
take(30)(yellowstone())
|
|
))
|
|
pyplot.plot(
|
|
take(100)(yellowstone())
|
|
)
|
|
pyplot.xlabel(main.__doc__)
|
|
pyplot.show()
|
|
|
|
|
|
# GENERIC -------------------------------------------------
|
|
|
|
# Just :: a -> Maybe a
|
|
def Just(x):
|
|
'''Constructor for an inhabited Maybe (option type) value.
|
|
Wrapper containing the result of a computation.
|
|
'''
|
|
return {'type': 'Maybe', 'Nothing': False, 'Just': x}
|
|
|
|
|
|
# Nothing :: Maybe a
|
|
def Nothing():
|
|
'''Constructor for an empty Maybe (option type) value.
|
|
Empty wrapper returned where a computation is not possible.
|
|
'''
|
|
return {'type': 'Maybe', 'Nothing': True}
|
|
|
|
|
|
# cons :: a -> [a] -> [a]
|
|
def cons(x):
|
|
'''Construction of a list from x as head,
|
|
and xs as tail.
|
|
'''
|
|
return lambda xs: [x] + xs if (
|
|
isinstance(xs, list)
|
|
) else x + xs if (
|
|
isinstance(xs, str)
|
|
) else chain([x], xs)
|
|
|
|
|
|
# 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 go
|
|
|
|
|
|
# second :: (a -> b) -> ((c, a) -> (c, b))
|
|
def second(f):
|
|
'''A simple function lifted to a function over a tuple,
|
|
with f applied only to the second of two values.
|
|
'''
|
|
return lambda xy: (xy[0], f(xy[1]))
|
|
|
|
|
|
# showList :: [a] -> String
|
|
def showList(xs):
|
|
'''Stringification of a list.'''
|
|
return '[' + ','.join(repr(x) for x in 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, tuple))
|
|
else list(islice(xs, n))
|
|
)
|
|
|
|
|
|
# uncons :: [a] -> Maybe (a, [a])
|
|
def uncons(xs):
|
|
'''The deconstruction of a non-empty list
|
|
(or generator stream) into two parts:
|
|
a head value, and the remaining values.
|
|
'''
|
|
if isinstance(xs, list):
|
|
return Just((xs[0], xs[1:])) if xs else Nothing()
|
|
else:
|
|
nxt = take(1)(xs)
|
|
return Just((nxt[0], xs)) if nxt else Nothing()
|
|
|
|
|
|
# MAIN ---
|
|
if __name__ == '__main__':
|
|
main()
|