Set new Sequence in Object from Array after deletion of specific object - javascript

My Goal:
I need to have a continuous sequence of numbers in the sequenceIndex which is a value in my object.
So when I remove a specific object the sequence index of the other objects is of course not continuous anymore. The object I remove is being checked against a specific value to see whether there are other objects in the array which share the same value (second if-statement). If so then there should be a new value set which is continuous.
The output is that the iterator in the if-statement is always the same for all objects manipulated.
From this:
const objectsArray = [
{
folder: "folderName",
documents: [
{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 1,
sequenceIndex: "1",
documentType: "letter"
},
{
id: 2,
sequenceIndex: "2",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "3",
documentType: "letter"
}
]
}
];
By removing id 1 and 2 I would like to come to this (see continuous sequenceIndex):
const desiredObjectsArray = [
{
folder: "folderName",
documents: [
{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "1",
documentType: "letter"
}
]
}
];
My code so far:
case ActionType.RemoveDocumentInSpecificFolder:
return state.map(file => {
// if in the correct folder remove the object with the delivered id
if (file.folder=== folder) {
remove(file.documents, {
id: action.payload.documents[0].id
});
// create newObjArray from objects which share a specific value and replace the sequence index by new value
const newObjArray = file.documents.map((obj: any) => {
// if the object has the specific value create new object with new sequenceIndex
if (obj.documentType === action.payload.documents[0].documentType) {
//poor attempt to create a sequence
let i = 0;
const correctedSequenceDocObject = { ...obj, sequenceIndex: i };
i++;
return correctedSequenceDocObject;
}
return {
...obj
};
});
return {
...file,
documents: newObjArray
};
}
return file;
});
I hope someone can guide me in the right direction. I would also always appreciate a suggestion of best practice :)
Best regards

You can use filter and map something like this
const arr = [{folder: "folderName",documents: [{id: 0,sequenceIndex: "0",documentType: "letter"},{id: 1,sequenceIndex: "1",documentType: "letter"},{id: 2,sequenceIndex: "2",documentType: "letter"},{id: 3,sequenceIndex: "3",documentType: "letter"}]}];
let getInSequence = (filterId) => {
return arr[0].documents.filter(({ id }) => !filterId.includes(id))
.map((v, i) => ({ ...v, sequenceIndex: i }))
}
console.log(getInSequence([1, 2]))

As commented:
This is the classic case where .filter().map() will be useful. filter the data and then use .map((o, i) => ({ ...obj, sequenceIndex: i+1 }) )
Following is the sample:
const objectsArray = [{
folder: "folderName",
documents: [{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 1,
sequenceIndex: "1",
documentType: "letter"
},
{
id: 2,
sequenceIndex: "2",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "3",
documentType: "letter"
}
]
}];
const ignoreIds = [1, 2]
const updatedDocs = objectsArray[0].documents
.filter(({
id
}) => !ignoreIds.includes(id))
.map((doc, index) => ({ ...doc,
sequenceIndex: index
}));
console.log(updatedDocs)
Now lets cover your attempt
const newObjArray = file.documents.map((obj: any) => {
// For all the unmatching objects, you will have undefined as object as you are using `.map`
// This will make you `newObjArray: Array<IDocument | undefined>` which can break your code.
if (obj.documentType === action.payload.documents[0].documentType) {
// This will set it as 0 in every iteration making i as 0 always.
let i = 0;
const correctedSequenceDocObject = { ...obj, sequenceIndex: i };
i++;
return correctedSequenceDocObject;
}
return { ...obj };
});
An alternate with single loop:
Idea:
Create a loop using Array.reduce and pass it a blank array as list.
Add a check and inside it, push value to this list.
For sequenceIndex, fetch last element and fetch its sequenceIndex. Add one and set it again.
const newObjArray = file.documents.reduce((acc: Array<IDocument>, obj: any) => {
if (obj.documentType === action.payload.documents[0].documentType) {
const sequenceIndex: number = (!!acc[acc.length - 1] ? acc[acc.length - 1].sequenceIndex : 1) + 1;
acc.push({ ...obj, sequenceIndex });
}
return acc;
});

