Related
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!!!
Example sentence: "My passion is to work towards my goal is the whole idea"
I want to sort the words according to the word having highest vowels will be showed first followed by descending order of the vowels present in it and indexing it from left. Please help me with the javaScript code.
Thanks for the help in advance.
Maybe you can try with this code below:
let string = "My passion is to work towards my goal is the whole idea";
let myarr = string.split(' ');
function mostWolves(word) {
let count = 0;
for(let i=0; i < word.length; i++) {
let x = word[i].toLowerCase();
if (x === 'a' || x === 'e' || x === 'i' || x === 'o' || x === 'u'){
count++;
}
}
return count;
}
myarr.sort((a, b) => {
if(mostWolves(a) > mostWolves(b)) {
return -1;
}else if(mostWolves(a) < mostWolves(b)) {
return +1;
} else {
if(a.toLowerCase() > b.toLowerCase()) {
return +1;
}else if(a.toLowerCase() < b.toLowerCase()) {
return -1;
} else {
return 0;
}
}
});
console.log(myarr);
You may split them first, then count the number of vowels then do sorting.
var str = "My passion is to work towards my goal is the whole idea"
var vowels = 'aeiou'
str.split(' ')
.map(word => [word, Array.from(word.toLowerCase()).filter(char => vowels.indexOf(char) > -1 ).length])
.sort((_1, _2) => _1[1] > _2[1]? -1: 1).map(_ => _[0])
["passion", "idea", "towards", "goal", "whole", "is", "to", "work", "is", "the", "My", "my"]
You could split, get counting and original index, sort by counting and index, finally take the joined string.
var string = "My passion is to work towards my goal is the whole idea",
array = string.split(' '),
hash = array.reduce((o, k, index) => {
o[k] = { index, count: (k.match(/[aeiou]/g) || []).length };
return o;
}, {})
result = array
.sort((a, b) => hash[b].count - hash[a].count || hash[b].index - hash[a].index)
.join(' ');
console.log(result);
I want to merge two variable with stings alternately using javascript. What would be an algorithm to accomplish this task?
For example:
var a = "abc"
var b = "def"
result = "adbecf"
I would use Array.from to generate an array from the strings (unicode conscious).
After that, just add a letter from each string until there's no letters left in each. Please note this solution will combine strings of uneven length (aa+bbbb=ababbb)
var a = "abc"
var b = "def"
var d = "foo 𝌆 bar mañana mañana"
function combineStrings(a,b){
var c = "";
a = Array.from(a);
b = Array.from(b);
while(a.length > 0 || b.length > 0){
if(a.length > 0)
c += a.splice(0,1);
if(b.length > 0)
c += b.splice(0,1);
}
return c;
}
var test = combineStrings(a,b);
console.log(test);
var test2 = combineStrings(a,d);
console.log(test2);
The best way to do this is to perform the following algorithm:
Iterate through string 1
For each character, if there is a character in the same position in string 2, replace the original character with both
This can be achieved with the following code:
function merge(s, t) {
return s.split("")
.map(function(v,i) {
return t[i] ? v + t[i] : v
})
.join("")
}
or the more Codegolf type answer:
s=>t=>[...s].map((v,i)=>t[i]?v+t[i]:v).join``
The simple way would be define the longest string and assigned to for loop. Also you have to add if statments for strings of uneven length, because you want to ignore undefined values of shorter string.
function mergeStrings(s1, s2){
var n = s1.length;
if(s1.length < s2.length){
n = s2.length;
}
var string = '';
for(var i = 0; i < n; i++){
if(s1[i]){
string += s1[i];
}
if(s2[i]){
string += s2[i];
}
}
return string;
}
console.log(mergeStrings('ab','lmnap'));
console.log(mergeStrings('abc','def'));
If your strings are the same length, this will work. If not you'll have to append the rest of the longer string after the loop. You can declare i outside of the loop and then use substr() to get the end of the longer string.
const a = "abc"
const b = "def"
var res = "";
for (var i = 0;i < Math.min(a.length, b.length); i++) {
res += a.charAt(i) + b.charAt(i)
}
console.log(res)
Regex or array processing and joining do the job:
let a = 'abc';
let b = 'def';
console.log(a.replace(/./g, (c, i) => c + b[i])); // 'adbecf'
console.log(Array.from(a, (c, i) => c + b[i]).join('')); // 'adbecf'
You can solve this using array spread and reduce. Split each string into an array and merge into one array and then use reduce to generate the merged string.
function mergeStrings(a, b) {
const mergedValues = [
...a.split(''),
...b.split('')
].reduce((values, currentValue) => {
if (!values.includes(currentValue)) {
values.push(currentValue);
}
return values;
}, []);
return Array.from(mergedValues).join('');
}
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)];
}
As the title says, I've got a string and I want to split into segments of n characters.
For example:
var str = 'abcdefghijkl';
after some magic with n=3, it will become
var arr = ['abc','def','ghi','jkl'];
Is there a way to do this?
var str = 'abcdefghijkl';
console.log(str.match(/.{1,3}/g));
Note: Use {1,3} instead of just {3} to include the remainder for string lengths that aren't a multiple of 3, e.g:
console.log("abcd".match(/.{1,3}/g)); // ["abc", "d"]
A couple more subtleties:
If your string may contain newlines (which you want to count as a character rather than splitting the string), then the . won't capture those. Use /[\s\S]{1,3}/ instead. (Thanks #Mike).
If your string is empty, then match() will return null when you may be expecting an empty array. Protect against this by appending || [].
So you may end up with:
var str = 'abcdef \t\r\nghijkl';
var parts = str.match(/[\s\S]{1,3}/g) || [];
console.log(parts);
console.log(''.match(/[\s\S]{1,3}/g) || []);
If you didn't want to use a regular expression...
var chunks = [];
for (var i = 0, charsLength = str.length; i < charsLength; i += 3) {
chunks.push(str.substring(i, i + 3));
}
jsFiddle.
...otherwise the regex solution is pretty good :)
str.match(/.{3}/g); // => ['abc', 'def', 'ghi', 'jkl']
Building on the previous answers to this question; the following function will split a string (str) n-number (size) of characters.
function chunk(str, size) {
return str.match(new RegExp('.{1,' + size + '}', 'g'));
}
Demo
(function() {
function chunk(str, size) {
return str.match(new RegExp('.{1,' + size + '}', 'g'));
}
var str = 'HELLO WORLD';
println('Simple binary representation:');
println(chunk(textToBin(str), 8).join('\n'));
println('\nNow for something crazy:');
println(chunk(textToHex(str, 4), 8).map(function(h) { return '0x' + h }).join(' '));
// Utiliy functions, you can ignore these.
function textToBin(text) { return textToBase(text, 2, 8); }
function textToHex(t, w) { return pad(textToBase(t,16,2), roundUp(t.length, w)*2, '00'); }
function pad(val, len, chr) { return (repeat(chr, len) + val).slice(-len); }
function print(text) { document.getElementById('out').innerHTML += (text || ''); }
function println(text) { print((text || '') + '\n'); }
function repeat(chr, n) { return new Array(n + 1).join(chr); }
function textToBase(text, radix, n) {
return text.split('').reduce(function(result, chr) {
return result + pad(chr.charCodeAt(0).toString(radix), n, '0');
}, '');
}
function roundUp(numToRound, multiple) {
if (multiple === 0) return numToRound;
var remainder = numToRound % multiple;
return remainder === 0 ? numToRound : numToRound + multiple - remainder;
}
}());
#out {
white-space: pre;
font-size: 0.8em;
}
<div id="out"></div>
If you really need to stick to .split and/or .raplace, then use /(?<=^(?:.{3})+)(?!$)/g
For .split:
var arr = str.split( /(?<=^(?:.{3})+)(?!$)/ )
// [ 'abc', 'def', 'ghi', 'jkl' ]
For .replace:
var replaced = str.replace( /(?<=^(?:.{3})+)(?!$)/g, ' || ' )
// 'abc || def || ghi || jkl'
/(?!$)/ is to not stop at end of the string. Without it's:
var arr = str.split( /(?<=^(?:.{3})+)/ )
// [ 'abc', 'def', 'ghi', 'jkl' ] // is fine
var replaced = str.replace( /(?<=^(.{3})+)/g, ' || ')
// 'abc || def || ghi || jkl || ' // not fine
Ignoring group /(?:...)/ is to prevent duplicating entries in the array. Without it's:
var arr = str.split( /(?<=^(.{3})+)(?!$)/ )
// [ 'abc', 'abc', 'def', 'abc', 'ghi', 'abc', 'jkl' ] // not fine
var replaced = str.replace( /(?<=^(.{3})+)(?!$)/g, ' || ' )
// 'abc || def || ghi || jkl' // is fine
My solution (ES6 syntax):
const source = "8d7f66a9273fc766cd66d1d";
const target = [];
for (
const array = Array.from(source);
array.length;
target.push(array.splice(0,2).join(''), 2));
We could even create a function with this:
function splitStringBySegmentLength(source, segmentLength) {
if (!segmentLength || segmentLength < 1) throw Error('Segment length must be defined and greater than/equal to 1');
const target = [];
for (
const array = Array.from(source);
array.length;
target.push(array.splice(0,segmentLength).join('')));
return target;
}
Then you can call the function easily in a reusable manner:
const source = "8d7f66a9273fc766cd66d1d";
const target = splitStringBySegmentLength(source, 2);
Cheers
const chunkStr = (str, n, acc) => {
if (str.length === 0) {
return acc
} else {
acc.push(str.substring(0, n));
return chunkStr(str.substring(n), n, acc);
}
}
const str = 'abcdefghijkl';
const splittedString = chunkStr(str, 3, []);
Clean solution without REGEX
My favorite answer is gouder hicham's. But I revised it a little so that it makes more sense to me.
let myString = "Able was I ere I saw elba";
let splitString = [];
for (let i = 0; i < myString.length; i = i + 3) {
splitString.push(myString.slice(i, i + 3));
}
console.log(splitString);
Here is a functionalized version of the code.
function stringSplitter(myString, chunkSize) {
let splitString = [];
for (let i = 0; i < myString.length; i = i + chunkSize) {
splitString.push(myString.slice(i, i + chunkSize));
}
return splitString;
}
And the function's use:
let myString = "Able was I ere I saw elba";
let mySplitString = stringSplitter(myString, 3);
console.log(mySplitString);
And it's result:
>(9) ['Abl', 'e w', 'as ', 'I e', 're ', 'I s', 'aw ', 'elb', 'a']
try this simple code and it will work like magic !
let letters = "abcabcabcabcabc";
// we defined our variable or the name whatever
let a = -3;
let finalArray = [];
for (let i = 0; i <= letters.length; i += 3) {
finalArray.push(letters.slice(a, i));
a += 3;
}
// we did the shift method cause the first element in the array will be just a string "" so we removed it
finalArray.shift();
// here the final result
console.log(finalArray);
var str = 'abcdefghijkl';
var res = str.match(/.../g)
console.log(res)
here number of dots determines how many text you want in each word.
function chunk(er){
return er.match(/.{1,75}/g).join('\n');
}
Above function is what I use for Base64 chunking. It will create a line break ever 75 characters.
Here we intersperse a string with another string every n characters:
export const intersperseString = (n: number, intersperseWith: string, str: string): string => {
let ret = str.slice(0,n), remaining = str;
while (remaining) {
let v = remaining.slice(0, n);
remaining = remaining.slice(v.length);
ret += intersperseWith + v;
}
return ret;
};
if we use the above like so:
console.log(splitString(3,'|', 'aagaegeage'));
we get:
aag|aag|aeg|eag|e
and here we do the same, but push to an array:
export const sperseString = (n: number, str: string): Array<string> => {
let ret = [], remaining = str;
while (remaining) {
let v = remaining.slice(0, n);
remaining = remaining.slice(v.length);
ret.push(v);
}
return ret;
};
and then run it:
console.log(sperseString(5, 'foobarbaztruck'));
we get:
[ 'fooba', 'rbazt', 'ruck' ]
if someone knows of a way to simplify the above code, lmk, but it should work fine for strings.
Coming a little later to the discussion but here a variation that's a little faster than the substring + array push one.
// substring + array push + end precalc
var chunks = [];
for (var i = 0, e = 3, charsLength = str.length; i < charsLength; i += 3, e += 3) {
chunks.push(str.substring(i, e));
}
Pre-calculating the end value as part of the for loop is faster than doing the inline math inside substring. I've tested it in both Firefox and Chrome and they both show speedup.
You can try it here
Here's a way to do it without regular expressions or explicit loops, although it's stretching the definition of a one liner a bit:
const input = 'abcdefghijlkm';
// Change `3` to the desired split length.
const output = input.split('').reduce((s, c) => {
let l = s.length-1;
(s[l] && s[l].length < 3) ? s[l] += c : s.push(c);
return s;
}, []);
console.log(output); // output: [ 'abc', 'def', 'ghi', 'jlk', 'm' ]
It works by splitting the string into an array of individual characters, then using Array.reduce to iterate over each character. Normally reduce would return a single value, but in this case the single value happens to be an array, and as we pass over each character we append it to the last item in that array. Once the last item in the array reaches the target length, we append a new array item.
Some clean solution without using regular expressions:
/**
* Create array with maximum chunk length = maxPartSize
* It work safe also for shorter strings than part size
**/
function convertStringToArray(str, maxPartSize){
const chunkArr = [];
let leftStr = str;
do {
chunkArr.push(leftStr.substring(0, maxPartSize));
leftStr = leftStr.substring(maxPartSize, leftStr.length);
} while (leftStr.length > 0);
return chunkArr;
};
Usage example - https://jsfiddle.net/maciejsikora/b6xppj4q/.
I also tried to compare my solution to regexp one which was chosen as right answer. Some test can be found on jsfiddle - https://jsfiddle.net/maciejsikora/2envahrk/. Tests are showing that both methods have similar performance, maybe on first look regexp solution is little bit faster, but judge it Yourself.
var b1 = "";
function myFunction(n) {
if(str.length>=3){
var a = str.substring(0,n);
b1 += a+ "\n"
str = str.substring(n,str.length)
myFunction(n)
}
else{
if(str.length>0){
b1 += str
}
console.log(b1)
}
}
myFunction(4)
function str_split(string, length = 1) {
if (0 >= length)
length = 1;
if (length == 1)
return string.split('');
var string_size = string.length;
var result = [];
for (let i = 0; i < string_size / length; i++)
result[i] = string.substr(i * length, length);
return result;
}
str_split(str, 3)
Benchmark: http://jsben.ch/HkjlU (results differ per browser)
Results (Chrome 104)