Remove a substring to make given word in javascript - javascript

I want to know about the algorithm for below question in JavaScript.
Check whether the given word can be "programming" or not by removing the substring between them. You can only remove one substring from the given the word.
Give answer in 'yes' and 'no'
example answer explanation
"progabcramming" yes remove substring 'abc'
"programmmeding" yes remove substring 'med'
"proasdgrammiedg" no u have to remove 2 subtring 'asd' and 'ied'
which is not allowed
"pxrogramming" yes remove substring 'x'
"pxrogramminyg" no u have to remove 2 subtring 'x' and 'y'
which is not allowed
Please tell me an algorithm to solve it

{
// will create a regexp for fuzzy search
const factory = (str) => new RegExp(str.split('').join('(.*?)'), 'i')
const re = factory('test') // re = /t(.*?)e(.*?)s(.*?)t/i
const matches = re.exec('te-abc-st') ?? [] // an array of captured groups
const result = matches
.slice(1) // first element is a full match, we don't need it
.filter(group => group.length) // we're also not interested in empty matches
// now result contains a list of captured groups
// in this particular example a single '-abc-'
}

I'm not sure how efficient this code is, but only thing i can come up with is using regular expression.
const word = 'programming';
const test = ['progabcramming', 'programmmeding', 'proasdgrammiedg', 'pxrogramming', 'pxrogramminyg', 'programming'];
// create regular expression manually
// const regexp = /^(p).+(rogramming)|(pr).+(ogramming)|(pro).+(gramming)|(prog).+(ramming)|(progr).+(amming)|(progra).+(mming)|(program).+(ming)|(programm).+(ing)|(programmi).+(ng)|(programmin).+(g)$/;
// create regular expression programmatically
let text = '/^';
word.split('').forEach((character, i) => {
text += i ? `(${word.substring(0, i)}).+(${word.substring(i)})|` : '';
});
text = text.substring(text.length - 1, 1) + '$/';
const regexp = new RegExp(text);
// content output
let content = '';
test.forEach(element => {
content += `${element}: ${regexp.test(element)}\n`;
});
document.body.innerText = content;

Related

Match text outside html tags without use look behind regex in javascript [duplicate]

I have a regex pattern that works fine in regex101.com: ~<a .*?">(*SKIP)(*FAIL)|\bword\b
I am trying to make it a Regexp so it can be used in the replace() function in JavaScript.
The line of JavaScript code is:
var regex = new RegExp("~<a.*?\">(*SKIP)(*FAIL)|\\b"+ word + "\\b", 'g');
Where word is the word I'm trying to match.
When I run it though, the console shows the following error:
Uncaught (in promise) SyntaxError: Invalid regular expression:
/~<a.*?">(*SKIP)(*FAIL)|word/: Nothing to repeat
Am I escaping characters wrong?
I tried backslash-escaping every special character I could find (?, *, < and so on) in my JavaScript code and it still spat out that error.
You can work around the missing (*SKIP)(*FAIL) support in JavaScript using capturing groups in the pattern and a bit of code logic.
Note the (*SKIP)(*FAIL) verb sequence is explained in my YT video called "Skipping matches in specific contexts (with SKIP & FAIL verbs)". You can also find a demo of JavaScript lookarounds for four different scenarions: extracting, replacing, removing and splitting.
Let's adjust the code for the current question. Let's assume word always consists of word characters (digits, letters or underscores).
Extracting: Capture the word into Group 1 and only extract Group 1 values:
const text = `foo foo foobar`;
const word = 'foo';
const regex = new RegExp(String.raw`<a .*?">|\b(${word})\b`, 'gi');
console.log(Array.from(text.matchAll(regex), x=>x[1]).filter(Boolean)); // => 1st word and `>foo<`
Removing: Capture the context you need to keep into Group 1 and replace with a backreference to this group:
const text = `foo foo foobar`;
const word = 'foo';
const regex = new RegExp(String.raw`(<a .*?">)|\b${word}\b`, 'gi');
console.log(text.replace(regex, '$1')); // => foobar
Replacing: Capture the context you need to keep into Group 1 and when it is used, replace with Group 1 value, else, replace with what you need in a callback function/arrow function used as the replacement argument:
const text = `foo foo foobar`;
const word = 'foo';
const regex = new RegExp(String.raw`(<a .*?">)|\b${word}\b`, 'gi');
console.log(text.replace(regex, (match, group1) => group1 || 'buz' ));
// => buz buz foobar
Splitting: This is the most intricate scenario and it requires a bit more coding:
const text = `foo foo foobar`;
const word = 'foo';
const regex = new RegExp(String.raw`(<a .*?">)|\b${word}\b`, 'gi');
let m, res = [], offset = 0;
while (m = regex.exec(text)) { // If there is a match and...
if (m[1] === undefined) { // if Group 1 is not matched
// put the substring to result array
res.push(text.substring(offset, m.index)) // Put the value to array
offset = m.index + m[0].length // Set the new chunk start position
}
}
if (offset < text.length) { // If there is any more text after offset
res.push(text.substr(offset)) // add it to the result array
}
console.log(res);
// => ["", " ", " foobar"]

Vue: Make matching part of input bold, including special hyphens

I have made a simple select component in Vue with a search/filter system. Based on the user input I'm showing some Belgium city suggestions.
Working example: https://codesandbox.io/s/wandering-lake-lecok?file=/src/components/Select.vue (Sometimes there is an error message in Codesandbox. Refresh the build in browser and it should work)
I want to take the UX one step further and show the matching part of the user input bold and underlined. Therefore I have a working makeBold function. By splitting the suggestion string into multiple parts I can add a bold and underline tag and return the suggestion.
computed: {
results() {
return this.options.filter((option) =>
option.display_name
.replaceAll("-'", "")
.toLowerCase()
.includes(this.searchInput.replaceAll("-'", "").toLowerCase())
);
},
},
methods: {
makeBold(str, query) {
const n = str.toUpperCase();
const q = query.toUpperCase();
const x = n.indexOf(q);
if (!q || x === -1) {
return str;
}
const l = q.length;
return (
str.substr(0, x) + "<b><u>" + str.substr(x, l) + "</u></b>" + str.substr(x + l)
);
},
}
One problem, a lot of cities in Belgium use dashes and/or apostrophes. In the suggestions function I'm removing this characters so a user doesn't need to type them. But in the makeBold function I would like to make this characters bold and underlined.
For example:
When the input is 'sint j', 'sintj' or 'Sint-j' I want the suggestions to look like 'Sint-Jans-Molenbeek' and 'Sint-Job in't Goor'
Is there someone who can give me a breakdown on how to achieve this?
I would propose using a mask, to save the city name structure, and after you find the start and end index of substring in city name, restore the original string from mask, inserting the appropriate tags at the start and end index using a replacer function. this way you would not worry about any other non-word characters or other unexpected user input.
Here is the makeBold function:
makeBold(str, query) {
// mask all word characters in city name
const city_mask = str.replace(/\w/g, "#");
// strip city and query string from any non-word character
let query_stripped = query.toLowerCase().replace(/\W/g, "");
let string_stripped = str.replace(/\W/g, "");
// find the index of querystring in city name
let index = string_stripped.toLowerCase().indexOf(query_stripped);
if (index > -1 && query_stripped.length) {
// find the end position of substring in stripped city name
let end_index = index + query_stripped.length - 1;
// replacer function for each masked character.
// it will add to the start and end character of substring the corresponding tags,
// replacing all masked characters with the original one.
function replacer(i) {
let repl = string_stripped[i];
if (i === index) {
repl = "<b><u>" + repl;
}
if (i === end_index) {
repl = repl + "</u></b>";
}
return repl;
}
let i = -1;
// restore masked string
return city_mask.replace(/#/g, (_) => {
i++;
return replacer(i);
});
}
return str;
}
And here is the working sandbox. I've changed a bit your computed results to strip all non-word characters.
One way is to transform your search string into a RegExp object and use replace(regexp, replacerFunction) overload of string to achieve this.
For example the search string is "sintg"
new RegExp(this.searchInput.split("").join("-?"), "i");
Turns it into /s-?i-?n-?t-?g/gi
-? indicates optional - character and
"i" at the end is the RegExp case insensitive flag
Applied to codesandbox code you get this
computed: {
results() {
const regex = new RegExp(this.searchInput.split("").join("-?"), "i");
return this.options.filter((option) => option.display_name.match(regex));
},
},
methods: {
makeBold(str, query) {
const regex = new RegExp(query.split("").join("-?"), "i");
return str.replace(regex, (match) => "<b><u>" + match + "</u></b>");
},
},
Which gives this result
However there is a caveat: There will be errors thrown if the user puts a RegExp special symbol in the search box
To avoid this the initial search input text needs to get RegExp escape applied.
Such as:
new RegExp(escapeRegExp(this.searchInput).split("").join("-?"), "i");
But there is no native escapeRegExp method.
You can find one in Escape string for use in Javascript regex
There is also an escapeRegExp function in lodash library if it's already in your list of dependencies (saves you from adding another function)
You could create a function that removes all spaces and - in the query and city string. If the city includes the query, split the query string on the last letter and get the occurences of that letter in the query. Calculate the length to slice and return the matching part of the original city string.
const findMatch = (q, c) => {
const query = q.toLowerCase().replace(/[\s-]/g, "");
const city = c.toLowerCase().replace(/[\s-]/g, "");
if (city.includes(query)) {
const last = query.charAt(query.length - 1); // last letter
const occ = query.split(last).length - 1; // get occurences
// calculate slice length
const len = c.toLowerCase().split(last, occ).join(" ").length + 1;
return c.slice(0, len);
}
return "No matching city found."
}
const city = "Sint-Jan Test";
console.log(findMatch("sint j", city));
console.log(findMatch("sintj", city));
console.log(findMatch("Sint Jan t", city));
console.log(findMatch("sint-j", city));
console.log(findMatch("Sint-J", city));
console.log(findMatch("SintJan te", city));

How to check if string has vowels? [duplicate]

This question already has answers here:
Counting the vowels in a string using Regular Expression
(2 answers)
Closed 1 year ago.
I tried to write a function which checks if a given string contains vowels and I cannot see why it works for some words 'cat' and 'why' but not 'DOG', i believe that i have accounted for uppercase.
const containsVowels = string => {
var lowerCase = string.toLowerCase();
var word = lowerCase.split("");
var vowelsArray = ["a","o","i","u","y"];
const result = word.filter(letter => vowelsArray.includes(letter));
return result.includes("a","o","i","u","y");
};
includes takes only 2 parameters, the first one being searchElement and second parameter being fromIndex.
Reference : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#parameters
You wouldn't want to do the last check if the result array contains vowels or not, because in the previous step itself you are filtering out the word to get array that contains only vowels. So just check if the array is empty or it contains any elements inside it.
const containsVowels = str => {
let lowerCase = str.toLowerCase();
let word = lowerCase.split("");
let vowelsArray = ["a","o","i","u","y"];
const result = word.filter(letter => vowelsArray.includes(letter));
return result.length > 0;
};
console.log(containsVowels("cat"));
console.log(containsVowels("DOG"));
console.log(containsVowels("BCDF"));
Suggestion: Don't use built in keywords as variables.
As pointed out by Muhammad, we can regex to find if the string contains vowels
const containsVowel = str => {
const vowelRegex = /[aeiou]/i;
return vowelRegex.test(str);
};
2 Problems,
Why would you use includes twice ?
&
You cannot use includes like
result.includes("a","o","i","u","y");
includes only accepts 2 param:
includes(searchElement, fromIndex)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
By filtering, you already know the result.
What you should do is, compare the length of the result:
const containsVowels = string => {
let lowerCase = string.toLowerCase();
let word = lowerCase.split("");
let vowelsArray = ["a","o","i","u","y"];
const result = word.filter(letter => vowelsArray.includes(letter));
return result.length > 0 ? true : false
};
use regex to get the result.
var regEx = /[aeiou]/gi
var test_string = "Cat";
var match = test_string.match(regEx)
if(match)
console.log("Match found", match)
when you write something like this
result.includes("a","o","i","u","y")
this compare with only it's first element which is "a" and one thing you don't need to write the above mentioned code further.
After filtering just replace the above code with
return result.length > 0 ? true : false

