Recursive backtracking in javascript (closures) - javascript

I'm trying to implement a recursive backtracking algorithm in javascript (determine if a word is an anagram). The rough idea is I permute every single combination of the different letters, then I compare the final word to the wordlist.
I've gotten the code to work, but the syntax is a little ugly because to avoid the for-loop with closure errors, I've used a self-invoking anonymous function, stored that value, and if it is true, return to break the for loop. I'm just wondering if there is a smarter way to implement the code so I don't need to worry about closures?
var wordList = ['boats', 'horse', 'cow'];
var input = 'oastb';
function isAnagram(sofar, remaining) {
if (remaining.length === 0) {
console.log('returning: ' + (wordList.indexOf(sofar) !== -1))
if (wordList.indexOf(sofar) !== -1) {
console.log(sofar);
return true;
} else {
return false;
}
} else {
for (var i = 0; i < remaining.length; i++) {
var found = (function(index) {
if (isAnagram(sofar + remaining[index], remaining.substring(0, index) + remaining.substring(index + 1))) return true;
})(i);
if(found) return true;
};
}
return false;
}
isAnagram('', input);

Yes, there's an easier way to determine whether a given word is an anagram of a list of words. First, we need to normalize each word so that each anagram produces the same unique word.
For example:
normalize("boats") = "abost";
normalize("horse") = "ehors";
normalize("cow") = "cow";
normalize("oastb") = "abost";
As it turns out, implementing such a function in JavaScript is very simple:
function normalize(word) {
return word
.split("") // convert the string into an array of characters
.sort() // sort the array of characters in lexicographic order
.join(""); // convert the sorted array of characters into a string
}
Next, we create a function that takes a given list of words, normalizes them, and adds them to a dictionary which maps each normalized word to its list of anagrams.
function dictionaryOf(words) {
var dictionary = {};
words.forEach(function (word) {
var norm = normalize(word);
if (dictionary.hasOwnProperty(norm))
dictionary[norm].push(word);
else dictionary[norm] = [word];
});
return dictionary;
}
Then, we create a function which when given a dictionary of normalized words to anagrams and a specific word, returns the list of anagrams of that word.
function anagramsOf(dictionary, word) {
var norm = normalize(word);
return dictionary.hasOwnProperty(norm) ?
dictionary[norm] : [];
}
Finally, we can implement the isAnagram function as follows:
function isAnagram(dictionary, word) {
return anagramsOf(dictionary, word).length > 0;
}
We use it as follows:
var dictionary = dictionaryOf(["boats", "horse", "cow"]);
alert(isAnagram(dictionary, "oastb"));
Putting it all together:
var dictionary = dictionaryOf(["boats", "horse", "cow"]);
alert(isAnagram(dictionary, "oastb"));
function normalize(word) {
return word.split("").sort().join("");
}
function dictionaryOf(words) {
var dictionary = {};
words.forEach(function (word) {
var norm = normalize(word);
if (dictionary.hasOwnProperty(norm))
dictionary[norm].push(word);
else dictionary[norm] = [word];
});
return dictionary;
}
function anagramsOf(dictionary, word) {
var norm = normalize(word);
return dictionary.hasOwnProperty(norm) ?
dictionary[norm] : [];
}
function isAnagram(dictionary, word) {
return anagramsOf(dictionary, word).length > 0;
}
Hope that helps.

Related

counting the same letters in a string but not in all lenght

