Skip to content

Instantly share code, notes, and snippets.

@meodai
Created March 7, 2024 12:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save meodai/a7f5b0c3fe88d2890b81e6b76a28583e to your computer and use it in GitHub Desktop.
Save meodai/a7f5b0c3fe88d2890b81e6b76a28583e to your computer and use it in GitHub Desktop.
Scale Spread Array and generator function
<div class="array">Original</div>
<div class="generator">Generator</div>
<div class="optimizedArray">Combo</div>
console.clear();
/**
* Linearly interpolates between two values.
*
* @param {number} amt - The interpolation amount (usually between 0 and 1).
* @param {number} from - The starting value.
* @param {number} to - The ending value.
* @returns {number} - The interpolated value.
*/
const lerp = (amt, from, to) => from + amt * (to - from);
/**
* @callback FillFunction<T>
* @param {number} percent - The percentage between the current and next step
* @param {T} from - the previous step value
* @param {T} to - the next step value
* @returns {T} - the interpolated value
*/
/**
* Scales and spreads an array to the target size as a generator.
*
* @param {unknown[]} initial - The initial array of values.
* @param {number} targetSize - The desired size of the number of steps.
* @param {FillFunction} fillFunction - The interpolation function (default is lerp).
* @yeilds {unknown} The scaled and spread value for the current step.
* @throws {Error} If the initial array is empty or target size is invalid.
*/
function* scaleSpread(initial, targetSize, fillFunction = lerp){
if (initial.length < 2) {
throw new Error("Initial array must have minimum length of 2.");
}
if (targetSize < initial.length) {
throw new Error("Target size must be greater than or equal to the initial array length.");
}
// We need to track where our original values will end up
const originalValues = {}
// How much space is between each original entry as a decimal
const spaceBetween = (targetSize - 1)/(initial.length-1);
// loop over the remaining values
for(let i=0; i < initial.length; i++){
const key = Math.round(spaceBetween*i);
const next = Math.round(spaceBetween*(i+1));
//console.log(key, next);
const result = {
key,
value:initial[i],
next,
}
if(next > targetSize-1){
result.next = key;
}
// round the product of the index * the space between for more even distribution
originalValues[key]=result;
}
// Store our current and next values outside the loop for quicker lookup
let current = originalValues[0];
let next = originalValues[current.next];
// we will yeild in here
for(let i = 0; i<targetSize; i++){
// don't update current and next until we get to their index number
if(next?.key && i >= next?.key){
current = next;
next = originalValues[current.next];
}
if(next){
let percentage = (i - current.key)/(next.key-current.key)
if(Number.isNaN(percentage)) percentage = 1;
//console.log(`i:${i}, key:${key}, current.value:${current.value}, next.value:${next?.value}`);
yield fillFunction(percentage, current.value, next?.value)
}
}
}
/**
* Scales and spreads an array to the target size using interpolation.
*
* @param {Array} initial - The initial array of values.
* @param {number} targetSize - The desired size of the resulting array.
* @param {function} fillFunction - The interpolation function (default is lerp).
* @returns {Array} The scaled and spread array.
* @throws {Error} If the initial array is empty or target size is invalid.
*/
const scaleSpreadArray = (initial, targetSize, fillFunction = lerp) => {
if (initial.length === 0) {
throw new Error("Initial array must not be empty.");
}
if (targetSize < initial.length) {
throw new Error("Target size must be greater than or equal to the initial array length.");
}
const valuesToAdd = targetSize - initial.length;
const chunkArray = initial.map((value) => [value]);
for (let i = 0; i < valuesToAdd; i++) {
chunkArray[i % (initial.length - 1)].push(null);
}
for (let i = 0; i < chunkArray.length - 1; i++) {
const currentChunk = chunkArray[i];
const nextChunk = chunkArray[i + 1];
const currentValue = currentChunk[0];
const nextValue = nextChunk[0];
for (let j = 1; j < currentChunk.length; j++) {
const percent = j / currentChunk.length;
currentChunk[j] = fillFunction(percent, currentValue, nextValue);
}
}
return chunkArray.flat();
};
/**
* @callback FillFunction<T>
* @param {number} percent - The percentage between the current and next step
* @param {T} from - the previous step value
* @param {T} to - the next step value
* @returns {T} - the interpolated value
*/
/**
* Scales and spreads an array to the target size as a generator.
*
* @param {unknown[]} initial - The initial array of values.
* @param {number} targetSize - The desired size of the number of steps.
* @param {FillFunction} fillFunction - The interpolation function (default is lerp).
* @yeilds {unknown} The scaled and spread value for the current step.
* @throws {Error} If the initial array is empty or target size is invalid.
*/
function scaleSpreadArrayAlex(initial, targetSize, fillFunction = lerp){
if (initial.length < 2) {
throw new Error("Initial array must have minimum length of 2.");
}
if (targetSize < initial.length) {
throw new Error("Target size must be greater than or equal to the initial array length.");
}
// We need to track where our original values will end up
const originalValues = {}
// How much space is between each original entry as a decimal
const spaceBetween = (targetSize - 1)/(initial.length-1);
// loop over the remaining values
for(let i=0; i < initial.length; i++){
const key = Math.round(spaceBetween*i);
const next = Math.round(spaceBetween*(i+1));
//console.log(key, next);
const result = {
key,
value:initial[i],
next,
}
if(next > targetSize-1){
result.next = key;
}
// round the product of the index * the space between for more even distribution
originalValues[key]=result;
}
// Store our current and next values outside the loop for quicker lookup
let current = originalValues[0];
let next = originalValues[current.next];
const result = [];
// we will yeild in here
for(let i = 0; i<targetSize; i++){
// don't update current and next until we get to their index number
if(next?.key && i >= next?.key){
current = next;
next = originalValues[current.next];
}
if(next){
let percentage = (i - current.key)/(next.key-current.key)
if(Number.isNaN(percentage)) percentage = 1;
//console.log(`i:${i}, key:${key}, current.value:${current.value}, next.value:${next?.value}`);
result.push(fillFunction(percentage, current.value, next?.value));
}
}
return result;
}
function doIt () {
let h = Math.random() * 360;
let l = 80 + Math.random() * 20;
let c = Math.random() * 20;
const startColors = [
`lch(${l}% ${c}% ${h})`,
`lch(${l -= Math.random() * 40}% ${c += Math.random() * 40}% ${h += (Math.random() * 360) % 360})`,
`lch(${l -= Math.random() * 40}% ${c += Math.random() * 40}% ${h += (Math.random() * 360) % 360})`,
];
//const size = 5 + ~~(Math.random() * 1000)
const size = 5000;
const interpolate = (percent, lastValue, nextValue) => {
//console.log(percent, lastValue, nextValue, `color-mix(in lab, ${nextValue} ${(percent * 100).toFixed(2)}%, ${lastValue})`)
return `color-mix(in lab, ${nextValue} ${(percent * 100).toFixed(2)}%, ${lastValue})`
}
console.time('scaleSpread');
const generatorColors = [...scaleSpread(startColors,size,interpolate)]
console.timeEnd('scaleSpread');
console.time('scaleSpreadArray');
const arrayColors = scaleSpreadArray(startColors,size,interpolate);
console.timeEnd('scaleSpreadArray');
console.time('scaleSpreadArrayAlex');
const arrayAlexColors = scaleSpreadArrayAlex(startColors,size,interpolate);
console.timeEnd('scaleSpreadArrayAlex');
document.documentElement.style.setProperty(
'--gs-array', arrayColors.map(
(c, i) => `${c} ${i/arrayColors.length*100}% ${(i+1)/arrayColors.length*100}%`
).join(',')
);
document.documentElement.style.setProperty(
'--gs-generator', generatorColors.map(
(c, i) => `${c} ${i/generatorColors.length*100}% ${(i+1)/generatorColors.length*100}%`
).join(',')
);
document.documentElement.style.setProperty(
'--gs-optimizedarray', generatorColors.map(
(c, i) => `${c} ${i/generatorColors.length*100}% ${(i+1)/generatorColors.length*100}%`
).join(',')
);
const initalInts = [
~~(Math.random() * 10),
~~(Math.random() * 10),
~~(Math.random() * 10),
]
console.log(
`example using random ints between 0 and 10`,
initalInts,
scaleSpreadArray(
initalInts,
10
))
console.log(
`example using random ints between 0 and 10`,
initalInts,
[...scaleSpread(
initalInts,
10
)])
}
doIt();
document.documentElement.addEventListener('click', doIt);
//console.log(scaleSpreadArray([1,2,3], 5, ()=>{}))
// console.log(scaleSpreadArray([1], 2)) // this will error out
//console.log(...scaleSpread([1,2,3], 4))
//console.log(...scaleSpread([1,2,3,4,5,6,7], 17))
html {
height: 100%;
}
body {
display:grid;
grid-template-columns: 1fr 1fr 1fr;
min-height: 100vh;
margin: 10px;
gap:10px;
}
body>*{
height: 100%;
}
.array{
background: linear-gradient(to bottom, var(--gs-array));
}
.generator{
background: linear-gradient(to bottom, var(--gs-generator));
}
.optimizedArray{
background: linear-gradient(to bottom, var(--gs-optimizedarray));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment