I'm trying to sort an array containing strings. Each value typically has the following structure:
[registration number][ ][account number][, ][account name]
But in rare cases a value does not have the first 4 reg.numbers + a space.
This is an example of an array:
var accounts = ["1111 12345678, Account1",
"2222 12345678, Account2",
"11345678, Account3",
"12345678, Account4",
"3333 12345678, Account5"];
I can sort accounts by using accounts.sort(), and that works almost fine. BUT I would like to have the values sorted AND have the values without reg.no appear last in the sorted array (still sorted alpabetically).
So sorting the accounts array should result in this list:
1111 12345678, Account1
2222 12345678, Account2
3333 12345678, Account5
11345678, Account3
12345678, Account4
Any good suggestion to this?
You culd check for leading numbers and sort this numbers to top.
const hasLeading = s => /^\S+\s\S+\s\S+$/.test(s);
var accounts = ["1111 12345678, Account1", "2222 12345678, Account2", "11345678, Account3", "12345678, Account4", "3333 12345678, Account5"];
accounts.sort((a, b) => hasLeading(b) - hasLeading(a) || a > b || -(a < b));
console.log(accounts);
You can do it like this:
var accounts = ["1111 12345678, Account1",
"2222 12345678, Account2",
"11345678, Account3",
"12345678, Account4",
"3333 12345678, Account5"];
function compare(a, b) {
var aArr = a.split(' ');
var bArr = b.split(' ');
if (aArr.length > 2 && bArr.length > 2) {
return aArr[2] > bArr[2];
} else if (aArr.length > 2) {
return -1;
} else if (bArr.length > 2) {
return 1;
} else {
return aArr[1] > bArr[1];
}
}
console.log(accounts.sort(compare))
Get all items that contains > 2 spaces. Sort this array.
Get all items that contains === 2 spaces.
let arr = ["3333 12345678, Account5","1111 12345678, Account1",
"2222 12345678, Account2",
"11345678, Account3",
"12345678, Account4",
];
let arr1 = arr.filter((a) => {
return a.split(' ').length > 2;
});
let sortedArr = arr1.sort();
let appendArr = arr.filter((a) => {
return a.split(' ').length === 2;
});
sortedArr = [...sortedArr, ...appendArr];
console.log(sortedArr);
Related
I have an array like:
arr = ["100 abc", "ad", "5 star", "orange"];
I want to sort firstly strings with no numbers at the beginning and then strings with numbers add at the end, omit numbers and sort strings by name alphabetically.
Expected output:
ad, orange, 100 abc, 5 star.
How can I do that in TypeScript/Angular?
Probably something like this:
const startsWithNum = (str) => /^\d/.test(str);
const afterNumPart = (str) => str.match(/^\d+\s(.*)/)[1];
const compareStrings = (a, b) => {
if (startsWithNum(a)) {
if (startsWithNum(b)) {
// Both strings contain numbers, compare using parts after numbers
return afterNumPart (a) < afterNumPart (b) ? -1 : 1;
} else {
// A contains numbers, but B does not, B comes first
return 1;
}
} else if (startsWithNum(b)) {
// A does not contain numbers, but B does, A comes first
return -1;
} else {
// Neither string contains numbers, compare full strings
return a < b ? -1 : 1;
}
};
const arr = ["100 abc", "ad", "5 star", "orange"];
const sortedArr = arr.sort(compareStrings);
// ['ad', 'orange', '100 abc', '5 star']
The question is actually about partitioning rather than sorting. You can achieve this easily with two filter calls:
result = [
...arr.filter(a => !/\d/.test(a)),
...arr.filter(a => /\d/.test(a)),
]
Here you go:
const arr = ["100 abc", "ad", "5 star", "orange"]
arr.map(item => {
return item.split(' ').map((subItem: string) => +subItem ? +subItem : subItem)}
).sort((a,b) => {
if(a.find((item: any) => typeof item === 'number')){
return 1;
}else return -1
}).map(item => item.join(' '))
What I have so far:
var separate = [cats, water, dogs];
separate.sort(function(cats, water, dogs) {return cats-water-dogs});
Problem:
Dogs don't get along with cats, and cats don't get along with dogs.
What they both have in common is that they don't get along with water (baths).
Given an array of 'dogs', 'cats', and 'water', write a function called separate, which returns a new array so that the dogs are separated from the cats by water. Make sure that cats always come first in the array.
You can assume that the array will always at least three elements, and that there'll always be at least one dog, one cat, and one water to work with.
Examples:
separate(['dog','cat','water']) // ['cat','water','dog']
separate(['dog','cat','water','cat']) // ['cat', 'cat', 'water', 'dog'])
separate(['cat','cat','water','dog','water','cat','water','dog'])
// ['cat','cat','cat','water','water','water','dog','dog']
separate(
['cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat','cat',
'dog','water','water','water','water','water',
'water','water','water','water','water','water',
'water','water','water'
])
// ['cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat','cat',
'water','water','water','water','water',
'water','water','water','water','water','water',
'water','water','water', 'dog']
You should use the function sort and move the dog values to the last position as follow:
When a === 'dog' return 1 because dog should be at the end.
When b === 'dog' return -1 because the value of a should be before b.
let arr = ['cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat','cat',
'dog','water','water','water','water','water',
'water','water','water','water','water','water',
'water','water','water'];
let result = arr.sort((a, b) => {
if (a === 'dog') return 1;
if (b === 'dog') return -1;
else return a.localeCompare(b); // cat always at the beginning.
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
HEre we use sort with a custom sort algorithm that understands the difference between dogs, cats and water
// Use this to assign a value 0,1,2 to split them
const weight = (a) => (a === 'dog') ? 0 : (a === 'water') ? 1 : 2;
// our sort will use the weight
const sortItem = (a,b) => weight(a) - weight(b);
const separate = (arr) => arr.sort(sortItem);
console.log(separate(['dog','cat','water'])); // ['cat','water','dog']
console.log(separate(['dog','cat','water','cat'])); // ['cat', 'cat', 'water', 'dog'])
console.log(separate(['cat','cat','water','dog','water','cat','water','dog']));
up
let arr = ['cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat',
'cat','cat','cat','cat','cat','cat','cat',
'dog','water','water','water','water','water',
'water','water','water','water','water','water',
'water','water','water'];
let result = arr.sort((a, b) => {
if (a === 'dog') return 1;
if (b === 'dog') return -1;
else return a.localeCompare(b); // cat always at the beginning.
});
console.log(result);
// Use this to assign a value 0,1,2 to split them
const weight = (a) => (a === 'dog') ? 0 : (a === 'water') ? 1 : 2;
// our sort will use the weight
const sortItem = (a,b) => weight(a) - weight(b);
const separate = (arr) => arr.sort(sortItem);
console.log(separate(['dog','cat','water'])); // ['cat','water','dog']
console.log(separate(['dog','cat','water','cat'])); // ['cat', 'cat', 'water', 'dog'])
console.log(separate(['cat','cat','water','dog','water','cat','water','dog']));
I need to have a sort on two strings take the > and < symbols into consideration. So, for example, the sort might look like
<20
<40
<100
0.1
10
1,000,000.75
>100
>1,000
So basically all the strings with < are first, followed by a normal sort, followed by all numbers with a > symbol. I'd also like the sort to respect the exact order shown (e.g. >100 appears before >1,000 when sorted low to high)
Here is my code that works without the symbols (sorting all rows in a table):
if ($this.hasClass('sort-mixed')) {
sort_func = sort_mixed;
}
$rows.sort(sort_func);
function sort_mixed(a, b) {
var val_a = $(a).children().eq(column_index).text();
var val_b = $(b).children().eq(column_index).text();
val_a = Number(val_a.toString().replace(/,/g, ""));
val_b = Number(val_b.toString().replace(/,/g, ""));
if(val_a > val_b) {
return 1 * sort_direction;
}
if(val_a < val_b) {
return -1 * sort_direction;
}
return 0;
}
Here is not a complete solution but enough to get you started. We'll break the array into multiple parts, sort each part, then put the array back together.
function toNumber(s) {
return +s.replace(/[^0-9.]/g, '')
}
var arr = [
'<20',
'>1,000',
'1,000,000.75',
'<40',
'0.1',
'10',
'<100',
'>100'
];
var lt = arr
.filter(s => s.startsWith('<'))
.map(s => s.slice(1))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '<' + n);
var eq = arr
.filter(s => !s.startsWith('>') && !s.startsWith('<'))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '' + n);
var gt = arr.filter(s => s.startsWith('>'))
.map(s => s.slice(1))
.map(toNumber)
.sort((a, b) => a - b)
.map(n => '>' + n);
console.log([].concat(lt, eq, gt));
Outputs:
["<20", "<40", "<100", "0.1", "10", "1000000.75", ">100", ">1000"]
Sort with a single comparator function:
const order = { regular: 1, reverse: -1 };
let sortOrder = order.regular;
let strings = ['0.1', '1,000,000.75', '10', '<100', '<20', '<40', '>1,000', '>100'];
function compare(a, b) {
function toNum(str) { return +str.replace(/[^0-9.-]/g, ''); }
function range(str) { return { '<': -1, '>': 1 }[str[0]] || 0; }
const rangeA = range(a);
const rangeB = range(b);
const score = rangeA === rangeB ? toNum(a) - toNum(b) : rangeA - rangeB;
return score * sortOrder;
}
strings.sort(compare);
The function range() checks if the string starts with a '<' or '>' and sets a value that is used for sorting if the strings have different ranges. Otherwise, the strings are converted to numbers and simply sorted as numbers.
With the example input data, the resulting strings array is:
["<20", "<40", "<100", "0.1", "10", "1,000,000.75", ">100", ">1,000"]
Fiddle with the code:
https://jsfiddle.net/fxgt4uzm
Edited:
Added sortOrder.
Composition approach
// Sort function maker
// - create a sort fn based on two compare fns
// {f}= primary
// {g}= secondary
const sort = (f, g) => (a, b) => f(a,b) || g(a,b)
// Compare function maker
// - create a compare fn based on a weighting fn, {w}
const cmp_asc_of = (w) => (a, b) => w(a) - w(b)
const cmp_desc_of = (w) => (a, b) => w(b) - w(a)
// Weighting function
// - weight a given string, {string}, returns a number
const weight_op = (string) => ..
const weight_num = (string) => ..
// Then, create sort functions
const asc = sort(cmp_asc_of(weight_op), cmp_asc_of(weight_num))
const desc = sort(cmp_asc_of(weight_op), cmp_desc_of(weight_num))
// Usage
array.sort(asc)
array.sort(desc)
Demo
For your case..
..
function sort_mixed(a, b) {
var val_a = ..
var val_b = ..
return isHighToLow ? desc(val_a, val_b) : asc(val_a, val_b)
Consider this:
[{name:'John'},{age:25},{address:'some street'}]
As you can see none of the keys are a consistent name, so I cannot use.
arr.sort((a,b)=> a.consistentKey < b.consistentKey);
How can I go about sorting something like this, by name, and by value?
so the following sorted by key in alphabetical order should be:
[{address:'some street'},{age:25},{name:'John'}]
If you are thinking of sorting on the basis of key first and then further on values, you can try the following :
var a = [{name:'John'},{age:25},{address:'some street'}];
alert(JSON.stringify(a.sort((a, b) => {
nameA = Object.keys(a)[0];
nameB = Object.keys(b)[0];
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
})));
Here I have considered only one key, but you can always extend it to multiple keys and similarly you can further sort on the basis of values too.
If you extract the key name using Object.keys, then you can get the values you need to perform the comparison:
[{name: 'John'}, {age: 25}, {address:'some street'}].sort((a, b) => {
const keyA = Object.keys(a)[0]
const valA = a[keyA]
const keyB = Object.keys(b)[0]
const valB = a[keyB]
if (keyA > keyB) {
return 1
} else if (keyA < keyB) {
return -1
} else /* equal */ {
if (valA > valB) {
return 1
} else if (valA < valB) {
return -1
} else /* equal */ {
return 0
}
}
})
You can do this with
input.sort((a, b) => {
const keya = Object.keys(a)[0];
const keyb = Object.keys(b)[0];
return keya.localeCompare(keyb) || a[keya].localeCompare(b[keyb]);
});
Using localeCompare is both shorter and more robust in the face of different language locales.
Below is the solution I would use. This solution provides a keys only sort, a values only sort, a keys then values sort, and a values then keys sort.
class FunkySort {
sort (sortType) {
switch (sortType) {
case 'keysOnly':
return data => this._sortByKey(data);
case 'valuesOnly':
return data => this._sortByValue(data);
case 'valuesPrimary':
return data => {
data = this._sortByKey(data);
return this._sortByValue(data);
};
case 'keysPrimary':
return data => {
data = this._sortByValue(data);
return this._sortByKey(data);
};
}
}
_sortByKey (data) {
return data.sort((a, b) => {
var keyA = Object.keys(a)[0];
var keyB = Object.keys(b)[0];
return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;
});
}
_sortByValue (data) {
return data.sort((a, b) => {
// note that in Node >=v7 you could use `Object.values()`, but not in <v7.0
var valueA = a[Object.keys(a)[0]];
var valueB = b[Object.keys(b)[0]];
return valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
});
}
}
const dataArr = [{name:'John'},{age:25},{address:'some street'}];
const fs = new FunkySort();
fs.sort('keysPrimary')(dataArr);
Note that fs.sort is a curried function. The first call sets the type of sort to be done, so fs.sort('keysPrimary') returns a function that takes an array of objects and sorts it first by the values, and then by the keys, resulting in an array of objects sorted by key, and if there are multiple objects with the same key, those are sorted by value.
If you don't need this level of flexibility in the type of sort, then just the _sortByKey helper method should suffice.
I have an array of strings:
array = ["Henry","Brian","Henry","Matilda","Henry","Brian","Matthew"]
And want to sort them into a list which orders the array by the most commonly occuring items first, but then also deletes them afterwards to create a list like so:
sortedArray = ["Henry","Brian","Matilda","Matthew"]
Is there a way of doing this in javascript?
You could use this ES6 function which runs in O(nlogn), not O(n²) as some other solutions:
var array = ["Henry","Brian","Henry","Matilda","Henry","Brian","Matthew"]
var result = [...array.reduce( (acc, s) => acc.set(s, (acc.get(s) || 0)+1), new Map )]
.sort( (a, b) => b[1] - a[1] )
.map( a => a[0] );
console.log(result);
It first creates a map, by keeping a count for each string (runs in linear time).
Then this map is transformed to an array of pairs (with spread [... ]), which is then sorted ( O(nlogn) ) by that count.
Finally, the count is dropped again from that array, using .map()
First create hash table with the array elements and the number of occurences - then sort it.
See demo below:
var array = ["Henry","Brian","Henry","Matilda","Henry","Brian","Matthew"];
var hash = array.reduce(function(p,c) {
p[c] = (p[c] || 0) + 1;
return p;
},{});
// console.log(hash);
var result = Object.keys(hash).sort(function(a,b){
return hash[b] - hash[a];
});
console.log(result);
array.sort((a, b) =>
array.filter(e => e === b).length - array.filter(e=> e === a).length
)
Then , remove duplicated items :
[... new Set(array)]
let array = ["Henry", "Brian", "Henry", "Matilda", "Henry", "Brian", "Matthew"]
array = array.sort((a, b) =>
array.filter(e => e === b).length - array.filter(e => e === a).length
)
console.log(
[...new Set(array)]
)
You could count all items and sort the keys later with the count descending.
var array = ["Henry", "Brian", "Henry", "Matilda", "Henry", "Brian", "Matthew"],
count = Object.create(null),
result;
array.forEach(function (a) {
count[a] = (count[a] || 0) + 1;
});
result = Object.keys(count).sort(function (a, b) { return count[b] - count[a]; });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I guess the hash method should be fastest, yet here is another way which utilizes .findIndex() to run only among the unique items which might turn out to be not so bad as well. It just swaps the unique items array (p) with the previous one if it's length is bigger than the previous one.
var data = ["Henry","Brian","Henry","Matilda","Henry","Brian","Matthew","Matthew","John","Matthew"],
result = data.reduce(function(p,c){
var fi = p.findIndex(f => f[0] === c);
return fi === -1 ? (p.push([c]), p)
: (p[fi].push(c),
fi ? (p[fi].length > p[fi-1].length && ([p[fi-1],p[fi]] = [p[fi],p[fi-1]]),p)
: p);
},[])
.map(e => e[0]);
console.log(result);