How to sort Javascript objects in an array using regex

Viewed 175

I'm making a call to an API and getting an array with large amounts of objects. There are hundreds of objects in the array and a short snippet of it looks something like this:

[
    {
      "name": "total_kills_glock",
      "value": 70
    },
    {
      "name": "total_kills_mac10",
      "value": 39
    },
    {
      "name": "total_kills_ump45",
      "value": 136
    },
    {
      "name": "total_shots_glock",
      "value": 1262
    },
    {
      "name": "total_hits_glock",
      "value": 361
    }
    {
      "name": "total_shots_mac10",
      "value": 862
    },
    {
      "name": "total_hits_mac10",
      "value": 261
    },
    {
      "name": "total_shots_ump45",
      "value": 1610
    },
    {
      "name": "total_hits_ump45",
      "value": 598
    }
]

Is there a way to sort the array using regex to look something like this:

[
  {
    "name": "glock",
    "kills": 70,
    "shots": 1262,
    "hits": 361
  },
  {
    "name": "mac10",
    "kills": 39,
    "shots": 862,
    "hits": 261
  },
  {
    "name": "ump45",
    "kills": 136,
    "shots": 1610,
    "hits": 598
  }
]
6 Answers

You can use reduce() method to group the array items, split() method to extract name and action from name string.

var data = [{ "name": "total_kills_glock", "value": 70 }, { "name": "total_kills_mac10", "value": 39 }, { "name": "total_kills_ump45", "value": 136 }, { "name": "total_shots_glock", "value": 1262 }, { "name": "total_hits_glock", "value": 361 }, { "name": "total_shots_mac10", "value": 862 }, { "name": "total_hits_mac10", "value": 261 }, { "name": "total_shots_ump45", "value": 1610 }, { "name": "total_hits_ump45", "value": 598 } ];

var result = data.reduce((acc, curr) => {
  let words = curr.name.split('_');
  let name = words[2];
  let action = words[1];

  let item = acc.find(item => item.name === name);

  if (item) {
    item[action] = curr.value;
  } else {
    acc.push({
      "name": name,
      [action]: curr.value
    });
  }

  return acc;
}, []);

console.log(result);

Here's one way to do it, using regex to extract the name and data type from the raw name, and then building an object based on that:

const raw = [{
    "name": "total_kills_glock",
    "value": 70
  },
  {
    "name": "total_kills_mac10",
    "value": 39
  },
  {
    "name": "total_kills_ump45",
    "value": 136
  },
  {
    "name": "total_shots_glock",
    "value": 1262
  },
  {
    "name": "total_hits_glock",
    "value": 361
  },
  {
    "name": "total_shots_mac10",
    "value": 862
  },
  {
    "name": "total_hits_mac10",
    "value": 261
  },
  {
    "name": "total_shots_ump45",
    "value": 1610
  },
  {
    "name": "total_hits_ump45",
    "value": 598
  }
];

var final = [];
var keys = [];
raw.forEach(v => {
  const m = v.name.match(/^total_([^_]+)_(.+)$/);
  const k = keys.indexOf(m[2]);
  if (k == -1) {
    var o = { name: m[2] };
    o[m[1]] = v.value;
    final.push(o);
    keys.push(m[2]);
  } else {
    final[k][m[1]] = v.value;
  }
});
console.log(final);

Kindly check this out.

let arr=[] //your array
let map=new Map();
arr.forEach((obj)=>{
let x=obj.name.split('_');
map.set(x[2],{"name":x[2],...map.get(x[2]),[x[1]]:obj.value})
}
);
console.log([...map.values()]);

Feel free to use my repl https://repl.it/repls/PoisedEasyZettabyte in case you want it.

You could split the string and destructure the items and take an object as hash table for the same group. At the end take the values from the hash table.

var data = [{ name: "total_kills_glock", value: 70 }, { name: "total_kills_mac10", value: 39 }, { name: "total_kills_ump45", value: 136 }, { name: "total_shots_glock", value: 1262 }, { name: "total_hits_glock", value: 361 }, { name: "total_shots_mac10", value: 862 }, { name: "total_hits_mac10", value: 261 }, { name: "total_shots_ump45", value: 1610 }, { name: "total_hits_ump45", value: 598 }],
    result = Object.values(data.reduce((r, { name, value }) => {
        var [, type, name] = name.split('_');
        r[name] = r[name] || { name };
        r[name][type] = value;
        return r;
    }, {}));

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

Perhaps a loop would help a bit more then using regex to create a new object, not sure I've ever seen that before.

var rawData = [ { "name": "total_kills_glock", "value": 70 }, { "name": "total_kills_mac10", "value": 39 }, { "name": "total_kills_ump45", "value": 136 }, { "name": "total_shots_glock", "value": 1262 }, { "name": "total_hits_glock", "value": 361 }, { "name": "total_shots_mac10", "value": 862 }, { "name": "total_hits_mac10", "value": 261 }, { "name": "total_shots_ump45", "value": 1610 }, { "name": "total_hits_ump45", "value": 598 } ];

var gunStats = [{
    "name": "glock",
    "kills": 0,
    "shots": 0,
    "hits": 0
},
{
    "name": "mac10",
    "kills": 0,
    "shots": 0,
    "hits": 0
  },
  {
    "name": "ump45",
    "kills": 0,
    "shots": 0,
    "hits": 0
  }
];

rawData.forEach((item)  => { 
    if (item.name.includes("glock")) {
        applyStatsToGun(item, "glock");
    }

    if (item.name.includes("mac10")) {
        applyStatsToGun(item, "mac10");
    }

    if (item.name.includes("ump45")) {
        applyStatsToGun(item, "ump45");
    }

});


function applyStatsToGun(item, gunName) {
    if (item.name.includes(gunName)) {

        if (item.name.includes("kills")) {
            var gun = gunStats.find((gun) => gun.name == gunName);
            gun.kills += item.value;
        }

        if (item.name.includes("shots")) {
            var gun = gunStats.find((gun) => gun.name == gunName);
            gun.shots += item.value;
        }

        if (item.name.includes("hits")) {
            var gun = gunStats.find((gun) => gun.name == gunName);
            gun.hits += item.value;
        }
    }
}


console.log(gunStats);

And if you don't know want to hard code the gun name strings, say there's hundreds first you'd want to get a list of all unique names iterating through the list to and parse the 3rd element in name.split("_")[3] then iterating through that list and applying the value instead of hard coding those values

Below is a pure functional solution, which combines a couple of useful techniques. Note, that you can re-use partial functions somewhere else in the code. Also, this way the calculation can be parallelized and distributed (although JavaScript is not there yet).

 let raw = [
    {"name": "total_kills_glock", "value": 70},
    {"name": "total_kills_mac10", "value": 39},
    {"name": "total_kills_ump45", "value": 136},
    {"name": "total_shots_glock", "value": 1262},
    {"name": "total_hits_glock", "value": 361},
    {"name": "total_shots_mac10", "value": 862},
    {"name": "total_hits_mac10", "value": 261},
    {"name": "total_shots_ump45", "value": 1610},
    {"name": "total_hits_ump45", "value": 598}
];

//you can use any parsing technique here, including regexp
const parse = name => { 
    const a = name.split("_"); 
    return { key: a[2], metric: a[1] }
};

const groupBy = getKey => (rv, x) => {
    (rv[getKey(x)] = rv[getKey(x)] || []).push(x);
    return rv;
};

//converts [{key,metric,value},...] to {name:key, m1:v1, m2:v2,...} 
const recombine = arr => Object.assign({name: arr[0].key},
    ...Array.from(arr, ({key, metric, value}) => ({[metric]: value}) ));

const groupedByKey = raw
            .map( ({name, ...attrs}) => ({...parse(name), ...attrs}))
            .reduce(groupBy(x => x.key),{});

const result = Object.values(groupedByKey).map(recombine);
console.log(result);

I did not benchmark it, but I would not be surprised, if it performed better than Nick's solution with certain datasets. That said, it might not be as easy to maintain, depending on the team and skills.

Related