Extract duplicate values with number of occurrences for JS Array - javascript

For example we have an array
const nums = [1,1,8,12,2,3,3,3,7];
If I want to map number of occurrences of each array member I could use something like
function extractDupes(arr) {
return arr.reduce(function (acc, item) {
if (item in acc) {
acc[item]++
}
else {
acc[item] = 1
}
return acc
}, {})
}
This would return object like
{ '1': 2, '2': 1, '3': 3, '7': 1, '8': 1, '12': 1 }
Is there an optimal way to filter out numbers which are showing up more than once just with using reduce (in a single pass) and have just
{ '1': 2, '3': 3 }

const nums = [1,1,8,12,2,3,3,3,7];
const dups = {};
nums.forEach((v, i, s) => {
if (s.indexOf(v) != i)
if (dups[v])
dups[v]++;
else
dups[v] = 2;
});
console.log(dups);
If you also want the array of unique values at the end:
const nums = [1,1,8,12,2,3,3,3,7];
const dups = {};
const uniques = nums.filter((v, i, s) => {
if (s.indexOf(v) != i)
if (dups[v])
dups[v]++;
else
dups[v] = 2;
else
return true;
});
console.log(dups);
console.log(uniques);

have a staging variable for values equaling 1 and promote them to the main result when they're hit the second time?
const nums = [1,1,8,12,2,3,3,3,7];
function extractDupes(arr) {
const staging = {}
return arr.reduce(function (acc, item) {
if (item in acc) {
acc[item]++
} else if(item in staging) {
acc[item] = 2
delete staging[item]
} else {
staging[item] = 1
}
return acc
}, {})
}
document.getElementById('hi').innerHTML = JSON.stringify(extractDupes(nums))
<div id="hi"></div>

You could take a nested property for the final object.
function extractDupes(array) {
return array
.reduce(function(acc, item) {
if (acc[item]) acc.dupes[item] = (acc.dupes[item] || 1) + 1;
else acc[item] = true;
return acc;
}, { dupes: {} })
.dupes;
}
const nums = [1, 1, 8, 12, 2, 3, 3, 3, 7];
console.log(extractDupes(nums))

You could use Object.entries to transform the object to key-value pairs then filter the pair that have value greater than 1, and then transform the pairs back to object by Object.fromEntries
const nums = [1, 1, 8, 12, 2, 3, 3, 3, 7]
function extractDupes(arr) {
return Object.fromEntries(
Object.entries(
arr.reduce(function (acc, item) {
if (item in acc) {
acc[item]++
} else {
acc[item] = 1
}
return acc
}, {})
).filter(([key, value]) => value > 1)
)
}
console.log(extractDupes(nums))

let arrAssist = [];
array.sort();
arrAssist.push(inventory[0]);
for(var i = 1; i < array.length; i++){
if(array[i] != array[i - 1]){
arrAssist.push(array[i]);
}
}
in this example, arrAssist contains the array with no duplicates

It has been answered and accepted but anyhow here is how i would do.
const nums = [1, 1, 8, 12, 2, 3, 3, 3, 7];
const map = nums.reduce((acc, num) => {
acc.frequency[num] = (acc.frequency[num] || 0) + 1;
if (acc.frequency[num] > 1) {
acc.replicates[num] = acc.frequency[num];
}
return acc;
}, { frequency: {}, replicates: {} });
console.log(map);

Related

How to get the three key/value pairs with the lowest value from an object?

I have an object as below:
var countryobj = {
"Canada": 10,
"Peru": 1,
"Argentina": 5,
"Colombia": 2,
"Mexico": 8
};
I want to get the first 3 smallest key value pair so that my output would be:
Peru: 1
Colombia: 2
Argentina: 5
Just get the Object.entries and sort them based on their value in ascending order, then print the first three items formatted how you like.
var countryobj = {
"Canada": 10,
"Peru": 1,
"Argentina": 5,
"Colombia": 2,
"Mexico": 8
};
const entries = Object.entries(countryobj).sort(([, a], [, b]) => a - b);
for (let i = 0; i < 3; i++) {
console.log(entries[i][0] + ": " + entries[i][1]);
}
function getRange(obj, count) {
return Object.entries(obj)
.sort(([, prev], [, next]) => a - b)
.slice(0, count)
.reduce(
(result, [key]) => ({
...result,
[key]: obj[key],
}),
{},
);
}

