sort objects array by specific value in different keys - javascript

I have this array -
let array = [
{
"id": 123,
"pair": 312
},
{
"id": 321,
"pair": 111
},
{
"id": 312,
"pair": 123
},
{
"id": 111,
"pair": 321
}
];
And i need it to be sorted like this =
let array = [
{
"id": 123,
"pair": 312
},
{
"id": 312,
"pair": 123
},
{
"id": 321,
"pair": 111
},
{
"id": 111,
"pair": 321
}
];
Which means that i need to find the matched value of the pair key in the object, and put it right after the first element (eventually i need the to be sorted in pairs order - of course the array will be way bigger and mixed)
i could not find a efficient way to achieve this.
this is what i tried - it feels very unefficient
products is the array i get from the server.
let pairs = [];
let prods = [...products];
for(let product of prods){
if(product.matched){
continue;
}
let pairStock = product.pairStock;
let companyId = product.company;
let matched = prods.filter(prod => prod.productId === pairStock && String(prod.company) === String(companyId));
if(matched.length > 0){
pairs.push(product);
pairs.push(matched[0]);
let index = prods.findIndex(prod => prod.productId === matched[0].productId);
prods[index].matched = true;
}
};

this will sort data when the number of items that are linked togather is between 0 and array.length.
let products = [
{ productId: 'PK0154', pairStock: 'PK0112-02' },
{ productId: 'PK0112-02', pairStock: 'PK0154' },
{ productId: 'MGS-140', pairStock: 'MGS-136' },
{ productId: 'GM-0168', pairStock: 'GM-0169' },
{ productId: 'GM-0169', pairStock: 'GM-0168' },
{ productId: 'MGS-136', pairStock: 'MGS-140' },
]
function sort(data) {
var mappedArray = {}
data.forEach(obj => (mappedArray[obj.productId] = obj))
data.sort((a, b) => a.productId.localeCompare( b.productId) )
var addToRes = (res, id) => {
if (id !== undefined && mappedArray[id] !== undefined) {
var obj = mappedArray[id]
mappedArray[id] = undefined
res.push(obj)
addToRes(res, obj.pairStock)
}
}
var result = []
data.forEach(item => addToRes(result, item.productId))
return result
}
console.log(sort(products))
its results
0: {productId: "GM-0168", pairStock: "GM-0169"}
1: {productId: "GM-0169", pairStock: "GM-0168"}
2: {productId: "MGS-136", pairStock: "MGS-140"}
3: {productId: "MGS-140", pairStock: "MGS-136"}
4: {productId: "PK0112-02", pairStock: "PK0154"}
5: {productId: "PK0154", pairStock: "PK0112-02"}

The performant way (store a map of products and look up each product's pair by it's id) - O(n*2):
const productsObj = {};
products.forEach(product => productsObj[product.id] = product);
const [seenProducts, pairedProducts] = [{}, []];
products.forEach(product => {
const productPair = productsObj[product.pair);
if (!seenProducts[product.id]) pairedProducts.push(...[product, productPair])
seenProducts[productPair.id] = true;
});
console.log(pairedProducts)
The intuitive way O(n^2):
// Find the next pair if a pair doesn't already exist
const arr = [];
for (let i = 0; i < products.length; i++) {
const containsPair = arr.some(item => item.pair === products[i].id);
if (containsPair === false) {
const productPair = products.slice(i).find(({ id }) => id === item.pair));
arr.push(...[products[i], productPair]);
}
}
console.log(arr)

Related

How To Build Javascript Recursion Tree

Sorry my english is not good, hope everyone understand. I have an array:
var data=[
{
"id": 2,
"parent_id": 1
},
{
"id": 3,
"parent_id": 2
},
{
"id": 7,
"parent_id": 3
},
{
"id": 67,
"parent_id": 1
}
]
And this is what I need the result to look:
[
{
"id": 2,
"parent_id": 1,
"child":[
{
"id": 3,
"parent_id": 2,
"child":[{
"id": 7,
"parent_id": 3
},
]}
]},
{
"id": 67,
"parent_id": 1
},]
My idea is: 1 method has 2 parameters of the same array. I use nested loop. If parent_id == id will add the field "child".
const getTree = function(data, maindata){
const result=data.forEach(item =>{
const child=maindata.forEach(element =>{
if(item.id === element.parent_id){
return true;
}
return false
})
getTree(child, maindata)
item.child = child;
})
return result;
}
console.log(getTree(data,data))
But it is not working as it should. Hope everybody help please. thanks
I'm not sure what your original code is supposed to do, but you're not getting any results because data.forEach doesn't return anything. You need to first filter out the objects that are children (which I assume is what your original code was aiming to do) and then afterwards assign all the objects to their parents like this:
var data=[{"id": 2,"parent_id": 1},{"id": 3,"parent_id": 2},{"id": 7,"parent_id": 3},{"id": 67,"parent_id": 1},]
const filterData = function(data) {
return data.filter(item => {
let isChild = false;
data.forEach(parent => {
if (parent.id == item.parent_id) {
isChild = true;
return;
}
});
return !isChild;
});
}
const getTree = function(data, maindata){
return data.map(item =>{
let children = [];
maindata.forEach(child => {
if (item.id == child.parent_id) {
children.push(child);
}
});
if (children.length > 0) {
item.child = getTree(children, maindata);
}
return item;
});
}
console.log(getTree(filterData(data),data));
Another way:
var data=[{"id": 2,"parent_id": 1},{"id": 3,"parent_id": 2},{"id": 7,"parent_id": 3},{"id": 67,"parent_id": 1},]
data.sort(byHierarchy);
var dest = [], idx = {};
for (var i of data) {
if (i.parent_id === 1) {
dest.push(i);
} else {
let j = idx[i.parent_id];
if (j.child) j.child.push(i);
else j.child = [i];
}
idx[i.id] = i;
}
function byHierarchy(a, b) {
if (a.parent_id === b.parent_id) return a.id - b.id;
return a.parent_id - b.parent_id;
}
console.log(JSON.stringify(dest, null, 2).replace(/([{[\]}])[\s\n]+([{[\]}])/g, '$1$2'));
I think this is the cleanest way to do it
const data=[{"id": 2,"parent_id": 1},{"id": 3,"parent_id": 2},{"id": 7,"parent_id": 3},{"id": 67,"parent_id": 1},]
let makeTree = (data, parent) => {
let node = []
data
.filter(d => d.parent_id == parent)
.forEach(d => {
d.child = makeTree(data, d.id)
node.push(d)
})
return node
}
console.log(JSON.stringify(makeTree(data,1),null,2))

Array of objects how do i check for deeply nested text string duplicates & remove from array?

I have an array of objects
Deep inside those objects is a text string
I want to check if other objects in the same array have the same text string / are duplicates.
Then i need a new array with those duplicates removed.
I thought this would be quite simple but it's been testing my intellect for two days now.
const arr = [
{..obj 1}
{..obj 2}
{..obj 3}
{
id: 4,
uid: 24872-2847-249249892842,
tags: ['some', 'stuff'],
type: "blogpage",
href: "https://link-to-stuff",
first_publication_date: "2020-02-12T16:05:04+0000",
last_publication_date: "2020-02-18T21:52:06+0000",
data: {
...some stuff
heading: [
{ type: "heading1", text: "Here Is My Text I Need To Check Duplicates
Of"}
]
}
}
{..obj 5}
{..obj 6}
{..obj 7}
{..obj 8}
{..obj 9}
{..obj 10}
]
I figured something like:
filterOutDuplicates = (blogIndexContent) => {
let arr = blogIndexContent.pages;
let results = [];
arr.map(each => {
if (!results || !results.length) {
results.push(each);
} else {
for (let i = 0; i < results.length; i++) {
const headline = results[i].data.heading[0].text;
if (headline === each.data.heading[0].text) {
return;
} else {
return results.push(each);
}
}
}
})
console.log('Results :', results); // <-- this just gives me the same 9 blog stories again, no duplicates removed.
}
What am i doing wrong guys?
If you dont mind using lodash, it could be easily solved using _.uniqBy
const withoutDups = _.uniqBy(arr, 'data.heading[0].text')
Try this
const arr = [
{
id: 4,
data: {
heading: [
{
type: "heading1",
text: "Here Is My Text I Need To Check Duplicates Of"
}
]
}
},
{
id: 5,
data: {
heading: [
{
type: "heading1",
text: "Here Is My Text I Need To Check Duplicates Of"
}
]
}
},
{
id: 6,
data: {
heading: [
{
type: "heading1",
text: "Not Duplicates"
}
]
}
}
];
const withoutDuplicates = arr.reduce(
(prev, curr) =>
prev
.map(d => d["data"]["heading"][0]["text"])
.includes(curr["data"]["heading"][0]["text"])
? [curr]
: [...prev, curr],
[]
);
console.log(withoutDuplicates);
Slight changes to your code
1) remove using map, have loop over array.
2) Build the uniq object with keys. (Here headline is what we want)
3) Add to results array only when key is not in uniq
let arr = blogIndexContent.pages;
let results = [];
const uniq = {};
for (let i = 0; i < arr.length; i++) {
const headline = arr[i].data.heading[0].text;
if (!(headline in uniq)) {
results.push(each);
uniq[each] = 1;
}
}
console.log("Results :", results);
This should work for you:
filterOutDuplicates = blogIndexContent => {
let arr = blogIndexContent.pages
const result = []
arr.forEach(each => {
if (result.length === 0) {
result.push(each)
}
else {
const headline = each.data.heading[0].text
let found = false
for (let i = 0; i < result.length; i++) {
if (result[i].data.heading[0].text === headline) {
found = true
break
}
}
if (!found) {
result.push(each)
}
}
})
console.log('Results :', results)
}

