Time Complexity for JavaScript Anagram Function - javascript

I had an assignment to right a function that will take 2 strings and return the number of characters needed to be deleted in order to make the 2 strings anagrams of each other. My question is what the time complexity of this function is and if there is a faster way to achieve the same result.
Here is my solution:
function anagramDelete(str1, str2){
let obj1 = {}, obj2 = {};
// Load obj1 with all characters of str1 and their count
str1.split('').forEach((char)=> {
if(char in obj1){
obj1[char]++;
} else {
obj1[char] = 1;
}
});
// Load obj2 with all characters of str1 and their count
str2.split('').forEach((char)=> {
if(char in obj2){
obj2[char]++;
} else {
obj2[char] = 1;
}
});
// Track # of chars deleted
let numOfDeletions = 0;
// Compare each char in obj1 w obj2 and count deletions
for(char in obj1){
if(obj2[char]){
numOfDeletions += Math.abs(obj2[char] - obj1[char]);
} else {
numOfDeletions += obj1[char];
}
}
// Compare each char in obj2 w obj1 and count deletions
for(char in obj2){
if(!obj1[char]){
numOfDeletions += obj2[char];
}
}
return numOfDeletions;
}
As far as I can tell, because there are 4 loops it would be O(4n) or just O(n). I say this because there are no nested loops. Is this correct? Any better solutions?

You could use a single object and sum only the absolut values.
This solution uses the strings as array like objects.
function anagramDelete(str1, str2) {
var letters = {};
Array.prototype.forEach.call(str1, char => letters[char] = (letters[char] || 0) + 1);
Array.prototype.forEach.call(str2, char => letters[char] = (letters[char] || 0) - 1);
return Object.keys(letters).reduce((r, k) => r + Math.abs(letters[k]), 0);
}
console.log(anagramDelete('anagram', 'function'));

Your code is O(n + m); in general one does not really care too much about constants in a complexity class. n is the length of the first string and m is the length of the second string.
Also:
To be precise in your case, since you mentioned O(4n) - I am not sure if that is accurate. You use the split function twice, which turns a string into an array of characters in your case. You did not account for that in your analysis.
O(n + m) would be the correct answer.
And if you want to detail the analysis it would be O(3n + 3m). That is because:
- for the first string you use split (O(n)), you loop over each character (O(n)) and you loop again for comparison (O(n))
- for the second string you use split (O(m)), you loop over each character (O(m)) and you loop again for comparison (O(m))
I assume your code is correct. I did not check that.
P.S.:
If you are interested in fine tuning the constants you can refer to the other answers, they are probably faster than your code in theory. In practice I don't think that matters really.

Not better but shorter:
function anagramDelete(str1, str2){
const chars = {};
var result = 0;
for(const char of str1)
chars[char] = (chars[char] || 0) +1;
for(const char of str2)
chars[char] = (chars[char] || 0) -1;
for(const [char, count] of Object.entries(chars))
result += Math.abs(count);
return result;
}

Related

How to improve performance of this Javascript/Cracking the code algorithm?

