Array compare with for each performance issue - javascript

I have two arrays with array of objects as follows and one array will have more than 10k records and other have below 100 records
let bigArray = [{id:1, name:"Raj", level:0}, {id:2, name:"sushama", level:2}, {id:3, name:"Sushant", level:0}, {id:4, name:"Bhaskar", level:2},....upto 30k records]
let smallArray = [{id:2, name:"sushama"}, {id:3, name:"Sushant"}....upto 100 records]
I want to find where in the index of bigArray in which the object from smallArray resides and add to another array say indexArray I tried below
let indexArray = [];
bigArray.forEach((element, i) => {
smallArray.forEach(ele => {
if (element.name == ele.name && element.id == ele.id) {
indexArray.push(i); return;
}
});
});
But it takes time. What would be the fastest approach?

You can turn your O(N^2) approach into an O(N) approach by reducing the bigArray into an object indexed by a key made up from the name and id. Join the name and id by a character that isn't contained in either, such as _:
const indexArray = [];
const bigArrayIndiciesByNameAndId = bigArray.reduce((a, { name, id }, i) => {
a[name + '_' + id] = i;
return a;
}, {});
smallArray.forEach(ele => {
const keyToFind = ele.name + '_' + ele.id;
const foundIndex = bigArrayIndiciesByNameAndId[keyToFind];
if (foundIndex) {
indexArray.push(foundIndex);
}
});

You could take a Map and map the found indices.
const getKey = ({ id, name }) => [id, name].join('|');
let bigArray = [{ id: 1, name: "Raj", level: 0 }, { id: 2, name: "sushama", level: 2 }, { id: 3, name: "Sushant", level: 0 }, { id: 4, name: "Bhaskar", level: 2 }],
smallArray = [{ id: 2, name: "sushama" }, { id: 3, name: "Sushant" }],
map = new Map(bigArray.map((o, i) => [getKey(o), i]))
indexArray = smallArray.map((o) => map.get(getKey(o)));
console.log(indexArray);

return will not "break" the forEach loop. A forEach can't be stopped. The forEach callback function will be called one time per items always. When you find the element, continue running the forEach loop is a waste of resouces.
You should use for instead:
let indexArray = [];
bigArray.forEach((element, i) => {
for (var ii = 0; ii < smallArray.length; ii++) {
var ele = smallArray[ii];
if (element.name == ele.name && element.id == ele.id) {
indexArray.push(i);
break; // This will break the "for" loop as we found the item
}
}
});
TIP: Always have a good indentation in your code. Actually your code is really bad indented to identify code blocks at first sight. I fixed it in this example.

Related

Divide object array elements into groups of n each javascript

I have an Object as below:
const boxOfFruits = {
apples: [
{
name: "Kashmiri",
},
{
name: "Washington",
},
{
name: "Himalayan",
},
{
name: "Fuji",
}
],
oranges: [
{
name: "Nagpur",
},
{
name: "Clementine",
},
],
mangoes: [
{
name: "Totapuri",
},
{
name: "Alphonso",
},
{
name: "Langda",
},
],
}
I want to divide these fruits into boxes; maximum of n each, let's say where n is 3 and apples, oranges and mangoes are equally distributed.
So the output in this case would be:
box_1 = [{name: "Kashmiri"}, {name: "Nagpur"},{name: "Totapuri"}];
box_2 = [{name: "Washington"}, {name: "Clementine"},{name: "Alphonso"}];
box_3 = [{name: "Himalayan"},{name: "Langda"}, {name: "Fuji"}];
The type of fruits(apple,oranges,etc)/keys in object can increase/decrease and n is also variable. In case total fruits are less than n, then it would be just 1 box of fruits.
What I have tried so far:
Using Lodash, I am calculating the minimum and the maximum fruits in a single type:
const minFruitType = _.min(Object.values(basket).map((eachBasket: any) => eachBasket.length));
Total teams will the sum of the fruits / n
Will distribute the minimum fruits (l) in the first l boxes and fill the rest with the remaining fruits at every iteration while at the start of every iteration will calculate the minimum type of fruits again.
You can use Object.values(), array#reduce and array#forEach to transform your object.
const boxOfFruits = { apples: [ { name: "Kashmiri", }, { name: "Washington", }, { name: "Himalayan", }, ], oranges: [ { name: "Nagpur", }, { name: "Clementine", }, ], mangoes: [ { name: "Totapuri", }, { name: "Alphonso", }, { name: "Langda", }, ], },
result = Object.values(boxOfFruits).reduce((r, arr) => {
arr.forEach((o,i) => {
const key = `box_${i+1}`;
r[key] ??= r[key] || [];
r[key].push(o)
});
return r;
},{});
console.log(result);
The easiest way would be to use lodash.js's zip() function:
const boxes = _.zip( Object.values(boxOfFruits) );
Note that _.zip() will give you undefined values when the source arrays are different lengths, so you'll need/want to filter those out:
const boxes == _.zip( Object.values(boxOfFruits) )
.map(
box => box.filter(
x => x !== undefined
)
);
But that will not distribute the fruits evenly. For that, it shouldn't get much for difficult than this:
function distribute(boxOfFruits, n) {
const boxes = [];
const fruits = Object.keys(boxOfFruits);
for ( const fruit of fruits ) {
let i = 0;
const items = boxOfFruits[fruit];
for (const item of items) {
boxes[i] = !boxes[i] ?? [];
boxes[i] = boxes[i].push(item);
++i;
i = i < n ? i : 0 ;
}
}
return boxes;
}
A modified version of #Nicholas Carey's answer worked for me:
function distribute(boxOfFruits, n) {
let boxes = [];
let totalFruits = Object.values(boxOfFruits)
.reduce((content, current) => content + current.length, 0);
let maxBoxes = Math.ceil(totalFruits / 4);
Object.values(boxOfFruits).forEach((fruits) => {
let i = 0;
fruits.forEach((fruit) => {
boxes[i] ??= boxes[i] || [];
boxes[i].push(fruit);
++i;
i = i < (n+1) ? i : 0;
});
});
// Extra boxes created, redistribute them to
// starting boxes
let newBoxes = teams.slice(0, maxBoxes);
let pendingBoxes = teams.slice(maxBoxes);
let pendingFruits = pendingBoxes.flat();
let distributedBoxes = newBoxes.map((eachBox) => {
let required = n - eachBox.length;
if (required > 0) {
eachBox.push(...pendingFruits.splice(0, required));
}
return eachBox;
});
return distributedBoxes;
}
Code is pretty much the same as Nicholas's accept the below changes:
Directly fetched the values and iterated over those
empty array creation was failing, this way works
and checking on the max box size with n+1 instead of n

React.js: How to find duplicates for properties in an array of object and put a progressive number on that field

I have an array of object and each object is for example :
const myArr=[{name:"john",id:1}{name:"john",id:2}{name:"mary",id:3}]
for the first 2 element for the property "name" I have the name "john" that is duplicate.
How can I modify the rendered names like that:
const myArr=[{name:"john (1 of 2)",id:1}{name:"john (2 of 2)",id:2}{name:"mary",id:3}]
Thanks in advance!
Reduce the input array into a map by name (i.e. group by name property), and map the array of values to the result array. If the group array has more than 1 element in it then sub-map the group to include the numbering. Flatten the overall result.
const myArr = [
{ name: "john", id: 1 },
{ name: "john", id: 2 },
{ name: "mary", id: 3 }
];
const res = Object.values(
myArr.reduce((groups, current) => {
if (!groups[current.name]) {
groups[current.name] = [];
}
groups[current.name].push(current);
return groups;
}, {})
).flatMap((value) => {
if (value.length > 1) {
return value.map((current, i, arr) => ({
...current,
name: `${current.name} (${i + 1} of ${arr.length})`
}));
}
return value;
});
console.log(res);
You can do use reduce(), filter(), and flat() and do this:
const myArr = [
{name:"john", id:1},
{name:"john", id:2},
{name:"mary", id:3}
]
const res = Object.values(myArr.reduce((acc, curr) => {
const total = myArr.filter(({ name }) => name === curr.name).length;
if(!acc[curr.name]) {
acc[curr.name] = [
{...curr}
]
} else {
const currentSize = acc[curr.name].length;
if(currentSize === 1) {
acc[curr.name][0].name = `${acc[curr.name][0].name} (1 of ${total})`
}
acc[curr.name].push({
...curr,
name: `${curr.name} (${currentSize + 1} of ${total})`
})
}
return acc;
}, {})).flat();
console.log(res);
const myArr = [{name:"john",id:1}, {name:"john",id:2}, {name:"mary",id:3}];
const namesArray = myArr.map(elem => elem.name);
const namesTraversed = [];
let currentCountOfName = 1;
let len = 0;
myArr.forEach(elem => {
len = namesArray.filter(name => name === elem.name).length;
if (len > 1) {
if (namesTraversed.includes(elem.name)) {
namesTraversed.push(elem.name);
currentCountOfName = namesTraversed.filter(name => name === elem.name).length;
elem.name = `${elem.name} (${currentCountOfName} of ${len})`;
} else {
namesTraversed.push(elem.name);
currentCountOfName = 1;
elem.name = `${elem.name} (${currentCountOfName} of ${len})`;
}
}
});
console.log(myArr);
Check if this helps you
const myArr = [{
name: "john",
id: 1
}, {
name: "john",
id: 2
}, {
name: "mary",
id: 3
}]
// to keep a track of current copy index
let nameHash = {}
const newMyArr = myArr.map(ele => {
const noOccurence = myArr.filter(obj => obj.name ===ele.name).length;
if(noOccurence > 1){
// if there are multiple occurences get the current index. If undefined take 1 as first copy index.
let currentIndex = nameHash[ele.name] || 1;
const newObj = {
name: `${ele.name} (${currentIndex} of ${noOccurence})`,
id: ele.id
}
nameHash[ele.name] = currentIndex+ 1;
return newObj;
}
return ele;
})
console.log(newMyArr);

map over an array to find the sum of elements in sub arrays belonging to differents objects

hope the title was not cryptic enough :) . I'm trying to explain the question better: I have this array with the following data (example)
const data = [
{
name: "Bob",
items: [1]
},
{
name: "charlie",
items: [1,2]
},
{
name: "Chris",
items: [5]
}
]
I've put the items in an array because sometimes can be more than one. Now my question is: how can I find the sum of those items so it would be 4? (first array has one element, second has two, and the last one 1) I don't know why I'm having such a big time with this guys... I've tried to map the item property and then use a reduce function, but it gaves me back one single array...
Thanks guys for the help
data.filter(ele => {
var summ = ele.items.reduce((sum, current) => sum + current, 0);
if(summ === 9) return ele
})
You can use .reduce to calculate the sum of the lengths of items lists:
const data = [
{ name: "Bob", items: [1] },
{ name: "charlie", items: [1,2] },
{ name: "Chris", items: [5] }
];
const sum = data.reduce((total, { items = [] }) => total + items.length, 0);
console.log(sum);
Try this:
var sum = 0;
for (var i = 0; i < data .length; i++) {
var object = arrayOfObjects[i];
sum += object.items.length;
}
Iterate over the objects in the JSON array in the for loop and add the sums of the items arrays

Diffing 2 arrays, i.e which items to add and remove

Given:
array of "current" objects [{id: '1'},{id: '2'},{id: '3'},{id: '4'},{id: '5'}]
array of "new" objects [{id: '1'},{id: '5'},{id: '6'},{id: '7'},{id: '8'}]
How to determine which objects
to add (not in "current") and
to remove (not in "new")?
In this case:
{id: '2'},{id: '3'},{id: '4'} should be removed
{id: '6'},{id: '7'},{id: '8'} should be added
Performance is not extremely important, my dataset is usually around 200.
Edit: I need to know about elements to be added/removed because "current" array correlates to DOM nodes and I don't just want to delete them all and add from scratch - already tried that and performance is far from perfect.
In both cases, this is a classic use case of the set operation "difference". All you need to do is define a difference function, and then apply it with current.difference(new) and new.difference(current).
function difference(a, b, compare) {
let diff = [];
for (let ai = 0; ai < a.length; ai++) {
let exists = false;
for (let bi = 0; bi < b.length; bi++) {
if (compare(a[ai], b[bi])) {
exists = true;
break;
}
}
if (!exists) diff.push(a[ai]);
}
return diff;
}
function getRemoved(oldA, newA) {
return difference(oldA, newA, (a, b) => a.id == b.id);
}
function getAdded(oldA, newA) {
return difference(newA, oldA, (a, b) => a.id == b.id);
}
let current = [{id: '1'}, {id: '2'}, {id: '3'}, {id: '4'}, {id: '5'}];
let newArr = [{id: '1'}, {id: '5'}, {id: '6'}, {id: '7'}, {id: '8'}];
console.log(getRemoved(current, newArr));
console.log(getAdded(current, newArr));
For each item in current that is not found in new. That item is deleted.
For each item in new that is not in current. That item is added.
You run the run these checks in two different loops one after the other.
You could go with something like this :
const currentElements = [{id: '1'},{id: '2'},{id: '3'},{id: '4'},{id: '5'}]
const newElements = [{id: '1'},{id: '5'},{id: '6'},{id: '7'},{id: '8'}]
const elementsToAdd = newElements.filter(e1 => !currentElements.find(e2 => e2.id === e1.id))
const elementsToRemove = currentElements.filter(e1 => !newElements.find(e2 => e2.id === e1.id))
console.log({elementsToAdd, elementsToRemove})
Basically, I take an array, and find the elements not contained in the other array.
For the elements to add, check the elements in newElements that are not in currentElements, and vice versa.
You could use Set data structure to calculate the different and filter the expeced values (using its has method). Time complexity for this approach will be linear (O(n))
Set use hash table so complexity for retrival/lookup is O(1) (doc, Theory > Lookup Speed)
const prev = [{ id: "1" }, { id: "2" }, { id: "3" }, { id: "4" }, { id: "5" }] // length n
const current = [
{ id: "1" },
{ id: "5" },
{ id: "6" },
{ id: "7" },
{ id: "8" },
] // length m
const prevIdSet = new Set(prev.map((o) => o.id)) // O(n)
const currentIdSet = new Set(current.map((o) => o.id)) // O(m)
function difference(setA, setB) {
let _difference = new Set(setA)
for (let elem of setB) {
_difference.delete(elem)
}
return _difference
}
const removedIdSet = difference(prevIdSet, currentIdSet) // O(m)
const addedIdSet = difference(currentIdSet, prevIdSet) // O(n)
const removed = prev.filter((o) => removedIdSet.has(o.id)) // O(n)
const added = current.filter((o) => addedIdSet.has(o.id)) // O(m)
console.log("removed", removed)
console.log("added", added)
// Total complexity O(constantA * n + constantB * m) ~ O(n + m)
You could use filter. Something like this for items to remove:
arr1.filter(function(i) {return arr2.indexOf(i) < 0;});
And swap the arrays for items to add. For example:
let arr1 = [{id: '1'},{id: '2'},{id: '3'},{id: '4'},{id: '5'}];
let arr2 = [{id: '1'},{id: '5'},{id: '6'},{id: '7'},{id: '8'}];
arr1 = arr1.map((obj) => obj.id);
arr2 = arr2.map((obj) => obj.id);
console.log("Remove these IDs: ", arr1.filter(function(i) {return arr2.indexOf(i) < 0;}));
console.log("Add these IDs: ", arr2.filter(function(i) {return arr1.indexOf(i) < 0;}));
You you only needs the ids: You could extract the ids with flatMap and than filter with every for not incudes.
let old = [{id: '1'},{id: '2'},{id: '3'},{id: '4'},{id: '5'}];
let act = [{id: '1'},{id: '5'},{id: '6'},{id: '7'},{id: '8'}];
let oldIds = old.flatMap(el => el.id);
let actIds = act.flatMap(el => el.id);
let add = actIds.filter(id => oldIds.every(old=> !old.includes(id)));
let del = oldIds.filter(id => actIds.every(act=> !act.includes(id)));
console.log('Add: ', add);
console.log('Del: ', del);

