Group array of objects using a specific criteria - javascript

This question is related to this other question.
I'm sorry but I can't find the solution. I need to group an array of objects given by dataObject in this fiddle by the property objetivo with the properties: id, indicadores, objetivo and perspetiva.
This is my code:
var res = dataObject.reduce(function(res, currentValue) {
if ( res.indexOf(currentValue.objetivo) === -1 ) {
res.push(currentValue.objetivo);
}
return res;
}, []).map(function(objetivo) {
return {
objetivo: objetivo,
indicadores: dataObject.filter(function(_el) {
return _el.objetivo === objetivo;
}).map(function(_el) { return _el.indicador; }),
perspetiva: dataObject.perspetiva,
id: dataObject.id
}
});
console.log(res);
Which is grouping by objetivo correctly, but returning undefined for perspetiva and id.
Thanks for helping and sorry if this is duplicating other questions.

You're trying to access dataObject.id and .perspetiva, but dataObject is your main array and doesn't have those fields.
After reduce has picked out the unique objetivas, go back and find the items they came from:
.map(function(objetivo) {
var items = dataObject.filter(function(_el) {
return _el.objetivo === objetivo;
});
Then you can find all the indicadors, and since it looks like id and perspetiva are consistent for items with the same objetiva, you can just pick from the first one.
return {
objetivo: objetivo,
indicadores: items.map(function(_el) { return _el.indicador; }),
perspetiva: items[0].perspetiva,
id: items[0].id
}
Here's a working fiddle.

Related

Javascript Equalize Element From Array

Guys I want to get an element from array. Here:
Follower:
{ follower:
[ 5edfe8f3bfc9d677005d55ca,
5edfe92fbfc9d677005d55cc,
5ee2326cc7351c5bb0b75f1a ],
user id:
5edfe92fbfc9d677005d55cc
The process:
if(follower == user){
console.log("sdasdsad")
}else{
console.log("no")
}
But when I do it it always returns as no.
Also this is the codes of===> Nodejs Follow System Not Working Properly
It is a nodejs project. So please look at the above link.
When I do
if(follower.includes(user)){
It gives the error of:
TypeError: Cannot read property 'includes' of null
And when I try to change some I get this error:
TypeError: takip.includes is not a function
Guys so thats why I say please look at the above code.
So how to equalize them?
As other peoples said earlier the follower itself is a property which its value is an array itself, so if you want to check whether an item exists within it or not you can check it with includes(), if it exists it will return true otherwise it will return false.
const follow = {
follower: ["5edfe8f3bfc9d677005d55ca",
"5edfe92fbfc9d677005d55cc",
"5ee2326cc7351c5bb0b75f1a"
]
}
const user = "5edfe92fbfc9d677005d55cc";
if (follow.follower.includes(user)) {
console.log("sdasdsad")
} else {
console.log("no")
}
But if you looking to find the exact position of the item within that array you can find it with indexOf(). If the item does not exist within the array it will return -1, otherwise, it will return the index of that item within the array.
const follow = {
follower: ["5edfe8f3bfc9d677005d55ca",
"5edfe92fbfc9d677005d55cc",
"5ee2326cc7351c5bb0b75f1a"
]
}
const user = "5edfe92fbfc9d677005d55cc";
console.log(follow.follower.indexOf(user));
You are trying to compare a string to an array so it will never pass the if statement.
If you change your if to be if ( follower.includes(user)) { then it will search the array for the string.
var follower = [
'5edfe8f3bfc9d677005d55ca',
'5edfe92fbfc9d677005d55cc',
'5ee2326cc7351c5bb0b75f1a'
]
var user = '5edfe92fbfc9d677005d55cc'
// This will always fail as follower is an array not a string
if (follower.includes(user)){
console.log("sdasdsad")
} else {
console.log("no")
}
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
Looks like follower is a property. You can use this solution:
objectName.follower.forEach(item =>
if (item == user) console.log(`${item} is the answer`);
);
This way, javascript will go through all of the elements in the array and print it out if it is matching with your user variable.
You can also use for loop or while loop for the same process, however, since you're using an array, forEach will be much more useful.
If this was not your question and I misunderstood your question, let me know, I'll see if I can help.
I hope this helps
var obj = {
follower: [ '5edfe8f3bfc9d677005d55ca',
'5edfe92fbfc9d677005d55cc',
'5ee2326cc7351c5bb0b75f1a'
]
};
var userId = '5edfe92fbfc9d677005d55cc';
function searchUser(object, user){
if(obj.follower.includes(user)){
return object.follower.filter(x => x == user);
} else {
return 'no';
}
};
console.log(searchUser(obj, userId));
You can use Array.protorype.some() to check if user exists in the follower array.
const obj = {
follower: [
"5edfe8f3bfc9d677005d55ca",
"5edfe92fbfc9d677005d55cc",
"5ee2326cc7351c5bb0b75f1a"
]
}
const user = "5edfe92fbfc9d677005d55cc";
if(obj.follower.some(item => item === user)) {
console.log("found")
} else{
console.log("no")
}
You can also get the item with Array.protorype.find() with the same way as above, just assign it to a variable
Array.prototype.some
Array.prototype.find

JavaScript filter by all values of an array

Here my code and what i tried :
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => item.service.name.includes(arrayOfServices.values()));
},
I want to filter all items of this.filteredPrestations where the service name contains values of the arrayOfServices.
Anyone have an idea of what i can do ?
Thank's !
Remove .values() it returns an iterator which you don't need
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => item.service.name.includes(arrayOfServices));
}
You have to compare the items of a list with another. So you would have to have a compare each element of one data structure with another. Since you are comparing arrays you should do that way:
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => arrayOfServices.find(e => e === item.service.name))
},
That way you could compare the elements one by one.
Can you try this code. I think this code will work.
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => arrayOfServices.includes(item.service.name));
},

Optimise JavaScript

Can someone please help me to find best way to optimise the below code as it is taking a long time when I have thousand of records searching
var arr =[
{
children:[
{
children:[
{
children:[
{
name:'XYZZZZZ'
}
]
}
]
}
]
}
];
let list = [];
//Calculate column list
arr.forEach((obj0) => {
if (obj0.hasOwnProperty('children')) {
if (obj0.children.length > 0) {
let objchid1 = obj0.children;
objchid1.forEach((obj1) => {
if (obj1.hasOwnProperty('children')) {
if (obj1.children.length > 0) {
let objchid2 = obj1.children;
objchid2.forEach((obj2) => {
if (obj2.hasOwnProperty('children')) {
if (obj2.children.length > 0) {
let objchid3 = obj2.children;
objchid3.forEach((obj3) => {
if (obj3.name !== 'james') {
console.log('IN THREEE', obj3.name);
list.push(obj3.name);
}
});
}
}
});
}
}
});
}
}
});
I have tried searching a lot but no luck Thanks in advance.!!!
Optimize your data structure. Do not use nested arrays unless you really need to. NoSQL is so popular when it comes to WebDev because reads happen 100.000 times more than writes and saving on bandwidth (for you and the user) is worth more than saving on duplicate data in a database considering how cheap hardware is
You can save the elements of the deepest array as object keys (with the nested .name attribute in your case) and the index of the respective position in the array as the object value. This way you can do myArray[myElementsToIndexObject['elementIamLookingFor']] iterating only one single time over the nested array (for building myElementsToIndexObject)
If the data is from a JSON string, the search can be done during the parsing :
var list = [], json = '[{"child":[{"child":[{"child":[{"name":"XYZZZZZ"}]}]}]}]'
var arr = JSON.parse(json, (key, val) => (key === 'name' && list.push(val), val))
console.log(list)
console.log(arr)

Json and group by solution

I'm trying to select the last messages of an inbox and group them in a list by the topic_id. i want to display the last message of each topic.
the array looks like this:
[{
"id":"5",
"topic_id":"4",
"message_from":"24",
"message":"how do you do?",
"date":"2015-01-13 15:34:59"
},
{
"id":"6",
"topic_id":"1",
"message_from":"33",
"message":"go go go!!",
"date":"2015-01-13 13:35:06"
},
{
"id":"7",
"topic_id":"4",
"message_from":"33",
"message":"Je suis charlie",
"date":"2015-01-14 16:24:46"
},....
is there a solution to do it without a loop?
You can't do this without loops, but you can make this easier by breaking down the sequence of events into smaller functions. You might not like this approach, but it's the cleanest imo. Alternatively you can use a third-party library (underscore, perhaps?) that allows you to run groupings on data.
Basically, get a list of all the topic_ids for all records, loop over that topic_id array and pull out the last record for each and add that to an output array.
// Get a list of all the topic ids - no duplicates
function getTopicIds(arr) {
var out = [];
arr.forEach(function (el) {
if (out.indexOf(el.topic_id) === -1) out.push(el.topic_id);
});
return out;
}
// Given a topic_id, filter the array for only those records
// sort in desc order by id, and return the first record.
// Given that each record has a unique id, and we know that older
// messages will have higher ids, it's easier to sort by id than
// date here
function getLastMsg(id, arr) {
return arr.filter(function (el) {
return el.topic_id === id;
}).sort(function (a, b) { return +b.id - +a.id; })[0];
}
// return a array of the last messages for each topic_id
// in the records array
function getLastMsgs(arr) {
return getTopicIds(arr).map(function (id) {
return getLastMsg(id, arr);
});
}
var result = getLastMsgs(arr);
DEMO

AngularJS orderby with empty field

I am ordering a my data and its working all correcty except some fields are empty or have no value. When ordered these empty field come up first. For example when ordering numbers we would get a huge empty list before getting the "0"-values.
I am doing it like thise:
ng-click="predicate = 'name'; reverse=!reverse"
and
ng-repeat="name in names | orderBy:predicate:reverse"
JSFiddle: http://jsfiddle.net/JZuCX/1/
Is there an easy elegant way to fix this? I want the empty fields to come last, no matter what.
How about this for sorting strings:
item in (items|orderBy:['!name', 'name'])
The advantage (apart from being more concise) is it sorts null & undefined with the blank strings.
In my case I wanted the blanks & nulls & undefineds together at the top (nulls and undefineds by default sort to the bottom), so I used:
item in (items|orderBy:['!!name', 'name'])
I'd write a filter that takes items with empty name from ordered array and places them at the end:
<li ng-repeat="item in (items|orderBy:'name'|emptyToEnd:'name')">{{item.name}}</li>
Code might look like this:
.filter("emptyToEnd", function () {
return function (array, key) {
if(!angular.isArray(array)) return;
var present = array.filter(function (item) {
return item[key];
});
var empty = array.filter(function (item) {
return !item[key]
});
return present.concat(empty);
};
});
Working example.
By the way, your fiddle doesn't contain any relevant code. Did you use the wrong link?
Update 2:
Your fiddle with my filter.
Down here! :D
This solution extends the normal functionality of the angularJs orderBy filter to take a third argument specifying whether or not to invert the normal sorting of null and undefined values. It observes the property names it is passed (not just one), and doesn't iterate over items a second as some of the other solutions do. It's used like this:
<li ng-repeat="item in (items|orderBy:'name':false:true)">{{item.name}}</li>
I found a bunch of threads, some not directly about orderBy, and compiled their techniques plus a couple bits of my own into this:
angular.module('lib')
.config(['$provide', function ($provide) {
$provide.decorator('orderByFilter', ['$delegate', '$parse', function ($delegate, $parse) {
return function () {
var predicates = arguments[1];
var invertEmpties = arguments[3];
if (angular.isDefined(invertEmpties)) {
if (!angular.isArray(predicates)) {
predicates = [predicates];
}
var newPredicates = [];
angular.forEach(predicates, function (predicate) {
if (angular.isString(predicate)) {
var trimmed = predicate;
if (trimmed.charAt(0) == '-') {
trimmed = trimmed.slice(1);
}
var keyFn = $parse(trimmed);
newPredicates.push(function (item) {
var value = keyFn(item);
return (angular.isDefined(value) && value != null) == invertEmpties;
})
}
newPredicates.push(predicate);
});
predicates = newPredicates;
}
return $delegate(arguments[0], predicates, arguments[2]);
}
}])
}]);
To use this code verbatim, be to specify 'lib' as a dependency for your app.
Credits to:
$parse
[nullSorter].concat(originalPredicates)
decorator pattern
I don't believe there's an "out of the box" solution for this. I could easily be wrong.
Here's my attempt at a solution using a function as the predicate:
ng-repeat="name in names | orderBy:predicate"
Inside your controller:
$scope.predicate = function(name) {
return name === '' ? 'zzzzzzz' : !name;
/* The 'zzzzzz' forces the empty names to the end,
I can't think of a simpler way at the moment. */
}
In addition to the solution of Klaster_1, add an extra parameter to make the filter more generic:
http://jsfiddle.net/Zukzuk/JZuCX/27/
Implementation
<tr ng-repeat="name in (names | orderBy:predicate:reverse | orderEmpty:'name':'toBottom')">
Filter
.filter('orderEmpty', function () {
return function (array, key, type) {
var present, empty, result;
if(!angular.isArray(array)) return;
present = array.filter(function (item) {
return item[key];
});
empty = array.filter(function (item) {
return !item[key]
});
switch(type) {
case 'toBottom':
result = present.concat(empty);
break;
case 'toTop':
result = empty.concat(present);
break;
// ... etc, etc ...
default:
result = array;
break;
}
return result;
};
});
Thnx Klaster_1!
Sorting, and reverse sorting, using a variable sort column, and keeping the undefined at the bottom, even below the negative values
I love the elegance of Sean's answer above! I needed to give my users the ability to choose the column to sort on, and choice of sort direction, but still require the undefined's to fall to the bottom, even if there are negative numbers.
The key insight from Sean that fixes negative numbers is !!. Use '!'+predicate if you are doing forward sorting and '!!'+predicate if you are doing reverse sorting.
The snippet below demonstrates this. By the way, I have put the variables that set the predicate (choice of propery to sort on) and reverse inside an object ("d") just so that we don't get weird scope issues. You may not need the "d."s in your environment.
Moreover you would probably want to use something better than my crappy buttons at the bottom of the page to control your sort predicate and direction. However this keeps the key parts of the code easy to read.
function mainController($scope) {
$scope.userArray = [
{ name: "Don", age: 20 },
{ name: "Bob", age: 30, height: 170 },
{ name: "Abe", age: 40, height: 160 },
{ name: "Zoe", age: 70 },
{ age: 70, height: 155 },
{ name: "Shorty",age:45,height: -200},
{ name: "TwinkleInEye", age: -1, height: 152 }
]
$scope.d = {}; // Create an object into which info can be stored and not trashed by Angular's tendency to add scopes
$scope.d.predicate = "name"; // This string is the name of the property on which to sort
$scope.d.reverse = false; // True means reverse the sort order
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="" ng-controller="mainController">
<div ng-repeat="user in (userArray | orderBy: (d.reverse ?['!!'+d.predicate,d.predicate]:['!'+d.predicate,d.predicate]) : d.reverse)">
Name {{ user.name }} : Age {{ user.age }} : Height {{ user.height }}
</div>
<br/>
<button ng-click="d.predicate='name';">Name</button>
<button ng-click="d.predicate='age';">Age</button>
<button ng-click="d.predicate='height';">Height</button> Currently: {{d.predicate}}
<br/> Leave undefined at bottom, but otherwise:
<button ng-click="d.reverse= !d.reverse;">Reverse</button> Currently: {{d.reverse}}
</body>
#Klaster_1 was really on to something but as soon as I needed a nested value the filter stopped working. Also, if I was reverse ordering I still wanted my null values to show up before 0. I added $parse to take care of the nested keys and added a reverse parameter to I knew when to put the null values at the top.
.filter("emptyToEnd", function ($parse) {
return function (array, key, reverse) {
if(!angular.isArray(array)) return;
var keyFn = $parse(key);
var present = [];
var empty = [];
angular.forEach(array, function(item){
var val = keyFn(item);
if(angular.isUndefined(val) || val === null) {
empty.push(item);
} else {
present.push(item);
}
});
if (reverse) {
return present.concat(empty);
} else {
return empty.concat(present);
}
};
});
I don't know why other answer suggest to put the null value records at the bottom, If I want to sort normally, means in ASC order all the null on top and in DESC order all the nulls go to bottom, I tried other answers here but could not helped me so change the code to convert the null to '' in my array and it works now smooth like this:
$scope.convertNullToBlank = function (array) {
for (var i = 0; i < array.length; i++) {
if (array[i].col1 === null)
array[i].col1 = '';
if (array[i].col2 === null)
array[i].col2 = '';
}
return array;
}
I created a gist with an alternative filter based on the previous solutions:
https://gist.github.com/360disrupt/1432ee1cd1685a0baf8967dc70ae14b1
The filter extends the existing angular filter:
angular.module 'tsd.orderByEmptyLast', []
.filter 'orderByEmptyLast', ($filter) ->
return (list, predicate, reverse)->
orderedList = $filter('orderBy')(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList
On newer angular versions you might need to include orderByFilter instead of using $filter
angular.module 'tsd.orderByEmptyLast', ['orderByFilter']
.filter 'orderByEmptyLast', () ->
return (list, predicate, reverse)->
orderedList = orderByFilter(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList

Categories

Resources