Getting time and space complexity of a JS function - javascript

Here' i have a solution of a problem of removing consecutive duplicate characters. But i need to know the time and space complexity of this solution.
Here's the function:
function removeAdjacentDuplicates(str){
for (let i = 0; i< str.length -1; i++){
if(str[i] === str[i+1] && i+2==str.length){
return str.substr(0,i);
} else if(i+2==str.length ||str=="" || str.length==0){
return str;
} else if (str[i] === str[i+1]){
return removeAdjacentDuplicates(str.substr(0,i)+str.substr(i+2,(str.length)-(i+2)));
}
}
};
//removeAdjacentDuplicates('abbc') returns 'ac'
I'm guessing it should be O(n) for both because for each input the function needs to loop over the whole. Also suggestion for making the function better would be appreciated too.

I suppose a simple reduce will be sufficient and not too complex here.
How to determine complexity is explained nicely here. Furthermore, there are plenty of articles to be found on the subject (e.g. here or here).
See also
console.log(`original: abba and,, sommmme of thaat ook??`, ` `);
console.log(`removeAdjacentDuplicates: ${
removeAdjacentDuplicates('abba and,, sommmme of thaat ook??')}`);
console.log(`removeDuplicateCharacterPairs: ${
removeDuplicateCharacterPairs('abba and,, sommmme of thaat ook??')}`);
console.log(`RemoveConcurrentCharacters: ${
RemoveConcurrentCharacters([...'abba and,, sommmme of thaat ook??'])}`);
console.log(`RemoveConcurrentCharactersReducer: ${
RemoveConcurrentCharactersReducer([...'abba and,, sommmme of thaat ook??'])}`);
// this removes one of duplicate character pairs
function removeAdjacentDuplicates(str) {
return str.split('')
.reduce((acc, val) =>
val === acc.slice(-1) ? acc : acc + val, '');
}
// this removes actual duplicate pairs,
// but the result may contain new duplicates
function removeDuplicateCharacterPairs(str, result = []) {
str = str.split("");
const first = str.shift();
str.length && first !== str[0] && result.push(first) || str.shift();
return str.length ?
removeDuplicateCharacterPairs(str.join(""), result) :
result.join("");
}
// this removes duplicate pairs. When the result
// contains new duplicate pairs removes them too
// so the result will not contain any duplicate
// character pair
function RemoveConcurrentCharacters(arr) {
let result = [];
for (let i = 0; i < arr.length; i += 1) {
const len = result.length - 1;
i < 1 && result.push(arr[i]);
if (i > 0) {
arr[i] !== result[len] &&
result.push(arr[i]) ||
result.splice(len, 1);
}
}
return result.join("");
};
// making a round trip: RemoveConcurrentCharacters can be
// condensed using a reducer method.
function RemoveConcurrentCharactersReducer(arr) {
return arr.reduce((acc, val) =>
!acc.length ? [val] : val === acc[acc.length-1] ?
acc.slice(0, -1) : [...acc, val], [])
.join("");
};

Related

i need to find the distance between two characters in string js. i have a solution but i can't understand the snippet of code related to if statement

The task was to write 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.
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;
}
// could somebody explain why -1 is equal to len and then len is reassigned to i???
if (len == -1) {
len = i;
} else {
len = i - len + 1
}
}
}
if (charCount < 2) {
return 0;
}
return len;
};
dude there are other ways to do this.
see if my code helps you to understand any better :)
const subLength = (str, char) => {
let charCount = 0;
let letterFound = [];
let arr = str.split('');
for (let i = 0; i < arr.length; i++) {
if (arr[i] === char) {
letterFound.push(i);
charCount++;
}
}
let result = arr.slice(letterFound[0], letterFound[1]).length + 1;
if (charCount > 2 || charCount < 2) return 0;
return result;
}
console.log(subLength('saturday', 'a'));
in the first occurrence len= -1 so:
(len ==-1) turned to true;
and len is changed to i
so len=i;
in the second occurance len is not -1 so:
len = i - len -1;
in essence, in the above expression, len keeps the index of the first occurrence, and i has the index of second occurrence, so the difference will be, the difference between two occurrences, 'qweraq' : first occurrance: 0, second: 6. 6-0-1= 5 is difference;
This is how I manage to rewrite your code.
Hope this helps.
const subLength = (str, char) => {
const arr = str.split('');
let count = 0;
arr.forEach(letter => letter === char ? count++ : '')
const result = arr.some(letter => {
if (arr.indexOf(char) !== arr.lastIndexOf(char) && count == 2) {
return true
}
return false
})
return result ? Math.abs(arr.indexOf(char) - arr.lastIndexOf(char)) + 1 : 0
}
Whilst its possible to calculate the indexes and the count within a single loop, given the triviality I'd favor standard API methods for scanning. one could always optimize later if the application actually called for it.
First we convert the string to an array so that we have the ability to filter for matches to the input character, and derive the length.
Only if there are 2 occurrences do we calculate the distance between using the indexOf and lastIndexOf methods on string (note that these will only require a second full scan of the string if the two occurrences are consecutive).
Otherwise, the result should be 0.
const subLength = (str, chr) => {
const count = str.split('').filter(ltr => ltr === chr).length
if (count === 2) {
return (str.lastIndexOf(chr) - str.indexOf(chr)) + 1
}
return 0
}
console.log(subLength('asddfghvcba', 'a'))
console.log(subLength('asddfghvcba', 'x'))
console.log(subLength('aaaaaaaaaaa', 'a'))

Return the first non-repeating character of a string

In the first chunk of my code I have an ' if ' statement that is not working as it should, and I can't figure out why.
When using the argument 'hous', it should enter the first ' if ' statement and return 0. It returns -1 instead.
var firstUniqChar = function(s) {
for (let i = 0; i < s.length; i++){
let letter = s[i];
// console.log('s[i]: ' + letter);
// console.log(s.slice(1));
// console.log( 'i: ' + i);
if ((i = 0) && !(s.slice(1).includes(letter))) {
return 0;
}
if ((i = s.length - 1) && !(s.slice(0, i).includes(letter))) {
return 1;
}
if(!(s.slice(0, i).includes(letter)) && !(s.slice(i + 1).includes(letter))) {
return 2;
}
}
return -1;
};
console.log(firstUniqChar("hous"));
This is another way you can write your function:
const firstUniqChar = s => [...s].filter(c=>!(s.split(c).length-2))[0] || -1;
console.log(firstUniqChar("hous"));
console.log(firstUniqChar("hhoous"));
console.log(firstUniqChar("hhoouuss"));
Look up method for scattered repeated characters and functional find()-based approach
You may break your input string into array of characters (e.g. using spread syntax ...) and make use of Array.prototype.find() (to get character itserlf) or Array.prototype.findIndex() (to get non repeating character position) by finding the character that is different from its neighbors:
const src = 'hhoous',
getFirstNonRepeating = str =>
[...str].find((c,i,s) =>
(!i && c != s[i+1]) ||
(c != s[i-1] && (!s[i+1] || c != s[i+1])))
console.log(getFirstNonRepeating(src))
.as-console-wrapper{min-height:100%;}
Above will work perfectly when your repeating characters are groupped together, if not, I may recommend to do 2-passes over the array of characters - one, to count ocurrence of each character, and one more, to find out the first unique:
const src = 'hohuso',
getFirstUnique = str => {
const hashMap = [...str].reduce((r,c,i) =>
(r[c]=r[c]||{position:i, count:0}, r[c].count++, r), {})
return Object
.entries(hashMap)
.reduce((r,[char,{position,count}]) =>
((count == 1 && (!r.char || position < r.position)) &&
(r = {char, position}),
r), {})
}
console.log(getFirstUnique(src))
.as-console-wrapper{min-height:100%;}
function nonRepeat(str) {
return Array
.from(str)
.find((char) => str.match(newRegExp(char,'g')).length === 1);
}
console.log(nonRepeat('abacddbec')); // e
console.log(nonRepeat('1691992933')); // 6
console.log(nonRepeat('thhinkninw')); // t

count duplicate value in recursion

