Reduce array by counting repeated and remap keys - javascript

I have an array
[{fruit: "Apple", cost: 2},{fruit: "Banana", cost: 3}, {fruit: "Apple", cost: 4}]
I just want an array of how many instances of each fruit in this array. So I used: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#remove_duplicate_items_in_an_array.
except that it gives me
[{"Apple", 2}, {"Banana", 1}]
and I'd like it to be
[{fruit:"Apple", count:2}, {fruit:"Banana", count:1}]
How to remap it to have the desired keys?
const reduced = array.reduce((allTypes, item) => {
if (item in allTypes) {
allTypes[item] ++;
}
else {
allTypes[item] = 1;
}
return allTypes;
}, []);

You can easily achieve this result using reduce and find
You just need to use reduce(that will give you a single result, array in this case) and increment the count if its respective fruit is already present else create a new object with 2 keys fruit and count. Make new object count to 1 by default.
const arr = [
{ fruit: "Apple", cost: 2 },
{ fruit: "Banana", cost: 3 },
{ fruit: "Apple", cost: 4 },
];
const result = arr.reduce((acc, curr) => {
const { fruit } = curr;
const isExist = acc.find((el) => el.fruit === fruit);
if (isExist) ++isExist.count;
else acc.push({ fruit, count: 1 });
return acc;
}, []);
console.log(result);

you can also do
const data =
[ { fruit: 'Apple', cost: 2 }
, { fruit: 'Banana', cost: 3 }
, { fruit: 'Apple', cost: 4 }
]
const result = data.reduce((acc, {fruit}, i, arr) =>
{
if (!acc.some(x=>x.fruit === fruit))
{
let count = arr.filter(x=>x.fruit === fruit).length
acc.push({ fruit, count })
}
return acc;
}, []);
console.log(result);
.as-console-wrapper {max-height: 100%!important;top:0}

Other responses are fine, but I'd like to propose you a one-liner solution, but that returns an object instead of an array of objects.
arr.reduce((acc, { fruit }) => ({ ...acc, [fruit]: (acc[fruit] || 0) + 1 }), {})
// returns {Apple: 2, Banana: 1}
I think it servers your use case, as the result you want is the count value mapped by the fruit name as key, anyway.
If you really need an array of objects, you can convert the result of the previous expression x by doing this:
Object.entries(x).map(([f, c]) => ({ fruit: f, count: c}))

Related

Find the best match in an array

I have got the following array:
let x = [
{ name: "Bad", value: 2 },
{ name: "Critical", value: 1 },
{ name: "High", value: 5 },
{ name: "Medium", value: 5 },
];
The expectation is to look for "Critical" first, if the array has it, return that, else look for "High" then look for "Medium" and so on.
You can store the priorities in an array and then loop over the array and for every priority check if there's an object and whenever an object is found, return it. If the entire priorities array is exhausted then return null (or whatever you want).
const arr = [
{ name: "Bad", value: 2 },
{ name: "Critical", value: 1 },
{ name: "High", value: 5 },
{ name: "Medium", value: 5 },
],
priorities = ["Critical", "High", "Medium", "Bad"],
search = (arr, priorities) => {
for (let p of priorities) {
const obj = arr.find(({ name }) => name === p);
if (obj) {
return obj;
}
}
return null;
};
console.log(search(arr, priorities));
You can also sort the array based on the priority.
Create a Map that stores the priorities.
Sort arr based on the priorities stored in the map.
const arr = [
{ name: "Bad", value: 2 },
{ name: "Critical", value: 1 },
{ name: "High", value: 5 },
{ name: "Medium", value: 5 },
],
priorities = new Map([
["Critical", 4],
["High", 3],
["Medium", 2],
["Bad", 1],
]),
sortedArr = [...arr].sort(
(a, b) => priorities.get(b.name) - priorities.get(a.name)
);
console.log(sortedArr);
let value = x.find(x => x.name == "Critical")
|| x.find(x => x.name == "High")
|| x.find(x => x.name == "Medium") ...
First, define the order in which you want to look for items, then use that to sort the items in the array. Lastly, find based on all the items in your sort order. The first match will be returned, since the items are sorted.
const x = [{name: 'Bad',value: 2}, {name: 'High', value: 5}, {name: 'Medium', value: 5}, {name: 'Critical', value: 1}],
order = ['Critical','High','Medium'],
//sort according to above order
output = x.sort(
({name:na},{name:nb}) =>
order.findIndex(v => v === na) - order.findIndex(v => v === nb)
)
//Now find items matching name in order
.find( ({name}) => order.includes(name) );
console.log( output );
NOTE
Since your data is already sorted in the desired order, I moved the element with name = "Critical" to the end of the array.
If the data will always be sorted according to the priority you want to find the items, then no sorting is needed.

