Javascript - Count first element of multi dimensional array - javascript

I have below code to extract the ID and username of a unique list of authors.
let authorsList = await Poem.find({ 'author.id': {$nin: community.busAdmins}}).where('communities').equals(community._id).populate('authors').sort({"author.username": 1});
let uniqueAuthorsList = [];
authorsList.forEach(details => {
if(!uniqueAuthorsList.some(code => code.username == details.author.username)){
uniqueAuthorsList.push({username: details.author.username, id: details.author.id});
}
});
For each of those authors, I want to count how many blogs they have written. So far I have this code:
const counts = {};
uniqueAuthorsList.forEach((el) => {
counts[el] = counts[el] ? (counts[el] += 1) : 1;
});
console.log(counts);
But this only returns:
{ '[object Object]': 7 }
How can I count the records using only the first element of the array (username), so I can return a list like this?
Dave: 4
Emily: 7
Mark: 2

Put the counts in uniqueAuthorsList when you're creating it.
let uniqueAuthorsList = [];
authorsList.forEach(details => {
let author = uniqueAuthorsList.find(code => code.username == details.author.username);
if (author) {
author.count++;
} else {
uniqueAuthorsList.push({
username: details.author.username,
id: details.author.id,
count: 1
});
}
});
You might want to just make uniqueAuthors an object rather than an array.
let uniqueAuthors = {};
authorsList.forEach(details => {
if (uniqueAuthors[details.author.username]) {
uniqueAuthors[details.author.username].count++;
} else {
uniqueAuthors[details.author.username] = {
username: details.author.username,
id: details.author.id,
count: 1
};
}
});

Related

Convert array of strings into array of objects where each string itself has a separator

I have an array of strings:
["aa-q1-true", "bb-q1-false", "cc-q1-true", "aa-q2-true", "xx-q2-false", "yy-q2-true", "mm-q3-true", "mn-q3-false", "qr-q3-false"]
Where each string has a meaning. For example if we consider the first string i.e., "aa-q1-true"
Here the first part - aa is the quiz answer, middle part - q1 is the question and true, the last part is the answer status. Status can be true or false. This rule applies for each of the string inside the array.
Now I want to convert it into an array of objects like the following -
[
0: [{
quizAns: [{aa: true}, {bb: false}, {cc: true}]
quizQuestion: q1
}]
1: [{
quizAns: [{aa: true}, {xx: false}, {yy: true}]
quizQuestion: q2
}]
2: [{
quizAns: [{mm: true}, {mn: false}, {qr: false}]
quizQuestion: q3
}]
]
I just could not able to apprehend the logic to do this on my own. If you can just gimme some ideas or solution, it would be very helpful. Thank you so much for your time.
You would want to split each item by -, and then you get the first and last item to get your answer object, and the middle to get which question it belongs to, then you just iteratively construct the solution:
let ans = ["aa-q1-true", "bb-q1-false", "cc-q1-true", "aa-q2-true", "xx-q2-false", "yy-q2-true", "mm-q3-true", "mn-q3-false", "qr-q3-false"].map((s) => s.split('-')).reduce((carry, current) => {
let existingIndex = carry.findIndex((item) => item.quizQuestion === current[1]);
if (existingIndex === -1) {
carry.push({quizAns: [], quizQuestion: current[1]});
existingIndex = carry.length - 1;
}
carry[existingIndex].quizAns.push({[current[0]]: current[2]});
return carry;
}, []);
console.log(ans);
Just another approach with less code
const resultMap = array.reduce((acc, item) => {
// current data
const [answer, quizQuestion, status] = item.split("-");
// previous answers
const previousAnswers = acc[quizQuestion] ? acc[quizQuestion].quizAns : [];
// new answers
const newAnswers = [...previousAnswers, { [answer]: status }];
return { ...acc, [quizQuestion]: { quizQuestion, quizAns: newAnswers } };
}, {});
const result = Object.values(resultMap)
console.log(result)
Just an another approach. Has less looping over the array than the other answer.
let questions = { };
let ans = ["aa-q1-true", "bb-q1-false", "cc-q1-true", "aa-q2-true", "xx-q2-false", "yy-q2-true", "mm-q3-true", "mn-q3-false", "qr-q3-false"]
.reduce((acc, curr) => {
let elements = curr.split("-");
let obj = {};
obj[elements[0]] = elements[2];
if (questions[elements[1]]) {
questions[elements[1]].push(obj);
} else {
questions[elements[1]]= [obj];
}
}, {})
let result = [];
for (let prop in questions) {
result.push({ "quizAns": questions[prop], "quizQuestion": prop });
}
console.log(result);
I am not a big fan of array.reduce syntax cause of readability factor.
This loop will work, even if the input comes in a scrambled fashion.
const a = ["aa-q1-true", "bb-q1-false", "cc-q1-true", "aa-q2-true", "xx-q2-false", "yy-q2-true", "mm-q3-true", "mn-q3-false", "qr-q3-false"];let results = [];
const retObj = (que, ans) => {
const obj = {};
obj[`${que}`] = ans;
return obj;
};
for (let i in a){
let currEleArr = a[i].split("-");
let tarInd = parseInt(currEleArr[1].split("")[1]) - 1;
let que = currEleArr[0];
let ans= currEleArr[2];
if (!results[tarInd])
results[tarInd] = [{quizAns: []}];
if(!results[tarInd][0].quizQuestion)
results[tarInd][0]["quizQuestion"] = `q${tarInd + 1}`;
results[tarInd][0].quizAns.push(retObj(que, ans));
}
console.log(results);

How can i remove the duplicated elements in my array?

I used this code but with that i can only remove the next one and the previuous one if its equal
for (let i = 0; i < this.userlist.length; i++) {
if (this.userlist[i] == this.userlist[i+1])
this.userlist.splice(i+1, 1);
if (this.userlist[i-1] == this.userlist[i+1])
this.userlist.splice(i+1, 1);
}
How can i remove all the duplicated elements?
edit n°1
data() {
return {
formlogin: "",
userID: "Guest",
logged: false,
userlist: []
};
},
mounted() {
this.userID = localStorage.getItem("userID");
if (this.userID != "Guest") this.logged = localStorage.getItem("logged");
if (localStorage.userlist)
this.userlist = JSON.parse(localStorage.getItem("userlist"));
},
props: {},
methods: {
login: function() {
if (this.formlogin != "") {
this.userID = this.formlogin;
this.formlogin = "";
this.logged = true;
localStorage.setItem("logged", this.logged);
localStorage.setItem("userID", this.userID);
this.userlist.push(this.userID);
for (let i = 0; i < this.userlist.length; i++) {
if (this.userlist[i] == this.userlist[i + 1])
this.userlist.splice(i + 1, 1);
if (this.userlist[i - 1] == this.userlist[i + 1])
this.userlist.splice(i + 1, 1);
}
localStorage.setItem("userlist", JSON.stringify(this.userlist));
console.log("data sent :", this.userID, this.logged);
alert("Welcome : " + this.userID);
} else alert("cant login with a null username");
},
thats how my userlist will be updated.
es6 spread operator with Set()
var items = [4,5,4,6,3,4,5,2,23,1,4,4,4];
console.log([...new Set(items)]);
OR,
var items = [4,5,4,6,3,4,5,2,23,1,4,4,4];
console.log(Array.from(new Set(items)));
Using filter method
var items = [4,5,4,6,3,4,5,2,23,1,4,4,4];
var newItems = items.filter((item, i) => items.indexOf(item) === i);
console.log(newItems);
Using reduce method
var items = [4,5,4,6,3,4,5,2,23,1,4,4,4];
var newItems = items.reduce((uniq, item) => uniq.includes(item) ? uniq: [...uniq, item], []);
console.log(newItems);
You almost got it!
for (let i = 0; i < this.userlist.length; i++) {
if (this.userlist[i] == this.userlist[i+1]){
this.userlist.splice(i+1, 1);
i--;
}
}
In your solution, two elements are removed at most. What you can do instead is remove the next element an make sure that the index doesn't increase (hence the i--, so in the next iteration if will check the same index again).
This however only works for sorted lists. Check solanki's answer for a more generic one.
Using reduce you can do something like this. Check if the current index is same as the first index found in data
var data = ["user", "user", "user", "foo", "foo"]
var res = data.reduce((acc, elem, idx, arr)=> (arr.indexOf(elem) === idx ? [...acc, elem] : acc),[]);
console.log(res)

array multiple filter and variable assignment at once

I have the following forEach statement:
reviews.forEach(review => {
if (review.timestamp >= beforeThreeMonthsDate) {
lastThreeMonths.push(review);
}
if (review.timestamp >= beforeSixMonthsDate) {
lastSixMonths.push(review);
}
if (review.timestamp >= beforeOneYearDate) {
lastYear.push(review);
}
if (review.timestamp >= beforeTwoYearsDate) {
lastTwoYear.push(review);
}
});
Is there any way to get the same result with looping just one time through the reviews list using filter or any new ES6 functionality
It changes the output a little but, you could reduce to an object. instead of 4 separate variables.
const {
lastThreeMonths,
lastSixMonths,
lastYear,
lastTwoYear
} = reviews.reduce(
(groups, review) => {
if (review.timestamp >= beforeThreeMonthsDate) {
groups.lastThreeMonths.push(review);
}
if (review.timestamp >= beforeSixMonthsDate) {
groups.lastSixMonths.push(review);
}
if (review.timestamp >= beforeOneYearDate) {
groups.lastYear.push(review);
}
if (review.timestamp >= beforeTwoYearsDate) {
groups.lastTwoYear.push(review);
}
},
{ lastThreeMonths: [], lastSixMonths: [], lastYear: [], lastTwoYear: [] }
);
This is really similar to a groupBy function.
But I'd think about how this info is used. If it's for a view layer I would think about sorting and then using a takeUntil type function for flexibility. If you needed lastWeek, lastDay and more then the previous and original solutions could get unwieldy.
const takeUntil = (pred, list) =>
list.reduce((taken, next) => (pred(next) ? taken.concat(next) : taken), []);
const sinceTwoDays = takeUntil(review => review.timestamp >= twoDaysAgo, reviews);
This way loops at minimum 2 times but it gives a lot of future flexibility. You can pass any date in and return it.
You can use Array.reduce. You also can make this function rather versatile by reducing to an array and (optionally) destructuring to extract each nested array as a separate variable. This way, you don't need to define each key name within the function. The result may or may not be considered "cleaner" depending on what your goals are, though. For example, see the getReviewsSince() function in the snippet below:
// Don't mind these functions, they're just for the sake of a working example snippet
const writeLine = (() => {
const preEl = document.querySelector('.js-pre')
return (s = '') => preEl.textContent += `${s}\n`
})()
const writeArr = (name, arr) => {
writeLine(name)
arr.forEach(r => writeLine(JSON.stringify(r)))
writeLine()
}
const getExampleReviews = () => {
let i = 0
return [
{ id: i++, timestamp: 1562166556565 },
{ id: i++, timestamp: 1514985756565 },
{ id: i++, timestamp: 1514995756565 },
{ id: i++, timestamp: 1562165556565 },
{ id: i++, timestamp: 1451837356565 },
{ id: i++, timestamp: 1451837356565 },
]
}
// ---
// Get an array - each element is a nested array for the matching timestamp
const getReviewsSince = (reviews, timestamps) =>
reviews.reduce((arr, review) => {
timestamps.forEach((ts, idx) => {
if (review.timestamp >= ts) {
arr[idx].push(review)
}
})
return arr
}, timestamps.map(() => []))
// Example usage of getReviewsSince() w/ destructuring
const reviews = getExampleReviews()
const [
lastThreeMonths,
lastSixMonths,
lastYear,
lastTwoYears,
] = getReviewsSince(reviews, [
1562166556565,
1546531756565,
1514995756565,
1451837356565,
])
// Output the results for us to see
writeArr('lastThreeMonths', lastThreeMonths)
writeArr('lastSixMonths', lastSixMonths)
writeArr('lastYear', lastYear)
writeArr('lastTwoYears', lastTwoYears)
<pre class="js-pre"></pre>
You can do something like this to remove the if statements and make it more compact.
reviews.forEach(review => {
review.timestamp >= beforeThreeMonthsDate && lastThreeMonths.push(review);
review.timestamp >= beforeSixMonthsDate && lastSixMonths.push(review);
review.timestamp >= beforeOneYearDate && lastYear.push(review);
review.timestamp >= beforeTwoYearsDate && lastTwoYear.push(review);
});
Your approach is great but if you want cleaner i ocurred something like this:
let lastThreeMonths = [];
let lastSixMonths = [];
let lastYearMonths = [];
let lastTwoYears = [];
reviews.forEach(review => {
if (review.timestamp >= beforeThreeMonthsDate) {
lastThreeMonths = [...lastThreeMonths, review];
}
else if (review.timestamp >= beforeSixMonthsDate) {
lastSixMonths = [...lastSixMonths, review];
}
else if (review.timestamp >= beforeOneYearDate) {
lastYear = [...lastYear, review ];
}
else if (review.timestamp >= beforeTwoYearsDate) {
lastTwoYear = [...lastTwoYear, review];
}
});
You can check the docs of destructing assignment Here:
Since your code already have only one loop, I am not sure what you need for "with looping just one time".
With the filter, you can do this.
lastThreeMonths = reviews.filter(review => review.timestamp >= beforeThreeMonthsDate);
lastSixMonths = reviews.filter(review => review.timestamp >= beforeSixMonthsDate);
lastYear = reviews.filter(review => review.timestamp >= beforeOneYearDate);
lastTwoYear = reviews.filter(review => review.timestamp >= beforeTwoYearsDate);

Find next available id in array of objects

I have an array of objects. These objects have a property id. I need a function which returns the next available id (which is not used by an object).
array = [
{
id: 1
},
{
id: 2
},
{
id: 5
},
{
id: 3
}
]
I would like to have a function which takes an array as an input and returns a number (which is the next free id).
In the example case:
findFreeId(array){
magic happens
}
result --> 4
How about something like this?
function findFreeId (array) {
const sortedArray = array
.slice() // Make a copy of the array.
.sort(function (a, b) {return a.id - b.id}); // Sort it.
let previousId = 0;
for (let element of sortedArray) {
if (element.id != (previousId + 1)) {
// Found a gap.
return previousId + 1;
}
previousId = element.id;
}
// Found no gaps.
return previousId + 1;
}
// Tests.
let withGap = [{id: 1}, {id: 2}, {id: 5}, {id: 3}];
let noGap = [{id: 1}, {id: 2}];
let empty = [];
console.log(findFreeId(withGap)); // 4
console.log(findFreeId(noGap)); // 3
console.log(findFreeId(empty)); // 1
A simple approach is to get all the ID values, sort them, then starting at 0 look for the first missing number in the sequence. That may be OK where efficiency doesn't matter, but a more efficient method is to:
Get the IDs
Sort them
Step through the values to get the next available number
Insert the value in the list of IDs
Store the value so next time it starts at #3 from the previous value + 1
E.g.
class IDStore {
constructor(dataArray) {
if (!Array.isArray(dataArray)) {
return null;
}
this.previousIndex = 0;
this.indexes = dataArray.map(obj => obj.id).sort();
}
get nextIndex() {
while (this.indexes[this.previousIndex] == this.previousIndex) {
this.previousIndex++;
}
return this.previousIndex;
}
addIndex(index) {
if (!Number.isInteger(index) || this.indexes.find[index]) {
return null;
}
this.indexes.push(index);
this.indexes.sort();
return index;
}
}
var data = [ { id: 1 }, { id: 2 }, { id: 5 }, { id: 3 } ];
// Create an ID store
var idStore = new IDStore(data);
// Insert more objects in the array with unique IDs
for (var i=0, next; i<4; i++) {
// Current list of indexes
console.log('Indexes: ' + idStore.indexes);
// Get the next available index
next = idStore.nextIndex;
console.log('Next available: ' + next);
// Calling nextIndex doesn't affect next index
next = idStore.nextIndex;
console.log('Next available: ' + next);
// Use next index
data.push({id: next});
// Adding next index is manual
idStore.addIndex(next);
console.log('Added: ' + next);
}
// Data structure is independent
console.log('End: ' + JSON.stringify(data));
This is somewhat simplistic in that it assumes the IDs are sequential integers starting at 0 and doesn't have much validation or error handling.
Maintaining the id is separate from adding new members to the data array. It would be much better to combine the operations, so an "add object" method gets the next available ID, adds it to the object, adds the object to the array, updates the index and returns the new ID.
const findFreeId = (ids) => {
let id = 0;
for (id; ; id++) {
let isFree = true;
for (let i = 0; i < array.length; i++) {
const e = ids[i];
if (e === id) {
isFree = false;
break;
}
}
if (isFree) break;
}
return id;
}

JavaScript ES6 - count duplicates to an Array of objects

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 }

Categories