0

I am searching through a set of strings for a set of keywords. Then, if certain combinations of words are present, I'm assigning given point values to a scorekeeping array.

As I iterate through the strings, I modify these arrays to keep track of information on a specific string. I want to print the scores ranked high to low, and then reset the values in the arrays to their original assignments.

Therefore, I need a way to create a new object to modify at the beginning of each iteration so that I can preserve my original arrays.

I've tried to define a variable outside the loop and then use .slice(), Object.create(), and [...arr], but I have not been successful.

The only way I can get the function to work is by defining the array at the beginning of the loop using literal notation (This is okay, but if I need to build an object with hundreds of keywords for each of hundreds of notes, I'm worried it will slow down processing time).

  const keyMast = [ //pre-decided keywords, set to false
    ["No Word", true],
    ["elbow", false],
    ["ss" , false], 
    ["student" , false],
    ["read", false]
  ];  
  const combos = [ //for each set of words, assings values for T1, T2,... 
    ["student", true, true, 1, 2, 3, 0, 0, 0, 0, 0],
    ["elbow", true, true, 3, 2, 1, 0, 0, 0, 0, 0],
    ["student", "read", true, 0, 0, 0, 1, 2, 3, 0, 0],
    ["talk", "read", true, 0, 0, 0, 0, 0, 1, 2, 3]
  ];
  const scoreMast= [ //master list of Ts set to zero
    ["T1", 0],
    ["T2", 0],
    ["T3", 0],
    ["T4", 0],
    ["T5", 0],
    ["T6", 0],
    ["T7", 0],
    ["T8", 0]
  ];



  //this loop grabs each line in the notes
  var i=6; //the first line of notes starts in Cell B6
  do {
    //reset values for our tables after each note
    tempCombo = [...combos];
    tempKeywords = [...keyMast];
    tempScore = [...scoreMast];
    //do stuff with the lists on this line of notes

Like I said, I have tried a bunch of different strategies, but nothing seems to yield new objects with identical properties as score, keywords, and combos for each iteration.

Two questions: is it easier to just define the variables inside the loop using literal notation (var combo = [ [blah, blah], []])? Will that slow my code down?

Either way, I'd love to know how to do this for generality's sake.

EDIT Tanaike asked for outputs. The Do Loop needs to sort the scores and then runs

var suggest = tempScores[0][0] + " " + tempScores[1][0] + " " +tempScores[2][0]
Ss.getRange(i,3). setValue(suggest)

My clients are principals. When they observe teachers, they assess their work on 8 Teaching Standards (hence, T1, T2...). They want to take notes during a lesson and then have the spreadsheet suggest which standards most closely align on that line of notes based on keywords they use.

1 Answers1

0

As you're discovering, the nested array bit means that sliceing the variable results in still accessing the same inner objects as the original variable you sliced from. The simplest tip I have is "Don't modify the source arrays." If you don't modify them, then you don't need to create a new copy. Rather, write a function that constructs a related scorekeeping object.

ex:

function getNewScoreObject(numStandards) {
  const scorer = [];
  for (var i = 0; i < numStandards; ++i) {
    scorer.push(["T" + i, 0]);
  }
  return scorer;
}

This gives you a different object each call, so you can freely modify the return value. You don't share how you modify the combos or keyMast objects, so we can't really suggest implementations of those functions.

If you'd rather not write instantiating functions for your objects, and only because they are 2D arrays of primitives, you just need to go one level deeper, and slice the inner Array:

teacherReviews.forEach(function (notesOnTeacher) {
  var workingCombos = combos.map(function (combo) { return combo.slice(); });
  // `workingCombos` is now wholly different than `combos`, but the internal primitives have the same value.
  ...
});

If you have more complex objects to copy, then you need to be more elaborate in your deep clone.


Probably you can improve your application logic as well, to use objects, but that's outside the scope here (and you didn't share the logic). As a teaser, I might have defined combos as an array of objects with convenient property names, so your code can be considerably more expressive:

const combos = [
  {
    words: [ <words that could be in the note> ],
    points: {
      T1: <num>,
      T2: <num>,
       ...
      T8: <num>
    },
    requires: "ALL" // "ALL" | "ANY" | "NONE" (logic for earning the points)
  },
  {
    ...
];
// case sensitive check
function wordInText(text, word, idx, allWords) {
  return text.indexOf(word) !== -1;
}
...
    var wordInNoteText = wordInText.bind(null, noteText.toLowerCase()); // prefix all calls with this note's lowercased text.
    combos.forEach(function (combo) {
      var hasAtLeastOneWord = combo.words.some(wordInNoteText);
      var hasAllWords = hasAtleastOneWord && combo.words.every(wordInNoteText);
      var earnsPoints = (
        (combo.requires === "NONE" && !hasAtLeastOneWord) ||
        (combo.requires === "ANY" && hasAtLeastOneWord) ||
        (combo.requires === "ALL" && hasAllWords)
      );
      if (earnsPoints) {
        for (var ts in combo.points) {
          teacher.score[ts] += combo.points[ts];
        }
      }
    });
...
function writeTeacherData(sheet, teachers) {
  const order = ["T1", "T2", "T3", ... "T8"]; // Could `.sort()` a var created from `Object.keys(teacher.score);`
  const data = teachers.map(function (teacher) {
    var row = [teacher.name, teacher.classPeriod, /** etc */];
    Array.prototype.push.apply(row, order.map(function (ts) { return teacher.score[ts]; }));
    return row;
  });
  if (data.length) {
    order.unshift("Class Period");
    order.unshift("Teacher Name");
    // etc
    data.unshift(order); // prefix the headers to the data output
    sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
  }
}

References:

tehhowch
  • 9,645
  • 4
  • 24
  • 42