I have the following object that I'm receiving from an API:
{
'2012-12-12': [
{ 'id': 1234,
'type': 'A' },
{ 'id': 1235,
'type': 'A' },
{ 'id': 1236,
'type': 'B' },
],
'2012-12-13': [
{ 'id': 1237,
'type': 'A' },
{ 'id': 1238,
'type': 'C' },
{ 'id': 1239,
'type': 'B' },
]
}
Then I want to have another variable named types of type Array that will hold every possible value of the type attribute of each one of the objects. In this case it would be:
types = ['A', 'B', 'C']
I'm trying to have it done in a functional way (I'm using underscore.js) but I'm unable to figure out a way of doing it. Right now I'm using
types = [];
_.each(response, function(arr1, key1) {
_.each(arr1, function(arr2, key2) {
types.push(arr2.type);
});
});
types = _.uniq(types);
But that's very ugly. Can you help me in figuring out a better way of writing this code?
Thanks!
This should work:
types = _.chain(input) // enable chaining
.values() // object to array
.flatten() // 2D array to 1D array
.pluck("type") // pick one property from each element
.uniq() // only the unique values
.value() // get an unwrapped array
Fiddle: http://jsfiddle.net/NFSfs/
Of course, you can remove all whitespace if you want to:
types = _.chain(input).values().flatten().pluck("type").uniq().value()
or without chaining:
types = _.uniq(_.pluck(_.flatten(_.values(input)),"type"));
flatten seems to work on objects, even though the documentation clearly states it shouldn't. If you wish to code against implementation, you can leave out the call to values, but I don't recommend that. The implementation could change one day, leaving your code mysteriously broken.
If you just want shorter code, you could flatten the objects into a single Array, then map that Array.
var types = _.unique(_.map(_.flatten(_.toArray(response)), function(arr) {
return arr.type;
}));
Here's another version. Mostly just for curiosity's sake.
var types = _.unique(_.pluck(_.reduce(response, _.bind(Function.apply, [].concat), []), "type"));
Here's another one.
var types = _.unique(_.reduce(response, function(acc, arr) {
return acc.concat(_.pluck(arr,"type"));
}, []));
And another.
var types = _.unique(_.pluck([].concat.apply([], _.toArray(response)), "type"))
Related
I want to display the data in a Tree View in Angular and need to transform an array of dot-notated elements into a collection of objects with children.
This is the array I'm working with. Notice the key field in every element.
So the structure I need is for example (for the first 4 elements in the array):
const data = [
{
key: 'bs',
children: [
{
key: 'ass',
children: [
{
key: 'fixAss',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 15542000,
children: [
{
key: 'intan',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 8536000,
children: [
{
key: 'concessionBrands',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 8536000,
children: [] // If there are no children in the element this can be empty or left out
}
]
},
{
key: 'tan',
decimals: '0',
unitRef: 'unit_euro',
contextRef: 'period_2019',
value: 6890000,
children: []
}
]
}
]
}
]
}
];
That means elements are combined by having a key attribute which holds the notation for that level (i.e "bs", "ass", "fixAss", ...) and then children of the next level. An element can have values of its own ("decimals", "unitRef",...) and might additionally also have children that are made up the same way. There is no restriction on the amount of levels this can have.
I have the lodash and dot object libraries in my package.json. Any help is very much appreciated.
it seems the dot-object lib has no things to work with something like "children" that you have, so it seems custom code is required to build what you expected
// balanceData got somehow
let data = [];
const getOrBuildPathObject = (path) => {
let currentLevel = data;
let obj = null;
for(let keyFragment of path.split('.')) {
obj = currentLevel.find(v => v.key == keyFragment);
if(!obj) {
obj = {key: keyFragment, children: []};
currentLevel.push(obj);
}
currentLevel = obj.children;
}
return obj;
}
balanceData.forEach((d) => {
let {key, ...data} = d;
Object.assign(getOrBuildPathObject(key), data);
})
should be something like that
I would just iterate through the array and check each key.
Split the key at the dots myArray.split('.') returns an array.
Now iterate through that array and create an Object for each element.
Like
bs.ass.fixAss
Check if a root element bs exists.
If no, create an (empty) bs Element.
Check if an ass element is a child of bs
If no, create an (empty) ass Element
Check if an (empty) fixAss Element exists.
If no, create the fixAss Element with values and add it as child to the ass Element
If yes, fill the values
If its guaranteed that the data will always be in the right order (that means bs.ass.fixAss will always be AFTER bs.ass) then you may skip the checks.
I would use a HashMap for the children (not an array), because that makes it much easier to walk through the tree
myTrees[bs].children[ass].children[fixAss]
The whole thing could be created with plain TypesScript. I do not know any library that would solve this specific problem out of the box.
Okay, so I am trying to create a function that allows you to input an array of Objects and it will return an array that removed any duplicate objects that reference the same object in memory. There can be objects with the same properties, but they must be different in-memory objects. I know that objects are stored by reference in JS and this is what I have so far:
const unique = array => {
let set = new Set();
return array.map((v, index) => {
if(set.has(v.id)) {
return false
} else {
set.add(v.id);
return index;
}
}).filter(e=>e).map(e=>array[e]);
}
Any advice is appreciated, I am trying to make this with a very efficient Big-O. Cheers!
EDIT: So many awesome responses. Right now when I run the script with arbitrary object properties (similar to the answers) and I get an empty array. I am still trying to wrap my head around filtering everything out but on for objects that are referenced in memory. I am not positive how JS handles objects with the same exact key/values. Thanks again!
Simple Set will do the trick
let a = {'a':1}
let b = {'a': 1,'b': 2, }
let c = {'a':1}
let arr = [a,b,c,a,a,b,b,c];
function filterSameMemoryObject(input){
return new Set([...input])
}
console.log(...filterSameMemoryObject(arr))
I don't think you need so much of code as you're just comparing memory references you can use === --> equality and sameness .
let a = {'a':1}
console.log(a === a ) // return true for same reference
console.log( {} === {}) // return false for not same reference
I don't see a good reason to do this map-filter-map combination. You can use only filter right away:
const unique = array => {
const set = new Set();
return array.filter(v => {
if (set.has(v.id)) {
return false
} else {
set.add(v.id);
return true;
}
});
};
Also if your array contains the objects that you want to compare by reference, not by their .id, you don't even need to the filtering yourself. You could just write:
const unique = array => Array.from(new Set(array));
The idea of using a Set is nice, but a Map will work even better as then you can do it all in the constructor callback:
const unique = array => [...new Map(array.map(v => [v.id, v])).values()]
// Demo:
var data = [
{ id: 1, name: "obj1" },
{ id: 3, name: "obj3" },
{ id: 1, name: "obj1" }, // dupe
{ id: 2, name: "obj2" },
{ id: 3, name: "obj3" }, // another dupe
];
console.log(unique(data));
Addendum
You speak of items that reference the same object in memory. Such a thing does not happen when your array is initialised as a plain literal, but if you assign the same object to several array entries, then you get duplicate references, like so:
const obj = { id: 1, name: "" };
const data = [obj, obj];
This is not the same thing as:
const data = [{ id: 1, name: "" }, { id: 1, name: "" }];
In the second version you have two different references in your array.
I have assumed that you want to "catch" such duplicates as well. If you only consider duplicate what is presented in the first version (shared references), then this was asked before.
Take for example this extremely simplified Array of Objects:
[
{
createID: '1'
// Many other properties...
},
{
createID: '1'
// Many other properties...
},
{
createID: '1'
// Many other properties...
},
{
createID: '37'
// Many other properties...
},
{
createID: '37'
// Many other properties...
},
{
createID: '2'
// Many other properties...
},
{
createID: '2'
// Many other properties...
},
{
createID: '14'
// Many other properties...
},
];
Given this Array I then use the objects createID property to create an Array of Arrays containing Objects [[{..},{..}], [{..}], ..n]. This final format is required by the current front end framework I am using (Angular v6).
To accomplish this task I use the following code, where tempArr is an array like the example array provided above.
let currentGroup: string = tempArr[0].createID;
let tempGrouped: any[] = [];
let childGroup: any[] = [];
tempArr.forEach(item => {
if (item.createID !== currentGroup) {
tempGrouped.push(childGroup);
childGroup = [];
currentGroup = item.createID;
}
childGroup.push(item);
});
tempGrouped.push(childGroup);
This code works fine. However, I can't help but believe there must be a more efficient and elegant way given the data to convert an Array of objects into an Array of Arrays containing objects.
UpdateIt is important to note that the createID's are only id's that signify which objects should be grouped together. Therefore, they do not need to be numerically ordered by createID. In addition, the objects do come from the server "grouped" with their sibling objects (same createID) as you can see in the given example array provided.
Your example has all identical IDs adjacent to each other. If that is guaranteed to always be the case, looping though and pushing to a new array is all you need. However if this isn't the case, your solution will fail to group items properly. In that case using a hash table will allow you to still group by ID with same asymptotic complexity.
You can group your objects into a hash table object with keys created from createdID. This will let you group everything efficiently. Then just take the objects from the hash table:
let arr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];
let o = arr.reduce((a, c) => {
(a[c.createID] || (a[c.createID] = [])).push(c)
return a
}, {} )
// o is a an object with createID keys pointing to arrays of grouped objects
// just take the values
console.log(Object.values(o))
Edit based on question edit
Since the objects will already be grouped, there's not a better way than looping through. If you want an option that doesn't add the temp arrays, you can still use reduce(), which is essentially the same as your current solution, but maybe a little more self contained:
let tempArr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];
let r = tempArr.reduce((a, c, i, self) => {
if (i === 0 || self[i-1].createID !== c.createID)
a.push([])
a[a.length - 1].push(c)
return a
}, [])
console.log(r)
Assuming that your array of data is stored into a variable called data:
const result = data.reduce((acc, current) => {
if (!acc.dictionary[current.createID]) {
const createIdArray = [];
acc.dictionary[current.createID] = createIdArray;
acc.array.push(createIdArray);
}
acc.dictionary[current.createID].push(current);
return acc;
}, {array: [], dictionary: {}}).array;
This way, you'll loop only once on data, and it's efficient as we don't use filter or find (which would go through the whole array again and again).
Here's the output:
[
[
{
createID: '1',
},
{
createID: '1',
},
{
createID: '1',
},
],
[
{
createID: '37',
},
{
createID: '37',
},
],
[
{
createID: '2',
},
{
createID: '2',
},
],
[
{
createID: '14',
},
],
];
Here's a running demo: https://stackblitz.com/edit/typescript-phbzug
Summary:
The dictionary is self contained within the reduce function which means that as soon as the reduce is done, it'll be garbage collected
Not relying on any external variables, easier to reason about and IMO a better practice
This solution is more robust (the array doesn't need to be sorted) for ~ the same number of lines as OP's answer
Clean: With the dictionary you know directly what you're accessing and it's really fast
you want to group by createID?
let grouped=tempArray
//first get uniq values
.filter((s,index)=>tempArray.findIndex(f=>f.createID==s.createID)==index)
//then, with the uniq values make a map
.map(seg=>{ //with each uniq value, create an object with two properties
return {
createID:seg.createID, //the key
items:tempArray.filter(s=>s.createID==seg.createID) //An array with the values
}
})
I am looking for an efficient way to replace values within a multidimensional object using Lodash or even vanilla JS.
I have an array with multidimensional objects of unknown depth like (simplified)
objects = [{
id: 1,
view: {
id: 7
}
}, {
id: 2,
view: {
id: 9
},
childs: [{
id: 3,
view: {
id: 3
}
}]
}];
Now I want to replace the value of view of each node with a named import reference stored in a separate object. The references are accessible through the view.id as index of this object. So what I am trying to achieve is something like this
views = {
3: some,
7: random,
9: imports
};
objects = [{
id: 1,
view: views[7]
}, {
...
}];
Well I know how to iterate over a multidimensional object to achieve this manually but since I am working with large objects it would be nice if there would be a cleaner and more performant way using Lodash.
Does anybody have a genius solution?
Since lodash is just a utility layer written in JS, you're unlikely to get any performance gains over vanilla JS from using it.
The function below is probably the fastest way to do what you want: it mutates the supplied objects instead of creating new ones, and does not iterate over every key.
function transform(arr) {
arr.forEach(obj => {
if (obj.hasOwnProperty('view')) obj.view = views[obj.view.id];
if (obj.hasOwnProperty('childs')) transform(obj.childs);
});
}
You can use a recursive _.transform() call to iterate and updated the objects' views:
const fn = o => _.transform(o, (acc, v, k) => {
// if key is view, and it and has an id value replace it with equivalent from views
if(_.eq(k, 'view') && _.has(v, 'id')) acc[k] = _.get(views, v.id, v);
// if it's an object transform it recursively
else if (_.isObject(v)) acc[k] = fn(v);
// assign primitives to accumulator
else acc[k] = v;
});
const objects = [{"id":1,"view":{"id":7}},{"id":2,"view":{"id":9},"childs":[{"id":3,"view":{"id":3}}]}];
const views = {
3: 'some',
7: 'random',
9: 'imports'
};
const result = fn(objects);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
I'm working on a JavaScript function that will assign the enumerable properties of a source object (or objects-- that's important) to the destination object. An additional stipulation is that subsequent sources overwrite the property assignments of previous sources.
I have indeed searched and read many similar questions, but I run into trouble for a couple reasons. To be honest, I've been stuck on it for three days.
Trouble spot 1: The boilerplate provided is just function extend(){} with no parameters. I'm not sure whether or not to add arguments. I'm sure it could be written either way.
Trouble spot 2: I can write code that successfully extends one object to another, but nothing that extends from multiple sources.
So for example, if the function were to be called thusly:
extend({ 'name': 'lola' }, { 'age': 9 }, { 'name': 'mickey' });
The result should be { 'user': 'mickey', 'age': 9 }
Guidance is greatly appreciated.
this seems like a perfect situation for Object.assign,
It's an ES6 feature so check support on your target browsers;
var objects = [{ 'name': 'lola' }, { 'age': 9 }, { 'name': 'mickey' }]
var r = objects.reduce((a,b) => Object.assign(a,b));
console.log(r)
in this snippet I use [].reduce for looping through the array of objects and executing against the accumulator object;
this will modify the first entry of the array..
using the spread operator and assigning to a new object is probably a better solution:
var objects = [{ 'name': 'lola' }, { 'age': 9 }, { 'name': 'mickey' }]
var r = Object.assign({}, ...objects )
console.log(r)
Just for fun lets invent Object.prototype.concat(). Here it goes;
Object.prototype.concat = function(...o){
return o.reduce((p,c) => (Object.keys(c).forEach(k => p[k] = c[k]),p),this);
};
var objects = [{ 'name': 'lola' }, { 'age': 9 }, { 'name': 'mickey' }],
reduced = {}.concat(...objects);
console.log(JSON.stringify(reduced));