How to build a function that searches for string occurrences? - javascript

I need help Writing a function subLength() that takes 2 parameters, a string and a single character. The function should search the string for the two occurrences of the character and return the length between them including the 2 characters. If there are less than 2 or more than 2 occurrences of the character the function should return 0. How can I solve this problem using loops?
subLength('Saturday', 'a'); // returns 6
subLength('summer', 'm'); // returns 2
subLength('digitize', 'i'); // returns 0
subLength('cheesecake', 'k'); // returns 0

Here I loop through the characters of the string to find each value that is the char.
if the length isn't 2, return 0.
using slice, get only the characters within the two found indexs and get that length adding one to fix the offset
const subLength = (str, char) => {
let strChars = str.toLowerCase().split(""),
found = [],
length = 0;
strChars.forEach((val, index) => {
if (val === char) {
found.push(index);
}
});
if (found.length != 2) {
return length;
}
return str.slice(found[0], found[1]).length + 1;
}
console.log(subLength('Saturday', 'a')); // returns 6
console.log(subLength('summer', 'm')); // returns 2
console.log(subLength('digitize', 'i')); // returns 0
console.log(subLength('cheesecake', 'k')); // returns 0

You can try this logic:
Loop over string and count number of occurance
if count is 2,
Create a regex to capture the string in between.
Return its length
Else return 0
function subLength(str, char) {
let length = 0;
const occuranceCount = Array
.from(str)
.filter((c) => c.toLowerCase() === char.toLowerCase())
.length
if (occuranceCount === 2) {
const regex = new RegExp(`${char}(.*)${char}`)
length = str.match(regex)[0].length
}
console.log(length)
return length;
}
subLength('Saturday', 'a'); // returns 6
subLength('summer', 'm'); // returns 2
subLength('digitize', 'i'); // returns 0
subLength('cheesecake', 'k'); // returns 0
Using just for loop:
function subLength(str, char) {
let count = 0;
let initPosition;
let lastPosition;
for (let i = 0; i < str.length; i++) {
if (str[i] === char) {
count++
if (count > 2) {
return 0;
}
if (initPosition === undefined) {
initPosition = i
} else {
lastPosition = i+1
}
}
}
return count < 2 ? 0 : lastPosition - initPosition;
}
console.log(subLength('Saturday', 'a')); // returns 6
console.log(subLength('summer', 'm')); // returns 2
console.log(subLength('digitize', 'i')); // returns 0
console.log(subLength('cheesecake', 'k')); // returns 0

I too am going through the Codecademy course where this question came up which led me to this post.
Using the RegExp solution provided by #Rajesh (thank you!!) I started to break it down to better understand what was going on and making notes/comments because I am still pretty new and haven't used or been exposed to some of these things.
At the end of it all I thought I'd share what I ended up with in case anyone found it helpful.
function subLength(str, char) {
// Outputting to the console what we are looking for given the value of the string and character from the test cases at the end of this script.
console.log(`Showing the subLength for the string: "${str}" between "${char}" and "${char}" including the "${char}" positions.`);
// create the length variable which will be returned by the function
let length = 0;
// ** Search the string for the two occurrences of the character and count them. Then assign the result to the occurrenceCount variable for use in the if else statement.
// The "Array" class is a global object that is used in the construction off arrays.
// The Array.from() static method creates a new, shallow-copied Array instance from an array-like or iterable object.
// The Array.filter() method creates a new array with all elements that pass the test implemented by the provided function. The "c" represents each element of the array/string which is then compared to the char variable. if it is a match it gets added to the Array. We use .toLowerCase on both to ensure case compatibility.
// Appending the Array with ".length" assigns occurrenceCount the numeric value of the array's length rather than the array of characters.
const occurrenceCount = Array.from(str).filter((c) => c.toLowerCase() === char.toLowerCase());
console.log(' The contents of the occurrenceCountArray = ' + occurrenceCount);
console.log(' The character occurrence count = ' + occurrenceCount.length);
// if the string has two occurrences : return the length between them including the two characters : else the string has less than 2 or more than 2 characters : return 0.
if (occurrenceCount.length === 2) {
// The RegExp object is used for matching text with a pattern. The "(.*)" in between the ${char}'s will match and capture as much as possible aka greedy match. "()" = capture anything matched. (" = start of group. "." = match any character. "*" = Greedy match that matches everything in place of the "*". ")" = end of group.
const regex = new RegExp(`${char}(.*)${char}`);
// log to console the pattern being matched
console.log(` regex pattern to find = ${regex}`);
// log to the console the [0] = index 0 pattern that was captured from the string using str.match(regex)[0]
console.log(` regex output = ${str.match(regex)[0]}`);
// Use".length" to count the number of characters in the regex string at index 0 of the regex array and assign that value to the length variable.
length = str.match(regex)[0].length;
// Output the results to the console
console.log(` The distance from "${char}" to "${char}" (including the "${char}" positions) in the string: ${str} = ${length}\n`);
// return the length value
return length;
} else {
// Output the results to the console
console.log(` The string either has too many or too few occurrences.\n The subLength = ${length}\n`);
// return the length value
return length;
}
}
// test cases
subLength('Saturday', 'a'); // returns 6
subLength('summer', 'm'); // returns 2
subLength('digitize', 'i'); // returns 0
subLength('cheesecake', 'k'); // returns 0

