Sort array of objects by property length - javascript

I have this array:
var itemList = [
{
image: "images/home.jpg",
name: "Home"
},
{
name: "Elvis",
},
{
name: "Jonh"
},
{
image: "images/noah.jpg",
name: "Noah"
},
{
name: "Turtle"
}
]
How can I organize the array to objects with image property come first, so that it looks like this?:
var itemList = [
{
image: "images/home.jpg",
name: "Home"
},
{
image: "images/noah.jpg",
name: "Noah"
},
{
name: "Elvis",
},
{
name: "Jonh"
},
{
name: "Turtle"
}
]

This code put at the beginning elements that have the property 'image'. Other elements stay in the same order.
function compare(a,b) {
if ('image' in a) {
return 1;
} else if ('image' in b) {
return -1;
} else {
return 0;
}
}
itemList.sort(compare);

Try this:
function compare(a,b) {
if (a.image && b.image)
return 0;
if (a.image)
return 1;
return -1;
}
objs.sort(compare);

A bit late, but an alternative:
itemList.sort(function(e1,e2){ return (e1.image === undefined) - (e2.image === undefined); });

Related

JS: append array of objects with data from another

I've got some JS data holding all kinds of data, numbers, child objects, arrays, etc in all manner of different structures:
let datapile = {
cover_img: { uid:'u2a3j4' },
avatar_img: { uid:'u5j3vg' },
created: 8273736384,
friends: [
{ name:'John', img: { uid:'u2726b' }, },
{ name:'Jane', parent: { profile_img: { uid:'u293k4' }, } },
],
occupation: {
past: current,
prior: {
title: 'Accountant',
company: {
logo: { img: { uid:'u29374' } },
}
},
},
...
}
And then I've got this JS list of images:
let imgs : [
{ uid:'u2a3j4', format:'jpg', alt_txt:'Lorem...', size:583729, dominant_color:'#d79273' },
{ uid:'u5j3vg', format:'png', alt_txt:'Lorem...', size:284849, dominant_color:'#f99383' },
{ uid:'u2726b', format:'gif', alt_txt:'Lorem...', size:293742, dominant_color:'#349a83' },
...
],
Now, what I need is a function I can call that will look through the datapile and append img data objects from the imgs list below. So where the datapile now has only the uid reference, it should have the entire img object. And I will then do the same with all kinds of other pieces of referenced data.
I've tried the following function:
function isArray(x){ return ( x !== undefined && Array.isArray(x) ) }
function isObject(x){ return (x && typeof x === "object" && !Array.isArray(x)) }
function get_item(type, uid) { /* loops through eg. imgs and returns img matching uid */ }
function append_referenced_relations(data){
if( !data ) return data
if( isObject(data) && data['uid'] !== undefined ) {
let item = get_item('any', data['uid'])
data = item
}
if( isObject(data) || isArray(data) ) {
for( let key in data ) {
data[key] = this.append_referenced_relations(deepClone(data[key]))
}
}
return data
}
... but I just can't get it to work. And my best googling efforts for similar scenarios have also come up empty. Can the internet help me out here?
you can try something like this
basically it use recursion and Object.fromEntries /entries to check all the keys of the inner object
if you have any specific question feel free to ask me
const decorate = (obj, data) => {
if (typeof obj !== 'object') {
return obj
}
if (Array.isArray(obj)) {
return obj.map(e => decorate(e, data))
}
return Object.fromEntries(
Object.entries(obj).flatMap(([k, v]) => {
if (k === 'uid') {
const imgData = data.find(d => v === d.uid)
return Object.entries(imgData || [[k, v]])
}
return [
[k, decorate(v, data)]
]
})
)
}
let datapile = {
cover_img: {
uid: 'u2a3j4'
},
avatar_img: {
uid: 'u5j3vg'
},
created: 8273736384,
friends: [{
name: 'John',
img: {
uid: 'u2726b'
},
},
{
name: 'Jane',
parent: {
profile_img: {
uid: 'u293k4'
},
}
},
],
occupation: {
past: 'current',
prior: {
title: 'Accountant',
company: {
logo: {
img: {
uid: 'u29374'
}
}
}
}
}
}
let imgs = [{
uid: 'u2a3j4',
format: 'jpg',
alt_txt: 'Lorem...',
size: 583729,
dominant_color: '#d79273'
},
{
uid: 'u5j3vg',
format: 'png',
alt_txt: 'Lorem...',
size: 284849,
dominant_color: '#f99383'
},
{
uid: 'u2726b',
format: 'gif',
alt_txt: 'Lorem...',
size: 293742,
dominant_color: '#349a83'
}
]
console.log(decorate(datapile, imgs))
You need to recurse in the nested datapile to identify the object with uids and add the img properties to be added.
Few cases to consider:
Objects. (If the Object has uid property, then stop recursion for its properties)
Object values having objects.
Array of Objects.
No need to return anywhere in your function actually as we can update objects inline.
Try like below.
let imgs = [ { uid: "u2a3j4", format: "jpg", alt_txt: "Lorem...", size: 583729, dominant_color: "#d79273", }, { uid: "u5j3vg", format: "png", alt_txt: "Lorem...", size: 284849, dominant_color: "#f99383", }, { uid: "u2726b", format: "gif", alt_txt: "Lorem...", size: 293742, dominant_color: "#349a83", }, { uid: "u293k4", format: "gif", alt_txt: "Lorem...", size: 193742, dominant_color: "#349a83", }, { uid: "u29374", format: "gif", alt_txt: "Lorem...", size: 793742, dominant_color: "#349a83", }, ]; let datapile = { cover_img: { uid: "u2a3j4" }, avatar_img: { uid: "u5j3vg" }, created: 8273736384, friends: [ { name: "John", img: { uid: "u2726b" } }, { name: "Jane", parent: { profile_img: { uid: "u293k4" } } }, ], occupation: { past: "current", prior: { title: "Accountant", company: { logo: { img: { uid: "u29374" } }, }, }, }, };
function isArray(x) {
return x !== undefined && Array.isArray(x);
}
function isObject(x) {
return typeof x === "object" && !Array.isArray(x);
}
function get_item(uid) {
return imgs.find((img) => img.uid === uid);
}
function append_referenced_relations(data) {
if (isObject(data)) {
if (data["uid"] !== undefined) {
const img = get_item(data.uid);
// Add img properties to the same object as properties
Object.entries(img).forEach(([key, value]) => {
data[key] = value;
});
} else {
// Recurse for the object values
Object.values(data).forEach((item) => {
append_referenced_relations(item);
});
}
} else if (isArray(data)) {
data.forEach((item) => {
// Recurse for the array entries
append_referenced_relations(item);
});
}
}
append_referenced_relations(datapile);
console.log(JSON.stringify(datapile, null, 2));