The solution I used now to this problem was:
let count = 0;
const newObject = file.documents.map(obj => {
if (obj.documentType === firstDocument.documentType) {
count++;
return { ...obj, sequenceIndex: count - 1 };
}
return obj;
});
Both of the provided answers were not able to handle objects which were out of interest because of the different documentType so they dropped the object. with this solution, I am checking against the last element and increasing the count if the last element was the same documentType.

Related

Create unique values from duplicates in Javascript array of objects

I have an array of duplicated objects in Javascript. I want to create an array of unique objects by adding the index of occurrence of the individual value.
This is my initial data:
const array= [
{name:"A"},
{name:"A"},
{name:"A"},
{name:"B"},
{name:"B"},
{name:"C"},
{name:"C"},
];
This is expected end result:
const array= [
{name:"A-0"},
{name:"A-1"},
{name:"A-2"},
{name:"B-0"},
{name:"B-1"},
{name:"C-0"},
{name:"C-1"},
];
I feel like this should be fairly simple, but got stuck on it for a while. Can you please advise how I'd go about this? Also if possible, I need it efficient as the array can hold up to 1000 items.
EDIT: This is my solution, but I don't feel like it's very efficient.
const array = [
{ name: "A" },
{ name: "A" },
{ name: "C" },
{ name: "B" },
{ name: "A" },
{ name: "C" },
{ name: "B" },
];
const sortedArray = _.sortBy(array, 'name');
let previousItem = {
name: '',
counter: 0
};
const indexedArray = sortedArray.map((item) => {
if (item.name === previousItem.name) {
previousItem.counter += 1;
const name = `${item.name}-${previousItem.counter}`;
return { name };
} else {
previousItem = { name: item.name, counter: 0};
return item;
}
});
Currently you are sorting it first then looping over it, which may be not the most efficient solution.
I would suggest you to map over it with a helping object.
const a = [{name:"A"},{name:"A"},{name:"A"},{name:"B"},{name:"B"},{name:"C"},{name:"C"},], o = {};
const r = a.map(({ name }) => {
typeof o[name] === 'number' ? o[name]++ : o[name] = 0;
return { name: `${name}-${o[name]}` };
});
console.log(r);
Keep a counter, and if the current name changes, reset the counter.
This version mutates the objects. Not sure if you want a copy or not. You could potentially sort the array by object name first to ensure they are in order (if that's not already an existing precondition.)
const array = [
{ name: "A" },
{ name: "A" },
{ name: "A" },
{ name: "B" },
{ name: "B" },
{ name: "C" },
{ name: "C" },
];
let name, index;
for (let i in array) {
index = array[i].name == name ? index + 1 : 0;
name = array[i].name;
array[i].name += `-${index}`;
}
console.log(array);
Another way, if you don't want to sort, and don't want to mutate any objects, is to use a map and keep track of the current index for each object.
const array = [
// NOTE: I put the items in mixed up order.
{ name: "A" },
{ name: "C" },
{ name: "A" },
{ name: "B" },
{ name: "A" },
{ name: "C" },
{ name: "B" },
];
let index = {};
let next = name => index[name] = index[name] + 1 || 0;
let result = array.map(obj => ({ ...obj, name: obj.name + '-' + next(obj.name) }));
console.log(result);

Comparing two array of objects and replacing an object with another

I have a working function that appends an array of objects customData to the end of another array of objects testData. If an object with the same property name value appears in both arrays, then the testData object is removed and replaced with the customData object in the resulting array. The customData object takes on the order of the previous testData object.
This is my attempt however, I'm wondering if there is a better way of doing this which is also easy to read (es6)?
Thanks
https://codesandbox.io/s/recursing-river-bdp5k?file=/src/App.js
export default function App() {
const testData = [
{ display: "1", name: "TEST1" },
{ display: "2", name: "TEST2" }
];
const customData = [
{ display: "CUSTOM_1", name: "TEST1", custom: "YES" },
{ display: "CUSTOM_3", name: "TEST3", custom: "YES" }
];
let i = 0;
const newTestData = testData;
let newCustomData = customData;
while (i < customData.length) {
const view = customData[i];
const index = testData.findIndex(x => x.name === view.name);
if (index >= 0) {
newTestData[index] = customData[i];
newCustomData.splice(i, 1);
}
i += 1;
}
const concatData = newTestData.concat(newCustomData);
console.log(concatData)
return null;
}
Array#concat does not mutate the arrays, there's no need to assign them to new variables (which doesn't copy the arrays anyway). If you're seeking to use concise ES6 code, skip the while loop - there are numerous equivalents. Here's one example:
You don't define "better way" but I'll interpret it to mean "most optimized for performance and readability". In the below approach I use one pass to populate a map, and another pass to overwrite the map entries with customData where needed, and finally a Object.values() (technically a third pass) to produce the results. This is O(n) (no nested loops) versus your O(n^2) implementation.
const testData = [
{ display: "1", name: "TEST1" },
{ display: "2", name: "TEST2" }
];
const customData = [
{ display: "CUSTOM_1", name: "TEST1", custom: "YES" },
{ display: "CUSTOM_3", name: "TEST3", custom: "YES" }
];
let map = {};
testData.forEach(item => map[item.name] = item);
customData.forEach(item => map[item.name] = item);
const result = Object.values(map);
console.log(result);
For many cases including this one, where you have a bunch of data identified by a unique key (such as name), you really ought to be using objects to begin with. Any time you need an array, there's Object.values() and Object.keys().
Logic behind your code is correct.
This is almost the same, but without explicit loops:
const testData = [
{ display: "1", name: "TEST1" },
{ display: "2", name: "TEST2" }
];
const customData = [
{ display: "CUSTOM_1", name: "TEST1", custom: "YES" },
{ display: "CUSTOM_3", name: "TEST3", custom: "YES" }
];
Array.prototype.uniqueWith = function(comparator) {
return this.reduce((a, c, i) => {
const j = a.slice(i+1).findIndex(e => comparator(e, c));
if(j !== -1) {
a[i] = a[i+j+1];
a.splice(i+j+1, 1);
}
return a;
}, this);
}
const eqByName = (a, b) => a.name === b.name;
const result = [...testData, ...customData].uniqueWith(eqByName);
console.log(result);
Take a note that extending Array prototype may not be the best idea, so you may create separate function, which will take concatenated array as an argument.
Is this the kind of function you're looking for?
const result = App(myTestData(), myCustData());
console.log(result);
function App(testData, custData) {
// `indByName` returns index (in arr) of obj w/ matching name (or -1 if no match)
const indByName = (arr, name) =>
ind = arr.findIndex(o => o.name === name);
let custInd; // Identifier for output of `indByName`
const custDataClone = custData.slice(); // Prevents mutating `custData`
return testData.map(item => (
// Uses indByName to get index of corresponding custom item
custInd = indByName(custDataClone, item.name),
// Moves corresponding custom item into testData if appropriate
custInd > -1 ? custDataClone.splice(custInd, 1)[0] : item
// Appends remaining items from custDataClone to the new array
)).concat(custDataClone)
}
function myTestData(){
return [
{ display: "1", name: "TEST1" },
{ display: "2", name: "TEST2" }
];
}
function myCustData(){
return [
{ display: "CUSTOM_1", name: "TEST1", custom: "YES" },
{ display: "CUSTOM_3", name: "TEST3", custom: "YES" }
];
}

How to add a value to an array in a specific location with JS

I have an array of objects:
const array = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 }
];
and I need to add another entry to it, but it needs to be placeable within any location in the array. So for example:
array.push({ id: 5, after_id: 2 }); and this should place the new entry between ids 2 and 3. Is there some standard way of doing this?
#p.s.w.g Has posted what is probably the best solution in a comment already, but I thought I'd post my original solution here as an answer now this is reopened.
You can use some to iterate through the array until the correct index is found, then you can slice the array and insert the item at the relevant index:
const arrayTest = [{
id: 1
},
{
id: 2
},
{
id: 3
},
{
id: 4
}
];
const insertAfterId = (array, item, idAfter) => {
let index = 0;
array.some((item, i) => {
index = i + 1;
return item.id === idAfter
})
return [
...array.slice(0, index),
item,
...array.slice(index, array.length),
];
};
const result = insertAfterId(arrayTest, {
id: 6
}, 2)
console.dir(result)

Get array of all unique object values based on property name

How can I get an array with all the unique values based on a property name?
In my case my object looks like this and I want an array with the unique documentID's.
const file = {
invoice: {
invoiceID: 1,
documentID: 5
},
reminders: [
{
reminderID: 1,
documentID: 1
},
{
reminderID: 2,
documentID: 1
}
]
}
The result should be an array [5, 1] //The unique documentID's are 5 and 1
It doesn't seem like possible to add a property name to the Object.values() function.
You can use Set to get unique documentID.
const file = {
invoice: {
invoiceID: 1,
documentID: 5
},
reminders: [
{
reminderID: 1,
documentID: 1
},
{
reminderID: 2,
documentID: 1
}
],
payments: {
documentID : 5
}
};
var keys = Object.keys(file).map(key=>file[key].map ? file[key].map(i=>i.documentID) : file[key].documentID)
var keysFlattened= [].concat.apply([], keys);
var unique = new Set(keysFlattened);
console.log(Array.from(unique));
I use something like this that does what you want I think
const keepUniqueBy = key => (array, item) => {
if (array.find(i => item[key] === i[key])) {
return array;
} else {
return [ ...array, item ];
}
};
Then you can simply: const unique = reminders.reduce(keepUniqueBy('documentID'))
NB: It's probably low performing, but for small arrays it doesn't matter.

