regex to extract array indices - javascript

I'm still having a hard time to understand regex... :-/
Given strings (JavaScript-like expressions) like these...
foo[0]
foo[4][2]
foo[4][2][234523][3]
...I'm trying to deconstruct the indices in regex, so that I have
the name of the variable: foo
the single indices: fox example 4, 2, 234523 and 3 in the last example
while not accepting invalid syntax like
foo[23]bar[55]
foo[123]bar
[123]bla
foo[urrrr]
It would be nice to also ignore whitespace like foo [13] or foo[ 123 ] but that's not important.
Is that possible with regex?
I was able to extract the brackets with var matches = s.match(/\[([0-9]?)\]/g); but that includes the brackets in the result, is missing the variable name (could get around that) and also does not respect the edge cases as described above.

You'll have to use loops to extract multiple matches. Here's one way:
function run(string) {
var match;
if(match = string.match(/^([^[]+)\s*(\[\s*(\d+)\s*\]\s*)+\s*$/)) {
var variable = match[1], indices = [];
var re = /\[\s*(\d+)\s*\]/g;
while(match = re.exec(string)) {
indices.push(+match[1]);
}
return { variable: variable, indices: indices };
} else {
return null;
}
}
var strings = [
"foo[0]",
"foo[4][2]",
"foo[4][2][234523][3]",
"foo [13]",
"foo[ 123 ]",
"foo[1] [2]",
"foo$;bar%[1]",
// The following are invalid
"foo[23]bar[55]",
"foo[123]bar",
"[123]bla",
"foo[urrrr]",
];
// Demo
strings.forEach(function(string) {
document.write("<pre>" + JSON.stringify(run(string), null, 4) + "</pre>");
});

That is not possible.
You can test if it is a correct statement, and as long you know how many indices you have you can select them, but there is no way to catch a group multiple times with javascript .exec.
However the language is regular. So it would be this:
^([a-zA-Z][a-zA-Z_0-9]*)(\[[0-9]*\])*
The first group will match the variable, and the second group (with the *quantifier 0-n times) the index.
So if you want to do this I recommend to use another parsing approach:
function parse(str) {
let idx = 0;
while(str[idx+1] != '[') {
idx++;
}
let name = str.substr(0, idx+1);
let indices = [];
while(str[idx+1] == '[') {
idx++;
let startIdx = idx;
while(str[idx+1] != ']') {
idx ++;
}
console.log(idx);
indices.push(str.substr(startIdx+1, idx-startIdx));
idx++;
}
return {name,indices};
}

Here is small ES6 version of the 2 step regular expression to get the desired array:
function interpret(s) {
return (/^(\w+)\s*((?:\[\s*\d+\s*\]\s*)*)$/.exec(s) || [,null]).slice(1).reduce(
(fun, args) => [fun].concat(args.match(/\d+/g)));
}
var s = 'foo[4][2][234523][3]';
var result = interpret(s);
console.log(result);
It first gets the 2 main parts via exec(), which returns the complete match, the function name and the rest in an array (with 3 elements). Then with slice(1) it ignores the first of those three. The two others are passed to reduce.
The reduce callback will only be called once, since there is no initial value provided.
This is convenient, as it actually means the callback gets the two parts as its two arguments. It applies the second regular expression to split the index string, and returns the final array.
The || [,null] will take care of the case when the original match fails: it ensures that reduce acts on [null] and thus will return null.

Related

Isomorphic Strings Function Always Returns True

I am attempting the Isomorphic Strings problem on LeetCode and am having issues with my current solution. I'm sure there are plenty of answers on exactly how to complete this problem, but I would really prefer to finish it through my own thought process before learning the best possible way to do it. For reference, here is the problem: https://leetcode.com/problems/isomorphic-strings/?envType=study-plan&id=level-1
This is my code as it is right now:
var isIsomorphic = function(s, t) {
const map = new Map();
const array1 = [...s];
const array2 = [...t];
for (i = 0; i < s.length; i++) {
if ((map.has(array1[i]) === true) && (map.has(array2[i]) === true)) {
if (map.get(array1[i]) !== array2[i]) {
return false;
} else {
continue;
}
} else if (map.has(array1[i]) === false) {
map.set(array1[i], array2[i]);
}
}
return true;
};
It's messy but I can't figure out why it isn't giving me the desired results. Right now, it seems to always return true for any given values, even though I have the initial if statement to return false if it ever comes across previously-mapped values that don't match. Am I missing something obvious? This is my first question on SA, so I apologize if the format is wrong.
The map is set like:
map.set(array1[i], array2[i]);
The key is the character in the first string, and the value is the corresponding character in the second string. So, when iterating over a new character, checking map.has will only make sense if the character being passed is from the first string; doing map.has(array2[i]) === true)) does not test anything useful, because the second string's characters are not keys of the Map.
You need to perform two tests: that the 1st string's character corresponds to the 2nd string's character (which you're doing right), and that the 2nd string's character is not already set to a different 1st string's character (which needs to be fixed). For this second condition, consider having another Map that's the reverse of the first one - the keys are the characters from the 2nd string, and the values are the characters from the 1st string. (You don't have to have another Map - you could also iterate through the .entries of the first, check that, for every entry's value that matches the 2nd character, the entry's key matches the 1st - but that could feel a bit messy.)
Cleaning up your code some, there's also no need to turn the strings into arrays, and === true can be omitted entirely, and the i variable should be declared with let.
You also might want to check if the length of the first string is equal to the length of the second.
var isIsomorphic = function(s1, s2) {
if (s1.length !== s2.length) return false;
const map1to2 = new Map();
const map2to1 = new Map();
for (let i = 0; i < s1.length; i++) {
// Check that s1 here corresponds to s2
if (map1to2.has(s1[i]) && map1to2.get(s1[i]) !== s2[i]) {
return false;
}
// And that s2 here doesn't correspond to any other s1
if (map2to1.has(s2[i]) && map2to1.get(s2[i]) !== s1[i]) {
return false;
}
map1to2.set(s1[i], s2[i]);
map2to1.set(s2[i], s1[i]);
}
return true;
};
console.log(isIsomorphic('aa', 'bb'));
console.log(isIsomorphic('aa', 'ba'));
console.log(isIsomorphic('badc', 'baba'));

Javascript object

I came across a problem in an online course:
Write a function called vowelCount which accepts a string and returns an object with the keys as the vowel and the values as the number of times the vowel appears in the string. This function should be case insensitive so a lowercase letter and uppercase letter should count
Examples:
vowelCount('Elie') // {e:2,i:1};
the solution from the instructor came like this:
function vowelCount(str){
var splitArr = str.toLowerCase().split("");
var obj = {};
var vowels = "aeiou";
splitArr.forEach(function(letter){
if(vowels.indexOf(letter) !== -1){
if(obj[letter]){
obj[letter]++;
} else{
obj[letter] = 1;
}
}
});
return obj;
}
I understand the solution until the second "if" statement. I know that the first "if" statement is to check if the "letters" in the input string belongs to the "vowels". Then in the second "if" it is checking if the "letter is in the empty "obj" object created above, but at that line, the "obj" is empty bofore the "letter" is added to it, so what is the point for that "if". Also, why does adding this new "letter" to the object require an increment. I tried the code without increment and the object is still empty.
It's checking if you've ever seen the letter before in the loop. If you've never written to obj[letter], then when you do obj[letter], you get back the value undefined, which is falsy (treated as false by things like an if). if(obj[letter]) is checking for a truthy value (a value that isn't falsy) so that it adds to the number already stored at obj[letter] if it's there (obj[letter]++). But when it sees a falsy value like undefined, it takes the else branch and sets obj[letter] to 1 because the code knows that letter hasn't been seen before.
Just FWIW, while still entirely valid, that's fairly old-style JavaScript code (circa the ES5 standard, 2009). ES2015 added several features you'd use to solve this problem today:
function vowelCount(str){
// Use a Map to remember how many of each ltter you've
// seen. You could use an object as well, but ideally you'd
// create the object with out a prototype to avoid having
// any conflict with inherited properties from `Object.prototype`.
const counts = new Map(); // const counts = Object.create(null);
// The set of vowels
const vowels = new Set("aeiou");
// Loop through the letters
for (const letter of str) {
// If it's not a vowel...
if (!vowels.has(letter)){
// Count it
const currentCount = counts.get(letter) || 0;
counts.set(letter, currentCount + 1);
// Or using an object:
// const currentCount = counts[letter] || 0;
// counts[letter] = currentCount + 1;
}
});
return counts;
}
We can use regular expression to match vowels in a sentence.
Regular expression to match all occurrence of vowel in a string:/[aeiouAEIOU]+?/g
Below is the working code snippet:
//function that takes string as input
//function returns an object containing vowel count without case in account.
function vowelCount(input) {
//to get vowel count using string.match
var arrVowels =input.match(/[aeiouAEIOU]+?/g);
//acc=accumulator, curr=current value
return arrVowels.reduce(function (acc, curr) {
if (typeof acc[curr.toLowerCase()] == 'undefined') {
acc[curr.toLowerCase()] = 1;
}
else {
acc[curr.toLowerCase()] += 1;
}
return acc;
// the blank object below is default value of the acc (accumulator)
}, {});
}