How to traverse in a tree and filter matching nodes in javascript?

I have tried to filter nodes, but succeed to filter only root nodes.
My implementation is below.
How can I use a methodology to filter also sub-nodes?
function getMatchedValues(nodes)
{
var leafContainsText = false;
for (var i = 0; i < nodes.length; i++)
{
if (nodes[i].items && nodes[i].items.length > 0)
{
leafContainsText = getMatchedValues(nodes[i].items);
if (leafContainsText)
break;
}
else
{
if (nodes[i].text.toLowerCase().indexOf($scope.filterReports.toLowerCase()) !== -1)
{
leafContainsText = true;
break;
}
}
}
return leafContainsText;
}
$scope.temp_reports = [];
for (var i = 0; i < $scope.reports.length; i++)
{
if ($scope.reports[i].items && $scope.reports[i].items.length > 0)
{
if (getMatchedValues($scope.reports[i].items))
$scope.temp_reports.push($scope.reports[i]);
else if ($scope.reports[i].text.toLowerCase().indexOf($scope.filterReports.toLowerCase()) !== -1)
$scope.temp_reports.push($scope.reports[i]);
}
else
{
if ($scope.reports[i].text.toLowerCase().indexOf($scope.filterReports.toLowerCase()) !== -1)
$scope.temp_reports.push($scope.reports[i]);
}
}
The tree object($scope.reports) looks likes below:
[
{
text:"abc",
items:[
{
text:"abf"
},
{
text:"abd",
items:[
{
text:"bbb"
},
{
text:"dba"
}
]
}
]
},
{
text:"def",
items:[
{
text:"ddf"
},
{
text:"ddd",
items:[
{
text:"dfg"
},
{
text:"dba",
items:[
{
text:"qqq"
},
{
text:"www"
}
]
}
]
}
]
}
]
For example, If want to filter nodes that contain 'd' then result tree should look like this;
[
{
text:"abc",
items:[
{
text:"abd",
items:[
{
text:"dba"
}
]
}
]
},
{
text:"def",
items:[
{
text:"ddf"
},
{
text:"ddd",
items:[
{
text:"dfg"
},
{
text:"dba",
items:[]
}
]
}
]
}
]
You could filter the array by filtering the items array as well.
This solution mutates the original data.
function filter(array, search) {
return array.filter(function f(o) {
var t;
if (o.items) {
t = o.items.filter(f);
}
if (o.text.includes(search) || t && t.length) {
if (t) {
o.items = t;
}
return true;
}
});
}
var array = [{ text: "abc", items: [{ text: "abf" }, { text: "abd", items: [{ text: "bbb" }, { text: "dba" }] }] }, { text: "def", items: [{ text: "ddf" }, { text: "ddd", items: [{ text: "dfg" }, { text: "dba", items: [{ text: "qqq" }, { text: "www" }] }] }] }],
result = filter(array, 'd');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Using Array.prototype.filter to filter nested children objects

