I've seen several similar questions about how to generate all possible combinations of elements in an array. But I'm having a very hard time figuring out how to write an algorithm that will only output combination pairs. Any suggestions would be super appreciated!
Starting with the following array (with N elements):
var array = ["apple", "banana", "lemon", "mango"];
And getting the following result:
var result = [
"apple banana"
"apple lemon"
"apple mango"
"banana lemon"
"banana mango"
"lemon mango"
];
I was trying out the following approach but this results in all possible combinations, instead only combination pairs.
var letters = splSentences;
var combi = [];
var temp= "";
var letLen = Math.pow(2, letters.length);
for (var i = 0; i < letLen ; i++){
temp= "";
for (var j=0;j<letters.length;j++) {
if ((i & Math.pow(2,j))){
temp += letters[j]+ " "
}
}
if (temp !== "") {
combi.push(temp);
}
}
Here are some functional programming solutions:
Using EcmaScript2019's flatMap:
var array = ["apple", "banana", "lemon", "mango"];
var result = array.flatMap(
(v, i) => array.slice(i+1).map( w => v + ' ' + w )
);
console.log(result);
Before the introduction of flatMap (my answer in 2017), you would go for reduce or [].concat(...) in order to flatten the array:
var array = ["apple", "banana", "lemon", "mango"];
var result = array.reduce( (acc, v, i) =>
acc.concat(array.slice(i+1).map( w => v + ' ' + w )),
[]);
console.log(result);
Or:
var array = ["apple", "banana", "lemon", "mango"];
var result = [].concat(...array.map(
(v, i) => array.slice(i+1).map( w => v + ' ' + w ))
);
console.log(result);
A simple way would be to do a double for loop over the array where you skip the first i elements in the second loop.
let array = ["apple", "banana", "lemon", "mango"];
let results = [];
// Since you only want pairs, there's no reason
// to iterate over the last element directly
for (let i = 0; i < array.length - 1; i++) {
// This is where you'll capture that last value
for (let j = i + 1; j < array.length; j++) {
results.push(`${array[i]} ${array[j]}`);
}
}
console.log(results);
Rewritten with ES5:
var array = ["apple", "banana", "lemon", "mango"];
var results = [];
// Since you only want pairs, there's no reason
// to iterate over the last element directly
for (var i = 0; i < array.length - 1; i++) {
// This is where you'll capture that last value
for (var j = i + 1; j < array.length; j++) {
results.push(array[i] + ' ' + array[j]);
}
}
console.log(results);
In my case, I wanted to get the combinations as follows, based on the size range of the array:
function getCombinations(valuesArray: String[])
{
var combi = [];
var temp = [];
var slent = Math.pow(2, valuesArray.length);
for (var i = 0; i < slent; i++)
{
temp = [];
for (var j = 0; j < valuesArray.length; j++)
{
if ((i & Math.pow(2, j)))
{
temp.push(valuesArray[j]);
}
}
if (temp.length > 0)
{
combi.push(temp);
}
}
combi.sort((a, b) => a.length - b.length);
console.log(combi.join("\n"));
return combi;
}
Example:
// variable "results" stores an array with arrays string type
let results = getCombinations(['apple', 'banana', 'lemon', ',mango']);
Output in console:
The function is based on the logic of the following documentation, more information in the following reference:
https://www.w3resource.com/javascript-exercises/javascript-function-exercise-3.php
if ((i & Math.pow(2, j)))
Each bit of the first value is compared with the second, it is taken as valid if it matches, otherwise it returns zero and the condition is not met.
Although solutions have been found, I post here an algorithm for general case to find all combinations size n of m (m>n) elements. In your case, we have n=2 and m=4.
const result = [];
result.length = 2; //n=2
function combine(input, len, start) {
if(len === 0) {
console.log( result.join(" ") ); //process here the result
return;
}
for (let i = start; i <= input.length - len; i++) {
result[result.length - len] = input[i];
combine(input, len-1, i+1 );
}
}
const array = ["apple", "banana", "lemon", "mango"];
combine( array, result.length, 0);
I ended up writing a general solution to this problem, which is functionally equivalent to nhnghia's answer, but I'm sharing it here as I think it's easier to read/follow and is also full of comments describing the algorithm.
/**
* Generate all combinations of an array.
* #param {Array} sourceArray - Array of input elements.
* #param {number} comboLength - Desired length of combinations.
* #return {Array} Array of combination arrays.
*/
function generateCombinations(sourceArray, comboLength) {
const sourceLength = sourceArray.length;
if (comboLength > sourceLength) return [];
const combos = []; // Stores valid combinations as they are generated.
// Accepts a partial combination, an index into sourceArray,
// and the number of elements required to be added to create a full-length combination.
// Called recursively to build combinations, adding subsequent elements at each call depth.
const makeNextCombos = (workingCombo, currentIndex, remainingCount) => {
const oneAwayFromComboLength = remainingCount == 1;
// For each element that remaines to be added to the working combination.
for (let sourceIndex = currentIndex; sourceIndex < sourceLength; sourceIndex++) {
// Get next (possibly partial) combination.
const next = [ ...workingCombo, sourceArray[sourceIndex] ];
if (oneAwayFromComboLength) {
// Combo of right length found, save it.
combos.push(next);
}
else {
// Otherwise go deeper to add more elements to the current partial combination.
makeNextCombos(next, sourceIndex + 1, remainingCount - 1);
}
}
}
makeNextCombos([], 0, comboLength);
return combos;
}
The best solutions I have found - https://lowrey.me/es6-javascript-combination-generator/
Uses ES6 generator functions, I adapted to TS. Most often you don't need all of the combinations at the same time. And I was getting annoyed by writing loops like for (let i=0; ... for let (j=i+1; ... for (let k=j+1... just to get combos one by one to test if I need to terminate the loops..
export function* combinations<T>(array: T[], length: number): IterableIterator<T[]> {
for (let i = 0; i < array.length; i++) {
if (length === 1) {
yield [array[i]];
} else {
const remaining = combinations(array.slice(i + 1, array.length), length - 1);
for (let next of remaining) {
yield [array[i], ...next];
}
}
}
}
usage:
for (const combo of combinations([1,2,3], 2)) {
console.log(combo)
}
output:
> (2) [1, 2]
> (2) [1, 3]
> (2) [2, 3]
Just to give an option for next who'll search it
const arr = ['a', 'b', 'c']
const combinations = ([head, ...tail]) => tail.length > 0 ? [...tail.map(tailValue => [head, tailValue]), ...combinations(tail)] : []
console.log(combinations(arr)) //[ [ 'a', 'b' ], [ 'a', 'c' ], [ 'b', 'c' ] ]
There are also this answer:
https://stackoverflow.com/a/64414875/19518308
The alghorithm is this answer generates all the possible sets of combination(or choose(n, k)) of n items within k spaces.
The algorhitm:
function choose(arr, k, prefix=[]) {
if (k == 0) return [prefix];
return arr.flatMap((v, i) =>
choose(arr.slice(i+1), k-1, [...prefix, v])
);
}
console.log(choose([0,1,2,3,4], 3));
I had a similar problem and this algorhitm is working very well for me.
Using map and flatMap the following can be done (flatMap is only supported on chrome and firefox)
var array = ["apple", "banana", "lemon", "mango"]
array.flatMap(x => array.map(y => x !== y ? x + ' ' + y : null)).filter(x => x)
I think it is an answer to all such questions.
/**
*
* Generates all combination of given Array or number
*
* #param {Array | number} item - Item accepts array or number. If it is array exports all combination of items. If it is a number export all combination of the number
* #param {number} n - pow of the item, if given value is `n` it will be export max `n` item combination
* #param {boolean} filter - if it is true it will just export items which have got n items length. Otherwise export all posible length.
* #return {Array} Array of combination arrays.
*
* Usage Example:
*
* console.log(combination(['A', 'B', 'C', 'D'], 2, true)); // [[ 'A','A' ], [ 'A', 'B' ]...] (16 items)
* console.log(combination(['A', 'B', 'C', 'D'])); // [['A', 'A', 'A', 'B' ],.....,['A'],] (340 items)
* console.log(comination(4, 2)); // all posible values [[ 0 ], [ 1 ], [ 2 ], [ 3 ], [ 0, 0 ], [ 0, 1 ], [ 0, 2 ]...] (20 items)
*/
function combination(item, n) {
const filter = typeof n !=='undefined';
n = n ? n : item.length;
const result = [];
const isArray = item.constructor.name === 'Array';
const count = isArray ? item.length : item;
const pow = (x, n, m = []) => {
if (n > 0) {
for (var i = 0; i < count; i++) {
const value = pow(x, n - 1, [...m, isArray ? item[i] : i]);
result.push(value);
}
}
return m;
}
pow(isArray ? item.length : item, n);
return filter ? result.filter(item => item.length == n) : result;
}
console.log("#####first sample: ", combination(['A', 'B', 'C', 'D'], 2)); // with filter
console.log("#####second sample: ", combination(['A', 'B', 'C', 'D'])); // without filter
console.log("#####third sample: ", combination(4, 2)); // gives array with index number
Generating combinations of elements in an array is a lot like counting in a numeral system,
where the base is the number of elements in your array (if you account for the leading zeros that will be missing).
This gives you all the indices to your array (concatenated):
arr = ["apple", "banana", "lemon", "mango"]
base = arr.length
idx = [...Array(Math.pow(base, base)).keys()].map(x => x.toString(base))
You are only interested in pairs of two, so restrict the range accordingly:
range = (from, to) = [...Array(to).keys()].map(el => el + from)
indices = range => range.map(x => x.toString(base).padStart(2,"0"))
indices( range( 0, Math.pow(base, 2))) // range starts at 0, single digits are zero-padded.
Now what's left to do is map indices to values.
As you don't want elements paired with themselves and order doesn't matter,
those need to be removed, before mapping to the final result.
const range = (from, to) => [...Array(to).keys()].map(el => el + from)
const combinations = arr => {
const base = arr.length
return range(0, Math.pow(base, 2))
.map(x => x.toString(base).padStart(2, "0"))
.filter(i => !i.match(/(\d)\1/) && i === i.split('').sort().join(''))
.map(i => arr[i[0]] + " " + arr[i[1]])
}
console.log(combinations(["apple", "banana", "lemon", "mango"]))
With more than ten elements, toString() will return letters for indices; also, this will only work with up to 36 Elements.
Generating combinations is a classic problem. Here's my interpretation of that solution:
const combinations = (elements) => {
if (elements.length == 1) {
return [elements];
} else {
const tail = combinations(elements.slice(1));
return tail.reduce(
(combos, combo) => { combos.push([elements[0], ...combo]); return combos; },
[[elements[0]], ...tail]
);
}
};
const array = ["apple", "banana", "lemon", "mango"];
console.log(combinations(array));
Here is an non-mutating ES6 approach combining things (TS):
function combine (tail: any[], length: number, head: any[][] = [[]]): any[][] {
return tail.reduce((acc, tailElement) => {
const tailHeadVariants = head.reduce((acc, headElement: any[]) => {
const combination = [...headElement, tailElement]
return [...acc, combination]
}, [])
if (length === 1) return [...acc, tailHeadVariants]
const subCombinations = combine(tail.filter(t => t !== tailElement), length - 1, tailHeadVariants)
return [...acc, ...subCombinations]
}, [])
}
As this post is well indexed on Google under the keywords "generate all combinations", lots of people coming here simply need to generate all the unique combinations, regardless of the size of the output (not only pairs).
This post answers this need.
All unique combinations, without recursion:
const getCombos = async (a) => {
const separator = '';
const o = Object();
for (let i = 0; i < a.length; ++i) {
for (let j = i + 1; j <= a.length; ++j) {
const left = a.slice(i, j);
const right = a.slice(j, a.length);
o[left.join(separator)] = 1;
for (let k = 0; k < right.length; ++k) {
o[[...left, right[k]].join(separator)] = 1;
}
}
}
return Object.keys(o);
}
const a = ['a', 'b', 'c', 'd'];
const b = await getCombos(a);
console.log(b);
// (14) ['a', 'ab', 'ac', 'ad', 'abc', 'abd', 'abcd',
// 'b', 'bc', 'bd', 'bcd', 'c', 'cd', 'd']
This code splits the array into 2 sub arrays, left / right, then iterate over the right array to combine it with the left array. The left becomes bigger overtime, while the right becomes smaller. The result has only unique values.
Beating a dead horse a bit, but with smaller sets where recursion limit and performance is not a problem, the general combination generation can be done recursively with "recurse combinations containing the first element in given array" plus "recurse combinations not containing the first element". It gives quite compact implementation as a generator:
// Generator yielding k-item combinations of array a
function* choose(a, k) {
if(a.length == k) yield a;
else if(k == 0) yield [];
else {
for(let rest of choose(a.slice(1), k-1)) yield [a[0], ...rest];
for(let rest of choose(a.slice(1), k)) yield rest;
}
}
And even slightly shorter (and twice faster, 1 M calls of 7 choose 5 took 3.9 seconds with my MacBook) with function returning and array of combinations:
// Return an array of combinations
function comb(a, k) {
if(a.length === k) return [a];
else if(k === 0) return [[]];
else return [...comb(a.slice(1), k-1).map(c => [a[0], ...c]),
...comb(a.slice(1), k)];
}
Related
How could this one be tweaked so that it could increment a set of two letters, so that it'd look like this:
AA, AB, AC...AZ, BA, BB, BC, etc
This is borrowed from tckmn, but it addresses one letter only.
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
function incrementChar(c) {
var index = alphabet.indexOf(c)
if (index == -1) return -1 // or whatever error value you want
return alphabet[index + 1 % alphabet.length]
}
Appreciate your help!
You just need two loops. One to iterate over the alphabet, and the second to iterate over the alphabet on each iteration of the first loop.
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const arr = [];
for (let i = 0; i < alphabet.length; i++) {
for (let j = 0; j < alphabet.length; j++) {
arr.push(`${alphabet[i]}${alphabet[j]}`);
}
}
console.log(arr);
In your situation, how about the following sample script?
const increase = s => {
const idx = [...s].reduce((c, e, i, a) => (c += (e.charCodeAt(0) - 64) * Math.pow(26, a.length - i - 1)), -1);
// Ref: https://stackoverflow.com/a/53678158
columnIndexToLetter = (n) => (a = Math.floor(n / 26)) >= 0 ? columnIndexToLetter(a - 1) + String.fromCharCode(65 + (n % 26)) : "";
return columnIndexToLetter(idx + 1);
};
const samples = ["A", "Z", "AA", "AZ", "ZZ"];
const res1 = samples.map(e => increase(e));
console.log(res1); // <--- [ 'B', 'AA', 'AB', 'BA', 'AAA' ]
// If you want to give one value, please use the following script.
const sample = "AA";
const res2 = increase(sample);
console.log(res2); // <--- AB
When this script is run, ["A", "Z", "AA", "AZ", "ZZ"] is converted to [ 'B', 'AA', 'AB', 'BA', 'AAA' ].
Reference:
map()
I have an array of following strings:
['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']
...etc.
I need a solution that will give me following ordered result
['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0'].
I tried to implement a sort so it first sorts by the numbers in the first position, than in case of equality, sort by the numbers in the second position (after the first dot), and so on...
I tried using sort() and localeCompare(), but if I have elements '4.5.0' and '4.11.0', I get them sorted as ['4.11.0','4.5.0'], but I need to get ['4.5.0','4.11.0'].
How can I achieve this?
You could prepend all parts to fixed size strings, then sort that, and finally remove the padding again.
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.split('.').map( n => +n+100000 ).join('.') ).sort()
.map( a => a.split('.').map( n => +n-100000 ).join('.') );
console.log(arr)
Obviously you have to choose the size of the number 100000 wisely: it should have at least one more digit than your largest number part will ever have.
With regular expression
The same manipulation can be achieved without having to split & join, when you use the callback argument to the replace method:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.replace(/\d+/g, n => +n+100000 ) ).sort()
.map( a => a.replace(/\d+/g, n => +n-100000 ) );
console.log(arr)
Defining the padding function once only
As both the padding and its reverse functions are so similar, it seemed a nice exercise to use one function f for both, with an extra argument defining the "direction" (1=padding, -1=unpadding). This resulted in this quite obscure, and extreme code. Consider this just for fun, not for real use:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = (f=>f(f(arr,1).sort(),-1)) ((arr,v)=>arr.map(a=>a.replace(/\d+/g,n=>+n+v*100000)));
console.log(arr);
Use the sort compare callback function
You could use the compare function argument of sort to achieve the same:
arr.sort( (a, b) => a.replace(/\d+/g, n => +n+100000 )
.localeCompare(b.replace(/\d+/g, n => +n+100000 )) );
But for larger arrays this will lead to slower performance. This is because the sorting algorithm will often need to compare a certain value several times, each time with a different value from the array. This means that the padding will have to be executed multiple times for the same number. For this reason, it will be faster for larger arrays to first apply the padding in the whole array, then use the standard sort, and then remove the padding again.
But for shorter arrays, this approach might still be the fastest. In that case, the so-called natural sort option -- that can be achieved with the extra arguments of localeCompare -- will be more efficient than the padding method:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.sort( (a, b) => a.localeCompare(b, undefined, { numeric:true }) );
console.log(arr);
More about the padding and unary plus
To see how the padding works, look at the intermediate result it generates:
[ "100005.100005.100001", "100004.100021.100000", "100004.100022.100000",
"100006.100001.100000", "100005.100001.100000" ]
Concerning the expression +n+100000, note that the first + is the unary plus and is the most efficient way to convert a string-encoded decimal number to its numerical equivalent. The 100000 is added to make the number have a fixed number of digits. Of course, it could just as well be 200000 or 300000. Note that this addition does not change the order the numbers will have when they would be sorted numerically.
The above is just one way to pad a string. See this Q&A for some other alternatives.
If you are looking for a npm package to compare two semver version, https://www.npmjs.com/package/compare-versions is the one.
Then you can sort version like this:
// ES6/TypeScript
import compareVersions from 'compare-versions';
var versions = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var sorted = versions.sort(compareVersions);
You could split the strings and compare the parts.
function customSort(data, order) {
function isNumber(v) {
return (+v).toString() === v;
}
var sort = {
asc: function (a, b) {
var i = 0,
l = Math.min(a.value.length, b.value.length);
while (i < l && a.value[i] === b.value[i]) {
i++;
}
if (i === l) {
return a.value.length - b.value.length;
}
if (isNumber(a.value[i]) && isNumber(b.value[i])) {
return a.value[i] - b.value[i];
}
return a.value[i].localeCompare(b.value[i]);
},
desc: function (a, b) {
return sort.asc(b, a);
}
}
var mapped = data.map(function (el, i) {
return {
index: i,
value: el.split('')
};
});
mapped.sort(sort[order] || sort.asc);
return mapped.map(function (el) {
return data[el.index];
});
}
var array = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0'];
console.log('sorted array asc', customSort(array));
console.log('sorted array desc ', customSort(array, 'desc'));
console.log('original array ', array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can check in loop if values are different, return difference, else continue
var a=['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
a.sort(function(a,b){
var a1 = a.split('.');
var b1 = b.split('.');
var len = Math.max(a1.length, b1.length);
for(var i = 0; i< len; i++){
var _a = +a1[i] || 0;
var _b = +b1[i] || 0;
if(_a === _b) continue;
else return _a > _b ? 1 : -1
}
return 0;
})
console.log(a)
Though slightly late this would be my solution;
var arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"],
sorted = arr.sort((a,b) => {var aa = a.split("."),
ba = b.split(".");
return +aa[0] < +ba[0] ? -1
: aa[0] === ba[0] ? +aa[1] < +ba[1] ? -1
: aa[1] === ba[1] ? +aa[2] < +ba[2] ? -1
: 1
: 1
: 1;
});
console.log(sorted);
Here's a solution I developed based on #trincot's that will sort by semver even if the strings aren't exactly "1.2.3" - they could be i.e. "v1.2.3" or "2.4"
function sortSemVer(arr, reverse = false) {
let semVerArr = arr.map(i => i.replace(/(\d+)/g, m => +m + 100000)).sort(); // +m is just a short way of converting the match to int
if (reverse)
semVerArr = semVerArr.reverse();
return semVerArr.map(i => i.replace(/(\d+)/g, m => +m - 100000))
}
console.log(sortSemVer(["1.0.1", "1.0.9", "1.0.10"]))
console.log(sortSemVer(["v2.1", "v2.0.9", "v2.0.12", "v2.2"], true))
This seems to work provided there are only digits between the dots:
var a = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']
a = a.map(function (x) {
return x.split('.').map(function (x) {
return parseInt(x)
})
}).sort(function (a, b) {
var i = 0, m = a.length, n = b.length, o, d
o = m < n ? n : m
for (; i < o; ++i) {
d = (a[i] || 0) - (b[i] || 0)
if (d) return d
}
return 0
}).map(function (x) {
return x.join('.')
})
'use strict';
var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
Array.prototype.versionSort = function () {
var arr = this;
function isNexVersionBigger (v1, v2) {
var a1 = v1.split('.');
var b2 = v2.split('.');
var len = a1.length > b2.length ? a1.length : b2.length;
for (var k = 0; k < len; k++) {
var a = a1[k] || 0;
var b = b2[k] || 0;
if (a === b) {
continue;
} else
return b < a;
}
}
for (var i = 0; i < arr.length; i++) {
var min_i = i;
for (var j = i + 1; j < arr.length; j++) {
if (isNexVersionBigger(arr[i], arr[j])) {
min_i = j;
}
}
var temp = arr[i];
arr[i] = arr[min_i];
arr[min_i] = temp;
}
return arr;
}
console.log(arr.versionSort());
This solution accounts for version numbers that might not be in the full, 3-part format (for example, if one of the version numbers is just 2 or 2.0 or 0.1, etc).
The custom sort function I wrote is probably mostly what you're looking for, it just needs an array of objects in the format {"major":X, "minor":X, "revision":X}:
var versionArr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var versionObjectArr = [];
var finalVersionArr = [];
/*
split each version number string by the '.' and separate them in an
object by part (major, minor, & revision). If version number is not
already in full, 3-part format, -1 will represent that part of the
version number that didn't exist. Push the object into an array that
can be sorted.
*/
for(var i = 0; i < versionArr.length; i++){
var splitVersionNum = versionArr[i].split('.');
var versionObj = {};
switch(splitVersionNum.length){
case 1:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":-1,
"revision":-1
};
break;
case 2:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":parseInt(splitVersionNum[1]),
"revision":-1
};
break;
case 3:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":parseInt(splitVersionNum[1]),
"revision":parseInt(splitVersionNum[2])
};
}
versionObjectArr.push(versionObj);
}
//sort objects by parts, going from major to minor to revision number.
versionObjectArr.sort(function(a, b){
if(a.major < b.major) return -1;
else if(a.major > b.major) return 1;
else {
if(a.minor < b.minor) return -1;
else if(a.minor > b.minor) return 1;
else {
if(a.revision < b.revision) return -1;
else if(a.revision > b.revision) return 1;
}
}
});
/*
loops through sorted object array to recombine it's version keys to match the original string's value. If any trailing parts of the version
number are less than 0 (i.e. they didn't exist so we replaced them with
-1) then leave that part of the version number string blank.
*/
for(var i = 0; i < versionObjectArr.length; i++){
var versionStr = "";
for(var key in versionObjectArr[i]){
versionStr = versionObjectArr[i].major;
versionStr += (versionObjectArr[i].minor < 0 ? '' : "." + versionObjectArr[i].minor);
versionStr += (versionObjectArr[i].revision < 0 ? '' : "." + versionObjectArr[i].revision);
}
finalVersionArr.push(versionStr);
}
console.log('Original Array: ',versionArr);
console.log('Expected Output: ',['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0']);
console.log('Actual Output: ', finalVersionArr);
Inspired from the accepted answer, but ECMA5-compatible, and with regular string padding (see my comments on the answer):
function sortCallback(a, b) {
function padParts(version) {
return version
.split('.')
.map(function (part) {
return '00000000'.substr(0, 8 - part.length) + part;
})
.join('.');
}
a = padParts(a);
b = padParts(b);
return a.localeCompare(b);
}
Usage:
['1.1', '1.0'].sort(sortCallback);
const arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"];
const sorted = arr.sort((a,b) => {
const ba = b.split('.');
const d = a.split('.').map((a1,i)=>a1-ba[i]);
return d[0] ? d[0] : d[1] ? d[1] : d[2]
});
console.log(sorted);
This can be in an easier way using the sort method without hardcoding any numbers and in a more generic way.
enter code here
var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
splitArray = arr.map(elements => elements.split('.'))
//now lets sort based on the elements on the corresponding index of each array
//mapped.sort(function(a, b) {
// if (a.value > b.value) {
// return 1;
// }
// if (a.value < b.value) {
// return -1;
// }
// return 0;
//});
//here we compare the first element with the first element of the next version number and that is [5.1.2,5.7.2] 5,5 and 1,7 and 2,2 are compared to identify the smaller version...In the end use the join() to get back the version numbers in the proper format.
sortedArray = splitArray.sort((a, b) => {
for (i in a) {
if (parseInt(a[i]) < parseInt(b[i])) {
return -1;
break
}
if (parseInt(a[i]) > parseInt(b[i])) {
return +1;
break
} else {
continue
}
}
}).map(p => p.join('.'))
sortedArray = ["5.1.0", "5.1.1", "5.1.1", "5.1.2", "5.7.2.2"]
sort 1.0a notation correct
use native localeCompare to sort 1.090 notation
function log(label,val){
document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
}
const sortVersions = (
x,
v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
) => x.sort((a, b) => (a + b).match(/[a-z]/)
? v(b) < v(a) ? 1 : -1
: a.localeCompare(b, 0, {numeric: true}))
let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1","1.0a"];
log(' input : ',v);
log('sorted: ',sortVersions(v));
log('no dups:',[...new Set(sortVersions(v))]);
In ES6 you can go without regex.
const versions = ["0.4", "0.11", "0.4.1", "0.4", "0.4.2", "2.0.1","2", "0.0.1", "0.2.3"];
const splitted = versions.map(version =>
version
.split('.')
.map(i => +i))
.map(i => {
let items;
if (i.length === 1) {
items = [0, 0]
i.push(...items)
}
if (i.length === 2) {
items = [0]
i.push(...items)
}
return i
})
.sort((a, b) => {
for(i in a) {
if (a[i] < b[i]) {
return -1;
}
if (a[i] > b[i]) {
return +1;
}
}
})
.map(item => item.join('.'))
const sorted = [...new Set(splitted)]
If ES6 I do this:
versions.sort((v1, v2) => {
let [, major1, minor1, revision1 = 0] = v1.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
let [, major2, minor2, revision2 = 0] = v2.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
if (major1 != major2) return parseInt(major1) - parseInt(major2);
if (minor1 != minor2) return parseInt(minor1) - parseInt(major2);
return parseInt(revision1) - parseInt(revision2);
});
**Sorted Array Object by dotted version value**
var sampleData = [
{ name: 'Edward', value: '2.1.2' },
{ name: 'Sharpe', value: '2.1.3' },
{ name: 'And', value: '2.2.1' },
{ name: 'The', value: '2.1' },
{ name: 'Magnetic', value: '2.2' },
{ name: 'Zeros', value: '0' },
{ name: 'Zeros', value: '1' }
];
arr = sampleData.map( a => a.value).sort();
var requireData = [];
arr.forEach(function(record, index){
var findRecord = sampleData.find(arr => arr.value === record);
if(findRecord){
requireData.push(findRecord);
}
});
console.log(requireData);
[check on jsfiddle.net][1]
[1]: https://jsfiddle.net/jx3buswq/2/
It is corrected now!!!
I have this code that pairs same elements in an array, with the expectation that the array will have an odd length and it should return the only element that couldn't get a pair. So I wrote the code just well, and it works fine for smaller arrays, but with very large big integers of over 1 billion, the time complexity became O(N**2) and then the need to refactor my code to get a much better performance for large arrays and large array elements. Here is my code below;
function solution(A) {
if(!Array.isArray(A)) return 0;
var temp = new Array(A.length);
var position = 0;
for(let i=0; i<A.length; i++){
if(temp.includes(A[i])){
position = temp.indexOf(A[i]);
index = A.indexOf(A[i]);
delete temp[position];
delete A[index];
delete A[i];
}else{
temp[i] = A[i];
}
}
for(let j=0; j<A.length; j++){
if(A[j] !== undefined) return A[j];
else continue;
}
}
To test it, source data can look like [2,3,6,7,3,5,5,6,2] and it will give an output of 7. But when the array is so large up to [1,2,....] with length n = n=999,999, or n = 5000,000,000, the time complexity increases exponentially.
You might use Object to store non-paired elements only.
Please note that you don't need to store all the array elements and their counts in the Object and then filter by count (like #StepUp does).
Everything's been done in a single loop.
The function returns Array of all non-paired elements:
const solution = A => Array.isArray(A) ?
Object.keys(
A.reduce((r, k) => {
r[k] = r[k] || 0;
if (++r[k] > 1) delete r[k];
return r;
}, {})
) : [];
console.log(solution([2, 3, 6, 7, 3, 5, 5, 6, 2]))
We can try to find odd occurrences for one iteration by using great features of object. Object is key - value pair. So access to object key is O(1). So when we meet the same element, then we just increment value:
const hashMap = arr.reduce((a, c)=> {
a[c] = a[c] || 0;
a[c] += 1;
return a;
},{})
const result = Object.keys(hashMap).filter(key => hashMap[key] === 1);
An example:
let arr = [2, 3, 6, 7, 3, 5, 5, 6, 2];
const hashMap = arr.reduce((a, c)=> {
a[c] = a[c] || 0;
a[c] += 1;
return a;
},{})
const result = Object.keys(hashMap).filter(key => hashMap[key] === 1);
console.log(result);
My two 100% JavaScript solutions with optimized time complexity. The first one is using Set:
function solution(A) {
const pairs = new Set();
for (const num of A) {
if (pairs.has(num)) {
pairs.delete(num);
} else {
pairs.add(num);
}
}
const [unpaired] = pairs;
return unpaired;
}
The second one is using bitwise XOR:
function solution(A) {
let unpaired;
for (const num of A) {
unpaired ^= num;
}
return unpaired;
}
If I have an array :
var array_1 = ['c','b','a'];
What is the best way to transform array_1 into
['c','c','b','b','a','a'];
or even
['c','c','c','b','b','b','a','a','a'];
You could use flatMap and fill like this:
function duplicate(arr, times) {
return arr.flatMap(n => Array(times).fill(n))
}
console.log(JSON.stringify(duplicate([1,2,3], 2)))
console.log(JSON.stringify(duplicate([1,2,3], 3)))
Using the 'new' .flat() you can reduce an array to one dimension and to multiple the elements just map each element to an array of these elements.
var arr1 = ['c', 'b', 'a'];
var arr2 = arr1.map(e => ([e, e, e])).flat();
console.log(arr2);
It's a question of style, typically when you iterate over an array and want to perform additive operations you would use the Array.reduce method...
const ar1 = ['a', 'b', 'c']
const ar2 = ar1.reduce((ac, cv) => [...ac, cv, cv, cv], [])
... though map, forEach, etc. would also work.
Loop through the original array, and for each character, create a new array using Array(x).fill(c). Where x is the number of duplicates, and c is the character that you want to duplicate. Then concatenate each new array together.
Using simple forEach and for loop.
You can duplicate your array with a function call with your array and the number of times that you want to duplicate.
const myArray = ['c', 'b', 'a'];
const resultArr1 = duplicate(myArray, 2);
console.log(resultArr1);
const resultArr2 = duplicate(myArray, 3);
console.log(resultArr2);
function duplicate(arr, times) {
let duplicateArray = [];
arr.forEach(item => {
for(i=0; i < times; i++) {
duplicateArray.push(item);
}
});
return duplicateArray;
}
public static char[] duplicateAnarray(char arr[], int duplicacyCount) {
char arrnew[] = new char[((arr.length) * duplicacyCount)];
char temp;
int p = 0;
for (int k = 0; k < arr.length; k++) {
for (int i = p; i < arrnew.length; i++) {
for (int j = 0; j < duplicacyCount; j++) {
arrnew[i + j] = arr[k];
}
p = i + duplicacyCount;
break;
}
}
return arrnew;
}
var array_1 = ['a','b','c']
/* if array has numbers array_1 = [1,2,3] both case are handled and you can change duplicacy number for elements.*/
getResult = (array_1,elemDuplicacyNumber)=>{
return elemDuplicacyNumber <= 0 ? array_1 : array_1.reduce((curr, acc) =>{
let arr1 = Array(elemDuplicacyNumber).fill(curr);
let arr2 = Array(elemDuplicacyNumber).fill(acc);
arr1 = isNaN(curr) ? arr1 : arr1.map(Number);
arr2 = isNaN(acc) ? arr2 : arr2.map(Number);
return (Array.isArray(curr)?curr : arr1 ).concat(arr2)
})
}
console.log( getResult(array_1,5) )
I am trying to find values that commonly appear next to each other in an array.
E.G. given the array:
["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"]
it should return something similar to:
[[["dog","cat"],4],[["cat","pig"],2],[["dog","cat","pig"],2]]
Here is some better data: https://pastebin.com/UG4iswrZ
Help would be greatly appreciated. Here is my current failed attempt at doing something similar:
function findAssociations(words){
var temp = [],tempStore = [],store = [],found = false;
//loop through the words counting occurrances of words together with a window of 5
for(var i = 0;i<words.length-1;i++){
if(i % 5 == 0){
//on every fith element, loop through store attempting to add combinations of words stored in tempStore
for(var j = 0;j<5;j++){
temp = []
//create the current combination
for(var k = 0;k<j;k++){
temp.push(tempStore[k]);
}
//find if element is already stored, if it is, increment the occurrence counter
for(var k = 0;k<store.length;k++){
if(store[k][0]===temp){
found = true;
store[k][1] = store[k][1]+1;
}
}
//if it isn't add it
if(found == false){
store.push([temp,1]);
}
found == false;
}
tempStore = [];
} else {
//add word to tempStore if it i isnt a multiple of 5
tempStore.push(words[i]);
}
}
}
This script is doesn't remove combinations that appear once,it doesn't sort the output by occurrences, nor does it work. It is just an outline of how a possible solution might work (as suggested by benvc).
Here is a generic solution working with multiple group sizes.
You specify a range of group sizes, for example [2,4] for groups of 2 to 4 elements and a minimum number of occurrences.
The function then generates all groups of neighbours of the given sizes, sorts each group and counts the duplicates. The sorting step can be removed is the order in the groups matters.
The duplicates are counted by creating a dictionary whose keys are the group elements sorted and jointed with a special marker. The values in the dictionary are the counts.
It then returns the groups sorted by occurences and then by group size.
const data = ["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
function findSimilarNeighbors(groupSizeRange, minOccurences, data) {
const getNeighbors = (size, arr) => arr.reduce((acc, x) => {
acc.push([]);
for (let i = 0; i < size; ++ i) {
const idx = acc.length - i - 1;
(acc[idx] || []).push(x);
}
return acc;
}, []).filter(x => x.length === size);
const groups = [];
for (let groupSize = groupSizeRange[0]; groupSize <= groupSizeRange[1]; ++groupSize) {
groups.push(...getNeighbors(groupSize, data));
}
const groupName = group => group.sort().join('###'); // use a separator that won't occur in the strings
const groupsInfo = groups.reduce((acc, group) => {
const name = groupName(group);
acc[name] = acc[name] || {};
acc[name] = { group, count: (acc[name].count || 0) + 1 };
return acc;
}, {});
return Object.values(groupsInfo)
.filter(group => group.count >= minOccurences)
.sort((a, b) => {
const countDiff = b.count - a.count;
return countDiff ? countDiff : b.group.length - a.group.length;
})
.map(({ group, count }) => [group, count]);
};
console.log(findSimilarNeighbors([2, 4], 2, data));
console.log(findSimilarNeighbors([4, 4], 2, data));
Here is what I came up with. It only finds pairs, but you could modify it to find sets of 3, 4, etc, based on what you % by
const animals = ['dog','cat','goat','dog','cat','elephant','dog','cat','pig','seal','dog','cat','pig','monkey'];
let pairs = ',';
animals.forEach((animal, i) => {
let separator = ',';
if (i % 2 === 0) {
separator = ';'
}
pairs += animal + separator;
});
const evenPairs = pairs.split(',');
const oddPairs = pairs.split(';');
const allPairs = evenPairs.concat(oddPairs).map(pair => pair.replace(/[;,]/, ' '));
let result = {}
allPairs.forEach(pair => {
if (pair.length) {
if (result[pair] === undefined) {
result[pair] = 1;
} else {
result[pair]++;
}
}
});
results in:
dog: 1
cat elephant: 1
cat goat: 1
cat pig: 2
dog cat: 4
elephant dog: 1
goat dog: 1
monkey : 1
pig monkey: 1
pig seal: 1
seal dog: 1
https://stackblitz.com/edit/typescript-wvuvnr
You need to be clear what you mean by close and how close. Just looking at first neighbours you could try:
const findAssociations = words => {
const associations = {}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i]
const wordRight = words[i+1]
const wordOne = word < wordRight ? word : wordRight;
const wordTwo = word < wordRight ? wordRight : word;
const keys = Object.keys(associations)
const key = `${wordOne}:${wordTwo}`
if (keys.indexOf(key) >= 0) {
associations[key]++
} else {
associations[key] = 1
}
}
const keys = Object.keys(associations)
const values = Object.values(associations)
const zipped = keys.map((key, index) => [key, values[index]])
zipped.sort((a, b) => a[1] < b[1] ? 1 : -1);
return zipped;
}
https://stackblitz.com/edit/js-3ppdit
You can use this function inside another function and add every time an element to ["dog", "cat"]
const arr = ["dog", "cat", "goat", "dog", "cat", "dog", "cat", "elephant", "dog", "cat", "pig", "seal", "dog", "cat", "pig", "monkey"]
const findArrayInArray = (arr1, arr2) => {
let count = 0,
arrString1 = arr1.join(""),
arrString2 = arr2.join("");
while (arrString2.indexOf(arrString1) > -1) {
count += 1;
arrString2 = arrString2.replace(arrString1, '');
}
return count;
}
console.log(`["dog", "cat"] exist ${findArrayInArray(["dog", "cat"], arr)} times`)
Assuming each item in the list is a delimiter of a set, and each set counts once for each item (i.e. ["dog", "cat", "goat"] counts as ["dog", "cat"] and ["dog", "cat", "goat"], and assuming you don't want any single occurrences, then here's one way:
const full_list = ["dog","cat","goat","dog","cat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
// create list of unique items
const distinct = (value, index, self) => {
return self.indexOf(value) ===index;
}
const unique_items = full_list.filter(distinct);
// get all patterns
var pre_report = {};
for (var i in unique_items) {
item = unique_items[i];
var pattern = [item];
var appending = false;
for (var j = full_list.indexOf(item) + 1; j < full_list.length; ++j) {
const related_item = full_list[j];
if (item == related_item) {
pattern = [item]
continue;
}
pattern.push(related_item);
if (pattern in pre_report) {
++pre_report[pattern];
} else {
pre_report[pattern] = 1;
}
}
}
// filter out only single occurring patterns
var report = {};
for (key in pre_report) {
if (pre_report[key] > 1) {
report[key] = pre_report[key];
}
}
console.log(report);
produces:
{ 'dog,cat': 5, 'dog,cat,pig': 2, 'cat,pig': 2 }