Search code examples
javascriptarraysmappinglookupreduce

Increment value of map based on array lookup index


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?


Solution

  • 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; }