I have an array of objects similar to the following block of code:
var arr = [
{
text: 'one',
children: [
{
text: 'a',
children: [
{
text: 'something'
}
]
},
{
text: 'b'
},
{
text: 'c'
}
]
},
{
text: 'two'
},
{
text: 'three'
},
{
text: 'four'
}
];
In the above structure, I want to search a string in text property and I need to perform this search over all the children.
For example, if I search for something, the result should be an array of object in the following form:
[
{
children: [
{
children: [
{
text: 'something'
}
]
}
]
}
];
Notice all the text properties that do not match the input string something have been deleted.
I have come up with the following block of code using Array.prototype.filter. However, I can still see extra properties in the result:
function search(arr, str) {
return arr.filter(function(obj) {
if(obj.children && obj.children.length > 0) {
return search(obj.children, str);
}
if(obj.text === str) {
return true;
}
else {
delete text;
return false;
}
});
}
Here is the fiddle link: https://jsfiddle.net/Lbx2dafg/
What am I doing wrong?
I suggest to use Array#forEach, because filter returns an array, which is needed, but not practical for this purpose, because it returns all children with it.
This proposal generates a new array out of the found items, with the wantes item text and children.
The solution works iterative and recursive. It finds all occurences of the search string.
function filter(array, search) {
var result = [];
array.forEach(function (a) {
var temp = [],
o = {},
found = false;
if (a.text === search) {
o.text = a.text;
found = true;
}
if (Array.isArray(a.children)) {
temp = filter(a.children, search);
if (temp.length) {
o.children = temp;
found = true;
}
}
if (found) {
result.push(o);
}
});
return result;
}
var array = [{ text: 'one', children: [{ text: 'a', children: [{ text: 'something' }] }, { text: 'b' }, { text: 'c' }] }, { text: 'two' }, { text: 'three' }, { text: 'four' }];
console.log(filter(array, 'something'));
Your function search returns an array with object from "parent" level, that's why you "still see extra properties in the result".Secondly, this line delete text; doesn't delete an object or object property - it should be delete obj.text;. Here is solution using additional Array.map fuinction:
function search(arr, str) {
return arr.filter(function(obj) {
if (obj.text !== str) {
delete obj.text;
}
if (obj.children && obj.children.length > 0) {
return search(obj.children, str);
}
if (obj.text === str) {
return true;
} else {
delete obj.text;
return false;
}
});
}
var result = search(arr, 'something').map(function(v) { // filtering empty objects
v['children'] = v['children'].filter((obj) => Object.keys(obj).length);
return {'children':v['children'] };
});
console.log(JSON.stringify(result,0,4));
The output:
[
{
"children": [
{
"children": [
{
"text": "something"
}
]
}
]
}
]
https://jsfiddle.net/75nrmL1o/

