Increment value of map based on array lookup index

Viewed 67

I have an array:

const arr1 = [
  "Strongly positive",
  "Positive",
  "Neutral",
  "Negative",
  "Strongly negative"
];

and responses object:

const responses = [
  { values: { QID16: 3 } },
  { values: { QID16: ["1", "2", "3"] } },
  { values: { QID16: 1 } }
];

The goal is to create a function that returns a map. The map's keys are the elements of arr1 and the values are the count of those elements if their index + 1 appears in the responses object.

For example, responses[0].values.QID16 is 3 which is Neutral. (Neutral has index 2)

The problem is when the values in responses is an array like in responses[1]

I created the following function:

function getCounts(mainId, choices, responses) {
  let choicesCounts = new Map();
  choices.forEach((choice, i) => {
    choicesCounts.set(choice, 0);
    const id = i + 1;
    responses.forEach((response) => {
      if (response.values[mainId] && response.values[mainId] === id) {
        choicesCounts.set(choice, choicesCounts.get(choice) + 1);
      }
      if (
        response.values[mainId] &&
        Array.isArray(response.values[mainId]) &&
        response.values[mainId].includes(id.toString())
      ) {
        // this is the part where I need help
        response.values[mainId].forEach((n) => {
          choicesCounts.set(
            choices.at(parseInt(n, 10) - 1),
            choicesCounts.get(choices.at(parseInt(n, 10) - 1)) + 1
          );
        });
      }
    });
  });
  return choicesCounts;
}

It would be called like this:

console.log(getCounts("QID16", arr1, responses));

Desired output:

// desired output is a map not an object
const desiredOutput = {
  "Strongly positive": 2, // number 1 appears twice in responses
  Positive: 1, // number 2 appers once in responses
  Neutral: 2,
  Negative: 0,
  "Strongly negative": 0
};

It works in the case where the values are numbers but not when they're arrays.

What is wrong with this function? Any suggestions to make it simpler?

2 Answers

I think your problem lies in the way how you approached to solve this problem.

Currently you are iterating over your responses array once for each possible value from arr1. When you encounter the array of values within a responses entry, you check whether it contains a value you are currently "looking for", and then increment ALL choices that were made. Since this will get looped over several times, you will also increment all of them several times and get the wrong counts.

Without giving you the exact code for changing this, just restructuring your function this way should already help coming to a cleaner solution:

  • Change the processing order. Instead of searching for individual values in your responses, try to do a "pre-processing" step where you create a map which represents the count of each unique key you encounter in the responses values.
  • The result will be almost what you were looking for, only the keys are not mapped to the values of arr1 yet. This has the advantage of only requiring a single iteration over your responses array, getting rid of the problem of counting some values multiple times.
  • In case you chose this approach because the responses values might contain values you are not interested in and you want to "skip" those, that behaviour can be replicated in the pre-processing phase by checking if the currently inspected value is contained in the arr1 array before writing it to the result map.

Does this cover the problem you were seeing, or was there an actual other error you encountered?

The OP should think about choosing an approach which breaks the entire task apart into smaller ones.

  1. From the ratings-array (the one which literally names the ratings) create a rating-value (the ones that come with the response item's values) based map for looking up rating-keys (the rating-names which are the keys to the to be achieved rating-counts map).

    const ratingValuesToKeys = new Map([
    
      "Strongly positive",
      "Positive",
      "Neutral",
      "Negative",
      "Strongly negative",
    
    ].map((key, idx) => [idx + 1, key]));
    
    console.log(Object.fromEntries([...ratingValuesToKeys]));

  2. Also from the very same ratings-array create a rating-name based map for counting/summing up the occurrences of related rating-values.

    const ratingCounts = new Map([
    
      "Strongly positive",
      "Positive",
      "Neutral",
      "Negative",
      "Strongly negative",
    
    ].map(key => [key, 0]));
    
    console.log(Object.fromEntries([...ratingCounts]));

  3. Sanitize and collect all key specific rating-values for they are occurring as different types like number and/or string values as well as arrays.

    const ratingValues = [
      { values: { QID16: 3 } },
      { values: { QID16: ["1", "2", "3"] } },
      { values: { QID16: 1 } },
    ]
    .reduce((result, { values }) => {
    
      if (values.hasOwnProperty('QID16')) {
        const rating = values['QID16'];
    
        if (Array.isArray(rating)) {
          result
            .push(
              ...rating
                .map(value =>
                  parseInt(value, 10)
                )
            );
        } else {
          result
            .push(parseInt(rating, 10));
        }
      }
      return result;
    
    }, []);
    
    console.log({ ratingValues });

  4. Based on all sanitized key specific rating-values do update each rating's occurrence by incrementing the related rating-key's count-value.

The final combined implementation and example code then might look similar to this ...

const ratings = [
  "Strongly positive",
  "Positive",
  "Neutral",
  "Negative",
  "Strongly negative",
];
const responses = [
  { values: { QID16: 3 } },
  { values: { QID16: ["1", "2", "3"] } },
  { values: { QID16: 1 } },
];

function getRatingCounts(responseValueKey, ratings, responses) {
  // create a rating-value based map for looking up rating-keys.
  const ratingValuesToKeys = new Map(
    ratings
      .map((key, idx) => [idx + 1, key])
  );
  // create a rating-key based map for counting/summing up ratings.
  const ratingCounts = new Map(
    ratings
      .map(key => [key, 0])
  );

  // sanitize and collect all key specific rating-values
  // for they are occurring as different types ... like
  // number and/or string values as well as arrays.
  const ratingValues = responses
    .reduce((result, { values }) => {

      if (values.hasOwnProperty(responseValueKey)) {
        const rating = values[responseValueKey];

        if (Array.isArray(rating)) {
          result
            .push(
              ...rating
                .map(value =>
                  parseInt(value, 10)
                )
            );
        } else {
          result
            .push(parseInt(rating, 10));
        }
      }
      return result;

    }, []);

  // based on all sanitized key specific rating-values
  // do update each rating's occurrence by incrementing
  // the related rating-key's count-value.
  ratingValues
    .forEach(value => {

      const ratingKey = ratingValuesToKeys.get(value);
      const ratingCount = ratingCounts.get(ratingKey);

      ratingCounts.set(ratingKey, ratingCount + 1);
    });

  return ratingCounts;
}

const ratingCounts = getRatingCounts('QID16', ratings, responses);

console.log({
  ratingCounts,
  'counts as entry list': [...ratingCounts],
  'counts as object': Object.fromEntries([...ratingCounts]),
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

Related