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"
}
]
*/
CodePudding user response:
The beneath provided solution implements an approach which uses preprocessed lookup data ...
- the
catIDs
set for existing catalog-IDs - and the
catSegments
object for existing combinations of catalog- and segment-IDs.
The intermediate result, an array of newly created and valid catalog items, then gets computed by iterating over an array of existing catID
s via flatMap
where for each catID
one tries to create an array of new catalog items by reduce
ing the passed segments
array. The validity of each possible catalog item is assured by looking up whether it already exists as a combination of the same catID
and segmentId
values.
function getUpdatedCatalogsAggregatedBySegments(
catalogs = [], segments = []
) {
// create lookup data,
// - the `catIDs` set for existing catalog-IDs
// - and the `catSegments` object for existing
// combinations of catalog- and segment-IDs.
const {
catIDs,
catSegments,
} = catalogs
.reduce((
{ catIDs, catSegments },
{ catID, segmentId, segmentName }
) => {
catIDs.add(catID);
catSegments[`${ catID }_${ segmentId }`] ??= segmentName;
return { catIDs, catSegments };
}, {
catIDs: new Set,
catSegments: {},
});
// try to create new/valid catalog items for each existing `catId`.
const newCatalogs = [...catIDs]
.flatMap(catID =>
segments
.reduce((list, { id: segmentId, name: segmentName }) => {
const itemKey = `${ catID }_${ segmentId }`;
if (!catSegments.hasOwnProperty(itemKey)) {
// update lookup.
catSegments[itemKey] = segmentName;
// push new/valid catalog item into the aggregating list.
list.push({ catID, segmentId, segmentName });
}
return list;
}, [])
);
// - return a new array of ...
// ... originally passed ...
return catalogs
// ... and newly computed data.
.concat(newCatalogs);
}
const catalogs = [
{ catID: 2, segmentId: '2', segmentName: 'S2' },
{ catID: 1, segmentId: '1', segmentName: 'S1' },
{ catID: 1, segmentId: '2', segmentName: 'S2' },
];
console.log(
"The OP's use case ... (catalogs, [{ id: '3', name: 'S3' }]) =>",
getUpdatedCatalogsAggregatedBySegments(
catalogs, [{ id: '3', name: 'S3' }]
),
);
const segments = [
{ id: '3', name: 'S3' },
{ id: '2', name: 'S3' },
{ id: '1', name: 'S3' },
];
const updatedCatalogs =
getUpdatedCatalogsAggregatedBySegments(catalogs, segments);
console.log(
"Another, more complex, use case ...", {
segments,
catalogs,
updatedCatalogs,
},
);
.as-console-wrapper { min-height: 100%!important; top: 0; }