I'm currently programming a Discord bot, and was wondering if it is possible to predict the wanted command if the input was incorrect.
For example, I have this list of words :
['help','meme','ping'],
and if the user inputs "hepl", would it somehow be possible to "guess" they meant to type help ?
One option would be to find a command whose levenshtein distance from the input is 2 or less:
// https://gist.github.com/andrei-m/982927
const getEditDistance=function(t,n){if(0==t.length)return n.length;if(0==n.length)return t.length;var e,h,r=[];for(e=0;e<=n.length;e++)r[e]=[e];for(h=0;h<=t.length;h++)r[0][h]=h;for(e=1;e<=n.length;e++)for(h=1;h<=t.length;h++)n.charAt(e-1)==t.charAt(h-1)?r[e][h]=r[e-1][h-1]:r[e][h]=Math.min(r[e-1][h-1]+1,Math.min(r[e][h-1]+1,r[e-1][h]+1));return r[n.length][t.length]};
const commands = ['help','meme','ping'];
const getCommand = (input) => {
if (commands.includes(input)) return input;
return commands.find(command => getEditDistance(input, command) <= 2);
};
console.log(getCommand('hepl'));
(2 is just a number, feel free to pick the tolerance you want - the higher it is, the more commands will be guessed at, but the more false positives there will be)
You can find hits and show many words in suggestion. If you want same you can use to show most hit word.
const words = ["help", "meme", "ping"];
const getHits = (word, wordToMatch, hits = 0) => {
if (!word.length || !wordToMatch.length) return hits;
let charW = word.slice(0, 1);
let index = wordToMatch.indexOf(charW);
if (index !== -1) {
return getHits(
word.slice(1),
String(wordToMatch.slice(0, index) + wordToMatch.substr(index + 1)),
hits + 1
);
}
return getHits(word.slice(1), wordToMatch, hits);
};
const getMatch = mword => {
return words.reduce((m, word) => {
m[word] = getHits(mword, word);
return m;
}, {});
};
const sort = obj => {
return Object.entries(obj).sort(
([_, value1], [__, value2]) => value2 - value1
);
};
console.log(getMatch("help"));
console.log(sort(getMatch("help")));
console.log(getMatch("me"));
console.log(sort(getMatch("me")));
.as-console-row {color: blue!important}
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed last month.
Improve this question
lets say the input is
let word = 'I Lo1ve Co4ding'
the output would be I Loove Coooooding
so repeating x letters after putting a number after
Still don't understand how it works and how to replace it mid string.
You can use the callback function argument in String.prototype.replace, and call String.prototype.repeat on the first matching group (letter) and pass the second matching group (number) plus 1.
const expandStr = (str) =>
str.replace(/([a-z])(\d+)/gi, (g, g1, g2) => g1.repeat(+g2 + 1));
console.log(expandStr('I Lo1ve Co4ding'));
Caveat
As suggested in the comments, you may use the following:
/(\p{L})(\p{N}+)/gu
In place of:
/([a-z])(\d+)/gi
Explanation:
\p{L} – matches a single code point in the category "letter"
\p{N} – matches any kind of numeric character in any script
\p{S} – symbols e.g. emoji
const expandStr = (str) =>
str.replace(/(\p{L}|\p{S})(\p{N}+)/gu, (g, g1, g2) => g1.repeat(+g2 + 1));
console.log(expandStr('I Lo1ve Co4ding'));
console.log(expandStr('I give 📽️ I saw 👍1!')); // Works with emoji
Please refer to "Unicode Categories" to learn more.
Alternative pattern: /([^(\p{N}|\s)])(\p{N}+)/gu
Tokenizing
Here is a more traditional example that incorporates loops. It does not use regular expressions, but follows a tokenizer (parser) approach.
Note: Keep in mind that this naïve example does not account for Unicode.
const
NUMBER_RANGE_START = '0' .charCodeAt(0), // 48
NUMBER_RANGE_END = '9' .charCodeAt(0), // 57
LETTER_UPPER_RANGE_START = 'A' .charCodeAt(0), // 65
LETTER_UPPER_RANGE_END = 'Z' .charCodeAt(0), // 90
LETTER_LOWER_RANGE_START = 'a' .charCodeAt(0), // 97
LETTER_LOWER_RANGE_END = 'z' .charCodeAt(0), // 122
WHITESPACE_CARRIAGE_RETURN = '\r'.charCodeAt(0), // 13
WHITESPACE_LINE_FEED = '\n'.charCodeAt(0), // 10
WHITESPACE_SPACE = ' ' .charCodeAt(0), // 32
WHITESPACE_TAB = '\t'.charCodeAt(0); // 9
const codeInRange = (code, start, end) => code >= start && code <= end;
const charInRange = (char, start, end) => codeInRange(char.charCodeAt(0), start, end);
const isNumber = (char) =>
charInRange(char, NUMBER_RANGE_START, NUMBER_RANGE_END);
const isUpperLetter = (char) =>
charInRange(char, LETTER_UPPER_RANGE_START, LETTER_UPPER_RANGE_END);
const isLowerLetter = (char) =>
charInRange(char, LETTER_LOWER_RANGE_START, LETTER_LOWER_RANGE_END);
const isLetter = (char) => isLowerLetter(char) || isUpperLetter(char);
const isWhiteSpace = (char) => {
switch (char.charCodeAt(0)) {
case WHITESPACE_CARRIAGE_RETURN:
case WHITESPACE_LINE_FEED:
case WHITESPACE_SPACE:
case WHITESPACE_TAB:
return true;
default:
return false;
}
};
const expandStr = (str) => {
const result = [];
let index, char, prevChar, count, step;
for (index = 0; index < str.length; index++) {
char = str[index];
if (
isNumber(char) &&
(
isLetter(prevChar) ||
(
!isWhiteSpace(prevChar) &&
!isNumber(prevChar)
)
)
) {
count = parseInt(char, 10);
for (step = 0; step < count; step++) {
result.push(prevChar);
}
} else {
result.push(char);
}
prevChar = char;
}
return result.join('');
};
console.log(expandStr('I Lo1ve Co4ding')); // I Loove Coooooding
A little bit longer way as from Mr.Polywhirl. But I think foreach loops make for good readability and you will see how it works.
const word = 'I Lo1ve Co4ding'
function rewrite(w) {
const arr = [...w];
let n = [];
arr.forEach((s,i) => {
if(! isNaN(s) && s != ' ') {
n.push(arr[i-1].repeat(s));
} else {
n.push(s);
}
});
return n.join('');
}
console.log( rewrite(word) );
I need to execute an expression according to Order of Operations, and I can't figure out how to resolve the side-by-side brackets.
I am receiving an expression as a string ->
"(add (multiply 4 5) (add 13 1))"
I can't figure out how to resolve this side by side case.
How I have it working for a case where everything is nested by grabbing the innermost brackets and then solving the next like this:
// Find innermost equasion
const findInnerEq = (str) =>
str.substring(str.lastIndexOf("(") + 1, str.lastIndexOf(")"));
// Find Brackets RegEx
const brakets = /(?<=\().*(?=\))/g;
// Changing RegEx function
const changeRE = (str) =>
str.slice(str.lastIndexOf("(") + 1, str.indexOf(")"));
// Return calculation
const calculate = (str) => {
let exp = findInnerEq(str).toLowerCase().split(" ");
let calc = exp
.slice(1)
.map((element) => parseInt(element))
switch (exp[0]) {
case "add":
if (calc.length > 2){
return calc.reduce((acc, cur) => acc + cur, 0);
}
return calc[0] + calc[1]
break;
case "multiply":
return calc.reduce((acc, cur) => acc * cur, 1);
break;
default:
console.log("Please enter valid expression");
process.exit();
}
};
// Recursive function
const calculator = (str) => {
let curString;
if (str.match(brakets)) {
curString = str;
let changingReg = `\(${changeRE(curString)}\)`;
curString = curString.replace(changingReg, calculate(curString));
return calculator(curString);
} else {
return str;
}
};
console.log(calculator("(add 2 (multiply 2 2))"));
console.log(calculator("(add (multiply 2 2) (add 2 2)"));
How can I deal with the side-by-side brackets?
Well, it looks like a classic problem from CS, and you are approaching it wrong.
What you should do instead, is use stacks, pushing items to them until you meet ')', which will tell you to execute the action on the stack collected so far.
Here is one of explanations https://orkhanhuseyn.medium.com/what-are-stack-based-calculators-cf2dbe249264
try this:
let arr = str.split("(").join("|:|:|:|").split(")").join("|:|:|:|").split("|:|:|:|")
//arr is now an array that has been split at both ( and )
// the middle value: arr[arr.length/2]
//evaluate it, and then combine it with arr[arr.length/2+1] and arr[arr.length/2-1]
//continue the cycle until your whole expression is evaluated
I want to compare two sentences. Here is the example:
Correct: Experts believe industrial development will help the economy.
Given: Xperts does believe that development won't economy.
Expected Output:
Experts Xperts does believe industrial that development will won't help the economy.
I have tried to compare string by splitting the both words and checking them.
let given= "Xperts does believe that development won't economy.";
let correct= "Experts believe industrial development will help the economy.";
function checkSentence(given,correct){
let final='';
for(i=0; i<given.length; i++){
if(given[i].trim()==correct[i].trim()){
final += given[i]+' ';
}else{
final += "<i>given[i]</i> <b>correct[i]</b>";
}
}
return final;
}
I would solve the problem recursively with the following steps:
Try to find the closest matching word in each sentence
If match found: Split each sentence at the matching word and run the same function on each side
If no match found: Both sentences have nothing in common and you can return each sentence formatted in the way you want
Note: I start and try to find the longest matching word because those are most indicative of sentence structure rather than searching for 'it' and 'and'
const correct= "Experts believe industrial development will help the economy.";
const given= "Xperts does believe development that won't economy.";
const correctArray = correct.split(" ")
const givenArray = given.split(" ")
// Returns [correctIndex, givenIndex] if match found or [-1, -1] if no match found
function findIndexOfLongestMatchingWord(correctArray, givenArray) {
// Create an array of word length and its index for the correct word array
const correctWordLengthIndexArray = correctArray.map((word, index) => [word.length, index]).sort(([length1, index1], [length2, index2]) => length2 - length1)
for(let matchingIndex = 0; matchingIndex < correctArray.length; matchingIndex++) {
const correctArrayIndex = correctWordLengthIndexArray[matchingIndex][1]
const correctArrayWord = correctArray[correctArrayIndex]
const foundIndexOfGivenWord = givenArray.findIndex(givenWord => givenWord === correctArrayWord)
if(foundIndexOfGivenWord != -1) return [correctArrayIndex, foundIndexOfGivenWord];
}
return [-1,-1]
}
function formatCorrectArray(correctArray) {
return correctArray.length == 0 ? "" : `<b>${correctArray.join(" ")}</b>`
}
function formatGivenArray(givenArray) {
return givenArray.length == 0 ? "" : `<i>${givenArray.join(" ")}</i>`
}
function findDifferenceRecursively(correctArray, givenArray) {
// If either array empty there is nothing to compare, return each one formatted
if(correctArray.length == 0 || givenArray.length == 0) {
return formatCorrectArray(correctArray) + formatGivenArray(givenArray)
}
const [correctIndex, givenIndex] = findIndexOfLongestMatchingWord(correctArray, givenArray);
if (correctIndex != -1) {
// Split each string at their index and run find difference on each side of the indexes;
const leftCorrect = correctArray.slice(0, correctIndex)
const rightCorrect = correctArray.slice(correctIndex + 1)
const leftGiven = givenArray.slice(0, givenIndex)
const rightGiven = givenArray.slice(givenIndex + 1)
// Run function on words on each side
return findDifferenceRecursively(leftCorrect, leftGiven) + ` ${correctArray[correctIndex]} ` + findDifferenceRecursively(rightCorrect, rightGiven)
} else {
return formatCorrectArray(correctArray) + formatGivenArray(givenArray)
}
}
const result = findDifferenceRecursively(correctArray, givenArray)
// Returns "<b>Experts</b><i>Xperts does</i> believe <b>industrial</b> development <b>will help the</b><i>that won't</i> economy. "
console.log(result)
So I have the below setup where a user inputs text and I want to see if any of the inputted characters match the words. It all works great when I input say.
const input = 'memb';
but if do something like this.
const input = 'member has';
Then it returns false. It should stay true if it finds a match of characters which it does, member is a match. Member has is also a match as the characters m, e, m, b, e, r is still a match event though h, a, s doesn't match any the other words.
Anyone know how I can get it to keep returning true if the characters match ?
const input = 'member has';
const inputLower = input.toLowerCase();
const words = ['member', 'support', 'life'];
const result = words.some(word => {
const words = word.split(',');
return words.some(r => r.toLowerCase().includes(inputLower));
});
console.log('result = ', result);
You can add the reverse logic, where you also check whether inputLower includes r:
const input = 'memb has';
let inputLower = input.toLowerCase();
const words = ['member', 'support', 'life'];
const result = words.some(word => {
const words = word.split(',');
return words.some(r => {
if (~inputLower.indexOf( " " )) {
// only check the first word if there are multiple
inputLower = inputLower.substring( 0, inputLower.indexOf( " " ) );
}
return r.toLowerCase().includes(inputLower) || inputLower.includes(r.toLowerCase());
});
});
console.log('result = ', result);
I am trying to make a function that loops through a word, identifies the first vowel found (if any) in the word, and then splits up the word after the vowel.
example input: 'super'
example output: 'su', 'per'
function consAy(word){
if(word[i].indexOf("a" >= 0) || word[i].indexOf("e" >= 0) || word[i].indexOf("i" >= 0) || word[i].indexOf("o" >= 0) || word[i].indexOf("u" >= 0)){
}
One way to do it is to use a regular expression to .match() the pattern you are looking for:
function consAy(word){
var result = word.match(/^([^aeiou]*[aeiou])(.+)$/i)
return result ? result.slice(1) : [word]
}
console.log( consAy('super') )
console.log( consAy('AMAZING') )
console.log( consAy('hi') )
console.log( consAy('why') )
The function I've shown returns an array. If there was a vowel that was not at the end then the array has two elements. If there was only a vowel at the end, or no vowel, then the array has one element that is the same as the input string.
A brief breakdown of the regex /^([^aeiou]*[aeiou])(.+)$/i:
^ // beginning of string
[^aeiou]* // match zero or more non-vowels
[aeiou] // match any vowel
.+ // match one or more of any character
$ // end of string
...where the parentheses are used to create capturing groups for the two parts of the string we want to separate, and the i after the / makes it case insensitive.
The .match() method returns null if there was no match, so that's what the ternary ?: expression is for. You can tweak that part if you want a different return value for the case where there was no match.
EDIT: I was asked for a non-regex solution. Here's one:
function consAy(word){
// loop from first to second-last character - don't bother testing the last
// character, because even if it's a vowel there are no letters after it
for (var i = 0; i < word.length - 1; i++) {
if ('aeiou'.indexOf(word[i].toLowerCase()) != -1) {
return [word.slice(0, i + 1), word.slice(i + 1)]
}
}
return [word]
}
console.log( consAy('super') )
console.log( consAy('AMAZING') )
console.log( consAy('hi') )
console.log( consAy('why') )
This assumes a reasonably modern browser, or Node.
const string = "FGHIJK";
const isVowel = c => c.match(/[AEIOU]/i);
const pos = [...string].findIndex(isVowel);
const truncatedString = `${[...string].slice(0, pos + 1)}`;
truncatedString; // "FGHI"
Edit
As has been pointed out, the above is significantly more hassle than it's worth. Without further ado, a much saner approach.
const string = "FGHIJK";
const vowels = /[aeiou]/i;
const truncateAfter = (string, marker) => {
const pos = string.search(marker);
const inString = pos >= 0;
return string.slice(0, inString ? pos : string.length);
};
const truncated = truncateAfter(string, vowels);
Without using a RegEx of any kind. Ye ole fashioned algorithm.
const truncateAfter = (string, markers) => {
let c = "";
let buffer = "";
for (let i = 0, l = string.length; i < l; i += 1) {
c = string[i];
buffer += c;
if (markers.includes(c)) {
break;
}
}
return buffer;
};
const truncatedString = truncateAfter(
"XYZABC",
["A", "E", "I", "O", "U"],
);
With RegEx golf.
const string = "BCDEFG";
const truncatedString = string.replace(/([aeiou]).*/i, "$1");
With a reduction.
const isVowel = c => /[aeiou]/i.test(c);
const last = str => str[str.length - 1];
const truncatedString = [...string].reduce((buffer, c) =>
isVowel(last(buffer)) ? buffer : buffer + c, "");
Via a dirty filter hack, that takes way too much power O(n**2).
const truncatedString = [...string]
.filter((_, i, arr) => i <= arr.search(/[aeiou]/i));
There are others, but I think that's enough to shake the cobwebs out of my brain, for now.
I always like to take opportunities to write incomprehensible array-based code, so with that in mind...
const regexMatcher = pattern => input => {
return input.match(pattern)
};
const splitAtFirstMatch = matcher => arrayLike => {
return [...arrayLike]
.reduce(([pre, post, matchFound], element) => {
const addPre = matchFound || matcher(element);
return [
matchFound ? pre :[...pre, element],
matchFound ? [...post, element] : post,
addPre
];
}, [[],[], false])
.slice(0, 2)
.map(resultArrays => resultArrays.join(''));
};
console.log(splitAtFirstMatch(regexMatcher(/[aeiou]/))('super'));