how to count duplicate values object to be a value of object

how to count the value of object in new object values
lets say that i have json like this :
let data = [{
no: 3,
name: 'drink'
},
{
no: 90,
name: 'eat'
},
{
no: 20,
name: 'swim'
}
];
if i have the user pick no in arrays : [3,3,3,3,3,3,3,3,3,3,3,90,20,20,20,20]
so the output should be an array
[
{
num: 3,
total: 11
},
{
num: 90,
total: 1
},
{
num:20,
total: 4
}
];
I would like to know how to do this with a for/of loop
Here is the code I've attempted:
let obj = [];
for (i of arr){
for (j of data){
let innerObj={};
innerObj.num = i
obj.push(innerObj)
}
}
const data = [{"no":3,"name":"drink"},{"no":90,"name":"eat"},{"no":20,"name":"swim"}];
const arr = [3,3,3,3,3,3,3,3,3,3,3,20,20,20,20,80,80];
const lookup = {};
// Loop over the duplicate array and create an
// object that contains the totals
for (let el of arr) {
// If the key doesn't exist set it to zero,
// otherwise add 1 to it
lookup[el] = (lookup[el] || 0) + 1;
}
const out = [];
// Then loop over the data updating the objects
// with the totals found in the lookup object
for (let obj of data) {
lookup[obj.no] && out.push({
no: obj.no,
total: lookup[obj.no]
});
}
document.querySelector('#lookup').textContent = JSON.stringify(lookup, null, 2);
document.querySelector('#out').textContent = JSON.stringify(out, null, 2);
<h3>Lookup output</h3>
<pre id="lookup"></pre>
<h3>Main output</h3>
<pre id="out"></pre>
Perhaps something like this? You can map the existing data array and attach filtered array counts to each array object.
let data = [
{
no: 3,
name: 'drink'
},
{
no:90,
name: 'eat'
},
{
no:20,
name: 'swim'
}
]
const test = [3,3,3,3,3,3,3,3,3,3,3,90,20,20,20,20]
const result = data.map((item) => {
return {
num: item.no,
total: test.filter(i => i === item.no).length // filters number array and then checks length
}
})
You can check next approach using a single for/of loop. But first I have to create a Set with valid ids, so I can discard noise data from the test array:
const data = [
{no: 3, name: 'drink'},
{no: 90, name: 'eat'},
{no: 20, name: 'swim'}
];
const userArr = [3,3,3,3,3,3,3,3,7,7,9,9,3,3,3,90,20,20,20,20];
let ids = new Set(data.map(x => x.no));
let newArr = [];
for (i of userArr)
{
let found = newArr.findIndex(x => x.num === i)
if (found >= 0)
newArr[found].total += 1;
else
ids.has(i) && newArr.push({num: i, total: 1});
}
console.log(newArr);

How to create a single object from key-value pairs objects in JavaScript?

I have several objects and I want to create another one that will have keys from a particular array (const props = []), and values from those objects - if it only exists in those objects, but if not - I want to push null or some other fake values.
My code:
const props = ["name", "price", "qty", "category"]
let len = props.length;
const obj_1 = {
name: "Product_1",
price: 120,
category: 'phone'
}
const obj_2 = {
name: "Product_2",
price: 7893,
category: 'program_eq',
qty: 5
}
const final_obj = {
name: ["Product_1", "Product_2"],
price: [120, 7893],
category: ["phone", "program_eq"],
qty: [null, 5]
}
I have spent lots of time with this problem and have some solution - but it gives me only the first object.
I am using lodash/map and it helps me to work with different type of collection.
You can see my solution bellow:
const final_obj = {};
const props = ["name", "price", "qty", "category"];
let len = props.length;
const obj = {
c1s6c156a1cascascas: {
item: {
name: "Product_1",
price: 120,
category: "phone"
}
},
c454asc5515as41cas78: {
item: {
name: "Product_2",
price: 7893,
category: "program_eq",
qty: 5
}
}
};
_.map(obj, (element, key) => {
console.log(element.item);
while (len) {
let temp = props.shift();
let tempData = [];
if (element.item.hasOwnProperty([temp])) {
tempData.push(element.item[temp]);
} else {
tempData.push("---");
}
final_obj[temp] = tempData;
len--;
}
});
console.log(final_obj);
//
category:["phone"]
name:["Product_1"],
price:[120],
qty:["---"],
You could do this with reduce() method that will return object and inside use forEach() loop.
const props = ["name", "price", "qty", "category"];
const obj = {"c1s6c156a1cascascas":{"item":{"name":"Product_1","price":120,"category":"phone"}},"c454asc5515as41cas78":{"item":{"name":"Product_2","price":7893,"category":"program_eq","qty":5}}}
const result = Object.values(obj).reduce((r, e) => {
props.forEach(prop => {
if(!r[prop]) r[prop] = []
r[prop].push(e.item[prop] || null)
})
return r;
}, {})
console.log(result)
This is how I would handle it:
const final_obj = { };
const props = ["name", "price", "qty", "category"];
const obj = {"c1s6c156a1cascascas":{"item":{"name":"Product_1","price":120,"category":"phone"}},"c454asc5515as41cas78":{"item":{"name":"Product_2","price":7893,"category":"program_eq","qty":5}}}
// set up each property as an empty array in the object
props.forEach(item => {
final_obj[item] = [];
});
// this iterates over every property in the object
_.forOwn(obj, value => {
props.forEach(item => {
// just push the values undefined or no into each property array
final_obj[item].push(value.item[item]);
});
});
console.log(final_obj);
You can do as well using some lodash functions.
Transform the array of props into an object which keys are the values of props and which values are extracted from the object. If the property doesn't exist in the object, return null.
const getValFromObj = (obj, key) => _.map(obj, _.partial(_.get, _, key, null));
const setValInResult = (res, key) => _.set(res, key, getValFromObj(obj, 'item.' + key));
const groupByProps = (props, obj) => _.transform(props, setValInResult, {});
const props = ["name", "price", "qty", "category"];
const obj = {
"c1s6c156a1cascascas": {
"item": {
"name": "Product_1",
"price": 120,
"category": "phone"
}
},
"c454asc5515as41cas78": {
"item": {
"name": "Product_2",
"price": 7893,
"category": "program_eq",
"qty": 5
}
}
}
console.log(groupByProps(props, obj));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>

Filtering array of objects with arrays based on nested value

