0

The problem I am trying to solve is how best to generate a unique string from a set of question/answer strings.

So say an end user fills out a questionnaire and answers the following:

   [
       {
           "is there a problem": "yes"
       },
       {
           "what is it?": "product is damaged"
       },
       {
           "do you want a refund?": "yes"
       }
   ]

I need to generate a string that represents these questions and answers and then map that to a templateId to send a templated message to.

I thought about simply running a foreach and adding all the questions and responses together but the issue there is what if the questions and answers change or additional questions are added that we are not interested in for sending message purposes.

Should I use something like find or a hashtable type of look up instead so I am only picking out the questions I need. I am a bit worried about performance here too but it shouldn't be a big deal as this set of questions/answers should remain relatively small.

I was also wondering about this string -> templateId mapping, what is the best way to store this information, a simple object works fine?

Clarification:

It doesn't have to be unique or be a hashmap. Basically we have email templates in the backend to send messages. In the frontend we need to figure out which email template to use given the set of responses to the questions. So my simple solution was to append the questions/answers together to generate a string, i.e "isThereProblemYesWhatisIt?ProductDamagedDoyouwantaRefund?Yes"

i.e Available templates in the backend:

productDamaged
productLost

Mapping created in the front end.

"isThereProblem?YesWhatisIt?ProductDamagedDoyouwantaRefund?Yes" : 
"productDamaged"

Thanks

motime545
  • 11
  • 4
  • 1
    The best according to who/what? – evolutionxbox Jun 12 '21 at 23:22
  • "*what if the questions and answers change or additional questions are added*" - well, what do you *want* to do in that case? Should the "unique string" change or not? How do you determine which questions are relevant for selecting the email template and which are not? – Bergi Jun 12 '21 at 23:45
  • A hash table does not seem like a good solution here - it only works if you can enumerate all possible inputs. If you ever add a free text field to your questionaire, this is gonna blow. Instead, write a *function* that receives the form values, and returns a string to select an email template. Then put whatever algorithm you want in that function, e.g. testing for the answer on the "*what is the problem*" question. – Bergi Jun 12 '21 at 23:48
  • @Bergi there is no possibility of a freetext input into the questionnaire. The questions/responses might change but we would have to then update the customStringGenerated -> templateId mapping manually. Currently we have access to the array of questions/responses. Do you suggest I take this array into a function and do some conditional logic on select questions and then generate a templateID? In that case I don't even need another mapping for stringGenerated->templateId. I just feel that function would require a ton of conditional logic though and it isn't as simple. Thanks – motime545 Jun 12 '21 at 23:59
  • @motime545 Yes, you would always have to manually update the table. If you use a function, it would just return the appropriate fallback value, or select the template by some other means. Yes, this needs a ton of conditional business logic. Start small. Keep the interface of the function. If the function becomes too complicated, refactor. If you have an `if`/`else` chain or `switch` statement that checks a specific question, use a lookup table for that - but handle the cases that the question was not asked, or the answer is not in the table. A function provides all the necessary flexibility. – Bergi Jun 13 '21 at 00:12

2 Answers2

0

From your updated response, you should take a look at react conditional rendering which is very similar to what you want to complete.

On your frontend you should map each question to an index in an array such as.

["is there a problem", "what is it?", "do you want a refund?"]

That way you'll know that index 0 of the results will ALWAYS be "is there a problem" and then you can send clean info to the backend where you will be able to understand where everything is like:

["yes", "product is damaged", "yes"]

In your backend you will be able to run switch statements or if statements. This is completely up to you but it will look like:

if(data[0] === "yes" && data[1] === "product is damaged") {
   //return email template 1
}
else if(data[0] === "yes" && data[1] === "product is lost") {
   //return email template 2
}
Edward
  • 335
  • 1
  • 3
  • 9
  • Pasted a clarification in the original question. Thanks – motime545 Jun 12 '21 at 23:19
  • So I want to clarify something then. Lets say those are the only three questions, are they a picklist AKA close ended questions where you know every possible answer? Second question is will the answers affect the template AKA are there factorial of number of questions amount of templates? I think you could use a switch statement or if statement if there aren't too many templates. An example that I think you might find similar to your problem is react conditional rendering. There are different 'views' that you can render based on specific conditions which is very similar to your templates. – Edward Jun 12 '21 at 23:26
  • Yes we know every possible answer. The answers to these questions determine which template we will use. We don't have many templates now but it could grow. I actually might add another string to the mapping, i.e questionResonsesString: templateId : ReasonForContact This reason for contact could be something the template could take in as text and render. Essentially we manually need to map questionResponses string to templateId and also add reasonForContact. I felt this was an okay and simple solution for now. – motime545 Jun 12 '21 at 23:32
  • Okay I updated my answer now that I fully understand your problem and it should fix the problem you're having. Let me know. :) – Edward Jun 12 '21 at 23:35
  • Thanks for the response. The reason I don't want to keep this logic in the backend is because its client specific. Some other client might use these templates for other reasons so we want to keep the logic of what template to use on the clientside. – motime545 Jun 12 '21 at 23:39
  • You could keep all of that logic frontend too it doesn't matter where it can go. I just mentioned the backend because I think it's better security-wise. – Edward Jun 12 '21 at 23:48
0

With a small set of questions and answers, an enumeration is feasible and maintainable. Something like this:

const userSubmission = [{
    "is there a problem": "yes"
  },
  {
    "what is it?": "product is damaged"
  },
  {
    "do you want a refund?": "yes"
  }
]

const userSubmission2 = [{
    "is there a problem": "yes"
  },
  {
    "what is it?": "product is damaged"
  },
  {
    "do you want a refund?": "no"
  }
]

const templates = {
  "is there a problem-yeswhat is it?-product is damageddo you want a refund?-yes": "templateForDamagedAndRefundWanted",
  "is there a problem-yeswhat is it?-product is damageddo you want a refund?-no": "templateForDamagedAndRefundNotWanted"
}

function keyFromSubmission(submission) {
  return submission.reduce((acc, obj) => {
    let [key, value] = Object.entries(obj)[0]
    acc += `${key}-${value}`
    return acc
  }, "")
}


const key = keyFromSubmission(userSubmission)
console.log(templates[key])

console.log("\nOr, with a different user submission...")
const key2 = keyFromSubmission(userSubmission2)
console.log(templates[key2])

EDIT

You can plan for textual changes to questions and answers by adding a level of indirection. With this, questions, answers and their variants are represented symbolically.

const questions = [{
    questionId: "q1",
    text: "what is the problem?",
    answers: [{
      answerId: "a1",
      text: "product was broken"
    }, {
      answerId: "a2",
      text: "product didn't arrive"
    }]
  },
  {
    questionId: "q2",
    text: "do you want a refund?",
    answers: [{
      answerId: "a1",
      text: "yes"
    }, {
      answerId: "a2",
      text: "no"
    }]
  }
]

const userSubmission = [{
    "what is the problem?": "product didn't arrive"
  },
  {
    "do you want a refund?": "yes"
  }
]

function userSubmissionAsSymbols(submission) {
  return submission.map(s => {
    let [key, value] = Object.entries(s)[0]
    let question = questions.find(q => q.text === key)
    let answer = question.answers.find(a => a.text === value)
    return `${question.questionId}-${answer.answerId}`
  })
}

console.log(userSubmissionAsSymbols(userSubmission))

With this, you do the same thing as before, mapping the keys derived from the q/a values to templates. In this version, though, the text presented to the user can be changed arbitrarily.

danh
  • 62,181
  • 10
  • 95
  • 136
  • Thanks, I think I had something similar in mind with my solution and I am leaning towards this. The issue I was concerned about needing to manually update the mapping if questions/answers were changed in anyway. From what I understand we shouldnt need to but thats why I was thinking of maybe using a function to manually select certain questions from the responses and then generate the string. – motime545 Jun 13 '21 at 00:23
  • @motime545, independence from the text can be achieved by indirection. See the edit. – danh Jun 13 '21 at 15:26
  • Thanks, yeah I was thinking of using the find function but I thought it might have some performance issues. Do you think it is safe to go with the simplest solution for now, i.e just create the string by concatenating the questions/answers and then refactor this later to use more logic/find if questions/answers do have a higher probability of changing? Thanks – motime545 Jun 13 '21 at 20:23
  • Yes, I think the simple approach will work fine for even hundreds of questions and answers. – danh Jun 13 '21 at 22:08