im trying to add counting number for duplicate in JS.
and i am completely stack in this case below.
i need to compare objects with two value (x, y) and if there are same values of (x, y) add count 1 on new objects.
is there any way to convert data to newData such as below?
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const newData = [
{x: 1, y:1 ,count:2}
{x: 2, y:2 ,count:1}
]
use .reduce() function
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const output = data.reduce((acc, curr) => {
curr.count = 1;
const exists = acc.find(o => o.x === curr.x && o.y === curr.y);
exists ? exists.count++ : acc.push(({ x, y, count } = curr));
return acc;
}, []);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way of doing so, is to create a map with the x and y values, and increment the count accordingly, then convert the map into an array:
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const makeXYMap = (data) => data.reduce((acc, cur) => {
const { x, y } = cur;
const entry = acc[`${x}_${y}`];
if (entry) {
acc[`${x}_${y}`] = {...entry, count: entry.count + 1};
} else {
acc[`${x}_${y}`] = { x, y, count: 1 };
}
return acc;
}, {});
const makeArray = (XYMap) => Object.values(XYMap);
console.log(makeArray(makeXYMap(data)));
Note that complexity wise, this solution is a O(N).
https://jsfiddle.net/9o35neg7/
const data = [
{ id: 1, x: 1, y: 1 },
{ id: 2, x: 2, y: 2 },
{ id: 3, x: 1, y: 1 },
// .. so on ..
];
const countedData = data.reduce((acc, { x, y }, index, array) => {
acc[`x${x}y${y}`] = {
x,
y,
count: (acc[`x${x}y${y}`] ? acc[`x${x}y${y}`].count : 0) + 1
};
return index === (array.length - 1) ? Object.values(acc) : acc;
}, {});
console.log(countedData);
Use forEach and build an object with key (made of x, y) and values (aggregate count). Get the Object.values to get the results as array.
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const counts = (arr, res = {}) => {
arr.forEach(({x , y}) =>
res[`${x}-${y}`] = { x, y, count: (res[`${x}-${y}`]?.count ?? 0) + 1 })
return Object.values(res);
}
console.log(counts(data))
Related
I have a problem when I try to calculate the position of each ID so I can spread them out on canvas hierarchically.
It can be a tree or many trees (I guess it called forest).
cannot be cycles.
boxes need to be? 240 X 100.
I start from the connections I get: connectionsArr.
Inside the function graphWithTopLeft is the way I calculate the positions for each ID , in my example some of the ID's on top of each other & spacing between them not as I wish for.
In the end I tried to get something like this for all cases:
I know these algorithm are nontrivial, but I don't want to use an external library.
Thanks in advance to those who helped and will help.
Code example: codesandbox & also here.
const connectionsArr = [
{ sourceId: 1, targetId: 2 },
{ sourceId: 1, targetId: 3 },
{ sourceId: 1, targetId: 4 },
{ sourceId: 2, targetId: 5 },
{ sourceId: 2, targetId: 6 },
{ sourceId: 2, targetId: 7 },
{ sourceId: 2, targetId: 8 },
{ sourceId: 3, targetId: 10 },
{ sourceId: 3, targetId: 9 },
{ sourceId: 4, targetId: 11 },
{ sourceId: 4, targetId: 12 },
{ sourceId: 11, targetId: 12 },
{ sourceId: 13, targetId: 12 }
];
const WIDTH = 200;
//Find the roots id
function findParents(connections) {
const parents = [];
const notParents = [];
connections.forEach((con1) => {
const t = connections.filter((cf) => cf.targetId === con1.sourceId);
t.forEach((t2) => notParents.push(t2.targetId));
});
const allIds = connections.map((con) => con.sourceId);
const arrayWithDuplicate = allIds.filter((val) => !notParents.includes(val));
arrayWithDuplicate.forEach((a, index) => {
if (!parents.find((p) => a === p)) {
parents.push(a);
}
});
return parents;
}
//return arrays of objects (object is like a tree)
function getTrees(flatList) {
const graph = [];
flatList.forEach((item) => {
if (!graph[item.sourceId]) {
graph[item.sourceId] = {
id: item.sourceId,
children: []
};
}
if (!graph[item.targetId]) {
graph[item.targetId] = {
id: item.targetId,
children: []
};
}
graph[item.sourceId].children.push(graph[item.targetId]);
});
return graph;
}
//function that return the number of the vertex all the way to the id given
function getAllIds(connections, id) {
const result = [];
connections.forEach((connection) => {
if (connection.sourceId === id) {
result.push(connection.targetId);
result.push(...getAllIds(connections, connection.targetId));
}
});
return result;
}
//function that return tree with levels
function updateLevels(trees, level) {
for (let i = 0; i < trees.length; i++) {
if (trees[i].children && trees[i].children.length) {
updateLevels(trees[i].children, level + 1) //next level for children
}
trees[i].level = level;
}
return trees;
}
//get the max children in the level tree
function getMaxChildren(tree) {
let max = 0;
let id = 1;
const getMax = (node) => {
if (node.children) {
if (node.children.length > max) {
max = node.children.length;
id = node.id;
}
node.children.forEach((child) => {
getMax(child);
});
}
};
getMax(tree);
return { max, id };
}
const parents = findParents(connectionsArr);
// console.log(parents);
const filterArray = getTrees(connectionsArr).filter(
(f, index) =>
f.id === parents[0] || f.id === parents[1] || f.id === parents[2]
);
const updated = updateLevels(filterArray, 0);
// console.log( JSON.stringify(updated, null, " ") );
//get the tree hight
function getDepth(jsonTree) {
let depth = 0;
const recurse = (obj, currentDepth) => {
if (obj.children) {
depth = Math.max(depth, currentDepth);
obj.children.forEach((child) => recurse(child, currentDepth + 1));
}
};
recurse(jsonTree, 0);
return depth;
}
function graphWithTopLeft(
trees,
newPositionNodes= [],
index = 0,
depth = 0,
parent = 0
) {
const findLevel = trees
.filter((lev) => lev.level === index)
.sort((a, b) =>
getAllIds(connectionsArr, a.id).length <
getAllIds(connectionsArr, b.id).length
? 1
: -1
);
findLevel.forEach((node, indexLevel) => {
if (node.level === 0) {
const y = 0;
const x =
indexLevel === 0
? 0
: indexLevel *
getAllIds(connectionsArr, findLevel[indexLevel].id).length *
findLevel.length *
200;
newPositionNodes.push({
id: node.id,
name: `Node${node.id}`,
left: x,
top: y,
isDragging: false
});
}
if (node.level === 1) {
const getMaxChildInLevel = getMaxChildren(node);
const findParent = newPositionNodes.find((f) => f.id === parent);
const axisX =
Math.pow(findLevel.length, depth - 1) * getMaxChildInLevel.max;
const y = node.level * WIDTH;
const x =
indexLevel === 0
? -(axisX / 3) - (indexLevel * WIDTH + WIDTH)
: axisX / 3 + indexLevel <= 1
? (findParent?.left ) +
indexLevel *
WIDTH *
(node.children.length > 0 ? node.children.length : 1)
: (newPositionNodes[indexLevel - 1]?.left) +
indexLevel * WIDTH * node.children.length;
if (axisX === 0 && indexLevel === 0) {
if (!newPositionNodes.find((n) => n.id === node.id)) {
const findParent = newPositionNodes.find((f) => f.id === parent);
newPositionNodes.push({
id: node.id,
name: `Node${node.id}`,
left: findParent?.left ,
top: y,
isDragging: false
});
}
} else if (!newPositionNodes.find((n) => n.id === node.id)) {
newPositionNodes.push({
id: node.id,
name: `Node${node.id}`,
left: x,
top: y,
isDragging: false
});
}
} else {
if (!newPositionNodes.find((n) => n.id === node.id)) {
const findParent = newPositionNodes.find((f) => f.id === parent);
const y = (node.level ) * WIDTH;
const x =
indexLevel === 0
? (findParent?.left) - WIDTH
: indexLevel <= 1
? (findParent?.left) +
indexLevel *
WIDTH *
(node.children.length > 0 ? node.children.length : 1)
: (newPositionNodes[indexLevel - 1]?.left) +
indexLevel * WIDTH * node.children.length;
if (findLevel.length <= 1) {
newPositionNodes.push({
id: node.id,
name: `Node${node.id}`,
left: findParent?.left,
top: y,
isDragging: false
});
} else {
newPositionNodes.push({
id: node.id,
name: `Node${node.id}`,
left: x,
top: y,
isDragging: false
});
}
}
}
});
for (let i = 0; i < trees.length; i++) {
const depth = getDepth(trees[i]);
if (trees[i].children && trees[i].children?.length > 0) {
graphWithTopLeft(
trees[i].children,
newPositionNodes,
index + 1,
depth,
trees[i].id
);
}
}
return newPositionNodes;
}
const display = graphWithTopLeft(updated, [], 0);
console.log(display);
// <------------------------------Canvas------------------------------>
const canvas = document.querySelector("#paper");
const WIDTHcA = canvas.width;
const HEIGHTCA = canvas.height;
// drag related variables
let dragok = false;
let startX;
let startY;
const ctx = canvas.getContext("2d");
function clear() {
ctx.clearRect(0, 0, WIDTHcA, HEIGHTCA);
}
function drawLine(
ctx,
begin,
end,
stroke = "black",
width = 1
) {
if (stroke) {
ctx.strokeStyle = stroke;
}
if (width) {
ctx.lineWidth = width;
}
ctx.beginPath();
ctx.moveTo(begin[0] + 100, begin[1] + 100);
ctx.lineTo(end[0] + 100, end[1]);
ctx.stroke();
}
const draw = (t) => {
clear();
ctx.translate(600, 100);
ctx.stroke();
for (let i = 0; i < display.length; i++) {
const x = display[i].left;
const y = display[i].top;
ctx.fillStyle = "red";
ctx.fillRect(x, y, 200, 100);
ctx.fillStyle = "white";
ctx.font = "24px Arial";
ctx.fillText(display[i].name, display[i].left, display[i].top);
}
//draw line
for (let i = 0; i < connectionsArr.length; i++) {
const sourcePaths = connectionsArr.filter(
(f) => f.sourceId === connectionsArr[i].sourceId
);
const from = display.find((f) => f.id === connectionsArr[i].sourceId);
sourcePaths.forEach((s) => {
const to = display.find((f) => f.id === s.targetId);
if (to && from)
drawLine(ctx, [from?.left, from?.top], [to.left, to.top], "green", 1);
});
}
};
requestAnimationFrame(draw);
<html>
<head>
<title>Sandbox</title>
<meta charset="UTF-8" />
<style>
body {
background: black;
margin: 0;
}
</style>
</head>
<body>
<canvas id="paper" width="10000" height="10000"></canvas>
<script src="src/index.ts"></script>
</body>
</html>
Please excuse my using an answer for what is really an extended comment. It's far too long for the comment box.
Here's an answer that seems to meet all the requirements you've given us:
const unique = (xs) => [... new Set (xs)]
const vertices = (edges) =>
unique (edges .flatMap (({sourceId, targetId}) => [sourceId, targetId])) .sort ((a, b) => a - b)
const coordinates = (edges) =>
vertices (edges) .map (v => ({id: v, location: {x: 0, y: 0}}))
const connectionsArr = [{sourceId: 1, targetId: 2}, {sourceId: 1, targetId: 9}, {sourceId: 9, targetId: 3}, {sourceId: 3, targetId: 5}, {sourceId: 3, targetId: 7}, {sourceId: 5, targetId: 6}, {sourceId: 6, targetId: 10}, {sourceId: 11, targetId: 12}]
console .log (coordinates (connectionsArr))
.as-console-wrapper {max-height: 100% !important; top: 0}
For each vertex in the graph, we return an element with id and location properties, the latter itself containing numeric x and y properties. The results look like this:
[
{id: 1, location: {x: 0, y: 0}},
{id: 2, location: {x: 0, y: 0}},
{id: 3, location: {x: 0, y: 0}},
{id: 5, location: {x: 0, y: 0}},
{id: 6, location: {x: 0, y: 0}},
{id: 7, location: {x: 0, y: 0}},
{id: 9, location: {x: 0, y: 0}},
{id: 10, location: {x: 0, y: 0}},
{id: 11, location: {x: 0, y: 0}},
{id: 12, location: {x: 0, y: 0}},
]
I sense an objection. The boxes can't all be at the same spot? Well, why didn't you say so? We can fix that easily enough. Just change coordinates to
const coordinates = (edges) =>
vertices (edges) .map ((v, i) => ({id: v, location: {x: i, y: i}}))
Now they're in different spots, as we can see with
[
{id: 1, location: {x: 0, y: 0}},
{id: 2, location: {x: 1, y: 1}},
{id: 3, location: {x: 2, y: 2}},
{id: 5, location: {x: 3, y: 3}},
{id: 6, location: {x: 4, y: 4}},
{id: 7, location: {x: 5, y: 5}},
{id: 9, location: {x: 6, y: 6}},
{id: 10, location: {x: 7, y: 7}},
{id: 11, location: {x: 8, y: 8}},
{id: 12, location: {x: 9, y: 9}},
]
Wait, what's that? They can't overlap? Well, just make the boxes small enough and they won't overlap. You can't do that? Well, why didn't you say any of this? How big do the boxes need to be? You don't know? Ok, we can make it a parameter. But first, how about if we try with 50 x 50?
Oh, wait, you don't want them in a line? Ok. We can fix that too. Here's another attempt:
const coordinates = (edges) => {
const vs = vertices (edges)
const angle = 2 * Math.PI / vs .length
return vs .map ((v, i) => ({id: v, location: {
x: Math .round (150 * Math .cos (i * angle)) + 300,
y: Math .round (150 * Math .sin (i * angle)) + 300,
}}))
}
Now they won't overlap at all, giving us something like this:
Is this not what you want? So far each step has met all the requirements given so far. Perhaps you need better requirements:
Do you know if your edges form a tree? A forest? If it's either, is it a binary tree /forest?
Or might there be cycles?
Do you know if your graph is planar? (That is, it can be drawn on the plane with no edges crossing)
How do you want your nodes laid out? A picture of a graph that doesn't even match your code sample is little help.
You need to put more thought into your requirements, and work into your own attempt before we can really help you.
I'm trying to sum the values of arrays inside an array.
const arr = [
{ key: 0, x: [4,5,6], y: [1,2,3,4]},
{ key: 0, x: [1], y: [] }
]
The expected output would be 26 ( 4 + 5 + 6 + 1 + 2 ...)
No idea how to do this, tried with reduce but I don't know how to access the other array.
Any help would be great.
You can try getting the sum of nested object's array first, then sum them up and return like the following way:
const arr = [
{ key: 0, x: [4,5,6], y: [1,2,3,4]},
{ key: 0, x: [1], y: [] }
]
const sum = arr.reduce((a, c) => {
const cTemp = Object.values(c).flat().reduce((aa,cc) => aa+cc, 0);;
return a + cTemp;
}, 0);
console.log(sum);
const total = [
{key: 0, x: [4, 5, 6], y: [1, 2, 3, 4]},
{key: 0, x: [1], y: []},
]
.map(({x, y}) => [...x, ...y])
.reduce((total, curr) => total + curr.reduce((a, b) => a + b), 0);
const total = [
{key: 0, x: [4, 5, 6], y: [1, 2, 3, 4]},
{key: 0, x: [1], y: []},
]
.map(({x, y}) => [...x, ...y])
.reduce((total, curr) => total + curr.reduce((a, b) => a + b), 0);
console.log(total);
No idea how to do this, tried with reduce but I don't know how to
access the other array.
You can create another function named sum to sum all values in an array. After that, at each object, you can use Spread ... to merge 2 arrays into one like this.
const arr = [ { key: 0, x: [4,5,6], y: [1,2,3,4]},
{ key: 0, x: [1], y: [] }];
const sum = (arr) => arr.reduce((acc, curr) => acc+= curr, 0);
const result = arr.reduce((acc, {x, y}) => acc += sum([...x, ...y]), 0);
console.log(result);
Another way is to use Array#flatMap to get all values, then use Array#reduce to sum them.
const allValues = arr.flatMap(({x, y}) => [...x, ...y]);
const result = allValues.reduce((acc, curr) => acc+= curr, 0);
const arr = [ { key: 0, x: [4,5,6], y: [1,2,3,4]},
{ key: 0, x: [1], y: [] }];
const allValues = arr.flatMap(({x, y}) => [...x, ...y]);
const result = allValues.reduce((acc, curr) => acc+= curr, 0);
console.log(result);
The flatMap() method returns a new array formed by applying a given
callback function to each element of the array, and then flattening
the result by one level
This works for me.
const arr = [{
key: 0,
x: [4, 5, 6],
y: [1, 2, 3, 4]
},
{
key: 0,
x: [1],
y: []
}
]
var total = 0;
arr.forEach(function(value) {
value.x.forEach(function(x) {
total += x;
});
value.y.forEach(function(y) {
total += y;
});
});
console.log(total);
I have an object like this:
let a = { x: 3, y: '3', z: 'z' };
And an array like this:
let b = [{ x: 1, y: '1'}, { x: 2, y: '2' }];
How can I do something like this:
b.push({ x, y } = a);
Instead of this:
b.push({ x: a.x, y: a.y });
// or this:
const { x, y } = a;
b.push({ x, y });
You need to return a new object with the destructured properties.
const
getXY = ({x, y}) => ({ x, y }),
a = { x: 3, y: '3', z: 'z' },
b = [{ x: 1, y: '1'}, { x: 2, y: '2' }];
b.push(getXY(a));
console.log(b);
I know this has been asked before but I can't seem to find the answer, i want to get no_anggota, nama_lahir and tgl_lahir from array isiuser, and i want to insert to ZZZ .
isiseat = [
[{ x: 0, y: 0, val: 1 },
{ x: 1, y: 0, val: 1 },
{ x: 2, y: 0, val: 1 }]
[{ x: 0, y: 1, val: 1 },
{ x: 1, y: 1, val: 1 },
{ x: 2, y: 1, val: 1 }]
[{ x: 0, y: 2, val: 1 },
{ x: 1, y: 2, val: 1 },
{ x: 2, y: 2, val: 1 }]
];
isiuser = [ { no_anggota: '12345', nama_lahir: ' Agus dwi', tgl_lahir: '04-09-2018'},
{ no_anggota: '12345', nama_lahir: 'Septano', tgl_lahir: '15-08-2018'},
{ no_anggota: '12345', nama_lahir: 'Septian putra', tgl_lahir: '15-08-2018'}
];
isiseat.forEach((item1) => {
item1.forEach((item2) => {
var detaildata = {};
detaildata.x = item2.x
detaildata.y = item2.y
detaildata.val = item2.val
detaildata.no_anggota = 'ZZZ'
detaildata.nama_lahir = 'ZZZ'
detaildata.tgl_lahir = 'ZZZ'
detaildata.jadwal = req.body.jadwal
detaildata.section = req.body.section
var savedata = new DetailBooking(detaildata);
savedata.save()
})
});
You can use bulk insert bulk insert
var dataArray = [];
var isiseat = [
[
{ x: 0, y: 0, val: 1 },
{ x: 1, y: 0, val: 1 },
{ x: 2, y: 0, val: 1 }
],
[
{ x: 0, y: 1, val: 1 },
{ x: 1, y: 1, val: 1 },
{ x: 2, y: 1, val: 1 }
],
[
{ x: 0, y: 2, val: 1 },
{ x: 1, y: 2, val: 1 },
{ x: 2, y: 2, val: 1 }
]
]
var bulk = db.items.initializeUnorderedBulkOp();
isiseat.forEach((item1) => {
item1.forEach((item2) => {
var detaildata = {};
detaildata.x = item2.x
detaildata.y = item2.y
detaildata.val = item2.val
detaildata.no_anggota = 'ZZZ'
detaildata.nama_lahir = 'ZZZ'
detaildata.tgl_lahir = 'ZZZ'
detaildata.jadwal = 'sssss'
detaildata.section = 'sssssss'
bulk.insert(detaildata);
})
});
bulk.execute();
my saved data look like this
{
"_id" : ObjectId("5b61aab063727f69dc5ad1b8"),
"x" : 0.0,
"y" : 0.0,
"val" : 1.0,
"no_anggota" : "ZZZ",
"nama_lahir" : "ZZZ",
"tgl_lahir" : "ZZZ",
"jadwal" : "sssss",
"section" : "sssssss"
}
{
"_id" : ObjectId("5b61aab063727f69dc5ad1b9"),
"x" : 1.0,
"y" : 0.0,
"val" : 1.0,
"no_anggota" : "ZZZ",
"nama_lahir" : "ZZZ",
"tgl_lahir" : "ZZZ",
"jadwal" : "sssss",
"section" : "sssssss"
}
If both isiseat and isiuser arrays are of the same length, you can pass the index parameter to the forEach function. (See "Loop through an two arrays simultaneously")
Therefore you could pass the index to your first forEach function:
// Index passed here
isiseat.forEach((item1, index) => {
item1.forEach((item2) => {
var detaildata = {
x: item2.x,
y: item2.y,
val: item2.val,
// ...And then access the `isiuser` array accordingly
no_anggota: isiuser[index].no_anggota,
nama_lahir: isiuser[index].nama_lahir,
tgl_lahir: isiuser[index].tgl_lahir,
jadwal: req.body.jadwal,
section: req.body.section
};
// console.log to see results
console.log(detaildata)
var savedata = new DetailBooking(detaildata);
savedata.save()
})
});
I have object array like this.
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ]
I want to count duplicates objects and store the count as new object field.
I found this snippet and it work great but it not exactly what i need.
const names = [{ _id: 1 }, { _id: 1}, { _id: 2}, { _id: 1}]
const result = [...names.reduce( (mp, o) => {
if (!mp.has(o._id)) mp.set(o._id, Object.assign({ count: 0 }, o));
mp.get(o._id).count++;
return mp;
}, new Map).values()];
console.log(result);
It works with object with one field _id. In my case there are two, x and y
How should I modify that code?
In brief...I would like to receive the result:
result = [ { x: 1, y: 2, count:3 }, { x: 3, y: 4, count:2 }, { x: 3, y: 12, count:1 } ]
You can use Object.values() and reduce() methods to return new array of objects.
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ]
const result = Object.values(array.reduce((r, e) => {
let k = `${e.x}|${e.y}`;
if(!r[k]) r[k] = {...e, count: 1}
else r[k].count += 1;
return r;
}, {}))
console.log(result)
Here is the solution with Map and spread syntax ...
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ]
const result = [...array.reduce((r, e) => {
let k = `${e.x}|${e.y}`;
if(!r.has(k)) r.set(k, {...e, count: 1})
else r.get(k).count++
return r;
}, new Map).values()]
console.log(result)
One way to do it would be to create an index mapping both x and y to the result entry:
let index = { };
let result = [ ];
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ];
array.forEach(point => {
let key = '' + point.x + '||' + point.y;
if (key in index) {
index[key].count++;
} else {
let newEntry = { x: point.x, y: point.y, count: 1 };
index[key] = newEntry;
result.push(newEntry);
}
});
console.log(result);