1

I am writing a Javascript function to recognise the blanks (represented by four underscores) a user has filled in a string. For instance, when given the template ____ world, and the user's input of Hello world, it should return the array ["Hello"].

So far, I've cobbled together the below, but it has issues identifying the blanks

function identifyBlanks(template, filledString) {
  // Split the template and filled string into arrays of words
  const templateWords = template.split(' ');
  const filledWords = filledString.split(' ');

  // Initialize an array to store the filled words
  const filledBlanks = [];

  // Iterate over the template and check for blanks
  for (let i = 0; i < templateWords.length; i++) {
    // Check if the template word contains a blank (indicated by multiple underscores)
    if (templateWords[i].includes('____')) {
      const blankIndex = templateWords[i].indexOf('____');
      
      // Find the corresponding filled word by matching the substring before and after the blank
      const filledWord = filledWords.find(word => {
        const wordBeforeBlank = word.substring(0, blankIndex);
        const wordAfterBlank = word.substring(blankIndex + 4); // 4 represents the length of '____'
        return templateWords[i].startsWith(wordBeforeBlank) && templateWords[i].endsWith(wordAfterBlank);
      });

      // Add the filled word to the array if found
      if (filledWord) {
        filledBlanks.push(filledWord);
      }
    }
  }

  return filledBlanks;
}

const template = "letters a____c";
const filledString = "letters abc";

const filledBlanks = identifyBlanks(template, filledString);
console.log(filledBlanks); // Output: ["b"]

There's a couple more edge cases I'm struggling with too - it should be able to recognise when a blank has been replaced by an empty string, as well as detecting blanks that are inside words, instead of surrounded by spaces. For instance, when given the template H____o ____ world! and the string Hello world!, it should return ["ell", ""]

edit: The use case for this is to be able to reverse engineer a user's answer to a Python problem, where they are given a problem like

____ = ____("Enter your name")
____(name)

and could answer it like

name = input("Enter your name")
print(name)

I then need to get the blanks they've entered at a later time. I'm aware there are better ways of implementing this (e.g. storing just the blanks they've entered, as opposed to their whole solution), but in the context of the system it's part of this isn't feasible.

Jacob Barrow
  • 631
  • 1
  • 8
  • 25
  • Can you please add a bit more of context? It's a bit hard to see your use case just with the function code. Could you maybe create a codepen/jsfiddle to check live the function behaviour with an example of an input to which you are applying it? – Vigil May 22 '23 at 12:04
  • 1
    Your last example complicates things. `H____o ____ world!` - even if the second placeholder in there would have an "empty" value, then it would still not really "match" `Hello world!`- it would rather match `Hello world!` (with _two_ spaces between the words.) If it did not come with _that_ complication, you could simply create a regular expression, where you replace every `____` with `(.*)`, and then use that regex to match the parts ... – CBroe May 22 '23 at 12:08
  • @Vigil thanks for having a look, I've edited the Q – Jacob Barrow May 22 '23 at 13:11
  • @CBroe thanks, it actually should be as you've suggested, just forgot to add the extra space there. Edited to fix that – Jacob Barrow May 22 '23 at 13:12
  • Check if something like this works for you: https://jsfiddle.net/8qy96znr/ (the RegExp.quote implementation is from https://stackoverflow.com/q/2593637/1427878, which has been deleted - perhaps there are better implementations for this.) – CBroe May 22 '23 at 13:27
  • 3
    What if for `"H____o ____ world!"` the user would enter `"How to go to no world!"`? The result could be `["ow t", "go to no"]` or `["ow to g", "to no"]` or `["ow to go t", "no"]` or `["ow to go to n", ""]`. – trincot May 22 '23 at 14:09
  • @CBroe: that question was closed in favor of https://stackoverflow.com/q/3561493, which does the same thing. – Scott Sauyet May 22 '23 at 14:14

1 Answers1

2

See comments on a related question, Extracting values from input by template in Javascript for some caveats. Those caveats are important. This can never be entirely precise, as @trincot points out in his comment.

You can write a function like this:

const fillBlanks = (template, response, match = response .match (new RegExp (
  template .replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&') // https://stackoverflow.com/a/3561711
           .replace(/ /g, '\\s*')                     // condense spaces 
           .replace (/____/g, (s) => `(.*)`),         // add capture group
  "m"                                                 // multiline regex
))) => match && match .slice (1)

console .log (fillBlanks ('____ World!', 'Hello World!')) //=> ["Hello"]
console .log (fillBlanks ('H____o ____World!', 'Hello World!')) //=> ["ell", ""]

const template = `____ = ____("Enter your name")
____(name)`

const answer = `name = input("Enter your name")
print(name)`

console .log (fillBlanks (template, answer)) //=> ["name", "input", "print"]
.as-console-wrapper {max-height: 100% !important; top: 0}

Note that the condensing of spaces might allow for some matches you don't want. We could remove it, but then "H____o ____ World!" with at least two spaces wouldn't match "Hello World!" with only one. I don't know of a good solution for that.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • This is great - minor issue is that the first blank found sometimes has a trailing space, but marking as accepted as it pretty much works perfectly – Jacob Barrow May 22 '23 at 14:18
  • 1
    You could always tack on `.map (s => s .trim ())`. But as trincot demonstrated, there is no perfect solution to this. – Scott Sauyet May 22 '23 at 14:41