http://mathling.com/core/sequences  library module

http://mathling.com/core/sequences


Sequence constructors: permutations, combinations, Fibonacci, deBruijn

See http://oeis.org/ for information about all kinds of lovely sequences.
The Axxxxxxx numbers reference sequences described there.

deBruijn:
Recursive algorithm using Lempel's D-morphism
http://debruijnsequence.org/db/recursive
See also http://www.hakank.org/comb/debruijn.cgi
for deBruijn sequences over arbitrary alphabets (not just binary)

The two-argument debruijn() function is based on the Wikipedia algorithm
plus http://www.hakank.org/unicon/debruijn.icn which is itself a port of
the original C program with this copyright:
--------------------------------------------------------------------------
C program to generate necklaces, Lyndon words, and De Bruijn
sequences. The algorithm is CAT and is described in the book
"Combinatorial Generation." This program, was obtained from the
(Combinatorial) Object Server, COS, at http://www.theory.csc.uvic.ca/~cos
The inputs are n, the length of the string, k, the arity of the
string, and density, the maximum number of non-0's in the string.
The De Bruijn option doesn't make sense unless density >= n.
The program can be modified, translated to other languages, etc.,
so long as proper acknowledgement is given (author and source).
Programmer: Frank Ruskey (1994), translated to C by Joe Sawada
--------------------------------------------------------------------------
Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)

December 2021
Status: Active

Imports

http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "utilities.xqy"
http://mathling.com/core/sparse
import module namespace sparse="http://mathling.com/core/sparse"
       at "sparse.xqy"
http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors"
       at "errors.xqy"

Variables

Variable: $ODIOUS as xs:integer*


First few odious numbers = numbers with odd numbers of 1s in binary
expansion
A000069

Variable: $KATYDID as xs:integer*


First few Katydid numbers
A060031

Variable: $GRASSHOPPER as xs:integer*


First few Grasshopper numbers

Functions

Function: fibonacci-sequence
declare function fibonacci-sequence($n as xs:integer) as xs:integer*


fibonacci-sequence()
All Fibonacci numbers to the nth (starting at 1)
A000045

Params
  • n as xs:integer: up to how many to generate n >= 1
Returns
  • xs:integer*: sequence
declare function this:fibonacci-sequence($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1,1)
  else (
    fold-left(
      1 to ($n - 2), (1,1),
      function($fib as xs:integer*, $i as xs:integer) {
        $fib, $fib[last()]+$fib[last()-1]
      }
    )
  )
}
Errors

ML-BADARGS if $n is less than 1

e.g.: $n=1 => (1)
e.g.: $n=7 => (1,1,2,3,5,8,13)

Function: fibonacci
declare function fibonacci($n as xs:integer) as xs:integer


fibonacci()
Return nth Fibonacci number

Params
  • n as xs:integer: which item in the sequence to return
Returns
  • xs:integer: integer
declare function this:fibonacci($n as xs:integer) as xs:integer
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then 1
  else this:fibonacci-sequence($n)[last()]
}
Errors

ML-BADARGS if $n is less than 1

Function: fibonacci-word
declare function fibonacci-word($n as xs:integer) as xs:integer*


fibonacci-word()
Return the first $n terms of the infinite Fibonacci word
e.g. fibonacci-word(10) = (0,1,0,0,1,0,1,0,0,1)
A003849

Params
  • n as xs:integer: up to how many to generate n >= 1
Returns
  • xs:integer*: sequence
declare function this:fibonacci-word($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then (0, 1)
  else (
    util:while(
      function($a as array(*), $b as array(*)) as xs:boolean {
        count(array:flatten($b)) <= $n
      },
      function($a as array(*), $b as array(*)) as array(*)* {
        $b,
        array { array:flatten($b), array:flatten($a) }
      },
      array {0}, array {0, 1}
    )=>tail()=>array:flatten()
  )[position() <= $n]
}
Errors

ML-BADARGS if $n is less than 1

Function: padovan
declare function padovan($n as xs:integer) as xs:integer*


padovan()
Return the first n terms of the Padovan sequence
a(n) = a(n-2) + a(n-3); a(0)=1, a(1)=a(2)=0
First few terms:
1,0,0,1,0,1,1,1,2,2,3,4,5,7,9,12,16,21,28,37,49,65,86,114,151,200,265,351

A000931

Params
  • n as xs:integer: up to how many to generate n >= 1
Returns
  • xs:integer*: sequence
