如何获取对象数组并减少它,以便组合重复对象键处的数据?

Posted

技术标签:

【中文标题】如何获取对象数组并减少它,以便组合重复对象键处的数据?【英文标题】:How do I take an array of objects and reduce it so that data at a repeated object key is combined? 【发布时间】:2021-10-11 12:54:58 【问题描述】:

我正在开发一个模仿零售网站的 React 应用程序。我的主页显示一个项目,下面有相关产品的卡片组件。当我单击其中一个相关产品上的按钮时,我会打开一个比较模式,用于比较当前产品和点击产品的功能。我认为要实现这一点,我将创建一个包含点击产品和主页产品的组合功能的数组。我一直在努力创建一个对象数组,其中每个独特的功能都有一个对象,其中包含有关功能的数据以及该功能所属的产品。

截至目前,我已经能够获得两个产品具有的所有功能的数组,但是如果产品具有重叠的功能,则该数组会重复。这让我不确定如何呈现比较表,因为我计划映射数组并为每个特征创建一个表行。我当前格式化这些功能的代码如下:

formatFeatures: (currentProd, clickedProd) => 
let combinedFeatures = [];
if (clickedProd.features) 
  clickedProd.features.forEach(feature => 
    let obj = 
    let vals = Object.values(feature);
    obj[vals[0]] = [vals[1], clickedProd.id]
    combinedFeatures.push(obj)
  )

currentProd.features.forEach(feature => 
  let obj = 
  let vals = Object.values(feature);
  obj[vals[0]] = [vals[1], currentProd.id]
  combinedFeatures.push(obj)
)

let formattedFeatures = combinedFeatures.reduce((allFeatures, feature) => 
  if (Object.keys(feature) in allFeatures) 
    allFeatures = [allFeatures[Object.keys(feature)]].concat(feature);
   else 
    allFeatures.push(feature);
  
  return allFeatures;
, [])

这样的结果是:

[
  "Fabric": ["100% Cotton", 28214]
, 
  "Cut": ["Skinny", 28214]
, 
  "Fabric": ["Canvas", 28212]
, 
  "Buttons": ["Brass", 28212]
]

这与我正在寻找的非常接近,其中我有一组对象,其中包含有关产品的功能和产品 ID 的信息,但是“Fabric”中的重复是我正在努力解决的问题.理想情况下,结果如下所示:

[
  "Fabric": ["100% Cotton", 28214],
  ["Canvas", 28212]
, 
  "Cut": ["Skinny", 28214]
, 
  "Buttons": ["Brass", 28212]
]

如果有人可以帮助指导我如何更改我的格式化功能以完成此操作,我将不胜感激。或者,如果有人知道一种更好的方法,可以根据我当前的结果,为每个独特功能动态设置单行格式的表格,那也很棒。

进入我的辅助函数的数据如下:

当前产品:


  "id": 28212,
  "name": "Camo Onesie",
  "slogan": "Blend in to your crowd",
  "description": "The So Fatigues will wake you up and fit you in. This high energy camo will have you blending in to even the wildest surroundings.",
  "category": "Jackets",
  "default_price": "140.00",
  "created_at": "2021-07-10T17:00:03.509Z",
  "updated_at": "2021-07-10T17:00:03.509Z",
  "features": [
    "feature": "Fabric",
    "value": "Canvas"
  , 
    "feature": "Buttons",
    "value": "Brass"
  ]

点击产品:


  "name": "Morning Joggers",
  "category": "Pants",
  "originalPrice": "40.00",
  "salePrice": null,
  "photo": "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80",
  "id": 28214,
  "features": [
    "feature": "Fabric",
    "value": "100% Cotton"
  , 
    "feature": "Cut",
    "value": "Skinny"
  ]

【问题讨论】:

没有,谢谢大家 【参考方案1】:

您已经在两者中循环了一次。不减就可以得到。

const formatFeatures = (currentProd, clickedProd) => 
  const formattedFeatures = ;

  if (clickedProd.features) 
    clickedProd.features.forEach(feature => 
      const vals = Object.values(feature);

      if (!formattedFeatures.hasOwnProperty(vals[0])) 
        formattedFeatures[vals[0]] = [];
      
      formattedFeatures[vals[0]].push([vals[1], clickedProd.id]);
    );
  

  currentProd.features.forEach(feature => 
    const vals = Object.values(feature);

    if (!formattedFeatures.hasOwnProperty(vals[0])) 
      formattedFeatures[vals[0]] = [];
    
    formattedFeatures[vals[0]].push([vals[1], currentProd.id]);
  )

  return formattedFeatures;


const currentProd = 
  "id": 28212,
  "name": "Camo Onesie",
  "slogan": "Blend in to your crowd",
  "description": "The So Fatigues will wake you up and fit you in. This high energy camo will have you blending in to even the wildest surroundings.",
  "category": "Jackets",
  "default_price": "140.00",
  "created_at": "2021-07-10T17:00:03.509Z",
  "updated_at": "2021-07-10T17:00:03.509Z",
  "features": [
    "feature": "Fabric",
    "value": "Canvas"
  , 
    "feature": "Buttons",
    "value": "Brass"
  ]
;
const clickedProd = 
  "name": "Morning Joggers",
  "category": "Pants",
  "originalPrice": "40.00",
  "salePrice": null,
  "photo": "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80",
  "id": 28214,
  "features": [
    "feature": "Fabric",
    "value": "100% Cotton"
  , 
    "feature": "Cut",
    "value": "Skinny"
  ]
;
console.log(formatFeatures(currentProd, clickedProd));
.as-console-wrapper  min-height: 100%!important; top: 0; 

【讨论】:

这种方法不会将任何东西推入formattedFeatures 基本数组,但它确实(错误地)将其用作对象/地图/索引。也许最好将其初始化更改为const formattedFeatures = ; 我在 console.log 中添加了它输出的值 我将累积/聚合formattedFeatures 的类型从Array 更改为纯Object,因为它被用作后者而不是前者。因此,SO 中的登录现在也显示了目标结构,而不仅仅是[]【参考方案2】:

首先需要修复/优化目标数据结构。看起来 OP 确实专注于基于通用 Feature 的东西(如 FabricCut按钮),而这些特征值似乎与Product 相关联更多。因此,对于一个相同的功能,值对于产品功能来说是唯一的。为了不丢失产品信息,目标格式的特征项需要反映其相关产品的id 属性。

一个可行且仍然足够灵活的目标数据结构可能看起来像这样......


  "Fabric": [
    productId: 28214,
    value: "100% Cotton",
  , 
    productId: 28212,
    value: "Canvas",
  ],
  "Cut": [
    productId: 28214,
    value: "Skinny",
  ],
  "Buttons": [
    productId: 28212,
    value: "Brass",
  ],

任何方法都应从productfeatures 列表的数据规范化映射过程开始,其中每个功能项 将获得其product相关id已分配。

因此,像 feature: "Buttons", value: "Brass" 这样的功能项会临时映射到 productId: 28212, feature: "Buttons", value: "Brass"

现在可以将两个规范化的数据项列表连接起来并最终处理/简化为最终的目标结构...

function mergeBoundProductId(item) 
  return  ...this, ...item ;

function aggregateProductFeatureValueLists(index, productFeature) 
  const  feature, ...featureValue  = productFeature;
  const featureList = index[feature] ??= [];
  //const featureList = index[feature] || (index[feature] = []);

  featureList.push(featureValue);

  return index;


function createIndexOfProductFeatureValues(clickedProd, currentProd) 
  const  features:clickedFeatures  = clickedProd;
  const  features:currentFeatures  = currentProd;

  return [

    ...clickedFeatures.map(mergeBoundProductId,  productId: clickedProd.id ),
    ...currentFeatures.map(mergeBoundProductId,  productId: currentProd.id ),

  ].reduce(aggregateProductFeatureValueLists, );


