Javascript - How to know how much string matched in another string? - javascript

I have been implementing a simple quiz for English. In that, we need to validate answers, which are entered by users in input field. In the current implementation, I am comparing the correct answer with user's answer exactly. Like,
HTML
<input type="text" id="answer" />
<button onclick="validate()">Validate</button>
Javascript
var question = "Do you like movies?",
answer = "No, I don't like movies.";
function validate() {
var userInput = document.getElementById('answer').value;
if(answer == userInput) {
console.log("correct");
} else {
console.log("wrong");
}
}
But I don't want validate exactly. Like, ignore case sensitive, commas, apostrophe, etc. For example if user enters,
i dont like movies
The answer can be correct. I don't know how start and where to start. Anyone please help.

One option would be to strip out all non-word characters and spaces, and compare the lower-case version of each replaced string:
var question = "Do you like movies?",
answer = "No, I don't like movies.";
const normalize = str => str
.replace(/[^\w ]/g, '')
.toLowerCase();
function validate(userInput) {
const noramlizedInput = normalize(userInput)
const noramlizedAnswer = normalize(answer);
if (noramlizedInput == noramlizedAnswer) {
console.log("correct");
} else {
console.log("wrong");
}
}
validate('No i dont like movies');
validate("NO!!!!! I DON''t like movies.");
Another option would be to loop through all possible substrings of the userInput and figure out which has the most overlap with the desired answer, but that's a whole lot more complicated.
An easier option would be to check to see how many overlapping words there are:
var question = "Do you like movies?",
answer = "No, I don't like movies.";
const normalize = str => str
.replace(/[^\w ]/g, '')
.toLowerCase()
.split(/\s+/)
function validate(userInput) {
const noramlizedInputArr = normalize(userInput);
const noramlizedAnswerArr = normalize(answer);
const overlapCount = noramlizedInputArr.reduce((a, word) => (
a + Number(noramlizedAnswerArr.includes(word))
), 0);
console.log(overlapCount);
if (overlapCount >= 4) {
console.log("correct");
} else {
console.log("wrong");
}
}
validate('No i dont like movies');
validate("NO!!!!! I DON''t like movies.");
validate("i dont like movies.");
validate("Yes I like movies.");

If you are interested in simply catching spelling errors and small variations, a standard metric is called edit distance or Levenshtein distance. This is a count of the minimum number of deletions, insertions, or substitutions you need to change one text into another. Strings like "No I don't like the movies" and "No I don't like the moveys" will have small edit distances.
Here's a quick and dirty recursive edit distance function that will give you an idea:
function validate(text, pattern) {
// some simple preprocessing
let p = pattern.toLowerCase().replace(/[^a-z]+/ig, '')
let t= text.toLowerCase().replace(/[^a-z]+/ig, '')
// memoize recursive algorithm
let matrix = Array.from({length: t.length + 1}, () => [])
function editDistance(text, pattern, i = 0, j = 0){
if(i == text.length && j == pattern.length) return 0
if(i == text.length) return pattern.length - j
if(j == pattern.length) return text.length - i
let choices = [
(matrix[i+1][j+1] || (matrix[i+1][j+1] = editDistance(text, pattern, i+1, j+1))) + (text[i].toLowerCase() === pattern[j].toLowerCase() ? 0 : 1),
(matrix[i+1][j] || (matrix[i+1][j] = editDistance(text, pattern, i+1, j))) + 1,
(matrix[i][j+1] || (matrix[i][j+1] = editDistance(text, pattern, i, j+1))) + 1
]
return Math.min(...choices)
}
return editDistance(t, p)
}
// similar strings have smaller edit distances
console.log(validate("No I dont lik moves","No i dont like movies"))
// a little less similar
console.log(validate("Yes I like movies","No i dont like movies"))
// totally different
console.log(validate("Where is the bathroom","No i dont like movies"))
// careful -- small edit distance !== close meaning
console.log(validate("I do like tacos","I don't like tacos"))
Picking a minimum acceptable distance works pretty well for matching strings with small typos. Of course, if you are trying to gauge user intent, none of these simple hues tics will work. Strings like "I love tacos" and "I loath tacos" have a small edit distance and you can't tell that they mean the opposite without knowledge of the language. If you need to do this level of checking you can try using a service like Watson Conversation that will return user intents to input.

Related

Can't get values past array[0] to translate properly