Why is the for loop with a conditional not iterating all the way to the end of the array?

I am writing a function to change the letters in a string (to camelCase) and the dashes and underscores are used as markers for the end of a word. I want to know why my for loop is stopping before it reaches the end of the array, especially since the conditional code isn't being used.
I've tried console logging my tmp array and it has what I want in it (the '_' or '-'). But the code seems to mess up after the conditional so I'm thinking it has something to do with that.
for (let letter of arr) {
arr.pop(letter)
if (letter === '-' || letter === '_') {
let tmp = []
tmp.push(letter)
console.log(tmp)
} else {
camelArr.push(letter)
console.log(camelArr)
}
}
Like pointed out you modify the array as you loop.
Something like a camelCase function might make sense using Array.reduce.
Eg.
const camelCase = str =>
[...str].reduce((a, v) => {
if (['_', '-'].includes(v)) a.firstLet = true;
else {
a.str += a.firstLet ? v.toUpperCase() : v.toLowerCase()
a.firstLet = false;
}
return a;
}, {str: '', firstLet: false}).str;
console.log(camelCase('this_is-a-Test'));
console.log(camelCase('one-two-three-four'));
you may have to be more careful about
arr.pop(letter)
The pop() method removes the last element of an array
: for every loop you are poping up last item in array..
try removing value # specified index..

