You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确向Set添加唯一对象?Recipe食材去重异常排查

问题

我开发了一个包含Recipe数组的应用,每个Recipe包含ingredients子数组,想要提取所有Recipe中的唯一食材。当前实现为遍历所有食材,调用getEmptyIngredient创建空Calculation对象并加入Set,但控制台打印的Set中对象已被填充,且因对象引用差异导致相同食材被重复添加。此外,计算逻辑在打印之后执行,我疑惑为何打印时对象已被修改。

当前代码如下:

calculate(recipes: Recipe[]): Observable<Calculation[]> {

    let calculations: Calculation[] = [];
    let uniqueIngredients = new Set();

    for (let i = 0; i < recipes.length; i++) {
      for (let j = 0; j < recipes[i].ingredients.length; j++) {
        // First get all the unique ingredients        
        uniqueIngredients.add(this.getEmptyIngredient(recipes[i].ingredients[j]));
      }
    }

    console.log('unique ingredients', uniqueIngredients);

    let $this = this;
    uniqueIngredients.forEach(function (value) {
      let ingredientCalculation = value as Calculation;
      for (let i = 0; i < recipes.length; i++) {
        for (let j = 0; j < recipes[i].ingredients.length; j++) {
          if (ingredientCalculation.ingredient.definition.name === recipes[i].ingredients[j].definition.name) {
            // Calculation already exists so update it
            let calculation: Calculation = $this.calculateIngredient(recipes[i].ingredients[j], recipes[i].quantity);

            ingredientCalculation.quantityMeasurement += calculation.quantityMeasurement;
            ingredientCalculation.quantityUnits += calculation.quantityUnits;
            ingredientCalculation.totalCost += calculation.totalCost;
          }
        }
      }

      calculations.push(ingredientCalculation);
    });

    return of(calculations);
}

private getEmptyIngredient(ingredient: Ingredient): Calculation {
    let calculation: Calculation = {
      ingredient: ingredient,
      quantityUnits: 0,
      quantityMeasurement: 0,
      totalCost: 0
    };

    return calculation;
}

控制台打印结果显示存在多个相同名称(如Apple)的对象,且属性值不为0。我理解对象相等性的问题,但不明白为何打印时对象已被修改,毕竟计算逻辑在打印之后执行?

解答

1. 为什么打印时对象已经被修改?

这是因为浏览器/Node.js的控制台对引用类型(比如对象)采用延迟求值机制。你调用console.log时,只是把对象的引用传给了控制台,但控制台真正渲染对象内容的时候,后面的循环已经修改了这些对象的属性。

简单说,console.log不会立刻生成对象的当前状态快照,而是保存了对象的引用,当你展开控制台里的对象条目时,才会去读取它的最新值——这时候你的计算逻辑已经执行完毕,所以看到的是修改后的属性。

如果想要打印对象的即时状态,可以在console.log时把对象转成JSON字符串:

console.log('unique ingredients', JSON.stringify([...uniqueIngredients], null, 2));

这样就能看到调用log时对象的真实初始状态了。

2. Set无法去重相同食材的问题

Set判断元素相等是基于引用相等的,你每次调用getEmptyIngredient都会创建一个新的Calculation对象,哪怕它们关联的食材名称相同,也是不同的引用,所以Set会把它们全部存储进去。

修复后的代码示例

calculate(recipes: Recipe[]): Observable<Calculation[]> {
    // 先按食材名称分组,收集所有相同名称的食材实例
    const ingredientMap = new Map<string, Ingredient[]>();
    for (const recipe of recipes) {
        for (const ingredient of recipe.ingredients) {
            const name = ingredient.definition.name;
            if (!ingredientMap.has(name)) {
                ingredientMap.set(name, []);
            }
            ingredientMap.get(name)!.push(ingredient);
        }
    }

    // 遍历分组后的食材,创建并计算每个唯一食材的Calculation
    const calculations: Calculation[] = [];
    for (const [name, ingredients] of ingredientMap) {
        // 用第一个食材实例初始化空的Calculation
        const calculation = this.getEmptyIngredient(ingredients[0]);
        // 遍历所有该名称的食材,累加计算值
        for (const ingredient of ingredients) {
            // 找到当前食材所属的食谱
            const recipe = recipes.find(r => r.ingredients.includes(ingredient))!;
            const calcResult = this.calculateIngredient(ingredient, recipe.quantity);
            calculation.quantityMeasurement += calcResult.quantityMeasurement;
            calculation.quantityUnits += calcResult.quantityUnits;
            calculation.totalCost += calcResult.totalCost;
        }
        calculations.push(calculation);
    }

    return of(calculations);
}

private getEmptyIngredient(ingredient: Ingredient): Calculation {
    return {
        ingredient: ingredient,
        quantityUnits: 0,
        quantityMeasurement: 0,
        totalCost: 0
    };
}

修复思路说明

  • 先用Map按食材名称分组,确保相同名称的食材被归到同一组,自然实现“唯一食材”的筛选
  • 针对每个分组创建一个Calculation对象,再遍历组内所有食材实例,累加计算结果
  • 既解决了Set的引用相等问题,也避免了重复创建Calculation对象,逻辑更简洁高效

内容的提问来源于stack exchange,提问作者sterix24

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 17:11:16