so here is the question below, with my answer to it. I know that because of the double nested for loop, the efficiency is O(n^2), so I was wondering if there were a way to improve my algorithm/function's big O.
// Design an algorithm and write code to remove the duplicate characters in a string without using any additional buffer. NOTE: One or two additional variables are fine. An extra copy of the array is not.
function removeDuplicates(str) {
let arrayString = str.split("");
let alphabetArray = [["a", 0],["b",0],["c",0],["d",0],["e",0],["f",0],["g",0],["h",0],["i",0],["j",0],["k",0],["l",0],["m",0],["n",0],["o",0],["p",0],["q",0],["r",0],["s",0],["t",0],["u",0],["v",0],["w",0],["x",0],["y",0],["z",0]]
for (let i=0; i<arrayString.length; i++) {
findCharacter(arrayString[i].toLowerCase(), alphabetArray);
}
removeCharacter(arrayString, alphabetArray);
};
function findCharacter(character, array) {
for (let i=0; i<array.length; i++) {
if (array[i][0] === character) {
array[i][1]++;
}
}
}
function removeCharacter(arrString, arrAlphabet) {
let finalString = "";
for (let i=0; i<arrString.length; i++) {
for (let j=0; j<arrAlphabet.length; j++) {
if (arrAlphabet[j][1] < 2 && arrString[i].toLowerCase() == arrAlphabet[j][0]) {
finalString += arrString[i]
}
}
}
console.log("The string with removed duplicates is:", finalString)
}
removeDuplicates("Hippotamuus")
The ASCII/Unicode character codes of all letters of the same case are consecutive. This allows for an important optimization: You can find the index of a character in the character count array from its ASCII/Unicode character code. Specifically, the index of the character c in the character count array will be c.charCodeAt(0) - 'a'.charCodeAt(0). This allows you to look up and modify the character count in the array in O(1) time, which brings the algorithm run-time down to O(n).
There's a little trick to "without using any additional buffer," although I don't see a way to improve on O(n^2) complexity without using a hash map to determine if a particular character has been seen. The trick is to traverse the input string buffer (assume it is a JavaScript array since strings in JavaScript are immutable) and overwrite the current character with the next unique character if the current character is a duplicate. Finally, mark the end of the resultant string with a null character.
Pseudocode:
i = 1
pointer = 1
while string[i]:
if not seen(string[i]):
string[pointer] = string[i]
pointer = pointer + 1
i = i + 1
mark string end at pointer
The function seen could either take O(n) time and O(1) space or O(1) time and O(|alphabet|) space if we use a hash map.
Based on your description, I'm assuming the input is a string (which is immutable in javascript) and I'm not sure what exactly does "one or two additional variables" mean so based on your implementation, I'm going to assume it's ok to use O(N) space. To improve time complexity, I think implementations differ according to different requirements for the outputted string.
Assumption1: the order of the outputted string is in the order that it appears the first time. eg. "bcabcc" -> "bca"
Suppose the length of s is N, the following implementation uses O(N) space and O(N) time.
function removeDuplicates(s) {
const set = new Set(); // use set so that insertion and lookup time is o(1)
let res = "";
for (let i = 0; i < s.length; i++) {
if (!set.has(s[i])) {
set.add(s[i]);
res += s[i];
}
}
return res;
}
Assumption2: the outputted string has to be of ascending order.
You may use quick-sort to do in-place sorting and then loop through the sorted array to add the last-seen element to result. Note that you may need to split the string into an array first. So the implementation would use O(N) space and the average time complexity would be O(NlogN)
Assumption3: the result is the smallest in lexicographical order among all possible results. eg. "bcabcc" -> "abc"
The following implementation uses O(N) space and O(N) time.
const removeDuplicates = function(s) {
const stack = []; // stack and set are in sync
const set = new Set(); // use set to make lookup faster
const lastPos = getLastPos(s);
let curVal;
let lastOnStack;
for (let i = 0; i < s.length; i++) {
curVal = s[i];
if (!set.has(curVal)) {
while(stack.length > 0 && stack[stack.length - 1] > curVal && lastPos[stack[stack.length - 1]] > i) {
set.delete(stack[stack.length - 1]);
stack.pop();
}
set.add(curVal);
stack.push(curVal);
}
}
return stack.join('');
};
const getLastPos = (s) => {
// get the last index of each unique character
const lastPosMap = {};
for (let i = 0; i < s.length; i++) {
lastPosMap[s[i]] = i;
}
return lastPosMap;
}
I was unsure what was mean't by:
...without using any additional buffer.
So I thought I would have a go at doing this in one loop, and let you tell me if it's wrong.
I have worked on the basis that the function you have provided gives the correct output, you were just looking for it to run faster. The function below gives the correct output and run's a lot faster with any large string with lots of duplication that I throw at it.
function removeDuplicates(originalString) {
let outputString = '';
let lastChar = '';
let lastCharOccurences = 1;
for (let char = 0; char < originalString.length; char++) {
outputString += originalString[char];
if (lastChar === originalString[char]) {
lastCharOccurences++;
continue;
}
if (lastCharOccurences > 1) {
outputString = outputString.slice(0, outputString.length - (lastCharOccurences + 1)) + originalString[char];
lastCharOccurences = 1;
}
lastChar = originalString[char];
}
console.log("The string with removed duplicates is:", outputString)
}
removeDuplicates("Hippotamuus")
Again, sorry if I have misunderstood the post...

