Ordering an array in javascript by the most common item - javascript

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);

Related

Javascript - Delete all duplicates from array

I have problem with delete all duplicate in array.
Array = [1,1,2,2,3]
Every solution, what I found, haves result this
Array = [1,2,3]
But I need this
Array = [3]
How can I do this?
You can first iterate over the array once to obtain a Map of the frequencies of each item and then filter to find the elements that only appeared once.
const arr = [1,1,2,2,3];
const freq = arr.reduce((acc,curr)=>(acc.set(curr,(acc.get(curr)||0)+1),acc),new Map);
const res = arr.filter(x => freq.get(x) === 1);
console.log(res);
You could store an object for occurences of each element and get the elements that have the occurence of 1
const arr = [1, 1, 2, 2, 3]
const occurrences = arr.reduce((acc, el) => {
acc[el] = (acc[el] || 0) + 1
return acc
}, {})
const res = Object.entries(occurrences)
.filter(([el, time]) => time === 1)
.map(([el]) => +el)
console.log(res)
Unlike some of the other solutions, this allows you to make a single loop over the array, rather than a reduce followed by a filter and/or map loop. That said, there are trade-offs in readability and other condition checks, and it plays a bit fast and loose with the semantic intention of a reduce, so it might be a wash in terms of benefits.
const myArray = [1,1,2,2,3];
const dupesRemoved = myArray.reduce((acc, cur, idx, src) => {
if (!acc.dupes.has(cur)) {
if (acc.singleInstances.has(cur)) {
acc.singleInstances.delete(cur);
acc.dupes.add(cur);
} else {
acc.singleInstances.add(cur);
}
}
if (idx === src.length - 1) {
return [...acc.singleInstances];
}
return acc;
}, { singleInstances: new Set(), dupes: new Set() });
console.log(dupesRemoved);
Here is a simple and short solution:
let arr = [1,1,2,2,3];
let filtered_arr = arr.filter(v => arr.indexOf(v) === arr.lastIndexOf(v));
console.log(filtered_arr);

Create an arrays inside of another (main) array out of separated values

Problem
I have a string of numerical values separated by commas, and I want to include them in an array, and also each pair of them to be an array nested inside of the main array to be my drawing vertices.
How do I solve this problem?
Input:
var vertices = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
what I want them to be is:
Output:
var V_array = [[24,13],[47,20],[33,9],[68,18],[99,14],[150,33],[33,33],[34,15],[91,10]];
You could Split on every second comma in javascript and map the splitted pairs by converting the values to number.
var vertices = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10",
result = vertices.match(/[^,]+,[^,]+/g).map(s => s.split(',').map(Number));
console.log(result);
You can use the function reduce which operates over the splitted-string and check for the mod of each index.
let str = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
let result = str.split(',').reduce((a, s, i) => {
a.curr.push(Number(s));
if ((i + 1) % 2 === 0) {
a.arr.push(a.curr);
a.curr = [];
}
return a;
}, {arr: [], curr: []}).arr;
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can split string into array and use reduce method. Take a look at the code below
const vertices = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
const numbers = vertices.split(',').map(Number)
const res = numbers
.reduce((acc, number, index, srcArray) => {
if (index % 2) {
return acc
}
return [
...acc,
[ number, srcArray[index + 1] ],
]
}, [])
console.log(res)
My two cents :) [new version]
let
str = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10",
pair = [],
triplet = [];
JSON.parse(`[${str}]`).forEach((e,i)=>{pair.push( (i%2)?[pair.pop(),e]:e)})
console.log ( 'pair:', JSON.stringify(pair) )
// bonus => same idea for triplet :
JSON.parse(`[${str}]`).forEach((e,i)=>{
if ( (i%3)===2 ) triplet.push( [triplet.shift(),triplet.pop(),e] )
else if ( (i%3)===0 ) triplet.unshift(e)
else triplet.push(e)
})
console.log ( 'triplet:', JSON.stringify(triplet) )
You can use exec and JSON.parse
var vertices = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
var array1;
var reg = /[^,]+,[^,]+/g
let op = []
while((array1 = reg.exec(vertices))!== null){
op.push(JSON.parse(`[${array1[0]}]`))
}
console.log(op)
Split on the , and use Array.reduce to group the pair into a new 2-D array:
var vertices = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
const pair = vertices.split(",").reduce((acc, ele, idx, arr) => {
if(idx === 0 || idx%2 === 0) {acc.push([+ele, +arr[idx + 1]]);}
return acc;
}, []);
console.log(pair);
Same can be done using Array.map, if the index is odd skip the element and filter out the undefined elements:
var vertices = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
const pair = vertices.split(",").map((ele, idx, arr) => (idx === 0 || idx%2 === 0) ? [+ele, +arr[idx + 1]] : undefined).filter(e => e);
console.log(pair);
My two cents :)
( thanks to Code Maniac for the idea of using JSON.parse )
let str = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
let result = JSON.parse(`[${str}]`).reduce((acc, cur, i) => {
if (i===1) return [[acc,cur]]
if (i%2) acc.push( [acc.pop(), cur] )
else acc.push( cur )
return acc
});
console.log ( result )
Here is my solution.
var vertices = "24,13,47,20,33,9,68,18,99,14,150,33,33,33,34,15,91,10";
vertices = vertices.split(",");
function convertToMultiArray (arr, length) {
var nArr = [];
while(arr.length > 0) {
nArr.push(arr.splice(0,length));
}
return nArr;
}
const res = convertToMultiArray(vertices, 2);
console.log('res', res);

