Related
I have an array of object like that
const object = [
{name: 'One', data: [{x:1648741414000 , y:50},{x:1648741475000, y:60}, {x:1648741536000, y:70}]},
{name: 'Two', data: [{x:1648741403000, y:50},{x:1648741414000 , y:60}, {x:1648741525000, y:70}]},{x:1648741476000, y:80}
{name: 'Three', data: [{x:1648741422000, y:50},{x:1648741414000 , y:60},{x:1648741475000, y:70}, {x:1648741536000, y:90}]}
]
I need to map my array to have only x data values that match the others object
const result= [
{name: 'One', data: [{x:1648741414000 , y:50},{x:1648741475000, y:60},{x:1648741536000, y:70}]},
{name: 'Two', data: [{x:1648741414000 , y:60}, {x:1648741535000, y:70},{x:1648741476000, y:80}]},
{name: 'Three', data: [{x:1648741414000 , y:60},{x:1648741475000, y:70}, {x:1648741536000, y:90}]}
]
Thanks for your help
You could get common x values with their indices and slice the arrays with the found index.
const
array = [{ name: 'One', data: [{ x: 1648741414000, y: 50 }, { x: 1648741475000, y: 60 }, { x: 1648741536000, y: 70 }] }, { name: 'Two', data: [{ x: 1648741403000, y: 50 }, { x: 1648741414000, y: 60 }, { x: 1648741525000, y: 70 }, { x: 1648741476000, y: 80 }] }, { name: 'Three', data: [{ x: 1648741422000, y: 50 }, { x: 1648741414000, y: 60 }, { x: 1648741475000, y: 70 }, { x: 1648741536000, y: 900 }] }],
indices = Object
.values(array.reduce((r, { data }, _, { length }) => {
data.some((o, i) => (r[o.x] ??= []).push(i) === length);
return r;
}, {}))
.find(({ length }) => length === array.length),
result = array.map(({ name, data }, i) =>
({ name, data: data.slice(indices[i]) })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A bit longer code.
const
array = [{ name: 'One', data: [{ x: 1648741414000, y: 50 }, { x: 1648741475000, y: 60 }, { x: 1648741536000, y: 70 }] }, { name: 'Two', data: [{ x: 1648741403000, y: 50 }, { x: 1648741414000, y: 60 }, { x: 1648741525000, y: 70 }, { x: 1648741476000, y: 80 }] }, { name: 'Three', data: [{ x: 1648741422000, y: 50 }, { x: 1648741414000, y: 60 }, { x: 1648741475000, y: 70 }, { x: 1648741536000, y: 900 }] }],
length = array.length,
hashes = {},
result = [];
let indices;
outer: for (const { data } of array) {
for (let i = 0; i < data.length; i++) {
const { x } = data[i];
if (!hashes[x]) hashes[x] = [];
hashes[x].push(i);
if (hashes[x].length === length) {
indices = hashes[x];
break outer;
}
}
}
for (let i = 0; i < length; i++) {
const { name, data } = array[i];
result.push({ name, data: data.slice(indices[i]) });
}
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
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);
I have an array like this.
var nodes = [{ID:"101", x:100, y:200}
,{ID:"102", x:200, y:200}
,{ID:"103", x:300, y:300}
,{ID:"104", x:200, y:300}];
I'd like to have a function which takes node's ID as input and return its (x,y).
For example, the function coordinates(103)should read the array (nodes) and return x = 300, y = 300 when it's called. Any pointer is appreciated. Thanks :)
This is what I have so far. It works but I'd like to know neater and tidier methods.
function coordinates(id){
for (var i=0 in nodes){
if(nodes[i].ID == id){
return { x: nodes[i].x, y: nodes[i].y};
}
}
}
console.log(coordinates(102));
basically you're looking at something like this
var f = function(id){
var match = nodes.filter(function(d){
return d.ID === id;
})
return match && match.length && {x: match[0].x, y:match[0].y}
|| {x: undefined, y: undefined};
};
then f('101') outputs {x: 100, y:200} and if cannot find a match then it will output {x: undefined, y: undefined}
You can use .filter, like so
var nodes = [{
ID: "101",
x: 100,
y: 200
}, {
ID: "102",
x: 200,
y: 200
}, {
ID: "103",
x: 300,
y: 300
}, {
ID: "104",
x: 200,
y: 300
}];
function coordinates(nodes, id) {
var result = nodes.filter(function (el) {
return +el.ID === id;
});
if (result && result.length) {
result = result[0];
return {
x: result.x,
y: result.y
};
}
return null;
}
console.log(coordinates(nodes, 103));
See comments inline:
Demo
var nodes = [{
ID: "101",
x: 100,
y: 200
}, {
ID: "102",
x: 200,
y: 200
}, {
ID: "103",
x: 300,
y: 300
}, {
ID: "104",
x: 200,
y: 300
}];
var noOfCord = nodes.length;
var coordinates = function(id) {
for (var i = 0; i < noOfCord; i++) {
if (nodes[i].ID == id) {
return {
x: nodes[i].x,
y: nodes[i].y
};
}
}
}
document.write(coordinates(103).x + ', ' + coordinates(103).y);
Using array filter, Try:
function coordinates(id){
return nodes.filter(function(e){ return e.ID == id })[0]
}
var nodes=[{ID:"101",x:100,y:200},{ID:"102",x:200,y:200},{ID:"103",x:300,y:300},{ID:"104",x:200,y:300}];
var result = coordinates("103");
document.write("<pre>" + JSON.stringify(result, null, 3));
Brilliant solutions with concrete JavaScript have already been proposed by people here. So I propose another alternative using underscore.js, just in case you're curious.
function coordinates(id){
var n = _.findWhere(nodes, {ID: id});
return {x: n.x, y: n.y }
}
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.