Category talk:Wren-math: Difference between revisions
Content deleted Content added
m →Source code: Change to comment. |
→Source code: Nums.barChart method now allows for left or right alignment of labels, |
||
Line 933: | Line 933: | ||
// Draws a horizontal bar chart on the terminal representing a list of numerical 'data' |
// Draws a horizontal bar chart on the terminal representing a list of numerical 'data' |
||
// which must be non-negative. 'labels' is a list of the corresponding text for each bar. |
// which must be non-negative. 'labels' is a list of the corresponding text for each bar. |
||
// When printed |
// When printed and unless they are all the same length, 'labels' are right justified |
||
// within their maximum length if 'rjust' is true, otherwise they are left justified. |
|||
// 'width' is the total width of the chart in characters to which the labels/data will |
// 'width' is the total width of the chart in characters to which the labels/data will |
||
// automatically be scaled. 'symbol' is the character to be used to draw each bar, |
// automatically be scaled. 'symbol' is the character to be used to draw each bar, |
||
// 'symbol2' is used to represent scaled non-zero data and 'symbol3' zero data which |
// 'symbol2' is used to represent scaled non-zero data and 'symbol3' zero data which |
||
// would otherwise be blank. The actual data is printed at the end of each bar. |
// would otherwise be blank. The actual data is printed at the end of each bar. |
||
static barChart (title, width, labels, data, symbol, symbol2, symbol3) { |
static barChart (title, width, labels, data, rjust, symbol, symbol2, symbol3) { |
||
var barCount = labels.count |
var barCount = labels.count |
||
if (data.count != barCount) Fiber.abort("Mismatch between labels and data.") |
if (data.count != barCount) Fiber.abort("Mismatch between labels and data.") |
||
var maxLabelLen = max(labels.map { |l| l.count }) |
var maxLabelLen = max(labels.map { |l| l.count }) |
||
if (labels.any { |l| l.count < maxLabelLen }) { |
|||
if (rjust) { |
|||
labels = labels.map { |l| (" " * (maxLabelLen - l.count)) + l }.toList |
|||
} else { |
|||
labels = labels.map { |l| l + (" " * (maxLabelLen - l.count)) }.toList |
|||
} |
|||
} |
|||
var maxData = max(data) |
var maxData = max(data) |
||
var maxLen = width - maxLabelLen - maxData.toString.count - 2 |
var maxLen = width - maxLabelLen - maxData.toString.count - 2 |
||
Line 957: | Line 964: | ||
// Convenience version of the above method which uses default symbols. |
// Convenience version of the above method which uses default symbols. |
||
static barChart(title, width, labels, data, rjust) { |
|||
barChart(title, width, labels, data, rjust, "■", "◧", "□") |
|||
} |
|||
// As above method but uses right justification for labels. |
|||
static barChart(title, width, labels, data) { |
static barChart(title, width, labels, data) { |
||
barChart(title, width, labels, data, "■", "◧", "□") |
barChart(title, width, labels, data, true, "■", "◧", "□") |
||
} |
} |
||
} |
} |
Revision as of 22:11, 23 May 2023
Source code
/* Module "math.wren" */
/* Math supplements the Num class with various other operations on numbers. */
class Math {
// Constants.
static e { 2.71828182845904523536 } // base of natural logarithms
static phi { 1.6180339887498948482 } // golden ratio
static ln2 { 0.69314718055994530942 } // natural logarithm of 2
static ln10 { 2.30258509299404568402 } // natural logarithm of 10
// Log function.
static log10(x) { x.log/ln10 } // Base 10 logarithm
// Hyperbolic trig functions.
static sinh(x) { (x.exp - (-x).exp)/2 } // sine
static cosh(x) { (x.exp + (-x).exp)/2 } // cosine
static tanh(x) { sinh(x)/cosh(x) } // tangent
// Inverse hyperbolic trig functions.
static asinh(x) { (x + (x*x + 1).sqrt).log } // sine
static acosh(x) { (x + (x*x - 1).sqrt).log } // cosine
static atanh(x) { ((1+x)/(1-x)).log/2 } // tangent
// Angle conversions.
static radians(d) { d * Num.pi / 180}
static degrees(r) { r * 180 / Num.pi }
// Returns the square root of 'x' squared + 'y' squared.
static hypot(x, y) { (x*x + y*y).sqrt }
// Returns the integer and fractional parts of 'x'. Both values have the same sign as 'x'.
static modf(x) { [x.truncate, x.fraction] }
// Returns the IEEE 754 floating-point remainder of 'x/y'.
static rem(x, y) {
if (x.isNan || y.isNan || x.isInfinity || y == 0) return nan
if (!x.isInfinity && y.isInfinity) return x
var nf = modf(x/y)
if (nf[1] != 0.5) {
return x - (x/y).round * y
} else {
var n = nf[0]
if (n%2 == 1) n = (n > 0) ? n + 1 : n - 1
return x - n * y
}
}
// Round away from zero.
static roundUp(x) { (x >= 0) ? x.ceil : x.floor }
// Round to 'p' decimal places, maximum 14.
// Mode parameter specifies the rounding mode:
// < 0 towards zero, == 0 nearest, > 0 away from zero.
static toPlaces(x, p, mode) {
if (p < 0) p = 0
if (p > 14) p = 14
var pw = 10.pow(p)
var nf = modf(x)
x = nf[1] * pw
x = (mode < 0) ? x.truncate : (mode == 0) ? x.round : roundUp(x)
return nf[0] + x/pw
}
// Convenience version of above method which uses 0 for the 'mode' parameter.
static toPlaces(x, p) { toPlaces(x, p, 0) }
// Returns the linear interpolation of t between x and y, if t is in [0, 1] or
// linear extrapolation otherwise.
static lerp (x, y, t) { x + t * (y - x) }
// The power of 'p' which equals or first exceeds a non-negative number 'x'.
// 'p' must be an integer greater than 1.
// Returns a two element list containing the power and the exponent.
static nextPower(x, p) {
if (!((x is Num) && x >= 0)) Fiber.abort("x must be a non-negative number.")
if (!((p is Num) && p.isInteger && p > 1)) Fiber.abort("p must be an integer > 1.")
if (x <= 1) return [1, 0]
var pow = 1
var count = 0
while (x > pow) {
pow = pow * p
count = count + 1
}
return [pow, count]
}
// Returns the value of a polynomial when the unknown is 'x' using Horner's rule.
// Polynomials are represented by an ordered list of coefficients
// from the term with the highest degree down to the constant term.
static evalPoly(coefs, x) {
if (!(coefs is List) || coefs.count == 0 || !(coefs[0] is Num)) {
Fiber.abort("coefs must be a non-empty ordered list of numbers.")
}
if (!(x is Num)) Fiber.abort("x must be a number.")
var c = coefs.count
if (c == 1) return coefs[0]
var sum = coefs[0]
for (i in 1...c) sum = sum * x + coefs[i]
return sum
}
// Returns the coefficients of a polynomial following differentiation.
// Polynomials are represented as described in 'evalPoly' above.
static diffPoly(coefs) {
if (!(coefs is List) || coefs.count == 0 || !(coefs[0] is Num)) {
Fiber.abort("coefs must be a non-empty ordered list of numbers.")
}
var c = coefs.count - 1
if (c == 0) return [0]
var deriv = List.filled(c, 0)
for (i in 0...c) deriv[i] = (c - i) * coefs[i]
return deriv
}
// Gamma function using Lanczos approximation.
static gamma(x) {
var p = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7
]
var t = x + 6.5
var sum = p[0]
for (i in 0..7) sum = sum + p[i+1]/(x + i)
return 2.sqrt * Num.pi.sqrt * t.pow(x-0.5) * (-t).exp * sum
}
// The natural logarithm of the absolute value of gamma(x).
static lgamma(x) {
if (x == 1 || x == 2) return 0
return Math.gamma(x).abs.log
}
// Returns the positive difference of two numbers.
static dim(x, y) { (x >= y) ? x - y : 0 }
// Static alternatives to instance methods in Num class.
// Clearer when both arguments are complex expressions.
static min(x, y) { (x < y) ? x : y }
static max(x, y) { (x > y) ? x : y }
static atan(x, y) { x.atan(y) }
}
/* Int contains various routines which are only applicable to integers. */
class Int {
// Truncated integer division (consistent with % operator).
static quo(x, y) { (x/y).truncate }
// Floored integer division (consistent with 'mod' method below).
static div(x, y) { (x/y).floor }
// Floored integer division modulus.
static mod(x, y) { ((x % y) + y) % y }
// Returns the integer square root of 'x' or null if 'x' is negative.
static sqrt(x) { (x >= 0) ? x.sqrt.floor : null }
// Returns the integer cube root of 'x'.
static cbrt(x) { x.cbrt.truncate }
// Returns the integer 'n'th root of 'x' or null if 'x' is negative and 'n' is even.
static root(n, x) {
if (!(n is Num) || !n.isInteger || n < 1) {
Fiber.abort("n must be a positive integer.")
}
return (n == 1) ? x :
(n == 2) ? sqrt(x) :
(n == 3) ? cbrt(x) :
(n % 2 == 1) ? x.sign * x.abs.pow(1/n).floor :
(n >= 0) ? x.pow(1/n).floor : null
}
// Returns whether or not 'x' is a perfect square.
static isSquare(x) {
var s = sqrt(x)
return s * s == x
}
// Returns whether or not 'x' is a perfect cube.
static isCube(x) {
var c = cbrt(x)
return c * c * c == x
}
// Returns whether or not 'x' is a perfect 'n'th power.
static isPower(n, x) {
var r = root(n, x)
return r.pow(n) == x
}
// Returns the greatest common divisor of 'x' and 'y'.
static gcd(x, y) {
while (y != 0) {
var t = y
y = x % y
x = t
}
return x.abs
}
// Returns the least common multiple of 'x' and 'y'.
static lcm(x, y) {
if (x == 0 && y == 0) return 0
return (x*y).abs / gcd(x, y)
}
// Returns the greatest common divisor of a list of integers 'a'.
static gcd(a) {
if (!(a is List) || a.count < 2) {
Fiber.abort("Argument must be a list of at least two integers.")
}
var g = gcd(a[0], a[1])
if (a.count == 2) return g
return gcd(a[2..-1] + [g])
}
// Returns the least common multiple of a list of integers 'a'.
static lcm(a) {
if (!(a is List) || a.count < 2) {
Fiber.abort("Argument must be a list of at least two integers.")
}
var l = lcm(a[0], a[1])
if (a.count == 2) return l
return lcm(a[2..-1] + [l])
}
// Private helper method for modMul method.
static checkedAdd_(a, b, c) {
var room = c - 1 - a
if (b <= room) {
a = a + b
} else {
a = b - room - 1
}
return a
}
// Returns 'x' multiplied by 'n' modulo 'm'.
static modMul(x, n, m) {
if (m == 0) Fiber.abort("Cannot take modMul with modulus 0.")
var sign = x.sign * n.sign
var z = 0
m = m.abs
x = x.abs % m
n = n.abs % m
if (n > x) {
var t = x
x = n
n = t
}
while (n > 0) {
if (n % 2 == 1) z = checkedAdd_(z, x, m)
x = checkedAdd_(x, x, m)
n = div(n, 2)
}
return z * sign
}
// Returns the remainder when 'b' raised to the power 'e' is divided by 'm'.
static modPow(b, e, m) {
if (m == 0) Fiber.abort("Cannot take modPow with modulus 0.")
if (m == 1 || m == -1) return 0
var r = 1
b = b % m
if (e < 0) {
e = -e
b = modInv(b, m)
}
while (e > 0) {
if (b == 0) return 0
if (e % 2 == 1) r = modMul(r, b, m)
e = div(e, 2)
b = modMul(b, b, m)
}
return r
}
// Returns the multiplicative inverse of 'x' modulo 'n'.
static modInv(x, n) {
var r = n
var newR = x.abs
var t = 0
var newT = 1
while (newR != 0) {
var q = quo(r, newR)
var lastT = t
var lastR = r
t = newT
r = newR
newT = lastT - q * newT
newR = lastR - q * newR
}
if (r != 1) Fiber.abort("%(x) and %(n) are not co-prime.")
if (t < 0) t = t + n
return (x < 0) ? -t : t
}
// Returns the factorial of 'n'.
static factorial(n) {
if (!(n is Num && n >= 0 && n < 19)) {
Fiber.abort("Argument must be a non-negative integer < 19.")
}
if (n < 2) return 1
var fact = 1
for (i in 2..n) fact = fact * i
return fact
}
// Returns the multinomial coefficient of n over a list f where sum(f) == n.
static multinomial(n, f) {
if (!(n is Num && n >= 0 && n < 19)) {
Fiber.abort("First argument must be a non-negative integer < 19.")
}
if (!(f is List)) Fiber.abort("Second argument must be a list.")
var sum = f.reduce { |acc, i| acc + i }
if (n != sum) {
Fiber.abort("The elements of the list must sum to 'n'.")
}
var prod = 1
for (e in f) {
if (e < 0) Fiber.abort("The elements of the list must be non-negative integers.")
if (e > 1) prod = prod * factorial(e)
}
return factorial(n)/prod
}
// Returns the binomial coefficent of n over k.
static binomial(n, k) { multinomial(n, [k, n-k]) }
// Private helper method for 'isPrime' method.
// Returns true if 'n' is prime, false otherwise but uses the
// Miller-Rabin test which should be faster for 'n' >= 2 ^ 32.
static isPrimeMR_(n) {
if (n%2 == 0) return n == 2
if (n%3 == 0) return n == 3
if (n%5 == 0) return n == 5
if (n%7 == 0) return n == 7
var a
if (n < 4759123141) {
a = [2, 7, 61]
} else if (n < 1122004669633) {
a = [2, 13, 23, 1662803]
} else if (n < 2152302898747) {
a = [2, 3, 5, 7, 11]
} else if (n < 3474749660383) {
a = [2, 3, 5, 7, 11, 13]
} else if (n < 341550071728321) {
a = [2, 3, 5, 7, 11, 13, 17]
} else {
a = [2, 3, 5, 7, 11, 13, 17, 19, 23]
}
var nPrev = n - 1
var b = nPrev
var r = 0
while (b % 2 == 0) {
b = div(b, 2)
r = r + 1
}
for (i in 0...a.count) {
if (n >= a[i]) {
var x = modPow(a[i], b, n)
if (x != 1 && x != nPrev) {
var d = r - 1
var next = false
while (d != 0) {
x = modMul(x, x, n)
if (x == 1) return false
if (x == nPrev) {
next = true
break
}
d = d - 1
}
if (!next) return false
}
}
}
return true
}
// Determines whether 'n' is prime using a wheel with basis [2, 3, 5].
// Not accessing the wheel via a list improves performance by about 25%.
// Automatically changes to Miller-Rabin approach if 'n' >= 2 ^ 32.
static isPrime(n) {
if (!n.isInteger || n < 2) return false
if (n > 4294967295) return isPrimeMR_(n)
if (n%2 == 0) return n == 2
if (n%3 == 0) return n == 3
if (n%5 == 0) return n == 5
var d = 7
while (d*d <= n) {
if (n%d == 0) return false
d = d + 4
if (n%d == 0) return false
d = d + 2
if (n%d == 0) return false
d = d + 4
if (n%d == 0) return false
d = d + 2
if (n%d == 0) return false
d = d + 4
if (n%d == 0) return false
d = d + 6
if (n%d == 0) return false
d = d + 2
if (n%d == 0) return false
d = d + 6
if (n%d == 0) return false
}
return true
}
// Returns the next prime number greater than 'n'.
static nextPrime(n) {
if (n < 2) return 2
n = (n%2 == 0) ? n + 1 : n + 2
while (true) {
if (Int.isPrime(n)) return n
n = n + 2
}
}
// Sieves for primes up to and including 'n'.
// If primesOnly is true returns a list of the primes found.
// If primesOnly is false returns a bool list 'c' of size (n + 1) where:
// c[i] is false if 'i' is prime or true if 'i' is composite.
static primeSieve(n, primesOnly) {
if (n < 2) return []
var primes = [2]
var k = ((n-3)/2).floor + 1
var marked = List.filled(k, true)
var limit = ((n.sqrt.floor - 3)/2).floor + 1
limit = limit.max(0)
for (i in 0...limit) {
if (marked[i]) {
var p = 2*i + 3
var s = ((p*p - 3)/2).floor
var j = s
while (j < k) {
marked[j] = false
j = j + p
}
}
}
for (i in 0...k) {
if (marked[i]) primes.add(2*i + 3)
}
if (primesOnly) return primes
var c = List.filled(n+1, true)
for (p in primes) c[p] = false
return c
}
// Convenience version of above method which uses true for the primesOnly parameter.
static primeSieve(n) { primeSieve(n, true) }
// Sieves for primes up to and including 'limit' using a segmented approach
// and returns a list of the primes found in order.
// Second argument needs to be the cache size (L1) of your machine in bytes.
// Translated from public domain C++ code at:
// https://gist.github.com/kimwalisch/3dc39786fab8d5b34fee
static segmentedSieve(limit, cacheSize) {
cacheSize = (cacheSize/8).floor // 8 bytes per list element
var sqroot = limit.sqrt.floor
var segSize = sqroot.max(cacheSize)
if (limit < 2) return []
var allPrimes = [2]
var isPrime = List.filled(sqroot + 1, true)
var primes = []
var multiples = []
var i = 3
var n = 3
var s = 3
var low = 0
while (low <= limit) {
var sieve = List.filled(segSize, true)
var high = limit.min(low + segSize - 1)
while (i * i <= high) {
if (isPrime[i]) {
var j = i * i
while (j <= sqroot) {
isPrime[j] = false
j = j + i
}
}
i = i + 2
}
while (s * s <= high) {
if (isPrime[s]) {
primes.add(s)
multiples.add(s * s - low)
}
s = s + 2
}
for (ii in 0...primes.count) {
var j = multiples[ii]
var k = primes[ii] * 2
while (j < segSize) {
sieve[j] = false
j = j + k
}
multiples[ii] = j - segSize
}
while (n <= high) {
if (sieve[n - low]) allPrimes.add(n)
n = n + 2
}
low = low + segSize
}
return allPrimes
}
// Computes and returns how many primes there are up to and including 'n'.
// See https://rosettacode.org/wiki/Legendre_prime_counting_function for details.
static primeCount(n) {
if (n < 2) return 0
if (n < 9) return ((n + 1)/2).floor
var masks = [1, 2, 4, 8, 16, 32, 64, 128]
var half = Fn.new { |n| (n - 1) >> 1 }
var rtlmt = n.sqrt.floor
var mxndx = Int.quo(rtlmt - 1, 2)
var arrlen = (mxndx + 1).max(2)
var smalls = List.filled(arrlen, 0)
var roughs = List.filled(arrlen, 0)
var larges = List.filled(arrlen, 0)
for (i in 0...arrlen) {
smalls[i] = i
roughs[i] = i + i + 1
larges[i] = Int.quo(Int.quo(n, i + i + 1) - 1 , 2)
}
var cullbuflen = Int.quo(mxndx + 8, 8)
var cullbuf = List.filled(cullbuflen, 0)
var nbps = 0
var rilmt = arrlen
var i = 1
while (true) {
var sqri = (i + i) * (i + 1)
if (sqri > mxndx) break
if ((cullbuf[i >> 3] & masks[i & 7]) != 0) {
i = i + 1
continue
}
cullbuf[i >> 3] = cullbuf[i >> 3] | masks[i & 7]
var bp = i + i + 1
var c = sqri
while (c < arrlen) {
cullbuf[c >> 3] = cullbuf[c >> 3] | masks[c & 7]
c = c + bp
}
var nri = 0
for (ori in 0...rilmt) {
var r = roughs[ori]
var rci = r >> 1
if ((cullbuf[rci >> 3] & masks[rci & 7]) != 0) continue
var d = r * bp
var t = (d <= rtlmt) ? larges[smalls[d >> 1] - nbps] :
smalls[half.call(Int.quo(n, d))]
larges[nri] = larges[ori] - t + nbps
roughs[nri] = r
nri = nri + 1
}
var si = mxndx
var pm = ((rtlmt/bp).floor - 1) | 1
while (pm >= bp) {
var c = smalls[pm >> 1]
var e = (pm * bp) >> 1
while (si >= e) {
smalls[si] = smalls[si] - c + nbps
si = si - 1
}
pm = pm - 2
}
rilmt = nri
nbps = nbps + 1
i = i + 1
}
var ans = larges[0] + ((rilmt + 2 * (nbps - 1)) * (rilmt - 1) / 2).floor
for (ri in 1...rilmt) ans = ans - larges[ri]
var ri = 1
while (true) {
var p = roughs[ri]
var m = Int.quo(n, p)
var ei = smalls[half.call(Int.quo(m, p))] - nbps
if (ei <= ri) break
ans = ans - (ei - ri) * (nbps + ri - 1)
for (sri in (ri + 1..ei)) {
ans = ans + smalls[half.call(Int.quo(m, roughs[sri]))]
}
ri = ri + 1
}
return ans + 1
}
// Returns the prime factors of 'n' in order using a wheel with basis [2, 3, 5].
static primeFactors(n) {
if (!n.isInteger || n < 2) return []
var inc = [4, 2, 4, 2, 4, 6, 2, 6]
var factors = []
while (n%2 == 0) {
factors.add(2)
n = (n/2).truncate
}
while (n%3 == 0) {
factors.add(3)
n = (n/3).truncate
}
while (n%5 == 0) {
factors.add(5)
n = (n/5).truncate
}
var k = 7
var i = 0
while (k * k <= n) {
if (n%k == 0) {
factors.add(k)
n = (n/k).truncate
} else {
k = k + inc[i]
i = (i + 1) % 8
}
}
if (n > 1) factors.add(n)
return factors
}
// Returns all the divisors of 'n' including 1 and 'n' itself.
static divisors(n) {
if (!n.isInteger || n < 1) return []
var divisors = []
var divisors2 = []
var i = 1
var k = (n%2 == 0) ? 1 : 2
while (i <= n.sqrt) {
if (n%i == 0) {
divisors.add(i)
var j = (n/i).floor
if (j != i) divisors2.add(j)
}
i = i + k
}
if (!divisors2.isEmpty) divisors = divisors + divisors2[-1..0]
return divisors
}
// Returns all the divisors of 'n' excluding 'n'.
static properDivisors(n) {
var d = divisors(n)
var c = d.count
return (c <= 1) ? [] : d[0..-2]
}
// As 'divisors' method but uses a different algorithm.
// Better for larger numbers.
static divisors2(n) {
var pf = primeFactors(n)
if (pf.count == 0) return (n == 1) ? [1] : pf
var arr = []
if (pf.count == 1) {
arr.add([pf[0], 1])
} else {
var prevPrime = pf[0]
var count = 1
for (i in 1...pf.count) {
if (pf[i] == prevPrime) {
count = count + 1
} else {
arr.add([prevPrime, count])
prevPrime = pf[i]
count = 1
}
}
arr.add([prevPrime, count])
}
var divisors = []
var generateDivs
generateDivs = Fn.new { |currIndex, currDivisor|
if (currIndex == arr.count) {
divisors.add(currDivisor)
return
}
for (i in 0..arr[currIndex][1]) {
generateDivs.call(currIndex+1, currDivisor)
currDivisor = currDivisor * arr[currIndex][0]
}
}
generateDivs.call(0, 1)
return divisors.sort()
}
// As 'properDivisors' but uses 'divisors2' method.
static properDivisors2(n) {
var d = divisors2(n)
var c = d.count
return (c <= 1) ? [] : d[0..-2]
}
// Returns the sum of all the divisors of 'n' including 1 and 'n' itself.
static divisorSum(n) {
if (!n.isInteger || n < 1) return 0
var total = 1
var power = 2
while (n % 2 == 0) {
total = total + power
power = 2 * power
n = div(n, 2)
}
var i = 3
while (i * i <= n) {
var sum = 1
power = i
while (n % i == 0) {
sum = sum + power
power = i * power
n = div(n, i)
}
total = total * sum
i = i + 2
}
if (n > 1) total = total * (n + 1)
return total
}
// Returns the number of divisors of 'n' including 1 and 'n' itself.
static divisorCount(n) {
if (!n.isInteger || n < 1) return 0
var count = 0
var prod = 1
while (n % 2 == 0) {
count = count + 1
n = div(n, 2)
}
prod = prod * (1 + count)
var i = 3
while (i * i <= n) {
count = 0
while (n % i == 0) {
count = count + 1
n = div(n, i)
}
prod = prod * (1 + count)
i = i + 2
}
if (n > 2) prod = prod * 2
return prod
}
// Private helper method which checks a number and base for validity.
static check_(n, b) {
if (!(n is Num && n.isInteger && n >= 0)) {
Fiber.abort("Number must be a non-negative integer.")
}
if (!(b is Num && b.isInteger && b >= 2 && b < 64)) {
Fiber.abort("Base must be an integer between 2 and 63.")
}
}
// Returns a list of an integer n's digits in base b. Optionally checks n and b are valid.
static digits(n, b, check) {
if (check) check_(n, b)
if (n == 0) return [0]
var digs = []
while (n > 0) {
digs.add(n%b)
n = (n/b).floor
}
return digs[-1..0]
}
// Returns the sum of an integer n's digits in base b. Optionally checks n and b are valid.
static digitSum(n, b, check) {
if (check) check_(n, b)
var sum = 0
while (n > 0) {
sum = sum + (n%b)
n = (n/b).floor
}
return sum
}
// Returns the digital root and additive persistence of an integer n in base b.
// Optionally checks n and b are valid.
static digitalRoot(n, b, check) {
if (check) check_(n, b)
var ap = 0
while (n > b - 1) {
n = digitSum(n, b)
ap = ap + 1
}
return [n, ap]
}
// Convenience versions of the above methods which never check for validity
// and/or use base 10 by default.
static digits(n, b) { digits(n, b, false) }
static digits(n) { digits(n, 10, false) }
static digitSum(n, b) { digitSum(n, b, false) }
static digitSum(n) { digitSum(n, 10, false) }
static digitalRoot(n, b) { digitalRoot(n, b, false) }
static digitalRoot(n) { digitalRoot(n, 10, false) }
// Returns a new integer formed by reversing the base 10 digits of 'n'.
// Works with positive, negative and zero integers.
static reverse(n) {
var m = (n >= 0) ? n : -n
var r = 0
while (m > 0) {
r = 10*r + m%10
m = div(m, 10)
}
return r * n.sign
}
// Returns the unique non-negative integer that is associated with a pair
// of non-negative integers 'x' and 'y' according to Cantor's pairing function.
static cantorPair(x, y) {
if (x.type != Num || !x.isInteger || x < 0) {
Fiber.abort("Arguments must be non-negative integers.")
}
if (y.type != Num || !y.isInteger || y < 0) {
Fiber.abort("Arguments must be non-negative integers.")
}
return (x*x + 3*x + 2*x*y + y + y*y) / 2
}
// Returns the pair of non-negative integers that are associated with a single
// non-negative integer 'z' according to Cantor's pairing function.
static cantorUnpair(z) {
if (z.type != Num || !z.isInteger || z < 0) {
Fiber.abort("Argument must be a non-negative integer.")
}
var i = (((1 + 8*z).sqrt-1)/2).floor
return [z - i*(1+i)/2, i*(3+i)/2 - z]
}
}
/*
Nums contains various routines applicable to lists or ranges of numbers
many of which are useful for statistical purposes.
*/
class Nums {
// Methods to calculate sum, various means, product and maximum/minimum element of 'a'.
// The sum and product of an empty list are considered to be 0 and 1 respectively.
static sum(a) { a.reduce(0) { |acc, x| acc + x } }
static mean(a) { sum(a)/a.count }
static geometricMean(a) { a.reduce { |prod, x| prod * x}.pow(1/a.count) }
static harmonicMean(a) { a.count / a.reduce { |acc, x| acc + 1/x } }
static quadraticMean(a) { (a.reduce(0) { |acc, x| acc + x*x }/a.count).sqrt }
static prod(a) { a.reduce(1) { |acc, x| acc * x } }
static max(a) { a.reduce { |acc, x| (x > acc) ? x : acc } }
static min(a) { a.reduce { |acc, x| (x < acc) ? x : acc } }
// As above methods but applying a function 'f' to each element of 'a'
// before performing the operation.
// 'f' should take a single Num parameter and return a Num.
static sum(a, f) { a.reduce(0) { |acc, x| acc + f.call(x) } }
static mean(a, f) { sum(a, f)/a.count }
static geometricMean(a, f) { a.reduce { |prod, x| prod * f.call(x)}.pow(1/a.count) }
static harmonicMean(a, f) { a.count / a.reduce { |acc, x| acc + 1/f.call(x) } }
static quadraticMean(a, f) { (a.reduce(0) { |acc, x| acc + f.call(x).pow(2) }/a.count).sqrt }
static prod(a, f) { a.reduce(1) { |acc, x| acc * f.call(x) } }
static max(a, f) { a.reduce { |acc, x|
var fx = f.call(x)
return (fx > acc) ? fx : acc
} }
static min(a, f) { a.reduce { |acc, x|
var fx = f.call(x)
return (fx < acc) ? fx : acc
} }
// Returns the median of a sorted list 'a'.
static median(a) {
var c = a.count
if (c == 0) {
Fiber.abort("An empty list cannot have a median")
} else if (c%2 == 1) {
return a[(c/2).floor]
} else {
var d = (c/2).floor
return (a[d] + a[d-1])/2
}
}
// Returns a list whose first element is a list of the mode(s) of 'a'
// and whose second element is the number of times the mode(s) occur.
static modes(a) {
var m = {}
for (e in a) m[e] = (!m[e]) ? 1 : m[e] + 1
var max = 0
for (e in a) if (m[e] > max) max = m[e]
var res = []
for (k in m.keys) if (m[k] == max) res.add(k)
return [max, res]
}
// Returns the sample variance of 'a'.
static variance(a) {
var m = mean(a)
var c = a.count
return (a.reduce(0) { |acc, x| acc + x*x } - m*m*c) / (c-1)
}
// Returns the population variance of 'a'.
static popVariance(a) {
var m = mean(a)
return (a.reduce(0) { |acc, x| acc + x*x }) / a.count - m*m
}
// Returns the sample standard deviation of 'a'.
static stdDev(a) { variance(a).sqrt }
// Returns the population standard deviation of 'a'.
static popStdDev(a) { popVariance(a).sqrt }
// Returns the mean deviation of 'a'.
static meanDev(a) {
var m = mean(a)
return a.reduce { |acc, x| acc + (x - m).abs } / a.count
}
// Converts a string list to a corresponding numeric list.
static fromStrings(a) { a.map { |s| Num.fromString(s) }.toList }
// Converts a numeric list to a corresponding string list.
static toStrings(a) { a.map { |n| n.toString }.toList }
// Draws a horizontal bar chart on the terminal representing a list of numerical 'data'
// which must be non-negative. 'labels' is a list of the corresponding text for each bar.
// When printed and unless they are all the same length, 'labels' are right justified
// within their maximum length if 'rjust' is true, otherwise they are left justified.
// 'width' is the total width of the chart in characters to which the labels/data will
// automatically be scaled. 'symbol' is the character to be used to draw each bar,
// 'symbol2' is used to represent scaled non-zero data and 'symbol3' zero data which
// would otherwise be blank. The actual data is printed at the end of each bar.
static barChart (title, width, labels, data, rjust, symbol, symbol2, symbol3) {
var barCount = labels.count
if (data.count != barCount) Fiber.abort("Mismatch between labels and data.")
var maxLabelLen = max(labels.map { |l| l.count })
if (labels.any { |l| l.count < maxLabelLen }) {
if (rjust) {
labels = labels.map { |l| (" " * (maxLabelLen - l.count)) + l }.toList
} else {
labels = labels.map { |l| l + (" " * (maxLabelLen - l.count)) }.toList
}
}
var maxData = max(data)
var maxLen = width - maxLabelLen - maxData.toString.count - 2
var scaledData = data.map { |d| (d * maxLen / maxData).floor }.toList
System.print(title)
System.print("-" * Math.max(width, title.count))
for (i in 0...barCount) {
var bar = symbol * scaledData[i]
if (bar == "") bar = data[i] > 0 ? symbol2 : symbol3
System.print(labels[i] + " " + bar + " " + data[i].toString)
}
System.print("-" * Math.max(width, title.count))
}
// Convenience version of the above method which uses default symbols.
static barChart(title, width, labels, data, rjust) {
barChart(title, width, labels, data, rjust, "■", "◧", "□")
}
// As above method but uses right justification for labels.
static barChart(title, width, labels, data) {
barChart(title, width, labels, data, true, "■", "◧", "□")
}
}
/* Boolean supplements the Bool class with bitwise operations on boolean values. */
class Boolean {
// Private helper method to convert a boolean to an integer.
static btoi_(b) { b ? 1 : 0 }
// Private helper method to convert an integer to a boolean.
static itob_(i) { i != 0 }
// Private helper method to check its arguments are both booleans.
static check_(b1, b2) {
if (!((b1 is Bool) && (b2 is Bool))) Fiber.abort("Both arguments must be booleans.")
}
// Returns the logical 'and' of its boolean arguments.
static and(b1, b2) {
check_(b1, b2)
return itob_(btoi_(b1) & btoi_(b2))
}
// Returns the logical 'or' of its boolean arguments.
static or(b1, b2) {
check_(b1, b2)
return itob_(btoi_(b1) | btoi_(b2))
}
// Returns the logical 'xor' of its boolean arguments.
static xor(b1, b2) {
check_(b1, b2)
return itob_(btoi_(b1) ^ btoi_(b2))
}
}