1

I have this array:

var markers = [
  {
    "type":"Chocolate",
    "name":"KitKat",
    "group":"candy",
    "icon":"candy",
    "coords":[5246,8980],
  },
  {
    "type":"Fruit",
    "name":"Orange",
    "group":"fruits",
    "icon":"fruis",
    "coords":[9012,5493],
  },
  {
    "type":"Fruit",
    "name":"Banana",
    "group":"fruits",
    "icon":"fruis",
    "coords":[9012,5493],
  },
  {
    "type":"Food",
    "name":"Rice",
    "group":"foods",
    "icon":"foods",
    "coords":[6724,9556],
  },
  {
    "type":"Food",
    "name":"Meat",
    "group":"foods",
    "icon":"foods",
    "coords":[6724,9556],
  },
  {
    "type":"Food",
    "name":"Beam",
    "group":"foods",
    "icon":"foods",
    "coords":[6724,9556],
  },
  {
    "type":"Liquid",
    "name":"Water",
    "group":"liquids",
    "icon":"liquids",
    "coords":[6724,9556],
  },
  {
    "type":"Liquid",
    "name":"Coffe",
    "group":"liquids",
    "icon":"liquids",
    "coords":[6724,9556],
  },
]

Which I want to count how many items each group has in this array.

I managed to count it with this:

var count = []

for (var i = 0; i < markers.length; i++) {
  count[markers[i].group] = count[markers[i].group] + 1 || 1 ;
}

Which outputs this result:

count = [
candy: 1
foods: 3
fruits: 2
liquids: 2
]

I want to use this values in another part, and for that I need to change the array structure, to something like this:

count = [
{"item": "candy","qnt":1},
{"item": "foods","qnt":3},
{"item": "fruits","qnt":2},
{"item": "liquids","qnt":2}
]

I know that I could do something like this:

var total_fruits = 0;
for (var i = 0; i < markers.length; i++) {
  if (markers[i].group == "fruits"){
    total_fruits++
  }
}

But imagine how many if's I will need for a group of more than 50 types...

I will use the values in the html part with the same class that the item value is like this:

<ul>
  <li class="candy">
    <span class="qnt">1</span>
  </li>
  <li class="fruits">
    <span class="qnt">2</span>
  </li>
  <li class="foods">
    <span class="qnt">3</span>
  </li>
  <li class="liquids">
    <span class="qnt">2</span>
  </li>
</ul>

Any suggestions or how to improve this?

3
  • In your original code, you are using an array as an object which works but it's wrong. Commented Nov 28, 2018 at 17:44
  • @ibrahimmahrir Isn't that what they already have (barring the incorrect use of array type)? Commented Nov 28, 2018 at 17:46
  • It would possibly be easiest to do what your already doing, and then take that resulting object, and convert it into the list of objects like you want. So you don't have to find the element in the array for your group each time Commented Nov 28, 2018 at 17:47

9 Answers 9

4

You can build the object you want in one step with reduce(). This will build an object keyed to group. To get just the array, take the Object.values() of that object:

var markers = [{"type":"Chocolate","name":"KitKat","group":"candy","icon":"candy","coords":[5246,8980],},{"type":"Fruit","name":"Orange","group":"fruits","icon":"fruis","coords":[9012,5493],},{"type":"Fruit","name":"Banana","group":"fruits","icon":"fruis","coords":[9012,5493],},{"type":"Food","name":"Rice","group":"foods","icon":"foods","coords":[6724,9556],},{"type":"Food","name":"Meat","group":"foods","icon":"foods","coords":[6724,9556],},{"type":"Food","name":"Beam","group":"foods","icon":"foods","coords":[6724,9556],},{"type":"Liquid","name":"Water","group":"liquids","icon":"liquids","coords":[6724,9556],},{"type":"Liquid","name":"Coffe","group":"liquids","icon":"liquids","coords":[6724,9556],},]

let counts = markers.reduce((obj, {group}) => {
  if(!obj[group]) obj[group] = {"item": group, "qnt":1}  // make a count item if it doesn't exist
  else obj[group].qnt++                                  // or increment it
  return obj
}, {})

console.log(Object.values(counts))

Sign up to request clarification or add additional context in comments.

Comments

2

var markers = [{
    "type": "Chocolate",
    "name": "KitKat",
    "group": "candy",
    "icon": "candy",
    "coords": [5246, 8980],
  },
  {
    "type": "Fruit",
    "name": "Orange",
    "group": "fruits",
    "icon": "fruis",
    "coords": [9012, 5493],
  },
  {
    "type": "Fruit",
    "name": "Banana",
    "group": "fruits",
    "icon": "fruis",
    "coords": [9012, 5493],
  },
  {
    "type": "Food",
    "name": "Rice",
    "group": "foods",
    "icon": "foods",
    "coords": [6724, 9556],
  },
  {
    "type": "Food",
    "name": "Meat",
    "group": "foods",
    "icon": "foods",
    "coords": [6724, 9556],
  },
  {
    "type": "Food",
    "name": "Beam",
    "group": "foods",
    "icon": "foods",
    "coords": [6724, 9556],
  },
  {
    "type": "Liquid",
    "name": "Water",
    "group": "liquids",
    "icon": "liquids",
    "coords": [6724, 9556],
  },
  {
    "type": "Liquid",
    "name": "Coffe",
    "group": "liquids",
    "icon": "liquids",
    "coords": [6724, 9556],
  }
];

