Skip to content

Instantly share code, notes, and snippets.

@numist
Last active March 14, 2016 14:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save numist/32e90c48ad51b335569e to your computer and use it in GitHub Desktop.
Save numist/32e90c48ad51b335569e to your computer and use it in GitHub Desktop.
A function that plots (as a string) a function of (Double) -> Double
import Darwin // Provides: sin/cos/tan
// memoize function from https://gist.github.com/berkus/8a9e104f8aac5d025eb5
func memoize<T: Hashable, U>( body: ( (T)->U, T )->U ) -> (T)->U {
var memo = Dictionary<T, U>()
var result: ((T)->U)!
result = { x in
if let q = memo[x] { return q }
let r = body(result, x)
memo[x] = r
return r
}
return result
}
func popcount(p: UInt8) -> Int {
var result = 0
for i in 0..<8 {
if (p & UInt8(1 << i)) != 0 { result += 1}
}
return result
}
func plot(f: (Double) -> Double, p: (String) -> Void, window: ((Double, Double), (Double, Double)), outputSize: (Int, Int)) {
let chartOrigin = window.0
let apogee = window.1
precondition(chartOrigin.0 < apogee.0 && chartOrigin.1 < apogee.1)
let width = outputSize.0
let height = outputSize.1
precondition(width > 0 && height > 0)
let chartWidth = apogee.0 - chartOrigin.0
let cellWidth = chartWidth / Double(outputSize.0)
let chartHeight = apogee.1 - chartOrigin.1
let cellHeight = chartHeight / Double(outputSize.1)
// Compute cell center in terms of function input space
let g: (Int) -> Int = { px in
let fmx = chartOrigin.0 + Double(px) * cellWidth + (cellWidth / 2)
let fy = f(fmx)
return Int((fy - chartOrigin.1) / cellHeight)
}
let h = memoize { _, x in g(x) }
/* This is the only control flow path using h (and thus g, and thus f)
* TODO: memoization in a µC isn't really a thing; the values of `column` in calls to `cellWillDraw()`
* should be well-defined (with respect to `window.(0...1).0`); establish those bounds and
* replace cellWillDraw with a LUT
*/
let cellWillDraw: (Int,Int) -> Bool = { column, row in
let l = h(column - 1)
let val = h(column)
return val == row || (row > val && row < l) || (row < val && row > l)
}
// Represent cell neighborhood with an 8-bit vector.
// Bit positions are arbitrarily chosen; doesn't matter as long as each direction is unique.
let (nw, n, ne, e, se, s, sw, w) = (UInt8(1<<0), UInt8(1<<1), UInt8(1<<2), UInt8(1<<3), UInt8(1<<4), UInt8(1<<5), UInt8(1<<6), UInt8(1<<7))
let neighborBitMapForPoint: (Int, Int) -> UInt8 = { column, row in
var result = UInt8(0)
if cellWillDraw(column - 1, row + 1) { result |= nw }
if cellWillDraw(column , row + 1) { result |= n }
if cellWillDraw(column + 1, row + 1) { result |= ne }
if cellWillDraw(column + 1, row ) { result |= e }
if cellWillDraw(column + 1, row - 1) { result |= se }
if cellWillDraw(column , row - 1) { result |= s }
if cellWillDraw(column - 1, row - 1) { result |= sw }
if cellWillDraw(column - 1, row ) { result |= w }
return result
}
for row in (0..<height).reverse() {
for col in 0..<width {
if !cellWillDraw(col, row) {
p(" ")
} else {
switch neighborBitMapForPoint(col, row) {
case 0:
p("o")
case let shape where shape & (w|e) == (w|e) && shape & (n|s) == 0:
p("-")
case let shape where shape & (sw|ne) == (sw|ne) && (shape & (s|se|e) == 0 || shape & (w|nw|n) == 0):
p ("/")
case let shape where shape & (nw|se) == (nw|se) && (shape & (w|sw|s) == 0 || shape & (n|ne|e) == 0):
p("\\")
case let shape where (shape & n) != 0:
if (shape & (sw|s|se)) == 0 {
p("'")
} else if (shape & sw) != 0 {
p(";")
} else if (shape & se) != 0 {
p(":")
} else {
p("|")
}
case let shape where (shape & s) != 0:
if shape & (nw|n|ne) == 0 {
p(".")
} else if shape & (nw|ne) != 0 {
p(":")
} else {
p("|")
}
case let shape where (shape & s) == 0 && (shape & (nw|n|ne)) != 0:
if (shape & nw) != 0 {
p("`")
} else {
p("'")
}
case let shape where (shape & n) == 0 && (shape & (sw|s|se)) != 0:
if (shape & sw) != 0 {
p(",")
} else {
p(".")
}
default:
p("X")
}
}
}
p("\n")
}
}
// .----. .----.
// / \ / \
// : \ : \
// ; : ; :
// / : / :
// : \ : \
// ; : ; :
// : : : :
// ; : ; :
// : : : :
// ; : ; :
// / : / :
// \ : \ :
// : ; : ;
// : : : :
// : ; : ;
// : : : :
// : ; : ;
// : / : /
// \ : \ :
// : ; : ;
// : / : /
// \ / \ /
// `----' `----'
plot({sin($0)}, p: { print($0, terminator: "") }, window: ((0, -1), (M_PI * 4, 1)), outputSize: (80, 24))
// ----. .----
// `. .'
// `. .'
// `. .'
// \ /
// \ /
// `. .'
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// `. .'
// \ /
// \ /
// `. .'
// `. .'
// `. .'
// `--------'
plot({cos($0)}, p: { print($0, terminator: "") }, window: ((0, -1), (M_PI * 2, 1)), outputSize: (80, 24))
// ; | ; |
// : | : |
// ; | ; |
// : | : |
// ; | ; |
// / | / |
// / | / |
// / | / |
// .' | .' |
// .-' | .-' |
// .-' | .-' |
// .--' | .--' |
// | .--' | .--'
// | .-' | .-'
// | .-' | .-'
// | .' | .'
// | / | /
// | / | /
// | : | :
// | ; | ;
// | : | :
// | ; | ;
// | : | :
// | | | |
plot({tan($0)}, p: { print($0, terminator: "") }, window: ((0, -4), (M_PI * 2, 4)), outputSize: (80, 24))
// . . .
// |\ |\ |\
// | \ | \ | \
// | \ | \ | \
// | \ | \ | \
// | \ | \ | \
// | \ | \ | \
// | \ | \ | \
// | \ | \ | \
// | \ | \ | \
//\ | \ | \ |
// \ | \ | \ |
// \ | \ | \ |
// \ | \ | \ |
// \ | \ | \ |
// \ | \ | \ |
// \ | \ | \ |
// \ | \ | \ |
// \ ; \ ; \ ;
// ` ` `
let sawtooth: (Double) -> Double = { x in
return abs((x - Double(Int(x))) - 1)
}
plot(sawtooth, p: { print($0, terminator: "") }, window: ((0.5, 0), (3.5, 1)), outputSize: (60, 20))
// ,. ,.
// / \ / \
// / \ / \
// / \ / \
// / \ / \
// / \ / \
// / \ / \
// / \ / \
// / \ / \
/// \ / \
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// \ /
// `'
let triangle: (Double) -> Double = { x in
let val = x - Double(Int(x))
if Int(x) % 2 == 0 {
return val
} else {
return 1 - val
}
}
plot(triangle, p: { print($0, terminator: "") }, window: ((0.5, 0), (3.5, 1)), outputSize: (60, 20))
@numist
Copy link
Author

numist commented Mar 3, 2016

Please forgive the lack of thoughtfulness around generics and such; this is an experiment for code that's ultimately intended to allow a microcontroller to plot graphs over a serial interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment