I have an array of catalogs where catId
could be repeated.
Each catalog has a unique segmentId
const catalogs = [
{ catID: 2, segmentId: '2', segmentName: 'S2' },
{ catID: 1, segmentId: '1', segmentName: 'S1' },
{ catID: 1, segmentId: '2', segmentName: 'S2' }
];
Means against each catId
there could be multiple segmentIds but segmentIds can not be duplicated for that specific catId
Now I want to assign an array of segments to the main catalogs array:
e.g here is an example array of segments that I need to assign:
[{id: '3', name: 'S3'}]
I am expecting this response:
[
{ catID: 2, segmentId: '2', segmentName: 'S2' },
{ catID: 2, segmentId: '3', segmentName: 'S3' },
{ catID: 1, segmentId: '1', segmentName: 'S1' },
{ catID: 1, segmentId: '2', segmentName: 'S2' },
{ catID: 1, segmentId: '3', segmentName: 'S3' },
]
I also tried to solve this myself, I am very close but not getting the expecting result:
const catalogs = [
{ catID: 2, segmentId: '2', segmentName: 'S2' },
{ catID: 1, segmentId: '1', segmentName: 'S1' },
{ catID: 1, segmentId: '2', segmentName: 'S2' }
];
function addSegmentsToCatalogs(segments){
let catalogUid = catalogs[0].catID;
const lastCatalog = catalogs.slice(-1).pop();
const insertionArray = [];
catalogs.forEach(async (catalog) => {
if (catalogUid != catalog.catID) {
segments.forEach((segment) => {
insertionArray.push({
catalogId: catalogUid,
segmentId: segment.id,
segmentName: segment.name,
});
});
catalogUid = catalog.catID;
}
if (!segments.some((segment) => segment.id === catalog.segmentId)) {
segments.push({ id: catalog.segmentId, name: catalog.segmentName });
if (
catalog.catID == lastCatalog.catID &&
catalog.segmentId == lastCatalog.segmentId
) {
segments.forEach((segment) => {
insertionArray.push({
catalogId: catalogUid,
segmentId: segment.id,
segmentName: segment.name,
});
});
}
}
});
console.log(insertionArray);
return insertionArray;
}
addSegmentsToCatalogs([{id: '3', name: 'S3'}])
Can anyone help me find the issue or suggest me better approach to solve this?
CodePudding user response:
You could take nested Map
s and group by catID
and segmentId
.
map
looks like this:
2:
'2': { catID: 2, segmentId: '2', segmentName: 'S2' }
1:
'1': { catID: 1, segmentId: '1', segmentName: 'S1' }
'2': { catID: 1, segmentId: '2', segmentName: 'S2' }
After having a map with nested maps, you could take key of the outer map and add the new segements to new keys of the inner maps.
Finally take a flat array of all values.
const
add = (catalogs, segments) => {
const
map = catalogs.reduce((m, o) => m.set(
o.catID,
(m.get(o.catID) || new Map).set(o.segmentId, o)
), new Map);
segments.forEach(({ id: segmentId, name: segmentName }) =>
map.forEach((mm, catID) => mm.set(catID, { catID, segmentId, segmentName }))
);
return Array
.from(map.values(), m => Array.from(m.values()))
.flat();
},
catalogs = [
{ catID: 2, segmentId: '2', segmentName: 'S2' },
{ catID: 1, segmentId: '1', segmentName: 'S1' },
{ catID: 1, segmentId: '2', segmentName: 'S2' }
],
segments = [{ id: '3', name: 'S3' }],
result = add(catalogs, segments);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
CodePudding user response:
const catalogs = [{
catID: 2,
segmentId: '2',
segmentName: 'S2'
},
{
catID: 1,
segmentId: '1',
segmentName: 'S1'
},
{
catID: 1,
segmentId: '2',
segmentName: 'S2'
}
];
function addSegmentsToCatalogs(segments) {
const copyOfCatlogs = [...catalogs];
const catlogIds = catalogs.map(c => c.catID);
const uniqCatIds = [...new Set(catlogIds)];
const segIds = segments.map(s => s.id);
uniqCatIds.forEach(catId => {
segIds.forEach(segId => {
const hasCatIdWithSegment = copyOfCatlogs.findIndex(c => c.catID == catId && segId == c.segmentId);
if (hasCatIdWithSegment == -1) {
const newSegment = segments.find(s => s.id == segId);
copyOfCatlogs.push({
catID: catId,
segmentId: newSegment.id,
segmentName: newSegment.name,
});
}
})
})
console.log(copyOfCatlogs);
return copyOfCatlogs;
}
addSegmentsToCatalogs([{
id: '3',
name: 'S3'
}])
CodePudding user response:
Before getting to the answer: Your input data suggests that the pattern of the input segment name string is a capital letter S
followed by an integer sequence. If that's the case, a number
type would better represent the segmentId
value of each object in the output array for your dataset... a number
can be stringified using built-in methods with a 100% success rate — meaning that you can easily do that on demand later if you ever need the value as a string, but the reverse is not true: most strings cannot be parsed as numbers.
In context of the above, you can use a transform function to parse each input item into the desired output structure (and validate the values along the way). See the example code below:
class AssertionError extends Error {
override name = 'AssertionError';
}
function assert (expr: unknown, msg?: string): asserts expr {
if (!expr) throw new AssertionError(msg);
}
type OutputItem = {
catId: number;
segmentId: number;
segmentName: string;
};
function transform (item: InputItem): OutputItem {
const segmentName = item.name;
const catId = Number(item.id);
assert(Number.isInteger(catId), `Couldn't parse category ID as integer`);
const segmentId = Number(segmentName.slice(1));
assert(Number.isInteger(segmentId), `Couldn't parse segment ID as integer`);
return {
catId,
segmentId,
segmentName,
};
}
type InputSegmentName = `S${number}`;
type InputId = `${number}`;
type InputItem = { id: InputId; name: InputSegmentName; };
const input = [
{id: '2', name: 'S2'},
{id: '2', name: 'S3'},
{id: '1', name: 'S1'},
{id: '1', name: 'S2'},
{id: '1', name: 'S3'},
{id: '3', name: 'S3'},
] satisfies InputItem[];
const output = input.map(transform);
console.log(output); /* Logs:
[
{
"catId": 2,
"segmentId": 2,
"segmentName": "S2"
},
{
"catId": 2,
"segmentId": 3,
"segmentName": "S3"
},
{
"catId": 1,
"segmentId": 1,
"segmentName": "S1"
},
{
"catId": 1,
"segmentId": 2,
"segmentName": "S2"
},
{
"catId": 1,
"segmentId": 3,
"segmentName": "S3"
},
{
"catId": 3,
"segmentId": 3,
"segmentName": "S3"
}
]
*/
Compiled JS from the TS Playground:
"use strict";
class AssertionError extends Error {
constructor() {
super(...arguments);
this.name = 'AssertionError';
}
}
function assert(expr, msg) {
if (!expr)
throw new AssertionError(msg);
}
function transform(item) {
const segmentName = item.name;
const catId = Number(item.id);
assert(Number.isInteger(catId), `Couldn't parse category ID as integer`);
const segmentId = Number(segmentName.slice(1));
assert(Number.isInteger(segmentId), `Couldn't parse segment ID as integer`);
return {
catId,
segmentId,
segmentName,
};
}
const input = [
{ id: '2', name: 'S2' },
{ id: '2', name: 'S3' },
{ id: '1', name: 'S1' },
{ id: '1', name: 'S2' },
{ id: '1', name: 'S3' },
{ id: '3', name: 'S3' },
];
const output = input.map(transform);
console.log(output); /* Logs:
[
{
"catId": 2,
"segmentId": 2,
"segmentName": "S2"
},
{
"catId": 2,
"segmentId": 3,
"segmentName": "S3"
},
{
"catId": 1,
"segmentId": 1,
"segmentName": "S1"
},
{
"catId": 1,
"segmentId": 2,
"segmentName": "S2"
},
{
"catId": 1,
"segmentId": 3,
"segmentName": "S3"
},
{
"catId": 3,
"segmentId": 3,
"segmentName": "S3"
}
]
*/
Update in response to your comment: If your actual dataset is different (e.g. segment names aren't all in the format you showed in your question) such that the above observation doesn't apply, then — of course — you don't have to parse the segment identifier as a number:
Full code in the TS Playground
type OutputItem = {
catId: number;
segmentId: string;
segmentName: string;
};
function transform (item: InputItem): OutputItem {
const segmentName = item.name;
const catId = Number(item.id);
assert(Number.isInteger(catId), `Couldn't parse category ID as integer`);
const segmentId = segmentName.slice(1);
return {
catId,
segmentId,
segmentName,
};
}
Compiled JS from the TS Playground:
"use strict";
class AssertionError extends Error {
constructor() {
super(...arguments);
this.name = 'AssertionError';
}
}
function assert(expr, msg) {
if (!expr)
throw new AssertionError(msg);
}
function transform(item) {
const segmentName = item.name;
const catId = Number(item.id);
assert(Number.isInteger(catId), `Couldn't parse category ID as integer`);
const segmentId = segmentName.slice(1);
return {
catId,
segmentId,
segmentName,
};
}
const input = [
{ id: '2', name: 'S2' },
{ id: '2', name: 'S3' },
{ id: '1', name: 'S1' },
{ id: '1', name: 'S2' },
{ id: '1', name: 'S3' },
{ id: '3', name: 'S3' },
];
const output = input.map(transform);
console.log(output); /* Logs:
[
{
"catId": 2,
"segmentId": "2",
"segmentName": "S2"
},
{
"catId": 2,
"segmentId": "3",
"segmentName": "S3"
},
{
"catId": 1,
"segmentId": "1",
"segmentName": "S1"
},
{
"catId": 1,
"segmentId": "2",
"segmentName": "S2"
},
{
"catId": 1,
"segmentId": "3",
"segmentName": "S3"
},
{
"catId": 3,
"segmentId": "3",
"segmentName": "S3"
}
]
*/