Last active
April 28, 2019 23:38
-
-
Save FergusInLondon/d09e1f4535a9f8069b680c0fb7a0bd37 to your computer and use it in GitHub Desktop.
Processing Sequential Data w/ "*Sliding Windows*"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A (very over-engineered, yet still impressively scruffy) implementation of a | |
* Sliding Window (adjacent, overlapping, and buffered.) for the processing of | |
* sequential data. | |
* | |
* Written very hurriedly one evening, to support a blog post. | |
* | |
* @param {array} data | |
* @param {int|Number} width | |
* @param {array?} accumulator | |
*/ | |
function SlidingWindow(data, width, accumulator = []) { | |
/* Helper Method: a very hacky way of cloning a given variable. */ | |
const clone = (object) => { | |
return JSON.parse(JSON.stringify(object)); | |
} | |
let buffer = new Buffer(width); | |
buffer.setFromArray(data); | |
const AdjacentWindowSlider = (callback) => { | |
return (buffer.length() < 1) ? | |
// If there's no data available, just return the pre-defined accumulator | |
accumulator : | |
// Otherwise, clone accumulator, calculate the number of steps, and iterate through | |
((acc, windowCount) => { | |
for(let i=0; i < windowCount; i++) { | |
acc = callback({ | |
window : data.slice(i*width, ((i*width)+width)), | |
current: i, acc, windowCount | |
}) | |
} | |
return acc; | |
})(clone(accumulator), Math.ceil( data.length / width )); | |
}; | |
const OverlappingWindowSlider = (callback) => { | |
return (buffer.length() < 1) ? | |
// If there's no data available, just return the pre-defined accumulator | |
accumulator : | |
// Otherwise, clone accumulator, calculate the number of steps, and iterate through | |
((acc, windowCount) => { | |
for(let i = 0; i < windowCount; i++) { | |
acc = callback({ | |
window : data.slice(i, (i+width)), | |
current: i, acc, windowCount | |
}); | |
} | |
return acc; | |
})(clone(accumulator), Math.ceil((data.length - (width - 1)))); | |
}; | |
const BufferedOverlappingWindowSlider = () => { | |
const buffer = new Buffer(width); | |
buffer.setFromArray(data); | |
return (dataPoint) => { | |
buffer.push(dataPoint); | |
return buffer.getArray(); | |
}; | |
}; | |
const BufferedAdjacentWindowSlider = () => { | |
const buffer = new Buffer(width); | |
buffer.setFromArray(AdjacentWindowSlider(({window, acc}) => { | |
acc.push(window); | |
return acc; | |
}).pop()) | |
return (dataPoint) => { | |
buffer.rotatingPush(dataPoint); | |
return buffer.getArray(); | |
} | |
} | |
return { | |
AdjacentWindowSlider, | |
BufferedAdjacentWindowSlider, | |
OverlappingWindowSlider, | |
BufferedOverlappingWindowSlider | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A very primitive Buffer implementation; essentially a wrapper around an array. | |
* This is just used for some helper methods - facilitiating functionality such | |
* as rotating pushes and limits. | |
* | |
* @param {int|Number} size | |
*/ | |
function Buffer(size) { | |
let data = []; | |
let len = 0; | |
return { | |
/* Push to the array, ensure that the limit isn't breached. */ | |
push: (val) => { | |
if (data.length >= size) { | |
data.shift(); | |
} | |
pushCount++; | |
len = data.push(val); | |
}, | |
/* Rotating Push: i.e clear the buffer when the limit is reached, and then | |
start again. */ | |
rotatingPush: (val) => { | |
if (len >= width) { | |
data = []; | |
} | |
data.push(val) | |
pushCount++; | |
len = data.length; | |
return this; | |
}, | |
/* Return the last element pushed to the buffer; if a number is specified | |
- i.e `n` - then it will return up to `n` elements. */ | |
last: (n = 1) => { | |
return (n > data.length) | |
? data | |
: data.slice(Math.abs(n) * -1); | |
}, | |
/* Return the length of the buffer. */ | |
length: () => { | |
return data.length | |
}, | |
/* Provides access to the underlying array. */ | |
getArray: () => { | |
return data; | |
}, | |
/* Initialises the buffer from a primitive array. */ | |
setFromArray: (arr) => { | |
data = (arr.length >= size) | |
? arr.slice(size * -1) | |
: arr; | |
len = data.length | |
}, | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
OverlappingWindowSlider, | |
AdjacentWindowSlider, | |
BufferedOverlappingWindowSlider, | |
BufferedAdjacentWindowSlider | |
} = new SlidingWindow([1,2,3,4,5,8,9,3,9,4,5,2,6,1,], 3); | |
const randomNumber = (min, max) => { | |
return Number.parseFloat( | |
Math.random() * (max - min) - min | |
).toPrecision(2); | |
} | |
const demonstrationFunction = ({acc, window}) => { | |
acc.push(window); | |
return acc; | |
}; | |
const averagingFunction = ({acc, window}) => { | |
acc.push( window.reduce((accumulator, val) => accumulator + val, 0) / window.length ); | |
return acc; | |
} | |
console.log("--- Overlapping Window Sliding:"); | |
console.log(OverlappingWindowSlider(demonstrationFunction)); | |
console.log("--- Smoothing Average (Overlapping): ") | |
console.log(OverlappingWindowSlider(averagingFunction)) | |
console.log("\n--- Buffered Overlapping Window Sliding:"); | |
const bufferedOverlap = BufferedOverlappingWindowSlider(); | |
for (let i = 6; i < 15; i++) { | |
console.log(bufferedOverlap(i)); | |
} | |
console.log("\n--- Smoothing Average (Buffered Overlap):"); | |
for (let i = 6; i < 20; i++) { | |
let window = bufferedOverlap(Math.abs(randomNumber(5,30))); | |
let average = window.reduce( (acc, val) => acc + val, 0) / window.length | |
console.log( | |
"[%s] => %d", window.join(", "), | |
Number.parseFloat(average).toPrecision(2) | |
); | |
} | |
console.log("\n--- Adjacent Window Sliding:"); | |
console.log(AdjacentWindowSlider(demonstrationFunction)); | |
console.log("--- Smoothing Average (Adjacent): ") | |
console.log(AdjacentWindowSlider(averagingFunction)) | |
console.log("\n--- Buffered Adjacent Window Sliding:"); | |
const bufferedAdjacent = BufferedAdjacentWindowSlider(); | |
for (let i = 6; i < 15; i++) { | |
console.log(bufferedAdjacent(i)); | |
} | |
console.log("\n--- Smoothing Average (Buffered Adjacent):"); | |
for (let i = 6; i < 20; i++) { | |
let window = bufferedAdjacent(Math.abs(randomNumber(5,30))); | |
let average = window.reduce( (acc, val) => acc + val, 0) / window.length | |
console.log( | |
"[%s] => %d", window.join(", "), | |
Number.parseFloat(average).toPrecision(2) | |
); | |
} |
Author
FergusInLondon
commented
Apr 28, 2019
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment