skip to Main Content

I need to iterate over an array of strings, count the occurrences of each string, and return a object with the value and the number of occurrences.

I’m trying to use the array reduce function to achieve such thing, but, despite the attempts, I haven’t been able to achieve that yet.

So I have the following:

["tony", "tony", "tony", "tony", "kassandra", "tony", "tony", "kassandra"]

I need the output:

[{ name: "tony", matches: 6 }, { name: "kassandra", matches: 2 }]

What should I use to obtain the above output?

I have tried the following:

const names = nameList.map((user) => user.name);

const reduce = names.reduce((p, c) => {
  if (p == c) {
    return { name: p, matches: `idk what put here yet` };
  }
  return c;
});

3

Answers


  1. Firstly, count the occurrences of each name in the array.
    Secondly, convert it into an object with the desired format of name and their matches.

    const names = ["tony", "tony", "tony", "tony", "kassandra", "tony", "tony", "kassandra"];
    const counts = {};
    //1.
    names.forEach(name => {
      if (counts[name]) {
        counts[name] += 1;
      } else {
        counts[name] = 1;
      }
    });
    //2.
    const result = Object.keys(counts).map(name => {
      return {
        name: name,
        matches: counts[name]
      };
    });
    console.log(result);

    If you want to use .reduce() approach to counting occurrences of names:

    const counts = names.reduce((acc, name) => {
        acc[name] = (acc[name] || 0) + 1;
        return acc;
      }, {});
    

    Another approach to convert occurrences back into an object using Object.entries()

    Object.entries(counts).map(([name, matches]) => ({ name, matches }));
    

    Two approaches combined:

    const names = ["tony", "tony", "tony", "tony", "kassandra", "tony", "tony", "kassandra"];
    const counts = names.reduce((acc, name) => {
        acc[name] = (acc[name] || 0) + 1;
        return acc;
      }, {});
    const result = Object.entries(counts).map(([name, matches]) => ({ name, matches }));
    console.log(result);
    Login or Signup to reply.
  2. A pure Array::reduce() Typescript solution.

    We provide an aggregate object (r in the callback) where we store 2 values: the result array arr with the desired output and a map map where we can easily find an existing array items to increment found matches:

    A live typescript gist is here.

    const names = ["tony", "tony", "tony", "tony", "kassandra", "tony", "tony", "kassandra"];
    
    interface MatchItem {
      name: string, 
      matches: number
    };
    
    const result = names.reduce((r, name) => {
      const {arr, map} = r;
      const item = map.get(name);
      item ? item.matches++ : map.set(name, arr[arr.length] = { name, matches: 1 });
      return r;
    }, { arr: [], map: new Map } as {arr: MatchItem[], map: Map<string, MatchItem>}).arr;
    
    console.log(result);
    

    JS version:

    const names = ["tony", "tony", "tony", "tony", "kassandra", "tony", "tony", "kassandra"];
    
    const result = names.reduce((r, name) => {
      const {arr, map} = r;
      const item = map.get(name);
      item ? item.matches++ : map.set(name, arr[arr.length] = { name, matches: 1 });
      return r;
    }, { arr: [], map: new Map }).arr;
    
    console.log(result);

    And a benchmark:

    enter image description here

    <script benchmark data-count="1">
    
    // randomly fill with names
    const src = 'tony kassandra alex peter mike arnold tina maria stephen nicolas samuel sonya natali elena julia'.split(' ');
    
    let i = 16000000;
    const names = [];
    while (i--) {
        names.push(src[Math.floor(Math.random() * src.length)]);
    }
    
    // @benchmark XMehdi01's solution
    const counts = names.reduce((acc, name) => {
        acc[name] = (acc[name] || 0) + 1;
        return acc;
    }, {});
    Object.entries(counts).map(([name, matches]) => ({ name, matches }));
    
    //@benchmark Alexander's solution
    names.reduce((r, name) => {
        const {arr, map} = r;
        const item = map.get(name);
        item ? item.matches++ : map.set(name, arr[arr.length] = { name, matches: 1 });
        return r;
    }, { arr: [], map: new Map }).arr;
    
    </script>
    <script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
    Login or Signup to reply.
  3. To achieve the desired output, you can use the reduce function along with an object to store the counts of each name. Here’s an example of how you can implement it:

    const nameList = ["tony", "tony", "tony", "tony", "kassandra", "tony", "tony", "kassandra"];
    
    const countMatches = nameList.reduce((acc, curr) => {
      if (acc[curr]) {
        acc[curr]++;
      } else {
        acc[curr] = 1;
      }
      return acc;
    }, {});
    
    const result = Object.entries(countMatches).map(([name, matches]) => ({ name, matches }));
    
    console.log(result);
    

    Output:

    
    [
      { name: 'tony', matches: 6 },
      { name: 'kassandra', matches: 2 }
    ]
    

    In the code above, the reduce function is used to iterate over the nameList array. The accumulator (acc) is an object that keeps track of the count for each name. If the current name (curr) already exists as a property in the accumulator, the count is incremented by 1. Otherwise, a new property is created with the name as the key and an initial count of 1.

    After the reduce operation, we use Object.entries to convert the object back into an array of key-value pairs. Then, we use map to transform each key-value pair into an object with name and matches properties. Finally, the result is stored in the result variable.

    Note: In your attempted code, you were comparing p and c using the equality operator (==) which would only compare the values, not the count of occurrences. Additionally, you didn’t handle the case where the name is not equal to the previous one.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search