How to replace matches via regex with an array of strings? - javascript

I have the following sentence with quiz items to solve.
With regex I can easily identify these items and replace them with a text, e.g. to get a quiz sentence.
However, can replace the items with an array of strings, e.g. so that I can build the correct sentence by replacing each item with its appropriate answer?
const regex = /(\[.*?\])/gm;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
const replaceAllRegex = (text, regex, replace) => text.replace(new RegExp(regex, 'g'), replace);
const phrases = {};
phrases.cloze = replaceAllRegex(text, regex, '[_________]');
for (const answer of answers) {
phrases.right = replaceAllRegex(text, regex, answer);
}
console.log(phrases.cloze);
// OUTPUTS: This [_________] a sentence, and those [_________] some apples.
console.log(phrases.right)
// OUTPUTS: This are a sentence, and those are some apples.
// SHOULD OUTPUT: This is a sentence, and those are some apples.

I think that you are considering the correct answers by the order they appear in the answers array. So an option would be, for each element of answers, replace the next regex's match with it.
Something like:
const regex = /(\[.*?\])/;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
let final = text;
for (const answer of answers) {
final = final.replace(regex, answer);
}
console.log(final); // This is a sentence, and those are some apples.
Or you can use the replacer function which String.prototype.replaceAll accepts, with an incrementing counter:
const regex = /(\[.*?\])/g;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
let curr = 0;
final = text.replaceAll(regex, () => {
return answers[curr++];
});
console.log(final);
Note: In both of the previous excerpts of code, you may also want to check if the number of matches is equal to the number of elements in the answers array. I've omitted this kind of verification to keep the code small and just give the idea.

This answer allows you to keep your replaceAllRegex function for both question setup and answer.
The global flag was preventing your script from identifying individual replacements on the second for loop. I made that an argument of your function, and also, passed the modified phrases.right through your iterator rather than text, so it would replace each item in sequence one at a time.
const regex = /(\[.*?\])/gm;
const text = 'This [is/are] a sentence, and those [is/are] some apples.';
const answers = ['is', 'are'];
const replaceAllRegex = (text, regex, replace, global) => text.replace(new RegExp(regex, global || ''), replace);
const phrases = {};
phrases.cloze = replaceAllRegex(text, regex, '[_________]', 'g');
phrases.right = text;
for (const answer of answers) {
phrases.right = replaceAllRegex(phrases.right, regex, answer);
}
console.log(phrases.cloze);
// OUTPUTS: This [_________] a sentence, and those [_________] some apples.
console.log(phrases.right)
// OUTPUTS: This are a sentence, and those are some apples.
// SHOULD OUTPUT: This is a sentence, and those are some apples.

Related

How to count number of occurrences in a very big text for each keyword (regEx) of an array [JS]

This problem is mostly related to an optimization.
Suppose I have a very big text (const text = "...") and an big array of keywords (const keywords = ["one", "good days", "ar.i.t", ...]). You can notice that this keywords might be a single word, multiple words or any RegEx expresion. I need to count how many times each keyword (expression) occurs in the text.
My approach would be as follows:
const text = "..."
const keywords = [...]
keywords.forEach(keyword => {
const re = new RegExp(keyword, "gi");
const count = (text.match(re) || []).length;
})
The problem with this approach is that it is very slow (if text is big enough and array as well). I have to iterate through the entire text again and again until every keyword is checked. Could someone suggest an idea of an algorithm that would go through text only once and count occurence for every keyword, or any idea that could solve the problem faster.
This might help slightly:
const str = 'this is a piece of text this is text this is text';
const countOcc = () => {
const regex = /this/g, result, indices = [];
while ((result = regex.exec(str))) {
indices.push(result.index);
}
return indices.length
}

Could a regular expression be used to find text between pairs of delimiters

