Binary String to Text

Ever wanted to know how to write something in binary?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function textToBinaryString(text) {
let binaryArray = [];
for (let i = 0; i < text.length; i++) {
// string.charCodeAt(index) will give you the UTF-16 code point of a character at a given index
let charCode = text.charCodeAt(i);
// Number.toString(radix) takes a number and returns the string equivalent of that number in the provided base (radix)
let binary = charCode.toString(2);
// Since we know that we're going to be working with just letters a-zA-Z, we know we only care about up to 8 bits of data, so we can pad the start of the string so that a binary letter is represented by 8 bits
let binaryString = binary.padStart(8, 0);
binaryArray.push(binaryString);
}
// Now in order to get a string rather than the array, we need to join the elements of the array together. Since we want to differentiate our letters so they are more easily readable, we can include spaces between each letter.
return binaryArray.join(" ");
}

Ever wanted to convert a binary string to text?

Let’s say we have the binary string “01010100 01001000 01000001 01001110 01001011 00100000 01011001 01001111 01010101” :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function binaryStringToText(binaryString) {
let binaryLetters = binaryString.split(" "); // Each letter is represented by an 8-bit binary string

let sentenceAsArray = binaryLetters.map(binaryLetter => {
// Each letter is first parsed as an integer using the radix (number base) of 2 (for binary) in order to get the character code for that letter
let charCode = parseInt(binaryLetter, 2);
/* If you were to have a separate function just handling this conversion to character codes, you would get an array like:
[84, 72, 65, 78, 75, 32, 89, 79, 85]
*/

// We then get the letter by calling the String method fromCharCode to get the ASCII value associated with that char code

/* Since we're only dealing with 8-byte strings, it's backward-compatable with with ASCII, so you could look the values up in an ASCII table:
84 - T
72 - H
65 - A
78 - N
75 - K
32 - Space
89 - Y
79 - O
85 - U
*/
let letter = String.fromCharCode(charCode);
return letter;
});

/* The above map returns an array like this:
['T', 'H', 'A', 'N', 'K', ' ', 'Y', 'O', 'U']
So in order to get the sentence back in string form, you just join the elements of the array on an empty string
*/

let sentence = sentenceAsArray.join("");
return sentence;
// "THANK YOU"
}

Pig Latin Pt II

Clarification

In the previous post, I’d said I thought that the reason split_consonants had split in such a way that the consonants were in the first part of the split and the vowels were in the second was because the only words that would have made use of the variables created by split_consonants would be words that started with consonants. After playing around with IO.inspect() on the vowel and consonant variables given different input, I believe that to be the case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
defmodule PigLatin do
def piglify(word) do
first_char = String.at(word, 0)
second_char = String.at(word, 1)

split_consonants =
Regex.split(~r/([aeio]|(?<!q)u)(.*)/, word,
trim: true,
parts: 2,
include_captures: true
)

consonant = Enum.at(split_consonants, 0)
vowel = Enum.at(split_consonants, 1) || ""
IO.inspect("consonant " <> consonant)
IO.inspect("vowel " <> vowel)
cond do
String.match?(first_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

String.match?(first_char, ~r/y|x/) && !String.match?(second_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

true ->
vowel <> consonant <> "ay"
end
end
end

tests = Enum.map(["yttria", "over", "under", "why", "whatever", "whichever", "aquaduct", "dhdh", "square"], fn x -> PigLatin.piglify(x) end)

IO.inspect(tests)

The resturn from this function is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"consonant yttr"
"vowel ia"
"consonant over"
"vowel "
"consonant under"
"vowel "
"consonant why"
"vowel "
"consonant wh"
"vowel atever"
"consonant wh"
"vowel ichever"
"consonant aquaduct"
"vowel "
"consonant dhdh"
"vowel "
"consonant squ"
"vowel are"
["yttriaay", "overay", "underay", "whyay", "ateverwhay", "icheverwhay",
"aquaductay", "dhdhay", "aresquay"]

The Refactor!

So I realized that since two of my conditionals evaluated to the same thing, I decided to combine them into one condition:

Original:

1
2
3
4
5
String.match?(first_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

String.match?(first_char, ~r/y|x/) && !String.match?(second_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

Refactor:

1
2
3
String.match?(first_char, ~r/[aeio]|(?<!q)u/) or
(String.match?(first_char, ~r/y|x/) && !String.match?(second_char, ~r/[aeio]|(?<!q)u/)) ->
word <> "ay"

Another change I ended up making was to clean up the translate function. Since I’m passing the words argument into successive functions, I don’t actually need to name the variable – I could just use the pipe operator to pass it into the next function in the sequence:

Original:

1
2
3
4
5
6
def translate(phrase) do
words = String.split(phrase, " ", trim: true)

Enum.map(words, fn x -> piglify(x) end)
|> Enum.join(" ")
end

Refactor:

1
2
3
4
5
def translate(phrase) do
String.split(phrase, " ", trim: true)
|> Enum.map(fn x -> piglify(x) end)
|> Enum.join(" ")
end

The refactored program, then, becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
defmodule PigLatin do
@doc """
Given a `phrase`, translate it a word at a time to Pig Latin.

Words beginning with consonants should have the consonant moved to the end of
the word, followed by "ay".

Words beginning with vowels (aeiou) should have "ay" added to the end of the
word.

Some groups of letters are treated like consonants, including "ch", "qu",
"squ", "th", "thr", and "sch".

Some groups are treated like vowels, including "yt" and "xr".
"""
@spec translate(phrase :: String.t()) :: String.t()
def translate(phrase) do
String.split(phrase, " ", trim: true)
|> Enum.map(fn x -> piglify(x) end)
|> Enum.join(" ")
end

def piglify(word) do
first_char = String.at(word, 0)
second_char = String.at(word, 1)

split_consonants =
Regex.split(~r/([aeio]|(?<!q)u)(.*)/, word,
trim: true,
parts: 2,
include_captures: true
)

consonant = Enum.at(split_consonants, 0)
vowel = Enum.at(split_consonants, 1)

cond do
String.match?(first_char, ~r/[aeio]|(?<!q)u/) or
(String.match?(first_char, ~r/y|x/) && !String.match?(second_char, ~r/[aeio]|(?<!q)u/)) ->
word <> "ay"

true ->
vowel <> consonant <> "ay"
end
end
end

Short post today because I woke up early to watch the in-flight abort test for Space-X and I spent the day watching the Liverpool FC game, a hockey game, the Kansas City Chiefs game, and now the Packers game – and I am tired.

Catch ya later!

- Amy

Pig Latin

The Problem

Create a program that translates English into Pig Latin. In case you didn’t go through the Pig Latin phase as a kid (or in case you’ve forgotten how it works), here are the rules:

  1. If a word begins with a vowel, add “ay” to the end of the word
  2. If a word begins with a consonant, move it to the end of the word before adding “ay” to the end
  3. If a ‘q’ is followed by a ‘u’, treat them as one consonant group
  4. If a word starts with ‘y’ or ‘x’ followed by a consonant, treat them as vowels

The Test Input

Testing Rule 1

  • Given the input “apple”, the program should return “appleay”
  • Given the input “ear”, the program should return “earay”
  • Given the input “igloo”, the program should return “iglooay”
  • Given the input “object”, the program should return “objectay”
  • Given the input “under”, the program should return “underay”
  • Given the input “equal”, the program should return “equalay”

Testing Rule 2 with one starting consonant

  • Given the input “pig”, the program should return “igpay”
  • Given the input “koala”, the program should return “oalakay”
  • Given the input “yellow”, the program should return “ellowyay”
  • Given the input “xenon”, the program should return “enonxay”
  • Given the input “qat”, the program should return “atqay”
  • Given the input “pleasure”, the program should return “easureplay”
  • Given the input “stringify”, the program should return “ingifystray”
  • Given the input “zkrrkrkrkrzzzkewk”, the program should return “ewkzkrrkrkrkrzzzkay”

Testing Rule 2 with multiple starting consonants

  • Given the input “chair”, the program should return “airchay”
  • Given the input “therapy”, the program should return “erapythay”
  • Given the input “thrush”, the program should return “ushthray”
  • Given the input “school”, the program should return “oolschay”

Testing Rule 3

  • Given the input “queen”, the program should return “eenquay”
  • Given the input “square”, the program should return “aresquay”

Testing Rule 4

  • Given the input “yttria”, the program should return “yttriaay”
  • Given the input “yddria”, the program should return “yddriaay”
  • Given the input “xray”, the program should return “xrayay”
  • Given the input “xbot”, the program should return “xbotay”

Testing Phrases

  • Given the phrase “quick fast run”, the program should return “ickquay astfay unray”
  • Given the phrase “the quick brown fox jumps over the lazy dog”, the program should return “ethay ickquay ownbray oxfay umpsjay overay ethay azylay ogday”

Solving the Problem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Split the phrase into words
For each word:
If first character is a vowel:
Add "ay" to end of word
Else
While the character we're looking at is a consonant,
look at the next character
If the next character is a 'u' and the current character is a 'q':
treat them both as if they were consonants and continue
If the next character is a consonant and the current character is a 'y' or 'x':
treat the 'y' or 'x' as if it was a vowel
If the next character is treated as a vowel:
- Split the word before the vowel,
- Move the partial string before the vowel to the end of the word
- Add "ay" to the end of the word
1
2
3
4
5
6
def translate(phrase) do
words = String.split(phrase, " ")

Enum.map(words, fn x -> piglify(x) end)
|> Enum.join(" ")
end

String.split(phrase, " ") is taking the phrase and splitting them by a single space, in order to get a list of words. We then call Enum.map on that list of words in order to make a call to piglify, which will translate those words into Pig Latin. Then we use the pipe operator |> to take the output of the map and use it as the first argument to Enum.join(" "), which turns the list of piglified words back into a string, preserving the space between words.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

defmodule PigLatin do
@doc """
Given a `phrase`, translate it a word at a time to Pig Latin.

Words beginning with consonants should have the consonant moved to the end of
the word, followed by "ay".

Words beginning with vowels (aeiou) should have "ay" added to the end of the
word.

Some groups of letters are treated like consonants, including "ch", "qu",
"squ", "th", "thr", and "sch".

Some groups are treated like vowels, including "yt" and "xr".
"""
@spec translate(phrase :: String.t()) :: String.t()
def translate(phrase) do
words = String.split(phrase, " ", trim: true)

Enum.map(words, fn x -> piglify(x) end)
|> Enum.join(" ")
end

def piglify(word) do
first_char = String.at(word, 0)
second_char = String.at(word, 1)

split_consonants =
Regex.split(~r/([aeio]|(?<!q)u)(.*)/, word,
trim: true,
parts: 2,
include_captures: true
)

consonant = Enum.at(split_consonants, 0)
vowel = Enum.at(split_consonants, 1)

# string_after_second =
# Enum.slice(split_string, 1..-1)
# |> Enum.join()

# first_two_chars =
# Enum.slice(split_string, 0..1)
# |> Enum.join()

cond do
String.match?(first_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

String.match?(first_char, ~r/y|x/) && !String.match?(second_char, ~r/[aeio]|(?<!q)u/) ->
word <> "ay"

true ->
vowel <> consonant <> "ay"
end
end
end

String.at() is a fairly straightforward function. It takes a string as the first argument, an index as the second, and returns the grapheme (essentially, letter) that exists at that index of the string (or nil if the index is greater than the length of the string). So first_char and second_char are literally just the first and second letters of the word.

What is going on with this regex?!, you might ask. And it’s a fair question. Let’s break it down.

~r/([aeio]|(?<!q)u)(.*)

~r is a sigil for creating regular expressions
The way I interpreted () was as a capture group because that’s what I’m familiar with it being in other languages, though I’m not certain that’s the same thing that’s happening in Elixir.
[aeio] is to match characters who are members in that set.
| is, predictably, ‘or’
(?<!q)u is “match u but only if it isn’t following a ‘q’”
(.*) is “match everything else in the string”, since . is the wildcard to match any character and * is a quantifier to match 0 or more.

So if this is doing what I think it is, it’s taking the word and splitting it into two parts. Because the only cases we’re going to be working with split_consonants is in situations where the first part of the word is a consonant, I think that’s why the first part of the split is a consonant and the second is the vowel. I honestly need to play around with it a bit more to know for sure.

Then for the conditional, we want to have a case for if the first character in the string matches the regular expression we’ve made to determine if a character is a vowel or will be treated as a vowel and if it is, return the word concatenated with “ay” word <> "ay".

We then have a conditional for if the first character is a y or x if the second character is a consonant (which is really just putting a not in front of the String.match? for vowels). If that’s the case, we also want to return the word concatenated with “ay”.

Then, since evrerything else should follow the alternative method for building up pig latin, we can make this conditional simply be true since if the other two conditions fail, this should always pass. If the word gets to this conditional, then the vowel portion of the word should be concatenated with the consonant-leading portion of the word concatenated with “ay”, vowel <> consonant <> "ay"

Let me know if you thought this was helpful (or what you thought of it in general)!

See ya!

- Amy

Twelve Days of Christmas

Exercism - Twelve Days of Christmas

The premise for this problem’s fairly simple. Your mission, should you choose to accept it, is to output the lyrics to the 12 Days of Christmas.

The starter code you’re provided is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

defmodule TwelveDays do
@doc """
Given a `number`, return the song's verse for that specific day, including
all gifts for previous days in the same line.
"""
@spec verse(number :: integer) :: String.t()
def verse(number) do
end

@doc """
Given a `starting_verse` and an `ending_verse`, return the verses for each
included day, one per line.
"""
@spec verses(starting_verse :: integer, ending_verse :: integer) :: String.t()
def verses(starting_verse, ending_verse) do
end

@doc """
Sing all 12 verses, in order, one verse per line.
"""
@spec sing() :: String.t()
def sing do
end
end

I knew that there was repetition involved in this problem, and I was originally thinking about how to go about generating the previous days’ gifts, but I psyched myself out. You’re overthinking again! You could just hardcode it!

So that’s what I did, the first time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@spec verse(number :: integer) :: String.t()
def verse(number) do
case number do
1 ->
"On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree."

2 ->
"On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree."

3 ->
"On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

4 ->
"On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

5 ->
"On the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

6 ->
"On the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

7 ->
"On the seventh day of Christmas my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

8 ->
"On the eighth day of Christmas my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

9 ->
"On the ninth day of Christmas my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

10 ->
"On the tenth day of Christmas my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

11 ->
"On the eleventh day of Christmas my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

12 ->
"On the twelfth day of Christmas my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

_ ->
nil
end
end

Yeah, I know, cheating. But it wasn’t like I could avoid recursion if I wanted to write the verses function.

This part was actually difficult for me. Recursion and I have not exactly historically been friends and I couldn’t use my usual changing variables technique that I could use in JavaScript, since mutation isn’t possible in Elixir. SO RECURSION IT IS.

All right, if I’m going to do recursion, I’m going to need to know the base case. At what point am I done with recursion? Thinking about it in the paradigm I’m more used to, this would be the end condition of a while loop.

The first thing I generally do when approaching problems like this is say in English what I want to happen. Sometimes, I even do this aloud (if no one’s around to hear me).

Make a variable current_verse equal to starting_verse. While current_verse is less than or equal to ending_verse, we want to call verse on the current_verse and add that verse to the verses I’ve already got from calling verse, making sure to add a new line between them.

In JavaScript, that’d look like:

1
2
3
4
5
6
7
8
function verses(starting_verse, ending_verse) {
let current_verse = starting_verse;
let complete_verses = verse(current_verse);
while (current_verse <= ending_verse) {
complete_verses += "\n" + verse(++current_verse);
}
return complete_verses;
}

Thankfully for me, you can do string concatenation in Elixir too! <> is the way to accomplish it.

1
2
3
4
5
6
7
8
9
10
def verses(starting_verse, ending_verse) do
if starting_verse === ending_verse do
verse(ending_verse)
else
combined_verses = verse(starting_verse)

current_verse = starting_verse + 1
combined_verses <> "\n" <> verses(current_verse, ending_verse)
end
end

If the starting_verse equals the ending_verse, return verse(ending_verse). If the starting_verse doesn’t equal the ending_verse, take the value of the return of verse(starting_verse) and assign it to the variable combined_verses. Initialize a variable current_verse with the value of one greater than starting_verse. Return combined_verses concatenated with a newline concatenated with the return of the call to verses(current_verse, ending_verse).

Given verses(1, 3), the execution would go something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def verses(1, 3) do
if 1 === 3 do
verse(3)
else
combined_verses = verse(1) ( "On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.")

current_verse = 1 + 1
combined_verses <> "\n" <> verses(2, 3)
end
end

def verses(2, 3) do
if 2 === 3 do
verse(3)
else
combined_verses = verse(2) ( "On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.")

current_verse = 2 + 1
combined_verses <> "\n" <> verses(3, 3)
end
end

verses(2, 3) returns "On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\nOn the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."

def verses(3, 3) do
if 3 === 3 do
verse(3) ("On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.")

-- else doesn't evaluate
end
end

verses(3, 3) returns “On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.” So when we evaluate verses(2, 3), we can plug that value in to get the output of verses(2, 3). So the output of verses(2, 3) becomes “On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\nOn the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.” Then the output of verses(2, 3) can be plugged into the function call to verses(1, 3) to give us “On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\nOn the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\nOn the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.”

The final function was easier - sing all 12 verses in order:

1
2
3
def sing do
verses(1, 12)
end

Then after talking it over with some other programmers whom I respect the opinion of, it was pointed out to me that I completely ignored the point 9of the exercise in that I wasn’t really employing the functional paradigm that Elixir is known for to solve the problem. And that one of the best things with Erlang and Elixir is pattern-matching, so I might as well use it.

sigh

Not wanting to spend a million years trying to solve a problem while fighting syntax, I went the easy route and tried to solve the problem using functional programming in JavaScript (I am ever thankful that you can use multiple paradigms):

Pattern-Matching in JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const giftMap = {
1: "a Partridge in a Pear Tree",
2: "two Turtle Doves",
3: "three French Hens",
4: "four calling birds",
5: "five Golden Rings",
6: "six Geese-a-laying",
7: "seven Swans-a-Swimming",
8: "eight Maids-a-Milking",
9: "nine Ladies Dancing",
10: "ten Lords-a-Leaping",
11: "eleven Pipers Piping",
12: "twelve Drummers Drumming"
};

const ordinal = {
1: "first",
2: "second",
3: "third",
4: "fourth",
5: "fifth",
6: "sixth",
7: "seventh",
8: "eighth",
9: "ninth",
10: "tenth",
11: "eleventh",
12: "twelfth"
};

function verse(number) {
return `On the ${
ordinal[number]
} day of Christmas, my true love gave to me: ${composeVerse(number)}`;
}

function composeVerse(number) {
let verse = "";
while (number > 1) {
verse += `${giftMap[number]}, `;
number--;
}
verse += `and ${giftMap[number]}.`;
return verse;
}

Example execution:

1
2
3
4
5
6
verse(5)

function verse(5) {
return `On the ${ordinal[5]} day of Christmas, my true love gave to me:
${composeVerse(5)}`;
}
  • ordinal[5] evaluates to fifth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function composeVerse(5) {
let verse = ''
while (5 > 1) {
verse += `${giftMap[5]}, `;
// giftMap[5] is "five Golden Rings", so:
// "five Golden Rings, "
5--; // 4
}
while (4 > 1) {
verse += `${giftMap[4]}, `;
// giftMap[4] is "four calling birds", so:
// "five Golden Rings, four calling birds, "
4--; // 3
}
while (3 > 1) {
verse += `${giftMap[3]}, `;
// giftMap[3] is "three French Hens", so:
// "five Golden Rings, four calling birds, three French Hens, "
3--; // 2
}
while (2 > 1) {
verse += `${giftMap[2]}, `;
// giftMap[2] is "two Turtle Doves", so:
// "five Golden Rings, four calling birds, three French Hens, two Turtle Doves, "
2--; // 1
}
while (1 > 1) // evaluates to false, so while loop ends
verse += `and ${giftMap[1]}.`
// giftMap[1] is "a Patridge in a Pear Tree", so:
// "five Golden Rings, four calling birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."
return verse;
}
  • composeVerse(5) returns “five Golden Rings, four calling birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.”

The call to verse(5), then, returns:

“On the fifth day of Christmas, my true love gave to me: five Golden Rings, four calling birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.”

Converting it to Elixir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@giftMap %{
1 => "a Partridge in a Pear Tree",
2 => "two Turtle Doves",
3 => "three French Hens",
4 => "four Calling Birds",
5 => "five Gold Rings",
6 => "six Geese-a-Laying",
7 => "seven Swans-a-Swimming",
8 => "eight Maids-a-Milking",
9 => "nine Ladies Dancing",
10 => "ten Lords-a-Leaping",
11 => "eleven Pipers Piping",
12 => "twelve Drummers Drumming"
}

@ordinal %{
1 => "first",
2 => "second",
3 => "third",
4 => "fourth",
5 => "fifth",
6 => "sixth",
7 => "seventh",
8 => "eighth",
9 => "ninth",
10 => "tenth",
11 => "eleventh",
12 => "twelfth"
}

These are pretty much the exact same thing as the giftMap and ordinal in the JavaScript implementation. In both languages, I’m using maps to represent the key-value pairs for the gifts and the ordinal day. So far so good.

1
2
def start(number),
do: "On the #{Map.get(@ordinal, number)} day of Christmas my true love gave to me: "

This is almost the same as the JavaScript implementation. Only, this time I’m not calling another function to compose the verse. At least not in this function. This function is literally just to start us off. Map.get(map, key) is a way to get the value of a given key from a map in Elixir, so it’s equivalent to what we saw earlier with ordinal[number]. #{} is like the equivalent of ${} in JavaScript - an indication to evaluate what’s inside before turning it into a string.

I wrote some helper functions to return the gifts out of the giftMap. One of the cool things with Elixir is you can have multi-clause functions – the first function whose function clause matches the argument you provided will execute.

1
2
3
4
5
6
7
8
@spec gift(number :: integer) :: String.t()
def gift(1) do
"and #{Map.get(@giftMap, 1)}"
end

def gift(num) do
"#{Map.get(@giftMap, num)}, "
end

So the first gift is only run if the number 1 is passed in, else the second function definition is used.

1
2
3
4
5
6
7
8
9
10
11
def verse(1) do
"#{start(1)}#{Map.get(@giftMap, 1)}."
end

def verse(number) do
"#{start(number)}#{
Enum.reduce(number..1, "", fn verse, verses ->
verses <> gift(verse)
end)
}."
end

So… if we call verse(1), the first function definition for verse is executed.

The result of verse(1) still being:

“On the first day of Christmas, my true love gave to me: and a Partridge in a Pear Tree.”

Now let’s take a closer look at that second function:

1
2
3
4
5
6
7
def verse(number) do
"#{start(number)}#{
Enum.reduce(number..1, "", fn verse, verses ->
verses <> gift(verse)
end)
}."
end

Enum.reduce(enumerable, acc, fun) is the reduce being used here.
number..1 is a range from number to 1 - if number is 3, for example, this would be [3, 2, 1]. acc stands for accumulator - the initial value being passed into the function. The result of the function is used as the accumulator in the next run of the function. In this case, the original accumulator is an empty string (like the verse variable in the earlier JavaScript implementation). The function, then, takes the verse being iterated on (the current number in the range) and the accumulator and returns the accumulator concatenated with the value of the call to gift(verse).

Say you wanted to get the third verse, the Enum.reduce evaluation would go like this:

1
2
3
4
5
6
7
8
9
10
11
Enum.reduce(3..1, "",
fn 3, "" ->
"" <> gift(3)
// "three French Hens, "
fn 2, "three French Hens, " ->
"three French Hens, " <> gift(2)
// "three French Hens, two Turtle Doves, "
fn 1, "three French Hens, two Turtle Doves, " ->
"three French Hens, two Turtle Doves, " <> gift(1)
// "three French Hens, two Turtle Doves, and a Partridge in a Pear Tree"
end)

#{start(3)} would return:

“On the third day of Christmas my true love gave to me: “

Enum.reduce(number..1, "", fn verse, verses -> verses <> gift(verse) end) returns:

“three French Hens, two Turtle Doves, and a Partridge in a Pear Tree”

And the end of the function is to add a period, giving us:

“On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.”

Hurray!

1
2
3
4
@spec verses(starting_verse :: integer, ending_verse :: integer) :: String.t()
def verses(starting_verse, ending_verse) do
Enum.map_join(starting_verse..ending_verse, "\n", &verse(&1))
end

I will admit this looks intimidating if you don’t know Elixir. I literally wrote a note to myself under this function when I was writing it to remind myself what the &verse(&1) was equivalent to so that I wouldn’t freak out when next I cast eyes upon it.

Enum.map_join(enumerable, joiner \ “”, mapper. Joiner is something you want to join the enumerable things on. It’s like if you call String.join(‘,’) in JavaScript. Mapper is going to be a function that you call on the elements in the enumerable (since you’re mapping over them as well as joining – very intuitive naming).

In our case, & is a capture operator to partially apply a function and &1 is a value placeholder. So if we wanted to write code that was less confusing for other people, we could write &verse(&1) as fn x -> verse(x) end.

Anyway, it’s getting late and I spent a good hour on this longer than I meant to because I had a cat fall asleep on my arm. (Want to know how difficult it is to write code blocks with one arm? so hard)

See ya!

- Amy

About that 'getting back' business ...

If you thought I’d gotten to the point where I could deploy this blog without incident, you’re sorely mistaken. And it’s evident from the way the rest of the night went.

You see, the thing I had been trying to do, adding this blog onto the website I already had deployed through another Github repo on Netlify, wasn’t as easy as the documentation would have led me to believe. Or rather, there were some gotchas.

When this blog was deployed on Netilfy by itself, everything seemed fine. The CSS seemed to load appropriately, the navigation seemed to be operating as it should, things looked fine.

Yesterday at 6:18 PM

1
2
3
4
5
[[redirects]]
from = "/blog/*"
to = "https://modest-yonath-e1349c.netlify.com/:splat"
status = 200
force = true

Result:
Hilariously, I forgot it looked this good - that could have saved me _so_ much time.

Grah! So, it looks good, except for the fact that the images aren’t loading. Hitting the website link takes me back to my main site, style’s the way I have it set. That’s got to be a quick fix!

Yesterday at 6:28 PM

_config.yml:

1
2
3
4
5
menu:
Home: ../
Archives: /archives
rss: /atom.xml
banner: "images/banner.jpg"

How did this happen?!

None of the links work. The CSS doesn’t work. What happened to that post with the images in it? (I can’t honestly remember when I started writing the post, but looking at the Netlify preview from before this, I must have started it before this point?

Yesterday at 7:49 PM

Okay, okay, maybe it’s a pathing issue, I think. Maybe it’s that I need to enable the post_asset_folder, maybe I need to move the images around. Ooh, index_generator, that sounds like a good thing. Says something about blog root – I should probably change that to /blog/!

_config.yml:

1
2
3
4
5
6
post_asset_folder: true
relative_link: true

...
index_generator:
path: "/blog"

You know, now I think Netlify’s playing a mean trick with time, since the blog deploys still don’t have the last post…. hm.

Looking good!

Most of the links work! And the styling’s working! The Website link reads as broken, however, and the endpoint is at /blog instead of at the root. Hm.

Yesterday at 7:55 PM

Changed the root back:

_config.yml:

1
2
index_generator:
path: ""

Yesterday at 8:00 PM

Here’s yesterday’s post!

How do you know?, you might ask.

Because at this point I was feeling confident enough to have responsible git messages. The last … several… have been a variation of Config, but this one literally says Second post.

Now we’re to the point where the first image comes into play on the blog side of things. Style looked okay, images were broken. Meh.

The rest of the night in pictures

Commits on blog repo:

Commits to Blog 1

Commits to Blog 2

Commits to Blog 3

Commits to Blog 4

Commits to Blog 5

Commits to Blog 6

Commits to Blog 7

Commits on website repo:

Commits to Website 1

Commits to Website 2

Commits to Website 3

It even got so bad that Netlify seemed to be trying to get me to take a break:

8:42 p.m. - Netlify is tired of my shenanigans

8.46 p.m. - Last build hasn't even started and I've tried starting one on the other repo

The Importance of Breaks

See how many commits were made in the course of three hours? Do you see how uninformative the majority of those commit messages were?

There’s a reason for that. I was getting frustrated with my inability to do a task that others had written about being easy, I’d let it get to my head that not accomplishing the task would brand me a failure, and I was too stubborn to stop. The only reason I did stop was it became clear there was no way I was going to solve the problem any time soon and I had to be up in less than 7 hours for work.

Bringing us to today …

_config.yml:

1
relative_link: false

What my blog looks like

WHY ARE YOU HAPPY ABOUT THIS?! you might ask. Because it looks terribly broken. All that lovely CSS is gone. Clicking any of the links takes you to a page not found. And you don’t have to take my word for it.

I’m happy because with it looking like that on the blog’s own route, it looked like The lovely CSS styled blog you're hopefully seeing now

Take-away:

  • If you find yourself struggling with something that other people have labeled as easy, don’t sweat it. Everyone’s experience is different - it’s dependent on what you’ve done before, what you’re familiar with, if you’ve solved similar problems in the past.
  • If your git commit messages get to be flippant and give no information about what kind of changes are contained within, that might be the time to take a break from it. I was pounding my head at this problem for literally hours yesterday when it literally only took a few minutes of time today to realize what the actual problem was.

I’m going to cross my fingers, make a hopefully more thorough commit message, and hope that adding this post doesn’t completely break everything all over again.

I promise future posts will not all be rambling and uninformative like this. I hope to start actually talking about programming in the near future. A few co-workers and I currently spend an hour every Wednesday going through https://exercism.io/ problems in the Elixir track to try to learn Elixir and I think it’d be fun to start documenting some of my experience with that, especially breaking down syntax and comparing it with different programming languages I’m more familiar with.

Anyway, ramble, ramble, ramble…

Catch you tomorrow, maybe!

- Amy

Another Day, Another Post

I’m going to give you a quick overview of how this ‘getting a blog together’ thing has been (a story in git commits):

Good start!

All right, you tell yourself. _I’m trying out this framework I’ve never used before, but it seems really intuitive! I should have a blog in no time!

Static Files Are a Thing

Okay, don’t panic, you tell yourself when the layout you spent the last better part of an hour playing around with doesn’t render in the slightest. Static files are a common problem. You know this. Remember when you didn’t know Django? Static files were your nemesis until you found Whitenoise. This is probably just as simple.

Oh no

The first indicator that I do not understand what is going on with my code is when my git commit message isn’t informational in nature.

The second indicator is that I use the same commit message. This means that not only do I not have a high confidence level in the change being effective, but I’m too aware of this fact to put any effort into the operation beyond hitting the up arrow a set number of times and hitting enter.

git add .
Up arrow
Up arrow
Enter
Up arrow
Up arrow
Up arrow
Enter

Getting back

Ah-ha! Back to a semi-informative commit message!

There we go

And that’s where we find ourselves!

- Amy

New Year, New Beginnings

Last Known Good Configuration is a blog where I try to explain programming as I know it to whomever will listen in the hope that I can help someone else on their journey to becoming a better programmer.

We have nothing to fear but fear itself

When I was starting to learn to program, I would see my peers publishing articles about their milestones - when they learned a concept or mastered a new part of tech, they’d memorialize it by consolidating the information into an article on Medium or some other platform. And part of me always felt jealous of that. That they felt comfortable enough to share their vulnerability like that - admitting that they didn’t understand it right away, acknowledging that it was a process.

By the time I’d gained enough confidence in my own abilities, gotten over my imposter syndrome as it were, I felt that the mini milestones I achieved weren’t worthy writing about.

But the truth is that the reasoning is the same. Fear. Fear of appearing the fool, of being proven wrong, of being called out as not understanding something well enough.

“Amy’s not naturally smart, she just works hard” - a sentence fragment meant to inspire my brother who was “naturally smart” to work harder, but only really succeeded in making me feel like I would never reach the ceiling others could.

Am I working hard enough? Will I ever?

Future Plans

It is my firm belief that there are no dumb questions, only people too self-absorbed or impatient or tunnel-visioned to see the difference in view, to work out the gaps necessary to bridge misconception. Questions might seem stupid, but we often underestimate the amount we know, take it for granted, and forget that not everything we take to be common knowledge actually is. And we can forget that just because we’ve come to a conclusion about something doesn’t mean that we’ve come to the right conclusion, or for the right reasons.

I’ll admit that although one of my passions in life is to mentor and teach, I still fall short at times. It’s easy to fall into the temptation of offering a solution instead of encouraging someone to come up with their own solution.

But I’m working on it.

I want this to serve as a reminder to sit down and make the effort to truly understand the code that I’m writing in order to help other people better understand why it’s written the way that it is. And I want to share that journey.

So, if you can tolerate my sense of humor and my stumbles in the dark, maybe we can do this thing together.

We have nothing to fear but fear itself.

- Amy