Combine an array with other arrays, push each combination Javascript

I'm trying to take an array, and compare each value of that array to the next value in the array. When I run my code, components that should match with more than one array only return one match, instead of all of them. I'm probably doing something wrong somewhere, but for the life of my I don't seem to be able to figure it out.
This is my code:
INPUT
minterms = [["4",[0,1,0,0]],
["8",[1,0,0,0]],
["9",[1,0,0,1]],
["10",[1,0,1,0]],
["12",[1,1,0,0]],
["11",[1,0,1,1]],
["14",[1,1,1,0]],
["15",[1,1,1,1]]];
Function
function combineMinterms(minterms) {
var match = 0;
var count;
var loc;
var newMin = [];
var newMiny = [];
var used = new Array(minterms.length);
//First Component
for (x = 0; x < minterms.length; x++) {
if(minterms[x][1][minterms[x][1].length - 1] == "*") {
newMin.push(minterms[x].slice());
continue;
};
//Second Component
for (y = x + 1; y < minterms.length; y++) {
count = 0;
//Compare each value
for (h = 0; h < minterms[x][1].length; h++) {
if (minterms[x][1][h] != minterms[y][1][h]) {
count++;
loc = h;
}
if (count >= 2) {break; };
}
//If only one difference, push to new
if (count === 1) {
newMin.push(minterms[x].slice());
newMiny = minterms[y].slice();
newMin[match][1][loc] = "-";
while(newMin[match][0].charAt(0) === 'd') {
newMin[match][0] = newMin[match][0].substr(1);
}
while(newMiny[0].charAt(0) === 'd') {
newMiny[0] = newMiny[0].substr(1);
}
newMin[match][0] += "," + newMiny[0];
used[x] = 1;
used[y] = 1;
match++;
continue;
}
}
//If never used, push to new
if(used[x] != 1) {
newMin.push(minterms[x].slice());
newMin[match][1].push("*");
match++;
}
}
return newMin;
}
Desired Output
newMin = [["4,12",[-,1,0,0]],
["8,9",[1,0,0,-]],
["8,10",[1,0,-,0]],
["8,12",[1,-,0,0]],
["9,11",[1,0,-,1]],
["10,11",[1,0,1,-]],
["10,14",[1,-,1,0]],
["12,14",[1,1,-,0]],
["11,15",[1,-,1,1]],
["14,15",[1,1,1,-]]];
It will combine term 8, with 9 but won't continue to combine term 8 with 10, 12
Thanks in advance for the help.
Array.prototype.slice performs a shallow copy.
Each entry in minterms is an array of a string and a nested array.
When you slice the entry, you get a new array with a copy of the string and a copy of the Array object reference. But that copy of the Array reference still points to the array contained in an element of minterms.
When you update the nested array
newMin[match][1][loc] = "-";
you are updating the nested array within the input. I never fathomed the logic of what you are doing, but I believe this is the problem, with solution of cloning the nested array (as well) when cloning an input array element.
A secondary issue you will probably wish to fix is that not all variables were declared: var x,y,h; or equivalent inline declarations are missing.
let minterms = [4,8,9,10,12,11,14,15];
let newMin = [];
minterms.map((value, index) =>{
minterms.reduce((accumulator, currentValue, currentIndex, array) => {
accumulator = value;
let out = (accumulator ^ currentValue).toString(2);
if(out.split('').filter(n=>n==="1").length == 1) newMin.push([value, currentValue]);
}, value);
});
console.log(newMin);
There is a better approach (in 10 lines of code). Since you're working with binary representations, you might want to consider using BitWise operators. When coupled with array operators it makes most of this straight forward.
For instance:
Given a match means only a single bit differs between two binary numbers:
The bitwise XOR operator returns 1 for each bit that doesn't match. So:
0100 XOR 1000 results in 1000
Now, we need to count the number of '1' digits in the binary number returned. We can use the length property of an array to do this. To turn 1000 into an array, first we turn the binary into a string:
The binary representation of the integer 4 is easily retrieved with:
num.toString(2)
So if num === 4, the output above is the string "0100".
Now we use str.split() to turn the string into an array. Remove everything from the array that is not a '1'. Now simply get the length property. If the length === 1, it is a match.
I put together a solution like this for you. It is close to your use case. I didn't use the funny dash style in the output because that was not part of your question.
https://jsbin.com/xezuwax/edit?js,console

Javascript Split Method with Additive Persistence

This is my first Stack Overflow post, so please let me know if I am not formatting properly!
I am trying to answer this Coderbyte question:
"Using the JavaScript language, have the function AdditivePersistence(num) take the num parameter being passed which will always be a positive integer and return its additive persistence which is the number of times you must add the digits in num until you reach a single digit. For example: if num is 2718 then your program should return 2 because 2 + 7 + 1 + 8 = 18 and 1 + 8 = 9 and you stop at 9."
However, my solution keeps returning "str.split is not a function". I thought that was a standard method for converting strings into arrays. Any idea why this code might not work?
function AdditivePersistence(num) {
let str = num.toString; //number into string
let arr = str.split(""); //string into array
// adds numbers in array, then repeats until left with single digit
let count = 0;
while(arr.length > 1) {
arr.reduce(function(a,b){ return Number(a) + Number(b) });
count++;
}
return count;
};
I tried searching Stack Overflow, Google, W3Schools, MDN and other Coderbyte answers but could not figure out why this doesn't work. Any help would be appreciated.
Try using String() constructor.
let str = String(num);
Note also, while loop does not conclude as arr is not redefined by call to .reduce(). You can redefine arr by using String() constructor and .split() again within while loop
function AdditivePersistence(num) {
let str = String(num); //number into string
let arr = str.split(""); //string into array
// adds numbers in array, then repeats until left with single digit
let count = 0;
while (arr.length > 1) {
// set `arr` to string then array with values returned from `.reduce()`
arr = String(arr.reduce(function(a, b) {
return Number(a) + Number(b)
})).split("");
count++;
}
return count;
};
var n = AdditivePersistence(2718);
console.log(n);
You can convert to string this way:
let str = num + '';

Why can't I swap characters in a javascript string?

I am trying to swap first and last characters of array.But javascript is not letting me swap.
I don't want to use any built in function.
function swap(arr, first, last){
var temp = arr[first];
arr[first] = arr[last];
arr[last] = temp;
}
Because strings are immutable.
The array notation is just that: a notation, a shortcut of charAt method. You can use it to get characters by position, but not to set them.
So if you want to change some characters, you must split the string into parts, and build the desired new string from them:
function swapStr(str, first, last){
return str.substr(0, first)
+ str[last]
+ str.substring(first+1, last)
+ str[first]
+ str.substr(last+1);
}
Alternatively, you can convert the string to an array:
function swapStr(str, first, last){
var arr = str.split('');
swap(arr, first, last); // Your swap function
return arr.join('');
}
Let me offer my side of what I understood: swapping items of an array could be something like:
var myFish = ["angel", "clown", "mandarin", "surgeon"];
var popped = myFish.pop();
myFish.unshift(popped) // results in ["surgeon", "angel", "clown", "mandarin"]
Regarding swaping first and last letters of an strings could be done using Regular Expression using something like:
"mandarin".replace(/^(\w)(.*)(\w)$/,"$3$2$1")// outputs nandarim ==> m is last character and n is first letter
I just ran your code right out of Chrome, and it seemed to work find for me. Make sure the indices you pass in for "first" and "last" are correct (remember JavaScript is 0-index based). You might want to also try using console.log in order to print out certain variables and debug if it still doesn't work for you.
EDIT: I didn't realize you were trying to manipulate a String; I thought you just meant an array of characters or values.
function swapStr(str, first, last) {
if (first == last) {
return str;
}
if (last < first) {
var temp = last;
last = first;
first = temp;
}
if (first >= str.length) {
return str;
}
return str.substring(0, first) +
str[last] +
str.substring(first + 1, last) +
str[first] +
str.substring(last + 1);
}
Swap characters inside a string requires the string to convert into an array, then the array can be converted into string again:
function swap(arr, first, last){
arr = arr.split(''); //to array
var temp = arr[first];
arr[first] = arr[last];
arr[last] = temp;
arr = arr.join("").toString() //to string
return arr;
}
Following usage:
str = "ABCDE"
str = swap(str,1,2)
console.log(str) //print "ACBDE"
I hope this piece of code will help somebody.
var word = "DED MOROZ";
var arr = word.split('');
for (var i = 0; i < arr.length/2; i++) {
var temp = arr[i];
arr[i] = arr[arr.length - i - 1];
arr[word.length - i - 1] = temp;
}
console.log(arr.join(""));
Run this code and your characters will be swapped.