Compare 2 arrays and assign matching value [duplicate]

This question already has answers here:
Merge property from an array of objects into another based on property value lodash
(5 answers)
Closed 4 years ago.
I have 2 array of objects
The first one called data:
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
]
and the second called subs:
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
]
In which I want to compare that if they have the same ID, the subs array will pass its name value to it and if it does not match that it puts a '-' in the data array, try this way:
data.forEach((d)=>{
subs.forEach((s)=>{
if(d.id === s.id){
d.subname = s.name;
}
else {
d.subname = '-';
}
});
});
But always assign the values with '-' as if it does not match any. What part am I doing wrong? Is there any other simpler way to do this? I would greatly appreciate your help.
The size of the subs array may vary.
It looks like you are not exiting the inner loop when a successful match is found.
In the first example where you are looking for a match for Piero, in your first iteration 1===1 and d.subname is correctly set to 'Temprano'. However, you then continue to compare the values- 1 !== 4 so Temprano is overwritten with '-', and 1 !== 7 so it is overwritten again.
An alternate approach:
data.forEach(d => {
const match = subs.find(s => s.id === d.id);
d.subname = match ? match.name : '-';});
I'd also recommend adding a case where you're not expecting to find a match, so you can see that it works in both cases!
https://codepen.io/anon/pen/MGGBLP?editors=0010
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
];
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
];
// by caching one of the arrays in an object, it reduces the run time to linear.
const obj = subs.reduce((acc, item) => {
acc[item.id] = item;
return acc;
})
data.forEach(d => {
if (d.id in obj) {
d.subname = obj[d.id].name;
} else {
d.subname = '-';
}
});
console.log(data);
You just need two lines for this:
var findIds = id => subs.find(findId => findId.id === id);
data.forEach(findId => Object.assign(findId, findIds(findId.id)));
Your data array object should now include the name property from it's respective id sharing object in subs array.
jsFiddle: https://jsfiddle.net/AndrewL64/9k1d3oj2/1/

Categories

Resources