Discussion, Questions, and Resources for Part 3 of the April 2018 Cohort (Intermediate Algorithm Scripting and JavaScript Algorithms and Data Structures Projects)

Steamroller was interesting. I spent quite a bit of time trying to figure out how to get through multiple nested arrays in JS. Google was surprisingly not as helpful as I would’ve hoped. Settled on a Recursive solution though mine was not quite right at first. I had to spend some additional time googling to finally get that one down.

Which is also my suggestion for others. Don’t be ashamed to use google and stack overflow to help you understand/solve a problem. At least in my viewpoint, in the programming world, this is not cheating and you should not feel bad for doing it, unless you copy/paste someone else’s code and don’t take the time to understand why it works. Of course, FCC even encourages this with Read-Search-Ask.

1 Like

I voted to extend - selfish reason, I moved into a new house. The last couple of weeks have been painting, packing, and a little unpacking. I haven’t started this module yet due to that :frowning: .I will be starting today or tomorrow. I do have a couple solutions already, but I’ll probably need the time.

1 Like

I want to join!..

I just finished up. If anyone has any questions along the way, please ask them!

For now, for those who attempt the cash register project, my hint is to remember the dangers of floating point math. This tripped me up several times because in floating point, 1.01 + 2.05 does not equal 3.06, it equals 3.05999999996. :frowning:

1 Like

I’m going to extend the deadline to July 31. I think this will give us more time to learn about the different ways to solve the algorithms and complete the projects. Thanks for participating in the poll!

2 Likes

Here are my first five solutions for the Intermediate Algorithm Scripting section and some commentary about my solutions and solutions I found in the fCC Guide:

Sum All Numbers in a Range

My first solution was the following, using recursion:

function sumAll(arr) {
  // Create new array to use in function. Sort array from highest to lowest.
  let newArr = arr.sort((a, b) => b - a);

  // If the difference between both numbers is zero, then return the first number in the array.
  if (newArr[0] - newArr[1] === 0) return newArr[0];

  // return the first number in the array plus the function again, but this time with the first number minus one.
  return newArr[0] + sumAll([newArr[0] - 1, newArr[1]]);
}

The fCC Guide entry for the Sum All Numbers in a Range challenge shows a solution that uses a for-loop, a solution using something called the Arithetic Progression summing formula, and the following solution that uses the spread operator (...) along with a for-loop, Math.min(), and Math.max():

function sumAll(arr) {
    var sum = 0;
    for (var i = Math.min(...arr); i <= Math.max(...arr); i++){
        sum += i;
    }
  return sum;
}

I like this final solution because it seems self-contained and not too complicated. The main part of this challenge is using the first and last numbers, and this solution does that in an elegant way. I think my solution over-complicates this challenge.

Diff Two Arrays

Here’s my solution, using filter(), arrow functions, the spread operator, and indexOf():

function diffArray(arr1, arr2) {
  let newArr = [];
  
  newArr.push(...arr2.filter(item => arr1.indexOf(item) === -1));
  newArr.push(...arr1.filter(item => arr2.indexOf(item) === -1));
  
  return newArr;
}

The fCC Guide entry for Diff Two Arrays has some interesting solutions that use includes(). The solution I like best first concatenates both arrays and then filters the concatenated array to only include items that are not in either array:

function diffArray(arr1, arr2) {
      return arr1.concat(arr2).filter(item => !arr1.includes(item) || !arr2.includes(item));
}

I moved everything into one line here, but if you want to see it broken out into multiple lines, click on the guide link above.

Seek and Destroy

My first solution used filter(), slice, and indexOf():

function destroyer(arr) {  
  return arr.filter(item => [...arguments].slice(1).indexOf(item) === -1);
}

However, after reviewing the above solutions in the guide, I tried includes() instead of indexOf() and I think it’s a little cleaner:

function destroyer(arr) {  
  return arr.filter(item => ![...arguments].slice(1).includes(item));
}

The fCC Guide entry for Seek and Destroy shows a solution using nested for-loops and one that’s close to my solution that uses includes(), but uses Array.from(arguments) which does the same thing as [...arguments].

Wherefore Art Thou

I think I cheated a little on this one. My solution doesn’t work for all possible test cases, but it passes all of the tests given for the challenge. I used Object.keys and filter():

function whatIsInAName(collection, source) {

  var arr = [];
  const keys = Object.keys(source);

  arr.push(...collection.filter(item => item[keys[0]] === source[keys[0]] && item[keys[1]] === source[keys[1]]));

  return arr;
}

If, for example, the source for the second to last test case is { "apple": 1, "bat": 2, "cookie": 3 } instead of { "apple": 1, "bat": 2 }, my solution will return [{ "apple": 1, "bat": 2 }, { "apple": 1, "bat": 2, "cookie":2 }] even though it should return an empty array since none of the objects match the source. This is because I’m only checking the first two keys and ignoring any more after that. Since it passed the tests and I was in a hurry (thinking the deadline for this section was June 30, 2018), I moved on.