how to create 2 arrays by running once on an array containing objects with the arrays matching the fields?

for example - lets say I have the array -
const array = [{name: "first", val: 1}, {name: "second", val: 2}]
I want to run once on that array and at the end of that run to have two arrays -
const arrayOne = ["first", "second"];
const arrayTwo = [1,2];
to get the first one is easy, but getting both at once?
I remember there was a way to do it but couldn't find it..
I'd appreciate any help!
Any looping logic will help
Array.reduce implementation will be like below
const array = [{ name: "first", val: 1 }, { name: "second", val: 2 }];
const [arrayOne, arrayTwo] = array.reduce((acc, curr) => {
const { name, val } = curr;
acc[0].push(name);
acc[1].push(val);
return acc;
}, [[], []]);
console.log(arrayOne, arrayTwo);
The function extractArrays is general-purpose and can be used in other cases as well.
function extractArrays(arr) {
const result = {};
for (obj of arr) {
for (key in obj) {
result[key] = (result[key] || []).concat([obj[key]]);
}
}
return result;
}
const array = [{name: "first", val: 1}, {name: "second", val: 2}];
const result = extractArrays(array);
const arrayOne = result.name;
const arrayTwo = result.val;
console.log(`arrayOne=${arrayOne}`);
console.log(`arrayTwo=${arrayTwo}`);
You can use Array.reduce to achieve this:
const array = [{name: "first", val: 1}, {name: "second", val: 2}]
const result = array.reduce((res, item) => {
res[0].push(item.name)
res[1].push(item.val)
return res
}, [[], []])
console.log(result)
thanks everyone!
but I think that the easiest, most readable code would be something like -
const itemArray = [], valArray = [];
data.map(({name, val})=> {
if(name) nameArray.push(name);
if(val) valArray.push(val);
})
because basically in 4 lines of code it's finished
thanks again everyone!
const array = [{name: "first", val: 1}, {name: "second", val: 2}]
const keys = [];
const values = [];
array.forEach(item=>{
keys.push(item.name);
values.push(item.val);
})
console.log(keys, values)
Use the Array.map function:
const array = [ { name: 'first', val: 1 }, { name: 'second', val: 2 } ]
let names = array.map(item => item.name)
let vals = array.map(item => item.val)
console.log(names)
console.log(vals)
The map function calls a callback function you provide on each element and constructs a new array from the results of that function.
If you are not familiar with arrow functions like:
item => item.name
... it is a short form for:
function (item) {
return item.name
}
You could even do it in one line:
let [ names, vals ] = [ array.map(item => item.name), array.map(item => item.val) ]

Transforming Javascript Array

I'm not sure what would be the best way to approach transforming this JS Array.
I have an array with a structure like (could contain more than 1 element):
let arr1 = [{amount: '10', quantity: 2, id: '123'}, {....}]
but I need to take that and transform that into an array structured like
let arr2 = [{value: '10'}, {value: '10'}]
Essentially adding new objects into the array based on the quantity in array 1.
I was thinking basic for loop but it seems to be getting a bit messy. Is there a simple way in JS to do this? Either with some sort of built in function etc?
You can easily get the result using flatMap.
First, you can create a temp array with the number of elements as quantity then map over the temp array to get the object with property amount in it.
let arr1 = [
{ amount: "10", quantity: 2, id: "123" },
{ amount: "30", quantity: 5, id: "123" },
];
const result = arr1.flatMap((obj) => {
const { amount, quantity } = obj;
return Array(quantity)
.fill(0)
.map((x) => ({ amount }));
});
console.log(result);
You can also make the above snippet succinct
const result = arr1.flatMap(({ quantity, amount }) => Array(quantity).fill(0).map((x) => ({ amount })));
let arr1 = [
{ amount: "10", quantity: 2, id: "123" },
{ amount: "30", quantity: 5, id: "123" },
];
const result = arr1.flatMap(({ quantity, amount }) =>
Array(quantity)
.fill(0)
.map((x) => ({ amount }))
);
console.log(result);
I was thinking basic for loop but it seems to be getting a bit messy
You just need a simple for and a while loop:
const input = [{amount: '10', quantity: 2, id: '123'}],
output = []
for (let { amount, quantity } of input)
while (quantity--)
output.push({ value: amount })
console.log(output)
You could create o.quantity number of objects using Array.from(). Use flatMap to get a flat array for each item in the input array
const output = input.flatMap(o =>
Array.from({ length: o.quantity }, _ => ({ value: o.amount }) )
)
You could also do it using reduce, with a manual for loop to append the correct number of entries.
let arr1 = [{amount: '10', quantity: 2, id: '123'}];
const result = arr1.reduce((accum, v) => {
for (let i = 0; i < v.quantity; i++) {
accum.push({value: v.amount});
}
return accum;
} , []);
This might have a slight performance improvement over flatMap, as it does not create any temporaries.