The answer I am getting is this:
const subLength = (str, char) => {
let charCount = 0;
let len = -1;
for (let i=0; i<str.length; i++) {
if (str[i] == char) {
charCount++;
if (charCount > 2) {
return 0;
}
if (len == -1) {
len = i;
} else {
len = i - len + 1
}
}
}
if (charCount < 2) {
return 0;
}
return len;
};

It is better to try yourself a solution first. It is a very bad practice to just ask a solution for your homework!!!
Even if the solution can be JUST a few lines of code i wrote for you with commments a working solution :
const subLength = (str,char) => {
// create an empty array
const strarr = [];
// push string into array
strarr.push(str);
//initiate a count variable
let count = 0;
// WRITE YOUR REGULAR EXPRESSION
// Using the regular expression constructor - new RegExp("ab{2}", "g") .
const regString = `[${char}]`;
const regex = new RegExp(regString, "g");
// iterate through the string array to
for (let i = 0; i < strarr.length; i++) {
// calculate how many time the character occurs
count = (strarr[i].match(regex) || []).length;
};
// check with if condition
//if count is 2
if (count === 2) {
// calculate the index of first ocurrance of the string
first = str.indexOf(char);
// calculate the index of second ocurrance of the string
second = str.lastIndexOf(char);
// calculate the distance between them
return second - first + 1;
// if count is greater than two return 0
}
else if (count > 2) {
return count = 0;
}
// if count is less than two return 0
else if (count < 2) {
return 0;
}
};
console.log(subLength("iiiiliiile","l"));

I just answered this problem in codeAcademy and this is the solution that I came up with, just using if-statements and string.indexOf
const subLength = (strng, char) => {
let firstIndex = strng.indexOf(char);
let secondIndex = strng.indexOf(char, (firstIndex + 1));
let thirdIndex = strng.indexOf(char, (secondIndex + 1));
if (firstIndex === -1){
return 0
} else if (secondIndex === -1){
return 0
} else if (thirdIndex === -1 ){
return (secondIndex - firstIndex + 1)
} else {
return 0
};
};

Related

Checking if str1 can be re-arranged as str2 wth JavaScript

There are two strings called str1 and str2 and I'm trying to check if str1 can be re-arranged as str2.
FOR EXAMPLE: lets say str1 = "aabbcamaomsccdd" and str2="commas".
Is it possible to write the word "commas" out of "str1"
function scramble(str1, str2) {
let arr=[];
let str1arr = str1.split("");
let str2arr = str2.split("");
let j=0;
for(let i=0; i<str1.length; i++){
if(str1arr[i]==str2arr[j]){
arr.push(str1arr[i]);
str1arr=str1arr.splice(i,1);
j++;
i=0;
}
}if(arr.toString()===str2arr.toString()){
return true;
}else{
return false;
}
}
What I tried basically if str1arr[i]==str2arr[j] it will put the str1arr[i] value on a new array called arr and at the end it will compare str2 and the arr and return True or False.
The reason why I used str1arr=str1arr.splice(i,1); to delete the i after the match is because the for loop is reseting it self to check from the "i=0" each time i and j matches and that i would match with other duplicate letters (I hope thats what it does atleast :D).
It is an internet question and im not passing the tests. I only pass if the result is FALSE.
I want to know what I'm doing and thinking wrong here. Its not performance efficent too so any comment on that would be great too.
You could take arrays and sort them and check each character of the second string/array against the first one.
function compare([...a], [...b]) {
a.sort();
return b.sort().every((i => v => {
while (i < a.length && a[i] !== v) i++;
return a[i++] === v;
})(0));
}
console.log(compare("aabbcamaomsccdd", "commas")); // true
console.log(compare("aabbcamaomccdd", "commas")); // false
You should just check that both strings contain the same chars like so:
function scramble(str1, str2) {
var s1 = str1.split('');
var s2 = str2.split('');
var i;
for (i = 0; i < s2.length; i++) {
const idx = s1.indexOf(s2[i]);
if (idx === -1) {
return false;
}
s1.splice(idx, 1);
}
return s1.length === 0;
}
console.log(scramble('xcab1c', 'abxcc1'));
You could count the frequency of each character in your first string. Below I have used .reduce() to build an object with key-value pairs, where the key represents a character from your s1 string and the value is how many times it appears. You can then loop through the characters in s2 and check that every character appears in the frequency map. When you see a character you can subtract one from the value from the frequency object to signify that the character has been "used". If the .every() callback returns a falsy value (such as 0 for the value), then the result will be false, as your string can't be re-arranged:
const scramble = (s1, s2) => {
const s1Freq = [...s1].reduce((o, c) => ({...o, [c]: (o[c] || 0) +1}), {});
return [...s2].every(char => s1Freq[char]--);
}
console.log(scramble("aabbcamaomsccdd", "commas")); // true
console.log(scramble("abc321", "123")); // true
console.log(scramble("a3b2c11", "1231")); // true
console.log(scramble("a", "a")); // true
console.log(scramble("xyz", "xyt")); // false