how to count the duplicate values in string/array by recursion, well i know how to find the duplicate and unique by the loop with the object, but in this case, i tried to recursion it to learn my logic,
here is what i tried
function duplicate(word) {
let [obj, arr, count ] = [{},[],0]
for (i of word) {
obj[i] = (obj[i] || 0) + 1
if (obj[i] == 2) {
arr.push(i);
}
}
// console.log(arr);
let words = [...word];
words = [...new Set(words)];
// // return word
if (words.length === 0) {
return count
} else if (arr.includes(words[0])) {
count++
}
return count + duplicate(words.slice(1))
}
console.log(duplicate('xmikemk')) // 2
i tried to create object fist and pust to newArr if the values found 2 times,
then i tried to SET the word in again below to just show the unique value, then i recursions it, when i console log it, the slice method on that below to recursion and is not working, that is why the ouput i got here is 0, what is wrong with my code ? am I wrong with that condition??
that variable word has = ('xmikemk') which has 2 duplicate words, m and k , so the output i want is2`
You could spread the given string and take only the first character for counting, by taking another parameter for handing over the last counted values.
If no more spreadable items, the count is made and returned. Otherwise go on with the recursive call.
function duplicate([w, ...rest], counter = {}) {
counter[w] = (counter[w] || 0) + 1;
if (!rest.length) {
return Object.values(counter).reduce((s, c) => s + (c > 1), 0);
}
return duplicate(rest, counter);
}
console.log(duplicate('xmikemk')) // 2
You are making this more complicated than it is. Just create an internal accumulator method to populate a map and increment the frequency.
function duplicate(word) {
return duplicateInternal(word, {});
}
function duplicateInternal(word, mapping) {
if (word == null || word.length === 0) {
return mapping;
}
var c = word.charAt(0);
mapping[c] = (mapping[c] || 0) + 1;
return duplicateInternal(word.substr(1), mapping);
}
var mapping = duplicate('xmikemk');
var frequency = Object.keys(mapping)
.map(key => [key, mapping[key]])
.sort((a, b) => {
let diff = -1 * (a[1] - b[1]);
return diff === 0 ? a[0].localeCompare(b[0]) : diff;
});
console.log(frequency.map(x => x.join(' x')).join('\n'))
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}

Duplicate Encoder (duplicate letters in a string)

The goal is to convert a string to a new string where each character in the new string is '(' if that character appears only once in the original string, or ')' if that character appears more than once in the original string. Ignore capitalization when determining if a character is a duplicate.
my problem is if it's repeating a letter first parenthese is showing wrong.
function duplicateEncode(word){
var repeat = [];
var result = [];
var letters = word.split('');
for (i=0; i < letters.length; i++){
if (repeat.indexOf(letters[i]) > -1) {
result.push(")");
} else {
result.push("(");
}
repeat.push(letters[i]);
}
return result.join("");
}
console.log(duplicateEncode("aleluia"))
"my problem is if it's repeating a letter first parenthesis is showing wrong."
This is because your code doesn't do any look-ahead, it only checks what characters have already been processed. One way or another you need to check if the current letter also appears earlier or later in the string.
The first way that came to mind was to start by counting all of the letters (putting the counts in an object), then map each letter based on its count. That way you only loop through the original word exactly twice:
function duplicateEncode(word){
var letterCount = {};
var letters = word.toLowerCase().split('');
letters.forEach(function(letter) {
letterCount[letter] = (letterCount[letter] || 0) + 1;
});
return letters.map(function(letter) {
return letterCount[letter] === 1 ? '(' : ')';
}).join('');
}
console.log(duplicateEncode("aleluia"))
console.log(duplicateEncode("AleLuia"))
console.log(duplicateEncode("No duplicates"))
console.log(duplicateEncode("All duplicated ALL DUPLICATED"))
Or the same thing with .reduce() and arrow functions is only three lines:
function duplicateEncode(word){
const letters = word.toLowerCase().split('');
const counts = letters.reduce((ct, ltr) => ((ct[ltr] = (ct[ltr] || 0) + 1), ct), {});
return letters.map(letter => counts[letter] === 1 ? '(' : ')').join('');
}
console.log(duplicateEncode("aleluia"))
console.log(duplicateEncode("AleLuia"))
console.log(duplicateEncode("No duplicates"))
console.log(duplicateEncode("All duplicated ALL DUPLICATED"))
const duplicateEncode = word => {
let newString = ''
word = word.toLowerCase() || word
word.split('').filter((x, index) => {
if(word.indexOf(x) !== index){
newString += ')'
}else if(word.lastIndexOf(x) !== index){
newString += ')'
}else{
newString += '('
}
})
return newString
}
duplicateEncode("O!!!!#k!!!H!!!)!!n!")
You can check the .length of each matched letter in string using RegExp constructor and String.prototype.match(). If .length of matched character is 1 return "(" else return ")"
const word = "aleluia";
let res = [...word].map(letter =>
word.match(new RegExp(letter, "ig")).length === 1 ? "(" : ")"
).join("");
console.log(res);
There is a more simple way to solve this task. Guess it may be more understandable for newbies in JS because the following solution contains only 4 basic level methods. So here we go.
function duplicateEncode(word) {
return word
.toLowerCase()
.split("")
.map(function (a, i, w) {
return w.indexOf(a) == w.lastIndexOf(a) ? "(" : ")";
})
.join("");
}
so:
'a' => '('
'aa' => '))'
'aba' => ')()'
'abA' => ')()'
'aba'
.toLowerCase()
.split('')
.reduce((acc, char, i, arr) => {
const symbol = arr.filter(letter => letter === char).length < 2 ? '(' : ')'
return acc + symbol
}, '')
The reason is your result array is empty until the second iteration (i = 1). The solution is to start with an array with the first element.
function duplicateEncode(word) {
var repeat = [];
var result = [];
var letters = word.split('');
for (i = 0; i < letters.length; i++) {
repeat.push(letters[0]);
if (repeat.indexOf(letters[i]) > -1) {
result.push(")");
} else {
result.push("(");
}
repeat.push(letters[i]);
}
return result.join("");
}
console.log(duplicateEncode("aleluia"))
function duplicateEncode(word){
let w = word.toLowerCase();
return Array.from(w).map(x => w.replace( new RegExp(`[^${x}]`, 'g') , "").length > 1 ? ')' : '(').join('');
}

Get function parameter length including default params

If you make use of the Function.length property, you get the total amount of arguments that function expects.
However, according to the documentation (as well as actually trying it out), it does not include Default parameters in the count.
This number excludes the rest parameter and only includes parameters before the first one with a default value
- Function.length
Is it possible for me to somehow get a count (from outside the function) which includes Default parameters as well?
Maybe you can parse it yourself, something like:
function getNumArguments(func) {
var s = func.toString();
var index1 = s.indexOf('(');
var index2 = s.indexOf(')');
return s.substr(index1 + 1, index2 - index1 - 1).split(',').length;
}
console.log(getNumArguments(function(param1, param3 = 'test', ...param2) {})); //3
Copying my answer over to here from a duplicate question:
Well, it's a bit of a mess but I believe this should cover most edge cases.
It works by converting the function to a string and counting the commas, but ignoring commas that are in strings, in function calls, or in objects/arrays. I can't think of any scenarios where this won't return the proper amount, but I'm sure there is one, so this is in no way foolproof, but should work in most cases.
UPDATE: It's been pointed out to me that this won't work for cases such as getNumArgs(a => {}) or getNumArgs(function(a){}.bind(null)), so be aware of that if you try to use this.
function getNumArgs(func) {
var funcStr = func.toString();
var commaCount = 0;
var bracketCount = 0;
var lastParen = 0;
var inStrSingle = false;
var inStrDouble = false;
for (var i = 0; i < funcStr.length; i++) {
if (['(', '[', '{'].includes(funcStr[i]) && !inStrSingle && !inStrDouble) {
bracketCount++;
lastParen = i;
} else if ([')', ']', '}'].includes(funcStr[i]) && !inStrSingle && !inStrDouble) {
bracketCount--;
if (bracketCount < 1) {
break;
}
} else if (funcStr[i] === "'" && !inStrDouble && funcStr[i - 1] !== '\\') {
inStrSingle = !inStrSingle;
} else if (funcStr[i] === '"' && !inStrSingle && funcStr[i - 1] !== '\\') {
inStrDouble = !inStrDouble;
} else if (funcStr[i] === ',' && bracketCount === 1 && !inStrSingle && !inStrDouble) {
commaCount++;
}
}
// Handle no arguments (last opening parenthesis to the last closing one is empty)
if (commaCount === 0 && funcStr.substring(lastParen + 1, i).trim().length === 0) {
return 0;
}
return commaCount + 1;
}
Here are a few tests I tried it on: https://jsfiddle.net/ekzuvL0c/
Here is a function to retrieve the 'length' of a function (expression or object) or an arrow function expression (afe). It uses a regular expression to extract the arguments part from the stringified function/afe (the part between () or before =>) and a regular expression to cleanup default values that are strings. After the cleanups, it counts the comma's, depending on the brackets within the arguments string.
Note This will always be an approximation. There are edge cases that won't be covered. See the tests in this Stackblitz snippet
const determineFnLength = fnLenFactory();
console.log(`fnTest.length: ${determineFnLength(fnTest)}`);
function fnTest(a,
b,
c = 'with escaped \' quote and, comma',
d = "and double \" quotes, too!" ) { console.log(`test123`); }
function fnLenFactory() {
const fnExtractArgsRE = /(^[a-z_](?=(=>|=>{)))|((^\([^)].+\)|\(\))(?=(=>|{)))/g;
const valueParamsCleanupRE = /(?<=[`"'])([^\`,].+?)(?=[`"'])/g;
const countArgumentsByBrackets = params => {
let [commaCount, bracketCount, bOpen, bClose] = [0, 0, [...`([{`], [...`)]}`]];
[...params].forEach( chr => {
bracketCount += bOpen.includes(chr) ? 1 : bClose.includes(chr) ? -1 : 0;
commaCount += chr === ',' && bracketCount === 1 ? 1 : 0; } );
return commaCount + 1; };
const extractArgumentsPartFromFunction = fn => {
let fnStr = fn.toString().replace(RegExp(`\\s|function|${fn.name}`, `g`), ``);
fnStr = (fnStr.match(fnExtractArgsRE) || [fn])[0]
.replace(valueParamsCleanupRE, ``);
return !fnStr.startsWith(`(`) ? `(${fnStr})` : fnStr; };
return (func, forTest = false) => {
const params = extractArgumentsPartFromFunction(func);
const nParams = params === `()` ? 0 : countArgumentsByBrackets(params);
return forTest ? [params, nParams] : nParams;
};
}

Categories

Resources