The solutions in the fCC Guide for Wherefore Art Thou work correctly and as expected for all possible test cases using hasOwnProperty(). This solution in the guide that spoke to me the most:

function whatIsInAName(collection, source) {
 
  var srcKeys = Object.keys(source);

  return collection.filter(function (obj) {
    return srcKeys.every(function (key) {
      return obj.hasOwnProperty(key) && obj[key] === source[key];
    });
  });
}

With that, I was able to come up with the following similar solution using more ES6 syntax:

function whatIsInAName(collection, source) {

  const keys = Object.keys(source);

  return collection.filter(collectionItem => keys.every(key => collectionItem.hasOwnProperty(key) && collectionItem[key] === source[key]));

}

I hope I remember to use this filter() - every() combination in the future!

Spinal Tap Case

For this one, I had two solutions which basically combined split() and join() in various ways with regular expressions. Here’s one of them:

function spinalCase(str) {
  str.split(/[ _]/g).map(item => item.split(/(?=[A-Z])/g)).join('-').toLowerCase().replace(/[,]/g, '-');
}

Interestingly, the solutions in the fCC Guide for Spinal Tap Case don’t seem to pass all of the tests anymore.

@Velenir and the fCC Forum to the rescue. :smile: @Velenir’s solution passes all of the tests and uses a nice regular expression to do it in one line:

function spinalCase(str) {
  return str.split(/[-\s_]|\B(?=[A-Z])/).join('-').toLowerCase();
}

I’ll have to come back to this one when I have some time to truly understand how that regular expression is working. If anyone understands, please explain! Thanks.

Anyone come up with any other interesting ways to solve these challenges? :sunny:

1 Like

Nothing that different here - mostly just slight variations of the same algorithms. I am trying to stretch my ES6 wings a little

Sum All Numbers in a Range

Here I did cache values of larger, smaller to limit calls to Math.max and Math.min

function mySumAll(arr){
  const [smaller, larger] = [Math.min(...arr), Math.max(...arr)];
  let sum = 0;
  
  for(let i = smaller; i <= larger; i++){
    sum += i;
  }
  return sum;
}
Diff Two Arrays
function diffArray(arr1, arr2) {
  const halfDiff = (a1, a2) => a1.filter(el => !a2.includes(el));
  return [].concat(halfDiff(arr1, arr2)).concat(halfDiff(arr2, arr1));
}
Seek and Destroy

Got to try out rest parameters

function destroyer(arr, ...args) {
  return arr.filter(element => args.indexOf(element) === -1);
}
Wherefore art thou

I’ll have to read the guide. I did not see that we needed .hasOwnProperty - I was thinking it either passed or it failed the test for filter.

function whatIsInAName(collection, source) {
  var arr = [];
  // Only change code below this line
  const keys = Object.keys(source);
  arr = collection.filter( el => keys.every(key => el[key] === source[key]) );
  // Only change code above this line
  return arr;
}
Spinal Tap Case

My regex solution was not as sweet as Velenir’s solution. (I actually have to look at my notes to remember exactly what it does anyways). It did not occur to me to use .split(). I had forgotten that split takes a regex.

function spinalCase(str) {
  return str
  .replace(/([a-z])([A-Z])/g,'$1 $2')
  .replace(/[\s_]/g,'-')
  .toLowerCase();
}
2 Likes

@camperextraordinaire Thank you for your input here! I especially like your ES6 syntax for functions (const functionName = argument => {}) as that’s an excellent reminder of a different (better?) way to write them. :exclamation:

@alhazen1 I think you’re right! I remember thinking something similar but forgot about it. It seems to work without first checking to see if the property exists in all of my tests. Also, I like your Spinal Tap Case solution. Thanks for sharing! :sunny:

Here are my solutions for the next three, with some commentary and questions:

Pig Latin

My solution uses search() and slice(). Using ternary operators, it’s mostly a one-line solution. I don’t like the solutions in the guide because they seem more complicated than is needed.

function translatePigLatin(str) {
  // Find the index of the first vowel
  let vowelLocation = str.search(/[aeiou]/);

  /* If the index of the first vowel is at the beggining of
     the word `(vowelLocation === 0)`, return the string with 'way'
     added to the end.

     If the string has no vowels `vowelLocation === -1`, return
     the string with 'ay' added to the end.

     If the first vowel is somewhere else, slice the string
     at the index of the first vowel leaving all of the
     letters of the string following and including the first vowel
     `str.slice(vowelLocation)`. Then, add the letters before the
     first vowel to the end of the sliced string 
     `str.slice(0, vowelLocation)` along with 'ay' at the very end.
  */
  return vowelLocation === 0 ? str + 'way' : 
         vowelLocation === -1 ? str + 'ay' : 
          str.slice(vowelLocation) + str.slice(0, vowelLocation) + 'ay';
}
Search and Replace

For this one, I was able to solve it using slice() and replace(). The solutions in the guide were either overly complicated or similar to what I came up with but not in one line.

function myReplace(str, before, after) {
  return before[0].toUpperCase() === before[0] ? str.replace(before, after[0].toUpperCase() + after.slice(1)) : str.replace(before, after);
}
DNA Pairing

I did this one the easy way, by checking for every possible situation and returning the appropriate result. I used split() and map().

function pairElement(str) {
  return str.split('').map(element => element === 'A' ? ["A", "T"] : element === 'C' ? ["C", "G"] : element === 'G' ? ["G", "C"] : ["T", "A"]);
}

After looking at the fCC Guide for DNA Pairing, I was able to create the following which levarages an object and is much cleaner, in my opinion. It’s based on the Intermediate Solution in the guide:

const pairElement = str => {
      const dnaMap = {T:'A', A:'T', G:'C', C:'G'};
      return str.split('').map(element => [element, dnaMap[element]]);
}

I think this can be even more simplified by removing the dnaMap variable:

const pairElement = str => str.split('').map(element => [element, {T:'A', A:'T', G:'C', C:'G'}[element]]);

Does anyone know if creating a temporary object like this is significantly different from storing it in a variable?

How did others solve these? :sunny:

1 Like

Here are the next three. :sunny:

Missing Letters

I matched the string to a string containing every letter of the alphabet, then looped through each letter and placed the missing letter in a result variable.

function fearNotLetter(str) {
  const letters = "abcdefghijklmnopqrstuvwxyz";
  let letterArr = letters.split('').splice(letters.indexOf(str[0]), str.length + 1);
  let counter = 0;
  let result = [];

  letterArr.map(letter => {
    if (str[counter] !== letter) result.push(letter);
    counter++;
  });

  return result[0];
}

The fCC Guide for Missing Letters compares letters using character codes with charCodeAt():

function fearNotLetter(str) {
  for(var i = 0; i < str.length; i++) {
    var code = str.charCodeAt(i);

    /*
       If the current character code does not equal the current letter's
       character code, then return the previous character code.
     */
    if (code !== str.charCodeAt(0) + i) {
      return String.fromCharCode(code - 1);
    }  
  }
  return undefined;
}

It’s less code and doesn’t need all of the variables that my solution needs. From that, I was able to come up with this using filter():

function fearNotLetter(str) {
  let firstCharacter = str.charCodeAt();
  
  let notEqual = str.split('').filter((letter, letterIndex) => letter !== String.fromCharCode(firstCharacter + letterIndex))[0];

  return notEqual === undefined ? undefined : String.fromCharCode(notEqual.charCodeAt() - 1);
}

I’m not sure which way is best. They all work.

Sorted Union

I solved this one using map(), Set(), and the spread operator:

function uniteUnique(arr) {
  let allArrays = [];
  [...arguments].map(insideArr => allArrays.push(...insideArr));

  return [...new Set(allArrays)];
}

Apparently, this can be done with even less code, according to the fCC Guide on Sorted Union:

function uniteUnique(arr) {

  //make an array out of arguments and flatten it (using the spread operator)
  const args = [].concat(...arguments);

  // create a Set
  return [...new Set(args)];
}
Convert HTML Entities

I used regular expressions and replace(), which almost matches the intermediate code solution in the fCC Guide for Convert HTML Entities:

function convertHTML(str) {
  return str.replace(/&/g, '&amp;')
            .replace(/>/g, '&gt;')
            .replace(/</g, '&lt;')
            .replace(/'/g, '&apos;')
            .replace(/"/g, '&quot;');
}

The guide also has a solution that uses an object. I sensed this was possible and attmepted it, but didn’t solve it that way. I did modify the guide’s solution to use an arrow function:

function convertHTML(str) {
  const htmlEntities={
    '&':'&amp;',
    '<':'&lt;',
    '>':'&gt;',
    '\"':'&quot;',
    '\'':"&apos;"
  };

  return str.split('').map(entity => htmlEntities[entity] || entity).join('');
}

Either way seems to work. It’s trivial to add more properties/conditions for both solutions, however, the object-oriented solution seems to be more flexible.

not much different on the first 3 challenges, but I’m having a tough time with the 4th - wherefore art though… :sob:

@nvrqt03 It took me a while too. You’ll get it. :+1: It might help to explain how you think you can solve it here and we can help fill in gaps.

1 Like

ok I was able to figure it out with some hints/help lol -

the logic was to use filter to filter out what I don’t want. so filtering the collection by object, I made a loop to iterate through the arrays. what initially made it difficult was the Object.keys(source) - I didn’t know how to iterate through the collection and compare to source. the Object.keys takes the source and makes it an array. Now I can get started.

I filtered for what I don’t want - if the obj does not have its own property/key or its not the same value, then I ignore it. otherwise return true.

function whatIsInAName(collection, source) {
  // What's in a name?
 
  // Only change code below this line
  var keys = Object.keys(source);
  
  return collection.filter(function(obj) {
    for (var key of keys) {
      if (!obj.hasOwnProperty(key) || obj[key] !== source[key]) {
        return false;
      }
    }
    return true;
  })
  
  
  // Only change code above this line

}
1 Like

Here are the next five solutions:

Sum All Odd Fibonacci Numbers

My first attempt at this resulted in the following:

function sumFibs(num) {
  let fibs = [1, 1];
  let counter = 0;

  while (counter <= num) {
    fibs.push(fibs[counter] + fibs[counter + 1]);
    counter++;
  }

  let subTotal = 0;

  fibs.map(item => {
    if (item <= num) {
      if (item %2 !== 0) subTotal += item;
    } 
  });

  return subTotal;

}

First, this creates a list of fibonacci numbers up to and including the given number. Then it checks each of those numbers to see if it is less than the number given and if the it is odd. If it is less than the given number and odd, it adds it to the subtotal variable. This feels more straightforward, yet possibly not as efficient, as the basic solution in the fCC Guide for Sum All Fibonacci Numbers. The intermidiate solution in the guide lead me to this, using reduce(), which is probably more appropriate here:

function sumFibs(num) {
  let fibs = [1];
  let counter = 1;

  while (counter <= num) {
    fibs.push(counter);
    counter = fibs[fibs.length - 1] + fibs[fibs.length - 2];
  }

  return fibs.reduce((prev, cur) => cur % 2 !== 0 ? prev + cur : prev)
}
Sum All Primes

This one was difficult for me, specifically the part of trying to get a list of prime numbers. I used a StackOverflow answer to generate the primes – which is similar to the basic solution in the fCC Guide – and then it was a matter of getting the sum of those numbers:

function sumPrimes(num) {
 let tempArr = [];
  let i, j, primes = [];
    for (i = 2; i <= num; ++i) {
        if (!tempArr[i]) {
            // i has not been marked -- it is prime
            primes.push(i);
            for (j = i << 1; j <= num; j += i) {
                tempArr[j] = true;
            }
        }
    }
    return primes.reduce((prev, next) => prev + next);
}

The intermediate and advanced solutions in the fCC Guide for Sum All Primes are interesting and worth looking at.

Smallest Common Multiple

I needed to rely on StackOverflow again for this one, this time to figure out the greatest common denominator.

function smallestCommons(arr) {
  let sortedArr = arr.sort((a, b) => a - b);
  let fullArr = [];
  
  // Create an array with all necessary numbers
  let counter = sortedArr[0];

  while (counter <= sortedArr[1]) {
    fullArr.push(counter);
    counter++;
  }

  // Borrowed from https://stackoverflow.com/a/17136781
  function gcd(...fullArr) {
    return fullArr.reduce((acc, cur) => cur === 0 ? acc : gcd(cur, acc % cur));
  }

  return fullArr.reduce((acc, cur) => Math.abs(acc * cur) / gcd(acc, cur));
}
Drop It

Now that we’re back to less math-heavy challenges, I was able to solve this challenge using map(), indexOf(), and slice():

function dropElements(arr, func) {
  let tempArr = arr.map(item => func(item));
  return tempArr.indexOf(true) !== -1 ? arr.slice(tempArr.indexOf(true)) : [];
}

This first creates a temp array with true/false values based on whether or not the item in the given array is true or false in the given function. Then, if the temp array has a true value, it finds the index of the first ‘true’ value and slices the given array at that index, returning everything thereafter. If the temp array doesn’t have a true value, it returns an empty array.

The fCC Guide for Drop It has some other ways to solve this challenge.

The basic solution checks the first element of the given array with the given function. If the function returns true, the loop stops and the array is returned from that point. If the function returns false, the first element of the array is removed using shift():

function dropElements(arr, func) {
  var times = arr.length;
  for (var i = 0; i < times; i++) {
    if (func(arr[0])) {
      break;
    } else {
      arr.shift();
    }
  }
  return arr;
}

The intermediate solution is short and sweet. It relies on slice() and the findIndex() function. The findIndex() function returns the index of the element that returns true from the given function. findIndex() will return -1 if it doesn’t find an element that returns true, so the ternary operator is used to check for that and either return a sliced array from the first true element to the end of the array or a sliced array that returns an empty array:

function dropElements(arr, func) {
  return arr.slice(arr.findIndex(func) >= 0 ? arr.findIndex(func): arr.length, arr.length);
}

The advanced solution is also short and sweet, but this time using a while loop and shift:

function dropElements(arr, func) {
  while(arr.length > 0 && !func(arr[0])) {
    arr.shift();
  }
  return arr;
}

This is similar to the basic solution in that it removes the first element if it’s false, but it uses a while loop and doesn’t need to use break to end the loop when an element that’s true is found.

Steamroller

My initial solution is pretty close to the basic solution in the fCC Guide for Steamroller in that I create a function and then use recursion to keep sending arrays into that function until there are no arrays left:

function steamrollArray(arr) {

  let result = [];

  const tempFunc = (inArr) => {
    inArr.map(item => Array.isArray(item) ? tempFunc(item) : result.push(item));
  }

  tempFunc(arr);  
  return result;
}

The intermediate solution is really good. It takes advantage of the spread operator as well as some(). It first initializes a variable named flat that flattens the first level of the array. Then, it uses some() to check for the existence of another array. If another array exists, it recursively runs the function again. If and when another array doesn’t exist, it returns the flattened array in the flat variable.

function steamrollArray(arr) {
  let flat = [].concat(...arr);
  return flat.some(Array.isArray) ? steamrollArray(flat) : flat;
}

:sunny:

These definitely made me think. I had to use a pencil and my brain (at the same time no less!) to get through some of these.

Now that I’ve come down from from my functional programming high (some) I’m finding that some things still look clearer to me with classic for…loops, if…else statements, etc.

Sum All Odd Fibonacci Numbers
function sumFibs(num) {
  const prev = [0,1]
  let accum = 0;
  let next = 1;
  
  while(next <= num){
    
    if(next%2){ accum += next; }// accumulate odds
 
    next = prev[0] + prev[1];
    prev.push(next)
    prev.shift();
  } 
  return accum;
}
Sum All Primes

This makes (and remakes) what can be a huge range array but still ended up being the fastest of my attempts. The larger the number, the larger the range. Yet, the larger the number the more this solution left my others in the dust even with similar number of iterations. Go figure.

Watching console.log output clued me in that I could stop looking for primes at the square root of the given number.


function sumPrimes1(num) {
	// range 2 to num inclusive (1 not prime, skip 1)
	let range = [];

	for (let i = 2; i <= num; i++) {
		range.push(i);
	}

	let stop = Math.ceil(Math.sqrt(num));

	// remove all non-prime from range
	for (let i = 0; range[i] <= stop; i++) {
		const number = range[i];
		//console.log(range[i]);  //always the next prime!
		// remove multiples of range[i] from range
		range = range.filter(el => el % number || el === number);
	}
	// sum all primes
	return range.reduce((acc, cur) => (acc += cur));
}
Smallest Common Multiple

Most important lesson I learned from this challenge is sometimes pencil and paper is the best problem solving tool.

How it works

In the case of [1,5]
LCM of 4 and 5 try 5 - no, try 10 - no, 15 - no, 20 - YES!
LCM of 4 and 5 is 20 so any LCM for range must be a mult of 20 so only check multiples of 20 going forward
LCM of 20 and 3 try 20 - no, try 40 - no, try 60 - yes
LCM of 20 and 3 is 60 so LCM for range must be mult of 60 so only check multiples of 60
LCM of 60 and 2 is 60
LCM of 60 and 1 is 60

function smallestCommons(arr) {
  let smaller = Math.min(...arr);
  let LCM, nextLCM, larger;
  nextLCM = LCM = larger = Math.max(...arr);

  for (let i = larger; i > smaller; i--) {
    while (nextLCM % (i - 1) !== 0) {
      nextLCM += LCM;
    }
    LCM = nextLCM;
  }
  return LCM;
}
Drop It
function dropElements(arr, func) {
  const index = arr.findIndex(func);
  return index !== -1 ? arr.slice(index) : [] ;
}
Steamroller

I kept trying to come up with some array method magic that would make this elegant but nothing was as clear to my eye as a plain Jane if…else

function steamrollArray(arr) {
	var flattened = [];
	for (let elem of arr) {
		if (Array.isArray(elem)) {
			flattened = flattened.concat(steamrollArray(elem));
		} else {
			flattened.push(elem);
		}
	}
	return flattened;
}
1 Like

@nvrqt03, @camperextraordinaire and @alhazen1, thanks for sharing! I’ve learned a lot from all of your solutions. :fireworks: Reviewing code that more experienced people have written has proven to be really helpful for me.

Here are my final five algorithms:

Binary Agents

I used split(), map(), String.fromCharCode(), parseInt(), and join() for my solution. First the string is split by spaces to create an array of binary numbers. Then, I loop through each binary number using map() and convert each binary number into a letter. Then, join them back togther:

function binaryAgent(str) {
  return str.split(' ').map(binary => String.fromCharCode(parseInt(binary.toString(), 2))).join('');
}

The advanced solution in the fCC Guide for Binary Agents does the same thing, but in a different order. I cleaned it up a little with arrow functions:

const binaryAgent = str => String.fromCharCode(...str.split(" ").map(char => parseInt(char, 2)));

I like this a little better. I always forget that built-in functions can take functions as arguments, so this solution reminds me of this. Also, this is a great use of the spread operator, saving the need for join(). Nice!

Everything Be True

I started with a solution that used Boolean() and indexOf():

const truthCheck = (collection, pre) => collection.map(item => Boolean(item[pre])).indexOf(false) === -1 ? true : false;

Then, I remembered from previous solutions I’ve seen that some() might fit well here, and came up with this which is a little smoother:

const truthCheck = (collection, pre) => collection.some(item => Boolean(item[pre]) === false) ? false : true;

But, then in the fCC Guide for Everything Be True, the advanced solution shows an even more straightforward way to solve this challenge, using only only every(). This checks to see if all elements in the given array pass the test, in this case, all we’re looking for is whether the value is truthy:

const truthCheck = (collection, pre) => collection.every(obj => obj[pre]);
Arguments Optional

I used recursion for this one:

const addTogether = (...args) => {
  if (typeof args[0] !== 'number') return;

  return args.length === 1 ? item => addTogether(args[0], item) : typeof args[1] === 'number' ? args[0] + args[1] : undefined;
}

After looking through the fCC Guide for Arguments Optional, I like the recursive way of doing this.

Make a Person

This is my solution:

const Person = function(firstAndLast) {
  
  let [firstName, lastName] = firstAndLast.split(' ');

  this.getFullName = () => `${firstName} ${lastName}`;
  this.getFirstName = () => firstName;
  this.getLastName = () => lastName;
  this.setFirstName = (first) => firstName = first;
  this.setLastName = (last) => lastName = last;
  this.setFullName = (firstAndLast) => [firstName, lastName] = firstAndLast.split(' ');

};

If there are better/more efficient/more readable ways to do this, please share and explain why another way is better. Thanks!

Map the Debris

My solution loops through all of the given objects and creates an orbitalPeriod variable using the formula found on Wikipedia (also in the fCC Guide for Map the Debris) for each object. Then, it deletes the avgAlt property and creates a new orbitalPeriod property for each object:

const orbitalPeriod = arr => {
  var GM = 398600.4418;
  var earthRadius = 6367.4447;
   arr.map(item => {
    const orbitalPeriod = Math.round(2 * Math.PI * Math.sqrt(Math.pow(earthRadius + item.avgAlt, 3) / GM));
    delete item.avgAlt;
    item.orbitalPeriod = orbitalPeriod;
  });
return arr;
};

My solution is similar to the advanced solution in the guide, but the guide uses forEach() instead of map(). What is the advantage of using forEach() over map() in this case?

That’s it for the intermediate algorithms for me. Now on to the projects! :sunny:

I finished all of the Javascript projects! :fireworks: Here’s the first one with some explanation and a bunch of different ways to solve it:

Palindrome Checker

I started this one out by stripping the given string of all punctation, one symbol at a time. This resulted in a relatively messy regular expression:

const palindrome = str => {
  /* Remove (in order): dashes (-), spaces (\s), underscores (_),
     commas (,), left parenthesis [\(], right parenthesis [\)],
     periods (.), colons (:), bars (\|), and forward slashes (\/).
  */
  let strippedStr = str.replace(/[-\s_,\(\)\.:\|\/]/g, "").toLowerCase();

  return strippedStr === strippedStr.split('').reverse().join('');;
}

In looking at the fCC Guide for Palindrome Checker, I noticed the use of the much simpler \W regular expression and now my solution looks like this:

const palindrome = str => {
  let strippedStr = str.replace(/[\W_]/g, "").toLowerCase();
  return strippedStr === strippedStr.split('').reverse().join('');
}

Then, after reading about the advanced solution in the guide, I learned about using continue and some of the reasons why it might be a good idea to not use so many functions chained together. I’m not sure if I’ll ever need to use this solution for performance reasons, but it’s at least nice to know about in case I’m asked something similar in an interview or something like that:

const palindrome = str => {
  let beg = 0;
  let end = str.length - 1;

  while (end > beg) {
    if (str[beg].match(/[\W_]/)) { beg++; continue; }
    if (str[end].match(/[\W_]/)) { end--; continue; }
    if (str[beg].toLowerCase() !== str[end].toLowerCase()) return false
    beg++;
    end--;
  }
  return true;
}

The Two Ways to Check for Palindromes in JavaScript has a good explanation of some solutions.

Here’s an interesting solution that I found in the comments at Palindrome check in Javascript on Stack Overflow. I modified it a little to make it pass the fCC tests:

const palindrome = (str) => {
  let strippedStr = str.replace(/[\W_]/g, '').toLowerCase();
  return strippedStr === [...strippedStr].reverse().join``;
}

I like the use of the spread operator [...strippedStr] instead of split(). I’ve never seen join`` before. I think this is called a Tagged Template. In this case, I think, the tag is join() and it’s being passed an empty string (''). If someone knows more about this, please share as I’m not sure if this is what’s happening. Is this much better than join('')?

Anyone else finish this and want to share something interesting about their solution?

:sunny:

Here’s my explanation and code for the next JavaScript project:

Roman Numeral Converter

I started with a bunch of if and else if statements, using an object for the roman numerals:

const convertToRoman = num => {
    const romanNumerals = {
            1: "I",
            4: "IV",
            5: "V",
            9: "IX",
           10: "X",
           40: "XL",
           50: "L",
           90: "XC",
          100: "C",
          400: "CD",
          500: "D",
          900: "CM",
         1000: "M"
        };

    let result = "";

    if (num > 1 && num < 4) {
        result += romanNumerals[1] + convertToRoman(num - 1);
    }

    else if (num > 5 && num < 9) {
        result += romanNumerals[5] + convertToRoman(num - 5);
    }

    else if (num > 10 && num < 40) {
        result += romanNumerals[10] + convertToRoman(num - 10);
    }

    else if (num > 40 && num < 50) {
        result += romanNumerals[40] + convertToRoman(num - 40);
    }

    else if (num > 50 && num < 90) {
        result += romanNumerals[50] + convertToRoman(num - 50);
    }

    else if (num > 90 && num < 100) {
        result += romanNumerals[90] + convertToRoman(num - 90);
    }

    else if (num > 100 && num < 400) {
        result += romanNumerals[100] + convertToRoman(num - 100);
    }

    else if (num > 400 && num < 500) {
        result += romanNumerals[400] + convertToRoman(num - 400);
    }

    else if (num > 500 && num < 900) {
        result += romanNumerals[500] + convertToRoman(num - 500);
    }

    else if (num > 900 && num < 1000) {
        result += romanNumerals[900] + convertToRoman(num - 900);
    }

    else if (num > 1000) {
        result += romanNumerals[1000] + convertToRoman(num - 1000);
    }

    else {
        result = romanNumerals[num];
    }

    return result;

}

Even though that works, I noticed there was a lot of repeating code. However, I wanted to finish the other projects before refactoring. Once I finished the projects, I revisited this code and refactored into the following:

const convertToRoman = num => {
    const romanNumerals = {
            1: "I",
            4: "IV",
            5: "V",
            9: "IX",
           10: "X",
           40: "XL",
           50: "L",
           90: "XC",
          100: "C",
          400: "CD",
          500: "D",
          900: "CM",
         1000: "M"
        };

    let dif = 0;

    if (num <= 0) return;

    if (romanNumerals[num] !== undefined) {
      return romanNumerals[num];
    }
    else {
      while (romanNumerals[num] === undefined) {
        num--;
        dif++;
      }
    }

    return romanNumerals[num] + convertToRoman(dif);
}

This is better, but I’m sure there are better ways to solve this. In looking at the fCC Guide for Roman Numeral Converter, the basic solution shows a simpler, more straightforward way to solve this (updated with ES6 syntax):

const convertToRoman = num => {

  const decimalValue = [ 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 ];
  const romanNumeral = [ 'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I' ];

  let romanized = '';

  romanNumeral.map((numeral, index) => {
    while (decimalValue[index] <= num) {
      romanized += romanNumeral[index];
      num -= decimalValue[index];
    }
  });

  return romanized;
}

The other two solutions in the guide seem more complicated than they have to be. However, maybe there are performance gains from using those methods, I don’t know.

There are some interesting ways to solve this problem on the Convert a number into a Roman Numeral in javaScript Stack Overflow question. I found the following solution listed on that page to be interesting because it uses recursion:

function convertToRoman(num) {
    const decimals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
    const roman = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'];

    for (let i = 0; i < decimals.length; i++) {
        if(num < 1)
            return "";       

        if(num >= decimals[i]) {
            return roman[i] + convertToRoman(num - decimals[i]);        
        }
    }
}

I’m not sure if this solution is efficient or if it uses best practices, but it works.

The following solution from felixvolny.com also uses recursion, but uses the JavaScript Map Object instead of an object or arrays:

const map = new Map();
map.set('M', 1000);
map.set('CM', 900);
map.set('D', 500);
map.set('CD', 400);
map.set('C', 100);
map.set('XC', 90);
map.set('L', 50);
map.set('XL', 40);
map.set('X', 10);
map.set('IX', 9);
map.set('V', 5);
map.set('IV', 4);
map.set('I', 1);

const convertToRoman = (number) => {
  if (number === 0) {
    return '';
  }

  for (const [roman, integer] of map.entries()) {
    if (number >= integer) {
      return roman + convertToRoman(number - integer);
    }
  }
};

Read through the post at felixvolny.com for a great explanation of the code. Felix has some interesting solutions for other fCC algorithms as well.

I found a solution on selftaughtjs.com. It uses a little math, but the num%decimal[i] < num can be exchanged for decimalValue[index] <= num, which makes it identical to the basic solution in the fCC Guide. I like the detailed explanation. It describes what Roman Numerals are, how the solution was created, and how it works.

There’s also an exaplanation of how to convert from Roman Numerals to Arabic Numerals on the selftaughtjs.com page.

Maria Campbell has a great write up of the problem and solution as well, if one wants another explanation.

As mentoined on the selftaughtjs.com page, this Roman Number converter by Bert McLees on jsfiddle.net goes far deeper than the fCC requirements. It’s well-documented in case one’s curious how to do something like this.

Thoughts and criticisms welcome. :sunny:

1 Like

This is my solution and some other solutions for the third JavaScript project:

Caesars Cipher

Here’s my solution. It’s not that great, but it works.

const rot13 = str => {
  
  return [...str].map(char => {
    let charCode = char.charCodeAt();
    let decode = 0;

    if (charCode >= 65 && charCode <= 90) {
      decode = charCode - 13;
      if (decode < 65) decode = 90 - (13 - (charCode - 64));
    }
    else {
      return char;
    }

    return String.fromCharCode(decode);
  }).join('');
}

The basic solution in the fCC Guide for Caesar’s Cipher helped me create this solution:

const rot13 = str => {
  
  return [...str].map(char => {
    let charCode = char.charCodeAt();

    if (charCode < 65 || charCode > 90) {
      return char;
    }

    else if (charCode < 78) {
      return String.fromCharCode(charCode + 13);
    }

    else {
      return String.fromCharCode(charCode - 13);
    }
  }).join('');
}

It’s a little cleaner, only having charCode + 13 and charCode - 13 instead of the much more cumbersome decode = 90 - (13 - (charCode - 64)). The intermediate and advanced solution lead me to:

const rot13 = str => [...str].map(char => char.match(/[A-Z]/) ? String.fromCharCode(char.charCodeAt() % 26 + 65) : char).join('');

This is a nice one-line solution and the formula becomes a little more complicated. It enables me to only use one formula instead of multiple, like the above solution. It also uses a regular expression, which I hadn’t thought of using (but should have!). Then, thinking about the replace() string method shown in the advanced solution, I got this:

const rot13 = str => str.replace(/[A-Z]/g, char => String.fromCharCode(char.charCodeAt() % 26 + 65));
1 Like

This is my solution for the fourth JavaScript project along with some commentary and other solutions:

Telephone Number Validator

This was my first attempt. It’s a long and complicated regular expression. It passes all of the tests, but it doesn’t respond correctly for every possible input. For example, 5555555555 5555555555 – two sequences of 10 numbers returns true instead of false:

function telephoneCheck(str) {
  let regex = /^\d{10}(?!\d)|(^1(-|\s)\d{3}|^\(\d{3}\)|}|^1(-|\s|)\(\d{3}\)|^\d{3})(\s|-|)\d{3}(-|\s)\d{4}/;
  return regex.test(str);
}

What I didn’t use in my solution was the ? which matches either zero or one times. This would’ve helped the situations where sometimes there’s a space and sometimes there’s not, but it’s still valid. The basic solution in the fCC Guide for Telephone Number Validator uses the ? successfully. The guide has great explanations for this regular expression:

function telephoneCheck(str) {
   var regex = /^(1\s?)?(\(\d{3}\)|\d{3})[\s\-]?\d{3}[\s\-]?\d{4}$/;
   return regex.test(str);
}

The intermediate solution in the fCC Guide doesn’t pass all of the tests because it returns true for 555-5555 and 5555555. However, it is more comprehensive and returns true for phone numbers like 555.555.5555 and (555)555.5555 using dots instead of dashes or spaces. Instead of pasting it here, I added checking for periods and +1 to the basic solution to make that solution more comprehensive:

function telephoneCheck(str) {
   var regex = /^(\+?1\s?)?(\(\d{3}\)|\d{3})[\s-.]?\d{3}[\s-.]?\d{4}$/;
   return regex.test(str);
}

This solution on Stack Overflow (Comprehensive Regex for Phone Number Validation) is similar to the intermediate solution in the guide and matches every combination of U.S. phone number. It also fails the fCC tests for 555-5555 and 5555555 by returning true instead of false:

function telephoneCheck(str) {
   var regex = /^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/;
   return regex.test(str);
}