JavaScript ES6 - count duplicates to an Array of objects - javascript

I'm creating for my list of products a filter to count all producers and display like this:
Apple (3)
I eliminated the duplicates from array: ["Apple","Apple","Apple"] I used this link:
Get all non-unique values (i.e.: duplicate/more than one occurrence) in an array
But my problem is that I want to count these elements from array and display them in an Array of Objects cause i need to iterate it later.
From this Array of Apples above i need result: [{"Apple": 3},{...},{...}]
I was trying to do this but it returns me object and I can't iterate after it:
How to count duplicate value in an array in javascript
I need an Array of Objects it's not duplicated
I'm using Angular 4.
My code:
component.ts
async ngOnInit() {
this.cart$ = await this.cartService.getCart();
this.subscription = this.productService.getAll().subscribe(products => {
this.category = products.filter(
products => products.category == this.name
);
this.filters();
});
}
filters() {
this.category2 = this.category.map(value => value.producer);
this.filteredArray = this.eliminateDuplicates(this.category2);
console.log(this.filteredArray);
}
eliminateDuplicates(arr) {
let i,
len = arr.length,
out = [],
obj = {};
for (i = 0; i < len; i++) {
obj[arr[i]] = 0;
}
for (i in obj) {
out.push(i);
}
return out;
}
component.html
<div *ngFor="let f of filteredArray">
{{f}}
</div>

You can use reduce to summarize the array and map for form the desired output
let obj = ["Apple", "Apple", "Apple", "Orange"];
let result = Object.values(obj.reduce((c, v) => {
c[v] = c[v] || [v, 0];
c[v][1]++;
return c;
},{})).map(o=>({[o[0]] : o[1]}));
console.log(result);

Here:
const array = ["a", "a", "b"]
const result = { }
for (let i = 0; i < array.length; i++) {
result[array[i]] = (result[array[i]] || 0) + 1
}
Object.keys(result).map(key => ({ [key]: result[key] }))
That last line is the key for
I was trying to do this but it returns me object

you can simply do it by using Lodash countBy function
filters() {
this.category2 = this.category.map(value => value.producer);
this.filteredArray = _.countBy(this.category2);
console.log(this.filteredArray);
// Object {Apple: 3, Orange: 1}
}

You can simply do it by using array.reduce() method
const votes = ['Yes', 'Yes', 'Yes', 'No', 'No', 'Absent'];
const result = votes.reduce((prevValue, vote) => {
if (prevValue[vote]) {
prevValue[vote]++;
} else {
prevValue[vote] = 1;
}
return prevValue;
}, {});
console.log(result);
Output : { Yes: 3, No: 2, Absent: 1 }

Related

Passed function as param should use another param from the same call

