JS Challenges: Wherefore art thou and Spinal Tap Case

CodeDraken
Dev Compendium
Published in
7 min readOct 27, 2019

--

Two more intermediate JavaScript challenges from FreeCodeCamp.

Prerequisites

Basic JavaScript skills and knowledge of regular expressions would help.

Wherefore art thou

Challenge Description:

Make a function that looks through an array of objects (first argument) and returns an array of all objects that have matching name and value pairs (second argument). Each name and value pair of the source object has to be present in the object from the collection if it is to be included in the returned array.

For example, if the first argument is [{ first: “Romeo”, last: “Montague” }, { first: “Mercutio”, last: null }, { first: “Tybalt”, last: “Capulet” }], and the second argument is { last: “Capulet” }, then you must return the third object from the array (the first argument), because it contains the name and its value, that was passed on as the second argument.
https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/wherefore-art-thou

Understand the Problem and Examples

We are tasked with making a searching algorithm that searches an array of objects for one or more objects that contain our target key/value pairs.

Our Function Should:

  • Take in data in the form of an array of objects (collection)
  • Take in a target object (source)
  • Find any number of objects that match our target in the collection
  • Return any found objects in an array
  • If we found none then return an empty array

Examples:

whatIsInAName([{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], { last: "Capulet" }) should return [{ first: "Tybalt", last: "Capulet" }]

whatIsInAName([{ "apple": 1 }, { "apple": 1 }, { "apple": 1, "bat": 2 }], { "apple": 1 }) should return [{ "apple": 1 }, { "apple": 1 }, { "apple": 1, "bat": 2 }]

whatIsInAName([{"a": 1, "b": 2, "c": 3}], {"a": 1, "b": 9999, "c": 3}) should return []

Pseudo Code

Now that we have a good idea of what our function should do, we can write out the steps.

whatIsInAName pseudo code

Research and Solve

If you don’t have all the JavaScript methods memorized, it’s not too hard to figure out relevant documentation pages to check. As we know our data is an Array of Objects, we should check the documentation for Array/Object methods. Another page we might want to check is the page on loops.

If you haven’t solved the challenge already, then take a moment to scroll through those pages and try the challenge now.

Loop Solution:

One way to solve it is to start with an empty array to hold our matching objects, then add to it by looping over our collection, and the source keys, checking if they’re in the collection[i] item. If one of the source object’s keys is not in the collection[i] item or their values are not the same, then we stop and move onto the next item.

whatIsInAName Loop Solution

Review and Refactor

The loop solution is fine (it looks long with all the comments) but let's look at another way to solve it.

Using Array / Object Methods:
You can find these methods in the documentation pages listed above. If something doesn’t make sense, then ask questions in the comments.

whatIsInAName .filter, .every solution

Benefits of this solution over the for-loop solution: (they are minor)

  • Don’t need temporary holder variables i.e. match and found
  • It can be more readable — you see quickly that you’re filtering collection and checking that every key/value from source exists on the objects.
  • You only access the source object once

Spinal Tap Case

This challenge sounds a lot more ominous than it is…

Convert a string to spinal case.
Spinal case is all-lowercase-words-joined-by-dashes.
spinalCase("This Is Spinal Tap") should return "this-is-spinal-tap"

https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/spinal-tap-case

Understand the Problem and Examples

This challenge looks deceptively simple, however, an important part of understanding the problem is knowing the edge cases.

Our Function Should

  • Take in a String
  • Figure out what parts of the String are words
  • Lowercase the word
  • Return the words joined by dashes

Examples

spinalCase("This Is Spinal Tap") should return "this-is-spinal-tap"

spinalCase("thisIsSpinalTap") should return "this-is-spinal-tap"

spinalCase("AllThe-small Things") should return "all-the-small-things"

spinalCase("The_Andy_Griffith_Show") should return "the-andy-griffith-show"

Okay, so it’s not as simple as converting spaces to dashes because the strings are not guaranteed to have spaces. We have to look at all the examples to find a pattern.

  • All non-letter characters are replaced with a dash
  • If an uppercase letter comes after a lowercase letter then this is the start of a word and we need to add a dash between them.

Pseudo-Code

spinalCase pseudo code

Research and Solve

To implement the above pseudo-code we need to know how to work with loops and strings.

We can make use of break and continue to skip to the next character. And do char.toUpperCase() === char to check if a character is uppercase.

Loop Solution:

spinalCase loop solution

Review and Refactor

Okay, that loop solution was atrocious, terrible, ugly, and a headache to navigate. There is a better way to work with Strings in JavaScript and most languages, actually. It’s called Regular Expressions. This isn’t a RegExp tutorial, but I’ll explain a few solutions using them.

When working with regular expressions I strongly recommend using this online tool named Regex101. It shows live what your RegExp will match, explains how it works and has a quick reference guide.

We can use the pattern we found earlier to define our RegExp:

All non-letter characters are replaced with a dash

For replacing we can use String.replace(RegExp, replacement) and to match all non-letters we can use [^a-zA-Z] which matches everything that’s not lowercase a to z or uppercase a to z. (try playing with this on Regex101)

function spinalCase(str) {
// replace all non-letters with a dash
str = str.replace(/[^a-zA-Z]/g, '-')
...
}

If an uppercase letter comes after a lowercase letter then this is the start of a word and we need to add a dash between them.

We can make use of the same character range selector [a-z] and [A-Z] to select lowercase/uppercase characters. It would look like this: /[a-z][A-Z]/g and select every lowercase -> uppercase pair of characters.

For example, if we use String.match(regexp) we can see what we get:

'thisIsSpinalTap'.match(/[a-z][A-Z]/g) → [“sI”, “sS”, “lT”]

We could then do something with it… or there’s an even better way, we can use capture groups that let us use each part individually. All we have to do is add a pair of parentheses around a part of our RegExp we want to capture.

i.e. /([a-z])([A-Z])/g will separate the lowercase and uppercase characters into groups.

The real benefit is that we can access these groups when we use String.replace by using a special keyword, $ followed by a number representing the group’s order. i.e. [a-z] is first $1 and [A-Z] is second $2. Let’s just see the code and it will make more sense.

Read more about groups on the docs here

spinalCase RegExp solution

First, we replace the non-letters with a dash. Then we select any lowercase → uppercase pairs, put them in capture groups, and add a dash between them (not changing anything else). Finally, we lowercase the entire string and return it.

If none of that made any sense don’t worry regular expressions are hard and even harder to explain. But once you understand them, they become a powerful tool.

Pros of this solution

  • Much shorter and easier to write

Cons

  • Regular Expressions can be cryptic and hard to understand at a glance

There are many more ways to solve this challenge with RegExp and I encourage you to play with it and learn more about regular expressions.

Thanks for Reading!

If you have questions or would like to share your solutions, then leave a comment below.

More Challenges

--

--