0

I found this: count occurrences of search strings in a list (python) similar with solution i need but in javascript.

I have array of objects:

data_items = [
    {
        "no": "1",
        "category": "design",
        "class": "low",
        "status": "open",
    },
    {
        "no": "2",
        "category": "permit",
        "class": "low",
        "status": "close",
    },
    {
        "no": "3",
        "category": "permit",
        "class": "high",
        "status": "open",
    },
    {
        "no": "4",
        "category": "quality",
        "class": "high",
        "status": "close",
    }

]
    

and

categoryList = [
    "design",
    "permit",
    "quality",
    "safety",
    "commercial"
]

what I expected is to count occurrences each category includes the null result

countCategories = [
       { "design": 1 },
       { "permit": 2 },
       { "quality": 1 },
       { "safety": 0 },
       { "commercial": 0 }
]

and how do i get multidimension version of the result? such as

 countCategories = [
        {
            open: [
                { "design": 1 },
                { "permit": 1 },
                { "quality": 0 },
                { "safety": 0 },
                { "commercial": 0 }
            ]
        }, {
            close: [
                { "design": 0 },
                { "permit": 1 },
                { "quality": 1 },
                { "safety": 0 },
                { "commercial": 0 }
            ]
        }
    ]

I've been trying to get the result by reduce and filter, but got no luck

thank you for your help.

Anonymous Coder
  • 556
  • 2
  • 16
  • I think if you simplify the output to `countCategories = {"design": 1 ,"permit": 2}`, it will make it a lot easier for you to try then. Same goes for the multidimensional array, I don't see the point in having an array of objects with only one property inside. Let me know if this can be done. Will add the code for it if it is still not clear. – Ankit Gupta Jul 20 '21 at 17:43

6 Answers6

0

You can count all the categories like this, then convert it to whatever format you want afterwards:

const data_items = [{
    "no": "1",
    "category": "design",
    "class": "low",
    "status": "open",
  },
  {
    "no": "2",
    "category": "permit",
    "class": "low",
    "status": "close",
  },
  {
    "no": "3",
    "category": "permit",
    "class": "high",
    "status": "open",
  },
  {
    "no": "4",
    "category": "quality",
    "class": "high",
    "status": "close",
  }
]

const counting = new Map();
for (const item of data_items) {
  let count = counting.get(item.category) || 0;
  counting.set(item.category, count + 1);
}

// Convert it to the format you wanted (even though it's a weird one)
const countCategories = [];
for (const [key, value] of counting) {
  countCategories.push({
    [key]: value
  });
}

console.log(countCategories);
// [
//   {design: 1},
//   {permit: 2},
//   {quality: 1},
// ]

EDIT: It seems like you added the requirement of a multi-dimensional version while I was writing this answer. You can reuse the above code and run it twice, but one time filter for item.status === 'open' and the other time filter for 'close'. This will give you two resulting arrays, which you can combine together into a multidimensional version.

Kelvin Schoofs
  • 8,323
  • 1
  • 12
  • 31
0

Basic idea with reduce.

var obj = {
  data_items: [{
      "no": "1",
      "category": "design",
      "class": "low",
      "status": "open",
    },
    {
      "no": "2",
      "category": "permit",
      "class": "low",
      "status": "close",
    },
    {
      "no": "3",
      "category": "permit",
      "class": "high",
      "status": "open",
    },
    {
      "no": "4",
      "category": "quality",
      "class": "high",
      "status": "close",
    }
  ]
}

const result = obj.data_items.reduce(function(acc, item) {
  acc[item.status][item.category] = (acc[item.status][item.category] || 0) + 1;
  return acc;
}, {
  open: {},
  close: {}
});

console.log(result);

If you want to prefill your object with zeros

var obj = {
  categoryList: [
    "design",
    "permit",
    "quality",
    "safety",
    "commercial"
  ],
  data_items: [{
      "no": "1",
      "category": "design",
      "class": "low",
      "status": "open",
    },
    {
      "no": "2",
      "category": "permit",
      "class": "low",
      "status": "close",
    },
    {
      "no": "3",
      "category": "permit",
      "class": "high",
      "status": "open",
    },
    {
      "no": "4",
      "category": "quality",
      "class": "high",
      "status": "close",
    }
  ]
}


const defaultObj = obj.categoryList.reduce(function (acc, key) {
acc[key] = 0;
return acc;
}, {});


  const result = obj.data_items.reduce(function(acc, item) {
    acc[item.status][item.category] += 1;
    return acc;
  }, {
    open: { ...defaultObj
    },
    close: { ...defaultObj
    }
  });

console.log(result);
epascarello
  • 204,599
  • 20
  • 195
  • 236