I'm recreating the filter, map, find functions in js. And I have a function pipe, that takes the given array and passes it trough an array of functions such as filter, map etc.
These are my functions:
const filter = (arr, callback) => {
let newArr = [];
let j = 0;
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
newArr[j] = arr[i];
j++;
}
}
return newArr;
};
const map = (arr, callback) => {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr[i] = callback(arr[i], i);
}
return newArr;
};
const pipe = (arr, callbacks) => {
console.log(arr, "arr");
console.log(callbacks, "callbacks");
};
I can use filter like so, and the function works fine.
const arr = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']
filter(arr, item => item.length > 6) // returns ["exuberant", "destruction", "present"]
However, when using pipe, I should not pass arr to each function. Instead filter and map should somehow take arr from pipe. But since those functions are being called directly in the pipe call, I do not see how can I achieve that functionality.
const arr = ['spray', 'limit', 'elite', 'exuberant', 'destruction']
pipe(
arr,
[
filter(item => item.length > 6), // returns ["exuberant", "destruction"]
map((item, index) => ({ id: index, name: item })) // returns [{ id: 0, name: 'exuberant' }, { id: 1, name: 'destruction' }]
]
)
// pipe returns [{ id: 0, name: 'exuberant' }, { id: 1, name: 'destruction' }]
Usually filter and map takes 2 params, arr and a callback. But in pipe it takes just the callback.
Any help would be appreciated.
Thanks.
I see that you are trying to implement a concept of functional programming known as pipe.
There's a better way to do what you are trying to achieve.
Instead of passing the array to the pipe function, only pass the map and filter functions to the pipe function and have pipe function return another function that takes the input array and returns the result of executing each function from left to right and passing the input array to each function.
Also change map and filter functions to return another function that takes in the input array as an argument and returns a new array after iterating over the array and passing each each element to the callback function.
const filter = callback => {
return (arr) => {
let newArr = [];
let j = 0;
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
newArr[j] = arr[i];
j++;
}
}
return newArr;
};
};
const map = callback => {
return (arr) => {
const newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr[i] = callback(arr[i], i);
}
return newArr;
};
};
const pipe = (...funcs) => {
return array => {
return funcs.reduce((acc, curr) => curr(acc), array);
};
};
const arr = ['spray', 'limit', 'elite', 'exuberant', 'destruction'];
const result = pipe(
filter(item => item.length > 6),
map((item, index) => ({ id: index, name: item }))
)(arr);
console.log(result);
there is no easy solution for this since you call the function filter and map directly in the pipe creation (simple functions will execute then)
one thing you can do is change your filter and map functions so they return an object with a method .execute that needs to be called to execute the filter or the map
const filter = (callback) => {
function execute (arr) = {
let newArr = [];
let j = 0;
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
newArr[j] = arr[i];
j++;
}
}
return newArr;
}
return { // return the object ready to execute
execute
}
};
const map = (callback) => {
function execute (arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr[i] = callback(arr[i], i);
}
return newArr;
}
return {
execute
}
};
now if you want to use a filter, do
const arr = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']
filter(item => item.length > 6).execute(arr)
// returns ["exuberant", "destruction", "present"]
and now the pipe function
const pipe = (arr, callbacks) => {
for (let i = 0; i < callbacks.length; i++) {
callback.execute(arr)
}
};
now use the pipe as wanted
const arr = ['spray', 'limit', 'elite', 'exuberant', 'destruction']
pipe(
arr,
[
filter(item => item.length > 6),
map((item, index) => ({ id: index, name: item }))
]
)
// returns [{ id: 0, name: 'exuberant' }, { id: 1, name: 'destruction' }]

Inconsistency, when returning index of duplicate values

I'm trying to create an algorithm to find duplicate values in a list and return their respective indexes, but the script only returns the correct value, when I have 2 equal elements:
array = [1,2,0,5,0]
result -> (2) [2,4]
Like the example below:
array = [0,0,2,7,0];
result -> (6) [0, 1, 0, 1, 0, 4]
The expected result would be [0,1,4]
Current code:
const numbers = [1,2,0,5,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(numbers.indexOf(avg),numbers.indexOf(avg,n_loop))
};
};
};
return tie;
}
console.log(checkATie(numbers));
if possible I would like to know some way to make this code more concise and simple
Use a Set
return [...new Set(tie)]
const numbers1 = [1,2,0,5,0];
const numbers2 = [0,0,2,7,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(avgList.indexOf(avg),avgList.indexOf(avg,n_loop))
};
};
};
return [...new Set(tie)]
}
console.log(checkATie(numbers1));
console.log(checkATie(numbers2));
I hope this help you.you can use foreach function to check each item of array
var array = [0,0,2,7,0];
var result = [] ;
array.forEach((item , index)=>{
if(array.findIndex((el , i )=> item === el && index !== i ) > -1 ){
result.push(index)
}
})
console.log(result);
//duplicate entries as an object
checkDuplicateEntries = (array) => {
const duplicates = {};
for (let i = 0; i < array.length; i++) {
if (duplicates.hasOwnProperty(array[i])) {
duplicates[array[i]].push(i);
} else if (array.lastIndexOf(array[i]) !== i) {
duplicates[array[i]] = [i];
}
}
console.log(duplicates);
}
checkDuplicateEntries([1,2,0,5,0]);
// hope this will help
Create a lookup object with value and their indexes and then filter all the values which occurred more than once and then merge all indexes and generate a new array.
const array = [1, 2, 0, 5, 0, 1, 0, 2],
result = Object.values(array.reduce((r, v, i) => {
r[v] = r[v] || [];
r[v].push(i);
return r;
}, {}))
.filter((indexes) => indexes.length > 1)
.flatMap(x => x);
console.log(result);