const currentProduct = 
  id: 28212,
  name: "Camo Onesie",
  // ... more properties ...
  features: [
    feature: "Fabric",
    value: "Canvas",
  , 
    feature: "Buttons",
    value: "Brass",
  ],
;
const clickedProduct = 
  name: "Morning Joggers",
  // ... more properties ...
  id: 28214,
  features: [
    feature: "Fabric",
    value: "100% Cotton",
  , 
    feature: "Cut",
    value: "Skinny",
  ],
;

console.log(
  'createIndexOfProductFeatureValues(clickedProduct, currentProduct) ...',
  createIndexOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper  min-height: 100%!important; top: 0; 

将代码分解为专用进程的优势在于更容易重构,例如更改了目标结构,例如更接近 OP 正在寻找的结构。

reducer 函数的变化很小。这只是两个变化,每个变化都几乎看不到它的行......

function mergeBoundProductId(item) 
  return  ...this, ...item ;

function aggregateProductFeatureValueLists(index, productFeature) 
  const  feature, productId, value  = productFeature;
  const featureList = index[feature] ??= [];

  featureList.push([value, productId]);

  return index;


function createIndexOfProductFeatureValues(clickedProd, currentProd) 
  const  features:clickedFeatures  = clickedProd;
  const  features:currentFeatures  = currentProd;

  return [

    ...clickedFeatures.map(mergeBoundProductId,  productId: clickedProd.id ),
    ...currentFeatures.map(mergeBoundProductId,  productId: currentProd.id ),

  ].reduce(aggregateProductFeatureValueLists, );


console.log(
  'createIndexOfProductFeatureValues(clickedProduct, currentProduct) ...',
  createIndexOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper  min-height: 100%!important; top: 0; 
<script>
  const currentProduct = 
    id: 28212,
    name: "Camo Onesie",
    // ... more properties ...
    features: [
      feature: "Fabric",
      value: "Canvas",
    , 
      feature: "Buttons",
      value: "Brass",
    ],
  ;
  const clickedProduct = 
    name: "Morning Joggers",
    // ... more properties ...
    id: 28214,
    features: [
      feature: "Fabric",
      value: "100% Cotton",
    , 
      feature: "Cut",
      value: "Skinny",
    ],
  ;
</script>

最后一个示例的目的也是为了证明易于重构的代码库的优势。

这里主函数从createIndexOfProductFeatureValues重命名为createListOfProductFeatureValues

它的实现也发生了类似的变化,但只是在使用其初始值调用 reducer 函数的方式上。

reducer 函数也没有显着变化,只是累加/聚合collector 对象的处理方式不同。

结果是一个干净的基于数组的对象结构......

function mergeBoundProductId(item) 
  return  ...this, ...item ;

function aggregateProductFeatureValueLists(collector, productFeature) 
  const  feature, productId, value  = productFeature;
  const  index, list  = collector;
  const featureItem = index[feature] ??=  feature, values: [] ;

  if (featureItem.values.length === 0) 
    list.push(featureItem);
  
  featureItem.values.push([value, productId]);

  return collector;


function createListOfProductFeatureValues(clickedProd, currentProd) 
  const  features:clickedFeatures  = clickedProd;
  const  features:currentFeatures  = currentProd;

  return [

    ...clickedFeatures.map(mergeBoundProductId,  productId: clickedProd.id ),
    ...currentFeatures.map(mergeBoundProductId,  productId: currentProd.id ),

  ].reduce(aggregateProductFeatureValueLists,  index: , list: [] ).list;


console.log(
  'createListOfProductFeatureValues(clickedProduct, currentProduct) ...',
  createListOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper  min-height: 100%!important; top: 0; 
<script>
  const currentProduct = 
    id: 28212,
    name: "Camo Onesie",
    // ... more properties ...
    features: [
      feature: "Fabric",
      value: "Canvas",
    , 
      feature: "Buttons",
      value: "Brass",
    ],
  ;
  const clickedProduct = 
    name: "Morning Joggers",
    // ... more properties ...
    id: 28214,
    features: [
      feature: "Fabric",
      value: "100% Cotton",
    , 
      feature: "Cut",
      value: "Skinny",
    ],
  ;
</script>

【讨论】:

【参考方案3】:

似乎还有一个更大的问题是如何构建数据。你说理想情况下你的结果应该是这样的:

[
  
    "Fabric":
      ["100% Cotton",28214],
      ["Canvas",28212]
  ,
  
    "Cut":
      ["Skinny",28214]
  ,
  
    "Buttons":
      ["Brass",28212]
  
]

但您真正想要摆脱的是每个项目特征的行和关联值的组合列表(如果存在)。然后,您真正需要的只是要显示的每一行的键数组,以及允许您通过该键访问所需属性的对象。

键数组可能如下所示:

["Fabric", "Cut", "Buttons"]

您想要使用这些键访问属性的对象,例如您的CurrentProd,可能是这样的(请注意,您可以通过调用CurrentProd.features["FeatureName"] 访问功能):


  "id":28212,
  "name":"Camo Onesie",
// ... //
  "features":  
    "Fabric": "Canvas",
    "Buttons": "Brass"
  

话虽如此,要获得这些东西,您可以通过减少 CurrentProd.featuresClickedProd.features 的组合数组来获得键数组,我们将其称为 allFeatureKeys

const allFeatureKeys = [
    ...CurrentProd.features,
    ...ClickedProd.features
  ].reduce((acc, cur) => 
      return acc.findIndex(cur.feature) > -1 ? [...acc, cur.feature] : acc
    ,
    []
  );

您可以通过减少其特征数组来将 CurrentProd 修改为上述数据形状,我们称之为modifiedCurrentProd

const modifiedCurrentProd = 
  ...CurrentProd,
  features: CurrentProd.features.reduce((acc, cur) => 
    return ...acc, [cur.feature]: cur.value 
  , )

modifiedClickedProd 对象重复此操作,然后您可以在创建表值时同时查找 CurrentProd.features 和 ClickedProd.features 值。

仅作为示例,由于我不知道您的反应结构或您要显示的数据,因此您可以渲染表行中的值映射到键上以生成每一行,并且对于每个功能键,您从 modifiedCurrentProdmodifiedClickedProd 对象的 features 属性访问值:

  <div id="table">
    allFeatureKeys.map((featureKey) => 
       return <div id="table-row">
         <div>featureKey</div>
         <div>
            
             modifiedCurrentProd.features[featureKey] !== undefined 
               ? modifiedCurrentProd.id 
               : "n/a"
           
         </div>
         <div>
           
             modifiedClickedProd.features[featureKey] !== undefined
               ? modifiedClickedProd.id
               : "n/a"
           
         </div>
       </div>
     )
  </div>

【讨论】:

这真的有助于改变我处理这个问题的方式,非常感谢。我最终成功地走上了这条路线。需要注意的是,在 allFeatureKeys.reduce() 中,我遇到了类型错误,它警告“Fabric 不是函数”。我相信这是因为三元的回报是相反的。如果找到索引,则只返回累加器。如果未找到索引,则添加该功能。 没问题!我只是一个代码猴子,所以..会犯错误。 reduce 函数就是一个例子。更大的图景是正确设置数据。您绝对可以以一种可能更高效/更适合您的风格的方式编写代码,但这不是重点。

以上是关于如何获取对象数组并减少它,以便组合重复对象键处的数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何组合 2 个对象并在其中合并数组 [重复]

如何获取对数组中对象的引用[重复]

如何将数组内的对象和每个对象相乘,获取一个或多个键并减少它们的值?

如何在 JSON 文件中导出对象数组,然后使用 es6 导入 [重复]

我将如何减少这个数组,以便每个对象中的对象被合并 javascript 但不知道键值名称

从数组中获取对象并推送到新数组[重复]