Javascript - Find most frequent number in array, even if there are two

If I have an array of numbers and I want to get the one that occurs the most frequent, yet there are two possible answers, Im having trouble sorting that part out. For example, below should return 1 and 7 but I only get 7. Any help is appreciated.
let arr = [1, 1, 2, 3, 4, 5, 6, 7, 7];
function findMode(numbers) {
let counted = numbers.reduce((acc, curr) => {
if (curr in acc) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
let mode = Object.keys(counted).reduce((a, b) => counted[a] > counted[b] ? a : b);
return mode;
}
console.log(findMode(arr));
You could group equal items in sub-arrays, then sort by sub-array length and retrieve the first values with the same array length like this:
const arr = [1, 1, 2, 3, 4, 5, 6, 7, 7],
output = arr
.sort((a, b) => a - b)
.reduce(
(acc, cur, i, { [i - 1]: last }) =>
(cur === last ? acc[acc.length - 1].push(cur) : acc.push([cur])) && acc,
[]
)
.sort((a, b) => b.length - a.length)
.reduce(
(a, b, _, { 0: first }) => (first.length === b.length ? [...a, b[0]] : a),
[]
);
console.log(output);
You can use an array as the accumulator.
let arr = [1, 1, 2, 3, 4, 5, 6, 7, 7];
function findMode(numbers) {
let counted = numbers.reduce((acc, curr) => {
if (curr in acc) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
let mode = Object.keys(counted).reduce((acc, curr) => {
if(!acc.length || counted[curr] > counted[acc[0]]) return [curr];
if(counted[curr] === counted[acc[0]]) acc.push(curr);
return acc;
}, []);
return mode;
}
console.log(findMode(arr));
Alternatively, you can find the highest frequency and then use filter to find numbers with that frequency.
let arr = [1, 1, 2, 3, 4, 5, 6, 7, 7];
function findMode(numbers) {
let counted = numbers.reduce((acc, curr) => {
if (curr in acc) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
let mode = Math.max(...Object.values(counted));
return Object.keys(counted).filter(x => counted[x] === mode);
}
console.log(findMode(arr));
You can keep track of the max number of occurrences in the first loop, and then use Array#filter to get the keys with such value:
function findMode(numbers) {
let max = 0;
const counted = numbers.reduce((acc, curr) => {
if (curr in acc) acc[curr]++;
else acc[curr] = 1;
if(acc[curr] > max) max = acc[curr];
return acc;
}, {});
const mode = Object.keys(counted)
.filter(key => counted[key] === max)
.map(Number);
return mode;
}
console.log( findMode([1, 1, 2, 3, 4, 5, 6, 7, 7]) );
As the whole action is happening within a function scope we can also do it with two .forEach() loops: in the first one we collect the counts and in the second one we then assemble a results array with the "winners".
By using a map for collecting the counts we avoid the type conversion to string that would have occurred had we used a plain object.
let arr = [1, 1, 2, "1", 3, 4, "1", 5, 6, 7, 7];
function findMode(nums) {
let cn=new Map(),mx=0,res;
nums.forEach(n=>cn.set(n,(cn.get(n)||0)+1));
[...cn.entries()].forEach(([v,c])=>{
if(c>mx) {res=[v];mx=c}
else if (c===mx) res.push(v) });
return res;
}
console.log(findMode(arr));

How to filter an array and return new array of objects with indexed values?

Given the array const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9];
How can I filter and return a new array of indexed key/value pair objects for example:
const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// My fail attempt using filter()
let obj = vals.filter((n, i) => {
return new Object({ i: n % 2 });
});
return obj;
// expected result [{1:2}, {3:4}, {5:6}, {7:8}]
I need to keep the index values as I will filter 2 different arrays with different criteria and associated them later.
Update
Second attempt using map() as suggested in the comments
let obj = vals.map((n, i) => {
if (n % 2) {
return { [i]: n };
}
});
Gives me the following:
[{0:1}, undefined, {2:3}, undefined, {4:5}, undefined, {6:7}, undefined, {8:9}]
To get a list of { key: value } objects where key is the index, and the values are only even without the odd values, you can do this:
const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = vals.map((v, i) => [i, v])
.filter(([_, v]) => v % 2 == 0)
.map(([i, v]) => ({ [i]: v }));
console.log(result);
With the first map, you make a list of [[0, 1], ...] pairs to save the index for later.
Then you filter your index-value pairs so only even values remain.
Then you pack those pairs into an object in another map.
This can be done more efficiently with a single iteration using reduce:
const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = vals.reduce((a, v, i) => {
if (v % 2 == 0) {
a.push({ [i]: v });
}
return a;
}, []);
console.log(result);
Youn can try simple for loop or the reduce function
let arr = [];
for(let i = 0; i<vals.length-1;i += 2)
{
let obj={};
obj[vals[i]]=vals[i+1];
arr.push(obj);
};

Getting values of nested objects and arrays - with plunker

I have two arrays, with nested objects, downloaded as part of calls to API endpoints, one (preview) has just numbers.
Example:
[{
obj1:[1, 2],
obj2:[3, 4]
}]
I had to make a second call to another endpoint, to get a list of IDs with strings
Example:
[{
obj1:[{
id:1,
name:'string_name1'
}, {
id:2,
name:'string_name2'
}]
}, {
obj2:[{
id:3,
name:'string_name3'
}, {
id:4,
name:'string_name4'
}]
}];
I need to match the IDs to the first array of objects numbers, so I have strings/text values to display on my web page
I have 2 functions
The first one, pulls the numbers from the preview array and pushes them to my own editable array that I will use to display on the page
This is the array before function runs
objName = [['obj1'], ['obj2']];
This is the first function, matches the names in preview to the names in my array and pushes values
setNumbers(){
for(let i = 0; i < this.objName.length; i++){
for(var name in this.preview[0]) {
if (name == this.objName[i][0]){
for(var val in this.preview[0][name]) {
this.objName[i].push(this.preview[0][name][val])
}
}
}
}
this.setStrings()
}
The second matches the IDs in fields to the numbers in objName and replaces with the string value
public setStrings(){
let feildId, feildName;
for(let i = 0; i < this.fields.length; i++){
var obj = this.fields[i]
for(var name in obj) {
if(this.objName[i][0] == name){
for(let j = 0; j < obj[name].length; j++){
feildId = obj[name][j].id
feildName = obj[name][j].name;
for(let x = 0; x < this.objName[i].length; x++){
if (this.objName[i][x] == feildId){
var index = this.objName[i].indexOf(feildId)
if (index !== -1) {
this.objName[i][index] = feildName;
}
}
}
}
}
}
}
console.log(this.objName)
}
The objName array, for output, ends up looking like:
[['obj1', 'string_name1', 'string_name2'], ['obj2', 'string_name3', 'string_name4']]
It works, but makes my eyes hurt, there must be an easier cleaner way of doing this?
Plunker link:
https://plnkr.co/edit/KBDu3ZehHl04er6eut6r?p=preview
Your data structures are not ideal for this kind of transformation. For instance, it would have been better if the display strings could be addressed directly given an "obj"-property and array index, without having to iterate through arrays.
Anyway, using the existing structure, you can still improve by using array functions, such as find and map:
class App {
constructor(preview, objName, fields) {
this.preview = preview;
this.objName = objName;
this.fields = fields;
this.setNumbers();
}
setNumbers() {
this.objName = this.objName.map( arr => arr.concat(this.preview[0][arr[0]]) );
this.setStrings();
}
setStrings() {
this.objName = this.objName.map( arr =>
[arr[0]].concat(arr.slice(1).map( val =>
this.fields.find( field => arr[0] in field )[arr[0]]
.find( item => item.id === val ).name
))
);
console.log(this.objName);
}
}
var objName = [['obj1'], ['obj2']],
preview = [{
obj1: [1, 2],
obj2: [3, 4]
}],
fields = [{
obj1:[{
id:1,
name:'string_name1'
}, {
id:2,
name:'string_name2'
}]
}, {
obj2:[{
id:3,
name:'string_name3'
}, {
id:4,
name:'string_name4'
}]
}];
new App(preview, objName, fields);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Note that this code assumes all searches lead to matches. If this is not your case, you'll have to add some code to define which values should be returned in case of non-matching references.
Here is such a variant of the code:
class App {
constructor(preview, objName, fields) {
this.preview = preview;
this.objName = objName;
this.fields = fields;
this.setNumbers();
}
setNumbers() {
this.objName = this.objName.map( arr =>
arr[0] in this.preview[0]
? arr.concat(this.preview[0][arr[0]])
: arr
);
this.setStrings();
}
setStrings() {
this.objName = this.objName.map( arr =>
[arr[0]].concat(arr.slice(1).map( val => {
let find = this.fields.find( field => arr[0] in field );
if (find) find = find[arr[0]].find( item => item.id === val );
return find ? find.name : val;
}))
);
console.log(this.objName);
}
}
var objName = [['obj1'], ['obj2'], ['obj3']],
preview = [{
obj1: [1, 2],
obj2: [3, 4, 5],
}],
fields = [{
obj1:[{
id:1,
name:'string_name1'
}, {
id:2,
name:'string_name2'
}]
}, {
obj2:[{
id:3,
name:'string_name3'
}, {
id:4,
name:'string_name4'
}]
}];
new App(preview, objName, fields);
.as-console-wrapper { max-height: 100% !important; top: 0; }
It's easier and cleaner to do this if you break it down into smaller pieces:
let objsToMap = [{
obj1: [1, 2, 7],
obj2: [3, 4],
obj3: [1, 2]
}]
let objValues = [{
obj1: [{
id: 1,
name: 'string_name1'
}, {
id: 2,
name: 'string_name2'
}]
}, {
obj2: [{
id: 3,
name: 'string_name3'
}, {
id: 4,
name: 'string_name4'
}]
}];
function findValueForId(objDef, id) {
let idKeyMap = objDef.find(item => item.id === id);
return idKeyMap ? idKeyMap.name : null;
}
function findObjectValues(valueMapping, key) {
let objectWithObjectValues = valueMapping.find(item => key in item);
return objectWithObjectValues ? objectWithObjectValues[key] : null;
}
// returns an array containing key followed by the values corresponding to the specified ids
function lookupObject(key, ids, valueMapping) {
let objDef = findObjectValues(valueMapping, key) || [];
let valuesForIds = ids.map(id => findValueForId(objDef, id));
let valuesWithoutBlanks = valuesForIds.filter(value => value);
return [key].concat(valuesWithoutBlanks);
}
let result = Object.entries(objsToMap[0]).map(([k, v]) => lookupObject(k, v, objValues));
console.log(result);
You'll notice that this approach uses .find() in two places because your second data structure nests everything into arrays instead of having direct property references. This isn't very good because it's not good for performance and makes the code more convoluted than it has to be.
Another option is to rearrange the second array before consuming it, so that it's like this:
let objValues = {
obj1: {
'1': 'string_name1',
'2': 'string_name2'
},
obj2: {
'3': 'string_name3',
'4': 'string_name4'
}
};
Here's how you could do that:
let objsToMap = [{
obj1: [1, 2, 7],
obj2: [3, 4],
obj3: [1, 2]
}]
let objValuesRaw = [{
obj1: [{
id: 1,
name: 'string_name1'
}, {
id: 2,
name: 'string_name2'
}]
}, {
obj2: [{
id: 3,
name: 'string_name3'
}, {
id: 4,
name: 'string_name4'
}]
}];
function cleanupObjDef(objDef) {
return objDef.reduce(function(acc, el) {
acc[el.id] = el.name;
return acc;
}, {});
}
function cleanupObjValues(objValues) {
let allCombined = Object.assign({}, ...objValues);
return Object.entries(allCombined).reduce(function (acc, [k, v]) {
acc[k] = cleanupObjDef(v);
return acc;
}, {});
}
// returns an array containing key followed by the values corresponding to the specified ids
function lookupObject(key, ids, valueMapping) {
let objDef = valueMapping[key] || {};
let valuesForIds = ids.map(id => objDef[id]);
let valuesWithoutBlanks = valuesForIds.filter(value => value);
return [key].concat(valuesWithoutBlanks);
}
let objValues = cleanupObjValues(objValuesRaw);
let result = Object.keys(objsToMap[0]).map(key => lookupObject(key, objsToMap[0][key], objValues));
console.log(result);

Unflatten JS object and convert arrays

I have a function used to flatten objects like so:
let object = {
a: 1,
b: [
{ c: 2 },
{ c: 3 }
]
};
flatten(object)
// returns {
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}
I need to unflatten objects, but also revert arrays to how they were. I have the following code:
unflatten(obj) {
let final = {};
for (let prop in obj) {
this.assign(final, prop.split('.'), obj[prop]);
}
return final;
}
assign(final, path, value) {
let lastKeyIndex = path.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
let key = path[i];
if (!(key in final)) {
final[key] = {};
}
final = final[key];
}
final[path[lastKeyIndex]] = value;
}
which works for the most part, but it treats arrays like so:
{
a: 1,
b: { // Notice how b's value is now an object
"0": { c: 2 }, // Notice how these now have a key corresponding to their index
"1": { c: 3 }
}
}
Whereas I need b to be an array like before:
{
a: 1,
b: [
{ c: 2 },
{ c: 3 }
]
}
I'm at a loss for where to go from here. It needs to be able to deal with an arbitrary number of arrays like:
'a.b.0.c.0.d',
'a.b.0.c.1.d',
'a.b.1.c.0.d',
'a.b.1.c.1.d',
'a.b.1.c.2.d',
// etc
It needs to be vanilla JS, but es2015 is fine. It it assumed that any key that's a number is actually part of an array.
If anyone has any advice, it's appreciated!
When you find that key is not in final, you should check to see if the next key in the path is only digits (using a regular expression) and, if so, assign to an array instead of an object:
if (!(key in final)) {
final[key] = /^\d+$/.test(path[i + 1]) ? [] : {};
}
let object = {
a: 1,
b: [{
c: 2
},
{
c: 3
}
]
};
let flattened = {
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}
function unflatten(obj) {
let final = {};
for (let prop in obj) {
assign(final, prop.split('.'), obj[prop]);
}
return final;
}
function assign (final, path, value) {
let lastKeyIndex = path.length - 1;
for (var i = 0; i < lastKeyIndex; ++i) {
let key = path[i];
if (!(key in final)) {
final[key] = /^\d+$/.test(path[i + 1]) ? [] : {};
}
final = final[key];
}
final[path[lastKeyIndex]] = value;
}
console.log(unflatten(flattened))
.as-console-wrapper { min-height: 100vh; }
You could iterate the keys and then split the string for single properties. For building a new object, you could check for number and take an array for these properties.
function setValue(object, path, value) {
var way = path.split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk) {
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
}, object)[last] = value;
}
function unFlatten(object) {
var keys = Object.keys(object),
result = isFinite(keys[0][0]) ? [] : {};
keys.forEach(function (k) {
setValue(result, k, object[k]);
});
return result;
}
console.log(unFlatten({
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}));
console.log(unFlatten({
'0': 1,
'1.0.c': 2,
'1.1.c': 3
}));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories