I'm using node.js currently, and i would like to find / try all the unique possibilities resulting from the combinations of data stored in an array.
Example :
// source array
var array = [0,1,2];
// what i need :
0
1
2
00
01
02
10
11
12
20
21
22
000
001
002
etc, up to a configurable length.
I've looked on the forums and didn't manage to find something like this.
I've searched on npm and found some librairies that are doing similar things, but never all the possibilities i need. I'm feeling like my need is very similar to a bubble sorting algorithm, but i don't know how to do this.
Also it would be very much better to do it without storing the whole output in a variable at the same time, as the idea is that my code needs to work with larger arrays.
Any idea or solution i missed would help at this point !
Edit : Also, i would like to continuously try combination until i decide it's enough, such as 500 try or if the last combination length is 5 for example.
Another approach, with several reusable functions:
const flatten = arrays => [].concat.apply([], arrays);
const range = (lo, hi) => [...new Array(hi - lo + 1)].map((_, i) => i + lo)
const join = joiner => list => list.join(joiner)
const charSeqs = (n, chars) => (n < 1)
? [[]]
: flatten(chars.map(char => charSeqs(n - 1, chars).map(
seq => flatten([char].concat(seq))
)))
const allCharSeqs = (n, chars) => flatten(range(1, n).map(i => charSeqs(i, chars)))
console.log(allCharSeqs(3, [0, 1, 2]).map(join('')))
function variations(arr, length) {
if (length == 0)
return [];
else if (length == 1)
return arr.map(e => e.toString());
let result = [];
for (let i = 0; i < arr.length; i++) {
for (let tail of variations(arr, length - 1))
result.push(arr[i].toString() + tail);
}
return result;
}
function variations2(arr, maxLength) {
let result = [];
for (let i = 0; i <= maxLength; i++)
result = result.concat(variations(arr, i));
return result;
}
Example:
var array = [0,1,2];
console.log(variations2(array, 2));
Output:
["0", "1", "2", "00", "01", "02", "10", "11", "12", "20", "21", "22"]
Related
Is there a better way to convert
["88 99", "20 99", "12 12"]
to a hashmap in the form
{"88": 1, "99": 2, "20": 1, "12": 1}
Using map or reduce?
Where in this case, a string with duplicate numbers only gets increases it's count by 1.
Currently I'm converting the above array into a 2d array using .split(' ')
and iterating over that 2d array in another for loop as so:
var counts = {}
for (let i = 0; i < logs.length; i++){
let ack = logs[i].split(' ');
if(ack[0]==ack[1]){
counts[ack[0]] = counts[ack[0]] ? counts[ack[0]] + 1 : 1;
}
else{
for(let j= 0; j < 2; j++){
counts[ack[j]] = counts[ack[j]] ? counts[ack[j]] + 1 : 1;
}
}
}
First I group by numbers, summing the appearances of each. This is using the reduce part. That's it. I used adaption of method by https://stackoverflow.com/a/62031473/3807365
var arr = ["88 99", "20 99", "12 12"]
var step1 = arr.reduce(function(agg, pair) {
pair.split(" ").forEach(function(item) {
agg[item] = (agg[item] || 0) + 1
})
return agg;
}, {})
console.log(step1)
Yes. I'm assuming you want to write in a functional style, so I'm not worrying about efficiency, etc.
You can collapse this into a hairy one-liner but I wrote the intermediate steps and output them for illustration.
const input = ["88 99", "20 99", "12 12"];
const split = input.map( (string) => string.split(" ") );
console.log("split: " + JSON.stringify(split));
const flattened = split.reduce( (acc,array) => acc = acc.concat(array), []);
console.log("flattened: " + JSON.stringify(flattened));
const output = flattened.reduce( (byKey, item) => {
if (!byKey[item]) byKey[item] = 0;
byKey[item]++;
return byKey;
}, {});
console.log(JSON.stringify(output))
I am looking for a solution with runtime better than O(n squared), or the most optimized solution for this question.
I had this question come up in an interview. I'll do my best to reproduce it:
Given an array of objects with properties 'year' and 'value', and a number k, return the largest valid subset of that array wherein the difference between max and min values of that subset is <= k.
EDIT: the objects in a valid subset do not have to be originally consecutive objects
For example:
data = [
{ 'year': 2002,
'value': 1
},
{ 'year': 2003,
'value': 5
},
{ 'year': 2004,
'value': 4
},
{ 'year': 2005,
'value': 3
},
{ 'year': 2006,
'value': 3
}
]
k = 1
solution: [
{ 'year': 2004,
'value': 4
},
{ 'year': 2005,
'value': 3
},
{ 'year': 2006,
'value': 3
}
]
In this example, another valid subset is:
{ 'year': 2003,
'value': 5
},
{ 'year': 2004,
'value': 4
}
But the other subset is longer, so you return that one.
One solution I came up with is to sort the two arrays, iterate over the sorted arrays, add all valid subsets to a new array, and then iterate over it and return the longest subset:
function findLargestValidSubset(k, data) {
let allSubsets = []
data = data.sort((a, b) => a.value - b.value)
for (var i = 1; i < data.length; i++) {
if (data[i].value - data[i-1].value <= k) {
let subset = [data[i-1], data[i]]
let j = i + 1
while (j < data.length &&
(data[j].value - data[j-1].value <= k) &&
(data[j].value - data[i-1].value <= k)) {
subset.push(data[j])
j++
}
allSubsets.push(subset)
}
}
let maxSubsetLength = 0
let maxSubset
for (var i = 0; i < allSubsets.length; i++) {
if (allSubsets[i].length > maxSubsetLength) {
maxSubset = allSubsets[i]
maxSubsetLength = maxSubset.length
}
}
return maxSubset
}
console.log(findLargestValidSubset(1, data))
This still seems like it is not the most optimized solution, and I am struggling to come up with one that is better.
wishful thinking
We can approach this problem using one of my favourite programming techniques, wishful thinking. We simply write how we wish to express our program -
console.log(longest(solve(remap(data), 1))) // k = 1
Then one by one, we make our wishes come true. You have good intuition to sort the input, however I'll go step further and group elements with the same value too. This allows us to skip potentially huge spans where the same value appears many times in a row -
function remap (arr) {
const m = arr
.sort((a, b) => a.value - b.value)
.reduce
( (m, t) =>
m.has(t.value)
? m.get(t.value).push(t) && m
: m.set(t.value, [t])
, new Map
)
return Array.from(m.entries(), ([ value, items ]) => ({ value, items }))
}
Here's what our remap'd input looks like -
remap(data)
[ {value:1,items:[{year:2002,value:1}]}
, {value:3,items:[{year:2005,value:3},{year:2006,value:3}]} // <- grouped
, {value:4,items:[{year:2004,value:4}]}
, {value:5,items:[{year:2003,value:5}]}
]
Fulfill any remaining wishes. Next write solver. Stay as high-level as possible, without zooming too closely on any specific implementation detail. If the input array, t, has a length of less than two (2), there is nothing to solve. Otherwise for each array index of t, yield a single solution -
function* solver (t, k) {
if (t.length < 2)
return yield [t]
for (let i = 0; i < t.length; i++)
yield solve1(solution(t, k, range(i)))
}
A solution is simply a collection of inputs, our input array, arr, our constant, k, and a selected range -
function solution (arr, k, range) {
return { arr, k, range }
}
Oh and a range is simply a left and a right numeric boundary -
function range (left = 0, right = left + 1) {
return { left, right }
}
To solve a single solution, we write solve1. If the input solution, sln, is valid, attempt to grow the solution and re-solve. Otherwise, the solution has outgrown k or the arr boundaries. shrink the solution and return its subset -
function solve1 (sln) {
if (valid(sln))
return solve1(grow(sln))
else
return subset(shrink(sln))
}
Checking if a solution is valid is a matter of selecting the left and right boundaries. If the difference is less-than-or-equal to k, it is valid -
function valid (sln) {
const left = sln.arr[sln.range.left]
const right = sln.arr[sln.range.right]
return left && right && right.value - left.value <= sln.k
}
We can grow and shrink a single solution by incrementing or decrementing the solution's range -
function grow (sln) {
return solution(sln.arr, sln.k, range(sln.range.left, sln.range.right + 1))
}
function shrink (sln) {
return solution(sln.arr, sln.k, range(sln.range.left, sln.range.right - 1))
}
To extract a solution's subset, we slice the solution's arr using the solution's range. Simply flatMap over the slice and select each element's items -
function subset (sln) {
return sln.arr.slice(sln.range.left, sln.range.right + 1).flatMap(v => v.items)
}
At this point our solver is functional -
for (const subset of solver(remap(data), 1))
console.lorg(subset)
[{"year":2002,"value":1}]
[{"year":2005,"value":3},{"year":2006,"value":3},{"year":2004,"value":4}]
[{"year":2004,"value":4},{"year":2003,"value":5}]
[{"year":2003,"value":5}]
Finally we implement longest to compute the longest subset -
function longest (it) {
let r = []
for (const v of it)
if (v.length > r.length)
r = v
return r
}
console.log("smalldata", longest(solver(remap(data), 1)))
smalldata [{"year":2005,"value":3},{"year":2006,"value":3},{"year":2004,"value":4}]
simple objects
Note we are careful not to slice and manipulate the input as we solve. This is important because the input may be significantly large and each iteration would involve a costly operation on a large dataset. Instead we modelled solution and range as simple objects and perform step-by-step operations on these objects. We only extract a particular solution's items once it has grown to the maximum size. On that note...
one million elements
Given an input of one million elements, we can calculate the longest subset of k = 10 in less than one second -
const randInt = (n) =>
Math.ceil(Math.random() * n)
const bigdata =
Array.from(Array(1e6), _ => ({ year: 1900 + randInt(120), value: randInt(1e7) }))
console.time("solver")
const answer = longest(solver(remap(bigdata), 10))
console.timeEnd("solver")
console.log("bigdata", JSON.stringify(answer))
solver: 643.000 ms
bigdata [{"year":1901,"value":182530},{"year":1905,"value":182531},{"year":1988,"value":182533},{"year":1924,"value":182533},{"year":1930,"value":182535},{"year":1973,"value":182535},{"year":2010,"value":182536},{"year":1977,"value":182537},{"year":2011,"value":182540}]
demo
Expand the snippet below to verify the result in your own browser -
function remap (arr) {
const m = arr
.sort((a, b) => a.value - b.value)
.reduce
( (m, t) =>
m.has(t.value)
? m.get(t.value).push(t) && m
: m.set(t.value, [t])
, new Map
)
return Array.from(m.entries(), ([ value, items ]) => ({ value, items }))
}
function* solver (t, k) {
if (t.length < 2)
return yield [t]
for (let i = 0; i < t.length; i++)
yield solve1(solution(t, k, range(i)))
}
function solve1 (sln) {
if (valid(sln))
return solve1(grow(sln))
else
return subset(shrink(sln))
}
function longest (it) {
let r = []
for (const v of it)
if (v.length > r.length)
r = v
return r
}
function range (left = 0, right = left + 1) {
return { left, right }
}
function solution (arr, k, range) {
return { arr, k, range }
}
function valid (sln) {
const left = sln.arr[sln.range.left]
const right = sln.arr[sln.range.right]
return left && right && right.value - left.value <= sln.k
}
function grow (sln) {
return solution(sln.arr, sln.k, range(sln.range.left, sln.range.right + 1))
}
function shrink (sln) {
return solution(sln.arr, sln.k, range(sln.range.left, sln.range.right - 1))
}
function subset (sln) {
return sln.arr.slice(sln.range.left, sln.range.right + 1).flatMap(v => v.items)
}
const randInt = (n) =>
Math.ceil(Math.random() * n)
const smalldata =
[{year: 2002,value: 1},{year: 2003,value: 5},{year: 2004,value: 4},{year: 2005,value: 3},{year: 2006,value: 3}]
const bigdata =
Array.from(Array(1e6), _ => ({ year: 1900 + randInt(120), value: randInt(1e7) }))
console.log("smalldata", longest(solver(remap(smalldata), 1)))
console.time("solver")
const answer = longest(solver(remap(bigdata), 10))
console.timeEnd("solver")
console.log("bigdata", JSON.stringify(answer))
smalldata [{"year":2005,"value":3},{"year":2006,"value":3},{"year":2004,"value":4}]
solver: 643.000 ms
bigdata [{"year":1901,"value":182530},{"year":1905,"value":182531},{"year":1988,"value":182533},{"year":1924,"value":182533},{"year":1930,"value":182535},{"year":1973,"value":182535},{"year":2010,"value":182536},{"year":1977,"value":182537},{"year":2011,"value":182540}]
generators
Generators are super powerful because they allow a function to return, or yield, more than one result. You may not have noticed it earlier in the post, but you can iterate over solver using a for loop to get all of its outputs, or use Array.from to collect them all in an array. However, to answer your specific question, longest consumes the entire generator and returns only a single value.
I'm working on a small algorithm to find the closest values of a given number in an random array of numbers. In my case I'm trying to detect connected machines identified by a 6-digit number ID ("123456", "0078965", ...) but it can be useful for example to find the closest geolocated users around me.
What I need is to list the 5 closest machines, no matter if their IDs are higher or lower. This code works perfectly but I'm looking for a smarter and better way to proceed, amha I got to much loops and arrays.
let n = 0; // counter
let m = 5; // number of final machines to find
// list of IDs founded (unordered: we can't decide)
const arr = ["087965","258369","885974","0078965","457896","998120","698745","399710","357984","698745","789456"]
let NUM = "176789" // the random NUM to test
const temp = [];
const diff = {};
let result = null;
// list the [m] highest founded (5 IDs)
for(i=0 ; i<arr.length; i++) {
if(arr[i] > NUM) {
for(j=0 ; j<m; j++) {
temp.push(arr[i+j]);
} break;
}
}
// list the [m] lowest founded (5 IDs)
for(i=arr.length ; i>=0; i--) {
if(arr[i] < NUM) {
for(j=m ; j>=0; j--) {
temp.push(arr[i-j]);
} break;
}
}
// now we are certain to get at least 5 IDs even if NUM is 999999 or 000000
temp.sort(function(a, b){return a - b}); // increase order
for(i=0 ; i<(m*2); i++) {
let difference = Math.abs(NUM - temp[i]);
diff[difference] = temp[i]; // [ 20519 : "964223" ]
}
// we now get a 10-values "temp" array ordered by difference
// list the [m] first IDs:
for(key in diff){
if(n < m){
let add = 6-diff[key].toString().length;
let zer = '0'.repeat(add);
let id = zer+diff[key]; // "5802" -> "005802"
result += (n+1)+":"+ id +" ";
n+=1;
}
}
alert(result);
-> "1:0078965 2:087965 3:258369 4:357984 5:399710" for "176789"
You actually don't need to have so many different iterations. All you need is to loop twice:
The first iteration attempt is to use .map() to create an array of objects that stores the ID and the absolute difference between the ID and num
The second iteration attempt is simply to use .sort() through the array of objects created in step 1, ranking them from lowest to highest difference
Once the second iteration is done, you simply use .slice(0, 5) to get the first 5 objects in the array, which now contains the smallest 5 diffs. Iterate through it again if you want to simply extract the ID:
const arr = ["087965","258369","885974","078965","457896","998120","698745","399710","357984","698745","789456"];
let num = "176789";
let m = 5; // number of final machines to find
// Create an array of objects storing the original arr + diff from `num`
const diff = arr.map(item => {
return { id: item, diff: Math.abs(+item - +num) };
});
// Sort by difference from `num` (lowest to highest)
diff.sort((a, b) => a.diff - b.diff);
// Get the first m entries
const filteredArr = diff.slice(0, m).map(item => item.id).sort();
// Log
console.log(filteredArr);
// Completely optional, if you want to format it the way you have in your question
console.log(`"${filteredArr.map((v, i) => i + ": " + v).join(', ')}" for "${num}"`);
You could take an array as result set, fill it with the first n elements and sort it by the delta of the wanted value.
For all other elements check if the absolute delta of the actual item and the value is smaller then the last value of the result set and replace this value with the actual item. Sort again. Repeat until all elements are processed.
The result set is ordered by the smallest delta to the greatest by using the target value.
const
absDelta = (a, b) => Math.abs(a - b),
sortDelta = v => (a, b) => absDelta(a, v) - absDelta(b, v),
array = [087965, 258369, 885974, 0078965, 457896, 998120, 698745, 399710, 357984, 698745, 789456],
value = 176789,
n = 5,
result = array.reduce((r, v) => {
if (r.length < n) {
r.push(v);
r.sort(sortDelta(value));
return r;
}
if (absDelta(v, value) < absDelta(r[n - 1], value)) {
r[n - 1] = v;
r.sort(sortDelta(value));
}
return r;
}, []);
console.log(result); // sorted by closest value
A few good approaches so far, but I can't resist throwing in another.
This tests a sliding window of n elements in a sorted version of the array, and returns the one whose midpoint is closest to the value you're looking for. This is a pretty efficient approach (one sort of the array, and then a single pass through that) -- though it does not catch cases where there's more than one correct answer (see the last test case below).
const closestN = function(n, target, arr) {
// make sure we're not comparing strings, then sort:
let sorted = arr.map(Number).sort((a, b) => a - b);
target = Number(target);
let bestDiff = Infinity; // actual diff can be assumed to be lower than this
let bestSlice = 0; // until proven otherwise
for (var i = 0; i <= sorted.length - n; i++) {
let median = medianOf(sorted[i], sorted[i+n-1]) // midpoint of the group
let diff = Math.abs(target - median); // distance to the target
if (diff < bestDiff) { // we improved on the previous attempt
bestDiff = diff; // capture this for later comparisons
bestSlice = i;
}
// TODO handle diff == bestDiff? i.e. more than one possible correct answer
}
return sorted.slice(bestSlice, bestSlice + n)
}
// I cheated a bit here; this won't work if a > b:
const medianOf = function(a, b) {
return (Math.abs(b-a) / 2) + a
}
console.log(closestN(5, 176789, ["087965", "258369", "885974", "0078965", "457896", "998120", "698745", "399710", "357984", "698745", "789456"]))
// more test cases
console.log(closestN(3, 5, [1,2,5,8,9])) // should be 2,5,8
console.log(closestN(3, 4, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(1, 4, [1,2,5,8,9])) // should be 5
console.log(closestN(3, 99, [1,2,5,8,9])) // should be 5,8,9
console.log(closestN(3, -99, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(3, -2, [-10, -5, 0, 4])) // should be -5, 0, 4
console.log(closestN(1, 2, [1,3])) // either 1 or 3 would be correct...
The array I get my original strings from looks something like this:
arr[0]:
11-3
12-6
arr[1]:
5-9
7-2
18-2
arr[2]:
2-7
(That's just an example, the general idea is that there can be any number of objects in arr and the string in each of them contains any number of #-# combos)
I'm trying to add all the numbers on the left together (if using the example above it would add something like 11, 12, 5, 7, 18, and 2 together) and store that number in a variable.
How would I go about this?
Edit 1: attempted code:
var winsLossNums = winLoss[0].match(/\d+/g).map(Number)
for (var i = 0; i < winLoss[0].match(/\d+/g).map(Number).length; i++) {
if (i % 2 === 0) {
totalNums.push(winLoss[0].match(/\d+/g).map(Number)[i]);
}
}
}
This code is in a loop, and every loop there is a new arr object like in the example above
Assuming your array values are strings with a new line between them, you can reduce over the array, split each value on \n and reduce again on that taking the first value of splitting on '-':
let arr = ['11-3\n12-6', '5-9\n7-2\n18-2', '2-7']
let tot = arr.reduce((a, c) => {
let pairs = c.split('\n')
return a + pairs.reduce((a, c)=> a + Number(c.split('-')[0]), 0)
}, 0)
console.log(tot)
console.log(11 + 12 + 5 + 7+ 18 + 2)
You might need to clean up data or split on whitespace if it's not cleanly one \n per line. But this should be a good start.
You can try this:
let arr = [
[
'11-3',
'12-6'
], [
'5-9',
'7-2',
'18-2'
], [
'2-7'
]
];
let sum = 0;
for (let index=0; index<arr.length; index++) {
let arrayMasterElement = arr[index];
// console.log(arrayMasterElement);
for (let index2=0; index2<arrayMasterElement.length; index2++) {
let arrayElement = arrayMasterElement[index2];
let elementArray = arrayElement.split('-');
let intVal = parseInt(elementArray[0]);
console.log('Int value: ' + intVal);
sum += intVal;
}
if (index == arr.length - 1) {
console.log('Sum: ' + sum);
}
}
I wrote this function that generates prime numbers for a given range of numbers, I think it's simple and clear that must give the expected result, though, there are numbers that are excluded from the result:
function numberator(from, to) {
numbers = [];
for (x = from; x <= to; x++) {
numbers.push(x);
}
return numbers;
}
function primeNumbers(array) {
for (i = 0; i < array.length; i++) {
for (j = 2; j < array[i]; j++) {
if (array[i] % j == 0) {
array.splice(i, 1);
}
}
}
return array;
}
console.log(primeNumbers(numberator(1,100)));
The result contains: 27, 35 and 95 (also 1, that I may handle later.)
I tried to figure out why but I couldn't.
The bug is in-place modification of array with splice. You keep on incrementing i, while the size of array is getting modified at execution time.
You can debug this, by printing the whole array after each slice operation.
Also the solution is highly inefficient and will perform terribly for slightly long ranges. Use Sieve of Eratosthenes
In this
array.splice(i, 1);
statement you are changing the length of the array but not modifying the index i accordingly. Change it to
array.splice(i--, 1)
Just want to share a simple and very easy to understand Javascript code to generate small prime numbers: https://github.com/HMaxF/generate-small-prime-number
Take it more simple :
const isPrime = (n =>
n>1 &&
Array.from({length: Math.floor(Math.sqrt(n))-1},(e,i) => i+2)
.every(m => n%m)
);
const generateRange = (from, to) => Array.from({length: to-from + 1}, (v, k) => k+from);
then
generateRange(1, 100).filter(isPrime); // DONE!
//---- Utilities
const isPrime = (n =>
n>1 &&
Array.from({length: Math.floor(Math.sqrt(n))-1},(e,i) => i+2)
.every(m => n%m)
);
const generateRange = (from, to) => Array.from({length: to-from + 1}, (v, k) => k+from);
//---------Apply ---
const from = 1;
const to = 100;
console.log (
`Prime numbers from ${from} to ${to} are : `,
generateRange(from, to).filter(isPrime)
)