Filter array of strings, keeping only ones starting with vowels

I realise I've massively overengineered this, but as I'm just starting out with JS, I can't think of how to condense this into something not entirely ridiculous. I know I'm probably going to kick myself here, but can someone refactor this for me?
The aim was to create a new array from a provided one, one that only contained strings starting with vowels. It also needed to be case insensitive.
let results = []
for (let i = 0; i < strings.length; i++) {
if ((strings[i].startsWith('a')) || (strings[i].startsWith('A')) || (strings[i].startsWith('e')) || (strings[i].startsWith('E')) || (strings[i].startsWith('i')) || (strings[i].startsWith('I')) || (strings[i].startsWith('o')) || (strings[i].startsWith('O')) || (strings[i].startsWith('u')) || (strings[i].startsWith('U'))) {
results.push(strings[i])
}
}
return results
You can use a single RegExp and Array.prototype.filter() for that:
console.log([
'Foo',
'Bar',
'Abc',
'Lorem',
'Ipsum'
].filter(str => /^[aeiou]/i.test(str)));
Array.prototype.filter() returns a new array with all the elements that pass (return a truthy value) the predicate.
RegExp.prototype.test() returns true if the RegExp finds a match on the string you pass in.
Then, /^[aeiou]/i means:
^ matches the start of the string.
[aeiou] matches any of the characters inside the square brackets, a single time.
i is a case-insensitive modifier.
I'd use Array#filter and a regular expression:
let rex = /^[aeiou]/i;
let results = strings.filter(str => rex.test(str));
/^[aeiou]/i says "At the beginning of the string (^), match a, e, i, o, or u, case-insensitive (the i flag)."
Live Example:
let strings = [
"I'll match",
"But I won't",
"And I will",
"This is another one that won't",
"Useful match here"
];
let rex = /^[aeiou]/i;
let results = strings.filter(str => rex.test(str));
console.log(results);
Other answers are great, but please consider this approach shown below.
If you are new to JS, it will certainly help you understand cornerstones of JS like its array methods.
The map() method creates a new array with the results of calling a provided function on every element in the calling array.
var new_array = arr.map(function callback(currentValue, index, array {
// Return element for new_array
}, thisArg)
Try using a REPL website like https://repl.it/ in order to see what these methods do...
The following is my proposed answer...
function onlyVowels(array) {
// For every element (word) in array we will...
return array.map((element) => {
// ...convert the word to an array with only characters...
return (element.split('').map((char) => {
// ...and map will only return those matching the regex expression
// (a || e || i || o || u)
// parameter /i makes it case insensitive
// parameter /g makes it global so it does not return after
// finding first match or "only one"
return char.match(/[aeiou]/ig)
// After getting an array with only the vowels the join function
// converts it to a string, thus returning the desired value
})).join('')
})
};
function test() {
var input = ['average', 'exceptional', 'amazing'];
var expected = ['aeae', 'eeioa', 'aai']
var actual = onlyVowels(input)
console.log(expected);
console.log(actual);
};
test()

In Javascript is there a function that returns the number of times that a given string occurs?

In Javascript is there a function that returns the number of times that a given string occurs?
I need to return a numeric value that is equal to the number of times that a given string occurs within a particular string for instance:
var myString = "This is a test text"
If I had to search for 'te' in the above string it would return 2.
Very nearly: You can use String#match to do this:
var count = "This is a test text".match(/te/g).length;
That uses the regular expression /te/g (search for "te" literally, globally) and asks the string to return an array of matches. The array's length is then the count.
Naturally that creates an intermediary array, which may not be ideal if you have a large result set. If you don't mind looping:
function countMatches(str, re) {
var counter;
counter = 0;
while (re.test(str)) {
++counter;
}
return counter;
}
var count = countMatches("This is a test text", /te/g);
That uses RegExp#test to find matches without creating intermediary arrays. (Thanks to kennebec for the comment pointing out that my earlier use of RegExp#exec in the above created intermediary arrays unnecessarily!) Whether it's more efficient will depend entirely on how many of these you expect to match, since the version creating the one big array will probably be optimized within the String#match call and so be faster at the expense of more (temporary) memory use — a large result set may bog down trying to allocate memory, but a small one is unlikely to.
Edit Re your comment below, if you're not looking for patterns and you don't mind looping, you may want to do this instead:
function countMatches(str, substr) {
var index, counter, sublength;
sublength = substr.length;
counter = 0;
for (index = str.indexOf(substr);
index >= 0;
index = str.indexOf(substr, index + sublength))
{
++counter;
}
return counter;
}
var count = countMatches("This is a test text", "te");
There's no pre-baked non-RegExp way to do this that I know of.
Here is an implementation of php's substr_count() in js. May this function bring you much joy...
substr_count = function(needle, haystack)
{
var occurrences = 0;
for (var i=0; i < haystack.length; i++)
{
if (needle == haystack.substr(i, needle.length))
{
occurrences++;
}
}
return occurrences;
}
alert(substr_count('hey', 'hey hey ehy w00lzworth'));
I like to use test to count matches- with a global regular expression it works through a string from each lastIndex, like exec, but does not have to build any arrays:
var c=0;
while(rx.test(string)) c++
String.prototype.count= function(rx){
if(typeof rx== 'string') rx= RegExp(rx,'g');
var c= 0;
while(rx.test(this)) c++;
return c;
}

Categories

Resources