How to transform multidimensional array into chart data with es6 array methods

I need to convert one array to in specific data format to display the chart.
chrat.js library require data in this format
dataset = [ { label: 'one', data: []},
{label: 'two', data: []}
];
and I receive the response data in another format in random order so need to change appropriately with the respective label.
here is my code and trial.
const dataset = [
{
detail: {
team: [
{ name: 'alpha', game: 1 },
{ name: 'beta', game: 1 },
{ name: 'gamma', game: 1 },
{ name: 'delta', game: 1 },
{ name: 'echo', game: 1 }
]
}
},
{
detail: {
team: [
{ name: 'alpha', game: 2 },
{ name: 'beta', game: 2 },
{ name: 'echo', game: 2 },
{ name: 'gamma', game: 2 },
{ name: 'delta', game: 2 }
]
}
},
{
detail: {
team: [
{ name: 'echo', game: 1 },
{ name: 'delta', game: 0 },
{ name: 'beta', game: 0 },
{ name: 'gamma', game: 0 },
{ name: 'alpha', game: 0 }
]
}
},
{
detail: {
team: [
{ name: 'delta', game: 0 },
{ name: 'echo', game: 0 },
{ name: 'beta', game: 0 },
{ name: 'gamma', game: 1 },
{ name: 'alpha', game: 0 }
]
}
},
{
detail: {
team: [
{ name: 'delta', game: 0 },
{ name: 'echo', game: 0 },
{ name: 'alpha', game: 2 },
{ name: 'gamma', game: 3 },
{ name: 'beta', game: 2 }
]
}
},
{
detail: {
team: [
{ name: 'delta', game: 0 },
{ name: 'echo', game: 1 },
{ name: 'beta', game: 0 },
{ name: 'gamma', game: 2 },
{ name: 'alpha', game: 0 }
]
}
}
];
const teams = dataset.map(ds => ds.detail.team);
let z = teams.map(element => {
return element.map(e => {
let p = {};
let n = e.name;
let c = e.game;
p[n] = c;
return p;
});
});
console.log('z', z);
let nt = [];
z.reduce((c, n, i, a) => {
let z1 = n.map((i) => {
console.log(i);
let entries = Object.entries(i);
return entries.map((e) => {
return { label: e[0], data: e[1] };
});
});
return z1;
}, [])
desired output:
[
{
label: 'alpha',
data: [1, 2, 0, 0, 2, 0]
},
{
label: 'beta',
data: [1, 2, 0, 0, 2, 0]
},
{
label: 'gamma',
data: [1, 2, 0, 1, 3, 2]
},
{
label: 'delta',
data: [ 1, 2, 0, 0, 0, 0]
},
{
label: 'echo',
data: [1, 2, 1, 0, 0, 1]
}
]
I lost somewhere in the array.reduce method to achieve the output.
I am preferably looking for a es6 solution
any help is appreciated.
So I'm going to leave your dataset the same but lets start from the ground up and create some code to step through your data set and get to the desired output.
First we need to de-nest the data:
dataset.map(d => d.detail.team)
Now that we have teams lets reduce them all to a single array
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team))
Okay good now we have one big set of names and games. We can now make this pretty easily into a hash
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team))
.reduce((acc, team) =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
return acc
}, {})
Now we have a hash of names to games. Calling Object.entries on this hash will give us pairs of lables
Object.entries(
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team))
.reduce((acc, team) =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
return acc
}, {})
)
Now we can map over these pairs to construct the final object
Object.entries(
dataset
.map(object => object.detail.team)
.reduce((acc, team) => acc.concat(team), [])
.reduce((acc, team) =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
return acc
}, {})
)
.map(([team, games]) => ({ team, games }))
The real trick now is how many of these steps can be combined?
Well most of them! We can reduce this to looping over each object, referencing manually since we know structure, and then looping over each individual team array and finally constructing our hash.
Object.entries(
dataset
.reduce((acc, object) =>{
object.detail.team.forEach(team =>{
acc[team.name] = acc[team.name] || []
acc[team.name].push(team.game)
})
return acc
}, {})
)
.map(([team, games]) => ({ team, games }))
Extra Notes
Arrow Functions
We used arrow functions in this example to adhere to the request of using ES6 as much as possible. More information on arrow functions can be found on the MDN. Basically though it's another way to declare a function
function test(value){ return console.log(value) }
// same as
let test = value => console.log(value)
function add(a, b){ return a + b)
// same as
let add = (a,b) => a + b
Note the Array.prototype.forEach()
Now you'll notice we used an Array.prototype.forEach() in the combined example to manipulate the accumulator. That sentence should say all we need to there but for clarification for those who might not know, forEach is to be used when you want no return value and only want side effects. In this situation it's faster than attempting to actually return something since we don't want the overhead of discarding a bunch of arrays we've made when the end goal is to only change the way the accumulator looks.
That funky array being passed to a function
Ah yes, destructuring. Again more information can be found on the MDN. Basically it lets us pull values out of Objects or Arrays we know the structure of in advance. Note: Example courtesy of MDN article
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20
// Stage 3 proposal
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}
You can use Array.reduce(), to create a map and than use that map to get the desired output.
const dataset = [{detail:{team:[{name:'alpha',game:1},{name:'beta',game:1},{name:'gamma',game:1},{name:'delta',game:1},{name:'echo',game:1}]}},{detail:{team:[{name:'alpha',game:2},{name:'beta',game:2},{name:'echo',game:2},{name:'gamma',game:2},{name:'delta',game:2}]}},{detail:{team:[{name:'echo',game:1},{name:'delta',game:0},{name:'beta',game:0},{name:'gamma',game:0},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'beta',game:0},{name:'gamma',game:1},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'alpha',game:2},{name:'gamma',game:3},{name:'beta',game:2}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:1},{name:'beta',game:0},{name:'gamma',game:2},{name:'alpha',game:0}]}}];
var map = dataset.reduce((a,curr)=>{
curr.detail.team.forEach((e)=> (a[e.name]= (a[e.name] || [])).push(e.game));
return a;
}, {});
var result =[];
Object.keys(map).forEach((key)=>{
result.push({
"label" : key,
"data" : map[key]
});
});
console.log(result);
You can use reduce to make a flat array and then loop over to get the wanted format
const dataset = [{detail:{team:[{name:'alpha',game:1},{name:'beta',game:1},{name:'gamma',game:1},{name:'delta',game:1},{name:'echo',game:1}]}},{detail:{team:[{name:'alpha',game:2},{name:'beta',game:2},{name:'echo',game:2},{name:'gamma',game:2},{name:'delta',game:2}]}},{detail:{team:[{name:'echo',game:1},{name:'delta',game:0},{name:'beta',game:0},{name:'gamma',game:0},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'beta',game:0},{name:'gamma',game:1},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'alpha',game:2},{name:'gamma',game:3},{name:'beta',game:2}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:1},{name:'beta',game:0},{name:'gamma',game:2},{name:'alpha',game:0}]}}];
const flat = dataset.reduce( (a,b) => a.concat(b.detail.team), []);
let result = [];
for (let element of flat) {
let match = null;
for (let e of result) {
if (e.label === element.name) {
match = e;
}
}
if (match) {
match.data.push(element.game)
}
else {
result.push({
label : element.name,
data : [element.game]
});
}
}
console.log(result);
Another way: loop through the data set as it is, storing the results in a map dictionary-like object as well as in the array of results to be returned.
const dataset = [{detail:{team:[{name:'alpha',game:1},{name:'beta',game:1},{name:'gamma',game:1},{name:'delta',game:1},{name:'echo',game:1}]}},{detail:{team:[{name:'alpha',game:2},{name:'beta',game:2},{name:'echo',game:2},{name:'gamma',game:2},{name:'delta',game:2}]}},{detail:{team:[{name:'echo',game:1},{name:'delta',game:0},{name:'beta',game:0},{name:'gamma',game:0},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'beta',game:0},{name:'gamma',game:1},{name:'alpha',game:0}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:0},{name:'alpha',game:2},{name:'gamma',game:3},{name:'beta',game:2}]}},{detail:{team:[{name:'delta',game:0},{name:'echo',game:1},{name:'beta',game:0},{name:'gamma',game:2},{name:'alpha',game:0}]}}];
var result = [],
map = {};
dataset.forEach(a => {
a.detail.team.forEach(b => {
if (!(b.name in map)) {
map[b.name] = [];
result.push({
'label': b.name,
'data': map[b.name]
})
}
map[b.name].push(b.game);
});
});
console.log(result);
There's not much need to reduce or map any arrays here.

Javascript Alphabetically sort and move all doubles to the end of array

I am trying to sort an array of objects by a property. I run:
array.sort(function(a, b){
var textA = a.name.toUpperCase();
var textB = b.name.toUpperCase();
return (textA < textB) ? -1 : (textA > textB) ? 1: 0
});
To alphabetically sort the array objects first and then I run an array.sort with a custom compare function as below:
array.sort(function(a, b){
if(a.name === b.name){
return -1;
}
return 1;
});
It seems to work with anything object that does not have a duplicate, however, as soon as there are doubles it pushes them all to the end of the array instead of just the extras.
Example:
[
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'}
{name: 'Joe'},
{name: 'Joe'}
]
Expected Output:
Amy
Clark
Dan
Dave
Joe
Amy
Clark
Joe
Actual Result:
Dan
Dave
Amy
Amy
Clark
Clark
Joe
Joe
Sort Code To try and get Expected Result
array.sort(function(a,b){
if(a.name === b.name){return -1}
return 1;
});
I have a feeling the array.sort with a compare function can handle this however I keep playing with return values of 0, -1, 1 and cannot seem to get it to work fully as I would like.
Update
Expected Result Criteria:
If an object has the same name the duplicate should go to the end of the array. For example if there are two 'Amy' one stays at the begining of the array and the duplicate goes to the end. So that all first occurrences of the names wil be at the begining of the array and all the doubles, triples etc will will be reordered each time at the end of the array. So that it could potentially arrange alhpabetical multiple items.
Example:
[
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'},
]
Expected result:
Amy
Clark
Dan
Dave
Joe
Amy - Duplicate
Clark - Duplicate
Joe - Duplicate
Clark - Had a third
Joe - Had a third
As you can see it orders the first occurrence of all names alphabetically. Then orders the second occurrence alphabetically, and then the third. Until all duplicates are resolved.
After talking in comments it has come to my understanding that it cannot be done in an array.sort function alone. Sort alone with a compare function seems to be great for single or grouping doubles but not for putting doubles at the end of the array.
Your comparator function is incorrect. The function must:
Return a negative number when the first argument should sort before the second;
Return a positive number when the first argument should sort after the second;
Return zero when the two items have equivalent sort keys.
Because yours is not consistent, the sort process gets confused. For your case, the simplest thing to use is the .localeCompare() function, which returns exactly the sort of result you need:
array.sort(function(a, b) { return a.name.localeCompare(b.name); });
From your "expected output", your ordering criteria are unclear. In any case, the sort comparator, whatever it does, has to be consistent: when two items are passed to it in either order, the function should report the same ordering.
edit if the original ordering has some semantic meaning, and the "doubles" (I'd call them "duplicates") should sort further down in the array, you can add another property to each object that captures that original status:
var keyMap = {};
array.forEach(function(item) {
if (item.name in keyMap)
item.position = ++keyMap[item.name];
else
keyMap[item.name] = item.position = 1;
});
Now you can sort:
array.sort(function(a, b) {
var c = a.position - b.position;
if (c) return c;
return a.name.localeCompare(b.name);
});
If the "position" values are the same, the items will be ordered by name. Items that were duplicates in the original array will be sorted after items that weren't (and triplicates will be sorted after those, etc).
You could use sorting with map by using a temporary object with a hash table for the same group array. Take from it the length of the used array as group for sorting.
The sorting happens with the group and index.
The result is mapped with index of the sorted temporary array.
Tge first part generates an array with an index of the original array and their group which is taken from pushing a value into the same group. Actually we need oly the array length after pushing of the group. If more items are in the same group, the items will be sorted later.
[
{
index: 0, // Amy
group: 1
},
{
index: 1, // Amy
group: 2
},
{
index: 2, // Dan
group: 1
},
{
index: 3, // Joe
group: 1
},
{
index: 4, // Joe
group: 2
}
]
The above given array is then sorted by group and index, both ascending.
At the last part, a new array is mapped with the index value of the sorted array.
var array = [{ name: 'Amy' }, { name: 'Amy' }, { name: 'Dan' }, { name: 'Joe' }, { name: 'Joe' }],
groups = Object.create(null),
result = array
// this part is only necessary if the names should be in ascending order
// for keeping the given order, remove the part until next comment
.sort(function (a, b) {
return a.name.localeCompare(b.name);
})
// remove until here, if necessary
.map(function (a, i) {
return { index: i, group: (groups[a.name] = groups[a.name] || []).push(0) };
})
.sort(function (a, b) {
return a.group - b.group || a.index - b.index;
})
.map(function (o) {
return array[o.index];
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Example for unsorted data.
var array = [{ name: 'Joe', i: 0 }, { name: 'Dan', i: 1 }, { name: 'Amy', i: 2 }, { name: 'Joe', i: 3 }, { name: 'Amy', i: 4 }],
groups = Object.create(null),
result = array
.map(function (a, i) {
return {
index: i,
group: (groups[a.name] = groups[a.name] || []).push(0),
value: a.name
};
})
.sort(function (a, b) {
return a.group - b.group || a.value.localeCompare(b.value);
})
.map(function (o) {
return array[o.index];
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You may join duplicates first and count their occurences:
const array = [
{name: 'Amy'},
{name: 'Amy'},
{name: 'Dan'},
{name: 'Joe'},
{name: 'Joe'}
];
const counted = [], byName = {};
for(var {name} of array){
if(byName[name]){
byName[name].count++;
}else{
counted.push(byName[name] = {name, count:1});
}
}
Now that the names are unique we can sort them alphabetically:
counted.sort((a, b) => a.name.localeCompare(b.name));
Finally, we need to spread the names again:
const result = [];
while(counted.length){
for(var i = 0; i < counted.length; i++){
const name = counted[i];
result.push(name.name);
name.count--;
if(!name.count){
counted.splice(i, 1);
i--;
}
}
}
function compareSimple(a, b) {
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
return 0;
}
function compareAlphabetic(a, b) {
return compareSimple(a.name.toUpperCase(), b.name.toUpperCase());
}
let input = [
{ name: 'Amy' },
{ name: 'Amy' },
{ name: 'Clark' },
{ name: 'Clark' },
{ name: 'Dan' },
{ name: 'Clark' },
{ name: 'Dave' },
{ name: 'Joe' },
{ name: 'Joe' },
];
let output = input
.sort(compareAlphabetic)
.reduce(function(acc, curr) {
let rank = 0
let prev = acc.length > 0 ? acc[acc.length-1] : null
if (prev && compareAlphabetic(prev.value, curr) === 0) {
rank = prev.rank + 1
}
acc.push({ value: curr, rank: rank });
return acc
}, [])
// now we have an array like this
// [
// { value: Amy, rank: 0},
// { value: Amy, rank: 1},
// { value: Clark, rank: 0},
// ...]
// Now let's sort it by rank AND name
.sort(function(a, b) {
let result = compareSimple(a.rank, b.rank);
if (result !== 0) {
return result;
}
return compareAlphabetic(a.value, b.value);
})
// we have to unpack it all back:
.map(function(obj) {
return obj.value;
});
console.log(output);
// [ { name: 'Amy' },
// { name: 'Clark' },
// { name: 'Dan' },
// { name: 'Dave' },
// { name: 'Joe' },
// { name: 'Amy' },
// { name: 'Clark' },
// { name: 'Joe' },
// { name: 'Clark' } ]
A little late to the party but this should definitely do it:
var arr = [
{name: 'Amy'},
{name: 'Amy'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Clark'},
{name: 'Dan'},
{name: 'Dave'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'},
{name: 'Joe'}
];
const o = arr.reduce(
(acc,item)=>{
(acc[item.name]===undefined)
? acc[item.name]=1
: acc[item.name]+=1
return acc;
}
,{}
);
const highest = Object.keys(o).reduce(
(acc,item)=>
(o[item]>acc)
? o[item]
: acc
,1
);
const sort = (all,level=1,results=[]) => {
const dub = [""," - Duplicate"," - Had a third"," - Had a fourth"];
if(level>highest){
return results;
}
results = results.concat(
all.filter(
item=>
//if you don't want Clark and Joe to show up 3 times:
// level===1
// ? level<=o[item]
// : level===o[item]
level<=o[item]
)
.filter((item,index,all)=>all.indexOf(item)===index)
.sort()
.map(x=>
x+
(dub[level-1]===undefined
? " - more than four"
: dub[level-1]
)
)
);
return sort(all,level+1,results)
}
console.log(
sort(
arr.map(x=>x.name)
)
);

Categories