How do you sort letters in JavaScript, with capital and lowercase letters combined?

I'm working on a JavaScript (jQuery's OK too if this needs it, but I doubt it will) function to alphabetize a string of letters. Let's say the string that I want to sort is: "ACBacb".
My code as of now is this:
var string='ACBacb';
alert(string.split('').sort().join(''));
This returns ABCabc. I can see why that happens, but that is not the format that I am looking for. Is there a way that I can sort it by putting the same letters next to each other, capital letter first? So when I put in ACBacb, I get AaBbCc?
Array.sort can have a sort function as optional argument.
What about sorting the string first (ACBacbA becomes AABCabc), and then sorting it case-insensitive:
function case_insensitive_comp(strA, strB) {
return strA.toLowerCase().localeCompare(strB.toLowerCase());
}
var str = 'ACBacbA';
// split the string in chunks
str = str.split("");
// sorting
str = str.sort();
str = str.sort( case_insensitive_comp )
// concatenate the chunks in one string
str = str.join("");
alert(str);
As per Felix suggestion, the first sort function can be omitted and merged in the second one. First, do a case-insensitive comparison between both characters. If they are equal, check their case-sensitive equivalents. Return -1 or 1 for a difference and zero for equality.
function compare(strA, strB) {
var icmp = strA.toLowerCase().localeCompare(strB.toLowerCase());
if (icmp != 0) {
// spotted a difference when considering the locale
return icmp;
}
// no difference found when considering locale, let's see whether
// capitalization matters
if (strA > strB) {
return 1;
} else if (strA < strB) {
return -1;
} else {
// the characters are equal.
return 0;
}
}
var str = 'ACBacbA';
str = str.split('');
str = str.sort( compare );
str = str.join('');
You can pass a custom comparison function to Array.sort()
The already given answers are right so far that you have to use a custom comparison function. However you have to add an extra step to sort capital letters before lower case once:
function cmp(x,y) {
if(x.toLowerCase() !== y.toLowerCase()) {
x = x.toLowerCase();
y = y.toLowerCase();
}
return x > y ? 1 : (x < y ? -1 : 0);
// or
// return x.localeCompare(y);
}
If the letters are the same, the originals have to be compared, not the lower case versions. The upper case letter is always "larger" than the lower case version.
DEMO (based on #Matt Ball's version)
a working example http://jsfiddle.net/uGwZ3/
var string='ACBacb';
alert(string.split('').sort(caseInsensitiveSort).join(''));
function caseInsensitiveSort(a, b)
{
var ret = 0;
a = a.toLowerCase();b = b.toLowerCase();
if(a > b)
ret = 1;
if(a < b)
ret = -1;
return ret;
}
Use a custom sort function like this:
function customSortfunc(a,b){
var lca = a.toLowerCase(), lcb = b.toLowerCase();
return lca > lcb ? 1 : lca < lcb ? -1 : 0;
}
var string='ACBacb';
alert(string.split('').sort(customSortfunc).join(''));
You can read more about the sort function here: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort
Beware: if you use localeCompare like other answer suggests "u" and "ü" will be sorted together as the same letter, since it disregards all diacritics.
basic example for another replace, combined with lowercase :D
<button onclick="myFunction('U')">Try it</button>
<p id="demo"></p>
<script>
function myFunction(val) {
var str = "HELLO WORLD!";
var res = str.toLowerCase().split("o");
var elem = document.getElementById("demo").innerHTML
for(i = 0; i < res.length; i++){
(i > 0)?elem += val + res[i]:elem += res[i];
}
}
</script>

Categories

Resources