var temp = markers.reduce( function ( results, marker ) {
  results[ marker.group ] = ( results[ marker.group ] || 0 ) + 1;
  
  return results;
}, {});

//now convert the object to a list like you want it
temp = Object.keys( temp ).map( function ( group ) {
  return { group: group, quantity: temp[ group ] };
} );

console.log( temp );

Comments

2

You can do this via Array.reduce to do the grouping by group and then use Object.entries with Array.map to get the desired output format:

var data = [ { "type":"Chocolate", "name":"KitKat", "group":"candy", "icon":"candy", "coords":[5246,8980], }, { "type":"Fruit", "name":"Orange", "group":"fruits", "icon":"fruis", "coords":[9012,5493], }, { "type":"Fruit", "name":"Banana", "group":"fruits", "icon":"fruis", "coords":[9012,5493], }, { "type":"Food", "name":"Rice", "group":"foods", "icon":"foods", "coords":[6724,9556], }, { "type":"Food", "name":"Meat", "group":"foods", "icon":"foods", "coords":[6724,9556], }, { "type":"Food", "name":"Beam", "group":"foods", "icon":"foods", "coords":[6724,9556], }, { "type":"Liquid", "name":"Water", "group":"liquids", "icon":"liquids", "coords":[6724,9556], }, { "type":"Liquid", "name":"Coffe", "group":"liquids", "icon":"liquids", "coords":[6724,9556], }, ]

const group = data.reduce((r,c) => (r[c.group] = (r[c.group] || 0) + 1, r), {})
console.log(Object.entries(group).map(([k,v]) => ({ item: k, qnt: v })))

1 Comment

Good answer! It's very clear even if it uses more than one method call. I for some reason always overthink the assignment of the group (r[c.group] || 0) + 1 is always the best way when accumulating quantities. +1
2

The expected output is not right.An Array cannot have key & value like that. You may need an object for that.

var markers = [{
    "type": "Chocolate",
    "name": "KitKat",
    "group": "candy",
    "icon": "candy",
    "coords": [5246, 8980],
  },
  {
    "type": "Fruit",
    "name": "Orange",
    "group": "fruits",
    "icon": "fruis",
    "coords": [9012, 5493],
  },
  {
    "type": "Fruit",
    "name": "Banana",
    "group": "fruits",
    "icon": "fruis",
    "coords": [9012, 5493],
  },
  {
    "type": "Food",
    "name": "Rice",
    "group": "foods",
    "icon": "foods",
    "coords": [6724, 9556],
  },
  {
    "type": "Food",
    "name": "Meat",
    "group": "foods",
    "icon": "foods",
    "coords": [6724, 9556],
  },
  {
    "type": "Food",
    "name": "Beam",
    "group": "foods",
    "icon": "foods",
    "coords": [6724, 9556],
  },
  {
    "type": "Liquid",
    "name": "Water",
    "group": "liquids",
    "icon": "liquids",
    "coords": [6724, 9556],
  },
  {
    "type": "Liquid",
    "name": "Coffe",
    "group": "liquids",
    "icon": "liquids",
    "coords": [6724, 9556],
  },
]


let count = markers.reduce(function(acc, curr) {
  if (acc[curr.type]) {
    acc[curr.type] += 1;
  } else {
    acc[curr.type] = 1;
  }

  return acc;
}, {})

console.log(count)

/* if you need an array of objects then ,instead of object ,
pass an empty array as the accumulator. Then in that 
accumulator search if the type exist using findIndex.
If it returns -1 then create a new object with 
required values and push it in the accumulator,
 else update the value of qnt at that specific index*/

let count1 = markers.reduce(function(acc, curr) {
  let getItemIndex = acc.findIndex(function(item) {
    return item.item === curr.group
  });

  if (getItemIndex === -1) {
    let obj = {
      item: curr.group,
      qnt: 1
    }
    acc.push(obj)
  } else {
    acc[getItemIndex].qnt += 1;
  }

  return acc;
}, [])

console.log(count1)

2 Comments

No OP's output is count = [ candy: 1 foods: 3 fruits: 2 liquids: 2 ] , it is an array not an object
You're right, the OP's output in the question is impossible to reproduce, since there's no way to get key/value pairs to show up between square brackets like that. But the OP asks for a format with an array of objects with { item: name, qnt: count }, not { name: count } as your answer demonstrates.
2

The best way (in my opinion) is to use a format like { item: count } which you already have except for that count should be an object {} instead of an array [] (see @brk's answer). If you want the output to be an array of objects, then just use a cache object (that will hold the index of the count object from the count array):

var count = [], cache = {};
for(var i = 0; i < markers.length; i++) {
  var marker = markers[i];
  if(cache.hasOwnProperty(marker.type)) {           // if the current marker type has already been encountered
    count[cache[marker.type]].qnt++;                // then just retrieve the count object and increment its 'qnt'
  } else {                                          // otherwise
    cache[marker.type] = count.push({               // create a count object for this type and store its index in the cache object
      type: marker.type,
      qnt: 1
    }) - 1;                                         // - 1 because 'push' returns the new array length, thus the index is 'length - 1'
  }
}

Comments

1

You can use the reduce method to return a new array with the items and quantities.

We use a ternary statement to determine if the accumulator array already contains the group type with findIndex. If it doesn't we push the new type with a qnt of 1, if it does we simply increment the qnt value.

markers.reduce((ms, m) => (ms.findIndex(o => o.item === m["group"]) > 0) ? 
(ms[ms.findIndex(o => o.item === m["group"])]["qnt"]++, ms) : 
(ms.push({  qnt: 1,  item: m["group"]}), ms), []);

var markers=[{type:"Chocolate",name:"KitKat",group:"candy",icon:"candy",coords:[5246,8980]},{type:"Fruit",name:"Orange",group:"fruits",icon:"fruis",coords:[9012,5493]},{type:"Fruit",name:"Banana",group:"fruits",icon:"fruis",coords:[9012,5493]},{type:"Food",name:"Rice",group:"foods",icon:"foods",coords:[6724,9556]},{type:"Food",name:"Meat",group:"foods",icon:"foods",coords:[6724,9556]},{type:"Food",name:"Beam",group:"foods",icon:"foods",coords:[6724,9556]},{type:"Liquid",name:"Water",group:"liquids",icon:"liquids",coords:[6724,9556]},{type:"Liquid",name:"Coffe",group:"liquids",icon:"liquids",coords:[6724,9556]}];

let r = markers.reduce((ms, m) => (ms.findIndex(o => o.item === m["group"]) > 0) ? 
(ms[ms.findIndex(o => o.item === m["group"])]["qnt"]++, ms) : 
(ms.push({  qnt: 1,  item: m["group"]}), ms), []);

console.log(r);

2 Comments

I didn't knew the reduce method, it's a good way to solve this too. It is more faster than generating another array?
@RogerHN It technically does create another array but it may in fact be faster. The benefit is that it's innately a functional method and it doesn't pollute the global scope with unnecessary variables and operations.
1
count = {
  candy: 1
  foods: 3
  fruits: 2
  liquids: 2
}

After calculating the grouping counts from the initial data, you can send it to view to generate the html. (It is an object, not array). When generating html, you can iterate over its properties like this:

//Add ul
for(var item in count) {
   //Get data and add li (item = candy, count[item] = 1)
}

Comments

1

You can use your code

var count = {} // I made an Object out of your Array, the proper way to do this

for (var i = 0; i < markers.length; i++) {
  count[markers[i].group] = count[markers[i].group] + 1 || 1 ;
}

And convert your Object into an array afterwards:

var finalArray = [];
for(var key in count)
  finalArray.push({item: key, qnt: count[key]});
}

1 Comment

Thank you, very simple this way! I don't know which answer is the fastest way to do, but this way is pretty simple.
1

var markers = [{"type":"Chocolate","name":"KitKat","group":"candy","icon":"candy","coords":[5246,8980]},{"type":"Fruit","name":"Orange","group":"fruits","icon":"fruis","coords":[9012,5493]},{"type":"Fruit","name":"Banana","group":"fruits","icon":"fruis","coords":[9012,5493]},{"type":"Food","name":"Rice","group":"foods","icon":"foods","coords":[6724,9556]},{"type":"Food","name":"Meat","group":"foods","icon":"foods","coords":[6724,9556]},{"type":"Food","name":"Beam","group":"foods","icon":"foods","coords":[6724,9556]},{"type":"Liquid","name":"Water","group":"liquids","icon":"liquids","coords":[6724,9556]},{"type":"Liquid","name":"Coffe","group":"liquids","icon":"liquids","coords":[6724,9556]}]

counts = []
markers.map(marker => counts.filter(type => type.name == marker.group).length> 0 ? counts.filter(type=>type.name ==marker.group)[0].count ++ : counts.push({'name':marker.group,'count':1}));
console.log(counts);

This or as someone said above, using reduce also works. This is also rather inefficient due to multiple maps and filters

1 Comment

My concern is performance, because the original array has more than one thousand entries, and the application has a lot of complex functions. But itt's a good way to solve too, upvoted Colby! Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.