I'm starting my adventure with javascript and i got one of first tasks.
I must create function that count letter that most occur in string and write this in console.
For example:
var string = "assssssadaaaAAAasadaaab";
and in console.log should be (7,a) <---
the longest string is 7 consecutive identical characters (yes, before count i use .toLowerCase();, because the task requires it)
So far I have it and I don't know what to do next.
Someone want to help?
var string = "assssssadaaaAAAasadaaab";
var string = string.toLowerCase();
function writeInConsole(){
console.log(string);
var count = (string.match(/a/g) || []).length;
console.log(count);
}
writeInConsole();
One option could be matching all consecutive characters using (.)\1* and sort the result by character length.
Then return an array with the length of the string and the character.
Note that this will take the first longest occurrence in case of multiple characters with the same length.
function writeInConsole(s) {
var m = s.match(/(.)\1*/g);
if (m) {
var res = m.reduce(function(a, b) {
return b.length > a.length ? b : a;
})
return [res.length, res.charAt(0)];
}
return [];
}
["assssssadaaaAAAasadaaab", "a", ""].forEach(s => {
s = s.toLowerCase();
console.log(writeInConsole(s))
});
Another example when you have multiple consecutive characters with the same length
function writeInConsole(s) {
let m = s.match(/(.)\1*/g);
if (m) {
let sorted = m.sort((a, b) => b.length - a.length)
let maxLength = sorted[0].length;
let result = [];
for (let i = 0; i < sorted.length; i++) {
if (sorted[i].length === maxLength) {
result.push([maxLength, sorted[i].charAt(0)]);
continue;
}
break;
}
return result;
}
return [];
}
[
"assssssadaaaAAAasadaaab",
"aaabccc",
"abc",
"yyzzz",
"aa",
""
].forEach(s => {
s = s.toLowerCase();
console.log(writeInConsole(s))
});
I'm no sure if this works for you:
string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source)
if (c == '/') count++;
The answer given by using regular expressions is more succinct, but since you say you are just starting out with programming, I will offer a verbose one that might be easier to follow.
var string = "assssssadaaaAAAasadaaab";
var string = string.toLowerCase();
function computeLongestRun(s) {
// we set up for the computation at the first character in the string
var longestRunLetter = currentLetter = string[0]
var longestRunLength = currentRunLength = 1
// loop through the string considering one character at a time
for (i = 1; i < s.length; i++) {
if (s[i] == currentLetter) { // is this letter the same as the last one?
currentRunLength++ // if yes, reflect that
} else { // otherwise, check if the current run
// is the longest
if (currentRunLength > longestRunLength) {
longestRunLetter = currentLetter
longestRunLength = currentRunLength
}
// reset to start counting a new run
currentRunLength = 1
currentLetter = s[i]
}
}
return [longestRunLetter, longestRunLength]
}
console.log(computeLongestRun(string))

Word frequency counter on specific words using javascript

I need to count number of predetermined words (wordlist) in a text. This is what I have done so far:
function frequencies(text, wordlist){
var words = text.split(/\s/);
var freqMap = {};
words.forEach(function(w){
if (!freqMap[w] && wordlist){
freqMap[w] = 0;
}
freqMap[w] += 1;
});
return freqMap;
}
At the moment it counts all the words in given text, how do I make it count only words given in wordlist?
Check word is in given list using Array#indexOf method( or Array#includes method).
function frequencies(text, wordlist) {
var words = text.split(/\s/);
var freqMap = {};
words.forEach(function(w) {
if (wordlist && wordlist.indexOf(w) > -1) { // or wordlist.includes(w)
if (!freqMap[w]) {
freqMap[w] = 0;
}
freqMap[w] += 1;
}
});
return freqMap;
}
This example accounts for some basic punctuation in the text. It will remove said punctuation, split on spaces, then uses reduce to build the object you're wanting.
let wordlist = ['hello', 'bob', 'you', 'later'];
let text = 'hello bob, how are you doing? i hope you are doing well. see you later.';
function frequencies(text, wordlist) {
return text.replace(/(\.|\?|,)/g, '').split(' ').reduce(function(prev, curr) {
if (wordlist.includes(curr)) {
if (prev[curr])
prev[curr]++;
else
prev[curr] = 1;
}
return prev;
}, {});
}
console.log(frequencies(text, wordlist))

Replacing N strings in lines with a regex, where the N strings can have any order in the line

