Sunday, Nov 15
How to Recalculate a Spreadsheet
made by lord, submitted by nicholasbs
An in-depth review of the technical challenges of recalculating dependent values in a spreadsheet.
const knitout = require('knitout'); | |
const yarn = "3"; | |
const gauge = "half"; | |
const fingerQuantityMinimum = 5; | |
const fingerQuantityMaximum = 10; | |
const fingerWidthMinimum = 6; | |
const fingerWidthMaximum = 8; | |
const fingerHeightMinimum = 20; | |
const fingerHeightMaximum = 100; | |
const thumbChance = 1/4; | |
const numFingers = randomBetween(fingerQuantityMinimum, fingerQuantityMaximum); | |
k = new knitout.Writer({carriers:['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']}); | |
k.addHeader('Machine','SWGXYZ'); | |
k.addHeader('Gauge','15'); | |
k.addHeader('Presser','On'); | |
k.addHeader('X-Presser','On'); | |
k.stitchNumber(85); | |
let glove = {leftedge: 0, rightedge: 0}; | |
let yarnIsIn = false; | |
for (let f = 0; f<numFingers; f++) { | |
let fingerWidth = randomBetween(fingerWidthMinimum, fingerWidthMaximum); | |
let fingerHeight = randomBetween(fingerHeightMinimum, fingerHeightMaximum); | |
if (f>0 && Math.random() < thumbChance) { // can't add a thumb to a null palm | |
glove = addThumb(glove, fingerWidth, fingerHeight, yarn); | |
} | |
else { | |
glove = addFinger(glove, fingerWidth, fingerHeight, yarn); | |
} | |
} | |
glove = addCuff(glove, yarn); | |
k.write('glotch.k'); | |
// ============================================================================ | |
// ============ High-level ==================================================== | |
// ============================================================================ | |
function addFinger (confines, width, height, carrier) { | |
// finger adds a finger to a right side of the glove | |
let leftedge = (confines.rightedge - confines.leftedge > 0) ? confines.rightedge + 1 : confines.rightedge; | |
let finger = {leftedge: leftedge, rightedge: leftedge+width}; | |
console.log("adding finger: ", finger); | |
// breaks yarn if necessary | |
if (yarnIsIn) { | |
k.outhook(yarn); | |
yarnIsIn = false; | |
} | |
// casts on | |
caston("allneedle", finger, carrier); | |
// shortrows for roundedness | |
fingertip(finger, carrier); | |
// knits the height of the tube | |
knitRows(finger, height, carrier); | |
// transfers by one stitch onto the finger next to it | |
if (confines.rightedge - confines.leftedge > 0) { | |
transferChunk(finger, -1); | |
finger.rightedge = finger.rightedge - 1; | |
} | |
confines.rightedge = finger.rightedge; | |
// (if there is one -- that is, if incoming confines is not zero width) | |
let palmHeight = randomBetween(1,2); | |
knitRows(confines, palmHeight, carrier); | |
// and does a course or two of palm pickup | |
return confines; | |
} | |
function addThumb (confines, width, height, carrier) { | |
// a thumb rotates the confines for a handful of courses, then knits a new finger, joins, and narrows back the width of the thumb | |
console.log("thumb!"); | |
confines = rotate(confines, 3, carrier); | |
let originalRightedge = confines.rightedge; | |
confines = addFinger(confines, width, height, carrier); | |
for (let i = 0; i<width; i++) { | |
knitRows(confines, 1, carrier); | |
transferChunk({leftedge: originalRightedge + 1, rightedge: confines.rightedge}, -1); | |
confines.rightedge = confines.rightedge - 1; | |
} | |
knitRows(confines, 1, carrier); | |
return confines; | |
} | |
function addCuff (confines, carrier) { | |
// a cuff adds courses of 2x2 ribbing, then binds off | |
// height of courses is between 1/3 and 1/2 the width of the confines | |
let width = confines.rightedge - confines.leftedge; | |
let height = randomBetween(width/2, width); | |
knitRows(confines, height, carrier); | |
knitRibRows(confines, height, carrier); | |
bindoff("open", confines, carrier); | |
return {leftedge:0,rightedge:0}; | |
} | |
function fingertip (confines, carrier) { | |
function shortRows (bed) { | |
let setback = 1; | |
// shortrow a nice round fingertip | |
for (let s = confines.rightedge; s>confines.leftedge+((((confines.rightedge - confines.leftedge)/(2*setback))-1)*setback); s--) { | |
knit("-", bed, s, carrier); | |
} | |
for (let i = Math.round((confines.rightedge - confines.leftedge)/(2*setback)); i>0; i--) { | |
for (let s = Math.round(confines.rightedge - (i*setback) - 1); s>confines.leftedge+((i-1)*setback); s--) { | |
knit("-", bed, s, carrier); | |
} | |
tuck("+", bed, Math.round(confines.leftedge+((i-1)*setback)), carrier); | |
for (let s = Math.round(confines.leftedge+((i-1)*setback) + 1); s <= confines.rightedge - ((i)*setback); s++) { | |
knit("+", bed, s, carrier); | |
} | |
if (i>1) tuck("-", bed, Math.round(confines.rightedge - ((i - 1)*setback)), carrier); | |
else { | |
for (let s = Math.round(confines.rightedge - ((i - 1)*setback)); s<=confines.rightedge; s++) { | |
knit("+", bed, s, carrier); | |
} | |
} | |
} | |
} | |
shortRows("f"); | |
shortRows("b"); | |
} | |
// ============================================================================ | |
// ============ Mid-level ===================================================== | |
// ============================================================================ | |
function knitRows (confines, height, carrier) { | |
for (let h=0; h<height; h++) { | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
// console.log(h, s); | |
knit("-", "f", s, carrier); | |
} | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
// console.log(h, s); | |
knit("+", "b", s, carrier); | |
} | |
} | |
} | |
function knitRibRows (confines, height, carrier) { | |
function ribGen (face, stitch) { | |
if (face == "f"){ | |
return (stitch%4 <= 1) ? "k" : "p"; | |
} | |
else { | |
return (stitch%4 <= 1) ? "p" : "k"; | |
} | |
} | |
for (let h=0; h<height; h++) { | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
if (ribGen("f", s) != "k") xfer("f", s, "bs", s); | |
} | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
let bed = (ribGen("f", s) == "k") ? "f" : "bs"; | |
knit("-", bed, s, carrier); | |
} | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
if (ribGen("f", s) != "k") xfer("bs", s, "f", s); | |
if (ribGen("b", s) != "k") xfer("b", s, "fs", s); | |
} | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
let bed = (ribGen("b", s) == "k") ? "b" : "fs"; | |
knit("+", bed, s, carrier); | |
} | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
if (ribGen("b", s) != "k") xfer("fs", s, "b", s); | |
} | |
} | |
} | |
function rotate (confines, height, carrier) { | |
for (let h=0; h<height; h++) { | |
// knit to one before the edge | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
// console.log(h, s); | |
knit("-", "f", s, carrier); | |
} | |
for (let s=confines.leftedge; s<confines.rightedge; s++) { | |
// console.log(h, s); | |
knit("+", "b", s, carrier); | |
} | |
// xfer | |
xfer("f", confines.leftedge, "b", confines.leftedge - 1); | |
xfer("b", confines.rightedge, "f", confines.rightedge + 1); | |
for (let s = confines.leftedge + 1; s<=confines.rightedge + 1; s++) { | |
xfer("f", s, "bs", s-1); | |
} | |
for (let s = confines.leftedge; s<=confines.rightedge; s++) { | |
xfer("bs", s, "f", s); | |
} | |
for (let s = confines.leftedge - 1; s<=confines.rightedge - 1; s++) { | |
xfer("b", s, "fs", s+1); | |
} | |
for (let s = confines.leftedge; s<=confines.rightedge; s++) { | |
xfer("fs", s, "b", s); | |
} | |
align("centered"); | |
} | |
knitRows(confines, 1, carrier); | |
return confines; | |
} | |
function transferChunk (confines, offset) { | |
for (let s = confines.leftedge; s<=confines.rightedge; s++) { | |
xfer("f", s, "bs", s); | |
} | |
for (let s = confines.leftedge; s<=confines.rightedge; s++) { | |
xfer("bs", s, "f", s+offset); | |
} | |
for (let s = confines.leftedge; s<=confines.rightedge; s++) { | |
xfer("b", s, "fs", s+offset); | |
} | |
for (let s = confines.leftedge; s<=confines.rightedge; s++) { | |
xfer("fs", s+offset, "b", s+offset); | |
} | |
} | |
function pickup (confines, yarn) { | |
k.inhook(yarn); | |
yarnIsIn = true; | |
knitRows(confines, 1, yarn); | |
k.releasehook(yarn); | |
} | |
function caston (style, confines, carrier) { | |
k.inhook(yarn); | |
yarnIsIn = true; | |
if (style == "tube") { | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
if (s%2 == confines.rightedge%2) { | |
knit("-", "f", s, carrier); | |
} | |
} | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
if (s%2 != confines.rightedge%2) { | |
knit("+", "b", s, carrier); | |
} | |
} | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
if (s%2 != confines.rightedge%2) { | |
knit("-", "f", s, carrier); | |
} | |
} | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
if (s%2 == confines.rightedge%2) { | |
knit("+", "b", s, carrier); | |
} | |
} | |
} | |
else if (style == "allneedle") { | |
align(-1); | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
knit("-", "f", s, carrier); | |
knit("-", "b", s, carrier); | |
} | |
align("centered"); | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
knit("+", "f", s, carrier); | |
knit("+", "b", s, carrier); | |
} | |
} | |
k.releasehook(yarn); | |
} | |
function bindoff (style, confines, carrier) { | |
if (style == "closed") { | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
knit("-", "b", s, carrier); | |
xfer("b", s, "f", s); | |
knit("-", "f", s, carrier); | |
if (s != confines.leftedge) xfer("f", s, "b", s - 1, "centered"); | |
} | |
let tag = makeTag(confines.leftedge, carrier); | |
k.outhook(yarn); | |
yarnIsIn = false; | |
dropAll(tag); | |
} | |
else if (style == "open") { | |
for (let s=confines.rightedge; s>confines.leftedge; s--) { | |
knit("-", "f", s, carrier); | |
xfer("f", s, "bs", s); | |
xfer("bs", s, "f", s-1); | |
} | |
knit("-", "f", confines.leftedge, carrier); | |
xfer("f", confines.leftedge, "b", confines.leftedge); | |
for (let s=confines.leftedge; s<confines.rightedge; s++) { | |
knit("+", "b", s, carrier); | |
xfer("b", s, "fs", s); | |
xfer("fs", s, "b", s+1); | |
} | |
knit("+", "b", confines.rightedge, carrier); | |
xfer("b", confines.rightedge, "f", confines.rightedge); | |
align("centered"); | |
let tag = makeTag(confines.rightedge, carrier); | |
k.outhook(yarn); | |
yarnIsIn = false; | |
dropAll(tag); | |
} | |
else { | |
k.outhook(carrier); | |
yarnIsIn = false; | |
dropAll(confines); | |
} | |
} | |
function makeTag (s, carrier) { | |
knit("+", "f", s, carrier); | |
knit("-", "f", s, carrier); | |
knit("+", "f", s, carrier); | |
knit("-", "f", s+1, carrier); | |
knit("-", "f", s, carrier); | |
knit("+", "f", s, carrier); | |
knit("+", "f", s+1, carrier); | |
for (let i=0; i<3; i++) { | |
knit("-", "f", s+2, carrier); | |
knit("-", "f", s+1, carrier); | |
knit("-", "f", s, carrier); | |
knit("+", "f", s, carrier); | |
knit("+", "f", s+1, carrier); | |
knit("+", "f", s+2, carrier); | |
} | |
return {leftedge: s, rightedge: s+2}; | |
} | |
function dropAll (confines) { | |
for (let s=confines.rightedge; s>=confines.leftedge; s--) { | |
drop("f", s); | |
} | |
for (let s=confines.leftedge; s<=confines.rightedge; s++) { | |
drop("b", s); | |
} | |
} | |
// ============================================================================ | |
// ============ Low-level ===================================================== | |
// ============================================================================ | |
function randomBetween (min, max) { | |
return Math.floor(Math.random() * (max - min + 1) + min); | |
} | |
function mapNeedle (bed, needle) { | |
let mappedBed, mappedNeedle; | |
if (gauge == "full") { | |
mappedBed = bed; | |
mappedNeedle = needle; | |
} | |
else if (gauge == "half") { | |
mappedBed = (bed == "f" || bed == "fs") ? "f" : "b"; | |
mappedNeedle = (bed == "f" || bed == "bs") ? (2*needle) - 1 : 2*needle; | |
} | |
// console.log({bed: mappedBed, needle: mappedNeedle}); | |
return {bed: mappedBed, needle: mappedNeedle}; | |
} | |
function knit (direction, bed, needle, carrier) { | |
let mappedNeedle = mapNeedle(bed, needle); | |
k.knit(direction, mappedNeedle.bed + mappedNeedle.needle, carrier); | |
} | |
function miss (direction, bed, needle, carrier) { | |
let mappedNeedle = mapNeedle(bed, needle); | |
k.miss(direction, mappedNeedle.bed + mappedNeedle.needle, carrier); | |
} | |
function tuck (direction, bed, needle, carrier) { | |
let mappedNeedle = mapNeedle(bed, needle); | |
k.tuck(direction, mappedNeedle.bed + mappedNeedle.needle, carrier); | |
} | |
function drop (bed, needle) { | |
let mappedNeedle = mapNeedle(bed, needle); | |
k.drop(mappedNeedle.bed + mappedNeedle.needle); | |
} | |
function align (alignment) { | |
if (alignment == "centered") { | |
if (gauge == "full") { | |
k.rack(-0.75); | |
} | |
else { | |
k.rack(0); | |
} | |
} | |
else if (typeof alignment == "number") { | |
if (gauge == "full") { | |
k.rack(alignment); | |
} | |
else { | |
k.rack(2*alignment); | |
} | |
} | |
else { | |
console.log("unrecognized alignment style"); | |
} | |
} | |
function xfer (fromBed, fromNeedle, toBed, toNeedle, returnAlignment) { | |
if (fromBed == toBed || (fromBed == "f" && toBed == "fs")|| (fromBed == "fs" && toBed == "f")|| (fromBed == "b" && toBed == "bs")|| (fromBed == "bs" && toBed == "b")) { | |
console.log("cannot xfer to same bed! attempted: ", fromBed, fromNeedle, toBed, toNeedle); | |
} | |
else { | |
mappedFrom = mapNeedle(fromBed, fromNeedle); | |
mappedTo = mapNeedle(toBed, toNeedle); | |
offset = mappedTo.needle - mappedFrom.needle; | |
if (fromBed == "f" || fromBed == "fs") offset = 0 - offset; | |
k.rack(offset); | |
k.xfer(mappedFrom.bed+mappedFrom.needle, mappedTo.bed+mappedTo.needle); | |
if (typeof returnAlignment !== 'undefined') align(returnAlignment); | |
} | |
} |