Exercises for Programmers: Exercise 4- Mad Libs

The fourth exercise in Exercises for Programmers is to create a Mad Lib.

Mad Libs are a game you play when you’re a kid and you’re trying to learn different parts of speech. You name random nouns and verbs to fill in the blanks in a story you can’t see until you are finished.

This project continues with the theme of making the exercises more and more complex as you go along.

This project requires you to ask for four different user inputs and to create a story around them. If any of those inputs is missing, you want to alert the user rather than output a story with half the words missing.

As usual, I have uploaded this project to GitHub.

What this looks like when successful

What this looks like when successful

Data Structures

Since I want to unit test my application, I want to move as much of the programming logic outside of the view controller as possible. I need to grab the four user input responses and I want to pass those into another function.

I thought about making this into an array of strings, but I liked the idea of being able to have keys and values for the user input. I want to be able to tell the user which field they forgot to fill and that is a lot easier when you bundle the name of the field along with the input instead of just counting on things being in the right order. Having an array of random words with no indication about what they represent seemed like a recipe for disaster.

In the @IBAction for the Enter button, I created a dictionary of all of the user input values:

let userInput:[String:String] = [
    "noun":nounTextField.text!,
    "verb":verbTextField.text!,
    "adjective":adjectiveTextField.text!,
    "adverb":adverbTextField.text!
]

Now that I had a data structure and I knew what types were going into it, I could start working on the output logic.

Set up

Set up

To Map(), or not to Map()

One reason I wanted these values to exist in a data structure was because I wanted to use Map().

This seemed like a good time to use Map(). I had to iterate through a data structure, check to see if the input was empty, and if it was I had to do something.

Taking this approach really tripped me up and cost me a lot of time.

When I worked with Brad at SonoPlot, he used protocols and flatmaps to iterate through a collection of data we were writing to and getting from the NSUserDefaults. I thought I would set up a function that returns an optional string that would check each string to see if there was anything in it. If there wasn’t, I could return a nil and filter them out.

func isStringEmpty(input:String) -> String? {
    if input.characters.count != 0 {
        return input
    } else {
        return nil
    }
}

I started to realize this wasn’t really helpful to me. Here is why:

Let’s say my user forgets to specify a verb. I can flatmap my dictionary using this function that will give me an array with three values. I won’t know which value is missing. I can’t tell the user which value they forgot to enter.

I spent a bunch of time trying to figure out how to map my isStringEmpty() function to my dictionary. In order to try and figure out how to optimize this, I wrote it in the most inefficient manner I possibly could so I could see what code I was repeating:

func output(input:[String:String]) -> String {
    
    var outputString = String()
    
    let noun = isStringEmpty(input["noun"]!)
    let verb = isStringEmpty(input["verb"]!)
    let adjective = isStringEmpty(input["adjective"]!)
    let adverb = isStringEmpty(input["adverb"]!)
    
    if noun == nil {
        outputString += "Please enter a noun! 
"
    }
    
    if verb == nil {
        outputString += "Please enter a verb! 
"
    }
    
    if adjective == nil {
        outputString += "Please enter an adjective! 
"
    }
    
    if adverb == nil {
        outputString += "Please enter an adverb! 
"
    }
    
    if noun != nil &&
       verb != nil &&
       adjective != nil &&
       adverb != nil
    {
        outputString = "Do you (verb) your (adjective) (noun) (adverb)?"
    }
    
    return outputString
}

Clearly I am repeating my calls to isStringEmpty() for every input in my UI. This is really inefficient.

I am also constantly checking if something is nil. Since the variable I am checking has the same name as the type of word that I need to tell the user to enter, this could probably be more generic.

I optimized this from that monstrosity to the following:

func output(input:[String:String]) -> String {
    var outputString = String()
    
    for (key, value) in input {
        let checkValue = isStringEmpty(value)
        
        if checkValue.characters.count == nil {
            outputString += "Please enter a (key)! 
"
        }
        
    }
    
    if outputString.characters.count == 0 {
        let noun = input["noun"]!
        let verb = input["verb"]!
        let adjective = input["adjective"]!
        let adverb = input["adverb"]!
        
        outputString = "Do you (verb) your (adjective) (noun) (adverb)?"
    }
    
    return outputString
}

I tried for a bit to use a map() function rather than the for-in loop, but it felt just easier to do it this way. I would still like to get more comfortable using map(), but I also don’t want to create an “If you give a mouse a cookie” problem where I am adding a bunch of garbage to my code to try and force a square peg into a round hole.

If anyone can give me a good explanation about how I could have used map() more efficiently than using for-in I would greatly appreciate it. I have seen it used in other people’s code but I don’t process how I can implement it myself because I have not tried to solve a problem with it yet.

Lastly, I realized that my efforts to cram the flatmap() into the code was causing me to use the isStringEmpty() function even though there really was no point in doing so.

I condensed down all of my programming logic into one function:

func output(input:[String:String]) -> String {
    var outputString = String()
    
    for (key, value) in input {
        
        if value.characters.count == 0 {
            outputString += "Please enter a (key)! 
"
        }
        
    }
    
    if outputString.characters.count == 0 {
        let noun = input["noun"]!
        let verb = input["verb"]!
        let adjective = input["adjective"]!
        let adverb = input["adverb"]!
        
        outputString = "Do you (verb) your (adjective) (noun) (adverb)?"
    }
    
    return outputString
}
Completely empty response

Completely empty response

Limit to User Input Optimization

One thing that bothered me about this project was the fact that I couldn’t procedurally deal with every instance of pulling out the nouns, verbs, etc…

No matter what I do I have to spend four lines of code pulling out the user input and then another four lines doing something with them.

Those lines of code cause me to be bothered because I don’t like hard coding things. I was happy I could do the for-in loop because there could be forty elements or two and either way it was flexible. Having things hard coded isn’t flexible.

However, I do not think there is a way around this in this project. I believe that any time you are doing anything that touches the user interface, there is a limit to how far you can optimize it.

When I was going to write the unit tests, I had the urge to see what would happen if I sent a value that wasn’t noun or verb or if one of those was missing. I know that in this project that is impossible, but it still really bothers me because of how inefficient it feels. It had code smell. I think there is nothing I can do about it, but that won’t keep me from driving myself crazy trying to think of a way around it.

Partially empty response

Partially empty response

Takeaways

Initially, I thought this project would have a lot more programming logic than any other project before this, but it actually had the least amount.

I left my less efficient code in the project to show how much I was able to refactor the project.

I think a major takeaway that I want to bring up is the idea of rewriting your code a lot.

One issue I have had with working with other people on programming projects is that sometimes, as I did here, it’s easier for me to write some really crappy, inefficient code that just works so that I can get a better handle on how to make it better. When I am working on something and someone randomly demands to see what I have so far, it is incredibly upsetting to have to show that I did this really terrible code. There are probably ways around this, but it can be super demoralizing to show the early part of my process to someone when it is something I am still actively working on and trying to make better.

If you are a decent programmer, you will rewrite your code a few times. You will get a better idea about what problems you are trying to solve and how your current approach isn’t what you thought it would be. My final code is about a third of what it was initially because the way I thought I needed to do this was different than the way I actually needed to do it. I could have kept adding more and more junk to the code to try and force it to work, or I could change my perspective on how to approach the problem.

I firmly believe in writing bad code to get the garbage out of your head. Not every line of code needs to be a line from Shakespeare. Writing a lot of bad code helps you figure out how to write better code more quickly.