Modify array of dates

I Javascript I have an array of dates, for example:
["2018-04-25",
"2018-04-25",
"2018-04-26",
"2018-04-27",
"2018-04-28",
"2018-04-28"]
How can I recognise duplicate dates and change them to false so the values become:
["2018-04-25",
false,
"2018-04-26",
"2018-04-27",
"2018-04-28",
false]
This is what it needs to be.
Thanks
With a sorted array, you could check the predecessor and return false if the same, otherwide the value.
var array = ["2018-04-25", "2018-04-25", "2018-04-26", "2018-04-27", "2018-04-28", "2018-04-28"];
array = array.map((v, i, a) => a[i - 1] !== v && v);
console.log(array);
For unsorted arrays and respecting the first same date, as mentioned by Kevin Hoerr in the comments, take indexOf and check against the actual index.
var array = ["2018-04-25", "2018-04-25", "2018-04-26", "2018-04-27", "2018-04-28", "2018-04-28"];
array = array.map((v, i, a) => a.indexOf(v) === i && v);
console.log(array);
If you have a complete ISO date, you could use just the date without time. This approach needs sorted data.
var array = ['2018-04-14T13:00:00+02:00', '2018-04-15T08:45:00+02:00', '2018-04-15T10:00:00+02:00', '2018-04-22T08:45:00+02:00', '2018-04-22T10:00:00+02:00', '2018-04-29T08:45:00+02:00', '2018-04-29T10:00:00+02:00'];
array = array.map((v, i, a) => (a[i - 1] || '').slice(0, 10) !== v.slice(0, 10) && v);
console.log(array);
forEach seems to be easier to read than the map and reduce I see here
var data = ["2018-04-25","2018-04-25","2018-04-26","2018-04-27","2018-04-28","2018-04-28"];
var newArr = [];
data.forEach(function(x) {
newArr.push(newArr.includes(x) ? false : x)
});
console.log(newArr)

Removing duplicate sub-arrays

I have an array as such: [[1,3],[2,5],[1,3],[2,5]] and i would like to remove any duplicate sub-arrays. I tried using this code:
uniqueArray = array.filter(function(item, pos) {
return array.indexOf(item) == pos; });
It still returns true for all the cases.
Which function can i use to get the desired result.
Conver the 2D array into 1D array with stringified elements
Then put then into a Set to automatically filter out the repeating elements
Now convert the Set back to an array and map the array by JSON parsing each element to restore back the arrays.
Readable and no nested loops.
const arr = [[1,3],[2,5],[1,3],[2,5]];
const setArray = new Set(arr.map(x => JSON.stringify(x)))
const uniqArray = [...setArray].map(x => JSON.parse(x))
console.log(uniqArray)
Not the most efficient method, but the sub-arrays can be used as object keys:
a = [[1,3],[2,5],[1,3],[2,5]]
o = a.reduce((r, v) => (r[v] = v, r), {})
console.log(JSON.stringify( Object.values(o) ))
console.log(JSON.stringify( o ))
Update: seems a bit faster with numeric keys :
let a = [[1,3],[2,5],[1,3],[2,5]], t, b, n = _ => performance.now(),
v = Object.values, l = t => console.log(JSON.stringify(b), t)
t = n(); b = v(a.reduce((r, v) => (r[v] = v, r), {})) ; l(n() - t)
t = n(); b = v(a.reduce((r, v) => (r[v[0] + 1 / v[1]] = v, r), {})) ; l(n() - t)
t = n(); b = v(a.reduce((r, v) => (r[v[0] + 1 / v[1]] = v, r), new Map)); l(n() - t)
You can use Array#filter with an object that will store a hash for each iterated tuple:
var arr = [[1,3],[2,5],[1,3],[2,5]];
var result = arr.filter(function(t) {
var key = t.join('-');
return this[key] ? false : (this[key] = true);
}, Object.create(null));
console.log(result);

How do I sort taking greater than and less than into consideration?

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)

Categories

Resources