Find a secret word given an array of triplets? - javascript

My code works and I need help figuring out how to optimize it. If possible, don't give me any code, just tips on optimizing it, please.
The rules for the puzzle are:
Each triplet has the rules of how the letters are ordered in the secret word (each letter is followed by the next letter inside the triplet array).
all the letters of the secret word are distinct.
Example input:
triplets1 = [
['t','u','p'],
['w','h','i'],
['t','s','u'],
['a','t','s'],
['h','a','p'],
['t','i','s'],
['w','h','s']
]
Expected Output: "whatisup"
I came up with this code, which works. It selects the first letters that are not preceded by any others in the arrays they are placed, removes them, concatenates them into the final word, and keep doing that until all the arrays are empty. The code isn't being accepted though, because is exceeding the time limit.
function recoverSecret(triplets) {
let secretWord = '', character = '';
let notEmpty = true;
let size = triplets.length;
//it loops until array is empty
while(notEmpty) {
notEmpty = false;
for (let i = 0; i < size; i++) {
for (let j = 0; j < triplets[i].length; j++) {
if (character) j = 0; //everytime a character is included, this condition is truthy, so you have to go back to the start of the array because the character was removed last iteration
character = triplets[i][j];
let remove = []; //this array will have the positions of the letter to remove in the removal cycle
for (let k = 0; k < size; k++) {
if (character == triplets[k][0]) remove.push(k);
//if the letter is in the triplet and it's not the first position, then it isn't the letter we're looking for, so character equals to '', otherwise it will be the letter which will be added to the secretWord string
if (k != i && (triplets[k].includes(character))) {
if (character != triplets[k][0]) {
character = '';
break;
}
}
}
secretWord += character;
if (character) {
//if character is not '', then a removal loop is done to remove the letter because we just found its place
for (x of remove) {
triplets[x].shift();
}
}
// if (triplets[i].length == 0) break;
}
if (triplets[i] != 0) notEmpty = true; //if every triplet is empty, notEmpty remains false and while loop is over
}
}
return secretWord;
}
If possible I don't want any code, just tips on optimization, please.

After some thought, I got it right. The most important change was realizing that I didn't have to loop through each char in the array. I only needed to do it for the first char of each triplet sequence.
Then, made some other adjustments, like, instead of two ifs for checking if the char is the first element of every triplet it's in, I used the condition triplets[k].indexOf(character) > 0 to check both things at the same time, since indexOf method returns -1 if it doesn't find what it is looking for.
I'm not saying it's the best it could be, but now it is a little better in performance than it was
function recoverSecret(triplets) {
let secretWord = '', character = '';
let notEmpty = true;
while (notEmpty) {
notEmpty = false;
for (let i = 0; i < triplets.length; i++) {
if (triplets[i].length == 0) continue;
character = triplets[i][0];
let remove = [];
for (let k = 0; k < triplets.length; k++) {
if (triplets[k].indexOf(character) > 0) {
character = '';
break;
}
else if (triplets[k][0] == character) remove.push(k);
}
secretWord += character;
if (character) {
for (x of remove) {
triplets[x].shift();
}
}
if (triplets[i].length != 0) { notEmpty = true; }
console.log(triplets)
}
return secretWord;
}
}

Related

Palindrome in JavaScript

I'm trying to write code to determine if a string is a palindrome. I am making the string lowercase, taking out the spaces, and turning it into an array. Next, I am splitting it in half, reversing the second half, and comparing those two arrays to see if the string is a palindrome. The function will not log true.
let string = "Never odd or even";
let lowerString = string.toLowerCase();
let split = lowerString.split("");
let array = split.filter(noSpaces);
function noSpaces(i) {
return i !== " ";
}
function checkIfPal() {
if (array.length % 2 === 1) {
let firstHalf = array.slice(0, array.length / 2);
let secondHalf = array.slice(array.length / 2 + 1, array.length);
let revSecondHalf = [];
for (let i = secondHalf.length - 1; i > -1; i--) {
revSecondHalf.push(secondHalf[i]);
}
if (firstHalf === revSecondHalf) {
console.log("true for odd");
} else {
console.log("false for odd");
}
} else {
let firstHalf = array.slice(0, array.length / 2);
let secondHalf = array.slice(array.length / 2, array.length);
let revSecondHalf = [];
for (let i = secondHalf.length - 1; i > -1; i--) {
revSecondHalf.push(secondHalf[i]);
}
if (firstHalf === revSecondHalf) {
console.log("true for even");
} else {
console.log("false for even");
}
}
}
checkIfPal();
There's a much simpler way of doing this.
Strip out anything that isn't a letter or number
Make the string lower case
Loop over half the string
Compare current letter to last letter minus current position
function isPalindrome(str) {
str = str.replace(/[^\w\d]/g, '').toLowerCase();
const len = str.length;
for (let i = 0; i < len / 2; i++) {
if (str[i] !== str[len - 1 - i]) {
return false;
}
}
return true;
}
console.log(isPalindrome('A man, a plan, a canal, Panama!'));
console.log(isPalindrome('Mr. Owl Ate My Metal Worm'));
console.log(isPalindrome('A Santa Lived As a Devil At NASA'));
And then there is the super simple, but not very efficient on long strings way of doing it.
function isPalindrome(str) {
str = str.replace(/[^\w\d]/g, '').toLowerCase();
return str === str.split('').reverse().join('');
}
console.log(isPalindrome('A man, a plan, a canal, Panama!'));
console.log(isPalindrome('Mr. Owl Ate My Metal Worm'));
console.log(isPalindrome('A Santa Lived As a Devil At NASA'));
In this line
if (firstHalf === revSecondHalf) {
you're attempting to compare two arrays, but JavaScript doesn't allow you to do that with === as they are two different objects. Either join each array to a string and then compare them, or loop over the elements in one array comparing them to the elements at the same index in the other array.
The former method is easier.
In your script you are comparing two array objects against each other using ===.
If the variables in the comparison reference the exact same array object, then this will return true. But if they point to two different array objects, even if their contents are the same, it will always return false.
If you wish to continue using the arrays for comparison, you will need to check if each element of the array is the same.
function compareArrayElements(arr1, arr2) {
if (arr1.length != arr2.length)
return false;
for (var i=0;i<arr1.length;i++) {
if (arr1[i] != arr2[i])
return false;
}
return true;
}
Now that you have a solution, you can work to optimizing it and lowering the number of for loops.
An algorithm based on comparing an array with its reverse:
const isPalindrome = (str) => {
//Eliminate punctuation and spaces
// Force lower case
// Split
let arr = str.toString().replace(/[^A-Za-z0-9_]/g, "").toLowerCase().split('');
// Join into one word
let joined = arr.join('');
// Reverse adn join into one word
let reverseJoined = arr.reverse().join('');
//compare
return joined == reverseJoined;
}
console.log(isPalindrome('Red rum, sir, is murder'));
console.log(isPalindrome(404));
console.log(isPalindrome('Red rum, sir'));
console.log(isPalindrome(500));
"use strict";
function palindromeString(str) {
let rev = "";
const len = str.length;
for (let i = len - 1; i >= 0; i--) {
rev += str[i];
}
const value =
str.toLowerCase() === rev.toLowerCase()
? "given word is Palindrome"
: "given word is not Palindrome";
return value;
}
console.log(palindromeString("Raccar"));

Valid Anagram Space Complexity

var isAnagram = function(s, t) {
const len = s.length;
if (len !== t.length) return false;
const hashTab = {};
for (let i = 0; i < len; i++) {
if (!hashTab[s[i]]) {
hashTab[s[i]] = 1;
} else {
hashTab[s[i]]++;
}
if (!hashTab[t[i]]) {
hashTab[t[i]] = -1;
} else {
hashTab[t[i]]--;
}
}
for (let item in hashTab) {
if (hashTab[item]) return false;
}
return true;
Having a hard time figuring out the space complexity of this algorithm. My hypothesis is O(n) as the hashtable grows in size in relations to the input s. This question assumes that the string only contains lowercase letters.
As you mentioned that the string only contains lowercase letters, we can convert characters in given string into corresponding integers ranging from 0 to 25.
To keep track of frequencies of these letters we can declare an array of exactly 26 size space. So, overall space complexity will be O(M), where M is total different letters. In this case, M=26. So, space complexity is O(26).
var isAnagram = function(s, t) {
const len = s.length;
if (len !== t.length) return false;
hashTab = new Array(26);
for(i = 0; i<26; i++){
hashTab[i] = 0;
}
for (let i = 0; i < len; i++) {
idx = s[i].charCodeAt()-97;
hashTab[ idx ]++;
idx = t[i].charCodeAt()-97;
hashTab[ idx ]--;
}
for(i = 0; i<26; i++){
if(hashTab[i]!=0) return false;
}
return true;
}
The major space cost in your code is the hashmap which may contain a frequency counter for each lower case letter from a to z. So in this case, the space complexity is supposed to be constant O(26).
To make it more general, the space complexity of this problem is related to the size of alphabet for your input string. If the input string only contains ASCII characters, then for the worst case you will have O(256) as your space cost. If the alphabet grows to the UNICODE set, then the space complexity will be much worse in the worst scenario.
So in general its O(size of alphabet).
Since the used space is constant (26 letters/items in this case - or even if you used an array of 256 items) and not dependent on the length of the input(s), space complexity is O(1).
Also see this answer.

Browser crashing while loop JavaScript algorithm

Guys i'm trying to write an algorithm where I pass in a large string and let it loop through the string and whatever palindrome it finds, it pushes into array but for some reason my browser is crashing once i put in the while loop and I have no
function arrOfPalindromes(str) {
var palindromeArrays = []
var plength = palindromeArrays.length
// grab first character
// put that in a temp
// continue and look for match
// when match found go up one from temp and down one from index of loop
// if matched continue
// else escape and carry on
// if palendrome push into array
var counter = 0;
for (var i = 0; i < str.length; i++) {
for (var j = 1; j < str.length - 1; j++) {
if (str[i + counter] === str[j - counter]) {
while (str[i + counter] === str[j - counter]) {
console.log(str[j], str[i])
// append the letter to the last index of the array
palindromeArrays[plength] += str[i]
counter++
}
}
}
}
return palindromeArrays
}
var result2 = arrOfPalindromes('asdfmadamasdfbigccbigsdf')
console.log(result2)
Do not mention about the algorithm but the condition
while (str[i + counter] === str[j - counter])
Make your code crash. A little surprise but str[j+counter] when j+counter > str.length return undefined and the same as j-counter <0. There for, your while loop never end because of undefined === undefined.
Returning same sized array to handle nested palis.
ex: abxyxZxyxab => 00030703000 odd numbered nested palis.
ex: asddsa => 003000 even numbered pali.
ex: asdttqwe => 00020000 i dont know if this is a pali but here we go
smallest pali is 2 char wide so i start at index:1 and increment till str.len-1
for (var i = 1; i < str.length-1; i++) {
counter=0;
while(str[i]+1-counter == str[i]+counter || str[i]-counter == str[i]+counter) { // always true when counter is 0
// while (even numbered palis || odd numbered palis)
// IF counter is bigger than 0 but we are still here we have found a pali & middle of the pali is i(or i+0.5) &size of the pali is counter*2(or+1)
if(str[i]+1-counter == str[i]+counter){//even sized pali
res[i]=counter*2;
}else{//odd sized pali
res[i]=counter*2+1;
}
counter++;//see if its a bigger pali.
}
}
not super optimized while + if,else checks same stuff. These can be somehow merged. Maybe even even and odd can be handled without any checks.
You don't need to use three loops. You can do it with two for loops where one starts from the beginning and other one is from the end of the string.
Here we use array reverse() method to match palindromes.
Also I added additional minLength parameter and duplication removal logic to make it more nice.
function findPalindromes(str, minLength) {
var palindromes = [];
var _strLength = str.length;
for (var i = 0; i < _strLength; i++) {
for (var j = _strLength - 1; j >= 0; j--) {
if (str[i] == str[j]) {
var word = str.substring(i, j + 1);
//Check if the word is a palindrome
if (word === word.split("").reverse().join("")) {
//Add minimum length validation and remove duplicates
if(word.length >= minLength && palindromes.indexOf(word) === -1){
palindromes.push(word);
}
}
}
}
}
return palindromes;
}
var result = findPalindromes('asdfmadamasdfbigccbigsdf', 2)
console.log(result)

Trying to create a palindrome function that accounts for spaces

Okay so palindrome is a word that is the same spelled backwards. What if we want to take a phrase that is also the same backwards? So kook is one. race car is another one.
So I made one that doesn't account for spaces.
function isPal(string){
var l = string.length;
for (var i = 0; i < (l/2); ++i) {
if (string.charAt(i) != string.charAt(l - i - 1)){
return false;
}
}
return true;
}
This one works fine for words.
Now I'm thinking, push the string into an array, and split up each character into it's own string, then remove any spaces, and then run if (string.charAt(i) != string.charAt(string.length - i - 1)). So here's what I wrote but failed at..
function isPalindrome(string){
var arr = [];
arr.push(string.split(''));
for (i = 0; i < arr.length; i++){
if (arr[i] === ' '){
arr.splice(i, 1);
if I return arr, it still gives me the string with the space in it. How do I accomplish this? Thanks!
EDIT: Used the solution but still getting false on 'race car'
Here's what I got:
function isPalindrome(string){
var arr = string.split('');
for (i = 0; i < arr.length; i++){
if (arr[i] === ' '){
arr.splice(i, 1);
} else if (arr[i] != arr[arr.length - i - 1]){
return false;
}
}
return true;
}
where's my error?
Your problem is in the following line:
arr.push(string.split(''));
string.split('') returns an array. So, arr is actually an array with one entry it in (another array that contains your characters). Replace:
var arr = [];
arr.push(string.split(''));
with
var arr = string.split('');
and it should work as expected
Just check check the string without spaces:
function isPal(string){
string = string.split(" ").join(""); // remove all spaces
var l = string.length;
for (var i = 0; i < (l/2); ++i) {
if (string.charAt(i) != string.charAt(l - i - 1)){
return false;
}
}
return true;
}
isPal("a man a plan a canal panama"); // true
It seems much easier to just split into an array, reverse and join again to check if a word is a palindrome. If you want to ignore spaces, just remove all instances of spaces:
let word = 'race car';
let isPalindrome = (word) => {
let nospaces = word.replace(/\s/g, '');
return [...nospaces].reverse().join('') === nospaces;
}
Or non-es6:
var word = 'race car';
var isPalindrome = function(word) {
var nospaces = word.replace(/\s/g, '');
return nospaces.split('').reverse().join('') === nospaces;
}

Javascript: in a string, replace the capital letter and all letters following it

I have a bunch of strings, dateTable, lastName, redColor and I want to remove the capitalized letter and all letters after that, for example, remove Table, Name, and Color and leaving only date, last, and red. Any help is appreciated!
Can be done easily using .replace():
"dateTable".replace(/[A-Z].*/, "")
Iterate through the letters and match them to a regex. Then use the sub string to get the string from start to index of the first upper case letter.
var inputString = "What have YOU tried?";
var positions = [];
for(var i=0; i<inputString.length; i++){
if(inputString[i].match(/[A-Z]/) != null){
positions.push(i);
break;
}
}
alert(positions);
var res = str.substring(1, postions[0]);
res should be the intended string
Here is a method that will alert what you wanted - you can continue from there
Check it:
function checkWord(word) {
var foundFirst = false;
var lengthToChop = 0;
for (var i in word) {
foundFirst = /^[A-Z]/.test( word[i])
if (foundFirst){
lengthToChop = i;
break;
}
}
alert(word.substring(0, lengthToChop));
}
Simple fast non-regex loop that breaks at the first capital letter.
function strip(str) {
var i = -1, out = [], len = str.length;
while (++i < len) {
if (str.charCodeAt(i) >= 65 && str.charCodeAt(i) <= 90) break;
out.push(str[i]);
}
return out.join('');
}
DEMO
try this code. No regular expressions... it should work and is pretty simple. Here is the fiddle... https://fiddle.jshell.net/4ku3srjw/2/
Just replace the string for the variable word and you'll see it works for what you want
var word = "lastName";
for (var i = 0; i < word.length; i++) {
if (word[i] === word[i].toUpperCase()) {
var choppedWord = word.substr(0, i)
}
}
alert(choppedWord);
Hope it helps

Categories