0

Using reduce is the way to go, since you can start your final object with the open/close arrays. I added in a duplicate so it would show how it increments based on the conditions.

let data_items = [{
    "no": "1",
    "category": "design",
    "class": "low",
    "status": "open",
  },
  {
    "no": "2",
    "category": "permit",
    "class": "low",
    "status": "close",
  },
  {
    "no": "3",
    "category": "permit",
    "class": "high",
    "status": "open",
  },
  {
    "no": "4",
    "category": "quality",
    "class": "high",
    "status": "close",
  },
  {
    "no": "5",
    "category": "quality",
    "class": "high",
    "status": "close",
  }

]

let result = data_items.reduce((b, a) => {
  let l = b[a.status].findIndex(e => Object.keys(e)[0] === a.category)
  if (l > -1) b[a.status][l][a.category]++;
  else {
    b.open.push({
      [a.category]: a.status === 'open' ? 1 : 0
    });
    b.close.push({
      [a.category]: a.status === 'close' ? 1 : 0
    });
  }
  return b
}, {
  open: [],
  close: []
})

console.log(result)
Kinglish
  • 23,358
  • 3
  • 22
  • 43
0

You can avoid hard-coding status values (open and closed) with a general purpose grouping function, often called groupBy. Apply that with the status key first.

Then, within each status, the category count is a straight forward reduce().

The last, maybe optional, step is to transform the object result to an array (as the OP describes)...

const data_items = [
    {
        "no": "1",
        "category": "design",
        "class": "low",
        "status": "open",
    },
    {
        "no": "2",
        "category": "permit",
        "class": "low",
        "status": "close",
    },
    {
        "no": "3",
        "category": "permit",
        "class": "high",
        "status": "open",
    },
    {
        "no": "4",
        "category": "quality",
        "class": "high",
        "status": "close",
    }

]
 
// answer an object where each key is the value of key in the array
// and each value is an array of elements with the given key's values
function groupBy(array, key) {
  return array.reduce((acc, el) => {
    let value = el[key];
    if (!acc[value]) acc[value] = [];
    acc[value].push(el);
    return acc;
  }, {});
}

function reduceToCategories(array) {
  return array.reduce((acc, obj) => {
    const cat = obj['category'];
    if (!acc[cat]) acc[cat] = 0;
    acc[cat]++;
    return acc;
  }, {})
}


let groups = groupBy(data_items, 'status');
let result = {}
for (status in groups) {
  result[status] = reduceToCategories(groups[status])
}

// result in an object, rather than an array as the OP requests.
// If an array is necessary, the straight-forward transformation is...

const countCategories = Object.entries(result).map(([key, value]) => ({ [key] : value }))

console.log(countCategories)
danh
  • 62,181
  • 10
  • 95
  • 136
0

You could build a frequency map and then map the entries to a list of key-value pair objects.

const dataItems = [
  { "no": "1", "category": "design" , "class": "low" , "status": "open"  },
  { "no": "2", "category": "permit" , "class": "low" , "status": "close" },
  { "no": "3", "category": "permit" , "class": "high", "status": "open"  },
  { "no": "4", "category": "quality", "class": "high", "status": "close" }
];

const unique = (arr, fn) => [...new Set(arr.map(item => fn(item)).sort())];

const frequency = (arr, keyFn) =>
  arr.reduce((acc, item) =>
    (key => ({ ...acc, [key]: (acc[key] || 0) + 1 }))
    (keyFn ? keyFn(item) : item), {});

const mapToPairs = (map) => Object.entries(map).map(([ k, v ]) => ({ [k]: v }));

const categoryList = unique(dataItems, ({ category }) => category);

console.log(JSON.stringify(categoryList));

const catFreq = mapToPairs(frequency(dataItems, ({ category }) => category));
  
console.log(JSON.stringify(catFreq));

const freq = dataItems
  .reduce((acc, { status, category }) =>
    ({ ...acc, [status]: [...(acc[status] || []), { category }] }), {});

for (let key in freq) {
  freq[key] = mapToPairs(frequency(freq[key], ({ category }) => category));
}

console.log((JSON.stringify(freq)));
.as-console-wrapper {  top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
0

Thankyou for your answers, I've been trying since i post this, and I came with:

function map_it(data_items, dim_key) {
        var bucket = {};
        categoryList.forEach(function (cat) {
            bucket[cat] = 0;
        })
        data_items.forEach(function (item) {
            if (Object.keys(bucket).indexOf(item.category) >= 0) bucket[item.category]++;
            else bucket[item.category] = 1;
        })
        return bucket;
    }
    console.log(map_it(data_items));