How to compare single paths of a decision tree? - javascript

I want to compare single paths of a decision tree in one function in javascript.
I only know how to do this, as the nodes are adding up but thats just comparing the nodes.
Instead I want to look at one path and then look at the next one.
for example: next decision should be in range of -3. given are the numbers [9, 8, 6, 5, 3, 2, 0]. The sum of the list is the value. The path with a length of 6 with the biggest value is wanted.
2 - 0
3 <
5< 0
/ 2 - 0
6
/ \ 2 - 0
3<
0
8 2 - 0
\ 3<
/ 5 < 0
2 - 0
9 2 - 0
3 <
5< 0
\ / 2 - 0
6
\ 2 - 0
3<
0
I want to compare those paths:
[9,6,3,0],
[9,6,3,2,0],
[9,6,5,2,0],
[9,6,5,3,0],
[9,6,5,3,2,0],
[9,8,6,3,0],
[9,8,6,3,2,0],
[9,8,6,5,2,0],
[9,8,6,5,3,0],
[9,8,6,5,3,2,0],
and instead of calculating every path and then comparing them simultaneously, I want to only calculate the first path and then compare it with the second path like this:
[9,6,3,0], //value 18
[9,6,3,2,0]//value 21
|| which one has the nearest length of 6 with the most Value?
\/
[9,6,3,2,0]//value 21
[9,6,3,2,0],//value 21
[9,6,5,2,0] //value 23
|| which one has the nearest length of 6 with the most Value?
\/
[9,6,5,2,0] //value 23
... and so on
how to compare only 2 paths at a time instead of every ?
Edit 1 :
My Code so far: https://replit.com/#RubyKanima/BestSumwithlength#index.js
const numbers = [9, 8, 6, 5, 3, 2, 0];
const starting_node = Math.max(...numbers) + 3;
const path = (current_node, memo ={}) => {
if (current_node in memo) return memo[current_node];
if (current_node == 0) return [[]];
let paths = [];
let tmp_nodes = [];
for (n of numbers) {
if (n >= current_node - 3 && n < current_node) {
tmp_nodes.push(n);
}
}
for (let tmp of tmp_nodes) {
const tmp_path = path(tmp);
const tmp_paths = tmp_path.map(way => [tmp, ...way]);
paths.push(...tmp_paths);
}
memo[current_node] = paths;
paths = paths.filter(x => x.length <= 6);
return paths;
};
const bestPath = all_paths => {
all_paths = all_paths.filter(x => (x.length = 6));
let bestValue = 0;
let bestPath = null;
for (let path of all_paths) {
let value = 0;
for (node of path) value += node;
if (value > bestValue) {
bestValue = value;
bestPath = path;
}
}
return bestPath;
};
const path_array = path(starting_node);
console.log(bestPath(path_array));
It does the job, but if I get over a thousand numbers it gets a stack overflow. (In the example I reduced some numbers to make it easier to understand, in reality the range is -360 and not -3)
Biggest Problem: too much data
How to solve it?: Comparing only 2 Paths at a time and then calculating the next Path.
What I want to know: How to only calculate 2 Paths.

