Replace specific elements (unique or duplicates) from a string in JS - javascript

My code automatically search the string for /d+d/d+ elements (roll dices) and adds random number suffixes and stores them as elements in an array.
I want to create a new string with the new modified elements of my array.
(I don't want to split string in Array, replace the same elements with the other array in a brand new one and then join it to a string. I need to modify it and save it in a new string)
Example:
String changes through user input so if i have:
str = ' I roll 1d3 and 2d4+3 and 1d3 also 1d8 and 1d8 dice ';
then mydice(str) finds all dice names and produces a new array like that:
array = [ "1d3:[2]=2" , "2d4:[1,2]+3=6" , "1d3:[1]=1", "1d8:[7]=7", "1d8:[5]=5"] ;
Desired Output:
str = ' I roll 1d3:[2]=2 and 2d4:[1,2]+3=6 and 1d3:[1]=1 also 1d8:[7]=7 and 1d8:[5]=5 ';

Using only the two items you provide as input (the string and the array with ndn=(n) kind of words), you can proceed as follows:
let str = ' I roll 1d3 and 2d4+3 and 1d3 also 1d8 and 1d8 dice ';
let array = [ "1d3:[2]=2" , "2d4:[1,2]+3=6" , "1d3:[1]=1", "1d8:[7]=7", "1d8:[5]=5"];
let i = 0;
for (let item of array) {
let find = item.replace(/:.*\]|=.*/g, "");
i = str.indexOf(find, i);
str = str.slice(0, i) + item + str.slice(i + find.length);
i += item.length;
}
console.log(str);
It is assumed that the array is well-formed, i.e. that indeed those items were derived correctly from the string and all the string-parts before the equal sign (like "1d3") occur in the string.
Note that strings are immutable, so you cannot really mutate a string. The only way is to create a new string and assign it back to the same variable. But that is not mutation; that is assignment of a new string.

If I understood your requirements, I think your solution is overcomplicated. I'd suggest something like this:
const roll = dice => {
const [num, max] = dice.split('d');
let r = 0;
for (let i = 0; i < num; i++) {
r += Math.floor(Math.random() * max) + 1;
}
return r;
}
let output = input = 'I roll 1d3 and 2d4 and 1d3 also 1d8 and 1d8 dice';
const matches = input.match(/\d+d\d+/g);
const rolls = matches.map(dice => `${dice}=(${roll(dice)})`);
rolls.forEach(roll => {
const [dice] = roll.split('=');
output = output.replace(new RegExp(` ${dice} `), ` ${roll} `);
});
console.log('IN:', input)
console.log('OUT:', output);

Related

Replace element in Array from condition in another array

I'm using Javascript to replace elements in an array from a condition in another array.
I also need the final output to have the "" removed from any element which is replaced.
I have an array, tagArray which generates parts of speech for a given sentence theSentenceToCheck and it looks like this.
tagArray DET,ADJ,NOUN,VERB,ADP,DET,ADJ, NOUN ,ADP,DET,ADJ,NOUN
theSentenceToCheck The red book is in the brown shelf in a red house
I was able to write something that works and generates the desired output but its kinda redundant and total spaghetti.
I've looked at similar questions and tried other approaches using filter, map without success, especially on how to use those approaches and remove the "" for replaced elements.
This is my approach
var grammarPart1 = "NOUN";
var grammarPart2 = "ADJ";
var posToReplace = 0;
function assignTargetToFillIn(){
var theSentenceToCheckArrayed = theSentenceToCheck.split(" ");
var results = [];
var idx = tagArray.indexOf(grammarPart1);
var idx2 = tagArray.indexOf(grammarPart2);
while (idx != -1 || idx2 != -1) {
results.push(idx);
results.push(idx2)
idx = tagArray.indexOf(grammarPart1, idx + 1);
idx2 = tagArray.indexOf(grammarPart2, idx2 + 1);
posToReplace = results;
}
const iterator = posToReplace.values();
for (const value of iterator) {
theSentenceToCheckArrayed[value] ="xtargetx";
}
var addDoubleQuotesToElements = "\"" + theSentenceToCheckArrayed.join("\",\"") + "\"";
var addDoubleQuotesToElementsArray = addDoubleQuotesToElements.split(",");
/**This is where I remove the "" from element replaced with xtargetx*/
const iterator2 = posToReplace.values();
for (const value of iterator2) {
addDoubleQuotesToElementsArray[value] ="xtargetx";
console.log(value);
}
return results;
}
This gives me the desired output
"The",xtargetx,xtargetx,"is","in","the",xtargetx,xtargetx,"in","a",xtargetx,xtargetx
I was wondering what would be a more elegant solution or pointers on which other JS functions to look into.
A more idiomatically correct way to do this leveraging array methods might be like this.
Array.split(" ") splits a sentance into words
Array.filter(word => word.length) removes any value whose length is zero
Array.map((word, index) => {...}) iterates over the array and allows you to keep track of the current index value
Array.includes(element) simply tests that the array includes the value
Array.join(' ') does the opposite of Array.split(' ')
const tagArray = ["DET", "ADJ", "NOUN", "VERB", "ADP", "DET", "ADJ", "NOUN", "ADP", "DET", "ADJ", "NOUN"];
// Split on spaces and remove any zero-length element (produced by two spaces in a row)
const sentanceToCheck = "The red book is in the brown shelf in a red house".split(" ").filter(word => word.length);
const replaceTokens = ["ADJ", "NOUN"];
const replacementWord = "XXX";
const maskedSentance = sentanceToCheck.map((word, index) => {
const thisTag = tagArray[index];
if ( replaceTokens.includes(thisTag) ) {
return replacementWord;
} else {
return word;
}
}).join(' ');
console.log( maskedSentance );

How can I iterate text from a character to another character using Javascript?

Given a url as following :
https://website.com/demo?name=helloworld&timestamp=1234567890
How can I iterate from the = sign and the & sign, resulting in helloworld in JS?
Thanks in advance. :)
1) Though you don't need to iterate here. You can use slice here to get a part of a string
const str = "https://website.com/demo?name=helloworld&timestamp=1234567890";
const start = str.indexOf("=");
const end = str.indexOf("&");
const result = str.slice(start + 1, end);
console.log(result);
2) If you still want to iterate over from = and upto & then you can do
const str = "https://website.com/demo?name=helloworld&timestamp=1234567890";
const end = str.indexOf("&");
const resultArr = [];
for (let start = str.indexOf("=") + 1; start < end; ++start) {
resultArr.push(str[start]);
}
console.log(resultArr.join(""));
3) You can also use regex here
(?<==)\w+
const str = "https://website.com/demo?name=helloworld&timestamp=1234567890";
const match = str.match(/(?<==)\w+/);
console.log(match[0]);
Important note: You can apply this method if you are sure that there is only one "=" and "&" and the text that you want is between them. If that is not the case you will have to search the whole string.
Explanation:
str.indexOf('=') + 1 // will result 30
str.indexOf('&') // will result 40
So, str.slice(30, 40) will give the substring from 30th position to 39th position, it will ignore the 40th position.
let str = 'https://website.com/demo?name=helloworld&timestamp=1234567890'
//1. Find the index of "=" + 1, because slice selects the starting argument.
let subStr = str.slice(str.indexOf('=') + 1, str.indexOf('&'))
console.log(subStr)
You can also utilise regex to figure that out,
const extractStringBetween = (char1,char2) => (str) => str.match(RegExp(`${char1}([^${char1}${char2}]+)${char2}`))[1]
console.log(extractStringBetween('=','&')('https://website.com/demo?name=helloworld&timestamp=1234567890'))
This uses group capture.
You can learn more at https://regexr.com/

Compare two sentences word by word and return the number of word matches with some conditions