declare function this:padovan($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1,0)
  else if ($n = 3) then (1,0,0)
  else (
    fold-left(
      1 to ($n - 3), (1,0,0),
      function($pad as xs:integer*, $i as xs:integer) {
        $pad, $pad[last()-1]+$pad[last()-2]
      }
    )
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: fractal
declare function fractal($n as xs:integer) as xs:integer*


fractal()
Triangle read by rows
First few terms:
1,1,2,1,2,3,1,2,3,4,1,2,3,4,5,1,2,3,4,5,6,1,2,3,4,5,6,7,1,2,3,4,5,6,7,8

A002260

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:fractal($n as xs:integer) as xs:integer*
{
  (: n = Σ[i=1:k]i = (1 + k) * k/2 => k = -1/2 + √(2n + 1/4) :)
  (: ceiling to make sure we get enough :)
  let $k := ceiling(math:sqrt(2 * $n + 0.25) - 0.25) cast as xs:integer
  return (
    for $i in 1 to $k
    for $j in 1 to $i
    return $j
  )[position() <= $n]
}

Function: golomb
declare function golomb($n as xs:integer) as xs:integer*


golomb()
a[n] is number of times n occurs
First few terms:
1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,9,10,10,10,10,10,11

A001462

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:golomb($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1, 2)
  else if ($n = 3) then (1, 2, 2)
  else (
    fold-left(
      3 to $n, (1, 2, 2),
      function($gol as xs:integer*, $i as xs:integer) {
        $gol, for $j in 1 to $gol[$i] return $i
      }
    )[position() <= $n]
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: lazy-caterer
declare function lazy-caterer($n as xs:integer) as xs:integer*


lazy-caterer()
n(n+1)/2 + 1
First few terms:
1,2,4,7,11,16,22,29,37,46,56,67,79,92,106,121,137,154,172,191,211,232,254

A000124

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:lazy-caterer($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else for $i in 0 to $n - 1 return (floor($i * ($i + 1) div 2) + 1) cast as xs:integer
}
Errors

ML-BADARGS if $n is less than 1

Function: jacobsthal
declare function jacobsthal($n as xs:integer) as xs:integer*


jacobsthal()
a(n)=a(n-1) + 2*a(n-2) starting 0, 1, 1
First few terms:
0,1,1,3,5,11,21,43,85,171,341,683,1365,2731,5461,10923,21845,43691,87381

A001045

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:jacobsthal($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then (0, 1)
  else if ($n = 3) then (0, 1, 1)
  else (
    fold-left(
      4 to $n, (0, 1, 1),
      function($jac as xs:integer*, $i as xs:integer) {
        $jac, $jac[last()] + 2 * $jac[last()-1]
      }
    )
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: jacobsthal-lucas
declare function jacobsthal-lucas($n as xs:integer) as xs:integer*


jacobsthal-lucas()
a(n+1) = 2*a(n) - (-1)^n*3 = 2^n + (-1)^n
First few terms:
2,1,5,7,17,31,65,127,257,511,1025,2047,4097,8191,16385,32767,65537,131071

A014551

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:jacobsthal-lucas($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else for $i in 0 to $n - 1 return (math:pow(2, $i) + math:pow(-1, $i)) cast as xs:integer
}
Errors

ML-BADARGS if $n is less than 1

Function: recaman
declare function recaman($n as xs:integer) as xs:integer*


recaman()
Recamán's sequence. a(0)=0; a(n)=a(n)-n if >=0 and not already in sequence;
a(n)=a(n-1)+n otherwise
First few terms:
0,1,3,6,2,7,13,20,12,21,11,22,10,23,9,24,8,25,43,62,42,63,41,18,42,17,43,16

A005132

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:recaman($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else (
    fold-left(
       1 to $n - 1, 0,
       function($seq as xs:integer*, $i as xs:integer) {
         $seq,
         let $an := $seq[last()] - $i
         return (
           if ($an >= 0 and not($an=$seq)) then $an
           else $seq[last()] + $i
         )
       }
    )
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: gerrymander2
declare function gerrymander2($n as xs:integer) as xs:integer*


gerrymander2()
2-party gerrymander: a(n) is min number of total votes for one party
to win when there are n² voters divided into equal districts.
First few terms:
1,3,4,8,9,14,16,24,25,33,36,45,49,60,64,80,81,95,100,117,121,138,144,165
A341578

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:gerrymander2($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else (
    for $i in 1 to $n return (
      let $i2 := $i*$i
      let $divisors := this:factors($i2)
      return (
        min((
          for $d in $divisors return (
            (floor($d div 2)+1)*(floor($i2 div (2*$d))+1)
          ),
          if ($i mod 2 = 0) then (
            (($i div 2) + 1)*(($i div 2) + 1) - 1
          ) else ()
        )) cast as xs:integer
      )
    )
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: inventory
declare function inventory($n as xs:integer) as xs:integer*


inventory()

First few terms:
0,1,1,0,2,2,2,0,3,2,4,1,1,0,4,4,4,1,4,0,5,5,4,1,6,2,1,0,6,7,5,1,6,3,3,1
A342585

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:inventory($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) 
  else if ($n = 1) then 0
  else (
    fold-left(2 to $n, (0, 0),
      function($seq as xs:integer*, $i as xs:integer) {
        let $last := $seq[last()]
        let $looking-for := head($seq)
        return (
          if ($last = 0) then (
            let $an := count(tail($seq)[. = 0])
            return (
              1,
              tail($seq),
              $an
            )
          ) else (
            let $an := count(tail($seq)[. = $looking-for])
            return (
              $looking-for + 1,
              tail($seq),
              $an
            )
          )
        )
      }
    )=>tail()
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: pascal-row
declare function pascal-row($n as xs:integer) as xs:integer*


pascal-row()
The nth row of Pascal's triangle

Params
  • n as xs:integer: which row
Returns
  • xs:integer*: sequence
declare function this:pascal-row($n as xs:integer) as xs:integer*
{
  for $i in 0 to $n - 1 return util:binomial($n - 1, $i)
}

Function: pascal
declare function pascal($n as xs:integer) as xs:integer*


pascal()
Pascal's triangle read by rows
First few terms:
1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,1,5,10,10,5,1,1,6,15,20,15,6,1,1,7,21,35,35,21

A007318

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:pascal($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  (: n = Σ[i=1:k]i = (1 + k) * k/2 => k = -1/2 + √(2n + 1/4) :)
  (: ceiling to make sure we get enough :)
  let $k := ceiling(math:sqrt(2 * $n + 0.25) - 0.25) cast as xs:integer
  return (
    for $i in 1 to $k
    return this:pascal-row($i)
  )[position() <= $n]
}
Errors

ML-BADARGS if $n is less than 1

Function: katydid
declare function katydid($n as xs:integer) as xs:integer*


katydid()
Katydid sequence; closed under n=> 2n+2 and 7n+7
Only have the first 53 terms
A060031

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:katydid($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else $this:KATYDID[position() <= $n]
}
Errors

ML-BADARGS if $n is less than 1

Function: grasshopper
declare function grasshopper($n as xs:integer) as xs:integer*


grasshopper()
Grasshopper sequence; closed under n=> 2n+2 and 6n+6
Only have the first 100 terms

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:grasshopper($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else $this:GRASSHOPPER[position() <= $n]
}
Errors

ML-BADARGS if $n is less than 1

Function: thue-morse
declare function thue-morse($n as xs:integer) as xs:integer*


thue-morse()
Return the first n terms of the Thue-Morse sequence.
First few terms:
0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1

A010060

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:thue-morse($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  let $generations := util:count-bits($n)
  return (
    fold-left(1 to $generations, 0,
      function($seq as xs:integer*, $i as xs:integer) {
        for $bit in $seq
        return
          if ($bit=0) then (0, 1) else (1, 0)
      }
    )[position() <= $n]
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: thue-morse
declare function thue-morse($n as xs:integer, $k as xs:integer) as xs:integer*


thue-morse()
Return the first n terms of the Thue-Morse sequence over alphabet of size k
See A010060, A053838, etc.

First few terms of k=3
0,1,2,1,2,0,2,0,1,1,2,0,2,0,1,0,1,2,2,0,1,0,1,2,1,2,0,1,2,0,2,0,1,0,1,2,2

Params
  • n as xs:integer: up to how many to generate
  • k as xs:integer
Returns
  • xs:integer*: sequence
declare function this:thue-morse($n as xs:integer, $k as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  let $generations := util:count-digits($n, $k)
  let $mappings := (
    array { 0 to $k - 1 },
    for $i in 1 to $k - 1 return array { this:rotate(0 to $k - 1, $i + 1) }
  )
  return (
    fold-left(1 to $generations, 0,
      function($seq as xs:integer*, $i as xs:integer) {
        for $digit in $seq
        return array:flatten($mappings[$digit + 1])
      }
    )[position() <= $n]
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: feigenbaum1
declare function feigenbaum1($n as xs:integer) as xs:integer*


feigenbaum1()
Return first n terms in the Feigenbaum symbolic sequence.
First few terms:
1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1

A035263

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:feigenbaum1($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1, 0)
  else (
    let $bits := util:count-bits($n - 2)
    return (
      fold-left(
        1 to $bits, (1, 0),
        function($fg as xs:integer*, $i as xs:integer) {
          $fg, $fg[position()<last()],
          if ($fg[last()]=1) then 0 else 1
        }
      )[position() <= $n]
    )
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: ruler-sequence
declare function ruler-sequence($n as xs:integer) as xs:integer*


ruler-sequence()
First n terms of the ruler function.
nth term = exponent of highest power of 2 dividing n
e.g. 4th term = 2 because 2^2 mod 4 = 0
First few terms:
0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1

A007814

Params
  • n as xs:integer: up to how many to generate
Returns
  • xs:integer*: sequence
declare function this:ruler-sequence($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then (0, 1)
  else (
    let $bits := util:count-bits($n - 2)
    return (
      fold-left(
        1 to $bits, (0, 1),
        function($fg as xs:integer*, $i as xs:integer) {
          $fg, $fg[position()<last()],
          $fg[last()] + 1
        }
      )[position() <= $n]
    )
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: ruler
declare function ruler($n as xs:integer) as xs:integer


ruler()
nth term of the ruler function = exponent of highest power of 2 dividing n
e.g. ruler(4) = 2 because 2^2 mod 4 = 0
A007814

Params
  • n as xs:integer: which term to return
Returns
  • xs:integer: integer
declare function this:ruler($n as xs:integer) as xs:integer
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then 1
  else this:ruler-sequence($n)[last()]
}
Errors

ML-BADARGS if $n is less than 1

Function: debruijn
declare function debruijn($n as xs:integer) as xs:integer*


debruijn()
Compute a binary deBruijn sequence of order n
Cyclic sequence where every possible sequence of length n occurs exactly
once as a contiguous subsequence in the cycle.

Params
  • n as xs:integer the order
Returns
  • xs:integer*: sequence
declare function this:debruijn(
  $n as xs:integer
) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else this:lempel($n, ())
}
Errors

ML-BADARGS if $n is less than 1

Function: debruijn
declare function debruijn($alphabet as item()*, $n as xs:integer)


debruijn()
Construct a deBruijn sequence of order n using the given alphabet
Cyclic sequence where every possible sequence of length n occurs exactly
once as a contiguous subsequence in the cycle.

Example: debruijn(("a","b"), 3) = ("a","a","a","b","a","b","b","b")
Example: debruijn(2, 3) = (0,0,0,1,0,1,1,1)

Params
  • alphabet as item()*: either the sequence of alphabet values to use or an integer indicating the numerical range to use, e.g. 2 => (0,1)
  • n as xs:integer the order
declare function this:debruijn(
  $alphabet as item()*,
  $n as xs:integer
)
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  let $alphabet :=
    if ($alphabet instance of xs:integer) then 0 to $alphabet - 1
    else $alphabet
  let $k := count($alphabet)
  let $a := sparse:array()=>sparse:put(1,0)
  let $a := this:ruskey($a, $k, $n, 1, 1, 0)
  let $res := $a=>map:get("seq")
  for $ix in $res return $alphabet[$ix + 1]
}
Errors

ML-BADARGS if $n is less than 1

Function: prime-sequence
declare function prime-sequence($n as xs:integer, $k as xs:integer, $q as xs:integer, $r as xs:integer) as xs:integer*


prime-sequence()
A sequence of digits in range [0, k-1] from the division of two
mutually prime numbers (use actual primes or rand:mutual-prime-pair())
If the numbers aren't mutually prime, the sequence gets boring.
You want q > r, in general.

Params
  • n as xs:integer: length of sequence
  • k as xs:integer: number range (e.g. 2 = binary 0 and 1)
  • q as xs:integer: first number
  • r as xs:integer: second number
Returns
  • xs:integer*: sequence
declare function this:prime-sequence(
  $n as xs:integer,
  $k as xs:integer,
  $q as xs:integer,
  $r as xs:integer
) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  for $i in 1 to $n
  let $dx := (($k*$q - $r) * $i) mod ($k*$q)
  return
    (
      for $i in 1 to $k - 1
      return if ($dx < $i*$q) then $i - 1 else (),
      $k
    )[1]
}
Errors

ML-BADARGS if $n is less than 1

Function: permutations
declare function permutations($input as item()*) as array(*)*


permutations()
All permutations of the input. Very naive algorithm: Don't use
on large sequences. Doesn't eliminate duplicates if there are
duplicate values within the input.

Params
  • input as item()*: the input sequence of values to permute
Returns
  • array(*)*: sequence of arrays, one per permutation
declare function this:permutations($input as item()*) as array(*)*
{
  let $N := count($input)
  return (
    switch ($N)
    case 0 return $input
    case 1 return array {$input}
    case 2 return (
      array {$input},
      array {reverse($input)}
    )
    case 3 return (
      array {$input},
      array {($input[1], $input[3], $input[2])},
      array {($input[2], $input[1], $input[3])},
      array {($input[2], $input[3], $input[1])},
      array {($input[3], $input[1], $input[2])},
      array {reverse($input)}
    )
    default return (
      for $i in 1 to $N return (
        for $c in this:permutations($input[position()!=$i])
        return
          array {(
            $input[$i],
            util:array-values($c)
         )}
      )
    )
  )
}

Function: foreach-permutation
declare function foreach-permutation($input as item()*, $f as function(item()*) as item()*) as item()*


foreach-permutation()
Execute a function over all permutations of the input. Very naive algorithm:
Don't use on large sequences. Doesn't eliminate duplicates if there are
duplicate values within the input.

Params
  • input as item()*: the input sequence of values to permute
  • f as function(item()*)asitem()*: the function to call for each permutation
Returns
  • item()*: values of function for each permutation
declare function this:foreach-permutation(
  $input as item()*,
  $f as function(item()*) as item()*
) as item()*
{
  let $N := count($input)
  return (
    switch ($N)
    case 0 return $f($input)
    case 1 return $f($input)
    case 2 return (
      $f($input),
      $f(reverse($input))
    )
    case 3 return (
      $f($input),
      $f(($input[1], $input[3], $input[2])),
      $f(($input[2], $input[1], $input[3])),
      $f(($input[2], $input[3], $input[1])),
      $f(($input[3], $input[1], $input[2])),
      $f(reverse($input))
    )
    default return (
      for $i in 1 to $N return (
        for $c in this:permutations($input[position()!=$i])
        return
          $f((
            $input[$i],
            util:array-values($c)
         ))
      )
    )
  )
}

Function: powerset
declare function powerset($input as item()*) as array(*)*


powerset()
Get all combinations of the input.

Params
  • input as item()*: the input sequence of values to get combinations from
Returns
  • array(*)*: sequence of arrays, one per combination
declare function this:powerset($input as item()*) as array(*)*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  return
    array {
      for $j in 1 to $N
      where $i idiv math:pow(2, $j - 1) mod 2 = 1
      return $input[$j]
    }
}

Function: foreach-combination
declare function foreach-combination($input as item()*, $f as function(item()*) as item()*) as item()*


foreach-combination()
Execute a function over all combinations of the input.

Params
  • input as item()*: the input sequence of values to get combinations from
  • f as function(item()*)asitem()*: the function to call for each combination
Returns
  • item()*: values of function over each combinatuion
declare function this:foreach-combination(
  $input as item()*,
  $f as function(item()*) as item()*
) as item()*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  return (
    $f(
      for $j in 1 to $N
      where $i idiv math:pow(2, $j - 1) mod 2 = 1
      return $input[$j]
    )
  )
}

Function: k-combinations
declare function k-combinations($input as item()*, $k as xs:integer) as array(*)*


k-combinations()
Members of the powerset of $items that have cardinality $k

Params
  • input as item()*: the input sequence of values to get combinations from
  • k as xs:integer: cardinality constraint
Returns
  • array(*)*: sequence of arrays, one per combination of cardinality $k
declare function this:k-combinations(
  $input as item()*,
  $k as xs:integer
) as array(*)*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  let $members := (
    for $j in 1 to $N
    where $i idiv math:pow(2, $j - 1) mod 2 = 1
    return $input[$j]
  )
  where count($members)=$k
  return array { $members }
}

Function: foreach-k-combination
declare function foreach-k-combination($input as item()*, $k as xs:integer, $f as function(item()*) as item()*) as item()*


foreach-k-combination()
Execute a function over all combinations of the input of cardinality k.

Params
  • input as item()*: the input sequence of values to get combinations from
  • k as xs:integer: cardinality constraint
  • f as function(item()*)asitem()*: the function to call for each combination
Returns
  • item()*: values of function over each combinary
declare function this:foreach-k-combination(
  $input as item()*,
  $k as xs:integer,
  $f as function(item()*) as item()*
) as item()*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  let $members := (
    for $j in 1 to $N
    where $i idiv math:pow(2, $j - 1) mod 2 = 1
    return $input[$j]
  )
  where count($members)=$k
  return $f($members)
}

Function: rotate
declare function rotate($seq as item()*, $k as xs:integer) as item()*


rotate()
Rotate a sequence by k slots
Example this:rotate((1,0,1,1,1,0,0,0), 2) = (1,1,1,0,0,0,1,0)

Params
  • seq as item()*: input sequence
  • k as xs:integer: position in input sequence to start new sequence
Returns
  • item()*: rotated sequence
declare function this:rotate(
  $seq as item()*,
  $k as xs:integer
) as item()*
{
  $seq[position()>=$k], $seq[position() < $k]
}

Function: lodumo
declare function lodumo($seq as xs:integer*, $m as xs:integer) as xs:integer*


lodumo()
Modify the input sequence s(n) where a(n) = smallest non-negative integer
not yet in list such that a(n) = s(n) mod m

See: https://oeis.org/wiki/Lodumo_transform

Params
  • seq as xs:integer*: input sequence
  • m as xs:integer: modulo
Returns
  • xs:integer*: lodumo of input sequence
declare function this:lodumo(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else if (some $a in $seq satisfies $a < 0) then errors:error("ML-BADARGS", ("seq", $seq))
  else (
    fold-left(
       $seq, (),
       function($output as xs:integer*, $a as xs:integer) {
         $output,
         util:while(
           function($an as xs:integer) { $an = $output },
           function($an as xs:integer) { $an + $m },
           $a mod $m
         )
       }
    )
  )
}

Function: primes
declare function primes($n as xs:integer) as xs:integer*


primes()
Set of primes less than or equal $n

Params
  • n as xs:integer: maximum prime
Returns
  • xs:integer*: sequence of primes
declare function this:primes($n as xs:integer) as xs:integer*
{
  if ($n <= $util:CANVAS-PRIMES[last()]) then $util:CANVAS-PRIMES[. <= $n]
  else (
    $util:CANVAS-PRIMES,
    (: Brute force, may take a while :)
    for $i in $util:CANVAS-PRIMES[last()] + 1 to $n
    where util:is-prime($i)
    return $i
  )
}

Function: prime-factors
declare function prime-factors($n as xs:integer) as xs:integer*


prime-factors()
Prime factors of the number, excluding 1, in ascending order
So factor(1) = (); factor(12) = (2, 2, 3)
n > 0

Params
  • n as xs:integer: number to factor
Returns
  • xs:integer*: sequence of prime factors
declare function this:prime-factors($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then ()
  else if ($n = $util:CANVAS-PRIMES) then $n
  else (
    let $factors :=
      let $primes := reverse(this:primes(math:sqrt($n) cast as xs:integer))
      return (
        fold-left($primes, $n,
          function($factors as xs:integer*, $prime as xs:integer) as xs:integer* {
            util:while(
              function($residue as xs:integer*) { head($residue) mod $prime = 0 },
              function($residue as xs:integer*) { head($residue) idiv $prime, $prime, tail($residue) },
              head($factors)
            ),
            tail($factors)
          }
        )
      )
    for $f in $factors order by $f ascending where $f!=1 return $f
  )
}
Errors

ML-BADARGS if $n is less than 1

Function: factors
declare function factors($n as xs:integer) as xs:integer*


factors()
All distinct factors of the number, including 1
n >= 0

Params
  • n as xs:integer: number to factor
Returns
  • xs:integer*: set of distinct factors
declare function this:factors($n as xs:integer) as xs:integer*
{
  if ($n < 0) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 0) then 0
  else (
    for $f in 
    distinct-values(
      for $p in this:powerset(this:prime-factors($n))
      return fold-left(array:flatten($p), 1, function($prod as xs:integer, $f as xs:integer) {$prod*$f})
    )
    order by $f ascending
    return $f
  )
}
Errors

ML-BADARGS if $n is less than zero

Function: continued-fraction
declare function continued-fraction($n as xs:integer, $x as xs:double) as xs:integer*


continued-fraction()
Generated the continued fraction sequence for the given number.
$x=π => 3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3,...
$x=e => 2, 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8, ...
If $x is rational this is finite but this function will return 0s
to fill out the sequence (technically wrong: should be INF)

Params
  • n as xs:integer: now many terms to generate
  • x as xs:double: the number to use
Returns
  • xs:integer*
declare function this:continued-fraction(
  $n as xs:integer,
  $x as xs:double
) as xs:integer*
{
  fold-left(1 to $n, ($x, ()),
    function($data as xs:numeric*, $i as xs:integer) {
      let $fraction := head($data) - floor(head($data))
      return (
        if ($fraction = 0) then 0
        else 1 div $fraction
      ),
      tail($data),
      floor(head($data)) cast as xs:integer
    }
  )=>tail()
}

Function: convergents
declare function convergents($seq as xs:integer*) as array(xs:integer)*


convergents()
Get the numerator/denominator pairs of the convergents of the sequence,
taken as a continued fraction. Each pair $pair(1)/$pair(2) represents
an approximation of the value represented by the sequence.

Params
  • seq as xs:integer*: sequence to interpret as continued fraction sequence
Returns
  • array(xs:integer)*: sequence of the successive rational approximations
declare function this:convergents($seq as xs:integer*) as array(xs:integer)*
{
  let $h :=
    fold-left(1 to count($seq), (0, 1),
      function($h as xs:integer*, $i as xs:integer) {
        $h,
        $seq[$i]*$h[2 + $i - 1] + $h[2 + $i - 2]
      }
    )=>tail()=>tail()
  let $k :=
    fold-left(1 to count($seq), (1, 0),
      function($k as xs:integer*, $i as xs:integer) {
        $k,
        $seq[$i]*$k[2 + $i - 1] + $k[2 + $i - 2]
      }
    )=>tail()=>tail()
  for $n in 1 to count($seq) return (
    array { $h[$n], $k[$n] }
  )
}

Function: numerators
declare function numerators($seq as xs:integer*) as xs:integer*


numerators()
Get the numerators of the convergents of the sequence,
taken as a continued fraction.

Params
  • seq as xs:integer*: sequence to interpret as continued fraction sequence
Returns
  • xs:integer*: sequence of the numerators of successive rational approximations
declare function this:numerators($seq as xs:integer*) as xs:integer*
{
  fold-left(1 to count($seq), (0, 1),
    function($h as xs:integer*, $i as xs:integer) {
      $h,
      $seq[$i]*$h[2 + $i - 1] + $h[2 + $i - 2]
    }
  )=>tail()=>tail()
}

Function: denominators
declare function denominators($seq as xs:integer*) as xs:integer*


denominators()
Get the denominators of the convergents of the sequence,
taken as a continued fraction.

Params
  • seq as xs:integer*: sequence to interpret as continued fraction sequence
Returns
  • xs:integer*: sequence of the denominators of successive rational approximations
declare function this:denominators($seq as xs:integer*) as xs:integer*
{
  fold-left(1 to count($seq), (1, 0),
    function($k as xs:integer*, $i as xs:integer) {
      $k,
      $seq[$i]*$k[2 + $i - 1] + $k[2 + $i - 2]
    }
  )=>tail()=>tail()
}

Function: μ
declare function μ($n as xs:integer) as xs:integer


μ()
Möbius μ function:
0: if n has 1 or more repeated prime factors
1: if n = 1
(-1)^k: if n is product of k distinct primes

Params
  • n as xs:integer: number to calculate Möbius μ function for
Returns
  • xs:integer: μ(n)
declare function this:μ($n as xs:integer) as xs:integer
{
  if ($n = 1) then 1 else (
    let $factors := this:prime-factors($n)
    let $k := count($factors)
    let $d := count(distinct-values($factors))
    return (
      if ($k = $d) then (
        if ($k mod 2 = 0) then 1 else -1
      ) else 0
    )
  )
}

Function: mobius
declare function mobius($seq as xs:integer*) as xs:integer*


mobius()
Möbius transform b[n] = Σ (μ(n/d)a[d])
d|n d divides into n

Params
  • seq as xs:integer*: sequence to apply transform to
Returns
  • xs:integer*: Möbius transform of the sequence
declare function this:mobius($seq as xs:integer*) as xs:integer*
{
  for $n in 1 to count($seq)
  let $factors := distinct-values(
    for $p in this:powerset(this:prime-factors($n))
    return fold-left(array:flatten($p), 1, function($prod as xs:integer*, $f as xs:integer) {$prod*$f})
  )
  return (
    if ($seq[$n] = 0) then 0
    else sum(for $d in $factors return this:μ($n idiv $d) * $seq[$d])
  )
}

Function: binomial
declare function binomial($seq as xs:integer*) as xs:integer*


binomial()
n
Binomial transform of a sequence: b[n] = Σ (n choose k) a[k]
k=0

Params
  • seq as xs:integer*: sequence to apply transform to
Returns
  • xs:integer*: binomial transform of the sequence
declare function this:binomial($seq as xs:integer*) as xs:integer*
{
  for $n in 0 to count($seq) - 1 return (
    sum(
      for $k in 0 to $n return util:binomial($n, $k)*$seq[$k + 1]
    ) cast as xs:integer
  )  
}

Function: ordinal
declare function ordinal($seq as xs:integer*) as xs:integer*


ordinal()

Ordinal transformation: b[n] = count of number of times a[n] appears in a[i]
sequence up to and include a[n]

Params
  • seq as xs:integer*: sequence to apply transform to
Returns
  • xs:integer*: ordinal transform of the sequence
declare function this:ordinal($seq as xs:integer*) as xs:integer*
{
  for $a at $i in $seq return count($seq[position() <= $i and .=$a])
}

Function: mirror
declare function mirror($seq as xs:integer*) as xs:integer*


mirror()

Mirror transformation: reversed negation of first half of sequence
followed by first half of sequence

Params
  • seq as xs:integer*: sequence to apply transform to
Returns
  • xs:integer*: mirror transform of the sequence
declare function this:mirror($seq as xs:integer*) as xs:integer*
{
  let $n := count($seq)
  return (
    reverse($seq[position() <= $n div 2])!(-.),
    $seq[position() <= $n div 2]
  )
}

Function: alternate
declare function alternate($seq as xs:integer*) as xs:integer*


alternate()

Alternation transformation: b[n] = a[n] is n mod 2 = 1 else -a[n]

Params
  • seq as xs:integer*: sequence to apply transform to
Returns
  • xs:integer*: alternate transform of the sequence
declare function this:alternate($seq as xs:integer*) as xs:integer*
{
  for $val at $i in $seq return (
    if ($i mod 2 = 1) then abs($val) else -abs($val)
  )
}

Function: shift
declare function shift($seq as xs:integer*) as xs:integer*


shift()

Shift transformation: b[n] = a[n]+1
A lot of the sequences produce 0 values which we may not want.

Params
  • seq as xs:integer*: sequence to apply transform to
Returns
  • xs:integer*: shift transform of the sequence
declare function this:shift($seq as xs:integer*) as xs:integer*
{
  $seq!(. + 1)
}

Function: modix
declare function modix($seq as xs:integer*, $m as xs:integer) as xs:integer*


modix()
Modify the input sequence s(n) where a(n) = util:modix(s(n), m)
i.e. s(n) mod m shifted to 1 to m range (maps negative values as well)

Params
  • seq as xs:integer*: input sequence
  • m as xs:integer: modulo
Returns
  • xs:integer*: modix of input sequence
declare function this:modix(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    $seq!util:modix(., $m)
  )
}

Function: modp
declare function modp($seq as xs:integer*, $m as xs:integer) as xs:integer*


modp()
Modify the input sequence s(n) where a(n) = s(n) if s(n) mod m != 0, 0
otherwise.

Params
  • seq as xs:integer*: input sequence
  • m as xs:integer: modulo
Returns
  • xs:integer*: input sequence when value is positive mod m, 0 when not
declare function this:modp(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $a in $seq return if ($a mod $m = 0) then 0 else $a
  )
}

Function: modz
declare function modz($seq as xs:integer*, $m as xs:integer) as xs:integer*


modz()
Modify the input sequence s(n) where a(n) = s(n) if s(n) mod m = 0, 0
otherwise.

Params
  • seq as xs:integer*: input sequence
  • m as xs:integer: modulo
Returns
  • xs:integer*: input sequence when value is zero mod m, 0 when not
declare function this:modz(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $a in $seq return if ($a mod $m = 0) then $a else 0
  )
}

Function: unmodp
declare function unmodp($seq as xs:integer*, $m as xs:integer) as xs:integer*


unmodp()
Modify the input sequence s(n) where a(n) = $n if s(n) mod m != 0, 0
otherwise.

Params
  • seq as xs:integer*: input sequence
  • m as xs:integer: modulo
Returns
  • xs:integer*: index when value is positive mod m, 0 when not
declare function this:unmodp(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $i in 1 to count($seq) return if ($seq[$i] mod $m = 0) then 0 else $i
  )
}

Function: unmodz
declare function unmodz($seq as xs:integer*, $m as xs:integer) as xs:integer*


unmodz()
Modify the input sequence s(n) where a(n) = $n if s(n) mod m = 0, 0
otherwise.

Params
  • seq as xs:integer*: input sequence
  • m as xs:integer: modulo
Returns
  • xs:integer*: index when value is zero mod m, 0 when not
declare function this:unmodz(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $i in 1 to count($seq) return if ($seq[$i] mod $m = 0) then $i else 0
  )
}

Function: compose
declare function compose($seq1 as xs:integer*, $seq2 as xs:integer*, $op as function(xs:integer,xs:integer) as xs:integer) as xs:integer*


compose()
Compose two integer sequences using the particular operation.
If the sequences are different lengths, loop back to start of shorter seq.
e.g. compose((1,2,3,4),(-1,1),function($a,$b) {$a * $b}) = (-1,2,-3,4)

Params
  • seq1 as xs:integer*: one sequence
  • seq2 as xs:integer*: other sequence
  • op as function(xs:integer,xs:integer)asxs:integer composition function
Returns
  • xs:integer*
declare function this:compose(
  $seq1 as xs:integer*,
  $seq2 as xs:integer*,
  $op as function(xs:integer,xs:integer) as xs:integer
) as xs:integer*
{
  let $n1 := count($seq1)
  let $n2 := count($seq2)
  for $i in 1 to (max(($n1,$n2)) cast as xs:integer)
  let $a := $seq1[util:modix($i, $n1)]
  let $b := $seq2[util:modix($i, $n2)]
  return $op($a, $b)
}

Function: sequence
declare function sequence($name as xs:string, $n as xs:integer) as xs:integer*


sequence()
Get an integer sequence by name. The name is either the simple name of
the sequence, or the sequence modified.

Bare names:
fibonacci (Fibonacci sequence), fibonacci-word, padovan, fractal, golomb,
lazy-caterer, jacobsthal, jacobsthal-lucas, recaman, gerrymander2,
inventory, pascal, katydid, grasshopper, thue-morse, feigenbaum1,
ruler (Ruler sequence), primes, counter

Parameterized names:
thue-morse@k = thue-morse(n,k)
prime-sequence@k@q@r = prime-sequence(n, k, q, r)
continued-fraction@d = continued-fraction(n, d)

Operators: apply the operator to the sequence so far
mobius = seq=>mobius()
binomial = seq=>binomial()
ordinal = seq=>ordinal()
numerators = seq=>numerators()
denominators = seq=>denominators()
mirror = seq=>mirror()
alternate = seq=>alternate()
shift = seq=>shift()

Parameterized operators:
rotate@k = seq=>rotate(k)
lodumo@m = seq=>lodumo(m)
modix@m = seq=>modix(m)
modp@m = seq=>modp(m)
modz@m = seq=>modz(m)
unmodp@m = seq=>unmodp(m)
unmodz@m = seq=>unmodz(m)

seq1+operator = apply operator to seq1
seq1+seq1 = seq1 followed by seq2 (so sequence length will be 2*$n)

Params
  • name as xs:string: name of sequence
  • n as xs:integerame: name of sequence: length of each component sequence
Returns
  • xs:integer*: sequence
declare function this:sequence(
  $name as xs:string,
  $n as xs:integer
) as xs:integer*
{
  fold-left(tokenize($name, "[+]")[. ne ""], (),
    function($seq as xs:integer*, $part as xs:string) {
      switch($part)
      case "none" return $seq
      case "counter" return ($seq, 1 to $n)
      case "fibonacci" return ($seq, this:fibonacci-sequence($n))
      case "fibonacci-word" return ($seq, this:fibonacci-word($n))
      case "padovan" return ($seq, this:padovan($n))
      case "fractal" return ($seq, this:fractal($n))
      case "golomb" return ($seq, this:golomb($n))
      case "lazy-caterer" return ($seq, this:lazy-caterer($n))
      case "jacobsthal" return ($seq, this:jacobsthal($n))
      case "jacobsthal-lucas" return ($seq, this:jacobsthal-lucas($n))
      case "recaman" return ($seq, this:recaman($n))
      case "gerrymander2" return ($seq, this:gerrymander2($n))
      case "inventory" return ($seq, this:inventory($n))
      case "pascal" return ($seq, this:pascal($n))
      case "katydid" return ($seq, this:katydid($n))
      case "grasshopper" return ($seq, this:grasshopper($n))
      case "thue-morse" return ($seq, this:thue-morse($n))
      case "feigenbaum1" return ($seq, this:feigenbaum1($n))
      case "ruler" return ($seq, this:ruler-sequence($n))
      case "primes" return ($seq, this:primes($n))
      case "mobius" return $seq=>this:mobius()
      case "binomial" return $seq=>this:binomial()
      case "ordinal" return $seq=>this:ordinal()
      case "numerators" return $seq=>this:numerators()
      case "denominators" return $seq=>this:denominators()
      case "mirror" return $seq=>this:mirror()
      case "alternate" return $seq=>this:alternate()
      case "shift" return $seq=>this:shift()
      default return ( (: More parsing required :)
        let $pieces := tokenize($part,"@")[. ne ""]
        return switch ($pieces[1])
        case "thue-morse" return ($seq, this:thue-morse($n, xs:integer($pieces[2])))
        case "prime-sequence" return ($seq, this:prime-sequence($n, xs:integer($pieces[2]), xs:integer($pieces[3]), xs:integer($pieces[4])))
        case "rotate" return $seq=>this:rotate(xs:integer($pieces[2]))
        case "lodumo" return $seq=>this:lodumo(xs:integer($pieces[2]))
        case "modix" return $seq=>this:modix(xs:integer($pieces[2]))
        case "continued-fraction" return ($seq, this:continued-fraction($n, this:double($pieces[2])))
        case "modp" return $seq=>this:modp(xs:integer($pieces[2]))
        case "modz" return $seq=>this:modz(xs:integer($pieces[2]))
        case "unmodp" return $seq=>this:unmodp(xs:integer($pieces[2]))
        case "unmodz" return $seq=>this:unmodz(xs:integer($pieces[2]))
        default return $seq
      )
    }
  )
}

Original Source Code

xquery version "3.1";
(:~
 : Sequence constructors: permutations, combinations, Fibonacci, deBruijn
 :
 : See http://oeis.org/ for information about all kinds of lovely sequences.
 : The Axxxxxxx numbers reference sequences described there.
 :
 : deBruijn:
 : Recursive algorithm using Lempel's D-morphism
 : http://debruijnsequence.org/db/recursive
 : See also http://www.hakank.org/comb/debruijn.cgi
 : for deBruijn sequences over arbitrary alphabets (not just binary)
 :
 : The two-argument debruijn() function is based on the Wikipedia algorithm
 : plus http://www.hakank.org/unicon/debruijn.icn which is itself a port of
 : the original C program with this copyright:
 : --------------------------------------------------------------------------
 : C program to generate necklaces, Lyndon words, and De Bruijn              
 : sequences.  The algorithm is CAT and is described in the book             
 : "Combinatorial Generation."  This program, was obtained from the          
 : (Combinatorial) Object Server, COS, at http://www.theory.csc.uvic.ca/~cos 
 : The inputs are n, the length of the string, k, the arity of the           
 : string, and density, the maximum number of non-0's in the string.         
 : The De Bruijn option doesn't make sense unless density >= n.              
 : The program can be modified, translated to other languages, etc.,         
 : so long as proper acknowledgement is given (author and source).           
 : Programmer: Frank Ruskey (1994), translated to C by Joe Sawada            
 : --------------------------------------------------------------------------
 : Copyright© Mary Holstege 2020-2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since December 2021
 : @custom:Status Active
 :)
module namespace this="http://mathling.com/core/sequences"; 

import module namespace errors="http://mathling.com/core/errors"
       at "errors.xqy";
import module namespace util="http://mathling.com/core/utilities"
       at "utilities.xqy";
import module namespace sparse="http://mathling.com/core/sparse"
       at "sparse.xqy";

declare namespace svg="http://www.w3.org/2000/svg";

declare namespace array="http://www.w3.org/2005/xpath-functions/array";
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";

(:~
 : First few odious numbers = numbers with odd numbers of 1s in binary 
 : expansion
 : A000069
 :)
declare variable $this:ODIOUS as xs:integer* := (
  1, 2, 4, 7, 8, 11, 13, 14, 16, 19, 21, 22, 25, 26, 28, 31, 32, 35, 
  37, 38, 41, 42, 44, 47, 49, 50, 52, 55, 56, 59, 61, 62, 64, 67, 
  69, 70, 73, 74, 76, 79, 81, 82, 84, 87, 88, 91, 93, 94, 97, 98, 
  100, 103, 104, 107, 109, 110, 112, 115, 117, 118, 121, 122, 
  124, 127, 128
);

(:~ 
 : fibonacci-sequence()
 : All Fibonacci numbers to the nth (starting at 1)
 : A000045
 :
 : @param $n: up to how many to generate n >= 1
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :
 : e.g.: $n=1 => (1)
 : e.g.: $n=7 => (1,1,2,3,5,8,13)
 :)
declare function this:fibonacci-sequence($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1,1)
  else (
    fold-left(
      1 to ($n - 2), (1,1),
      function($fib as xs:integer*, $i as xs:integer) {
        $fib, $fib[last()]+$fib[last()-1]
      }
    )
  )
};

(:~
 : fibonacci()
 : Return nth Fibonacci number
 : @param $n: which item in the sequence to return
 : @return integer
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:fibonacci($n as xs:integer) as xs:integer
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then 1
  else this:fibonacci-sequence($n)[last()]
};

(:~
 : fibonacci-word()
 : Return the first $n terms of the infinite Fibonacci word
 : e.g. fibonacci-word(10) = (0,1,0,0,1,0,1,0,0,1)
 : A003849
 :
 : @param $n: up to how many to generate n >= 1
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:fibonacci-word($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then (0, 1)
  else (
    util:while(
      function($a as array(*), $b as array(*)) as xs:boolean {
        count(array:flatten($b)) <= $n
      },
      function($a as array(*), $b as array(*)) as array(*)* {
        $b,
        array { array:flatten($b), array:flatten($a) }
      },
      array {0}, array {0, 1}
    )=>tail()=>array:flatten()
  )[position() <= $n]
};

(:~
 : padovan()
 : Return the first n terms of the Padovan sequence
 : a(n) = a(n-2) + a(n-3); a(0)=1, a(1)=a(2)=0
 : First few terms:
 : 1,0,0,1,0,1,1,1,2,2,3,4,5,7,9,12,16,21,28,37,49,65,86,114,151,200,265,351
 :
 : A000931
 :
 : @param $n: up to how many to generate n >= 1
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:padovan($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1,0)
  else if ($n = 3) then (1,0,0)
  else (
    fold-left(
      1 to ($n - 3), (1,0,0),
      function($pad as xs:integer*, $i as xs:integer) {
        $pad, $pad[last()-1]+$pad[last()-2]
      }
    )
  )
};

(:~ 
 : fractal()
 : Triangle read by rows
 : First few terms:
 : 1,1,2,1,2,3,1,2,3,4,1,2,3,4,5,1,2,3,4,5,6,1,2,3,4,5,6,7,1,2,3,4,5,6,7,8
 :
 : A002260
 :
 : @param $n: up to how many to generate
 : @return sequence
 :)
declare function this:fractal($n as xs:integer) as xs:integer*
{
  (: n = Σ[i=1:k]i = (1 + k) * k/2 => k = -1/2 + √(2n + 1/4) :)
  (: ceiling to make sure we get enough :)
  let $k := ceiling(math:sqrt(2 * $n + 0.25) - 0.25) cast as xs:integer
  return (
    for $i in 1 to $k
    for $j in 1 to $i
    return $j
  )[position() <= $n]
};

(:~ 
 : golomb()
 : a[n] is number of times n occurs
 : First few terms:
 : 1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,9,10,10,10,10,10,11
 :
 : A001462
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:golomb($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1, 2)
  else if ($n = 3) then (1, 2, 2)
  else (
    fold-left(
      3 to $n, (1, 2, 2),
      function($gol as xs:integer*, $i as xs:integer) {
        $gol, for $j in 1 to $gol[$i] return $i
      }
    )[position() <= $n]
  )
};

(:~ 
 : lazy-caterer()
 : n(n+1)/2 + 1
 : First few terms:
 : 1,2,4,7,11,16,22,29,37,46,56,67,79,92,106,121,137,154,172,191,211,232,254
 :
 : A000124
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:lazy-caterer($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else for $i in 0 to $n - 1 return (floor($i * ($i + 1) div 2) + 1) cast as xs:integer
};

(:~ 
 : jacobsthal()
 : a(n)=a(n-1) + 2*a(n-2) starting 0, 1, 1
 : First few terms:
 : 0,1,1,3,5,11,21,43,85,171,341,683,1365,2731,5461,10923,21845,43691,87381
 :
 : A001045
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:jacobsthal($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then (0, 1)
  else if ($n = 3) then (0, 1, 1)
  else (
    fold-left(
      4 to $n, (0, 1, 1),
      function($jac as xs:integer*, $i as xs:integer) {
        $jac, $jac[last()] + 2 * $jac[last()-1]
      }
    )
  )
};

(:~ 
 : jacobsthal-lucas()
 : a(n+1) = 2*a(n) - (-1)^n*3 = 2^n + (-1)^n
 : First few terms:
 : 2,1,5,7,17,31,65,127,257,511,1025,2047,4097,8191,16385,32767,65537,131071
 :
 : A014551
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:jacobsthal-lucas($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else for $i in 0 to $n - 1 return (math:pow(2, $i) + math:pow(-1, $i)) cast as xs:integer
};

(:~
 : recaman()
 : Recamán's sequence. a(0)=0; a(n)=a(n)-n if >=0 and not already in sequence;
 : a(n)=a(n-1)+n otherwise
 : First few terms:
 : 0,1,3,6,2,7,13,20,12,21,11,22,10,23,9,24,8,25,43,62,42,63,41,18,42,17,43,16
 :
 : A005132
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:recaman($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else (
    fold-left(
       1 to $n - 1, 0,
       function($seq as xs:integer*, $i as xs:integer) {
         $seq,
         let $an := $seq[last()] - $i
         return (
           if ($an >= 0 and not($an=$seq)) then $an
           else $seq[last()] + $i
         )
       }
    )
  )
};

(:~
 : gerrymander2()
 : 2-party gerrymander: a(n) is min number of total votes for one party
 : to win when there are n² voters divided into equal districts.
 : First few terms:
 : 1,3,4,8,9,14,16,24,25,33,36,45,49,60,64,80,81,95,100,117,121,138,144,165
 : A341578
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:gerrymander2($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else (
    for $i in 1 to $n return (
      let $i2 := $i*$i
      let $divisors := this:factors($i2)
      return (
        min((
          for $d in $divisors return (
            (floor($d div 2)+1)*(floor($i2 div (2*$d))+1)
          ),
          if ($i mod 2 = 0) then (
            (($i div 2) + 1)*(($i div 2) + 1) - 1
          ) else ()
        )) cast as xs:integer
      )
    )
  )
};

(:~
 : inventory()
 : 
 : First few terms:
 : 0,1,1,0,2,2,2,0,3,2,4,1,1,0,4,4,4,1,4,0,5,5,4,1,6,2,1,0,6,7,5,1,6,3,3,1
 : A342585
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:inventory($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) 
  else if ($n = 1) then 0
  else (
    fold-left(2 to $n, (0, 0),
      function($seq as xs:integer*, $i as xs:integer) {
        let $last := $seq[last()]
        let $looking-for := head($seq)
        return (
          if ($last = 0) then (
            let $an := count(tail($seq)[. = 0])
            return (
              1,
              tail($seq),
              $an
            )
          ) else (
            let $an := count(tail($seq)[. = $looking-for])
            return (
              $looking-for + 1,
              tail($seq),
              $an
            )
          )
        )
      }
    )=>tail()
  )
};

(:~
 : pascal-row()
 : The nth row of Pascal's triangle
 :
 : @param $n: which row
 : @return sequence
 :)
declare function this:pascal-row($n as xs:integer) as xs:integer*
{
  for $i in 0 to $n - 1 return util:binomial($n - 1, $i)
};

(:~ 
 : pascal()
 : Pascal's triangle read by rows
 : First few terms:
 : 1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,1,5,10,10,5,1,1,6,15,20,15,6,1,1,7,21,35,35,21
 : 
 : A007318
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:pascal($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  (: n = Σ[i=1:k]i = (1 + k) * k/2 => k = -1/2 + √(2n + 1/4) :)
  (: ceiling to make sure we get enough :)
  let $k := ceiling(math:sqrt(2 * $n + 0.25) - 0.25) cast as xs:integer
  return (
    for $i in 1 to $k
    return this:pascal-row($i)
  )[position() <= $n]
};

(:~ 
 : katydid()
 : Katydid sequence; closed under n=> 2n+2 and 7n+7
 : Only have the first 53 terms
 : A060031
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:katydid($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else $this:KATYDID[position() <= $n]
};

(:~
 : First few Katydid numbers
 : A060031
 :)
declare variable $this:KATYDID as xs:integer* := (
  1, 4, 10, 14, 22, 30, 35, 46, 62, 72, 77, 94, 105, 126, 146, 156, 161, 190, 212, 217, 252, 254, 294, 314, 324, 329, 382, 426, 436, 441, 506, 510, 511, 546, 590, 630, 650, 660, 665, 742, 766, 854, 874, 884, 889, 1014, 1022, 1024, 1029, 1094, 1099, 1134, 1182
);

(:~ 
 : grasshopper()
 : Grasshopper sequence; closed under n=> 2n+2 and 6n+6
 : Only have the first 100 terms
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:grasshopper($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else $this:GRASSHOPPER[position() <= $n]
};

(:~
 : First few Grasshopper numbers
 :)
declare variable $this:GRASSHOPPER as xs:integer* := (
  1, 4, 10, 12, 22, 26, 30, 46, 54, 62, 66, 78, 94, 110, 126, 134, 138, 158, 162, 186, 190, 222, 254, 270, 278, 282, 318, 326, 330, 374, 378, 382, 402, 446, 474, 510, 542, 558, 566, 570, 638, 654, 662, 666, 750, 758, 762, 766, 806, 810, 834, 894, 950, 954, 978, 1022, 1086, 1118, 1122, 1134, 1142, 1146, 1278, 1310, 1326, 1334, 1338, 1502, 1518, 1526, 1530, 1534, 1614, 1622, 1626, 1670, 1674, 1698, 1790, 1902, 1910, 1914, 1958, 1962, 1986, 2046, 2174, 2238, 2246, 2250, 2270, 2274, 2286, 2294, 2298, 2418, 2558, 2622, 2654, 2670
);

(:~ 
 : thue-morse()
 : Return the first n terms of the Thue-Morse sequence.
 : First few terms:
 : 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1
 : 
 : A010060
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:thue-morse($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  let $generations := util:count-bits($n)
  return (
    fold-left(1 to $generations, 0,
      function($seq as xs:integer*, $i as xs:integer) {
        for $bit in $seq
        return
          if ($bit=0) then (0, 1) else (1, 0)
      }
    )[position() <= $n]
  )
};

(:~ 
 : thue-morse()
 : Return the first n terms of the Thue-Morse sequence over alphabet of size k
 : See A010060, A053838, etc.
 : 
 : First few terms of k=3
 : 0,1,2,1,2,0,2,0,1,1,2,0,2,0,1,0,1,2,2,0,1,0,1,2,1,2,0,1,2,0,2,0,1,0,1,2,2
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:thue-morse($n as xs:integer, $k as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  let $generations := util:count-digits($n, $k)
  let $mappings := (
    array { 0 to $k - 1 },
    for $i in 1 to $k - 1 return array { this:rotate(0 to $k - 1, $i + 1) }
  )
  return (
    fold-left(1 to $generations, 0,
      function($seq as xs:integer*, $i as xs:integer) {
        for $digit in $seq
        return array:flatten($mappings[$digit + 1])
      }
    )[position() <= $n]
  )
};

(:~
 : feigenbaum1()
 : Return first n terms in the Feigenbaum symbolic sequence.
 : First few terms:
 : 1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1
 : 
 : A035263
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:feigenbaum1($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 1
  else if ($n = 2) then (1, 0)
  else (
    let $bits := util:count-bits($n - 2)
    return (
      fold-left(
        1 to $bits, (1, 0),
        function($fg as xs:integer*, $i as xs:integer) {
          $fg, $fg[position()<last()],
          if ($fg[last()]=1) then 0 else 1
        }
      )[position() <= $n]
    )
  )
};

(:~ 
 : ruler-sequence()
 : First n terms of the ruler function.
 : nth term = exponent of highest power of 2 dividing n
 : e.g. 4th term = 2 because 2^2 mod 4 = 0
 : First few terms:
 : 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1
 :
 : A007814
 :
 : @param $n: up to how many to generate
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:ruler-sequence($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then (0, 1)
  else (
    let $bits := util:count-bits($n - 2)
    return (
      fold-left(
        1 to $bits, (0, 1),
        function($fg as xs:integer*, $i as xs:integer) {
          $fg, $fg[position()<last()],
          $fg[last()] + 1
        }
      )[position() <= $n]
    )
  )
};

(:~ 
 : ruler()
 : nth term of the ruler function = exponent of highest power of 2 dividing n
 : e.g. ruler(4) = 2 because 2^2 mod 4 = 0
 : A007814
 :
 : @param $n: which term to return
 : @return integer
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:ruler($n as xs:integer) as xs:integer
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then 0
  else if ($n = 2) then 1
  else this:ruler-sequence($n)[last()]
};

(:~
 : lempel()
 : Given seq is deBruijn sequence of order t, create sequence of order t+1
 :)
declare %private function this:lempel(
  $t as xs:integer,
  $seq as xs:integer*
) as xs:integer*
{
  if ($t <= 2) then (1,0,0,1) else (
    (: Recursively find sequence of order t - 1 :)
    let $a := this:lempel($t - 1, $seq)
    let $len := math:pow(2, $t - 1) cast as xs:integer
    let $b :=
      fold-left(1 to $len - 1, 0,
        function($b as xs:integer*, $i as xs:integer) as xs:integer* {
          $b,
          if ($a[$i] = 0) then $b[$i] else 1 - $b[$i]
        }
      )
    (: Find index s starting the length t-1 string 1010... in b :)
    let $s :=
      fold-left(2 to $len, (false(), 1),
        function($stop-s as item()*, $i as xs:integer) as item()* {
          if (head($stop-s)) then $stop-s
          else (
            let $s := tail($stop-s)
            let $s :=
              if (($b[$i] = $b[$i - 1]) or ($b[$i]=1 and $b[$s] = 0))
              then $i
              else $s
            return (
              ($i - $s + 1 = $t - 1), $s
            )
          )
        }
      )=>tail()
    return (
      (: 
       : Join b ands its complement together at 1010... to get 
       : deBruijn sequence of order t. Starting index of 1010...
       : is offset by 1 in the complement
       :)
      for $i in 1 to $len return (
        $b[util:modix($i + $s, $len)]
      ),
      for $i in 1 to $len return (
        if ($b[util:modix($s - 1, $len)])
        then 1 - $b[util:modix($i + $s + 1, $len)]
        else 1 - $b[util:modix($i + $s - 1, $len)]
      )
    )
  )
};

(:~
 : debruijn()
 : Compute a binary deBruijn sequence of order n
 : Cyclic sequence where every possible sequence of length n occurs exactly
 : once as a contiguous subsequence in the cycle.
 :
 : @param $n the order
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:debruijn(
  $n as xs:integer
) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else this:lempel($n, ())
};


declare %private function this:ruskey(
  $a as map(*),
  $k as xs:integer,
  $n as xs:integer,
  $t as xs:integer,
  $p as xs:integer,
  $ones as xs:integer
) as map(*)
{
  if ($ones <= $n) then (
    if ($t > $n) then (
      if ($n mod $p = 0) then (
        $a=>map:put("seq", (
          $a=>map:get("seq"),
          for $j in 1 to $p return $a=>sparse:get($j + 1)
        ))
      ) else $a
    ) else (
      let $a := $a=>sparse:put($t + 1, $a=>sparse:get($t - $p + 1))
      let $a :=
        if ($a=>sparse:get($t + 1) > 0)
        then this:ruskey($a, $k, $n, $t + 1, $p, $ones + 1)
        else this:ruskey($a, $k, $n, $t + 1, $p, $ones)
      return
        fold-left($a=>sparse:get($t - $p + 1) + 1 to $k - 1, $a,
          function($a as map(*), $j as xs:integer) as map(*) {
            let $a := $a=>sparse:put($t + 1, $j)
            return this:ruskey($a, $k, $n, $t + 1, $t, $ones + 1)
          }
        )
    )
  ) else (
    $a
  )
};

(:~
 : debruijn()
 : Construct a deBruijn sequence of order n using the given alphabet
 : Cyclic sequence where every possible sequence of length n occurs exactly
 : once as a contiguous subsequence in the cycle.
 :
 : Example: debruijn(("a","b"), 3) = ("a","a","a","b","a","b","b","b")
 : Example: debruijn(2, 3) = (0,0,0,1,0,1,1,1)
 :
 : @param $alphabet: either the sequence of alphabet values to use or an integer
 :   indicating the numerical range to use, e.g. 2 => (0,1)
 : @param $n the order
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:debruijn(
  $alphabet as item()*,
  $n as xs:integer
)
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  let $alphabet :=
    if ($alphabet instance of xs:integer) then 0 to $alphabet - 1
    else $alphabet
  let $k := count($alphabet)
  let $a := sparse:array()=>sparse:put(1,0)
  let $a := this:ruskey($a, $k, $n, 1, 1, 0)
  let $res := $a=>map:get("seq")
  for $ix in $res return $alphabet[$ix + 1]
};

(:~
 : prime-sequence()
 : A sequence of digits in range [0, k-1] from the division of two
 : mutually prime numbers (use actual primes or rand:mutual-prime-pair())
 : If the numbers aren't mutually prime, the sequence gets boring.
 : You want q > r, in general.
 : 
 : @param $n: length of sequence
 : @param $k: number range (e.g. 2 = binary 0 and 1)
 : @param $q: first number
 : @param $r: second number
 : @return sequence
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:prime-sequence(
  $n as xs:integer,
  $k as xs:integer,
  $q as xs:integer,
  $r as xs:integer
) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n)) else
  for $i in 1 to $n
  let $dx := (($k*$q - $r) * $i) mod ($k*$q)
  return
    (
      for $i in 1 to $k - 1
      return if ($dx < $i*$q) then $i - 1 else (),
      $k
    )[1]
};

(:~
 : permutations()
 : All permutations of the input. Very naive algorithm: Don't use
 : on large sequences. Doesn't eliminate duplicates if there are
 : duplicate values within the input.
 :
 : @param $input: the input sequence of values to permute
 : @return sequence of arrays, one per permutation
 :)
declare function this:permutations($input as item()*) as array(*)*
{
  let $N := count($input)
  return (
    switch ($N)
    case 0 return $input
    case 1 return array {$input}
    case 2 return (
      array {$input},
      array {reverse($input)}
    )
    case 3 return (
      array {$input},
      array {($input[1], $input[3], $input[2])},
      array {($input[2], $input[1], $input[3])},
      array {($input[2], $input[3], $input[1])},
      array {($input[3], $input[1], $input[2])},
      array {reverse($input)}
    )
    default return (
      for $i in 1 to $N return (
        for $c in this:permutations($input[position()!=$i])
        return
          array {(
            $input[$i],
            util:array-values($c)
         )}
      )
    )
  )
};

(:~
 : foreach-permutation()
 : Execute a function over all permutations of the input. Very naive algorithm:
 : Don't use on large sequences. Doesn't eliminate duplicates if there are
 : duplicate values within the input.
 :
 : @param $input: the input sequence of values to permute
 : @param $f: the function to call for each permutation
 : @return values of function for each permutation
 :)
declare function this:foreach-permutation(
  $input as item()*,
  $f as function(item()*) as item()*
) as item()*
{
  let $N := count($input)
  return (
    switch ($N)
    case 0 return $f($input)
    case 1 return $f($input)
    case 2 return (
      $f($input),
      $f(reverse($input))
    )
    case 3 return (
      $f($input),
      $f(($input[1], $input[3], $input[2])),
      $f(($input[2], $input[1], $input[3])),
      $f(($input[2], $input[3], $input[1])),
      $f(($input[3], $input[1], $input[2])),
      $f(reverse($input))
    )
    default return (
      for $i in 1 to $N return (
        for $c in this:permutations($input[position()!=$i])
        return
          $f((
            $input[$i],
            util:array-values($c)
         ))
      )
    )
  )
};

(:~
 : powerset()
 : Get all combinations of the input.
 :
 : @param $input: the input sequence of values to get combinations from
 : @return sequence of arrays, one per combination
 :)
declare function this:powerset($input as item()*) as array(*)*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  return
    array {
      for $j in 1 to $N
      where $i idiv math:pow(2, $j - 1) mod 2 = 1
      return $input[$j]
    }
};

(:~
 : foreach-combination()
 : Execute a function over all combinations of the input. 
 :
 : @param $input: the input sequence of values to get combinations from
 : @param $f: the function to call for each combination
 : @return values of function over each combinatuion
 :)
declare function this:foreach-combination(
  $input as item()*,
  $f as function(item()*) as item()*
) as item()*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  return (
    $f(
      for $j in 1 to $N
      where $i idiv math:pow(2, $j - 1) mod 2 = 1
      return $input[$j]
    )
  )
};

(:~
 : k-combinations()
 : Members of the powerset of $items that have cardinality $k
 :
 : @param $input: the input sequence of values to get combinations from
 : @param $k: cardinality constraint
 : @return sequence of arrays, one per combination of cardinality $k
 :)
declare function this:k-combinations(
  $input as item()*,
  $k as xs:integer
) as array(*)*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  let $members := (
    for $j in 1 to $N
    where $i idiv math:pow(2, $j - 1) mod 2 = 1
    return $input[$j]
  )
  where count($members)=$k
  return array { $members }
};

(:~
 : foreach-k-combination()
 : Execute a function over all combinations of the input of cardinality k. 
 :
 : @param $input: the input sequence of values to get combinations from
 : @param $k: cardinality constraint
 : @param $f: the function to call for each combination
 : @return values of function over each combinary
 :)
declare function this:foreach-k-combination(
  $input as item()*,
  $k as xs:integer,
  $f as function(item()*) as item()*
) as item()*
{
  let $N := count($input)
  for $i in 0 to (math:pow(2, count($input)) cast as xs:integer) - 1
  let $members := (
    for $j in 1 to $N
    where $i idiv math:pow(2, $j - 1) mod 2 = 1
    return $input[$j]
  )
  where count($members)=$k
  return $f($members)
};

(:~
 : rotate()
 : Rotate a sequence by k slots
 : Example this:rotate((1,0,1,1,1,0,0,0), 2) = (1,1,1,0,0,0,1,0)
 :
 : @param $seq: input sequence
 : @param $k: position in input sequence to start new sequence
 : @return rotated sequence
 :)
declare function this:rotate(
  $seq as item()*,
  $k as xs:integer
) as item()*
{
  $seq[position()>=$k], $seq[position() < $k]
};

(:~
 : lodumo()
 : Modify the input sequence s(n) where a(n) = smallest non-negative integer
 : not yet in list such that a(n) = s(n) mod m
 :
 : See: https://oeis.org/wiki/Lodumo_transform
 :
 : @param $seq: input sequence
 : @param $m: modulo
 : @return lodumo of input sequence
 :)
declare function this:lodumo(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else if (some $a in $seq satisfies $a < 0) then errors:error("ML-BADARGS", ("seq", $seq))
  else (
    fold-left(
       $seq, (),
       function($output as xs:integer*, $a as xs:integer) {
         $output,
         util:while(
           function($an as xs:integer) { $an = $output },
           function($an as xs:integer) { $an + $m },
           $a mod $m
         )
       }
    )
  )
};

(:~
 : primes()
 : Set of primes less than or equal $n
 :
 : @param $n: maximum prime
 : @return sequence of primes
 :)
declare function this:primes($n as xs:integer) as xs:integer*
{
  if ($n <= $util:CANVAS-PRIMES[last()]) then $util:CANVAS-PRIMES[. <= $n]
  else (
    $util:CANVAS-PRIMES,
    (: Brute force, may take a while :)
    for $i in $util:CANVAS-PRIMES[last()] + 1 to $n
    where util:is-prime($i)
    return $i
  )
};

(:~
 : prime-factors()
 : Prime factors of the number, excluding 1, in ascending order
 : So factor(1) = (); factor(12) = (2, 2, 3)
 : n > 0
 :
 : @param $n: number to factor
 : @return sequence of prime factors
 : @error ML-BADARGS if $n is less than 1
 :)
declare function this:prime-factors($n as xs:integer) as xs:integer*
{
  if ($n lt 1) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 1) then ()
  else if ($n = $util:CANVAS-PRIMES) then $n
  else (
    let $factors :=
      let $primes := reverse(this:primes(math:sqrt($n) cast as xs:integer))
      return (
        fold-left($primes, $n,
          function($factors as xs:integer*, $prime as xs:integer) as xs:integer* {
            util:while(
              function($residue as xs:integer*) { head($residue) mod $prime = 0 },
              function($residue as xs:integer*) { head($residue) idiv $prime, $prime, tail($residue) },
              head($factors)
            ),
            tail($factors)
          }
        )
      )
    for $f in $factors order by $f ascending where $f!=1 return $f
  )
};

(:~
 : factors()
 : All distinct factors of the number, including 1
 : n >= 0
 :
 : @param $n: number to factor
 : @return set of distinct factors
 : @error ML-BADARGS if $n is less than zero
 :)
declare function this:factors($n as xs:integer) as xs:integer*
{
  if ($n < 0) then errors:error("ML-BADARGS", ("n", $n))
  else if ($n = 0) then 0
  else (
    for $f in 
    distinct-values(
      for $p in this:powerset(this:prime-factors($n))
      return fold-left(array:flatten($p), 1, function($prod as xs:integer, $f as xs:integer) {$prod*$f})
    )
    order by $f ascending
    return $f
  )
};

(:~
 : continued-fraction()
 : Generated the continued fraction sequence for the given number.
 : $x=π => 3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3,...
 : $x=e => 2, 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8, ...
 : If $x is rational this is finite but this function will return 0s
 : to fill out the sequence (technically wrong: should be INF)
 : 
 : @param $n: now many terms to generate
 : @param $x: the number to use
 :)
declare function this:continued-fraction(
  $n as xs:integer,
  $x as xs:double
) as xs:integer*
{
  fold-left(1 to $n, ($x, ()),
    function($data as xs:numeric*, $i as xs:integer) {
      let $fraction := head($data) - floor(head($data))
      return (
        if ($fraction = 0) then 0
        else 1 div $fraction
      ),
      tail($data),
      floor(head($data)) cast as xs:integer
    }
  )=>tail()
};

(:~
 : convergents()
 : Get the numerator/denominator pairs of the convergents of the sequence,
 : taken as a continued fraction. Each pair $pair(1)/$pair(2) represents
 : an approximation of the value represented by the sequence.
 :
 : @param $seq: sequence to interpret as continued fraction sequence
 : @return sequence of the successive rational approximations
 :)
declare function this:convergents($seq as xs:integer*) as array(xs:integer)*
{
  let $h :=
    fold-left(1 to count($seq), (0, 1),
      function($h as xs:integer*, $i as xs:integer) {
        $h,
        $seq[$i]*$h[2 + $i - 1] + $h[2 + $i - 2]
      }
    )=>tail()=>tail()
  let $k :=
    fold-left(1 to count($seq), (1, 0),
      function($k as xs:integer*, $i as xs:integer) {
        $k,
        $seq[$i]*$k[2 + $i - 1] + $k[2 + $i - 2]
      }
    )=>tail()=>tail()
  for $n in 1 to count($seq) return (
    array { $h[$n], $k[$n] }
  )
};

(:~
 : numerators()
 : Get the numerators of the convergents of the sequence,
 : taken as a continued fraction. 
 :
 : @param $seq: sequence to interpret as continued fraction sequence
 : @return sequence of the numerators of successive rational approximations
 :)
declare function this:numerators($seq as xs:integer*) as xs:integer*
{
  fold-left(1 to count($seq), (0, 1),
    function($h as xs:integer*, $i as xs:integer) {
      $h,
      $seq[$i]*$h[2 + $i - 1] + $h[2 + $i - 2]
    }
  )=>tail()=>tail()
};

(:~
 : denominators()
 : Get the denominators of the convergents of the sequence,
 : taken as a continued fraction. 
 :
 : @param $seq: sequence to interpret as continued fraction sequence
 : @return sequence of the denominators of successive rational approximations
 :)
declare function this:denominators($seq as xs:integer*) as xs:integer*
{
  fold-left(1 to count($seq), (1, 0),
    function($k as xs:integer*, $i as xs:integer) {
      $k,
      $seq[$i]*$k[2 + $i - 1] + $k[2 + $i - 2]
    }
  )=>tail()=>tail()
};

(:~ 
 : μ()
 : Möbius μ function:
 : 0: if n has 1 or more repeated prime factors
 : 1: if n = 1
 : (-1)^k: if n is product of k distinct primes
 :
 : @param $n: number to calculate Möbius μ function for
 : @return μ(n)
 :)
declare function this:μ($n as xs:integer) as xs:integer
{
  if ($n = 1) then 1 else (
    let $factors := this:prime-factors($n)
    let $k := count($factors)
    let $d := count(distinct-values($factors))
    return (
      if ($k = $d) then (
        if ($k mod 2 = 0) then 1 else -1
      ) else 0
    )
  )
};

(:~
 : mobius()
 : Möbius transform b[n] = Σ (μ(n/d)a[d])
 :                        d|n  d divides into n
 :
 : @param $seq: sequence to apply transform to
 : @return Möbius transform of the sequence
 :)
declare function this:mobius($seq as xs:integer*) as xs:integer*
{
  for $n in 1 to count($seq)
  let $factors := distinct-values(
    for $p in this:powerset(this:prime-factors($n))
    return fold-left(array:flatten($p), 1, function($prod as xs:integer*, $f as xs:integer) {$prod*$f})
  )
  return (
    if ($seq[$n] = 0) then 0
    else sum(for $d in $factors return this:μ($n idiv $d) * $seq[$d])
  )
};

(:~
 : binomial()
 :                                          n
 : Binomial transform of a sequence: b[n] = Σ (n choose k) a[k]
 :                                         k=0
 :
 : @param $seq: sequence to apply transform to
 : @return binomial transform of the sequence
 :)
declare function this:binomial($seq as xs:integer*) as xs:integer*
{
  for $n in 0 to count($seq) - 1 return (
    sum(
      for $k in 0 to $n return util:binomial($n, $k)*$seq[$k + 1]
    ) cast as xs:integer
  )  
};

(:~
 : ordinal()
 :        
 : Ordinal transformation: b[n] = count of number of times a[n] appears in a[i]
 : sequence up to and include a[n]
 :
 : @param $seq: sequence to apply transform to
 : @return ordinal transform of the sequence
 :)
declare function this:ordinal($seq as xs:integer*) as xs:integer*
{
  for $a at $i in $seq return count($seq[position() <= $i and .=$a])
};

(:~
 : mirror()
 :        
 : Mirror transformation: reversed negation of first half of sequence
 : followed by first half of sequence
 :
 : @param $seq: sequence to apply transform to
 : @return mirror transform of the sequence
 :)
declare function this:mirror($seq as xs:integer*) as xs:integer*
{
  let $n := count($seq)
  return (
    reverse($seq[position() <= $n div 2])!(-.),
    $seq[position() <= $n div 2]
  )
};

(:~
 : alternate()
 :        
 : Alternation transformation: b[n] = a[n] is n mod 2 = 1 else -a[n]
 :
 : @param $seq: sequence to apply transform to
 : @return alternate transform of the sequence
 :)
declare function this:alternate($seq as xs:integer*) as xs:integer*
{
  for $val at $i in $seq return (
    if ($i mod 2 = 1) then abs($val) else -abs($val)
  )
};

(:~
 : shift()
 :        
 : Shift transformation: b[n] = a[n]+1
 : A lot of the sequences produce 0 values which we may not want.
 :
 : @param $seq: sequence to apply transform to
 : @return shift transform of the sequence
 :)
declare function this:shift($seq as xs:integer*) as xs:integer*
{
  $seq!(. + 1)
};

(:~
 : modix()
 : Modify the input sequence s(n) where a(n) = util:modix(s(n), m)
 : i.e. s(n) mod m shifted to 1 to m range (maps negative values as well)
 :
 : @param $seq: input sequence
 : @param $m: modulo
 : @return modix of input sequence
 :)
declare function this:modix(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    $seq!util:modix(., $m)
  )
};

(:~
 : modp()
 : Modify the input sequence s(n) where a(n) = s(n) if s(n) mod m != 0, 0
 : otherwise.
 :
 : @param $seq: input sequence
 : @param $m: modulo
 : @return input sequence when value is positive mod m, 0 when not
 :)
declare function this:modp(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $a in $seq return if ($a mod $m = 0) then 0 else $a
  )
};

(:~
 : modz()
 : Modify the input sequence s(n) where a(n) = s(n) if s(n) mod m = 0, 0
 : otherwise. 
 :
 : @param $seq: input sequence
 : @param $m: modulo
 : @return input sequence when value is zero mod m, 0 when not
 :)
declare function this:modz(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $a in $seq return if ($a mod $m = 0) then $a else 0
  )
};

(:~
 : unmodp()
 : Modify the input sequence s(n) where a(n) = $n if s(n) mod m != 0, 0
 : otherwise.
 :
 : @param $seq: input sequence
 : @param $m: modulo
 : @return index when value is positive mod m, 0 when not
 :)
declare function this:unmodp(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $i in 1 to count($seq) return if ($seq[$i] mod $m = 0) then 0 else $i
  )
};

(:~
 : unmodz()
 : Modify the input sequence s(n) where a(n) = $n if s(n) mod m = 0, 0
 : otherwise. 
 :
 : @param $seq: input sequence
 : @param $m: modulo
 : @return index when value is zero mod m, 0 when not
 :)
declare function this:unmodz(
  $seq as xs:integer*,
  $m as xs:integer
) as xs:integer*
{
  if ($m lt 1) then errors:error("ML-BADARGS", ("m", $m))
  else (
    for $i in 1 to count($seq) return if ($seq[$i] mod $m = 0) then $i else 0
  )
};

(:~
 : compose()
 : Compose two integer sequences using the particular operation.
 : If the sequences are different lengths, loop back to start of shorter seq.
 : e.g. compose((1,2,3,4),(-1,1),function($a,$b) {$a * $b}) = (-1,2,-3,4)
 :
 : @param $seq1: one sequence
 : @param $seq2: other sequence
 : @param $op composition function
 :)
declare function this:compose(
  $seq1 as xs:integer*,
  $seq2 as xs:integer*,
  $op as function(xs:integer,xs:integer) as xs:integer
) as xs:integer*
{
  let $n1 := count($seq1)
  let $n2 := count($seq2)
  for $i in 1 to (max(($n1,$n2)) cast as xs:integer)
  let $a := $seq1[util:modix($i, $n1)]
  let $b := $seq2[util:modix($i, $n2)]
  return $op($a, $b)
};

(:~
 : double()
 : String to double with some named special cases for π, e, φ, and square
 : roots given as, e.g. √2
 : Used for things like sequence("continued-fraction@√2")
 :)
declare %private function this:double($s as xs:string) as xs:double
{
  if ($s="π") then math:pi()
  else if ($s="φ") then (1 + math:sqrt(5)) div 2
  else if ($s="e") then math:exp(1)
  else if (starts-with($s, "√")) then math:sqrt(this:double(substring-after($s,"√")))
  else xs:double($s)
};

(:~
 : sequence()
 : Get an integer sequence by name. The name is either the simple name of
 : the sequence, or the sequence modified.
 :
 : Bare names:
 : fibonacci (Fibonacci sequence), fibonacci-word, padovan, fractal, golomb,
 : lazy-caterer, jacobsthal, jacobsthal-lucas, recaman, gerrymander2,
 : inventory, pascal, katydid, grasshopper, thue-morse, feigenbaum1,
 : ruler (Ruler sequence), primes, counter
 :
 : Parameterized names:
 : thue-morse@k = thue-morse(n,k)
 : prime-sequence@k@q@r = prime-sequence(n, k, q, r)
 : continued-fraction@d = continued-fraction(n, d)
 :
 : Operators: apply the operator to the sequence so far
 : mobius = seq=>mobius()
 : binomial = seq=>binomial()
 : ordinal = seq=>ordinal()
 : numerators = seq=>numerators()
 : denominators = seq=>denominators()
 : mirror = seq=>mirror()
 : alternate = seq=>alternate()
 : shift = seq=>shift()
 :
 : Parameterized operators:
 : rotate@k = seq=>rotate(k)
 : lodumo@m = seq=>lodumo(m)
 : modix@m = seq=>modix(m)
 : modp@m = seq=>modp(m)
 : modz@m = seq=>modz(m)
 : unmodp@m = seq=>unmodp(m)
 : unmodz@m = seq=>unmodz(m)
 :
 : seq1+operator = apply operator to seq1
 : seq1+seq1 = seq1 followed by seq2 (so sequence length will be 2*$n)
 :
 : @param $name: name of sequence
 : @param $n: length of each component sequence
 : @return sequence
 :)
declare function this:sequence(
  $name as xs:string,
  $n as xs:integer
) as xs:integer*
{
  fold-left(tokenize($name, "[+]")[. ne ""], (),
    function($seq as xs:integer*, $part as xs:string) {
      switch($part)
      case "none" return $seq
      case "counter" return ($seq, 1 to $n)
      case "fibonacci" return ($seq, this:fibonacci-sequence($n))
      case "fibonacci-word" return ($seq, this:fibonacci-word($n))
      case "padovan" return ($seq, this:padovan($n))
      case "fractal" return ($seq, this:fractal($n))
      case "golomb" return ($seq, this:golomb($n))
      case "lazy-caterer" return ($seq, this:lazy-caterer($n))
      case "jacobsthal" return ($seq, this:jacobsthal($n))
      case "jacobsthal-lucas" return ($seq, this:jacobsthal-lucas($n))
      case "recaman" return ($seq, this:recaman($n))
      case "gerrymander2" return ($seq, this:gerrymander2($n))
      case "inventory" return ($seq, this:inventory($n))
      case "pascal" return ($seq, this:pascal($n))
      case "katydid" return ($seq, this:katydid($n))
      case "grasshopper" return ($seq, this:grasshopper($n))
      case "thue-morse" return ($seq, this:thue-morse($n))
      case "feigenbaum1" return ($seq, this:feigenbaum1($n))
      case "ruler" return ($seq, this:ruler-sequence($n))
      case "primes" return ($seq, this:primes($n))
      case "mobius" return $seq=>this:mobius()
      case "binomial" return $seq=>this:binomial()
      case "ordinal" return $seq=>this:ordinal()
      case "numerators" return $seq=>this:numerators()
      case "denominators" return $seq=>this:denominators()
      case "mirror" return $seq=>this:mirror()
      case "alternate" return $seq=>this:alternate()
      case "shift" return $seq=>this:shift()
      default return ( (: More parsing required :)
        let $pieces := tokenize($part,"@")[. ne ""]
        return switch ($pieces[1])
        case "thue-morse" return ($seq, this:thue-morse($n, xs:integer($pieces[2])))
        case "prime-sequence" return ($seq, this:prime-sequence($n, xs:integer($pieces[2]), xs:integer($pieces[3]), xs:integer($pieces[4])))
        case "rotate" return $seq=>this:rotate(xs:integer($pieces[2]))
        case "lodumo" return $seq=>this:lodumo(xs:integer($pieces[2]))
        case "modix" return $seq=>this:modix(xs:integer($pieces[2]))
        case "continued-fraction" return ($seq, this:continued-fraction($n, this:double($pieces[2])))
        case "modp" return $seq=>this:modp(xs:integer($pieces[2]))
        case "modz" return $seq=>this:modz(xs:integer($pieces[2]))
        case "unmodp" return $seq=>this:unmodp(xs:integer($pieces[2]))
        case "unmodz" return $seq=>this:unmodz(xs:integer($pieces[2]))
        default return $seq
      )
    }
  )
};