See if the end of strings match

I have this exercise: Check if a string (first argument, str) ends with the given target string (second argument, target) without using the method endsWith() or any other method. What's wrong with my code?
function confirmEnding(str, target) {
for (var i = 1; i <= target.length; i++) {
val = false
if (str[str.length - i] === target[target.length - i]) {
val = true;
}
return val;
}
}
confirmEnding("Bastian", "n");
//original code from post above:
console.log(confirmEnding("Bastian", "n")); //added to provide working example
In your original code there are a few problems:
Will address them inline:
function confirmEnding(str, target) {
// using a for loop to iterate over the target string's length
for (var i = 1; i <= target.length; i++) {
//setting up a variable that says false
val = false
//trying to compare the individual characters
if (str[str.length - i] === target[target.length - i]) {
//so what happens here:
//when the two match this will set val to true
//but every time the loop is run is will reset to false.
val = true;
}
//the return value is in the loop, so the loop will run once
return val;
}
}
confirmEnding("Bastian", "n");
With the above script you have no way of knowing if all the characters match. If the last character matches it will return true, even if the other characters don't match.
string: Bastian target: irr
Will return true in the logic of your loop.
Take a look at the code below and the comments in it!
function confirmEnding(str, target) {
//get the length of the target string
const targetLength = target.length;
//set up an empty string
let endstr = "";
for (let i = 1; i <= targetLength; i++)
{
//start at 1 since str.length-1 is last character
//fill the empty string with the last characters of str
endstr = str[str.length-i] + endstr;
}
//compare and return
return target === endstr;
}
console.log(confirmEnding("Bastian", "ian")); //TRUE
console.log(confirmEnding("Bastian", "in")); //FALSE
The problem in your current code is that the variable for the result is initialised inside the loop.
So it actually only returns true/false depending on the last character it compares.
Which would be the first character of the target string.
What you can do is get out of that loop as soon a difference is found.
And additional, also check if the string isn't smaller than the target, because then it would be false anyway.
F.e.
function confirmEnding(str, target) {
if(str.length < target.length)
return false;
for (var i = 1; i <= target.length; i++) {
if (str[str.length - i] !== target[target.length - i]) {
return false;
}
}
return true;
}
console.log(confirmEnding("Bastian", "ian"));
console.log(confirmEnding("Bastian", "ion"));
console.log(confirmEnding("an", "ian"));
const confirmEnding = (str,target) => str.slice(-target.length) === target;
console.log(confirmEnding('peace','ace')); // true
console.log(confirmEnding('devotion','tio')); // false
console.log(confirmEnding('faith and love','nd love')); // true
we know that "slice" take 2 arguments: [ index | how many items ]
more so, a negative index would start extracting characters from the end of a string
we can use this to our advantage by utilizing the "length" property from "target" to set the "index" on the "slice" method
let's use "peace" as our example to breakdown the code:
('peace','e') => str.slice(-1) === 'e';
str.slice(-1) --> return the last character 'e' from "peace"
('peace','e') => 'e' === 'e'; --> "true"