Okay, to start with I should mention this is a very small personal project, and I've only have a handful of coding classes several years ago now. I can figure out a lot of the (very) basics, but have a hard time troubleshooting. I'm in a little bit over my head here, and need a dumbed down solution.
I'm trying to put together a VERY simple translator that takes in a word or sentence from the user via a text input box, puts each word of the string into an array, translates each word in order, then spits out each translated word in the order it was input. For example, typing "I like cats" would output "Ich mag Katze" in German.
I've got most of it, but I CAN'T get anything but the first array element to translate. It comes out like "Ich like cats".
I've used a loop, probably because I'm an amateur and don't know another way of doing this, and I'd rather not use any libraries or anything. This is a very small project I want to have a couple of friends utilize locally; and I know there has to be some very simple code that will just take a string, put it into an array, swap one word for another word, and then output the results, but I'm damned if I can make it work.
What I currently have is the closest I've gotten, but like I said, it doesn't work. I've jerry-rigged the loop and clearly that's the totally wrong approach, but I can't see the forest for the trees. If you can help me, please make it "Javascript for Babies" picture book levels of simple, I cannot stress enough how inexperienced I am. This is just supposed to be a fun little extra thing for my D&D group.
function checkForTranslation(input, outputDiv) {
var input = document.getElementById("inputTextField").value;
var outputDiv = document.getElementById("translationOutputDiv");
input = input.toLowerCase();
//puts user input into an array and then outputs it word by word
const myArray = input.split(" "); //added .split, thank you James, still otherwise broken
let output = "";
let translation = "";
for (let i = 0; i < myArray.length; i++) {
output += myArray[i]; //up to here, this works perfectly to put each word in the string into an array
//prints all words but doesnt translate the second onwards
translation += myArray[i];
if (output == "") {
//document.getElementById("print2").innerHTML = "Translation Here";
}
else if (output == "apple") {
translation = "x-ray";
}
else if (output == "banana") {
translation = "yak";
}
else {
translation = "???";
}
output += " "; //adds a space when displaying original user input
} // END FOR LOOP
document.getElementById("print").innerHTML = output; //this outputs the original user input to the screen
document.getElementById("print3").innerHTML = translation; //this should output the translated output to the screen
} // END FUNCTION CHECKFORTRANSLATION
What it looks like
P.S. I'm not worried about Best Practices here, this is supposed to be a quickie project that I can send to a couple friends and they can open the HTML doc, saved locally, in their browser when they want to mess around with it if they want their half-orc character to say "die by my hammer!" or something. If you have suggestions for making it neater great, but I'm not worried about a mess, no one is going to be reading this but me, and hopefully once it's fixed I'll never have to read it again either!
Since it is a manual simple translation, you should just create a "dictionary" and use it to get the translations.
var dictionary = {
"apple": "x-ray",
"banana": "yak"
}
function checkForTranslation() {
var input = document.getElementById("inputTextField").value.toLowerCase();
var words = input
.split(' ') // split string to words
.filter(function(word) { // remove empty words
return word.length > 0
});
var translatedWords = words.map(function(word) {
var wordTranslation = dictionary[word]; // get from dictionary
if (wordTranslation) {
return wordTranslation;
} else { // if word was not found in dictionary
return "???";
}
});
var translatedText = translatedWords.join(' ');
document.getElementById("translationOutputDiv").innerHTML = translatedText;
}
document.getElementById('translate').addEventListener('click', function() {
checkForTranslation();
});
<input type="text" id="inputTextField" />
<button id="translate">translate</button>
<br/>
<hr />
<div id="translationOutputDiv"></div>
Or if you want it a little more organized, you could use
const dictionary = {
"apple": "x-ray",
"banana": "yak"
}
function getTranslation(string) {
return string
.toLowerCase()
.split(' ')
.filter(word => word)
.map(word => dictionary[word] || '???')
.join(' ');
}
function translate(inputEl, outputEl) {
outputEl.innerHTML = getTranslation(inputEl.value);
}
document.querySelector('#translate').addEventListener('click', function() {
const input = document.querySelector('#inputTextField');
const output = document.querySelector('#translationOutputDiv');
translate(input, output);
});
<input type="text" id="inputTextField" />
<button id="translate">translate</button>
<br/>
<hr />
<div id="translationOutputDiv"></div>

How do I search for words similar to other words?