AngularJS making a sum between objects

I am quite new to AngularJS and have been building the following:
app.js
$scope.inkomsten= [
{ name: 'Salaris',value:'500' },
{ name: 'Toeslag',value:'200' },
{ name: 'Inkomsten',value:'211' },
{ name: 'Weekend', value:'22' }
];
$scope.uitgiften= [
{ name: 'eten',value:'120'},
{ name: 'kat',value:'230'},
{ name: 'schildpad', value: '300'},
{ name: 'huur', value: '200'}
];
Now I would like to take the values of both of these object and substract them from each other!
So either have all the values of Inkomsten added to another and the ones from Uitgiften.. and then substract or individually(Which is prefered... So taking the value of uitgiften and then from inkomsten and substract them.)
I have been tinkering around with it but I can't seem to find a solution for it. Anyways, not in Angular :D
I hope you guys can help
Clarification:
Sorry. I would like to have
uitgiften.value[1]+uitgiften.value[2]+uitgiften.value[3]
and then inkomsten.value[1]+inkomsten.value[2]+inkomsten.value[3]
and then the outcome of these 2 summs would need to be outcomeInkomsten-outcomeUitgiften=endOutcome
Since your values are strings, you will need to cast them to numbers. Here's what I would do:
var result = 0;
$scope.inkomsten.forEach(function(item){
result += Number(item.value);
});
$scope.uitgiften.forEach(function(item){
result -= Number(item.value);
});
Since it is an array you could add a function to the Array prototype.
$scope.inkomsten= [
{ name: 'Salaris',value:'500' },
{ name: 'Toeslag',value:'200' },
{ name: 'Inkomsten',value:'211' },
{ name: 'Weekend', value:'22' }
];
$scope.uitgiften= [
{ name: 'eten',value:'120'},
{ name: 'kat',value:'230'},
{ name: 'schildpad', value: '300'},
{ name: 'huur', value: '200'}
];
Array.prototype.sum = function (prop) {
var total = 0;
for ( var i = 0, _len = this.length; i < _len; i++ ) {
total += parseInt(this[i][prop]);
}
return total;
}
$scope.endOutcome = $scope.inkomsten.sum("value") - $scope.uitgiften.sum("value");
The Fiddle:
http://jsfiddle.net/U3pVM/20778/
you can use array method reduce:
var $scope={};
$scope.inkomsten= [
{ name: 'Salaris',value:'500' },
{ name: 'Toeslag',value:'200' },
{ name: 'Inkomsten',value:'211' },
{ name: 'Weekend', value:'22' }
];
var res = $scope.inkomsten.reduce(function(total, current){
return total + Number(current.value);
}, 0)
console.log(res)
this will give you the numeric total of an Array;
and then you can do the same with second array, and subtract the two;
well you can use
var results=0;
for(i=0;i<$scope.inkomsten.length;i++){
results+=Number($scope.inkomsten[i].value);
}
for(i=0;i<$scope.uitgiften.length;i++){
results-=Number($scope.uitgiften[i].value);
}
console.log(results);
You can try this below code
$scope.inkomsten= [
{ name: 'Salaris',value:'500' },
{ name: 'Toeslag',value:'200' },
{ name: 'Inkomsten',value:'211' },
{ name: 'Weekend', value:'22' }
];
$scope.uitgiften= [
{ name: 'eten',value:'120'},
{ name: 'kat',value:'230'},
{ name: 'schildpad', value: '300'},
{ name: 'huur', value: '200'}
];
$scope.sum1 = 0;
$scope.sum2 = 0;
for(i in $scope.inkomsten){
$scope.sum1 = $scope.sum1 + parseInt($scope.inkomsten[i].value);
}
for(i in $scope.uitgiften){
$scope.sum2 = $scope.sum2 + parseInt($scope.uitgiften[i].value);
}
$scope.endOutcome = $scope.sum1 - $scope.sum2;
alert($scope.endOutcome);