So I wrote a merge sort algorithm and I added some performance time calculation for both your solution and the merge sort solution. In this scenario, it seems the merge sort performs better. It compares two arrays at the time, until it reaches the one you need. Try this on your data, which should be bigger and see if it works better.
const numbers = [9, 8, 6, 5, 3, 2, 0];
const starting_node = Math.max(...numbers) + 3;
const path = (current_node, memo ={}) => {
if (current_node in memo) return memo[current_node];
if (current_node == 0) return [[]];
let paths = [];
let tmp_nodes = [];
for (n of numbers) {
if (n >= current_node - 3 && n < current_node) {
tmp_nodes.push(n);
}
}
for (let tmp of tmp_nodes) {
const tmp_path = path(tmp);
const tmp_paths = tmp_path.map(way => [tmp, ...way]);
paths.push(...tmp_paths);
}
memo[current_node] = paths;
paths = paths.filter(x => x.length <= 6);
return paths;
};
const bestPath = all_paths => {
all_paths = all_paths.filter(x => (x.length = 6));
let bestValue = 0;
let bestPath = null;
for (let path of all_paths) {
let value = 0;
for (node of path) value += node;
if (value > bestValue) {
bestValue = value;
bestPath = path;
}
}
return bestPath;
};
//-----merge sort algorithm---------
const sumArr = arr => arr.reduce((acc, next)=> acc+next)
function merge(arr1, arr2){
if (sumArr(arr1) > sumArr(arr2)) {
return arr1
} else if(sumArr(arr1) < sumArr(arr2)) {
return arr2
} else {return arr1}
}
function mergeSort(arr){
if(arr.length <= 1) return arr[0];
let mid = Math.floor(arr.length/2);
let left = mergeSort(arr.slice(0,mid));
let right = mergeSort(arr.slice(mid));
return merge(left, right);
}
//-----end of merge sort algorithm------
const path_array = path(starting_node);
const start = performance.now()
console.log(bestPath(path_array));
const end = performance.now()
console.log("bestPath performance ", end-start)
const start2 = performance.now()
console.log(mergeSort(path_array))
const end2 = performance.now()
console.log("mergeSort performance ", end2-start2)

Related

Is there a Javascript function to calculate the median of a Set [duplicate]