How to get odd and even position characters from a string?

I'm trying to figure out how to remove every second character (starting from the first one) from a string in Javascript.
For example, the string "This is a test!" should become "hsi etTi sats!"
I also want to save every deleted character into another array.
I have tried using replace method and splice method, but wasn't able to get them to work properly. Mostly because replace only replaces the first character.
function encrypt(text, n) {
if (text === "NULL") return n;
if (n <= 0) return text;
var encArr = [];
var newString = text.split("");
var j = 0;
for (var i = 0; i < text.length; i += 2) {
encArr[j++] = text[i];
newString.splice(i, 1); // this line doesn't work properly
}
}
You could reduce the characters of the string and group them to separate arrays using the % operator. Use destructuring to get the 2D array returned to separate variables
let str = "This is a test!";
const [even, odd] = [...str].reduce((r,char,i) => (r[i%2].push(char), r), [[],[]])
console.log(odd.join(''))
console.log(even.join(''))
Using a for loop:
let str = "This is a test!",
odd = [],
even = [];
for (var i = 0; i < str.length; i++) {
i % 2 === 0
? even.push(str[i])
: odd.push(str[i])
}
console.log(odd.join(''))
console.log(even.join(''))
It would probably be easier to use a regular expression and .replace: capture two characters in separate capturing groups, add the first character to a string, and replace with the second character. Then, you'll have first half of the output you need in one string, and the second in another: just concatenate them together and return:
function encrypt(text) {
let removedText = '';
const replacedText1 = text.replace(/(.)(.)?/g, (_, firstChar, secondChar) => {
// in case the match was at the end of the string,
// and the string has an odd number of characters:
if (!secondChar) secondChar = '';
// remove the firstChar from the string, while adding it to removedText:
removedText += firstChar;
return secondChar;
});
return replacedText1 + removedText;
}
console.log(encrypt('This is a test!'));
Pretty simple with .reduce() to create the two arrays you seem to want.
function encrypt(text) {
return text.split("")
.reduce(({odd, even}, c, i) =>
i % 2 ? {odd: [...odd, c], even} : {odd, even: [...even, c]}
, {odd: [], even: []})
}
console.log(encrypt("This is a test!"));
They can be converted to strings by using .join("") if you desire.
I think you were on the right track. What you missed is replace is using either a string or RegExp.
The replace() method returns a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a RegExp, and the replacement can be a string or a function to be called for each match. If pattern is a string, only the first occurrence will be replaced.
Source: String.prototype.replace()
If you are replacing a value (and not a regular expression), only the first instance of the value will be replaced. To replace all occurrences of a specified value, use the global (g) modifier
Source: JavaScript String replace() Method
So my suggestion would be to continue still with replace and pass the right RegExp to the function, I guess you can figure out from this example - this removes every second occurrence for char 't':
let count = 0;
let testString = 'test test test test';
console.log('original', testString);
// global modifier in RegExp
let result = testString.replace(/t/g, function (match) {
count++;
return (count % 2 === 0) ? '' : match;
});
console.log('removed', result);
like this?
var text = "This is a test!"
var result = ""
var rest = ""
for(var i = 0; i < text.length; i++){
if( (i%2) != 0 ){
result += text[i]
} else{
rest += text[i]
}
}
console.log(result+rest)
Maybe with split, filter and join:
const remaining = myString.split('').filter((char, i) => i % 2 !== 0).join('');
const deleted = myString.split('').filter((char, i) => i % 2 === 0).join('');
You could take an array and splice and push each second item to the end of the array.
function encrypt(string) {
var array = [...string],
i = 0,
l = array.length >> 1;
while (i <= l) array.push(array.splice(i++, 1)[0]);
return array.join('');
}
console.log(encrypt("This is a test!"));
function encrypt(text) {
text = text.split("");
var removed = []
var encrypted = text.filter((letter, index) => {
if(index % 2 == 0){
removed.push(letter)
return false;
}
return true
}).join("")
return {
full: encrypted + removed.join(""),
encrypted: encrypted,
removed: removed
}
}
console.log(encrypt("This is a test!"))
Splice does not work, because if you remove an element from an array in for loop indexes most probably will be wrong when removing another element.
I don't know how much you care about performance, but using regex is not very efficient.
Simple test for quite a long string shows that using filter function is on average about 3 times faster, which can make quite a difference when performed on very long strings or on many, many shorts ones.
function test(func, n){
var text = "";
for(var i = 0; i < n; ++i){
text += "a";
}
var start = new Date().getTime();
func(text);
var end = new Date().getTime();
var time = (end-start) / 1000.0;
console.log(func.name, " took ", time, " seconds")
return time;
}
function encryptREGEX(text) {
let removedText = '';
const replacedText1 = text.replace(/(.)(.)?/g, (_, firstChar, secondChar) => {
// in case the match was at the end of the string,
// and the string has an odd number of characters:
if (!secondChar) secondChar = '';
// remove the firstChar from the string, while adding it to removedText:
removedText += firstChar;
return secondChar;
});
return replacedText1 + removedText;
}
function encrypt(text) {
text = text.split("");
var removed = "";
var encrypted = text.filter((letter, index) => {
if(index % 2 == 0){
removed += letter;
return false;
}
return true
}).join("")
return encrypted + removed
}
var timeREGEX = test(encryptREGEX, 10000000);
var timeFilter = test(encrypt, 10000000);
console.log("Using filter is faster ", timeREGEX/timeFilter, " times")
Using actually an array for storing removed letters and then joining them is much more efficient, than using a string and concatenating letters to it.
I changed an array to string in filter solution to make it the same like in regex solution, so they are more comparable.