I am looking to make a small script in Node.js that will match words with another word that is similar. For example I am searching for ***ing and I have an array like ['loving', 'mating', 'cats', 'wording'] then I would expect it to return ['loving', 'mating'] and exclude ['cats'] (because it does not end in ing), and ['wording'] (because it is seven characters and not six.).
This is my current not working code that I have written.
let foundWords = [];
for (let i = 0, len = wordList.length; i < len; i++) {
for (let j = 0, len = wordList[i].split('').length; j < len; j++) {
if (wordToFind.charAt(j) == '*') {
return;
};
if (wordToFind.charAt(j) === wordList[i].charAt(j)) {
if (foundWords.includes(wordList[i]) == false) {
foundWords.push(wordList[i]);
};
}
}
}
console.log(foundWords);
The objective of writing this code is to allow me to brute force with a dictionary list all the combinations for this cryptogram and the words inside.
i really recommend you to read about Levenshtein distance
sound exactly like what you trying to achieve here
https://en.wikipedia.org/wiki/Levenshtein_distance#Example
an implementation in java script also
https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#JavaScript
in information theory and computer science, the Levenshtein distance
is a metric for measuring the amount of difference between two
sequences (i.e. an edit distance). The Levenshtein distance between
two strings is defined as the minimum number of edits needed to
transform one string into the other, with the allowable edit
operations being insertion, deletion, or substitution of a single
character.
Example The Levenshtein distance between "kitten" and "sitting" is 3,
since the following three edits change one into the other, and there
isn't a way to do it with fewer than three edits:
kitten sitten (substitution of 'k' with 's')
sitten sittin (substitution of 'e' with 'i')
sittin sitting (insert 'g' at the end).
You can use Array.prototype.filter along with a RegExp.
To construct the regex you will need to replace your wildcard characters * with the wildcard character of a regex: .. Then add ^ and $ to anchor the regex to match all the way from the beginning to the end of the string.
function filterMatches(needle, haystack) {
const regex = new RegExp('^' + needle.replace(/\*/g, '.') + '$');
return haystack.filter(word => regex.test(word));
}
console.log(filterMatches('***ing', ['loving', 'mating', 'cats', 'wording']));
Hey I think this should work. If you are not understanding a part try to look up the String.prototype functions at MDN. It really helps to know some of these functions since it will make you code more easy.
let input = '***ing';
let inputLength = input.length
let results = [];
while (input.charAt(0) === "*") {
input = input.substr(1);
}
const arr = ['loving', 'mating', 'cats', 'wording'];
for (let i = 0; i < arr.length; i++) {
if (inputLength != arr[i].length) {
continue;
}
if(arr[i].indexOf(input) != -1) {
results.push(arr[i]);
}
}
console.log(results);
Another approach could be like;
function getMatches(ts, ss){
var es = ts.split(/\*+/)[1]; // or ts.match(/[^\*]+$/)[0];
return ss.filter(s => s.endsWith(es) && s.length === ts.length)
}
var res = getMatches("***ing",['loving', 'mating', 'cats', 'wording']);
console.log(res);

Check for open tag in textarea

I'm making a Ukrainian phonetic keyboard. People type in English, and the letters automatically change to the corresponding Ukrainian characters. However, when I'm using it, I sometimes need to write myself notes in English (they serve the same purpose as comments in code- for other people and for myself).
I'd like to indicate the start of a comment with a tag ("<"). How can I check if there's currently an open tag?
I'm thinking something like this:
if (number of "<" is greater than ">") {//if a tag has been opened and not closed
//disable translation, type in English
}
I understand how to disable the translation- however, I'm unsure about the
"if"
How can I check if
number of "<" is greater than ">"
Thanks!
You can count number of specific characters using .match()
In your case
var string = "<<<>>";
if ((string.match(/</g)||[]).length > (string.match(/>/g)||[]).length) {
console.log("More");
}
else {
console.log("Less or equal");
}
counting each of them is like below
var countGreaterThan = (temp1.match(/</g) || []).length;
var countLessThan = (temp1.match(/</g) || []).length;
and temp is the string value of the textarea
Depending on where your data is, you can do:
var data = document.querySelector('#data-container').innerHTML;
var isOpenTagPresent = getSymbolCount('<') > getSymbolCount('<');
if(isOpenTagPresent) {
//execute your logic
}
function getSymbolCount(symbol) {
var count = 0;
for(var i = 0; i < data.length; ++i) {
if(data[i] === symbol) {
count++;
}
}
return count;
}
Hope this helps, cheers!

Is there an equivalent of find_first_of c++ string method in javascript

