Home > Enterprise >  javascript - add new record to each category only once
javascript - add new record to each category only once

Time:12-09

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 Maps 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:

TS Playground

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"
  }
]
*/

  • Related