Find elements that commonly appear next to each other in an array

I am trying to find values that commonly appear next to each other in an array.
E.G. given the array:
["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"]
it should return something similar to:
[[["dog","cat"],4],[["cat","pig"],2],[["dog","cat","pig"],2]]
Here is some better data: https://pastebin.com/UG4iswrZ
Help would be greatly appreciated. Here is my current failed attempt at doing something similar:
function findAssociations(words){
var temp = [],tempStore = [],store = [],found = false;
//loop through the words counting occurrances of words together with a window of 5
for(var i = 0;i<words.length-1;i++){
if(i % 5 == 0){
//on every fith element, loop through store attempting to add combinations of words stored in tempStore
for(var j = 0;j<5;j++){
temp = []
//create the current combination
for(var k = 0;k<j;k++){
temp.push(tempStore[k]);
}
//find if element is already stored, if it is, increment the occurrence counter
for(var k = 0;k<store.length;k++){
if(store[k][0]===temp){
found = true;
store[k][1] = store[k][1]+1;
}
}
//if it isn't add it
if(found == false){
store.push([temp,1]);
}
found == false;
}
tempStore = [];
} else {
//add word to tempStore if it i isnt a multiple of 5
tempStore.push(words[i]);
}
}
}
This script is doesn't remove combinations that appear once,it doesn't sort the output by occurrences, nor does it work. It is just an outline of how a possible solution might work (as suggested by benvc).
Here is a generic solution working with multiple group sizes.
You specify a range of group sizes, for example [2,4] for groups of 2 to 4 elements and a minimum number of occurrences.
The function then generates all groups of neighbours of the given sizes, sorts each group and counts the duplicates. The sorting step can be removed is the order in the groups matters.
The duplicates are counted by creating a dictionary whose keys are the group elements sorted and jointed with a special marker. The values in the dictionary are the counts.
It then returns the groups sorted by occurences and then by group size.
const data = ["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
function findSimilarNeighbors(groupSizeRange, minOccurences, data) {
const getNeighbors = (size, arr) => arr.reduce((acc, x) => {
acc.push([]);
for (let i = 0; i < size; ++ i) {
const idx = acc.length - i - 1;
(acc[idx] || []).push(x);
}
return acc;
}, []).filter(x => x.length === size);
const groups = [];
for (let groupSize = groupSizeRange[0]; groupSize <= groupSizeRange[1]; ++groupSize) {
groups.push(...getNeighbors(groupSize, data));
}
const groupName = group => group.sort().join('###'); // use a separator that won't occur in the strings
const groupsInfo = groups.reduce((acc, group) => {
const name = groupName(group);
acc[name] = acc[name] || {};
acc[name] = { group, count: (acc[name].count || 0) + 1 };
return acc;
}, {});
return Object.values(groupsInfo)
.filter(group => group.count >= minOccurences)
.sort((a, b) => {
const countDiff = b.count - a.count;
return countDiff ? countDiff : b.group.length - a.group.length;
})
.map(({ group, count }) => [group, count]);
};
console.log(findSimilarNeighbors([2, 4], 2, data));
console.log(findSimilarNeighbors([4, 4], 2, data));
Here is what I came up with. It only finds pairs, but you could modify it to find sets of 3, 4, etc, based on what you % by
const animals = ['dog','cat','goat','dog','cat','elephant','dog','cat','pig','seal','dog','cat','pig','monkey'];
let pairs = ',';
animals.forEach((animal, i) => {
let separator = ',';
if (i % 2 === 0) {
separator = ';'
}
pairs += animal + separator;
});
const evenPairs = pairs.split(',');
const oddPairs = pairs.split(';');
const allPairs = evenPairs.concat(oddPairs).map(pair => pair.replace(/[;,]/, ' '));
let result = {}
allPairs.forEach(pair => {
if (pair.length) {
if (result[pair] === undefined) {
result[pair] = 1;
} else {
result[pair]++;
}
}
});
results in:
dog: 1
cat elephant: 1
cat goat: 1
cat pig: 2
dog cat: 4
elephant dog: 1
goat dog: 1
monkey : 1
pig monkey: 1
pig seal: 1
seal dog: 1
https://stackblitz.com/edit/typescript-wvuvnr
You need to be clear what you mean by close and how close. Just looking at first neighbours you could try:
const findAssociations = words => {
const associations = {}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i]
const wordRight = words[i+1]
const wordOne = word < wordRight ? word : wordRight;
const wordTwo = word < wordRight ? wordRight : word;
const keys = Object.keys(associations)
const key = `${wordOne}:${wordTwo}`
if (keys.indexOf(key) >= 0) {
associations[key]++
} else {
associations[key] = 1
}
}
const keys = Object.keys(associations)
const values = Object.values(associations)
const zipped = keys.map((key, index) => [key, values[index]])
zipped.sort((a, b) => a[1] < b[1] ? 1 : -1);
return zipped;
}
https://stackblitz.com/edit/js-3ppdit
You can use this function inside another function and add every time an element to ["dog", "cat"]
const arr = ["dog", "cat", "goat", "dog", "cat", "dog", "cat", "elephant", "dog", "cat", "pig", "seal", "dog", "cat", "pig", "monkey"]
const findArrayInArray = (arr1, arr2) => {
let count = 0,
arrString1 = arr1.join(""),
arrString2 = arr2.join("");
while (arrString2.indexOf(arrString1) > -1) {
count += 1;
arrString2 = arrString2.replace(arrString1, '');
}
return count;
}
console.log(`["dog", "cat"] exist ${findArrayInArray(["dog", "cat"], arr)} times`)
Assuming each item in the list is a delimiter of a set, and each set counts once for each item (i.e. ["dog", "cat", "goat"] counts as ["dog", "cat"] and ["dog", "cat", "goat"], and assuming you don't want any single occurrences, then here's one way:
const full_list = ["dog","cat","goat","dog","cat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
// create list of unique items
const distinct = (value, index, self) => {
return self.indexOf(value) ===index;
}
const unique_items = full_list.filter(distinct);
// get all patterns
var pre_report = {};
for (var i in unique_items) {
item = unique_items[i];
var pattern = [item];
var appending = false;
for (var j = full_list.indexOf(item) + 1; j < full_list.length; ++j) {
const related_item = full_list[j];
if (item == related_item) {
pattern = [item]
continue;
}
pattern.push(related_item);
if (pattern in pre_report) {
++pre_report[pattern];
} else {
pre_report[pattern] = 1;
}
}
}
// filter out only single occurring patterns
var report = {};
for (key in pre_report) {
if (pre_report[key] > 1) {
report[key] = pre_report[key];
}
}
console.log(report);
produces:
{ 'dog,cat': 5, 'dog,cat,pig': 2, 'cat,pig': 2 }

Cleaning the json object by removing duplicates and null and merging them into a single record

Cleaning the JSON object by removing duplicates and null and merging them into a single record
The json array looks like this:
var result =
[
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
]
I want to merge records into one neglecting all the null fields and make it as a single record.Below is my expected output:
[
{"id":"10035","occupation":"doctor","state":"FL"}
]
You could do it with this ES6 script:
let data = [
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
];
let result = Object.values(data.reduce ( (acc, {id, occupation, state}) => {
acc[id] = Object.assign({ id }, acc[id],
occupation && { occupation },
state && { state });
return acc;
}, {}));
console.log(result);
It will still produce multiple records if you have different id values in your input. When there are more than one non-null values for the other properties, but for the same id, then only the last one will survive.
When you're without support for Object.values
Use this definition of it:
Object.values = Object.values || (o => Object.keys(o).map(k => o[k]));
var final = {};
for (var i in result) {
for (var k in result[i]) {
if (result[i][k] && final[k] !== result[i][k]) {
final[k] = result[i][k];
}
}
}
console.log(final); // outputs: {id: "10035", state: "FL", occupation: "doctor"}
Here's a simple to understand example, which works for objects with any number of properties.
let data = [
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
];
let result = data[0];
data.forEach(obj=> { // iterate through all objects in array
for(key in obj) // iterate through all properties of objects
if(obj[key]) result[key] = obj[key]; // if not null, assign to final result
});
console.log(result);
Here is a way to do it in O(n) time:
const mergeObjects = (data) => {
const dataWithoutDuplicates = {};
// first pass will get rid of dupes
let user;
for(let i = 0; i < data.length; data++) {
user = data[i];
if(!dataWithoutDuplicates[user.id]) {
dataWithoutDuplicates[user.id] = user
} else {
Object.keys(dataWithoutDuplicates[user.id]).forEach(key => {
if(dataWithoutDuplicates[user.id][key] === null && user[key]) {
dataWithoutDuplicates[user.id][key] = user[key]
}
})
}
return Object.values(dataWithoutDuplicates)
}

What is the most efficent way to filter an object with an array of arrays?

I'm trying to filter an Object by an array of arrays, getting back an array of objects.
Like this:
let obj =
{
"a.1":1,
"a.2":2,
"b.1":3,
"b.2":4,
"c.1":5,
"c.2":6
}
let array =
[
["a.1","b.1"],
["a"],
["b","c.1"]
]
let expectedResult =
[
{
"a.1":1,
"b.1":3,
},
{
"a.1":1,
"a.2":2,
},
{
"b.1":3,
"b.2":4,
"c.1":5
},
]
// this is what I came up with
const filterObjectByArray = (obj, arr) =>
Object.keys(obj)
.filter(ch => {
for (var index = 0; index < arr.length; index++)
if (ch.startsWith(arr[index]))
return true;
})
.reduce((ret, key) =>{
ret[key] = obj[key]
return ret
},{})
let result = array.map(arr => filterObjectByArray(obj, arr))
//kind of deepEqual
console.log(JSON.stringify(expectedResult) == JSON.stringify(result))
Is there a easier or more convenient way to do that? I need to do this operation quite often and my object will be up to couple hundreds entries big, so I see a potential bottleneck here.
I would create a one type mapping of the "base" (the letter) to the "real" keys, and then use it to translate the letter to the real keys when create the object.
const obj = {
"a.1": 1,
"a.2": 2,
"b.1": 3,
"b.2": 4,
"c.1": 5,
"c.2": 6
};
const array = [
["a.1", "b.1"],
["a"],
["b", "c.1"]
];
const getBaseKey = (key) => key.match(/^[a-z]+/)[0]; // get the base of the key - the letter. If it's only one letter, you can use key[0]
/** create a one time map of keys by their base **/
const oobjKeysMap = Object.keys(obj).reduce((map, key) => {
const baseKey = getBaseKey(key);
const curr = map.get(baseKey) || [];
curr.push(key);
return map.set(baseKey, curr);
}, new Map());
const result = array.map((sub) => // iterate the array
[].concat(...sub.map((k) => k in obj ? k : oobjKeysMap.get(getBaseKey(k)))) // create the least of "real" keys
.reduce((ret, key) => { // create the object
ret[key] = obj[key];
return ret;
}, {})
);
console.log(result);

Categories