I've been trying to calculate median but still I've got some mathematical issues I guess as I couldn't get the correct median value and couldn't figure out why. Here's the code;
class StatsCollector {
constructor() {
this.inputNumber = 0;
this.average = 0;
this.timeout = 19000;
this.frequencies = new Map();
for (let i of Array(this.timeout).keys()) {
this.frequencies.set(i, 0);
}
}
pushValue(responseTimeMs) {
let req = responseTimeMs;
if (req > this.timeout) {
req = this.timeout;
}
this.average = (this.average * this.inputNumber + req) / (this.inputNumber + 1);
console.log(responseTimeMs / 1000)
let groupIndex = Math.floor(responseTimeMs / 1000);
this.frequencies.set(groupIndex, this.frequencies.get(groupIndex) + 1);
this.inputNumber += 1;
}
getMedian() {
let medianElement = 0;
if (this.inputNumber <= 0) {
return 0;
}
if (this.inputNumber == 1) {
return this.average
}
if (this.inputNumber == 2) {
return this.average
}
if (this.inputNumber > 2) {
medianElement = this.inputNumber / 2;
}
let minCumulativeFreq = 0;
let maxCumulativeFreq = 0;
let cumulativeFreq = 0;
let freqGroup = 0;
for (let i of Array(20).keys()) {
if (medianElement <= cumulativeFreq + this.frequencies.get(i)) {
minCumulativeFreq = cumulativeFreq;
maxCumulativeFreq = cumulativeFreq + this.frequencies.get(i);
freqGroup = i;
break;
}
cumulativeFreq += this.frequencies.get(i);
}
return (((medianElement - minCumulativeFreq) / (maxCumulativeFreq - minCumulativeFreq)) + (freqGroup)) * 1000;
}
getAverage() {
return this.average;
}
}
Here's the snapshot of the results when I enter the values of
342,654,987,1093,2234,6243,7087,20123
The correct result should be;
Median: 1663.5
Change your median method to this:
function median(values){
if(values.length ===0) throw new Error("No inputs");
values.sort(function(a,b){
return a-b;
});
var half = Math.floor(values.length / 2);
if (values.length % 2)
return values[half];
return (values[half - 1] + values[half]) / 2.0;
}
fiddle
Here's another solution:
function median(numbers) {
const sorted = Array.from(numbers).sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
console.log(median([4, 5, 7, 1, 33]));
The solutions above - sort then find middle - are fine, but slow on large data sets. Sorting the data first has a complexity of n x log(n).
There is a faster median algorithm, which consists in segregating the array in two according to a pivot, then looking for the median in the larger set. Here is some javascript code, but here is a more detailed explanation
// Trying some array
alert(quickselect_median([7,3,5])); // 2300,5,4,0,123,2,76,768,28]));
function quickselect_median(arr) {
const L = arr.length, halfL = L/2;
if (L % 2 == 1)
return quickselect(arr, halfL);
else
return 0.5 * (quickselect(arr, halfL - 1) + quickselect(arr, halfL));
}
function quickselect(arr, k) {
// Select the kth element in arr
// arr: List of numerics
// k: Index
// return: The kth element (in numerical order) of arr
if (arr.length == 1)
return arr[0];
else {
const pivot = arr[0];
const lows = arr.filter((e)=>(e<pivot));
const highs = arr.filter((e)=>(e>pivot));
const pivots = arr.filter((e)=>(e==pivot));
if (k < lows.length) // the pivot is too high
return quickselect(lows, k);
else if (k < lows.length + pivots.length)// We got lucky and guessed the median
return pivot;
else // the pivot is too low
return quickselect(highs, k - lows.length - pivots.length);
}
}
Astute readers will notice a few things:
I simply transliterated Russel Cohen's Python solution into JS,
so all kudos to him.
There are several small optimisations worth
doing, but there's parallelisation worth doing, and the code as is
is easier to change in either a quicker single-threaded, or quicker
multi-threaded, version.
This is the average linear time
algorithm, there is more efficient a deterministic linear time version, see Russel's
post for details, including performance data.
ADDITION 19 Sept. 2019:
One comment asks whether this is worth doing in javascript. I ran the code in JSPerf and it gives interesting results.
if the array has an odd number of elements (one figure to find), sorting is 20% slower that this "fast median" proposition.
if there is an even number of elements, the "fast" algorithm is 40% slower, because it filters through the data twice, to find elements number k and k+1 to average. It is possible to write a version of fast median that doesn't do this.
The test used rather small arrays (29 elements in the jsperf test). The effect appears to be more pronounced as arrays get larger. A more general point to make is: it shows these kinds of optimisations are worth doing in Javascript. An awful lot of computation is done in JS, including with large amounts of data (think of dashboards, spreadsheets, data visualisations), and in systems with limited resources (think of mobile and embedded computing).
var arr = {
max: function(array) {
return Math.max.apply(null, array);
},
min: function(array) {
return Math.min.apply(null, array);
},
range: function(array) {
return arr.max(array) - arr.min(array);
},
midrange: function(array) {
return arr.range(array) / 2;
},
sum: function(array) {
var num = 0;
for (var i = 0, l = array.length; i < l; i++) num += array[i];
return num;
},
mean: function(array) {
return arr.sum(array) / array.length;
},
median: function(array) {
array.sort(function(a, b) {
return a - b;
});
var mid = array.length / 2;
return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2;
},
modes: function(array) {
if (!array.length) return [];
var modeMap = {},
maxCount = 1,
modes = [array[0]];
array.forEach(function(val) {
if (!modeMap[val]) modeMap[val] = 1;
else modeMap[val]++;
if (modeMap[val] > maxCount) {
modes = [val];
maxCount = modeMap[val];
}
else if (modeMap[val] === maxCount) {
modes.push(val);
maxCount = modeMap[val];
}
});
return modes;
},
variance: function(array) {
var mean = arr.mean(array);
return arr.mean(array.map(function(num) {
return Math.pow(num - mean, 2);
}));
},
standardDeviation: function(array) {
return Math.sqrt(arr.variance(array));
},
meanAbsoluteDeviation: function(array) {
var mean = arr.mean(array);
return arr.mean(array.map(function(num) {
return Math.abs(num - mean);
}));
},
zScores: function(array) {
var mean = arr.mean(array);
var standardDeviation = arr.standardDeviation(array);
return array.map(function(num) {
return (num - mean) / standardDeviation;
});
}
};
2022 TypeScript Approach
const median = (arr: number[]): number | undefined => {
if (!arr.length) return undefined;
const s = [...arr].sort((a, b) => a - b);
const mid = Math.floor(s.length / 2);
return s.length % 2 === 0 ? ((s[mid - 1] + s[mid]) / 2) : s[mid];
};
Notes:
The type in the function signature (number[]) ensures only an array of numbers can be passed to the function. It could possibly be empty though.
if (!arr.length) return undefined; checks for the possible empty array, which would not have a median.
[...arr] creates a copy of the passed-in array to ensure we don't overwrite the original.
.sort((a, b) => a - b) sorts the array of numbers in ascending order.
Math.floor(s.length / 2) finds the index of the middle element if the array has odd length, or the element just to the right of the middle if the array has even length.
s.length % 2 === 0 determines whether the array has an even length.
(s[mid - 1] + s[mid]) / 2 averages the two middle items of the array if the array's length is even.
s[mid] is the middle item of an odd-length array.
TypeScript Answer 2020:
// Calculate Median
const calculateMedian = (array: Array<number>) => {
// Check If Data Exists
if (array.length >= 1) {
// Sort Array
array = array.sort((a: number, b: number) => {
return a - b;
});
// Array Length: Even
if (array.length % 2 === 0) {
// Average Of Two Middle Numbers
return (array[(array.length / 2) - 1] + array[array.length / 2]) / 2;
}
// Array Length: Odd
else {
// Middle Number
return array[(array.length - 1) / 2];
}
}
else {
// Error
console.error('Error: Empty Array (calculateMedian)');
}
};
const median = (arr) => {
return arr.slice().sort((a, b) => a - b)[Math.floor(arr.length / 2)];
};
Short and sweet.
Array.prototype.median = function () {
return this.slice().sort((a, b) => a - b)[Math.floor(this.length / 2)];
};
Usage
[4, 5, 7, 1, 33].median()
Works with strings as well
["a","a","b","b","c","d","e"].median()
For better performance in terms of time complexity, use MaxHeap - MinHeap to find the median of stream of array.
Simpler & more efficient
const median = dataSet => {
if (dataSet.length === 1) return dataSet[0]
const sorted = ([ ...dataSet ]).sort()
const ceil = Math.ceil(sorted.length / 2)
const floor = Math.floor(sorted.length / 2)
if (ceil === floor) return sorted[floor]
return ((sorted[ceil] + sorted[floor]) / 2)
}
Simple solution:
function calcMedian(array) {
const {
length
} = array;
if (length < 1)
return 0;
//sort array asc
array.sort((a, b) => a - b);
if (length % 2) {
//length of array is odd
return array[(length + 1) / 2 - 1];
} else {
//length of array is even
return 0.5 * [(array[length / 2 - 1] + array[length / 2])];
}
}
console.log(2, calcMedian([1, 2, 2, 5, 6]));
console.log(3.5, calcMedian([1, 2, 2, 5, 6, 7]));
console.log(9, calcMedian([13, 9, 8, 15, 7]));
console.log(3.5, calcMedian([1, 4, 6, 3]));
console.log(5, calcMedian([5, 1, 11, 2, 8]));
Simpler, more efficient, and easy to read
cloned the data to avoid alterations to the original data.
sort the list of values.
get the middle point.
get the median from the list.
return the median.
function getMedian(data) {
const values = [...data];
const v = values.sort( (a, b) => a - b);
const mid = Math.floor( v.length / 2);
const median = (v.length % 2 !== 0) ? v[mid] : (v[mid - 1] + v[mid]) / 2;
return median;
}
const medianArr = (x) => {
let sortedx = x.sort((a,b)=> a-b);
let halfIndex = Math.floor(sortedx.length/2);
return (sortedx.length%2) ? (sortedx[Math.floor(sortedx.length/2)]) : ((sortedx[halfIndex-1]+sortedx[halfIndex])/2)
}
console.log(medianArr([1,2,3,4,5]));
console.log(medianArr([1,2,3,4,5,6]));
function Median(arr){
let len = arr.length;
arr = arr.sort();
let result = 0;
let mid = Math.floor(len/2);
if(len % 2 !== 0){
result += arr[mid];
}
if(len % 2 === 0){
result += (arr[mid] + arr[mid+1])/2
}
return result;
}
console.log(`The median is ${Median([0,1,2,3,4,5,6])}`)
function median(arr) {
let n = arr.length;
let med = Math.floor(n/2);
if(n % 2 != 0){
return arr[med];
} else{
return (arr[med -1] + arr[med])/ 2.0
}
}
console.log(median[1,2,3,4,5,6]);
The arr.sort() method sorts the elements of an array in place and returns the array. By default, it sorts the elements alphabetically, so if the array contains numbers, they will not be sorted in numerical order.
On the other hand, the arr.sort((a, b) => a - b) method uses a callback function to specify how the array should be sorted. The callback function compares the two elements a and b and returns a negative number if a should be sorted before b, a positive number if b should be sorted before a, and zero if the elements are equal. In this case, the callback function subtracts b from a, which results in a sorting order that is numerical in ascending order.
So, if you want to sort an array of numbers in ascending order, you should use arr.sort((a, b) => a - b), whereas if you want to sort an array of strings alphabetically, you can use arr.sort():
function median(numbers) {
const sorted = Array.from(numbers).sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
function findMedian(arr) {
arr.sort((a, b) => a - b)
let i = Math.floor(arr.length / 2)
return arr[i]
}
let result = findMedian([0, 1, 2, 4, 6, 5, 3])
console.log(result)

How to populate an array with integers

Please, how do you populate an array say ‘num’ with numbers not in a second array say ‘fig’? I’m trying to use a loop to have the values of the already populated array ‘fig’ compared to ‘num’ which is to be populated with integers not found in ‘fig’. I’m a bit confused.
If you need to do an array with n numbers you can use this two ways.
const arrayLength = 100;
const numberArray = [...new Array(arrayLength).keys()]
const anotherWay = new Array(arrayLength).fill().map((_, idx) => idx + 1);
console.log(numberArray, anotherWay)
so to do this we have to do a few things:
1) define an existing array with numbers to avoid
2) define length on new array
3) generate a random number and make it an integer
4) check to see if we need to avoid
5) if it's a new value add it to the second array
var first=[55,45,35,1,2,3,4,5];
var second = [];
var i = 7;
var x;
while (i != 0){
x = ~~(Math.random()*100);
var check = false;
for(let j=0; j<first.length;j++){
if(x == first[j]){
check = true;
}
}
if(!check){
second.push(x);
i--;
}
}
console.log(second);
const fig = [-21, 0, 3, 6, 7, 42]
const min = Math.min(...fig) // or fig[0] if the the array is already sorted
const max = Math.max(...fig) // or fig[fig.length - 1]
const num = Array.from({ length: max - min }, (_, i) => i + min)
.filter(el => !fig.includes(el))
or, saving one loop
const num = Array.from({ length: max - min }).reduce((acc, _, i) => {
const curr = i + min
if (!fig.includes(curr)) {
return acc.concat(curr)
}
return acc
}, [])
This is assuming your range is from the smallest number in fig to the largest in fig.

