javascript 如何将一个数组中的项目与另一个数组中的数据相乘并增强,而不创建副本?

iklwldmw  于 2022-12-21  发布在  Java
关注(0)|答案(7)|浏览(218)

我有一个目录数组,其中catId可以重复。
每个目录都有唯一的segmentId

const catalogs = [
  { catID: 3, segmentId: '3', segmentName: 'S3' },
  { 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可能有多个segmentId,但该特定catId的segmentId不能重复
现在,我要将线段数组指定给主目录数组:
例如,下面是我需要分配的段数组示例:

[{id: '3', name: 'S3'},{id: '4', name: 'S4'},{id: '5', name: 'S5'}]

我期待这样的回应:

[
  { catID: 3, segmentId: '3', label: { segmentIDName: '3_S3' } },
  { catID: 3, segmentId: '4', label: { segmentIDName: '4_S4' } },
  { catID: 3, segmentId: '5', label: { segmentIDName: '5_S5' } },
  { catID: 2, segmentId: '2', label: { segmentIDName: '2_S2' } },
  { catID: 2, segmentId: '3', label: { segmentIDName: '3_S3' } },
  { catID: 2, segmentId: '4', label: { segmentIDName: '4_S4' } },
  { catID: 2, segmentId: '5', label: { segmentIDName: '5_S5' } },
  { catID: 1, segmentId: '1', label: { segmentIDName: '1_S1' } },
  { catID: 1, segmentId: '2', label: { segmentIDName: '2_S2' } },
  { catID: 1, segmentId: '3', label: { segmentIDName: '3_S3' } },
  { catID: 1, segmentId: '4', label: { segmentIDName: '4_S4' } },
  { catID: 1, segmentId: '5', label: { segmentIDName: '5_S5' } }
]

我自己也尝试过解决这个问题,我已经很接近了,但没有得到预期的结果:

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'}])

任何人都可以帮助我找到这个问题或建议我更好的方法来解决这个问题吗?

5jvtdoz2

5jvtdoz21#

根据我的理解,您正在尝试基于传递的segments数组创建对象/将对象推送到catalogs数组中,并且segmentID对于每个catID都是唯一的。如果是,以下是实现此要求的步骤**:**

  • 使用Array#mapcatalogs数组上进行迭代,将segementId连接到segmentName中,并返回catID,即catIDArr
  • 现在,借助Set方法从catIDArr获取唯一catId,即uniqueCatIDs
  • 最后,迭代segments数组和uniqueCatIDs数组,将新对象压入catalogs数组。

现场演示**:**

// Input catalog array
const catalogs = [
  { catID: 3, segmentId: '3', segmentName: 'S3' },
  { 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' }
];

// segment array
let segments = [{id: '3', name: 'S3'},{id: '4', name: 'S4'},{id: '5', name: 'S5'}];

// Creating a new array based on the catalogs array as per the required updated property.
const catIDArr = catalogs.map(catalogObj => {
  catalogObj.label = {
    segmentIDName: `${catalogObj.segmentId}_${catalogObj.segmentName}`
  }
  delete catalogObj.segmentName;
  return catalogObj;
});

// Getting unique category ID which will use for the iteration and building the objects.
const uniqueCatIDs = [...new Set(catIDArr.map(obj => obj.catID))];

// Filtering out the duplicate segements from the segment array to get rid from the duplicate enteries.
segments = segments.filter(({ id }) => ![...new Set(catIDArr.map(obj => obj.segmentId))].includes(id))

// Pushing the new objects based on the segment array.
segments.forEach(obj => {
  uniqueCatIDs.forEach(id => {
    catIDArr.push({
      catID: id,
      segmentId: obj.id,
      label: {
        segmentName: `${obj.id}_${obj.name}`
      }
    })
  })
});

// output
console.log(catIDArr);
kzmpq1sx

kzmpq1sx2#

一些想法:
通过在不改变给定数据的情况下具有需要分组的元素的数据结构和需要笛卡尔积的另一数据集,可以通过具有其优点和缺点的至少两种方法来解决该任务:

  • 拿几张Map
  • 获取对象

拿一些Map

首先,您可以使用嵌套的Map并按catIDsegmentId分组。
外部的map如下所示:

2:
    '2': { catID: 2, segmentId: '2', label: { segmentIDName: "2_S2" } }
1:
    '1': { catID: 1, segmentId: '1', label: { segmentIDName: '1_S1' } }
    '2': { catID: 1, segmentId: '2', label: { segmentIDName: '2_S2' } }

然后,在保持给定数据集的情况下,迭代外部Map的密钥和segements,得到笛卡尔积。
最后将所有内部Map的值作为平面数组。
优点:

  • 干净利落,
  • 易于实现,
  • 按给定数据的顺序得出结果。

缺点:

  • 需要迭代Map和转换数据。

x一个一个一个一个x一个一个二个x

拿一个物体

这种方法遵循在迭代进行的同时更新直接结果的思想。
优点:

  • 没有一米五氮一,没有一米六氮一。
  • 结果直接可用。

缺点:

  • 需要在对象旁边跟踪catID/segmentId的可见组、catID的数组(类型保存)和结果。
  • 结果不符合catID的顺序。

一个三个三个一个

envsm3lx

envsm3lx3#

个人陈述

  • "我永远不会真正理解那些在没有深入研究的情况下对答案进行投票的人的态度或心态。因此,我真的很想知道为什么这个答案被认为没有用,即使它的方法已经与第一次实现确实解决了OP的问题,这与最后的编辑已被再次证明,而另一个从一开始到表决时都不符合要求的答案已经得到了两张有用的票。
  • 我知道人们可能会对我的文章和评论感到不快。这更多地反映了人们本身。对那些人来说,这种风格可能会显得强硬,甚至不友好;但是,难道不是精确和坚持让它听起来像这样吗?总是用温暖的声音读我的话,充满了对乐于助人的同情。"*
    编辑/备注-由于OP更改了最终数据格式的要求,因此在最初提供的解决方案中添加了一个小的代码重构。
    • 最初提供方法/解决方案-2022-12-09**

下面提供的解决方案实现了一种使用预处理的查找数据的方法...

  • 现有目录ID的catIDsset
  • catSegments对象用于目录ID和段ID的现有组合。

中间结果是新创建的有效目录项的数组,然后通过经由flatMap在现有catID的数组上迭代来计算,其中对于每个catID,试图通过对所传递的segments数组进行reduce运算来创建新目录项的数组。通过查找每个可能的目录项是否已作为相同catIDsegmentId值的组合存在,可以确保该目录项的有效性。

function getUpdatedCatalogsWithSegmentsAggregation(
  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);
}

// The OP's original use case.
const catalogs = [
  { catID: 2, segmentId: '2', segmentName: 'S2' },
  { catID: 1, segmentId: '1', segmentName: 'S1' },
  { catID: 1, segmentId: '2', segmentName: 'S2' },
];
const segments = [{ id: '3', name: 'S3' }];

console.log(
  "The expected result of the OP's original use case ...",
  getUpdatedCatalogsWithSegmentsAggregation(catalogs, segments),
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
    • 对上述原始解决方案-2022-12-11进行了其他更改**

修改后的示例代码保持上述原始实现不变,但引入了附加的map ping任务,该任务根据OP的数据结构变化为先前结果数组的每个目录项创建新项,因此Map函数接受OP的基本形式的目录项并创建新的标记目录项。
这也证明了所提供的第一方法的鲁棒性。
一个二个一个一个

rt4zxlrg

rt4zxlrg4#

我的previous answer是基于问题的前一个版本,而问题的当前版本似乎有了很大的变化(或者我只是误读了它)。无论如何,这似乎做了所需的事情,相当简单:

const groupBy = (fn) => (xs, k) => 
  xs .reduce ((a, x) => ((k = fn (x)), (a [k] ??= []), (a [k] .push (x)), a), {})
  
const addSegs = (catalogs, newSegs) => newSegs .reduce (
  (data, {id, name}) => data .map (([cat, o]) => [cat, Object .assign (o, {[id]: name})]),
  Object .entries (
    groupBy (x => `_${x .catID}`) (catalogs)
  ) .map (([catID, group]) => [
      catID, 
      Object .assign (...group .map (({segmentId, segmentName}) => ({[segmentId]: segmentName})))
  ])
) .flatMap (([catID, segs]) => Object .entries (segs) .map (([segmentId, segmentName]) => ({
  catID: Number (catID .slice (1)), segmentId, segmentName 
}))) 
.map (({catID, segmentId, segmentName}) => ({catID, segmentId, label: { segmentIDName: `${segmentId}_${segmentName}`}}))

const catalogs = [{catID: 3, segmentId: '3', segmentName: 'S3'}, {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'}]
const newSegs = [{id: '3', name: 'S3'}, {id: '4', name: 'S4'}, {id: '5', name: 'S5'}]

console .log (addSegs (catalogs, newSegs))
.as-console-wrapper {max-height: 100% !important; top: 0}

我认为将最后一行注解掉会更符合逻辑,在这种情况下,它给出的输出更像这样,没有label字段及其合并的数据属性:

[
  {"catID": 3, "segmentId": "3", "segmentName": "S3"},
  {"catID": 3, "segmentId": "4", "segmentName": "S4"},
  {"catID": 3, "segmentId": "5", "segmentName": "S5"},
  {"catID": 2, "segmentId": "2", "segmentName": "S2"},
  {"catID": 2, "segmentId": "3", "segmentName": "S3"},
  {"catID": 2, "segmentId": "4", "segmentName": "S4"},
  {"catID": 2, "segmentId": "5", "segmentName": "S5"},
  {"catID": 1, "segmentId": "1", "segmentName": "S1"},
  {"catID": 1, "segmentId": "2", "segmentName": "S2"},
  {"catID": 1, "segmentId": "3", "segmentName": "S3"},
  {"catID": 1, "segmentId": "4", "segmentName": "S4"},
  {"catID": 1, "segmentId": "5", "segmentName": "S5"}
]

在这两种情况下,逻辑都与这里的许多其他逻辑类似,首先使用一个非常标准的实用函数groupBycatalogs转换为:

{
  _3: [
    {catID: 3, segmentId: "3", segmentName: "S3"}
  ],
  _2: [
    {catID: 2, segmentId: "2", segmentName: "S2"}, 
    {catID: 2, segmentId: "3", segmentName: "S3"}
  ],
  _1:[
    {catID: 1, segmentId: "1", segmentName: "S1"}, 
    {catID: 1, segmentId: "2", segmentName: "S2"}, 
    {catID: 1, segmentId: "3", segmentName: "S3"}
  ]
}

然后使用Object .entries将其转换为键值对数组,其中键类似于_3,值是{catID, segmentId, segmentName}对象数组(添加前导下划线是为了避免在迭代Object时对键进行数字排序,最后将其删除)。
然后使用.map,我们将它们转换为更简单的格式:

[
    [_3, {3: "S3"}],
    [_2, {2: "S2", 3: "S3"}],
    [_1, {1: "S1", 2: "S2", 3: "S3"}]
]

对于newSegs .reduce,我们将新段添加到每个段中,得到以下结果:

[
    [_3, {3: "S3", 4: "S4", 5: "S5"}],
    [_2, {2: "S2", 3: "S3", 4: "S4", 5: "S5"}],
    [_1, {1: "S1", 2: "S2", 3: "S3", 4: "S4", 5: "S5"}]
]

通过.flatMap调用,我们对属性进行了再水合,使结构扁平化,并顺便删除了用于排序的_,从而得到了上面的首选结构。
最后,我们使用{label}子对象将结果.map到新对象中。
如果这确实是您想要的格式,那么我建议将.flatmap.map调用替换为:

.flatMap (([catID, segs]) => Object .entries (segs) .map (([segmentId, segmentName]) => ({
  catID: Number (catID .slice (1)), segmentId, label: { segmentIDName: `${segmentId}_${segmentName}`}
})))

它也会做同样的事情。

nue99wik

nue99wik5#

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'
}])
db2dz4w8

db2dz4w86#

在得到答案之前:输入数据表明输入段名称字符串的模式是大写字母S后跟整数序列。number类型最好表示数据集输出数组中每个对象的segmentId值... number可以使用内置方法进行字符串化,成功率为100%-这意味着,如果以后需要字符串形式的值,可以根据需要轻松地执行此操作,但反之则不成立:大多数字符串不能被解析为数字。
在上面的上下文中,您可以使用转换函数将每个输入项解析为所需的输出结构(并在此过程中验证值)。请参见下面的示例代码:
TSPlayground

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

从TS Playground编译的JS:

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

根据您的意见更新:如果您的实际数据集不同(例如,段名称不完全符合您在问题中显示的格式),以致上述观察结果不适用,那么-当然-您不必将段标识符解析为数字:
TSPlayground的完整代码

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,
  };
}

从TS Playground编译的JS:

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

hlswsv357#

我们可以通过添加一个groupBy实用函数来相当简单地完成这一任务,这在许多库中都可以找到。

const groupBy = (fn) => (xs, k) => 
  xs .reduce ((a, x) => ((k = fn (x)), (a [k] ??= []), (a [k] .push (x)), a), {})

const addSegs = (catalogs, newSegs) => 
  Object .entries (groupBy (x => x .catID) (catalogs)) .flatMap (([catID, records]) =>
    records .concat (newSegs .map (({id, name}) => ({
      catID: Number (catID),
      segmentId: id,
      segmentName: name
    })))
  )

const catalogs = [{catID: 2, segmentId: '2', segmentName: 'S2'}, {catID: 1, segmentId: '1', segmentName: 'S1'}, {catID: 1, segmentId: '2', segmentName: 'S2'}]
const newSegs = [{id: '3', name: 'S3'}]

console .log (addSegs (catalogs, newSegs))
.as-console-wrapper {max-height: 100% !important; top: 0}

我们首先使用groupBy基于共享的catID将输入分成多个组,得到如下结果:

{
    "1": [
        {catID: 1, segmentId: "1", segmentName: "S1"},
        {catID: 1, segmentId: "2", segmentName: "S2"}
    ],
    "2": [
        {catID: 2, segmentId: "2", segmentName: "S2"}
    ]
}

在上面调用Object .entries会得到这样的结果:

[
  ["1", [{"catID": 1, "segmentId": "1", "segmentName": "S1"}, {"catID": 1, "segmentId": "2", "segmentName": "S2"}]], 
  ["2", [{"catID": 2, "segmentId": "2", "segmentName": "S2"}]]
]

现在,我们通过调用flatMap,传递一个Map到新片段的函数,创建与主记录匹配的对象,来转换每个片段并将结果扁平化。将它们附加到记录中并返回整个集合。唯一有点奇怪的是,我们必须通过将catID Package 在Number中来转换catID。我们需要这样做是因为当我们在groupBy中创建一个带有数字键的对象时,它们会在幕后被转换成字符串,我们可以使用Map来代替,这样就避免了这个问题,但是会增加一些稍微复杂的语法。
我们可以很容易地将groupBy函数内联到主函数中,但这是一个可广泛重用的函数;如果您还没有使用Ramda、lodash或Underscore之类的工具,我建议您在实用程序库中保存一些这样的工具。

相关问题