I've come up against this issue in multiple contexts and languages and I always been able to work around it but I'd like to finally figure out a proper pattern to handle this. It comes from joining SQL tables. Usually I would make two calls, one for items and one for comments but I know there's a way to get it all in one call and then flatten the result.
What I'd like to do is to take an array that looks like this:
[
{
itemId: 1,
comments: {
commentId: 1
}
},
{
itemId: 1,
comments: {
commentId: 2
}
},
{
itemId: 2,
comments: {
commentId: 3
}
}
]
And turn it into this:
[
{
itemId: 1,
comments: [
{
commentId: 1
},
{
commentId: 2
}
]
},
{
itemId: 2,
comments: [
{
commentId: 3
}
]
}
]
The following should work for you:
function combine(arr) {
var newObj = {};
// combine the comments
for (var i=0; i < arr.length; i++) {
if (newObj[arr[i].itemId]) {
newObj[arr[i].itemId].push(arr[i].comments);
} else {
newObj[arr[i].itemId] = [arr[i].comments];
}
}
// make the list
var keys = Object.keys(newObj);
return keys.map(function(key){return {itemId: key, comments: newObj[key]} })
}
Also you can use filter():
function combine(src) {
var dict = {};
return src.filter(function(item) {
if (!dict[item.itemId]) {
item.comments = [ item.comments ];
dict[item.itemId] = item;
return true;
} else {
dict[item.itemId].comments.push(item.comments);
return false;
}
});
}
Related
I have data that is array of arrays. It is something like:
[
[
{
id: 1,
itemName: 'xxx',
...
},
{
id: 1,
itemName: 'yyy',
...
},
...
],
[
{
id: 2,
itemName: 'aaa',
...
},
{
id: 2,
itemName: 'kkk',
...
},
...
],
[
{
id: 3,
itemName: 'kkk',
...
},
{
id: 3,
itemName: 'yyy',
...
},
...
]
]
I need to iterate them while looking for itemName and when i find it put its ID as a key to state value (React). The issue is, that itemName can be the same in multiple arrays but I want to setState just for the first occurance. Here is what I have:
const handle = itemId => {
arrays.forEach(arrItem => {
arrItem.forEach(item => {
if (item.itemName === itemId) {
if (!Object.keys(this.state.cartons).includes(item.id)) {
this.setState(prevState => ({
...prevState,
cartons: {
...prevState.cartons,
[item.id]: {
id: item.id,
items: arrItem,
itemsFound: [item.itemName],
},
},
}));
}
}
});
});
};
After calling handle('yyy') my cartons from state should be:
{
1: {
...
}
}
But it is now:
{
1: {
...
}.
3: {
...
}
}
You can't use a forEach loop for this. you need to use a standard for loop to "break" out . From the docs:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool.
Early termination may be accomplished with:
A simple for loop
A for...of / for...in loops
Array.prototype.every()
Array.prototype.some()
Array.prototype.find()
Array.prototype.findIndex()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
const handle = itemId => {
for(let i = 0; i < arrays.length; i++) {
let arrItem = arrays[i];
for(let j = 0; j < arrItem.length; j++) {
let item = arrItem[j];
if (item.itemName === itemId) {
if (!Object.keys(this.state.cartons).includes(item.id)) {
this.setState(prevState => ({
...prevState,
cartons: {
...prevState.cartons,
[item.id]: {
id: item.id,
items: arrItem,
itemsFound: [itemName],
},
},
}));
}
}
};
};
};
So use a for loop like above, but add your "break" statement whether it needs to go based upon your logic
This is an example dataset:
const largeObject = {
"4249":{
"2018-07-25":[
{
"start":"2016-07-25T14:09:20.453Z",
"end":"2016-07-25T14:17:52.147Z"
}
]
},
"9939":{
"2018-07-25":[
{
"start":"2016-07-25T00:50:08.768Z",
"end":"2016-07-25T00:53:16.514Z"
}
]
},
"2149":{
"2018-07-25":[
{
"start":"2016-07-25T00:42:02.569Z",
"end":"2016-07-25T00:43:07.689Z"
}
]
},
"6929":{
"2018-07-24":[
{
"start":"2016-07-24T00:44:30.479Z",
"end":"2016-07-24T00:46:41.315Z"
}
]
},
"7930":{
"2018-07-24":[
{
"start":"2016-07-24T00:39:44.152Z",
"end":"2016-07-24T00:44:05.420Z"
}
]
},
"4796":{
"2018-07-22":[
{
"start":"2016-07-22T12:48:56.169Z",
"end":"2016-07-22T13:38:28.136Z"
}
]
}
}
I am trying to find the most efficient way to get to something like this:
const filteredObject = {
"2018-07-25": [
{
"start":"2016-07-25T14:09:20.453Z",
"end":"2016-07-25T14:17:52.147Z"
}, {
"start":"2016-07-25T00:50:08.768Z",
"end":"2016-07-25T00:53:16.514Z"
},
{
"start":"2016-07-25T00:42:02.569Z",
"end":"2016-07-25T00:43:07.689Z"
}
],
"2018-07-24": [
{
"start":"2016-07-24T00:44:30.479Z",
"end":"2016-07-24T00:46:41.315Z"
},
{
"start":"2016-07-24T00:39:44.152Z",
"end":"2016-07-24T00:44:05.420Z"
}
],
"2018-07-22": [
{
"start":"2016-07-22T12:48:56.169Z",
"end":"2016-07-22T13:38:28.136Z"
}
]
};
So far, I have done:
const filteredObject = {}
const newArr = []
for(key in largeObject){
console.log(largeObject[key])
}
And that gets rid of the random string, but still gets me this:
{ '2018-07-24':
[ { start: '2016-07-24T00:44:30.479Z',
end: '2016-07-24T00:46:41.315Z' } ] }
{ '2018-07-25':
[ { start: '2016-07-25T00:50:08.768Z',
end: '2016-07-25T00:53:16.514Z' } ] }
{ '2018-07-25':
[ { start: '2016-07-25T14:09:20.453Z',
end: '2016-07-25T14:17:52.147Z' } ] }
{ '2018-07-24':
[ { start: '2016-07-24T00:39:44.152Z',
end: '2016-07-24T00:44:05.420Z' } ] }
{ '2018-07-22':
[ { start: '2016-07-22T12:48:56.169Z',
end: '2016-07-22T13:38:28.136Z' } ] }
{ '2018-07-25':
[ { start: '2016-07-25T00:42:02.569Z',
end: '2016-07-25T00:43:07.689Z' } ] }
This is far as I've gotten. I still need to find a way to merge all the arrays with the same key values. It seems like I would need to iterate over this object, keep the date as the key, and push all of the arrays associated with that date-key into one array.
What would be the best way to handle something like this? I also want to do this as efficient as possible without having to iterate over the entire large object each time I check for the date-key and/or push the start/end object into an array of it's own.
You can start with Object.values() of your original data. This will give you an array of the values without the first level keys over which you can reduce(). Then for each of those break it into a key and value. Add the key with an array value if it's not already there and merge in the data.
const largeObject = { "4249":{ "2018-07-25":[ { "start":"2016-07-25T14:09:20.453Z","end":"2016-07-25T14:17:52.147Z"}]},"9939":{ "2018-07-25":[ { "start":"2016-07-25T00:50:08.768Z","end":"2016-07-25T00:53:16.514Z"}]},"2149":{ "2018-07-25":[ { "start":"2016-07-25T00:42:02.569Z","end":"2016-07-25T00:43:07.689Z"}]},"6929":{ "2018-07-24":[ { "start":"2016-07-24T00:44:30.479Z","end":"2016-07-24T00:46:41.315Z"}]},"7930":{ "2018-07-24":[ { "start":"2016-07-24T00:39:44.152Z","end":"2016-07-24T00:44:05.420Z"}]},"4796":{ "2018-07-22":[ { "start":"2016-07-22T12:48:56.169Z","end":"2016-07-22T13:38:28.136Z"}]}}
let filtered = Object.values(largeObject).reduce((a, c) => {
Object.entries(c).forEach(([k, v]) => {
(a[k] || (a[k] = [])).push(...v)
})
return a
},{})
console.log(filtered)
See jsfiddle here: https://jsfiddle.net/remenyLx/2/
I have data that contains objects that each have an array of images. I want only the first image of each object.
var data1 = [
{
id: 1,
images: [
{ name: '1a' },
{ name: '1b' }
]
},
{
id: 2,
images: [
{ name: '2a' },
{ name: '2b' }
]
},
{
id: 3
},
{
id: 4,
images: []
}
];
var filtered = [];
var b = data1.forEach((element, index, array) => {
if(element.images && element.images.length)
filtered.push(element.images[0].name);
});
console.log(filtered);
The output needs to be flat:
['1a', '2a']
How can I make this prettier?
I'm not too familiar with JS map, reduce and filter and I think those would make my code more sensible; the forEach feels unnecessary.
First you can filter out elements without proper images property and then map it to new array:
const filtered = data1
.filter(e => e.images && e.images.length)
.map(e => e.images[0].name)
To do this in one loop you can use reduce function:
const filtered = data1.reduce((r, e) => {
if (e.images && e.images.length) {
r.push(e.images[0].name)
}
return r
}, [])
You can use reduce() to return this result.
var data1 = [{
id: 1,
images: [{
name: '1a'
}, {
name: '1b'
}]
}, {
id: 2,
images: [{
name: '2a'
}, {
name: '2b'
}]
}, {
id: 3
}, {
id: 4,
images: []
}];
var result = data1.reduce(function(r, e) {
if (e.hasOwnProperty('images') && e.images.length) r.push(e.images[0].name);
return r;
}, [])
console.log(result);
All answers are creating NEW arrays before projecting the final result : (filter and map creates a new array each) so basically it's creating twice.
Another approach is only to yield expected values :
Using iterator functions
function* foo(g)
{
for (let i = 0; i < g.length; i++)
{
if (g[i]['images'] && g[i]["images"].length)
yield g[i]['images'][0]["name"];
}
}
var iterator = foo(data1) ;
var result = iterator.next();
while (!result.done)
{
console.log(result.value)
result = iterator.next();
}
This will not create any additional array and only return the expected values !
However if you must return an array , rather than to do something with the actual values , then use other solutions suggested here.
https://jsfiddle.net/remenyLx/7/
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.
I have a data structure that looks like this:
var someDataStructure = [
{
opts: {_id:1}
},
{
opts: {_id: 2},
children: [
{
opts: {_id: 3},
children: [
{
opts: {_id: 4}
}
]
}
]
},
{
opts: {_id: 5}
},
{
opts: {_id: 6},
children: [
{
opts: {_id: 7},
children: [
{
opts: {_id: 8}
}
]
}
]
}
];
That's an array of objects, all with an opts property, and an optional children property. If it exists the children property will be an array of the same sort of objects.
Given any opts._id, I need to find the _id of all parent objects. The _id's I give here are only sequential for convenience. You may not assume they are sequential integers
This project is using both jquery and lodash so both of those libraries are available for use.
Example desired output:
Given 4, return [2, 3].
Given 3, return [2].
Given 8, return [6, 7].
Given 7, return [6].
I have no problem recursing in and finding the given _id. However, I'm feeling dumb and stuck on maintaining the array of parents.
A solution returning found status and parents if found.
function getParentsHelper(tree, id, parents) {
if (tree.opts._id == id) {
return {
found: true,
parents: parents
};
}
var result = {
found: false,
}
if (tree.children) {
$.each(tree.children, function(index, subtree) {
var maybeParents = $.merge([], parents);
if (tree.opts._id != undefined) {
maybeParents.push(tree.opts._id);
}
var maybeResult = getParentsHelper(subtree, id, maybeParents);
if (maybeResult.found) {
result = maybeResult;
return false;
}
});
}
return result;
}
function getParents(data, id) {
var tree = {
opts: { },
children: data
}
return getParentsHelper(tree, id, []);
}
Usage example:
console.log(getParents(someDataStructure, 4).parents);
console.log(getParents(someDataStructure, 3).parents);
console.log(getParents(someDataStructure, 8).parents);
console.log(getParents(someDataStructure, 7).parents);
for one children this works:
function writeParent(id, arr) {
var ct = 0;
var found = false;
var parentsLine = [];
arr.some(function (e){
parentsLine = []
for (var curr = e; curr.children != null; curr = curr.children[0]) {
if (id == curr.opts._id) {
found = true;
return true;
}
parentsLine.push(curr.opts._id)
}
if (id == curr.opts._id) {
found = true;
return true;
}
})
if (found) {
return parentsLine;
} else {
return "ERR: elm not found";
}
}
see http://jsfiddle.net/alemarch/ufrLpLfx/11/