I have asked this question before, but it seems the solution doesn't work well.
I have two objects:
var a = [{ x: 0, y: 0, color: "green", value: undefined, weight: 1 }, { x: 0, y: 1, color: "red", value: undefined, weight: 1 }];
var b = [{ x: 0, y: 0, value: 1}, { x: 0, y: 1, value: 3}];
I want to join them into a single object, like this:
var c = [{ x: 0, y: 0, color: "green", value: 1, weight: 1 }, { x: 0, y: 1, color: "red", value: 3, weight: 1 }];
Note: array A will always have 25 entries, while array b not.
The proposed solution was:
var extendedArray = $.extend({}, a, b);
However this makes an array of two entries, where not all values are preserved.
I have also tried the following functions:
var e = $.merge(a, b);
var output = a.concat(b);
function jsonConcat(o1, o2) {
for (var key in o2) {
o1[key] = o2[key];
}
return o1;
}
var c = {};
c = jsonConcat(c, a);
c = jsonConcat(c, b);
Any help or push in the right direction would be very much appreciated!
Edited to reflect that b can be smaller but never larger than a.
function merge(a,b){
//Don't want to mutate a
var result = a.slice();
for(var i = 0; i < b.length; i++){
for (var attrname in b[i]) {
result[i][attrname] = b[i][attrname];
}
}
return result;
}
Code partly taken from accepted answer in: How can I merge properties of two JavaScript objects dynamically?
Assuming that the two arrays have the same length, an in place merge could be something like this:
var a = [{ x: 0, y: 0, color: "green", value: undefined, weight: 1 }, { x: 0, y: 1, color: "red", value: undefined, weight: 1 }];
var b = [{ x: 0, y: 0, value: 1, weight: 1 }, { x: 0, y: 1, value: 3, weight: 1 }];
function merge_objects(o1, o2) {
Object.keys(o2).forEach(
function(key) {
o1[key] = o2[key];
});
}
function merge(a, b) {
if (a.length != b.length) {
throw new Error();
}
for (var ix=0; ix<a.length; ix++) {
merge_objects(a[ix], b[ix]);
}
}
$.extend without the first argument set to true will only merge the "first level" of your objects. It works if your properties are only strings and numbers, but if some properties are objects, it can result in unexpected behaviour.
I think you're looking for $.extend(true,obj1,obj2) .
example
var a = [{ x: 0, y: 0, color: "green", value: undefined, weight: 1 }, { x: 0, y: 1, color: "red", value: undefined, weight: 1 }];
var b = [{ x: 0, y: 0, value: 1, weight: 1 }, { x: 0, y: 1, value: 3, weight: 1 }];
var c = $.extend(true,[],a,b);
console.log(c instanceof Array); // true
see the doc for details
the first argument true tells the method to perform a "deep" copy, recursively.
Related
I have this array of objects in Javascript:
var arrayData = [
{ id: "0", x: -1, y: 38},
{ id: "1", x: -2.7823, y: 43.444},
{ id: "2", x: -1.1654, y: 38.12088},
{ id: "3", x: -1, y: 38},
{ id: "4", x: -2.7823, y: 43.444 },
{ id: "5", x: -1.1654, y: 38.12088},
{ id: "6", x: -1.1654, y: 38.12088},
]
and i'd need to check what objects have the same property x and y and someway store this objects separately so i can access later to them.
i've got this:
var copy = arrayData.slice(0);
// first loop goes over every element
for (var i = 0; i < arrayData.length; i ++) {
// loop over every element in the copy and see if it's the same
for (var w = i + 1; w < copy.length; w++) {
if ((arrayFotos[i].x === copy[w].x) && (arrayFotos[i].y === copy[w].y)) {
if(!duplicates.includes(arrayFotos[i].id))
duplicates.push(arrayFotos[i].id);
if(!duplicates.includes(copy[w].id))
duplicates.push(copy[w].id);
}
}
This returns me an array with all ids of the objects which
have repeated x and y properties but i need a way to store
them separately so i have: [0,3][1,4][2,5,6]. Is there any
way to do it? Thanks if u want to help me.
You could take a hash table and collect id for same coordinates.
var arrayData = [{ id: "0", x: -1, y: 38 }, { id: "1", x: -2.7823, y: 43.444 }, { id: "2", x: -1.1654, y: 38.12088 }, { id: "3", x: -1, y: 38 }, { id: "4", x: -2.7823, y: 43.444 }, { id: "5", x: -1.1654, y: 38.12088 }, { id: "6", x: -1.1654, y: 38.12088 }],
duplicates
hashtable = {};
for (var i = 0; i < arrayData.length; i++) {
let key = ['x', 'y'].map(k => arrayData[i][k]).join('|');
hashtable[key] = hashtable[key] || [];
hashtable[key].push(arrayData[i].id);
}
duplicates = Object.values(hashtable);
console.log(duplicates);
I have a nested object and I want to flatten/map it into a single-layered, table-like object.
[{
a: 1,
b: 2,
c: [{
x: 10,
y: 20
}, {
x: 30,
y: 40
}]
}, {
a: 3,
b: 4,
c: [{
x: 50,
y: 60
}, {
x: 70,
y: 80
}]
}]
From that, I want to get something like this:
[{
a: 1,
b: 2,
x: 10,
y: 20
}, {
a: 1,
b: 2,
x: 30,
y: 40
}, {
a: 3,
b: 4,
x: 50,
y: 60
}, {
a: 3,
b: 4,
x: 70,
y: 80
}]
Sure, I could simply iterate over the object with two for loops and put the result info a separate array, but I wonder, if there is a simpler solution. I already tried to play around with flatMap. It works, if I only want the c portion of my nested object, but I don't know how to map a and b to this object.
As some of you asked for some working code, this should do it (untested):
let result = [];
for (const outer of myObj)
for (const inner of outer.c)
result.push({a: outer.a, b: outer.b, x: inner.x, y: inner.y});
The question is, if there is a functional one-liner or even another, better approach. In reality, my object consists of four layers and the nested for loops become messy quite fast.
You may use flatMap method alongwith map on property 'c':
var input = [{ a: 1, b: 2, c: [{ x: 10, y: 20 }, { x: 30, y: 40 }] }, { a: 3, b: 4, c: [{ x: 50, y: 60 }, { x: 70, y: 80 }] }];
const output = input.flatMap(obj =>
obj.c.map(arr => ({a: obj.a, b: obj.b, x: arr.x, y: arr.y}))
);
console.log(output);
Ideally a solution would require something to tell how far down to start classing the object as been a full object, a simple solution is just to pass the level you want. If you don't want to pass the level, you could do a check and if none of the properties have array's, then you would class this as a complete record, but of course that logic is something you would need to confirm.
If you want a generic version that works with multiple levels were you pass the level & using recursion you could do something like this ->
const a=[{a:1,b:2,c:[{x:10,y:20},{x:30,y:40}]},{a:3,b:4,c:[{x:50,y:60},{x:70,y:80}]}];
function flattern(a, lvl) {
const r = [];
function flat(a, l, o) {
for (const aa of a) {
o = {...o};
for (const [k, v] of Object.entries(aa)) {
if (Array.isArray(v) && l < lvl) flat(v, l + 1, o);
else o[k] = v;
}
if (l === lvl) r.push(o);
}
}
flat(a, 1);
return r;
}
console.log(flattern(a, 2));
//console.log(flattern(a, 1));
A flatMap solution would look like this:
const result = myObj.flatMap(outer =>
outer.c.map(inner =>
({a: outer.a, b: outer.b, x: inner.x, y: inner.y})
)
);
Of course, if your object has multiple layers, not just two, and possibly even multiple or unknown properties that have such a nesting, you should try to implement a recursive solution. Or an iterative one, where you loop over an array of property names (for your example case, ["c"]) and apply the flattening level by level.
One of the solution using reduce is:
const list = [{
a: 1,
b: 2,
c: [{
x: 10,
y: 20
}, {
x: 30,
y: 40
}]
}, {
a: 3,
b: 4,
c: [{
x: 50,
y: 60
}, {
x: 70,
y: 80
}]
}]
const flatten = (arr) => {
return arr.reduce((flattened, item) => {
return [
...flattened,
...item.c.reduce((flattenedItem, i) => {
return [
...flattenedItem,
{
a: item.a,
b: item.b,
x: i.x,
y: i.y
}
]
}, [])
]
}, [])
}
console.log(flatten(list));
Using two reducers to flatten your structure
const input = [{
a: 1,
b: 2,
c: [{
x: 10,
y: 20
}, {
x: 30,
y: 40
}]
}, {
a: 3,
b: 4,
c: [{
x: 50,
y: 60
}, {
x: 70,
y: 80
}]
}]
const result = input.reduce((acc_0, x) => {
return [...acc_0, ...x.c.reduce((acc_1, y) => {
const obj = {
a: x.a,
b: x.b,
x: y.x,
y: y.y
}
acc_1.push(obj);
return acc_1;
}, [])]
}, []);
console.log(result)
im trying to normalize some data sitting in an array of objects.
[
{id: 1, number: 10, x: 0.3, y: 0.4, …}
{id: 2, number: 5, x: 0.5, y: 0.2, …}
{...}
{...}
{...}
]
I want to map the x and y entry's on a new value between 0 - 1250. So I get the following Array of Objects
[
{id: 1, number: 10, x: 375, y: 500, …}
{id: 2, number: 5, x: 625, y: 250, …}
{...}
{...}
{...}
]
Whats the best Practice for that?
Best,
Chris
You can use Array.map
const arr = [
{id: 1, number: 10, x: 0.3, y: 0.4},
{id: 2, number: 5, x: 0.5, y: 0.2}
];
// Use Array.map to iterate
const arr1 = arr.map(ob => {
ob.x*=1250;
ob.y*=1250;
return ob;
});
console.log(arr1);
Some thing like this with map method.
const arr = [
{id: 1, number: 10, x: 0.3, y: 0.4},
{id: 2, number: 5, x: 0.5, y: 0.2},
];
const res = arr.map(({x, y, ...rest}) => ({...rest, x: x * 1250, y: y * 1250 }));
console.log(res)
Assuming arr is your array of object. You can use map which returns new modified array.
let arr = [
{
id: 1, number: 10, x: 0.3, y: 0.4,
},
{
id: 2, number: 5, x: 0.5, y: 0.2
}
];
const normalize = (obj) => {
x = obj.x * 1250;
y = obj.y * 1250;
return {...obj, x, y};
// If you're only using mutating then
// above lines can be
// obj.x *= 1250;
// obj.y *= 1250;
// return obj;
}
// Not mutating array, output new array
const nonMutating = (arr) => {
let newRes = [];
arr.forEach(a => {
newRes.push(normalize(a));
});
return newRes;
}
console.log(nonMutating(arr));
console.log("\n");
// Mutating input array
const mutating = (arr) => {
return arr.map(a => normalize(a));
}
console.log(mutating(arr));
Assume I have the following array of objects.
data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
]
what I need is to summarize the frequency of identical object in the array. The output will look like:
summary = [
{ x: 1, y: 1, f: 3 },
{ x: 1, y: 2, f: 1 },
{ x: 2, y: 2, f: 2 },
{ x: 3, y: 3, f: 1 }
]
For now I have this code
const summary = data.map((item, index, array) => {
return { x: item.x, y: item.y, f: array.filter(i => i === item).length };
});
But I suppose I can do better by using reduce or includes. Any ideas?
Reduce into an object whose keys uniquely represent an object, whose values are the object (with x, y, and f properties). On each iteration, increment the appropriate key's f property, or create the key on the accumulator if it doesn't exist yet:
const data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
const countObj = data.reduce((a, obj) => {
const objString = obj.x + '_' + obj.y;
if (!a[objString]) {
a[objString] = { ...obj, f: 1 };
} else {
a[objString].f++;
}
return a;
}, {});
const output = Object.values(countObj);
console.log(output);
Don't use map - you're better off using reduce like so:
const summary = Object.values(data.reduce((a, { x, y }) => {
a[`${x}-${y}`] = a[`${x}-${y}`] || { x, y, f: 0 };
a[`${x}-${y}`].f++;
return a;
}, {}));
Object.values(data.reduce((sum, i) => {
i_str = JSON.stringify(i); // objects can't be keys
sum[i_str] = Object.assign({}, i, {f: sum[i_str] ? sum[i_str].f+1 : 1});
return sum;
}, {}));
Note:
This snippet will work on an array of any arbitrary objects, as long as they are stringifiable.
Results are not ordered, since object keys aren’t ordered. If this is an issue, sort at will.
What you’re doing, is counting the times an object exists in an array. You probably want results external to the objects, as opposed to embedded in them. Something along these lines might be more manageable, returning a mapping of descriptions of the objects to a count:
data.reduce((sum, i) => {
i_str = JSON.stringify(i); // objects can't be keys
sum[i_str] = sum[i_str] ? sum[i_str]+1 : 1;
return sum;
}, {});
A simple solution based on Array#reduce would be as detailed below:
const data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
const summary = data.reduce((frequencySummary, item) => {
/* Find a match for current item in current list of frequency summaries */
const itemMatch = frequencySummary.find(i => i.x === item.x && i.y === item.y)
if(!itemMatch) {
/* If no match found, add a new item with inital frequency of 1 to the result */
frequencySummary.push({ ...item, f : 1 });
}
else {
/* If match found, increment the frequency count of that match */
itemMatch.f ++;
}
return frequencySummary;
}, []);
console.log(summary)
I know using reduce is probably better, but I tend to use forEach and findIndex for better readability.
var data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
var summary = [];
data.forEach(function(d){
var idx = summary.findIndex(function(i){
return i.x === d.x && i.y === d.y;
});
if(idx < 0){
var sum = Object.assign({}, d);
sum.f = 1;
summary.push(sum);
} else {
summary[idx].f = summary[idx].f + 1;
}
});
console.log(summary);
Create nested objects. The outer object uses x values as keys, the nested object contains y values as keys, and the values are the frequencies.
data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
const nested = data.reduce((a, {x, y}) => {
a[x] = a[x] || {};
a[x][y] = a[x][y] ? a[x][y] + 1 : 1
return a;
}, {});
const summary = [];
Object.keys(nested).forEach(x => Object.keys(nested[x]).forEach(y => summary.push({x, y, f: nested[x][y]})));
console.log(summary);
You can use reduce and Map, club the use x and y as key, on every iteration check if the same key is already present on Map than just increase f count by 1 if not than set it to 1
const data = [{ x: 1, y: 1 },{ x: 2, y: 2 },{ x: 3, y: 3 },{ x: 2, y: 2 },{ x: 1, y: 1 },{ x: 1, y: 2 },{ x: 1, y: 1 }];
const countObj = data.reduce((a, obj) => {
const objString = obj.x + '_' + obj.y;
let value = a.get(objString) || obj
let f = value && value.f || 0
a.set(objString, { ...value, f: f+1 })
return a;
}, new Map());
console.log([...countObj.values()]);
with d3.csv I am reading a csv that is then stored such that
console.log(data[0]) returns
Object {username: "mark", y: 0, x: 0, value: 0}
Now I want to extract from data only the first occurence of each username.
In python pandas I would have used data.drop_duplicates(columns='username')
EDIT:
Consider the following example:
var X = [{username: "a", y: 0, x: 0, value: 0},
{username: "b", y: 0, x: 0, value: 0},
{username: "a", y: 1, x: 0, value: 0}
{username: "c", y: 0, x: 0, value: 0}
{username: "b", y: 1, x: 0, value: 0}]
it should return only:
var Y = [{username: "a", y: 0, x: 0, value: 0},
{username: "b", y: 0, x: 0, value: 0},
{username: "c", y: 0, x: 0, value: 0}
i.e. f(X) = Y
Since you want to find the first occurrence of each username, this question has nothing to do with D3, and can be solved with plain JavaScript.
There are several ways for doing this. A simple one is using Array.prototype.find():
The find() method returns the value of the first element in the array that satisfies the provided testing function. (emphasis mine)
Here is a demo. First, we get the unique values for usernames as an array:
var users = [...new Set(data.map(function(d) {
return d.username
}))];
Then, we use a map with filter to get the first occurences:
var firstOccurrence = users.map(function(d) {
return data.find(function(e) {
return e.username === d
})
});
Check the demo:
var data = [{
username: "a",
y: 0,
x: 0,
value: 0
}, {
username: "b",
y: 0,
x: 0,
value: 0
}, {
username: "a",
y: 1,
x: 0,
value: 0
}, {
username: "c",
y: 0,
x: 0,
value: 0
}, {
username: "b",
y: 1,
x: 0,
value: 0
}];
var users = [...new Set(data.map(function(d) {
return d.username
}))]
var firstOccurrence = users.map(function(d) {
return data.find(function(e) {
return e.username === d
})
});
console.log(firstOccurrence)
You can employ a Map to keep track of what usernames have already been processed. Using the username as the key ensures the uniqueness of this value. To get the first occurence of this value instead of the last one, you further have to use Map.prototype.has() to check if each particular value has already been added.
var X = [
{username: "a", y: 0, x: 0, value: 0},
{username: "b", y: 0, x: 0, value: 0},
{username: "a", y: 1, x: 0, value: 0},
{username: "c", y: 0, x: 0, value: 0},
{username: "b", y: 1, x: 0, value: 0}
];
var uniqX = new Map(); // Construct map of unique usernames
X.forEach(x => {
if (!uniqX.has(x.username)) uniqX.set(x.username, x); // Only add, if not present
});
var Y = uniqX.values(); // Retrieve the result set
console.log(...Y);
Or, rewritten for the sake of mathematical aesthetics:
var X = [
{username: "a", y: 0, x: 0, value: 0},
{username: "b", y: 0, x: 0, value: 0},
{username: "a", y: 1, x: 0, value: 0},
{username: "c", y: 0, x: 0, value: 0},
{username: "b", y: 1, x: 0, value: 0}
];
function f(_) {
let uniqX = new Set();
return _.filter(({username}) => !uniqX.has(username) && uniqX.add(username));
}
var Y = f(X);
console.log(...Y);