TypeScript - Take object out of array based on attribute value

My array looks like this:
array = [object {id: 1, value: "itemname"}, object {id: 2, value: "itemname"}, ...]
all my objects have the same attibutes, but with different values.
Is there an easy way I can use a WHERE statement for that array?
Take the object where object.id = var
or do I just need to loop over the entire array and check every item? My array has over a 100 entries, so I wanted to know if there was a more efficient way
Use Array.find:
let array = [
{ id: 1, value: "itemname" },
{ id: 2, value: "itemname" }
];
let item1 = array.find(i => i.id === 1);
Array.find at MDN: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find
I'd use filter or reduce:
let array = [
{ id: 1, value: "itemname" },
{ id: 2, value: "itemname" }
];
let item1 = array.filter(item => item.id === 1)[0];
let item2 = array.reduce((prev, current) => prev || current.id === 1 ? current : null);
console.log(item1); // Object {id: 1, value: "itemname"}
console.log(item2); // Object {id: 1, value: "itemname"}
(code in playground)
If you care about iterating over the entire array then use some:
let item;
array.some(i => {
if (i.id === 1) {
item = i;
return true;
}
return false;
});
(code in playground)
You can search a certain value in array of objects using TypeScript dynamically if you need to search the value from all fields of the object without specifying column
var searchText = 'first';
let items = [
{ id: 1, name: "first", grade: "A" },
{ id: 2, name: "second", grade: "B" }
];
This below code will search for the value
var result = items.filter(item =>
Object.keys(item).some(k => item[k] != null &&
item[k].toString().toLowerCase()
.includes(searchText.toLowerCase()))
);
Same approach can be used to make a Search Filter Pipe in angularjs 4 using TypeScript
I had to declare the type to get it to work in typescript:
let someId = 1
array.find((i: { id: string; }) => i.id === someId)
You'll have to loop over the array, but if you make a hashmap to link each id to an index and save that, you only have to do it once, so you can reference any objeft after that directly:
var idReference = myArray.reduce(function( map, record, index ) {
map[ record.id ] = index;
return map;
}, {});
var objectWithId5 = myArray[ idReference["5"] ];
This does assume all ids are unique though.

Categories

Resources