I try to find lines with a number of strings in any order.
Next I replace the strings within the lines with <mark>string-n</mark>.
I use a small parser which translates "string-1&&string-2&&string-N' into
a regex pattern: (?=.*(string-1))(?=.*(string-2))(?=.*(string-N)).*
This works fine to find lines with at least all N strings, but there must be a much better way to replace / mark the strings per line.
The code:
function replacer() {
console.log('args', arguments);
const arg_len = arguments.length;
let result = arguments[arg_len - 1];
for (let i = 1; i < arg_len - 2; i++) {
let re = new RegExp(arguments[i], 'gum');
result = result.replace(re, `<mark>${arguments[i]}</mark>`);
}
return result
}
function testAndPattern(one_test_line) {
const and_pattern = 'string-1&&string-2&&string-n';
const regex_pattern = buildRexPattern(and_pattern);
let result = one_test_line.replace(new RegExp(regex_pattern,"gum"), replacer);
console.log('result', result);
The replacer arguments object shows one time: {0: '..one_test_line..', 1:'string-1', 2:'string-2', 3:'string-n', 4:0, 5: '..One_test_line..'}
The result is also fine. But I needed to use a lot of regular expressions in the replacer.
There must be another and easy way in javascript. Substring replacing is not really happening here.
"one_test_line" is actually a HTML table col with transaction descriptions. But it good be this example:
"The quick brown fox jumps over the lazy dog" and replacing 'dog', 'own' and 'jumps over', giving:
"The quick br<mark>own</mark> fox <mark>jumps over</mark> the lazy <mark>dog</mark>"
Without the dog, nothing will be replaced / marked in this example.
Update: while looking at the problem again I came up with a regex OR in the replacer like /string-1|string-2|string-N/g because in the replacer we know all N strings are present.
function marker(arg) {
return `<mark>${arg}</mark>`;
}
function replacer(re_or) {
return function() {
return arguments[0].replace(re_or, marker)
}
}
function testAndPattern(one_test_line) {
const and_pattern = 'string-1&&string-2&&string-n'
const or_pattern = and_pattern.split('&&').join('|');
const regex_pattern = buildRexPattern(and_pattern);
let result = one_test_line.replace(new RegExp(regex_pattern,"gum"), replacer(new RegExp(`(${or_pattern})`,"gum")));
console.log('result', result);
}
While looking at the problem again, I came up with a regex OR in the replacer like /string-1|string-2|string-N/g because in the replacer we know all N strings are present.
function marker(arg) {
return `<mark>${arg}</mark>`;
}
function replacer(re_or) {
return function(match) {
return match.replace(re_or, marker)
}
}
function testAndPattern(one_test_line) {
const and_pattern = 'string-1&&string-2&&string-n'
const or_pattern = and_pattern.split('&&').join('|');
const regex_pattern = buildRexPattern(and_pattern);
let result = one_test_line.replace(new RegExp(regex_pattern,"gum"), replacer(new RegExp(`(${or_pattern})`,"gum")));
console.log('result', result);
}
Below the parser:
function buildRexPattern(raw_pattern) {
let pattern = '';
const p_list = raw_pattern.split('&&');
// search and-ed list items in any order
if (p_list.length > 1) {
for (let i = 0; i < p_list.length; i++) {
pattern += `(?=.*(${p_list[i]}))`
}
pattern += '.*';
return pattern;
} else {
// search or-ed items in any order, example 'string-1|string-2|string-N'
return raw_pattern;
}
}

A function that will search an array for letters and return the position in which those letters are

I've created a function that will search for individual letters from a string regardless of case and order. Here is what it looks like.
function match(string, pattern) {
string = string.toLowerCase();
pattern = pattern.toLowerCase();
var patternarray = pattern.split("");
for (i = 0; i < pattern.length; i++) {
if (patternarray[i] >= "a" && patternarray[i] <= "z") {
if (string.indexOf(patternarray[i]) == -1) return false
}
}
return true
}
Now I want to do a similar thing except this time I will be searching an array, and instead of returning true/false I would like to return a new array containing the places in which the string pops up.
For example, if my variable contents were ["Oranges","Apples","Bananas"] and my search was "n", the function would return [0,2]. I am a beginner with JavaScript so thorough explanation would be helpful.
Thanks!
function matchArray(array, pattern) {
var i,
len = array.length,
result = [];
for (i = 0; i < len; ++i) {
if (match(array[i], pattern)) {
result.push(i);
}
}
return result;
}
Underscorejs has a function that should take care of this for you. Just take a look at the filter function. It has the ability to return a new array with items that passed a truth test.
var aArray = _.filter(['asdf','ddd','afsf'], function(item){
return (item.indexOf('a') !== -1);
}

Replace text but keep case

I have a set of strings that I need to replace, but I need to keep the case of letters.
Both the input words and output words are of the same length.
For example, if I need to replace "abcd" with "qwer", then the following should happen:
"AbcD" translates to "QweR"
"abCd" translates to "qwEr"
and so on.
Right now I'm using JavaScript's replace, but capital letters are lost on translation.
r = new RegExp( "(" + 'asdf' + ")" , 'gi' );
"oooAsdFoooo".replace(r, "qwer");
Any help would be appreciated.
Here’s a helper:
function matchCase(text, pattern) {
var result = '';
for(var i = 0; i < text.length; i++) {
var c = text.charAt(i);
var p = pattern.charCodeAt(i);
if(p >= 65 && p < 65 + 26) {
result += c.toUpperCase();
} else {
result += c.toLowerCase();
}
}
return result;
}
Then you can just:
"oooAsdFoooo".replace(r, function(match) {
return matchCase("qwer", match);
});
I'll leave this here for reference.
Scenario: case-insensitive search box on list of items, partial match on string should be displayed highlighted but keeping original case.
highlight() {
const re = new RegExp(this.searchValue, 'gi'); // global, insensitive
const newText = name.replace(re, `<b>$&</b>`);
return newText;
}
the $& is the matched text with case
String.prototype.translateCaseSensitive = function (fromAlphabet, toAlphabet) {
var fromAlphabet = fromAlphabet.toLowerCase(),
toAlphabet = toAlphabet.toLowerCase(),
re = new RegExp("[" + fromAlphabet + "]", "gi");
return this.replace(re, function (char) {
var charLower = char.toLowerCase(),
idx = fromAlphabet.indexOf(charLower);
if (idx > -1) {
if (char === charLower) {
return toAlphabet[idx];
} else {
return toAlphabet[idx].toUpperCase();
}
} else {
return char;
}
});
};
and
"AbcD".translateCaseSensitive("abcdefg", "qwertyu")
will return:
"QweR"
Here's a replaceCase function:
We turn the input pattern into a regular expression
We have a nested replacer function which iterates through every character
We use regular expression /[A-Z]/ to identify capital letters, otherwise we assume everything is in lowercase
function replaceCase(str, pattern, newStr) {
const rx = new RegExp(pattern, "ig")
const replacer = (c, i) => c.match(/[A-Z]/) ? newStr[i].toUpperCase() : newStr[i]
return str.replace(rx, (oldStr) => oldStr.replace(/./g, replacer) )
}
let out = replaceCase("This is my test string: AbcD", "abcd", "qwer")
console.log(out) // This is my test string: QweR
out = replaceCase("This is my test string: abCd", "abcd", "qwer")
console.log(out) // This is my test string: qwEr
You could create your own replace function such as
if(!String.prototype.myreplace){
String.prototype.myreplace = (function(obj){
return this.replace(/[a-z]{1,1}/gi,function(a,b){
var r = obj[a.toLowerCase()] || a;
return a.charCodeAt(0) > 96? r.toLowerCase() : r.toUpperCase();
});
});
}
This takes in a object that maps different letters. and it can be called such as follows
var obj = {a:'q',b:'t',c:'w'};
var s = 'AbCdea';
var n = s.myreplace(obj);
console.log(n);
This means you could potentially pass different objects in with different mappings if need be. Here's a simple fiddle showing an example (note the object is all lowercase but the function itself looks at case of the string as well)
Expanding on Ryan O'Hara's answer, the below solution avoids using charCodes and the issues that maybe encountered in using them. It also ensures the replacement is complete when the strings are of different lengths.
function enforceLength(text, pattern, result) {
if (text.length > result.length) {
result = result.concat(text.substring(result.length, text.length));
}
if (pattern.length > text.length) {
result = result.substring(0, text.length);
}
return result;
}
function matchCase(text, pattern){
var result = '';
for (var i =0; i < pattern.length; i++){
var c = text.charAt(i);
var p = pattern.charAt(i);
if(p === p.toUpperCase()) {
result += c.toUpperCase();
} else {
result += c.toLowerCase();
}
}
return enforceLength(text, pattern, result);
}
This should replace while preserving the case. Please let me know if anyone finds any flaws in this solution. I hope this helps. Thank-you!
function myReplace(str, before, after) {
var match=function(before,after){
after=after.split('');
for(var i=0;i<before.length;i++)
{
if(before.charAt(i)==before[i].toUpperCase())
{
after[i]=after[i].toUpperCase();
}
else if(before.charAt(i)==before[i].toLowerCase())
{
after[i]=after[i].toLowerCase();
}
return after.join('');
}
};
console.log(before,match(before,after));
str =str.replace(before,match(before,after));
return str;
}
myReplace("A quick brown fox jumped over the lazy dog", "jumped", "leaped");
I had a sentence where I had to replace each word with another word and that word can be longer/shorter than the word its replacing so its similar to the question but instead of a fixed length, they're dynamic.
My solution
For simplicity, I am focusing on a single word.
const oldWord = "tEsT";
const newWord = "testing";
Split both words so that I can iterate over each individual letters.
const oldWordLetters = oldWord.split("");
const newWordLetters = newWord.split("");
Now, I would iterate over the newWord letters and use its index to then get the corresponding oldWord letter in the same position. Then I would check if the old letter is capital and if it is then make the new letter in the same position capital as well.
for (const [i, letter] of newWordLetters.entries()) {
const oldLetter = oldWordLetters[i];
// stop iterating if oldWord is shorter (not enough letters to copy case).
if (!oldLetter) {
break;
}
const isCapital = oldLetter === oldLetter.toUpperCase();
// make the new letter in the same position as the old letter capital
if (isCapital) {
newWordLetters[i] = letter.toUpperCase();
}
}
The final world would be tEsTing after joining the letters again.
const finalWord = newWordLetters.join("");
console.log(finalWord); // "tEsTing"
Full code
const oldWord = "tEsT";
const newWord = "testing";
const oldWordLetters = oldWord.split("");
const newWordLetters = newWord.split("");
for (const [i, letter] of newWordLetters.entries()) {
const oldLetter = oldWordLetters[i];
// stop iterating if oldWord is shorter (not enough letters to copy case).
if (!oldLetter) {
break;
}
const isCapital = oldLetter === oldLetter.toUpperCase();
// make the new letter in the same position as the old letter capital
if (isCapital) {
newWordLetters[i] = letter.toUpperCase();
}
}
const finalWord = newWordLetters.join("");
console.log(finalWord);
I think this could work
function formatItem(text, searchText){
const search = new RegExp(escapeRegExp(searchText), 'iu')
return text?.toString().replace(search, (m) => `<b>${m}</b>`)
}
function escapeRegExp(text) {
return text?.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') ?? '';
}
Thank you for asking this. I had the same problem when I wanted to search text and replace certain words with links, which was a slightly more specific situation because it is replacing text strings with html strings. I'll put my solution here in case anyone who finds this is doing anything similar.
let elmt = document.getElementById('the-element');
let a = document.createElement('a');
a.href = "https://www.example.com";
let re = new RegExp('the string to find', 'gi');
elmt.innerHTML = elmt.innerHTML.replaceAll(re, function (match) {
a.innerText = match;
return a.outerHTML;
});
So the regular expression ensures that it searches for case-insensitive matches, and the function as the second argument of the replaceAll function specifies that it is supposed to set the innerText of the new tag equal to the old string verbatim, before then returning the outerHTML of the whole tag.
Here is a replaceAllCaseSensitive function. If your want, you can change replaceAll by replace.
const replaceAllCaseSensitive = (
text, // Original string
pattern, // RegExp with the pattern you want match. It must include the g (global) and i (case-insensitive) flags.
replacement // string with the replacement
) => {
return text.replaceAll(pattern, (match) => {
return replacement
.split("")
.map((char, i) =>
match[i] === match[i].toUpperCase() ? char.toUpperCase() : char
)
.join("");
});
};
console.log(replaceAllCaseSensitive("AbcD abCd", /abcd/gi, "qwer"));
// outputs "QweR qwEr"
console.log(replaceAllCaseSensitive("AbcD abCd", /abcd/gi, "qwe"));
// outputs "Qwe qwE"
The function works even if replacement is shorter than match.

Categories