Search code examples
javascriptarraysdata-structureslookupreduce

filtering array with multiple same properties


so I have an array of values like this

const foodsList = [
  {
    foodOrigin: "Padang",
    foodName: "Nasi Padang",
    originCode: "PDN"
  },
  {
    foodOrigin: "Padang",
    foodName: "Gulai",
    originCode: "PDN"
  },
  {
    foodOrigin: "Padang",
    foodName: "Rendang",
    originCode: "PDN"
  },
  {
    foodOrigin: "Palembang",
    foodName: "Pempek",
    originCode: "PLG"
  },
  {
    foodOrigin: "Palembang",
    foodName: "Tekwan",
    originCode: "PLG"
  },
  {
    foodOrigin: "Yogyakarta",
    foodName: "Gudeg",
    originCode: "YKT"
  }
];

and I want to filter the array so the result would be like this instead

const filteredFoodsList = [
  {
    foodOrigin: "Padang",
    originCode: "PDN"
  },
  {
    foodOrigin: "Palembang",
    originCode: "PLG"
  },
  {
    foodOrigin: "Yogyakarta",
    originCode: "YKT"
  }
];

In order to achieve the result, I tried doing it like below but is there a cleaner and better way to do this? ( especially since my actual array's data consist of more than 500 )

const filteredFoodsList = [];
for (let i = 0; i < foodsList.length; i++) {
  if (i === 0) {
    filteredFoodsList.push({
      originCode: foodsList[i].originCode,
      foodOrigin: foodsList[i].foodOrigin
    });
  }
  let isExist = false;
  for (let j = 0; j < filteredFoodsList.length; j++) {
    if (foodsList[i].originCode === filteredFoodsList[j].originCode) {
      isExist = true;
    }
  }
  if (!isExist) {
    filteredFoodsList.push({
      originCode: foodsList[i].originCode,
      foodOrigin: foodsList[i].foodOrigin
    });
  }
}

Solution

  • As the OP stated there should be some focus on performance, so..

    The Map could be the fastest way to collect unique values.
    (I've borrowed Array.from(map, ([originCode, foodOrigin]) => ({ foodOrigin, originCode })) from Nina's answer, really elegant).

    But it doesn't affect the performance since the resulting array should be quite small.

    But she forgot to include a Map::has() check that really improves the performance significantly.

    What's important is how fast the source array is iterated. As always I see there can't be a faster thing than for(let i = 0; ...)...

    const foodsList=[{foodOrigin:"Padang",foodName:"Gulai",originCode:"PDN"},{foodOrigin:"Padang",foodName:"Rendang",originCode:"PDN"},{foodOrigin:"Palembang",foodName:"Pempek",originCode:"PLG"},{foodOrigin:"Palembang",foodName:"Tekwan",originCode:"PLG"},{foodOrigin:"Yogyakarta",foodName:"Gudeg",originCode:"YKT"}];
    
    const map = new Map;
    for (let i = 0; i < foodsList.length; i++) {
        const item = foodsList[i];
        map.has(item.originCode) || map.set(item.originCode, item.foodOrigin);
    }
    
    const result = Array.from(map, ([originCode, foodOrigin]) => ({ foodOrigin, originCode }));
    
    console.log(result);

    And a benchmark:

    ` Chrome/121
    --------------------------------------------------------------------------------------
    >                         n=10000     |   n=100000    |   n=1000000    |  n=10000000  
    Alexander's solution   1.23x x10k 526 | 1.00x x1k 518 | 1.00x x100 543 | 1.00x x10 554
    Nina's improved        1.00x x10k 429 | 1.35x x1k 700 | 1.32x x100 717 | 1.31x x10 727
    Peter's solution       1.00x x10k 427 | 1.44x x1k 746 | 1.35x x100 732 | 1.37x x10 760
    Nina's solution        1.23x x10k 524 | 1.55x x1k 801 | 1.50x x100 815 | 1.46x x10 811
    --------------------------------------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    
    ` Firefox/122
    ---------------------------------------------------------------------------------------
    >                         n=10000     |    n=100000    |   n=1000000    |  n=10000000  
    Alexander's solution   1.00x x10k 355 | 1.00x  x1k 363 | 1.00x x100 383 | 1.00x x10 380
    Nina's improved        3.63x  x1k 129 | 3.64x x100 132 | 3.86x  x10 148 | 3.68x  x1 140
    Peter's solution       4.00x  x1k 142 | 3.55x x100 129 | 3.39x  x10 130 | 3.95x  x1 150
    Nina's solution        4.08x  x1k 145 | 4.16x x100 151 | 3.97x  x10 152 | 4.03x  x1 153
    ---------------------------------------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    

    <script>
    
        const chunk = [{ foodOrigin: "Padang", foodName: "Gulai", originCode: "PDN" }, { foodOrigin: "Padang", foodName: "Rendang", originCode: "PDN" }, { foodOrigin: "Palembang", foodName: "Pempek", originCode: "PLG" }, { foodOrigin: "Palembang", foodName: "Tekwan", originCode: "PLG" }, { foodOrigin: "Yogyakarta", foodName: "Gudeg", originCode: "YKT" }];
        const $chunk = [];
        let count = 2000;
        while (count--) {
            $chunk.push(...chunk);
        }
        const $input = [];
        const foodsList = $input;
        
        // @benchmark Peter's solution
    
        foodsList
          .reduce((collector, { foodOrigin, originCode = null }) => {
    
            if (originCode !== null && !collector.lookup.has(foodOrigin)) {
    
              collector.lookup.set(foodOrigin, true);
              collector.result.push({
                foodOrigin,
                originCode,
              });
            }
            return collector;
    
          }, { lookup: new Map, result: [] }).result;
    
        // @benchmark Nina's solution
        Array.from(
            foodsList.reduce((m, { foodOrigin, originCode }) => m.set(foodOrigin, originCode), new Map),
            ([foodOrigin, originCode]) => ({ foodOrigin, originCode })
        );
    
        // @benchmark Alexander's solution
        const map = new Map;
        for (let i = 0; i < foodsList.length; i++) {
            const item = foodsList[i];
            map.has(item.originCode) || map.set(item.originCode, item.foodOrigin);
        }
    
        Array.from(map, ([originCode, foodOrigin]) => ({ foodOrigin, originCode }));
    
        // @benchmark Nina's improved
    
        Array.from(
            foodsList.reduce((m, { foodOrigin, originCode }) => {
                m.has(foodOrigin) || m.set(foodOrigin, originCode);
                return m;
            }, new Map),
            ([foodOrigin, originCode]) => ({ foodOrigin, originCode })
        );
    
    
    </script>
    <script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>