I am trying to filter an array, based on some nested object. I prepared some Fiddle
Input array looks like this:
let arrayOfElements =
[
{
"name": "a",
"subElements":
[
{"surname": 1},
{"surname": 2}
]
},
{
"name": "b",
"subElements":
[
{"surname": 3},
{"surname": 1}
]
},
{
"name": "c",
"subElements":
[
{"surname": 2},
{"surname": 5}
]
}
];
I want the output for this case, to look like this:
let filteredArray =
[
{
"name": "a",
"subElements":
[
{"surname": 1}
]
},
{
"name": "b",
"subElements":
[
{"surname": 1}
]
}
];
I am using this formula to do that:
let filteredArray = arrayOfElements.filter((element) => element.subElements.some((subElement) => subElement.surname === 1));
Output is almost good, but it returns objects with all objects with surnames (better check that fiddle :D), instead of cutting them away. How can i improve the filtering ?
This way you can go as deep as you want in an array and filter elements at any level,
arrayOfElements.map((element) => {
return {...element, subElements: element.subElements.filter((subElement) => subElement.surname === 1)}
})
Spread operator will expand element and then filtered subElements will override the subElements in element.
After you call filter, you need to pipe the results to map, like this:
let filteredArray = arrayOfElements
.filter((element) =>
element.subElements.some((subElement) => subElement.surname === 1))
.map(element => {
let newElt = Object.assign({}, element); // copies element
return newElt.subElements.filter(subElement => subElement.surname === '1');
});
I am assuming here that you don't want to manipulate the original array. So, I am using Object.assign.
let filteredArray = arrayOfElements
.filter((element) =>
element.subElements.some((subElement) => subElement.surname == 1))
.map(element => {
return Object.assign({}, element, {subElements : element.subElements.filter(subElement => subElement.surname == 1)});
});
Just improved the answers above
let elements =
[
{
"name": "a",
"subElements":
[
{"surname": 1},
{"surname": 2}
]
},
{
"name": "b",
"subElements":
[
{"surname": 3},
{"surname": 1}
]
},
{
"name": "c",
"subElements":
[
{"surname": 2},
{"surname": 5}
]
}
];
var value = 1;
var filteredArray = elements
.filter(element => element.subElements
.some(subElement => subElement.surname === value)
)
.map(element => {
let n = Object.assign({}, element, {'subElements': element.subElements.filter(
subElement => subElement.surname === value
)})
return n;
})
console.log(filteredArray)
Try this solution:
data_filter = arrayOfElements.filter(function (element) {
return element.subElements.some( function (subElement) {
return subElement.surname === surname
});
});
You can make it generic as well:
Logic
Find all distinct surnames and loop over them
Filter every object to check if surnames exists. If yes, copy object using Object.assign and set subElements value to filtered list.
Create a temp array to hold all similar objects and push copied object to it.
Push this array to final array on every iteration of distinct surname.
Sample
let arrayOfElements=[{name:"a",subElements:[{surname:1},{surname:2}]},{name:"b",subElements:[{surname:3},{surname:1}]},{name:"c",subElements:[{surname:2},{surname:5}]}];
let distinct_surnames = [];
arrayOfElements.forEach(function(el) {
el.subElements.forEach(function(s) {
if (distinct_surnames.indexOf(s.surname) < 0) distinct_surnames.push(s.surname)
});
})
let result = [];
distinct_surnames.forEach(function(sn) {
let inter = [];
arrayOfElements.forEach(function(el) {
let f = el.subElements.filter(function(sub) {
return sub.surname === sn;
});
if (f.length > 0) {
let _tmp = Object.assign({}, el);
_tmp.subElements = f;
inter.push(_tmp);
}
});
result.push(inter);
})
console.log(result)
Note: Arrow functions are used to keep the reference of this. If you are not using this inside function, you can use normal functions as well.
function display_message() {
let arrayOfElements = [{
"name": "a",
"subElements": [{
"surname": 1
}, {
"surname": 2
}]
}, {
"name": "b",
"subElements": [{
"surname": 3
}, {
"surname": 1
}]
}, {
"name": "c",
"subElements": [{
"surname": 2
}, {
"surname": 5
}]
}];
// console.log(arrayOfElements);
var surname = 1;
let filteredArray = arrayOfElements.filter((element) => element.subElements.some((subElement) => subElement.surname === surname));
for(var data in filteredArray){
filteredArray[data].subElements = {"surname": surname};
}
console.log(filteredArray);
}
<input type="button" onclick="display_message();" value="click"/>
let filteredArray = arrayOfElements
.filter((element) =>
element.subElements.some((subElement) => subElement.surname === 1))
.map(element => {
let newElt = Object.assign({}, element); // copies element
newElt.subElements = newElt.subElements.filter(subElement => subElement.surName === '1');
return newElt;
});
is more correctly

Categories