Here is a piece of code to compare two sentences word by word and return the number of word matches with some conditions:
hint: the word in the first sentence :::: the word in the second sentence
1) protecting :::: i should result Not matched
2) protecting :::: protect should result matched
3) protect :::: protecting should result matched
4) him :::: i should result Not matched
5) i :::: i should result matched but only once not twice: (let me explain this)
We have this string as the first sentence:
let speechResult = "they're were protecting him i knew that i was aware";
It has two i as you see but there is only one i in the second sentence here:
let expectSt = ['i was sent to earth to protect you'];
So we should consider this match as one occurrence not two, If we had two i occurrences in the second sentence too, then we would consider the i matches as two occurrences.
6) was :::: was should result matched
Here is my code so far:
// Sentences we should compare word by word
let speechResult = "they're were protecting him i knew that i was aware";
let expectSt = ['i was sent to earth to protect you'];
// Create arrays of words from above sentences
let speechResultWords = speechResult.split(/\s+/);
let expectStWords = expectSt[0].split(/\s+/);
// Here you are..
//console.log(speechResultWords)
//console.log(expectStWords)
// Count Matches between two sentences
function includeWords(){
// Declare a variable to hold the count number of matches
let countMatches = 0;
for(let a = 0; a < speechResultWords.length; a++){
for(let b = 0; b < expectStWords.length; b++){
if(speechResultWords[a].includes(expectStWords[b])){
console.log(speechResultWords[a] + ' includes in ' + expectStWords[b]);
countMatches++
}
} // End of first for loop
} // End of second for loop
return countMatches;
};
// Finally initiate the function to count the matches
let matches = includeWords();
console.log('Matched words: ' + matches);
You could count the wanted words with a Map and iterate the given words by checking the word count.
function includeWords(wanted, seen) {
var wantedMap = wanted.split(/\s+/).reduce((m, s) => m.set(s, (m.get(s) || 0) + 1), new Map),
wantedArray = Array.from(wantedMap.keys()),
count = 0;
seen.split(/\s+/)
.forEach(s => {
var key = wantedArray.find(t => s === t || s.length > 3 && t.length > 3 && (s.startsWith(t) || t.startsWith(s)));
if (!wantedMap.get(key)) return;
console.log(s, key)
++count;
wantedMap.set(key, wantedMap.get(key) - 1);
});
return count;
}
let matches = includeWords('i was sent to earth to protect you', 'they\'re were protecting him i knew that i was aware');
console.log('Matched words: ' + matches);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think this should work:
let speechResult = "they're were protecting him i knew that i was aware";
let expectSt = ['i was sent to earth to protect you'];
function includeWords(){
let countMatches = 0;
let ArrayFromStr = speechResult.split(" ");
let Uniq = new Set(ArrayFromStr)
let NewArray = [Uniq]
let str2 = expectSt[0]
for (word in NewArray){
if (str2.includes(word)){
countMatches += 1
}
}
return countMatches;
};
let matches = includeWords();
I get the speechResult, made it into a array, remove duplicates, convert to array again, and then check if the expectSt string contains every word on the NewArray array.
Iterate over the strings and update the index of the matched word with the empty string and store the matches in an array.
let speechResult = "they're were protecting him i knew that i was aware";
let expectSt = ['i was sent to earth to protect you'];
// Create arrays of words from above sentences
let speechResultWords = speechResult.split(/\s+/);
let expectStWords = expectSt[0].split(/\s+/);
const matches = [];
speechResultWords.forEach(str => {
for(let i=0; i<expectStWords.length; i++) {
const innerStr = expectStWords[i];
if(innerStr && (str.startsWith(innerStr) || innerStr.startsWith(str)) && (str.includes(innerStr) || innerStr.includes(str))) {
if(str.length >= innerStr.length) {
matches.push(innerStr);
expectStWords[i] = '';
} else {
matches.push(str);
}
break;
}
}
});
console.log(matches.length);
By using stemming you intuit that words having the same stem are the same.
e.g
for verb: protect, protected, protecting, ...
but also plural: ball, balls
What you may want to do is:
stem the words: use some stemmer (which will have their pros & cons) (e.g PorterStemmer which seem to have a js implem)
count the occurrence on that "stemmed space", which is trivial
NB: splitting with '\s' may not be enough, think about commas and more generally punctuation. Should you have more need, keyword for this is tokenization.
Below an example using PorterStemmer with some poor home made tokenization
const examples = [
['protecting','i'],
['protecting','protect'],
['protect','protecting'],
['him','i'],
['i','i'],
['they\'re were protecting him i knew that i was aware','i was sent to earth to protect you'],
['i i', 'i i i i i']
]
function tokenize(s) {
// this is not good, get yourself a good tokenizer
return s.split(/\s+/).filter(x=>x.replace(/[^a-zA-Z0-9']/g,''))
}
function countWords(a, b){
const sa = tokenize(a).map(t => stemmer(t))
const sb = tokenize(b).map(t => stemmer(t))
const m = sa.reduce((m, w) => (m[w] = (m[w] || 0) + 1, m), {})
return sb.reduce((count, w) => {
if (m[w]) {
m[w]--
return count + 1
}
return count
}, 0)
}
examples.forEach(([a,b], i) => console.log(`ex ${i+1}: ${countWords(a,b)}`))
<script src="https://cdn.jsdelivr.net/gh/kristopolous/Porter-Stemmer/PorterStemmer1980.js"></script>
I think it will provide the primitive solution by comparing sentences' tokens. But here are two pitfalls that I can see:
You should compare both sentences' tokens in your main IF clause by an OR operand
You can add both occurrences in a SET collection to avoid any repetitions.
You can use the below function to get the count of all matched word between two sentence / set of strings.
function matchWords(str1, str2){
let countMatches = 0;
let strArray = str1.split(" ");
let uniqueArray = [...new Set(strArray)];
uniqueArray.forEach( word => {
if (str2.includes(word)){
countMatches += 1
}
})
return countMatches;
};
console.log("Count:", matchWords("Test Match Words".toLowerCase(),"Result Match Words".toLowerCase());
Above code is tested and working.

Insert random string into each instance of whitespace

I'm trying to insert a randomly selected string into each instance of whitespace within another string.
var boom = 'hey there buddy roe';
var space = ' ';
var words = ['cool','rad','tubular','woah', 'noice'];
var random_words = words[Math.floor(Math.random()*words.length)];
for(var i=0; i<boom.length; i++) {
boom.split(' ').join(space + random_words + space);
}
Output comes to:
=> 'hey woah there woah buddy woah roe'
I am randomly selecting an item from the array, but it uses the same word for each instance of whitespace. I want a word randomly generated each time the loop encounters whitespace.
What I want is more like:
=> 'hey cool there noice buddy tubular roe'
Thanks for taking a look.
(This is beta for a Boomhauer twitter bot, excuse the variables / strings 😅)
Maybe you can use regex instead however, you are not seeing the result you desire because you are randomly selecting one word and then replacing all occurrences of a space with it.
The regular expression below replaces occurrences of a space with a dynamic value returned by a callback. You could compare this callback to your for-loop but instead, it's iterating over the spaces found and by doing so you can replace each occurrence with a 'unique' random word.
const boom = 'hey there buddy roe';
const words = ['cool', 'rad', 'tubular', 'woah', 'noice'];
const random = () => Math.floor(Math.random() * words.length);
let replace = boom.replace(/ /g, () => ` ${words[random()]} `);
console.log(replace);
The problem is, that random_words is set to a single word.
Try this instead:
var boom = 'hey there buddy roe';
var words = ['cool','rad','tubular','woah', 'noice'];
boom.replace(/ /g, (space)=> space + words[Math.floor(Math.random()*words.length)] + space);
To get the effect you desire, you need to do the word selecting inside the loop, not outside of it.
for(var i=0; i<boom.length; i++) {
// get a new number every loop
var random_words = words[Math.floor(Math.random()*words.length)];
boom.split(' ').join(space + random_words + space);
}
What is wrong with OP's code: random_words is initialized once only, with a random word. Intention there is, however, to select random word for every whitespace encountered instead.
You can either go with:
for(var i=0; i<boom.length; i++) {
boom.split(' ').join(space + words[Math.floor(Math.random()*words.length)] + space);
}
... or make random_words a function that returns a random word, then call it in your 'boom' loop. With every call, a new word selection will occur.
You need to recalculate the random word on each loop. Right now you have picked out a single random word, stored it in the random_words variable, and you reuse it each time. You could modify your code like this:
var boom = 'hey there buddy roe';
var space = ' ';
var words = ['cool','rad','tubular','woah', 'noice'];
function getRandomWord() {
return words[Math.floor(Math.random()*words.length)];
}
// Uses the same because the value given to join is not recalculated each time:
console.log(boom.split(' ').join(space + getRandomWord() + space));
// You could do this with a standard for loop:
let result = "";
let split = boom.split(' ')
for(var i=0; i<split.length; i++) {
result += split[i] + space;
if (i === split.length - 1) break;
result += getRandomWord() + space;
}
console.log(result);
// Or you can use a reduce:
let otherResult = boom.split(' ').reduce((res, word, index, split) => {
if (index === split.length - 1) return res + space + word;
return res + space + word + space + getRandomWord();
});
console.log(otherResult)

A better way to get an array of strings separating a string on N character incrementally

I am trying to find a better way to get an array of strings based on a string by separating on a special character, in my case the / character.
So given the following input:
"/bob/ross/is/awesome/squirrels"
I would like to end up with:
[
"/bob/ross/is/awesome/squirrels",
"/bob/ross/is/awesome",
"/bob/ross/is",
"/bob/ross",
"/bob"
]
I must admit that despite my years of experience I am stuck as to how to approach this eloquently and succinctly.
Here is my current code:
getWords = function( string ){
// Get an array of all words in the provided string separating on "/"
let words = string.split("/");
// Filter out empty strings from leading/trailing "/" characters
words = words.filter( function(a){return a !== ""} );
// Create an array to store results in
let results = [];
// Create an iteration for each word in the array
for (var i=0, j=words.length; i<j; i++){
// Create a string to concatenate to
let result = "";
// Loop over each word in the array minus the current iteration of "i"
for (var k=0, l=words.length - i; k<l; k++){
// Contatenate using the special character plus the current word
result += "/" + words[k];
}
// Push the resulting string to the results array
results.push(result);
}
// Return the results array
return results;
}
And a code snippet for you to check what's going on:
getWords = function( string ){
// Get an array of all words in the provided string separating on "/"
let words = string.split("/");
// Filter out empty strings from leading/trailing "/" characters
words = words.filter( function(a){return a !== ""} );
// Create an array to store results in
let results = [];
// Create an iteration for each word in the array
for (var i=0, j=words.length; i<j; i++){
// Create a string to concatenate to
let result = "";
// Loop over each word in the array minus the current iteration of "i"
for (var k=0, l=words.length - i; k<l; k++){
// Contatenate using the special character plus the current word
result += "/" + words[k];
}
// Push the resulting string to the results array
results.push(result);
}
// Set the results to display
resultsDisplay.innerHTML = results.toString().replace(/,/g, " -- ");
// Return the results array
return results;
}
input = document.getElementsByTagName("input")[0];
resultsDisplay = document.getElementsByClassName("results")[0];
input.addEventListener("input", function(){
getWords(input.value)
});
getWords(input.value);
<input value="bob/ross/is/awesome/squirrels" />
<p class="results"></p>
You may do as follows;
var str = "/bob/ross/is/awesome/squirrels",
res = s => s.length ? [s].concat(res(s.match(/.*(?=\/.+$)/)[0])) : [];
console.log(res(str));
Explanation: So in the above snippet we have a String.match() portion with regexp which matches the text before the last "/" character. This matched text will be the next string we will feed to the res function call recursively from within itself like res(s.match(/.*(?=\/.+$)/)[0]).
So the res function is a recursive one with a terminating condition being an argument s of 0 length string in which case it returns an empty array. However if s is not empty then it's first placed in an array and then the array is concatenated with the result of a recursive call and returned.
This would do it:
var path = "/bob/ross/is/awesome/squirrels";
var arr = path.split('/').filter(val => val);
var res = arr.map((val, idx) => '/'+arr.slice(0,idx+1).join('/'))
console.log(res)
Here's another approach:
let str = "/bob/ross/is/awesome/squirrels";
let re = /\/+[^\/]+/g;
let result = [];
while (re.exec(str)) {
result.push(str.substr(0, re.lastIndex));
}
result.reverse();
console.log(result);

Categories