How to loop through Array of Objects to find ID then return a sibling key

I have a complex Array of Objects below, and I have a term_id to search on. I'm trying to find the matching term_id, then return the associated ticker: name from the same Object from which I found the term_id.
container = [Object, Object];
// container:
[
0: Object {
tags: [
0: {
term: "tag_name_1",
term_id: 1111
},
0: {
term: "tag_name_2",
term_id: 2222
}
],
ticker: {
name: "ticker1"
}
},
1: Object {
tags: [
0: {
term: "tag_name_3",
term_id: 3333
}
],
ticker: {
name: "ticker2"
}
}
]
How would you accomplish this? Is there an easy way with _lodash?
// You can do this with native JS:
var container = [{tags: [{term: "tag_name_1",term_id: 1111},{term: "tag_name_2",term_id: 2222}],ticker: {name: "ticker1"}},{tags: [{term: "tag_name_3",term_id: 3333}],ticker: {name: "ticker2"}}];
function search (container, id) {
var contains = false;
var result;
container.forEach(function(obj){
obj.tags.forEach(function(innerData){
if (innerData.term_id === id) {
contains = true;
}
})
if (contains) {
result = obj.ticker.name;
contains = false;
}
});
return result;
}
console.log(search(container, 1111));
You can use Array.prototype.some for this. For example:
function find(arr, t) {
var ticker = null;
arr.some(function (doc) {
var tagMatch = doc.tags.some(function (tag) {
return tag.term_id === t;
});
if (tagMatch) {
ticker = doc.ticker.name;
}
return tagMatch;
});
return ticker;
}
Here's a JSFiddle.
Hope this helps you. It's a function that you can pass your objects into and a term_id you search for and it returns found ticker names:
var objs = [
{
tags: [
{
term: "tag_name_1",
term_id: 1111
},
{
term: "tag_name_2",
term_id: 2222
}
],
ticker: {
name: "ticker1"
}
},
{
tags: [
{
term: "tag_name_3",
term_id: 3333
}
],
ticker: {
name: "ticker2"
}
}
];
function getTickerNamesById(objs,id){
var foundNames = [];
objs.forEach(function(obj){
obj.tags.forEach(function(term){
if(term.term_id===id)foundNames.push(obj.ticker.name);
});
});
return foundNames;
}
getTickerNamesById(objs,3333); // ["ticker2"]
A forEach() loop works, though there is no way to prevent it from cycling through the entire object once the id is matched. Assuming the id's are unique, a option with better performance would be the while loop:
function findId(id,container) {
var i = 0,
j;
while (i < container.length) {
j = 0;
while (j < container[i].tags.length) {
if (container[i].tags[j].term_id === id) {
return container[i].ticker.name;
}
j += 1;
}
i += 1;
}
throw "item not found";
}
If your containers will be large you may want to consider this optimization. If you preferred a functional approach, you could accomplish a similar thing with some() or every(), both of which exit out given a specified condition.

Categories

Resources