Category talk:Wren-math: Difference between revisions

→‎Source code: Added squareFree, cubeFree and powerFree methods to Int class.
(→‎Source code: Added some convenience methods to the Int and Nums classes.)
(→‎Source code: Added squareFree, cubeFree and powerFree methods to Int class.)
 
(42 intermediate revisions by the same user not shown)
Line 1:
===Source code===
<langsyntaxhighlight ecmascriptlang="wren">/* Module "math.wren" */
 
/* Math supplements the Num class with various other operations on numbers. */
Line 65:
// 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
}
 
// Attempts to find a real root of a polynomial using Newton's method from
// an initial guess, a given tolerance, a maximum number of iterations
// and a given multiplicity (usually 1).
// If a root is found, it is returned otherwise 'null' is returned.
// If the root is near an integer checks if the integer is in fact the root.
static rootPoly(coefs, guess, tol, maxIter, mult) {
var deg = coefs.count - 1
if (deg == 0) return null
if (deg == 1) return -coefs[1]/coefs[0]
if (deg == 2 && coefs[1]*coefs[1] - 4*coefs[0]*coefs[2] < 0) return null
if (Math.evalPoly(coefs, 0) == 0) return 0
var eps = 0.001
var deriv = Math.diffPoly(coefs)
var x0 = guess
var iter = 1
while (true) {
var den = Math.evalPoly(deriv, x0)
if (den == 0) {
x0 = (x0 >= 0) ? x0 + eps : x0 - eps
} else {
var num = Math.evalPoly(coefs, x0)
var x1 = x0 - num/den * mult
if ((x1 - x0).abs <= tol) {
var r = x1.round
if ((r - x1).abs <= eps && Math.evalPoly(coefs, r) == 0) return r
return x1
}
x0 = x1
}
if (iter == maxIter) break
iter = iter + 1
}
x0 = x0.round
if (Math.evalPoly(coefs, x0) == 0) return x0
return null
}
 
// Convenience versions of 'rootPoly' which use defaults for some parameters.
static rootpoly(coefs, guess) { rootPoly(coefs, guess, 1e-15, 100, 1) }
static rootPoly(coefs) { rootPoly(coefs, 0.001, 1e-15, 100, 1) }
 
 
// Gamma function using Lanczos approximation.
Line 84 ⟶ 174:
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.
Line 103 ⟶ 202:
static mod(x, y) { ((x % y) + y) % y }
 
// Returns whetherthe orinteger notsquare root of 'nx' isor anull perfectif square'x' is negative.
static isSquaresqrt(nx) { (x >= 0) ? x.sqrt.floor : null }
 
var s = n.sqrt.floor
// Returns the integer returncube sroot *of s == n'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 'nx' is a perfect cube'n'th power.
static isCubeisPower(n, x) {
var cr = root(n.cbrt.truncate, x)
return c * c * cr.pow(n) == nx
}
 
Line 149 ⟶ 272:
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 == 10) returnFiber.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 (e%2b == 1) r = (r*b0) %return m0
if (e =% e2 >>== 1) r = modMul(r, b, m)
be = div(b*be, 2) % m
b = modMul(b, b, m)
}
return r
}
 
// Returns the factorialmultiplicative inverse of 'nx'. Inaccurate formodulo 'n > 18'.
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)) Fiber.abort("Argument&& mustn be< a19)) non-negative integer"){
Fiber.abort("Argument must be a non-negative integer < 19.")
}
if (n < 2) return 1
var fact = 1
Line 173 ⟶ 356:
}
 
// DeterminesReturns whetherthe 'n'multinomial iscoefficient primeof usingn over a wheellist withf basiswhere [2,sum(f) 3]== n.
static isPrimemultinomial(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]) }
 
// The highest prime less than 2^53.
static maxSafePrime { 9007199254740881 }
 
// 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 > Num.maxSafeInteger) Fiber.abort("Argument must be a 'safe' integer.")
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
varif d(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 >= maxSafePrime) Fiber.abort("Argument is larger than maximum safe prime.")
if (n < 2) return 2
n = (n%2 == 0) ? n + 1 : n + 2
while (true) {
if (Int.isPrime(n)) return n
n = n + 2
}
}
 
// Returns the previous prime number less than 'n' or null if there isn't one.
static prevPrime(n) {
if (n > Num.maxSafeInteger) Fiber.abort("Argument must be a 'safe' integer.")
if (n < 3) return null
if (n == 3) return 2
n = (n%2 == 0) ? n - 1 : n - 2
while (true) {
if (Int.isPrime(n)) return n
n = n - 2
}
}
 