Find the longest occurrence of the "aeiou" in a string

I was recently doing an interview and was asked multiple questions, one of the questions was this and I had a bit of trouble trying to answer it.
Given a string, find the longest occurrence of vowels "aeiou" that appear.
The substring of vowels do not have to be consecutive, and there can be repeats.
The goal is the find the max occurrence of each vowel and join them, but it must be in the order of "a","e","i","o","u".
Edit: In addition, each individual vowel character must be chained as well. In the example below, there is "aaa" and "aa" , since 3 is longer, our result must contain the longer chain.
For example:
Input: "aaagtaayuhiejjhgiiiouaae"
Result: aaaeiiiou
The code that I have tried is below:
EDIT: Following the solution, I have written this below but I am still running into issues with strings such as "aeiouaaaeeeiiiooouuu". The correct result for that would be 15 but I am getting 5.
var findLongestVowels = function(s){
var count = 1;
var i = 0;
var j = 0;
var vowels = ['a','e','i','o','u'];
var total = 0;
var array = [];
while (i < s.length){
if (s.charAt(i) == vowels[j] && s.charAt(i) == s.charAt(i+1) ){
count++;
}
else if (s.charAt(i) == vowels[j] && s.charAt(i) != s.charAt(i+1)){
if (j === 0 && !array[vowels[j]]){
array[vowels[j]] = count;
}
else if (j === 0 && array[vowels[j]]){
array[vowels[j]] = Math.max(array[vowels[j]],count);
}
else if (j !== 0 && !array[vowels[j]] && array[vowels[j-1]]){
array[vowels[j]] = array[vowels[j-1]] + count;
}
else if (j !== 0 && array[vowels[j]] && array[vowels[j-1]]){
array[vowels[j]] = Math.max(array[vowels[j]],array[vowels[j-1]] + count);
}
count = 1;
}
else if (s.charAt(i) == vowels[j+1] && array[vowels[j]]){
j++;
i--;
}
i++;
}
console.log(array);
console.log('Answer: ' + array[vowels[j]]);
}
findLongestVowels("eeeeebbbagtaagaaajaaaaattyuhiejjhgiiiouaae");
Am I at least going in the right direction?
Thanks in advance.
We can solve this in O(n) time. Consider that for each block, if its vowel is at index v in the list of vowels, we are only interested in the best solution for the block with a vowel at index v-1 in the order of vowels. We save the last best solution for each block type (each vowel) as we go along:
|aaa|g|t|aa|y|u|h|i|e|jj|h|g|iii|o|u|aa|e
b: 1 2 3 4 5 6 7 8 9 10
b 1: v[a] = 3
b 2: v[a] = max(2,3)
b 3: v[u] = None recorded for v-1
b 4: v[i] = None recorded for v-1
b 5: v[e] = 1 + 3
b 6: v[i] = 3 + 4
b 7: v[o] = 1 + 7
b 8: v[u] = 1 + 8 // answer
b 9: v[a] = max(2,3)
b 10: v[e] = 1 + 3
JavaScript code:
function f(str){
console.log(`String: ${ str }\n`);
var vowels = {
a: {best: 0, prev: null},
e: {best: 0, prev: 'a'},
i: {best: 0, prev: 'e'},
o: {best: 0, prev: 'i'},
u: {best: 0, prev: 'o'}
};
function getBlock(i){
let length = 1;
while (str[i+1] && str[i] == str[i+1]){
length++;
i++;
}
return length;
}
for (let i=0; i<str.length;){
let length = getBlock(i);
console.log(`i: ${ i }; length: ${ length }`)
if (!vowels[str[i]]){
i = i + length;
continue;
}
if (!vowels[str[i]].prev){
vowels[str[i]].best = Math.max(
vowels[str[i]].best,
length
);
// make sure the previous vowel
// exists in the string before
// this vowel
} else if (vowels[ vowels[str[i]].prev ].best){
vowels[str[i]].best = Math.max(
vowels[str[i]].best,
length + vowels[ vowels[str[i]].prev ].best
);
}
i = i + length;
}
console.log(`\n${ JSON.stringify(vowels) }\n\n`);
return vowels['u'].best;
}
var s = 'eeeeebbbagtaagaaajaaaaattyuhiejjhgiiiouaae';
console.log(f(s) + '\n\n');
s = 'aaagtaayuhiejjhgiiiouaae';
console.log(f(s) + '\n\n');
s = 'aeiouaaaeeeiiiooouuu';
console.log(f(s));
This problem can be solved by using dynamic programming technique.
First, we have string x and we want to find the longest string for this string.
Traversing the string x from start to end, assuming at index i, we are trying to find vowel e, there are two possibilities:
Current character is e, so we take the whole block and move to next character
Or, we can try with the next character
So, we have our pseudocode:
int[][]dp;
int largestBlock (int index, int currentVowel, String x, String vowels){
if (currentVowel == 5) {
// We found all 5 vowel
return 0;
}
if visited this state (index, currentVowel) before {
return dp[index][currentVowel];
}
int result = largestBlock(index + 1, currentVowel, x, vowels) ;
if (x[index] == vowels[currentVowel]){
int nxt = nextIndexThatIsNotVowel(index, currentVowel, x, vowels);
result = max(result, nxt - index + largestBlock(nxt, currentVowel + 1, x , vowels));
}
return dp[index][currentVowel] = result;
}
Time complexity is O(n * m) with m is number of vowels which is 5 in this case.
You need to remember biggest combination of individual vowels.
Use reduce, map and Object.values
var vowels = "aeiou";
var input = "aaagtaayuhiejjhgiiiouaae";
var output = Object.values(
input.split( "" ).reduce( ( a, c, i, arr ) => {
var lastChar = arr[ i - 1 ];
if ( !vowels.includes( c ) ) return a; //if not vowel, return accumulator
if ( c != lastChar ) //if not same as last character then create a new array
{
a[ c ] = a[ c ] || [];
a[ c ].push( [ c ] );
}
else //else push to the last array;
{
var lastCombo = a[ c ].slice( -1 )[ 0 ];
lastCombo.push(c)
}
return a; //return accumulator
} , {}) ).map( s => {
var char = s[0][0]; //find the character to repeat
var maxLength = Math.max.apply( null, s.map( s => s.length ) ); //find how many times to repeat
return Array( maxLength + 1 ).join( char );
}).join( "" ); //join all the vowels
console.log( output );
It's just one of many possible solutions - feel free to try it out.
Store every vowel you're interested in, in vowels array.
Use map to loop over every vowel in array, create regex from vowel to split string into array of vowels. For example "aaabdmedaskaa" would be split into ["aaa", "a", "aa"].
Filter this array so it doesn't include empty strings.
Sort it by length, so accesing 0 element would give you longest occurance
After mapping over every vowel, return the result - filter out "undefined" in case some of your vowels don't occur at all and regex results in empty array (accesing empty array's 0th element would result in undefined), join array of occurances into a result string.
The regex created from "a" will be [^a]+ which means any character sequence that does not include "a".
function findLongestOccurance(str) {
const vowels = ["a", "e", "i", "o", "u"];
const result = vowels.map(vowel => {
const regex = new RegExp(`[^${vowel}]+`);
return str.split(regex)
.filter(r => r !== "")
.sort((a, b) => b.length - a.length)[0];
});
return result.filter(occ => typeof(occ) !== "undefined").join("");
}
console.log(findLongestOccurance("aaagtaayuhiejjhgiiiouaae"));
Why not regex?
var result = /(a+).*(e+).*(i+).*(o+).*(u+)/.exec("aaagtaayuhiejjhgiiiouaae");
console.log(result[1]+result[2]+result[3]+result[4]+result[5]);
First of all, from what I understand from the question the result for Input: "aaagtaayuhiejjhgiiiouaae" should be aaaaaeiiiou, like #PhamTrung asked in the comments but didn't get answered.
Because it a job interview I would start with the first thing that comes to mind, namely brute force the solution out of this
function a(string, prefix='') {
if(!string.length){
return prefix
}
if(!/[aeiou]/.test(string[0])){
return a(string.substr(1), prefix)
}
const option1 = a(string.substr(1), prefix)
const option2 = a(string.substr(1), prefix+string[0])
const validateRegex = /^a+e+i+o+u+$/
const isValidOption1 = validateRegex.test(option1)
const isValidOption2 = validateRegex.test(option2)
if(isValidOption1 && isValidOption2){
if(option1.length > option2.length) {
return option1
}
return option2
}
if(isValidOption1) {
return option1
}
if(isValidOption2) {
return option2
}
return null
}
const input = 'aaagtaayuhiejjhgiiiouaae'
console.log(a(input))
This has a terrible run time though, we are trying all possible substring that contains only vowels, than we are discarding those that aren't of the form required (a+e+i+o+u+) and than choosing only the biggest of them all. If I'm not mistake this has a worst case of ∑(n choose i) which is O(n^n) - well, the actual worst case here would be a stackOverflow exception for sufficiently large n in which case we'd have to reimplement this with a loop instead of recursing. In this case we could still get an out of memory exception in which case we'd be out of options but to improve our algorithm. It's fair to imagine that if the input were large enough that we got an out of memory exception than our code would also be slow enough to not be a reasonable solution to the problem. I'm just arguing all this because these are things that an interviewer would possibly like to see that you are aware of, meaning you know enough of CS 101.
Following that the interviewer would ask if I can improve the performance. This is a solution with running time of O(n).
const input = 'aeiouaaaeeeiiiooouuu'
let curr = { "1": {price: -1} }
const nodes = []
const voewels = '1aeiou'
const getPrevLetter = (node) => voewels[voewels.indexOf(node.letter) -1]
let resultNode
function setUpNodeByCurrent(node, letter){
node.price = curr[letter].price + 1
node.previous = curr[letter]
}
function setBestResultIfNeeded(node){
if(node.letter !== 'u') {
return
}
if(!resultNode || resultNode.price < node.price) {
resultNode = node
return
}
}
function setCurrent(letter){
const node = {
letter,
price: 0
}
const prevLetter = getPrevLetter(node)
if(!prevLetter || !curr[prevLetter]){
// either letter isn't on of aeiou or
// we got to an i without ever seeing an e, an o without ever seeing an i, ... this letter is irrelevant
return
}
if(curr[node.letter]) {
setUpNodeByCurrent(node, node.letter)
}
if(node.price < curr[prevLetter].price + 1) {
setUpNodeByCurrent(node, prevLetter)
}
curr[node.letter] = node
setBestResultIfNeeded(node)
}
function getStringResult(node){
let result = ''
while(node) {
result = node.letter + result
node = node.previous
}
return result
}
function getResult(){
const node = resultNode //getBestResultNode()
const result = getStringResult(node)
console.log(result)
console.log(result.length)
}
for(let l of input){
setCurrent(l)
}
getResult()
This can be seen as a simplification of the longest path problem over a DAG basically you'd run through the string and every a points to the next occurance of a and the next occurance of e. e points to the next e and to the next i and so on. You'd than have a start node pointing to every occurance of a and an end node pointed to by every occurance of u. Now what you want is the longest path from the start node to the end node which is an O(|V|+|E|), now |V|<=n and |E|<=2n since every node in your graph has at most 2 vertices going out of it so the total running time is O(n). I have simplified the code to build the result as it goes on building the graph, basically I already calculate the cost on the go so when I finished building a graph similar to what I described I already know what the result is.
Note that this solution is based on the assumption that the input string is one that necessarily has a solution embedded in it. If the input string is unsolvable (there isn't and aeiou sequence in it) than this case would need to be properly handled, it is actually trivial to add the code that handles that. The first solution will return null in such a case(if I'm not mistaken)
Hope this helps.
If you want to find a substring which contains the maximum number of vowels and you also want to give {specify} the length of the substring then you should use this program:
let newk = s;
const elementsArray = [];
const tempoArray = [];
const counting = [];
const maxPoint = [];
let count
for (var i = 0; i < newk.length; i++) {
while (tempoArray.length > 0) {
tempoArray.pop();
}
let fk = i + k;
if (fk <= newk.length) {
for (let j = i; j < fk; j++) {
tempoArray.push(newk[j]);
}
let makingArray = tempoArray.toString();
elementsArray.push(makingArray);
} else {
// console.log(" ");
}
}
for (let q = 0; q < elementsArray.length; q++) {
count = 0
let tempString = new String(elementsArray[q]).split(",")
for (let l = 0; l < tempString.length; l++) {
if (tempString[l] == "a" || tempString[l] == "e" || tempString[l] == "i" || tempString[l] == "o" || tempString[l] == "u") {
count ++;
}else{
}
}
// console.log(count);
counting.push(count)
}
let max = 0,Maximist
// for (let d = 0; d < counting.length; d++) {
// console.log(counting[d] , " this is the value of the counting array");
// }
for (let t = 0; t <= counting.length; t++) {
if (counting[t] != 0) {
if (max < counting[t]) {
max = counting[t]
Maximist = t
}
else if (max == counting[t]){
max = counting[t]
Maximist = t
}
else{
console.log("");
}
}
}
// console.log(Maximist);
// console.log(max);
// maxPoint.push(Maximist)
for (let t = 0; t <= counting.length; t++) {
if (counting[0] != 0) {
if (max == counting[t]) {
maxPoint.push(t)
}
}
}
for (let e = 0; e < maxPoint.length; e++) {
console.log("{", elementsArray[maxPoint[e]] ,"}")
}
}
findSubstring("captainamerica", 3);
The bigger your size of the substring will be the less chances that there will be less substring with same number of vowels in it