I come from C++ background and currently working on node.js server app.
I want to know if there exists an equivalent of find_first_of C++ string class method in Javascript string.
Basically I'll have a string like
var str ="abcd=100&efgh=101&ijkl=102&mnop=103". The order of & seprated words could be random. So, I wanted to do something like the following:
str.substr(str.find("mnop=") + string("mnop=").length, str.find_first_of("&,\n'\0'")
Is there a way to it in a single line like above?
You may find the search function useful.
"string find first find second".search("find"); // 7
In addition, you may also find this question useful.
There's no direct equivalent, but you always can employ regular expressions:
var str ="abcd=100&efgh=101&ijkl=102&mnop=103";
console.log(str.match(/&mnop=([^&]+)/)[1]);
However, in this specific case, it's better to use the dedicated module:
var qs = require('querystring');
var vars = qs.parse(str);
console.log(vars.mnop);
If you really want a method that behaves like find_first_of, it can be implemented like this:
String.prototype.findFirstOf = function(chars, start) {
var idx = -1;
[].some.call(this.slice(start || 0), function(c, i) {
if(chars.indexOf(c) >= 0)
return idx = i, true;
});
return idx >= 0 ? idx + (start || 0) : -1;
}
console.log("abc?!def??".findFirstOf('?!')); // 3
console.log("abc?!def??".findFirstOf('?!', 6)); // 8

How can I make a regex that matches any letter in a word X times?

Recently, I started to make a word game which involves making words out of the letters of another word. For example, if you have the word "Teutonic", you could make "tonic", "tone", etc. In the game, you can only use the available letters in the word, and not repeat them. That means that while "tonic" would be valid in the above example, "tonico" would not because there is only one "o" in "Teutonic".
My question is basically, how can I make a regular expression so that it searches for one instance of the letter and remembers it so that, given the same letter later in the loop, doesn't look for it again?
So if you have "Teutonic" and you are searching using a regex for each letter in "tonico" ("t","o","n","i","c","o"), you would get "true" for every one, except for the last "o".
If you have the word "Pencil", you would get true for "pen", "line", and "pile", but not from "Pipe".
Or if you have "small", you would get true for "mall", "malls" but not "mass".
Edit:
Thank you very much for all of your prompt replies!
I suspected that it wasn't going to be possible with a regex approach, but I wanted to make sure. Instead, I experimented a bit with the array method, thought I didn't get very far.
Thanks again guys!
Split the string to array, and then compare with array.
function checkStr(str1, str2) {
var arr1 = str1.split(''),
arr2 = str2.split(''),
i,j;
for (i = 0; i < arr2.length; i++) {
if ((j= arr1.indexOf(arr2[i])) === -1) {
return false;
}
arr1.splice(j, 1);
}
return true;
}
// usage
checkStr('small', 'mall');
Regex is just not a good fit for this task. I would build up a hash of (character -> occurrences) for the source and target strings, then compare the counts in the target hash to make sure they are all <= the corresponding counts in the source hash.
If you stick this logic in an object, you can build the source hash only once and then test against it multiple times (jsFiddle version):
function Comparer(sourceStr)
{
this.buildHash = function(str)
{
var hash = new Object();
for (var i in str)
hash[str[i]] = (hash[str[i]] || 0) + 1;
return hash
};
this.check = function(testStr)
{
var hash = this.buildHash(testStr);
for (var c in hash)
if ((this.sourceHash[c] || 0) < hash[c])
return false;
return true;
};
this.source = sourceStr;
this.sourceHash = this.buildHash(sourceStr);
};
var comp = new Comparer("teutonic");
alert(comp.check("tonic")); // true
alert(comp.check("tint")); // true
alert(comp.check("tonico")); // false
alert(comp.check("asdf")); // false
I'm not really sure this is a suitable use-case for a regular expression? I think because you're looking to use the letters in the original word, in different orders to what they appear, the expression is no longer "regular".
I would imagine that if it can be done, it would require the use of very complex back-references.
I'd personally attack this using some kind of custom (or pre-existing domain-specific) algorithm.
But I must admit I'm not a regex expert so happy to be proven wrong!
// It may not matter, but you can also compare strings without creating arrays.
String.prototype.contains= function(word){
var seed= String(this), i= 0, len= word.length, next= '';
while(i<len){
next= word.charAt(i++);
if(seed.indexOf(next)== -1) return false;
seed= seed.replace(next, '');
}
return word;
}
//testing
var startword= 'teutonic',report='startword= '+startword,
list= ['ton', 'on', 'out', 'tout', 'tone', 'tonic', 'tune', 'nice',
'note', 'not','tot', 'tote', 'ice', 'tic', 'nick', 'cone', 'con', 'cut', 'cute'];
var failed=list.filter(function(itm){
return !startword.contains(itm);
});
report+= failed.length? '\n'+failed.length+' incorrect: '+failed+';\n':'';
report+=list.length+' correct: '+list+';\n';
alert(report);
/* returned value: (String)
startword= teutonic
1 incorrect: nick;
19 correct: ton,on,out,tout,tone,tonic,tune,nice,note,
not,tot,tote,ice,tic,nick,cone,con,cut,cute;
*/

Categories

Resources