I need to parse an email template for custom variables that occur between pairs of dollar signs, e.g:
$foo$bar$baz$foo$bar$baz$wtf
So I would want to start by extracting 'foo' above, since it comes between the first pair (1st and 2nd) of dollar signs. And then skip 'bar' but extract 'baz' as it comes between the next pair (3rd and 4th) of dollar signs.
I was able to accomplish this with split and filter as below, but am wondering, if there's a way to accomplish the same with a regular expression instead? I presume some sort of formal parser, recursive or otherwise, could be used, but that would seem like overkill in my opinion
const body = "$foo$bar$baz$foo$bar$baz$wtf";
let delimitedSegments = body.split('$');
if (delimitedSegments.length % 2 === 0) {
// discard last segment when length is even since it won't be followed by the delimiter
delimitedSegments.pop();
}
const alternatingDelimitedValues = delimitedSegments.filter((segment, index) => {
return index % 2;
});
console.log(alternatingDelimitedValues);
OUTPUT: [ 'foo', 'baz', 'bar' ]
Code also at: https://repl.it/#dexygen/findTextBetweenDollarSignDelimiterPairs
Just match the delimiter twice in the regexp
const body = "$foo$bar$baz$foo$bar$baz$wtf";
const result = body.match(/\$[^$]*\$/g).map(s => s.replace(/\$/g, ''));
console.log(result);
You could use this regex /\$\w+\$/g to get the expected output'
let regex = /\$\w+\$/g;
let str = '$foo$bar$baz$foo$bar$baz$wtf';
let result = str.match(regex).map( item => item.replace(/\$/g, ''));
console.log(result);
You can use capturing group in the regex.
const str1 = '$foo$bar$baz$foo$bar$baz$wtf';
const regex1 = /\$(\w+)\$/g;
const str2 = '*foo*bar*baz*foo*bar*baz*wtf';
const regex2 = /\*(\w+)\*/g;
const find = (str, regex) =>
new Array(str.match(regex).length)
.fill(null)
.map(m => regex.exec(str)[1]);
console.log('delimiters($)', JSON.stringify(find(str1, regex1)));
console.log('delimiters(*)', JSON.stringify(find(str2, regex2)));

Replace regular expression matches with array of values

I have a regular expression to find text with ??? inside a string.
const paragraph = 'This ??? is ??? and ???. Have you seen the ????';
const regex = /(\?\?\?)/g;
const found = paragraph.match(regex);
console.log(found);
Is there a way to replace every match with a value from an array in order?
E.g. with an array of ['cat', 'cool', 'happy', 'dog'], I want the result to be 'This cat is cool and happy. Have you seen the dog?'.
I saw String.prototype.replace() but that will replace every value.
Use a replacer function that shifts from the array of replacement strings (shift removes and returns the item at the 0th index):
const paragraph = 'This ??? is ??? and ???. Have you seen the ????';
const regex = /(\?\?\?)/g;
const replacements = ['cat', 'cool', 'happy', 'dog'];
const found = paragraph.replace(regex, () => replacements.shift());
console.log(found);
(if there are not enough items in the array to replace all, the rest of the ???s will be replaced by undefined)

How can I split commas and periods from words inside of string using split?