// Returns the 'n'th prime number. For the formula used, see:
// https://en.wikipedia.org/wiki/Prime_number_theorem#Approximations_for_the_nth_prime_number
static getPrime(n) {
if (n < 1 || !n.isInteger) Fiber.abort("Argument must be a positive integer.")
if (n < 8) return [2, 3, 5, 7, 11, 13, 17][n-1]
var l1 = n.log
var l2 = l1.log
var l3 = (l2 - 2) / l1
var l4 = (l2*l2 - 6*l2 + 11) / (2*l1*l1)
var p = ((l1 + l2 + l3 - l4 - 1) * n).floor
var g = p.log.round
var pc = Int.primeCount(p)
p = p + (n - pc) * g
if (p % 2 == 0) p = p - 1
pc = Int.primeCount(p)
if (pc < n) {
for (i in pc...n) p = Int.nextPrime(p)
} else if (Int.isPrime(p)) {
for (i in n...pc) p = Int.prevPrime(p)
} else {
for (i in n..pc) p = Int.prevPrime(p)
}
return p
}
 
Line 278 ⟶ 602:
}
 
// Private helper method which countsComputes and returns how many primes there are up to and including 'n'.
// See https://rosettacode.org/wiki/Legendre_prime_counting_function for details.
// up to and including 'n' using the Legendre method.
static legendre_primeCount(n, primes, memoPhi) {
if (n < 2) return 0
varif phi(n < 9) return ((n + 1)/2).floor
phivar masks = Fn.new[1, {2, 4, 8, 16, 32, |x64, a|128]
var half = Fn.new if{ |n| (an ==- 01) return>> 1 x}
var keyrtlmt = cantorPair(x, a)n.sqrt.floor
var mxndx = if (memoPhiInt.containsKeyquo(key))rtlmt return- memoPhi[key]1, 2)
var arrlen = (mxndx var pa =+ primes[a-1]).max(2)
var smalls = List.filled(arrlen, 0)
return memoPhi[key] = phi.call(x, a-1) - phi.call((x/pa).floor, a-1)
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 acullbuflen = legendre_Int.quo(n.sqrt.floor,mxndx + primes8, memoPhi8)
returnvar phicullbuf = List.callfilled(ncullbuflen, a0) + a - 1
var nbps = 0
}
var rilmt = arrlen
 