How to write a character matching algorithm in JavaScript?

Given this input s1 = "dadxx" s2 = "ddxx" I'd expect the output to contain a bunch of a,b pairs wherever each character in s1 matched a character in s2 and vice versa (duplicates allowed). Among those pairs would be 0,0 because s1[0] and s2[0] are both equal to d.
The problem is that my output doesn't contain 2,1 even though s1[2] and s2[1] are both equal to d.
Can someone fix my algorithm or make a better one?
Here's a JSFiddle if it helps.
Here's my code:
// For each char, see if other string contains it
s1 = 'dadxx'
s2 = 'ddxx'
matchChars(s1,s2)
matchChars(s2,s1)
function matchChars(a,b) {
for (i = 0; i < a.length; i++) {
found = b.indexOf(a[i])
if (found >= 0) {
if (a===s1) console.log(i,found)
else console.log(found,i)
}
}
}
I believe the problem you're having is that you're only checking for a single match for s1[i] in s2 by using indexOf. That will find the first index of a matched value, not every index.
If you instead iterate through both strings and compare every character, you get the result I think you're trying to achieve.
// Define strings
s1 = 'dadxx'
s2 = 'ddxx'
matchChars(s1,s2)
matchChars(s2,s1)
function matchChars(a,b) {
// Convert strings to lower case for case insensitive matching
// Remove if case sensitive matching required
a = a.toLowerCase();
b = b.toLowerCase();
// Iterate through every letter in s1
for (i = 0; i < a.length; i++) {
// Iterate through every letter in s2
for (j = 0; j < b.length; j++) {
// Check if the letter in s1 matches letter in s2
if (a[i] === b[j]) {
// Changed per request of OP
(a === s1) ? console.log(i, j) : console.log(j, i);
// console.log([i, j]);
}
}
}
}
Working JSBin example: https://jsbin.com/wecijopohi/edit?js,console
You say duplicates are allowed but not required. I'm submitting this as a more modern approach, not as a correction to the accepted solution, which looks good to me. https://jsfiddle.net/avc705zr/3/
match = (a, b) => {
let re, match, matches = []
a.split('').forEach((l, i) => {
re = new RegExp(l, 'g')
while ((match = re.exec(b)) != null) {
matches.push([i, match.index])
}
})
return matches
}
However, in my experience when you actually need functionality like this, you only need one of the strings to exhausted. In other words, you are looking for matches in string 2 of all instances in string 1 -- which is to say, unique characters in string 1. So a modification which might come up in the real world might instead be like:
Array.prototype.unique = function() {
return this.filter(function (value, index, self) {
return self.indexOf(value) === index;
});
}
match = (a, b) => {
let re, match, matches = []
a.split('').unique().forEach(l => {
re = new RegExp(l, 'g')
while ((match = re.exec(b)) != null) {
matches.push([l, match.index])
}
})
return matches
}

Categories

Resources