I am trying to change specific word in a string with something else. For example, I want to change 'John' in let name = 'Hi, my name is John.'; to 'Jack'.
I know how to split a string by words or characters. I also know how to remove commas, periods, and other symbols in a string. However, if I split the given string with a separator (" "), I will have 'John.' which I do not want. (I know I can switch 'John.' with 'Jack.' but assume that I have an key and value pairs in an object and I am using the values which are names {Father: Jack, Mother: Susan, ...}
I don't know how to separate a string word by word including commas and periods.
For example, if I was given an input which is a string:
'Hi, my name is John.'
I want to split the input as below:
['Hi', ',', 'my', 'name', 'is', 'John', '.']
Does anyone know how to do it?
Below is the challenge I am working on.
Create a function censor that accepts no arguments. censor will return a function that will accept either two strings, or one string. When two strings are given, the returned function will hold onto the two strings as a pair, for future use. When one string is given, the returned function will return the same string, except all instances of a first string (of a saved pair) will be replaced with the second string (of a saved pair).
//Your code here
const changeScene = censor();
changeScene('dogs', 'cats');
changeScene('quick', 'slow');
console.log(changeScene('The quick, brown fox jumps over the lazy dogs.')); // should log: 'The slow, brown fox jumps over the lazy cats.'
I think your real question is "How do I replace a substring with another string?"
Checkout the replace method:
let inputString = "Hi, my name is John.";
let switch1 = ["John", "Jack"];
let switched = inputString.replace(switch1[0], switch1[1]);
console.log(switched); // Hi, my name is Jack.
UPDATE: If you want to get ALL occurrences (g), be case insensitive (i), and use boundaries so that it isn't a word within another word (\\b), you can use RegExp:
let inputString = "I'm John, or johnny, but I prefer john.";
let switch1 = ["John", "Jack"];
let re = new RegExp(`\\b${switch1[0]}\\b`, 'gi');
console.log(inputString.replace(re, switch1[1])); // I'm Jack, or johnny, but I prefer Jack.
You can Try This ...
var string = 'Hi, my name is John.';
//var arr = string.split(/,|\.| /);
var arr = string.split(/([,.\s])/);
console.log(arr);
Using 'Hi, my name is John.'.split(/[,. ]/); will do the job. It will split commas and periods and spaces.
Edit: For those who want to keep the comma and period, here is my wildly inefficient method.
var str = 'Hi, my name is John.'
str = str.replace('.', 'period');
str = str.replace(',', 'comma');
str = str.split(/[,. ]/);
for (var i = 0; i < str.length; i++) {
if (str[i].indexOf('period') > -1) {
str[i] = str[i].replace('period', '');
str.splice(i+1, 0, ".");
} else if (str[i].indexOf('comma') > -1) {
str[i] = str[i].replace('comma', '');
str.splice(i+1, 0, ",");
}
}
console.log(str);

Apply array of string with string.replace

Let's say I have a string like so:
const sentence = "This is my custom string";
I want to highlight the words of a input field inside this sentence.
Let's say a say user typed a string and I have converted the separate words into an array like so:
["custom", "string", "is"]
I know want to replace the words in my sentence with a highlighted version of the words in my array. For a single word I would do something like this:
const word = 'custom';
const searchFor = new RegExp(`(${word})`, 'gi');
const replaceWith = '<strong class="highlight">$1</strong>';
const highlightedSentence = sentence.replace(searchFor, replaceWith);
How can I apply this logic with an array to the entire sentence?
I can't simply loop through it because the string will contain my highlighted class which will also be taken into the highlighting process the the second loop, third loop, etc.
This means that on a second loop if a user where to type:
"high custom"
I would highlight my highlighted class, leading to highlight inception.
For an example of what I mean try commenting/uncommenting the 2 highlighter functions:
https://jsfiddle.net/qh9ttvp2/1/
Your problem is that while replacing words, you replace already added html tag with .class 'highlight'.
Solution here could be to replace anything that is not html tag. Replace this line in you jsfiddle example.
const searchFor = new RegExp(`(${word})(?!([^<]+)?>)`, 'gi');
You can split you sentence into array and check if your element is already highlighted:
let sentence = "This is a some type of long string with all kinds of words in it, all kinds.";
let sentenceArr = sentence.split(' '); // make an array
const query = "kinds words all type";
function highlighter(query, sentence) {
const words = query.match(/\S+/g);
words.forEach((word) => {
// Create a capture group since we are searching case insensitive.
const searchFor = new RegExp(`(${word})`, 'gi');
const replaceWith = '<strong class="highlight">$1</strong>';
sentenceArr = sentenceArr.map(sw => (sw.indexOf('strong class="highlight"') === -1) ? sw.replace(searchFor, replaceWith) : sw); // if already highlited - skip
//sentence = sentence.replace(searchFor, replaceWith);
});
// console.log(sentence);
document.querySelector('.highlighted-sentence').innerHTML = sentenceArr.join(' '); // notice sentenceArr
}
// Works.
//highlighter('kinds words all type', sentence);
// Doesn't work.
highlighter('kinds words high', sentence);
<div class="highlighted-sentence"></div>

Categories

Resources