var i = 1
// Private helper method which sieves for primes up to and including 'n'
while (true) {
// and returns how many there are. Faster than Legendre method for small n.
var sqri = (i + i) * (i + 1)
static primeCountSmall_(n) {
if (nsqri <> 2mxndx) return 0break
if ((cullbuf[i >> 3] & masks[i & 7]) != 0) {
var count = 1
var k i = ((n-3)/2).floori + 1
var marked = List.filled(k, true) continue
var limit = ((n.sqrt.floor - 3)/2).floor + 1}
cullbuf[i >> 3] = cullbuf[i >> 3] | masks[i & 7]
limit = limit.max(0)
for ( var bp = i in+ i 0...limit)+ {1
ifvar (marked[i])c {= sqri
while (c < arrlen) var p = 2*i + 3{
varcullbuf[c s>> 3] = ((p*pcullbuf[c ->> 3)/2).floor] | masks[c & 7]
var jc = sc + bp
while (j < k) {}
var marked[j]nri = false0
for (ori in 0...rilmt) j = j + p{
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 (i in 0...k) {
for (ri in if (marked[i]1...rilmt) countans = countans +- 1larges[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 countans + 1
}
 
// Returns how many positive integers up to 'n' are relatively prime to 'n'.
// Computes, using an appropriate method, and returns how many primes there are
static totient(n) {
// up to and including 'n'.
static primeCount if (n < 1) {return 0
ifvar (n < 1e4)tot return= primeCountSmall_(n)
var limiti = n.sqrt.floor2
varwhile primes(i*i <= primeSieve(limitn) {
var memoPhi if (n%i == 0) {}
return legendre_ while (n,%i primes,== memoPhi0) n = (n/i).floor
tot = tot - (tot/i).floor
}
if (i == 2) i = 1
i = i + 2
}
if (n > 1) tot = tot - (tot/n).floor
return tot
}
 
Line 333 ⟶ 703:
static primeFactors(n) {
if (!n.isInteger || n < 2) return []
if (n > Num.maxSafeInteger) Fiber.abort("Argument must be a 'safe' integer.")
var inc = [4, 2, 4, 2, 4, 6, 2, 6]
var factors = []
Line 361 ⟶ 732:
return factors
}
 
// Returns a list of the distinct prime factors of 'n' in order.
static distinctPrimeFactors(n) {
var factors = primeFactors(n)
if (factors.count < 2) return factors
for (i in factors.count-1..1) {
if (factors[i] == factors[i-1]) factors.removeAt(i)
}
return factors
}
 
// Returns a list of the distinct prime factors of 'n' together with the number
// of times each such factor is repeated.
static primePowers(n) {
var factors = primeFactors(n)
if (factors.count == 0) return []
var prev = factors[0]
var res = [[prev, 1]]
for (f in factors.skip(1)) {
if (f == prev) {
res[-1][1] = res[-1][1] + 1
} else {
res.add([f, 1])
}
prev = f
}
return res
}
 
// Returns true if the prime factorization of 'n' does not contain any
// factors which are repeated 'm' (or more) times, or false otherwise.
static powerFree(m, n) { Int.primePowers(n).all { |pp| pp[1] < m } }
 
// Covenience versions of above method for squares and cubes.
static squareFree(n) { powerFree(2, n) }
static cubeFree(n) { powerFree(3, n) }
 
// Returns all the divisors of 'n' including 1 and 'n' itself.
static divisors(n) {
if (!n.isInteger || n < 1) return []
if (n > Num.maxSafeInteger) Fiber.abort("Argument must be a 'safe' integer.")
var divisors = []
var divisors2 = []
Line 386 ⟶ 794:
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
if (n > Num.maxSafeInteger) Fiber.abort("Argument must be a 'safe' integer.")
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
if (n > Num.maxSafeInteger) Fiber.abort("Argument must be a 'safe' integer.")
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 && n <= Num.maxSafeInteger)) {
Fiber.abort("Number must be a non-negative 'safe' integer.")
}
if (!(b is Num && b.isInteger && b >= 2 && b < 64)) {
Line 431 ⟶ 936:
}
return [n, ap]
}
 
// Returns a new integer formed by reversing the base 10 digits of 'n'.
// Works with positive, negative and zero integers.
static reverse(n, check) {
if (check) check_(n, b)
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
}
Line 441 ⟶ 959:
static digitalRoot(n, b) { digitalRoot(n, b, false) }
static digitalRoot(n) { digitalRoot(n, 10, false) }
static reverse(n) { reverse(n, false) }
 
// Returns the unique non-negative integer that is associated with a pair
Line 548 ⟶ 1,067:
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, "■", "◧", "□")
}
 
// Plots a sequence of points 'pts' on x/y axes of lengths 'lenX'/'lenY' on the
// terminal automatically scaling them to those lengths. 'symbol' marks each point.
static plot(title, pts, lenX, lenY, symbol) {
var px = pts.map { |p| p[0] }
var py = pts.map { |p| p[1] }
var maxX = max(px).ceil
var minX = min(px).floor
var maxY = max(py).ceil
var minY = min(py).floor
var cy = max([maxY.toString.count, minY.toString.count, 5])
pts = pts.map { |p|
var q = [0, 0]
q[0] = Math.lerp(1, lenX, (p[0] - minX) / (maxX - minX)).round
q[1] = Math.lerp(1, lenY, (p[1] - minY) / (maxY - minY)).round
return q
}.toList
pts.sort { |p, q| p[1] != q[1] ? p[1] > q[1] : p[0] < q[0] }
System.print(title)
System.print("-" * (lenX + cy + 3))
var pc = 0
for (line in lenY..1) {
var s = ""
if (line == lenY) {
s = maxY.toString
} else if (line == 1) {
s = minY.toString
}
System.write("|")
if (s.count < cy) System.write(" " * (cy - s.count))
System.write(s + "|")
var xs = []
while (pc < pts.count && pts[pc][1] == line) {
xs.add(pts[pc][0])
pc = pc + 1
}
if (xs.isEmpty) {
System.print(" " * lenX + "|")
} else {
var lastX = 0
for (x in xs) {
if (x == lastX) continue
if (x - lastX > 1) System.write(" " * (x - lastX - 1))
System.write(symbol)
lastX = x
}
if (lenX > lastX) System.write(" " * (lenX - lastX))
System.print("|")
}
}
System.print("|" + "-" * (lenX + cy + 1) + "|")
var minL = minX.toString.count
var maxL = maxX.toString.count
System.write("| y/x" + " " * (cy - 4) + "|")
System.print(minX.toString + " " * (lenX - minL - maxL) + maxX.toString + "|")
System.print("-" * (lenX + cy + 3))
}
 
// As above method but uses a default symbol.
static plot(title, pts, lenX, lenY) { plot(title, pts, lenX, lenY, "▮") }
}
 
Line 580 ⟶ 1,207:
return itob_(btoi_(b1) ^ btoi_(b2))
}
}</langsyntaxhighlight>
9,476

edits