How to create my own substring method in JS? - javascript

Im trying create my own substring method in JS
function substr(string, p1 = 0, p2 = string.length) {
if (string.length === 0 || p2 < 0 || p1 > p2) return '';
const p11 = p1 <= 0 ? 0 : p1
let p22 = p2 > string.length ? string.length : p2
const str = string.split('').reduce((acc, item, i) => {
if (i >= p11 && i < p22) {
console.log('i:', i)
return acc.concat(item)
}
return acc
}, '');
console.log('inside', str)
return str
}
when Im test this code I have an error
expect(substr('abba', 1, 0)).toEqual(''); // pass
expect(substr('abba', 0, 1)).toEqual('a'); // pass
expect(substr('abba', 0, 1)).toEqual('a'); // pass
expect(substr('abba', 1, 2)).toEqual('bb'); // failed
How I can fix my code to pass tests?

There is your code example:
1 function substr(string, p1 = 0, p2 = string.length) {
2 if (string.length === 0 || p2 < 0 || p1 > p2) return '';
3 const p11 = p1 <= 0 ? 0 : p1
4 let p22 = p2 > string.length ? string.length : p2
5 const str = string.split('').reduce((acc, item, i) => {
6 if (i >= p11 && i < p22) {
7 console.log('i:', i)
8 return acc.concat(item)
9 }
10 return acc
11 }, '');
12
13 console.log('inside', str)
14 return str
15 }
There are multiple mistakes in your code. You are still learning. That's no problem – everyone has started somehow. Let's name the mistakes:
On the line 2, there is p1 > p2. That does not make sense. You do not want to return empty string if the distance between string start and the substring start is greater that the maximum substring length.
Your variable names do not make sense. Try using names like startpos or length instead of names like p1 or p2.
And the third thing I found is that your substring function returns substring between positions p1 and p2, unlike the JS's builtin String.prototype.substr which returns substring between p1 and p1+p2.
Try it this way:
function substr(string, start, length = string.length) {
let outStr = '';
for (let pos = start;
pos < start + length && pos < string.length;
pos++) {
outStr += string[pos];
}
return outStr;
}
function expect(actual) {
return {
'toEqual': function (expected) {
if (actual == expected) {
document.body.innerText += `“${actual}” == “${expected}” (pass)\n`;
} else {
document.body.innerText += `“${actual}” /= “${expected}” (fail)\n`;
}
},
};
}
expect(substr('abba', 1, 0)).toEqual('');
expect(substr('abba', 0, 1)).toEqual('a');
expect(substr('abba', 0, 1)).toEqual('a');
expect(substr('abba', 1, 2)).toEqual('bb');
I think that using split and reduce for this is overkill. But if you want to use it, there is the code in the “pure functional style”, but it is inefficient, because it needs string.length steps instead of length steps to compute the result.
function substr(string, start, length = string.length) {
return string
.split('') // split between every character
.reduce((head, char, index) =>
(index >= start && head.length < length) // if we reached the substring start and there “is space” in the substring,
? head + char // append another character
: head, // else keep the substring
''); // start with empty substring
}
function expect(actual) {
return {
'toEqual': function (expected) {
if (actual == expected) {
document.body.innerText += `“${actual}” == “${expected}” (pass)\n`;
} else {
document.body.innerText += `“${actual}” /= “${expected}” (fail)\n`;
}
},
};
}
expect(substr('abba', 1, 0)).toEqual('');
expect(substr('abba', 0, 1)).toEqual('a');
expect(substr('abba', 0, 1)).toEqual('a');
expect(substr('abba', 1, 2)).toEqual('bb');
You should also have better test cases. Write test cases as if wou would teach someone to use your routines. The test cases can be important for understanding complex code. For example, take you favorite sentence and test the function using it. It will be easier to understand the test case the next time you see the code.

Related

Remove invalid parenthesis - JS (Leetcode301)

I've been taking leetcode challenges and come across this interesting problem: 301. Remove Invalid Parentheses:
Given a string s that contains parentheses and letters, remove the minimum number of invalid parentheses to make the input string valid.
Return all the possible results. You may return the answer in any order.
Example 1:
Input: s = "()())()"
Output: ["(())()","()()()"]
Example 2:
Input: s = "(a)())()"
Output: ["(a())()","(a)()()"]
Example 3:
Input: s = ")("
Output: [""]
Constraints:
1 <= s.length <= 25
s consists of lowercase English letters and parentheses '(' and ')'.
There will be at most 20 parentheses in s.
I tried to solve it using recursion, but something is not right about my code, it's returning an empty array instead of the results.
Here is the code:
var removeInvalidParentheses = function(s) {
const validExpressions = [];
let minimumRemoved = 0;
function recurse(s, index, leftCount, rightCount, expression, removedCount) {
let possibleAnswer;
if (index === s.length) {
if (leftCount === rightCount) {
if (removedCount <= minimumRemoved) {
possibleAnswer = expression.join("");
if (removedCount < minimumRemoved) {
validExpressions.length = 0;
minimumRemoved = removedCount;
}
validExpressions.push(possibleAnswer);
}
}
} else {
let currentCharacter = s[index];
let length = expression.length;
if (currentCharacter !== '(' && currentCharacter !== ')') {
expression.push(currentCharacter);
recurse(s, index + 1, leftCount, rightCount, expression, removedCount);
expression.splice(length, 1);
} else {
recurse(s, index + 1, leftCount, rightCount, expression, removedCount + 1);
expression.push(currentCharacter);
if (currentCharacter == '(') {
recurse(s, index + 1, leftCount + 1, rightCount, expression, removedCount);
} else if (rightCount < leftCount) {
recurse(s, index + 1, leftCount, rightCount + 1, expression, removedCount);
}
expression.splice(length, 1);
}
}
}
recurse(s, 0, 0, 0, [], 0);
return validExpressions;
}
The problem is that you initialise minimumRemoved = 0 which makes it impossible to ever satisfy the condition if (removedCount <= minimumRemoved) { except if removeCount is 0. But if there is no match in that condition, nothing will be ever pushed to validExpressions.
You'll want to mimic the worst situation possible, so that any solution will be considered to be better than that. Therefore initialise removedCount as a high number, like s.length + 1 or -- why not -- Infinity.
There is a second issue in your code: it may collect duplicate values in validExpressions, so make your return statement like this:
return [...new Set(validExpressions)];
This will make it work. Now, this is not a very fast solution. You may want to try to find improvements.
The problems are correctly highlihted by trincot. Here is the final code:
let removeInvalidParentheses = function (s) {
const validExpressions = [];
let minimumRemoved = Number.MAX_SAFE_INTEGER;
function recurse(
s,
index,
leftCount,
rightCount,
expression,
removedCount
) {
let possibleAnswer;
// If we have reached the end of string.
if (index === s.length) {
// If the current expression is valid.
if (leftCount === rightCount) {
// If the current count of removed parentheses is <= the current minimum count
if (removedCount <= minimumRemoved) {
// Convert StringBuilder to a String. This is an expensive operation.
// So we only perform this when needed.
possibleAnswer = expression.join("");
// If the current count beats the overall minimum we have till now
if (removedCount < minimumRemoved) {
validExpressions.length = 0;
minimumRemoved = removedCount;
}
validExpressions.push(possibleAnswer);
}
}
} else {
let currentCharacter = s[index];
let length = expression.length;
// If the current character is neither an opening bracket nor a closing one,
// simply recurse further by adding it to the expression StringBuilder
if (currentCharacter !== "(" && currentCharacter !== ")") {
expression.push(currentCharacter);
recurse(
s,
index + 1,
leftCount,
rightCount,
expression,
removedCount
);
expression.splice(length, 1);
} else {
// Recursion where we delete the current character and move forward
recurse(
s,
index + 1,
leftCount,
rightCount,
expression,
removedCount + 1
);
expression.push(currentCharacter);
// If it's an opening parenthesis, consider it and recurse
if (currentCharacter == "(") {
recurse(
s,
index + 1,
leftCount + 1,
rightCount,
expression,
removedCount
);
} else if (rightCount < leftCount) {
// For a closing parenthesis, only recurse if right < left
recurse(
s,
index + 1,
leftCount,
rightCount + 1,
expression,
removedCount
);
}
// Undoing the append operation for other recursions.
expression.splice(length, 1);
}
}
}
recurse(s, 0, 0, 0, [], 0);
return Array.from(new Set(validExpressions));
};

First Unique Character in a String Leetcode - using pointers (javascript)

Given a string, find the first non-repeating character in it and return its index. If it doesn't exist, return -1.
leetcode question
"cc" // -1
"ccdd" // -1
"leetcode" // 1
"loveleetcode" // 2
"abcabd" // 2
"thedailybyte" // 1
"developer" // 0
My approach passed all the test cases except the 2nd test case "ccdd". I am expecting -1 but receiving 4. Not sure why.
var firstUniqChar = function(s) {
if(!s || s.length === 0 ) return -1
else if(s.length === 1) return 0
let pointer1 = 0
let pointer2 = pointer1 + 1
if(s.length > 2){
while(pointer2 <= s.length - 1){
if(s[pointer1] !== s[pointer2])
pointer2++
else if(s[pointer1] === s[pointer2])
pointer1++
}
return pointer1
}
return -1
};
this is too old, but still can be too much shorter:
const firstUniqChar = (_str) => {
for (let i= 0; i < _str.length; i+= 1) {
if (_str.indexOf(_str[i]) === _str.lastIndexOf(_str[i])) return i+1;
}
return -1;
}

Check how many times a char appears in a string

Simply trying to find how many times a given character appears in a string but I can't solve it any other way then this simple for-loop. Is there a method that would solve this quicker or more eloquently other than using Regex?
function countCharacter(str, char) {
var count = 0;
for(var i = 0; i < str.length; i++){
if(str.charAt(i) === char)
count++;
}
return count;
}
There are many possible ways are available in the market.
I am adding a few of them.
Method 1:
str = "The man is as good as his word"
str.split('a')
output: (4) ["The m", "n is ", "s good ", "s his word"]
str.split('a').length - 1
output: 3
Method 2:
str = "The man is as good as his word"
str.split('').map( function(char,i)
{ if(char === 'a')
return i;
}
).filter(Boolean)
Output: (3) [5, 11, 19]
str.split('').map( function(char,i)
{ if(char === 'a')
return i;
}
).filter(Boolean).length
ouput: 3
Edit: As per comment we can also make use of filter().
str.split('').filter(function(char, i){
if(char == 'a'){
return i;
}
})
output: (3) ["a", "a", "a"]
str.split('').filter(function(char, i){
if(char == 'a'){
return i;
}
}).length
output: 3
----edited by adding more cases from answers----
there are several ways, you can use split/for/regex/reduce/indexOf like this:
function countCharacter_reduce(str, ch) {
return Array.prototype.reduce.call(str, (prev, cur) => cur === ch && ++prev && prev, 0);
}
function countCharacter_split(str, ch) {
return str.split(ch).length - 1;
}
function countCharacter_for(str, ch) {
for (var count = 0, ii = 0; ii < str.length; ii++) {
if (str[ii] === ch)
count++;
}
return count;
}
function countCharacter_regex(str, ch) {
return str.length - str.replace(new RegExp(ch, 'g'), '').length;
}
function countCharacter_indexOf(str, char) {
var start = 0;
var count = 0;
while ((start = str.indexOf(char, start) + 1) !== 0) {
count++;
}
return count;
}
performance of them by running 1,000,000 times on counting '/' in a string.
-- case1: running 1000000 times on ( 'this/is/a/path/with/extension', '/' )
countCharacter_reduce: 2389.880ms
countCharacter_regex: 496.251ms
countCharacter_split: 114.709ms
countCharacter_for: 152.012ms
countCharacter_indexOf: 90.330ms
-- case2: running 1000000 times on ( '////////////', '/' )
countCharacter_reduce: 1138.051ms
countCharacter_regex: 619.886ms
countCharacter_split: 121.945ms
countCharacter_for: 74.542ms
countCharacter_indexOf: 204.242ms
Conclusion ('>' means 'has better performance'):
for|split|indexOf > regex > reduce.
furthermore,
if the string contains more searching characters (like case2),
for>split>indexOf,
otherwise (like case1)
indexOf > split > for.
BTW: you can change the for indexOf method to fit multi-characters search (example is single character)
using reduce:
function countCharacter(str, char) {
return str.split('').reduce((a, x) => x === char ? ++a : a, 0);
}
I guess this involves regex which you wanted to avoid but it's pretty quick:
function countCharacter(str, char) {
return str.length - str.replace(new RegExp(char,"g"),"").length;
}
You can also try the str.split(char).length-1 approach suggested by Jaromanda.
Or, go all out with some fun recursion (pass 0 to startingFrom):
function countCharacter(str, char, startingFrom) {
var idx = str.indexOf(char, startingFrom);
return idx == -1 ? 0 : 1 + countCharacter(str, char, idx + 1);
}
You can get rid of the annoying extra argument at the cost of some efficiency:
function countCharacter(str, char) {
var idx = str.indexOf(char);
return idx == -1 ? 0 : 1 + countCharacter(str.substr(idx+1), char);
}
And here's a version optimized for speed (this is about 3 times faster on my browser than your original and much faster than the regex versions, according to jsperf):
function countCharacter(str, char) {
var start = 0;
var count = 0;
while((start = str.indexOf(char, start)+1) !== 0) {
count++;
}
return count;
}
Note that the indexOf approaches will generally be substantially faster than manually iterating through the string. See jsperf
Here you go. One line code
"hello".match(new RegExp('l','g')).length
replace 'l' with any char here, new RegExp('l','g').
that is
str.match(new RegExp(char,'g')).length
Have you thought of using the split() method? How about this -
function myFunction(str, char) {
return string.split(char).length - 1
}
Let me know what you think of this solution.

How to split a string based on commas except inside parenthesis

I have a string that contains a create query, and I'm trying to split it based on commas. Unfortunately, some of the lines have commas in them surrounded by parenthesis.
Example:
dbo.Display_Test1.Column,
CASE WHEN CHARINDEX(' To ', Test3) > 0 AND CHARINDEX('XVR', Test3) = 0 THEN LEFT(Test3, (CHARINDEX(' To ', Test3) - 12)) ELSE Test3 END AS Test3,
dbo.Display_Test2.Column,
ISNULL((CASE WHEN [2-Display-Test4].[Total Number] > 0 AND [1-Display-Test5].SumOfNumber = 0 THEN 0 ELSE (([2-Display-Test4].[Total Number] * 1000) / [1-Display-Test5].SumOfNumber) END), 0) AS Test6,
I want to split my string based on commas that aren't inside of (possibly multiple) parenthesis. For reference, my example should be split where the line breaks are (although my string doesn't have the line breaks in it).
I've tried a number of different solutions, but none work quite right:
/(?:\(*[^()]*\)|[^,])+/g works on lines 1,3, and 4, but fails on line 2. It breaks up the line into multiple matches.
/((?:[^,(]+|(\((?:[^()]+)|$1\)))+)/g works on lines 1,2, and 3, but fails on line 4. It also breaks up the line into multiple matches.
I can't quite seem to make it work. Any help is appreciated.
One solution possible : ( note : we remove the separations comas )
var str = "dbo.Display_Test1.Column, CASE WHEN CHARINDEX(' To ', Test3) > 0 AND CHARINDEX('XVR', Test3) = 0 THEN LEFT(Test3, (CHARINDEX(' To ', Test3) - 12)) ELSE Test3 END AS Test3, dbo.Display_Test2.Column, ISNULL((CASE WHEN [2-Display-Test4].[Total Number] > 0 AND [1-Display-Test5].SumOfNumber = 0 THEN 0 ELSE (([2-Display-Test4].[Total Number] * 1000) / [1-Display-Test5].SumOfNumber) END), 0) AS Test6,";
// if the string don't end by a coma , we add it.
var strArr = str.replace(/,*\s*$/ , ',').split('');
var res;
res = strArr.reduce(function( trans , charValue ){
if(charValue === '(') {
trans.deep++;
}
if(charValue === ')') {
trans.deep--;
}
if( trans.deep === 0){
if(charValue===',') {
trans.arr.push( trans.str);
trans.str = '';
}else{
trans.str += charValue;
}
}else{
trans.str += charValue;
}
return trans;
}, { arr : [] , str : '' ,deep : 0});
document.write('<pre>' + JSON.stringify(res.arr , null , ' ') + '</pre>');
Edit :
As suggested by Thriggle in the comments i've added the case where the string don't end by a coma.
changed variables name .
Of course if we want to work with unlimited nesting of parentheses then it would be impossible to avoid a loop. Still for some bounded nesting we do can write a regular expression capturing it.
For up to 1 parentheses level it could be
/((?:[^(),]|\([^()]*\))+)/g
For up to 2 parentheses level (probably it's your case)
/((?:[^(),]|\((?:[^()]|\([^()]*\))*\))+)/g
And so on, recursively substituting [^()]* with (?:[^()]|\([^()]*\))*
Regex won't get you where you want and still allow arbitrary complexity.
One approach is to iterate through each character in the string, keeping track of when you hit open and close parentheses, and only splitting on commas when the numbers of open and close parentheses cancel each other out (so you know you're not within a parenthetical statement).
function splitQuery(input){
var arr = [];
var lastStart = 0;
var open = 0;
for(var i = 0, len = input.length; i < len; i++){
var curr = input[i];
if(curr === "("){open += 1;}
else if(curr === ")"){ open = open < 1 ? 0 :open -=1;}
else if(curr === ","){
if(open === 0){
arr.push(input.substring(lastStart,i));
lastStart = i+1;
}
}else if(i+1 === len){
arr.push(input.substring(lastStart,i+1));
}
}
return arr;
}
Just be aware that this approach can be expensive (from a performance perspective) when dealing with especially large strings.
Here's a working example:
var query = "dbo.Display_Test1.Column, CASE WHEN CHARINDEX(' To ', Test3) > 0 AND CHARINDEX('XVR', Test3) = 0 THEN LEFT(Test3, (CHARINDEX(' To ', Test3) - 12)) ELSE Test3 END AS Test3, dbo.Display_Test2.Column, ISNULL((CASE WHEN [2-Display-Test4].[Total Number] > 0 AND [1-Display-Test5].SumOfNumber = 0 THEN 0 ELSE (([2-Display-Test4].[Total Number] * 1000) / [1-Display-Test5].SumOfNumber) END), 0) AS Test6,";
var split = splitQuery(query);
var output = document.getElementById("output")
for(var el in split){
output.insertAdjacentHTML("beforeend",el + ": "+ split[el]+"<hr/>");
}
function splitQuery(input){
var arr = [];
var lastStart = 0;
var open = 0;
for(var i = 0, len = input.length; i < len; i++){
var curr = input[i];
if(curr === "("){open += 1;}
else if(curr === ")"){ open = open < 1 ? 0 :open -=1;}
else if(curr === ","){
if(open === 0){
arr.push(input.substring(lastStart,i));
lastStart = i+1;
}
}else if(i+1 === len){
arr.push(input.substring(lastStart,i+1));
}
}
return arr;
}
<div id="output"></div>

Inserting into a number string

Have the function DashInsert(num) insert dashes ('-') between each two odd numbers in num. For example: if num is 454793 the output should be 4547-9-3. Don't count zero as an odd number.
Here is my code (not working). When I run it, I get the same response as an infinite loop where I have to kill the page but I can't see why. I know there are ways to do this by keeping it as a string but now I'm wondering why my way isn't working. Thanks...
function DashInsert(num) {
num = num.split("");
for (i = 1; i < num.length; i++) {
if (num[i - 1] % 2 != 0 && num[i] % 2 != 0) {
num.splice(i, 0, "-");
}
}
num = num.join("");
return num;
}
Using num.splice you are inserting new entries into the array, therefor increasing its length – and that makes the value of i “running behind” the increasing length of the array, so the break condition is never met.
And apart from that, on the next iteration after inserting a -, num[i-1] will be that - character, and therefor you are practically trying to check if '-' % 2 != 0 … that makes little sense as well.
So, when you insert a - into the array, you have to increase i by one as well – that will a) account for the length of the array having increased by one, and also it will check the next digit after the - on the next iteration:
function DashInsert(num) {
num = num.split("");
for (i = 1; i < num.length; i++) {
if (num[i - 1] % 2 != 0 && num[i] % 2 != 0) {
num.splice(i, 0, "-");
i++; // <- this is the IMPORTANT part!
}
}
num = num.join("");
return num;
}
alert(DashInsert("454793"));
http://jsfiddle.net/37wA9/
Once you insert a dash -, the if statement is checking this '-'%2 != 0 which is always true and thus inserts another dash, ad infinitum.
Here's one way to do it with replace using a regex and function:
function DashInsert(n) {
var f = function(m,i,s) { return m&s[i+1]&1 ? m+'-' : m; };
return String(n).replace(/\d/g,f);
}
DashInsert(454793) // "4547-9-3"
When you are adding a dash, this dash will be processed as a number on the next iteration. You need to forward one step.
function DashInsert(num) {
var num = num.split("");
for (var i = 1; i < num.length; i++) {
if ((num[i - 1] % 2 != 0) && (num[i] % 2 != 0)) {
num.splice(i, 0, "-");
i++; // This is the only thing that needs changing
}
}
num = num.join("");
return num;
}
It's because there are cases when you use the % operator on dash '-' itself, e.g. right after you splice a dash into the array.
You can correct this behavior by using a clone array.
function DashInsert(num) {
num = num.split("");
var clone = num.slice(0);
var offset = 0;
for (i = 1; i < num.length; i++) {
if (num[i - 1] % 2 != 0 && num[i] % 2 != 0) {
clone.splice(i + offset, 0, "-");
offset++;
}
}
return clone.join("");
}
alert(DashInsert("45739"));
Output: 45-7-3-9
Demo: http://jsfiddle.net/262Bf/
To complement the great answers already given, I would like to share an alternative implementation, that doesn't modify arrays in-place:
function DashInsert(num) {
var characters = num.split("");
var numbers = characters.map(function(chr) {
return parseInt(chr, 10);
});
var withDashes = numbers.reduce(function(result, current) {
var lastNumber = result[result.length - 1];
if(lastNumber == null || current % 2 === 0 || lastNumber % 2 === 0) {
return result.concat(current);
} else {
return result.concat("-", current);
}
}, []);
return withDashes.join("");
}
It's longer, but IMHO reveals the intention better, and avoids the original issue.

Categories