How to generate an array of every permutation of a sequence, with duplicates?

I've had a look around this site but I have been unable to find an answer that includes duplicate elements. For example, given the array:
[1,2,3,4]
With a length of 3, A function should generate a list of every single possible combination with those numbers, using each one more than once:
[
[1,1,1],
[1,1,2],
[1,1,3],
...
[4,4,2],
[4,4,3],
[4,4,4]
]
I just haven't been able to get my head around the algorithm that I should use. I don't expect a code answer, but a push in the right direction would be appreciated.
I've tried using reduce like so:
const arr = [1, 2, 3, 4]
const len = 3
arr.reduce((acc, n) => {
for (let i = 0; i < len; i++) {
acc.push(/* ???? */)
}
return acc
}, [])
but I really don't know how to continue.
As a side note, ideally, I would like to do this as efficiently as possible.
One approach would be to use the length of the array as a base. You could then just access the array's elements from 0, and count up to the amount of combinations (array length ** length). If you're working with a small dataset, performance really shouldn't be an issue, but this answer is very performance oriented:
const getCombos = (arr, len) => {
const base = arr.length
const counter = Array(len).fill(base === 1 ? arr[0] : 0)
if (base === 1) return [counter]
const combos = []
const increment = i => {
if (counter[i] === base - 1) {
counter[i] = 0
increment(i - 1)
} else {
counter[i]++
}
}
for (let i = base ** len; i--;) {
const combo = []
for (let j = 0; j < counter.length; j++) {
combo.push(arr[counter[j]])
}
combos.push(combo)
increment(counter.length - 1)
}
return combos
}
const combos = getCombos([1, 2, 3, 4], 3)
console.log(combos)
You could take an algorithm for getting a cartesian prduct with an array of three arrays with the wanted values.
var values = [1, 2, 3, 4],
length = 3,
result = Array
.from({ length }, _ => values)
.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
console.log(result.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Picture an ancient mechanical rotary counter:
To count from 00000 to 99999 you rotate the rightmost wheel until it reaches 9, then it resets to 0 and the second right wheel is advanced by 1 etc.
In code: locate the rightmost wheel which position is less than the max. digit. Advance that wheel by 1 and reset all wheels on the right of it to 0. Repeat until there's no such wheel.
function counter(digits, size) {
let wheels = Array(size).fill(0),
len = digits.length,
res = [];
while (1) {
res.push(wheels.map(n => digits[n]));
for (let i = size - 1; i >= 0; i--) {
if (wheels[i] < len - 1) {
wheels[i]++;
wheels.fill(0, i + 1);
break;
}
if (i === 0)
return res;
}
}
}
all = counter('1234', 3)
console.log(...all.map(c => c.join('')))
Performance measures:
const kobe = (arr, len) => {
const base = arr.length
const counter = Array(len).fill(base === 1 ? arr[0] : 0)
if (base === 1) return [counter]
const combos = []
const increment = i => {
if (counter[i] === base - 1) {
counter[i] = 0
increment(i - 1)
} else {
counter[i]++
}
}
for (let i = base ** len; i--;) {
const combo = []
for (let j = 0; j < counter.length; j++) {
combo.push(arr[counter[j]])
}
combos.push(combo)
increment(counter.length - 1)
}
return combos
}
function nina(values, length) {
return Array
.from({length}, _ => values)
.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
}
function trincot(digits, size) {
if (!size) return [[]]; // base case
let shorter = trincot(digits, size - 1); // get all solutions for smaller size
// ...and prefix each of those with each possible digit
return Array.from(digits, dig => shorter.map(arr => [dig, ...arr])).flat();
}
function georg(digits, size) {
let wheels = Array(size).fill(0),
len = digits.length,
res = [];
while (1) {
res.push(wheels.map(n => digits[n]));
for (let i = size - 1; i >= 0; i--) {
if (wheels[i] < len - 1) {
wheels[i]++;
wheels.fill(0, i + 1);
break;
}
if (i === 0)
return res;
}
}
}
const gilad = (A, k) =>
k ? gilad(A, k - 1).reduce((a, s) => a.concat(A.map(e => s.concat(e))), []) : [[]]
//////////////////////////////////////////////////////////////
fns = [kobe, nina, trincot, georg, gilad];
ary = [0, 1, 2, 3, 4, 5, 6, 7, 8]
size = 5
res = []
for (f of fns) {
console.time(f.name);
res.push(f(ary, size));
console.timeEnd(f.name)
}
Using the same technique to generate cartesian products:
function product(...arrays) {
let size = arrays.length,
wheels = Array(size).fill(0),
lens = arrays.map(a => a.length),
res = [];
while (1) {
res.push(wheels.map((n, w) => arrays[w][n]));
for (let w = size - 1; w >= 0; w--) {
if (wheels[w] < lens[w] - 1) {
wheels[w]++;
wheels.fill(0, w + 1);
break;
}
if (w === 0)
return res;
}
}
}
// demo
p = product('12', 'abcde', 'XYZ')
console.log(...p.map(q => q.join('')))
// some timings
// https://stackoverflow.com/a/43053803/989121
const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
arrays = Array(7).fill(0).map(_ => Array(5).fill(0)) // 5**7=78125 combinations
console.time('func')
cartesian(...arrays)
console.timeEnd('func')
console.time('iter')
product(...arrays)
console.timeEnd('iter')
Maybe also add the recursive solution:
function counter(digits, size) {
if (!size) return [[]]; // base case
let shorter = counter(digits, size-1); // get all solutions for smaller size
// ...and prefix each of those with each possible digit
return Array.from(digits, dig => shorter.map(arr => [dig, ...arr])).flat();
}
// demo
let all = counter("1234", 3);
console.log(...all.map(c => c.join("")));
This is known as "n multichoose k" and has the following recurrence relation:
function f(ns, n, k){
if (n == 0)
return []
if (k == 0)
return [[]]
return f(ns, n - 1, k).concat(
f(ns, n, k - 1).map(s => s.concat(ns[n-1])))
}
var multiset = [1, 2, 3, 4]
var k = 3
console.log(JSON.stringify(f(multiset, multiset.length, k)))
An alternative, as others have answered, is to also include every permutation of every combination. One way is to append each element to each combination as we build towards the final length. (This idea is similar to trincot's.)
const f = (A, k) =>
k ? f(A, k - 1).reduce((a, s) => a.concat(A.map(e => s.concat(e))), []) : [[]]
var A = [1, 2, 3, 4]
var k = 3
console.log(JSON.stringify(f(A, k)))

How to reduce an array of timestamps to an array of counts of the number of elements within a particular time range

I have an array that looks like
[
"2017-05-08T13:42:00.318Z",
"2017-05-08T13:42:05.590Z",
"2017-05-08T13:42:12.377Z",
"2017-05-08T13:42:20.830Z",
"2017-05-08T13:42:22.634Z",
"2017-05-08T13:42:25.249Z"
]
And I want to end up with an array of counts of how many timestamps fall into 10 second time ranges.
So for the above example "2017-05-08T13:42:00.318Z" and "2017-05-08T13:42:05.590Z" would fall into the range 2017-05-08T13:42:00.000Z to 2017-05-08T13:42:10.000Z.
"2017-05-08T13:42:12.377Z" would fall into the range of 2017-05-08T13:42:10.000Z to 2017-05-08T13:42:20.000Z.
"2017-05-08T13:42:20.830Z", "2017-05-08T13:42:22.634Z", and "2017-05-08T13:42:25.249Z" would fall into the range of 2017-05-08T13:42:20.000Z to 2017-05-08T13:42:30.000Z.
My goal is to get an array of counts of the number of timestamps in each of these ranges. For the above example, the result would be: [2, 1, 3].
Note: I am using Typescript so answers using ES6 features will work.
You could slice the date and check it with the previous date.
value slot result
------------------------ ------------------ ---------
2017-05-08T13:42:00.318Z 2017-05-08T13:42:0 [1]
2017-05-08T13:42:05.590Z 2017-05-08T13:42:0 [2]
2017-05-08T13:42:12.377Z 2017-05-08T13:42:1 [2, 1]
2017-05-08T13:42:20.830Z 2017-05-08T13:42:2 [2, 1, 1]
2017-05-08T13:42:22.634Z 2017-05-08T13:42:2 [2, 1, 2]
2017-05-08T13:42:25.249Z 2017-05-08T13:42:2 [2, 1, 3]
var data = ["2017-05-08T13:42:00.318Z", "2017-05-08T13:42:05.590Z", "2017-05-08T13:42:12.377Z", "2017-05-08T13:42:20.830Z", "2017-05-08T13:42:22.634Z", "2017-05-08T13:42:25.249Z"],
count = data.reduce(function (r, a, i, aa) {
if (i && aa[i - 1].slice(0, 18) === a.slice(0, 18)) {
r[r.length - 1]++;
} else {
r.push(1);
}
return r;
}, []);
console.log(count)
This could work using some loops which I am not particularly proud of, but you can change the interval and it can account for more data later if you just use the function called by the forEach on its own.
const timestampsToCheck = [
"2017-05-08T13:42:00.318Z",
"2017-05-08T13:42:05.590Z",
"2017-05-08T13:42:12.377Z",
"2017-05-08T13:42:20.830Z",
"2017-05-08T13:42:22.634Z",
"2017-05-08T13:42:25.249Z"
];
let nextTime = null;
let outputTime = [0];
function getNextTime() {
nextTime.setSeconds(nextTime.getSeconds() + 10);
}
timestampsToCheck.forEach((element) => {
let thisTime = new Date(element);
let outputLast = outputTime.length - 1;
if (nextTime === null) {
nextTime = thisTime;
getNextTime();
outputTime[outputLast]++;
} else if (thisTime > nextTime) {
getNextTime();
while (thisTime > nextTime) {
getNextTime();
outputTime.push(0);
}
outputTime.push(1);
} else {
outputTime[outputLast]++;
}
});
console.log(outputTime);
const timestampsToCheck = [
'2017-05-08T13:42:00.318Z',
'2017-05-08T13:42:05.590Z',
'2017-05-08T13:42:12.377Z',
'2017-05-08T13:42:20.830Z',
'2017-05-08T13:42:22.634Z',
'2017-05-08T13:42:25.249Z',
];
const ranges = [
['2017-05-08T13:42:00.000Z', '2017-05-08T13:42:10.000Z'],
['2017-05-08T13:42:10.000Z', '2017-05-08T13:42:20.000Z'],
['2017-05-08T13:42:20.000Z', '2017-05-08T13:42:30.000Z'],
];
const timestampsInRange = ranges.reduce((acc, range) => {
const count = timestampsToCheck.reduce((innerAcc, time) => {
return innerAcc + ((time > range[0] && time < range[1]) ? 1 : 0);
}, 0);
return acc.concat(count);
}, []);
console.log(timestampsInRange);
Working fiddle: https://jsfiddle.net/9tjerwxa/

Print all values from array but with formatting if there are intervals

I have a array of numbers. I want to print all values, but with interval if it's possible. For example:
[1,2,3,5,7,8,10]
I think you will understand what I want to get with example. For above array I want to get the following string:
"1-3, 5, 7-8, 10"
So, we have 1,2,3 in the array, then 5. The 4 is not present, so we close first interval and add to the string 1-3. Then continue, after 5 goes 7. In this case we can't create interval, therefore we just add 5. Then continue, we have 7, after 7 goes 8, after 8 goes 10, so we can create 7-8 interval only. the 10 is last so we just add it to string. In result, we have:
"1-3, 5, 7-8, 10".
I solved this task with the following:
let numbers = [1,2,3,4,7,9,12,13];
numbers.sort((a,b) => {return a - b});
let str='';
let startIndex =0, start, end;
if(numbers.length === 1) {
str = `${numbers[0]}`;
}
for(let i=0; i< numbers.length; i++) {
if(i === 0) {
continue;
}
if(numbers[i-1] +1 !== numbers[i]) {
start = numbers[startIndex];
end = numbers[i -1];
if(start - end === 0) {
str += `${start},`;
}
else {
str += `${start} - ${end},`;
}
startIndex = i;
}
if(i+1 === numbers.length) {
start = numbers[startIndex];
end = numbers[i];
if(start - end === 0) {
str += `${numbers[i]}`;
} else {
str += `${numbers[startIndex]} - ${numbers[i]}`;
}
}
}
console.log(str);
But, code looks very complex for understanding. Is there a better way to do this?
DEMO
You could test the items with their predecessor and move it to the right group.
var data = [1, 2, 3, 5, 7, 8, 10],
result = data.reduce(function (r, a, i, aa) {
if (aa[i - 1] === a - 1) {
r[r.length - 1][1] = a;
} else {
r.push([a]);
}
return r;
}, []).map(a => a.join('-')).join(', ');
console.log(result);
Here's a function that converts a (sorted) array into an array of pairs (ranges).
let rangify = function(xs) {
let ranges = [];
for (let x of xs) {
let last = ranges[ranges.length - 1];
if (!last || x > last[1] + 1)
ranges.push([x, x])
else
last[1]++;
}
return ranges;
};
let r = rangify([1,2,3,5,7,8,10,11,12,13,77,78]);
console.log(r)
It should be obvious how to get your desired output from that.

Categories