Replace substring with its exact length of another character

I am trying to replace a substring within a string with an exact number of other characters.
Example:
Input: Hello There, General Kenobie!
Output: xxxxx There, xxxxxxx Kenobie!
I can get this to work if I replace it with a preset string:
const text = "'Hello' There, 'General' Kenobie!"
const pattern = /(?:'([^']*)')|(?:"([^"]*)")/g;
console.log(text.replace(pattern, "xxx"));
Output: xxx There, xxx Kenobie!
What am I missing wrapping my head around.
Thanks!
You are using a hard-coded string of 'xxx' as your replacement string. So, that's what you are seeing... the string(s) replaced with 'xxx'.
The .replace() method actually supports a function as the replacement, instead of a string, so that's what you need here.
Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter
const text = "'Hello' There, 'General' Kenobie!"
const pattern = /(?:'([^']*)')|(?:"([^"]*)")/g;
const newText = text.replace(pattern, (str, m) => 'x'.repeat(m.length));
console.log(newText);
You can always loop through the matches and replace each separately.
let text = "'Hello' There, 'General' Kenobie!"
const pattern = /(?:'([^']*)')|(?:"([^"]*)")/g;
let array1;
while ((array1 = pattern.exec(text)) !== null) {
wrap = array1[0][0];
text = text.replace(array1[0],wrap + "x".repeat(array1[0].length-2) + wrap);
}
console.log(text)

How to highlight multiple keywords/words in a string with Regex?

I have the following case that I am trying to solve.
Javascript Method that highlights keywords in a phrase.
vm.highlightKeywords = (phrase, keywords) => {
keywords = keywords.split(' ');
let highlightedFrase = phrase;
angular.forEach(keywords, keyword => {
highlightedFrase = highlightedFrase.replace(new RegExp(keyword + "(?![^<])*?>)(<\/[a-z]*>)", "gi"), function(match) {
return '<span class="highlighted-search-text">' + match + </span>';
});
});
return $sce.trustAsHtml(highlightedFrase)
}
How can I write a regular expression that will match this case so that I can replace the substrings
keyowrds = 'temperature high'
phrase = 'The temperature is <span class="highlight">hig</span>h'
ReGex Case
https://regex101.com/r/V8o6gN/5
If I'm not mistaken, your basically wanting to find each word that is a word in your keywords variable and match them in your string so you can wrap them in a span.
You'll want to first turn your keywords into a RegExp, then do a global match. Something like this:
const keywordsString = "cake pie cookies";
const keywords = keywordsString.split(/\s/);
// equivalent to: /(cake|pie|cookies)/g
const pattern = new RegExp(`(${keywords.join('|')})`, 'g');
const phrase = "I like cake, pie and cookies";
const result = phrase.replace(pattern, match => `<span>${match}</span>`);
console.log(result);
Basically, you want a pattern where your keywords are pipe (|) separated and wrapped in parentheses (()). Then you just want to do a global search (g flag) so you match all of them.
With the global flag, there is no need to do a loop. You can get them all in one shot.
Based on #samanime answer.
I remove duplicates, trim spaces and highlight longer words first.
The only problem is a matches that span an element boundary. For example ["elem", "lemen"] in "The element"
const tokens = [...new Set(state.highlight.split(' '))]
.map(s => s.trim())
.filter(s => s.length)
.sort((a,b) => b.length - a.length);
const pattern = new RegExp(`(${tokens.join('|')})`, 'ig');
const highlighted = this.state.value.replace(pattern, match => `<b>${match}</b>`);
console.log(highlighted);

Categories