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 );
Related
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.
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);
I've two strings in JavaScript like
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>"
var Title = "<DP_A>.<Del.Dce Lks.1>.<Pl.Rrs Bk 0.310-PT-304_(1)>"
here {Link} and {strm} are placeholders or more likely whatever comes between { } is placeholder
I need to compare both string like description and Title to find placeholder values, Output needs to be like
{"Link" : 1, "strm" : 1 }
or array
[{Link" : 1, "strm" : 1}]
I've tried some RegEx but not working, any help??
if (description.includes("{")) {
var found = [], // an array to collect the strings that are found
rxp = /{([^}]+)}/g,
curMatch;
while (curMatch = rxp.exec(description)) {
found.push(curMatch[1]);
}
}
I'm able to get array of Placeholders but not able to find values into title string.
You could get all parts and then splice the values out of the title string.
"<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>",
"<DP_A>.<Del.Dce Lks. 1 >.<Pl.Rrs Bk 0.310-PT-304_( 1 )>";
function getParts(pattern, values) {
var result = {}, value, p1, p2 = 0;
(pattern.match(/[^{}]+/g) || []).forEach((s, i, a) => {
if (i % 2) return Object.assign(result, { [s]: value });
p1 = values.indexOf(s, p2),
p2 = values.indexOf(a[i + 2], p1);
value = values.slice(p1 + s.length, p2 === -1 ? undefined : p2);
});
return result;
}
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>{last}",
title = "<DP_A>.<Del.Dce Lks.abcdef>.<Pl.Rrs Bk 0.310-PT-304_(ghijklöööö)>fubar";
console.log(getParts(description, title));
With a for statement and reusing known positions.
function getParts(pattern, values) {
var parts = pattern.match(/[^{}]+/g),
result = {}, p1, p2, i;
if (!parts || parts.length < 2) return {};
p1 = values.indexOf(parts[0]);
for (i = 1; i < parts.length; i += 2) {
p2 = values.indexOf(parts[i + 1], p1);
Object.assign(result, { [parts[i]]: values.slice(p1 + parts[i - 1].length, p2 === -1 ? undefined : p2) });
p1 = p2;
}
return result;
}
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>{last}",
title = "<DP_A>.<Del.Dce Lks.abcdef>.<Pl.Rrs Bk 0.310-PT-304_(ghijklöööö)>fubar";
console.log(getParts(description, title));
Use replace:
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>"
const obj = {
Link: 1,
strm: 2
};
const res = description.replace(/{(.*?)}/g, m => obj[m.slice(1, -1)]);
document.write(res);
Okay, this is far more complex than I actually expected.
I'm not actually that good at this kind of operations, but here is a "working" solution: you may want to rewrite it a bit, but still, the concept is actually fair to me.
The steps followed to achieve the results are:
Acquire all the indexes of "{". I've used a function generator below, but you may use whathever other criteria you want. The goal is to acquire the starting bracket of each match.
loop each matched bracket, look for the closing bracket and acquire the character just after it in the description string.
perform the value match upon the Title string.
Continue by applying currently matched values to update the offsets.
Map the result to collect the desired output: I've intentionally returned an array of items because a placeholder may exist twice.
Some side notes:
The below script, as mentioned above, won't take care of limit cases like "{hello{world}".
The below script can be improved by matching both the previous character and the next character.
The below script might fail in some situations, it just happens to work in this case, but I didn't test it with limit cases.
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>";
var Title = "<DP_A>.<Del.Dce Lks.1>.<Pl.Rrs Bk 0.310-PT-304_(1)>";
// Acquire all the indexes of every "{".
// BEWARE: This will actually fail if the description is "<{LLT{hello}", but you may change this.
const descriptionLookupIndexes = [].concat(...(function*(){
for (var i = 0; i < description.length; i++) {
if (description[i] === "{") yield [i];
}
})());
let matches = [];
descriptionLookupIndexes.forEach((i, index) => {
// acquire the description by replacing the currently known values.
let _replacedDescription = description;
let _replacedDescriptionIndex = i - matches.reduce((a,b) => a + b.amount, 0);
// This foreach will replace the placeholders already found with their respective values.
matches.forEach(k => {
let splitted = _replacedDescription.split('');
splitted.splice(k.from, k.amount, [k.value.split('')]);
_replacedDescription = splitted.join('');
});
// Acquire the relevant portion of the string.
const needle = _replacedDescription.substring(_replacedDescriptionIndex, _replacedDescription.length);
// Look for the next character after the first } occurrence in the current substring.
const nextChar = needle[needle.indexOf("}") + 1];
// Acquire the relevant substring for the title.
const titleNeedle = Title.substring(_replacedDescriptionIndex, Title.length);
matches.push({
from: _replacedDescriptionIndex,
amount: needle.match(/[^{\}]+(?=})/g)[0].length + 1,
needle: needle.match(/[^{\}]+(?=})/g)[0],
value: titleNeedle.substring(0, titleNeedle.indexOf(nextChar))
});
});
// Matches is now the array with all the occurrences, let's just map it to acquire a new array of objects with the desired format.
// BEWARE: If multiple keys exists, they will be mapped to an array.
const res = matches.reduce((acc, next) => {
acc[next.needle] = acc[next.needle] || [];
acc[next.needle].push({
[next.needle]: next.value
});
return acc;
}, {});
console.log(res);
How do I get the following conversion using Regex?
Content(input data structure):
a-test
b-123
c-qweq
d-gdfgd
e-312
Conversion:
1-test
2-123
3-qweq
4-gdfgd
Final-312
var index = 1;
function c_replace() {
if(index == 5) { return "Final"; }
return index++;
}
there you go :D
// i assume you have a string input that contains linebreaks due to your question format
const input = `a-test
b-123
c-qweq
d-gdfgd
e-312`.trim(); // removing whitespace in front or behind the input data.
//splitting the lines on whitespace using \s+
const output = input.split(/\s+/).map((s, i, a) => {
// this will map your pattern asd-foooasdasd
const data = s.match(/^[a-z]+-(.+)$/);
// you may want to tweak this. right now it will simply throw an error.
if (!data) throw new Error(`${s} at position ${i} is a malformed input`);
// figure out if we are in the final iteration
const final = i == a.length -1;
// the actual output data
return `${final ? "Final" : (i + 1)}-${data[1]}`;
// and of course join the array into a linebreak separated list similar to your input.
}).join("\n");
console.log(output);
Test
var index=1;
var text=`a-test
b-123
c-qweq
d-gdfgd
e-312`;
function c_replace() {
if(index == 5) { return "Final-"; }
return index++ +'-';
}
console.log(text.replace(/.-/g,c_replace));
var input = [
'a-test',
'b-123',
'c-qweq',
'd-gdfgd',
'e-312'
];
var output = input.map((e, i) => ++i + e.slice(1));
output[output.length - 1] = 'Final' + output[output.length - 1].slice(1);
console.log(output);
I want to find element from 3 arrays and create string using values. I have tried and given the output. But I want to know that, is there any better solution for this.
var numbers = ['1','2','3','4','5'];
var letters = ['A','B','C','D','E'];
var romans = ['I','II','III','IV','V'];
function findInArray(input){
var index = -1;
if(numbers.indexOf(input) >= 0){
index = numbers.indexOf(input);
} else if(letters.indexOf(input) >= 0){
index = letters.indexOf(input);
} else if(romans.indexOf(input) >= 0){
index = romans.indexOf(input);
}
if(index > -1){
var data = '{"numbers":"'+numbers[index]+'","letters":"'+letters[index]+'","romans":"'+romans[index]+'"}';
console.log(data);
}
}
findInArray('2');
output : {"numbers":"2","letters":"B","romans":"II"}
You don't need to check if indexOf exists for each of the arrays. You can just find the max value of index for all the three arrays.
If the argument exists in any of the array, it will return a positive values (which results in true)
Then you can simply return the concatenation of the result using the template strings
var numbers = ['1','2','3','4','5'];
var letters = ['A','B','C','D','E'];
var romans = ['I','II','III','IV','V'];
var findInArray = (i) => {
var index = Math.max(numbers.indexOf(i), letters.indexOf(i), romans.indexOf(i));
if (index) {
return `{numbers: ${numbers[index]}, letters: ${letters[index]}, romans: ${romans[index]}}`;
}
}
console.log(findInArray('2'));
console.log(findInArray('D'));
console.log(findInArray('V'));
Vishal,
#Jonas has used a self executing function.
For example it will output 25
(function(x){
return x*x;
}(5));
here 5is the parameter of this self executing function which will output to 25
Back to answer; When you convert his answer to raw it will look something like
const findInArray = val => (i => ({ //i = 1 here
numbers: numbers[1],
letters: letters[1],
romans: romans[1]
}))(Math.max(1,-1,-1) //will output 1);
Hope it makes sense.
resource - http://markdalgleish.com/2011/03/self-executing-anonymous-functions/
Might be simpler with:
const findInArray = val => (i => ({
numbers: numbers[i],
letters: letters[i],
romans: romans[i]
}))(Math.max(
numbers.indexOf(val),
letters.indexOf(val),
romans.indexOf(val)
));