Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS Swift Enums and Structs Enums What is an Enum?

lance samaria
lance samaria
3,642 Points

Using a for-in loop with a switch statement

Earlier in previous examples Amit used for-in loops with a switch statement to iterate through an array. I tried using that with the Enum code and I keep getting errors. I know there are diff ways to do things so i want to try it an alternate way. I keep getting swift errors, here's my code:

let daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

func weekdayOrWeekend(day: String) -> String? {

    for day in daysOfWeek{

        switch day{
        case 0...4:
            return "It's a weekday"
        case 5..<day.count:
            return "It's a Weekend"
//I also tried
//     case 5,6:
//     return "It's a Weekend"
        default:
            return "Not a valid day"
        }
    }
    return nil
}

if let randomDay = weekdayOrWeekend("Monday"){
    println(randomDay)
}

It looks like you are only a little ahead of me, I am having some some trouble with for-in loop statement and if statement, I'll give you a screen shot of what I have if you can help me I would appreciate it. I can check back her later or you can write to me. If I get to where you are before you solve the answer I'll help you too. Imgur

Hans,

You've missed an if at the beginning. You just start by testing whether month == 1 but that's not preceded by the if keyword. I think the test being tested needs to be surrounded with brackets too:

if (month == 1) {
 // do stuff
} else if (month == 2) {
  // do some other stuff
} ... etc ... 

That should help. If not, please shout!

Steve.

16 Answers

Hi there,

Your cases aren't going to work like you envisage, I don't think. You're trying to use their index position within the array but the for-in loop will use the value in the array.

In the first iteration of for day in daysOfWeek the variable day will hold "Monday", not zero. However, you've also passed in day as a parameter for your method. I think the day in the for-in loop will be out of scope of the incoming parameter. But either way, there will be no number associated with either, so the switch won't functon as you hope.

You need to combine the two methods here, first, we need to loop through your array to see if there's a match. Then, process the point in the array, its index, with your switch/case approach.

func dayType (day: String) -> String {
    var typeOfDay: String = ""
    for var i = 0; i < daysOfWeek.count; i++ {
        if(day == daysOfWeek[i]){
          // process the array index
            switch i {
                case 0...4: typeOfDay = "It's a weekday"
                case 5, 6: typeOfDay = "It's weekend!"
                default: typeOfDay = "Unlikely to be a day"
            } // end switch
        } else {
            typeOfDay = "Not a day"
        } // end if
    }// end for
    return typeOfDay
} // end func

Here, I've used a standard for loop. This uses an index counter i that is incremented at each iteration. At each loop, we test to see if the value passed in as a parameter, day is equal to the contents of the array at daysOfWeek[i]. If it isn't, we carry on with the loop. If the two are equal, we pass the index i into the switch statement that you created.

That switch statement sets a string which is returned, as required.

I hope that makes sense!

Steve.

@ Steve Hunter Thanks for looking at that code. Someone pointed that out to me a a little earlier. I haven't had a chance to enter it yet. I switched over to Objective C, and I started working at midnight now its 7 am I need to get some rest soon. Thanks

Get some sleep! :-)

lance samaria
lance samaria
3,642 Points

@Steve, thanks for the answer. I see where I made the mistake of thinking the for-in loop would iterate through by index instead of value. One quick question, why did you need the 'else'? The 'if' combined with the 'switch' contains all the possible outcomes.

@Hans, thanks for looking at my question :)

The if and switch don't capture all the possible outcomes - that's why the else is there.

Work it through - you're dealing with a user-input function here - the user uses your function in their app a bit wrong and enters dayType("tomorrow");. What happens?

The switch case is never reached; the string day is never equal to a member of the array at any index. So, the function would skip the if and reach the return, returning a wholly unhelpful empty string as a legacy of its initialisation, rather than any text. So, I use an else to capture the scenario that the user may enter a mistyped day, or a nonsense string. In the alternative, you could initialise the string to something like "Not a day" but that isn't quite as clear. My preference, I guess.

It is, as you say, about handling all eventualities. When your code accepts parameters it is dangerous to assume that the right parameters will be passed in so having code that is verbose in its output helps pin down the route the code is taking. Outputting blank strings or nulls can be hugely confusing! Here, if you get a "Not a day" output, you know why. Swift insists on the default statement although, in this example, that is impossible to reach. The else is very easy to get to, though.

I hope that makes sense.

Steve.

lance samaria
lance samaria
3,642 Points

ahhhhhh, I see your point with misspellings and foreseeing all possible outcomes, but wouldn't the default be triggered "Unlikely to be a day"? It seems the default would take care of that.

No - the switch case is only reached if the parameter entered equals one of the elements of the array.

The default is never reached in the current guise - all the case statements cover all positive if outcomes. The default: would only be reached if I forgot to implement all the outcomes 0 ... 6.

Make sense?

Walk the code through with a misspelled parameter ... you'll see what I mean (and you'll end up in the else clause!).

lance samaria
lance samaria
3,642 Points

I ran it, your right, no matter what I type in I can't trigger the default statement. Even after I remove the else. As you said earlier the Swift language requires it. Just seems odd I can't get to it in this scenario.

lance samaria
lance samaria
3,642 Points

Thank you very much for your help and very fast answers! :-)

It isn't odd, no.

The default is triggered when none of the case tests match what is passed to the switch in the current iteration. In our example, we have a totally closed and controlled switch - we gain control with the day == daysOfWeek[i] as we only progress into the switch when we have a direct match, a true boolean outcome. We have validated the parameter passed in.

The array is fixed with 7 elements - that is immutable. The switch has all seven outcomes handled - each array element, 0 to 6, have a case representing them. So, it is impossible for the default to be triggered.

If we approached this from where you started this and switch on the value passed into the function and try matching that to the array elements, then the default would handle the erroneous entries as we don't validate the entry outside of the switch. It would be ugly code but:

func daySwitch(day: String) -> String {
    switch day {
        case "Monday",
             "Tuesday",
             "Wednesday",
             "Thursday",
             "Friday": return "Weekday"
        case "Saturday",
             "Sunday": return "Weekend"
        default: return "Not a day"
    }
}

This negates the need to declare your array completely but covers all eventualities and uses default to pick up the pieces of any mistakes. It remains, however, inelegant code.

Hope that all makes sense!

Steve.

lance samaria
lance samaria
3,642 Points

I understand now. In the example when you used the for-loop, the switch is contained inside the for-loop's block of code, since it iterates through the array using the index # (daysOfWeek[i]), if the user enters a parameter that doesn't match a value in the array, the for-loop kicks over to the else because it doesn't see the default (because it has no index # association it's invisible to the for-loop). Basically the for-loop is in control of the switch and the default doesn't have a index #. I get that.

In the example you just wrote the the switch is in control and if the user doesn't enter a matching value the default does trigger because it's like like the for-loops else.

correct?

Perfecto - spot on, yes. Full marks. :-)

Steve.

lance samaria
lance samaria
3,642 Points

Thank you very much!!! :-)

No problem! Glad to have helped!

Steve.

lance samaria
lance samaria
3,642 Points

Steve, one more question. Answer whenever you can. I redid the code using an integer for the argument and this time using the for-in loop (I understand it goes through by value), but when i create a variable to hold my function, no matter what # I put inside the argument, I get the same exact string "It's a weekday":

let daysOfWeek = [1, 2, 3, 4, 5, 6, 7]

func weekdayOrWeekend(day: Int) -> String? {

    for day in daysOfWeek{

        switch day{
        case 1...5:
            return "It's a weekday"
        case 6,7:
            return "It's a weekend"
        default:
            return "Not a valid day"
        }
    }
    return nil
}

//variable with function
var randomDay = weekdayOrWeekend(15)

If we go back to the start on this - I mentioned a scope problem.

The parameter you pass in, day can be referenced inside the function without any problem. However, If you create a new variable, also called day, that will be a completely different bit of memory. So, the day you've passed in as a parameter has nothing to do with the index of the for loop that also happens to be day.

The for loop uses a variable to hold the current iteration value as it loops through. You've called that day. That variable exists only inside the for loop and has no link to the day parameter you've passed into the method.

This bit of code loops through the array. As soon as its first iteration starts, day is 1 (irrespective of what you passed into the function for the reasons above). That satisfies the first case and it returns out of the function - no further execution occurs. Your parameter is never used.

    for day in daysOfWeek{

        switch day{
        case 1...5:
            return "It's a weekday"
        case 6,7:
            return "It's a weekend"
        default:
            return "Not a valid day"
        }
    }

Think about what you're trying to achieve here. You're looping through an array. At each loop, you want to do something. Does that involve the parameter day? Probably. This example here makes the array serve no purpose. looping through the array adds nothing. The switch case applies functionality to each of those numbers, but they don't need to link to the array at all. Just the same as my last switch case did away with the array; this is exactly the same but with integers rather than strings.

I think the best thing to do is to do what a Business Analyst would do - define the requirements; what is this function supposed to do? Put that into words then worry about writing code. What does the array do? How is the parameter day used & why? How is the function going to be used?

As you've presented it, you want a function that takes an integer as a parameter and returns one of three strings depending on its value. Why does the array exist? (Bounds checking?)

The solution to that problem is a modification of my last string-based switch code:

func daySwitch(day: Int) -> String {
    switch day {
        case 1...5: return "Weekday"
        case 6, 7: return "Weekend"
        default: return "Not a day"
    }
}

No looping; no array but job done? What's the loop achieving that the above code doesn't? How does the array bring anything to the party?

Maybe the array isn't consecutive numbers - maybe it's your bingo card. As each number is drawn, you want to know if you've got that number, or not. Now we have a need for a loop and an array - that would be a real use for that type of function.

Let's have a quick go ...

let myCard =  [10, 3, 5, 34, 22, 17]

func numberDrawn(ball: Int) -> String{
    for number in myCard { // number will be each value above, in succession
        if(number == ball){ // does the current value from the array equal the parameter?
            return "Tick that box" // Yes it does.
        }
    }
    return "Next ball please!" // no it doesn't 
}

Not a great example, after all - we don't need a switch.

I'll try to think of another one ... I'll post this for now.

I'm trying to illustrate when best to use each type of construct. Your use of the loop isn't quite right due to scoping but also due to what a loop is there to do. That context is contrary to what you're trying to use it for. You were using, you thought, the parameter you passed in at the beginning of the for loop - presumably hoping for a test of equality? That's not quite right.

I hope that's of some use to you.

Steve.

lance samaria
lance samaria
3,642 Points

I have to look your notes over more intently. See I understand loops and conditionals, but using them in the right context seems to be my problem. When to use a 'for' vs a 'for-in' and an 'if-else' vs a 'switch' and combining then them -or not- is where it gets tricky. The first question you answered for me I understand 100%. For this example I do see your point in saying the for-in loop is pointless as the switch statement accomplishes the same exact task. I see why your saying the array is pointless for days of the week but if it were something such as school test scores I would think you would need to give the user something to reference from eg. let grades = [0...60, 61...69, 70...80, 81...90, 91...100] and using a switch would easily give grade scores F-A as instead of looping through the grades.

It makes sense to write the problem out first and then try to find the easiest way to solve it. I was at the beginning of the Enum track and wanted to try to accomplish this without using Enums.

I'll try to get it down packed and send you a message later. Thanks again!

The grades example you came up with is a great example of where a switch/case construct can be used. I'd implement it slightly differently.

So, you could have an array of all the test results in the array - so the marks for each individual student; loop through that array and count how many students are in each grade bracket using the switch/case. Inside each case, increment a counter for each grade.

There are much better ways of doing that but the task does require a loop and a switch/case! And you've got no link to the student's names, but hey.

You say you "wanted to try to accomplish this without using Enums" - it's the this that needs defining. What's the problem we're trying to solve.

Steve.

lance samaria
lance samaria
3,642 Points

This is something I came up with in the shower just now. I'm not sure if it's exactly what you were describing but it let's you input a student's name and associates that name with whatever score and grade you enter.

func studentNameAndGrade(#studentName: String, #studentGrade: Int) -> String{

    var student = studentName
    var score = studentGrade

    switch studentGrade {
    case 0...59:
        return "\(student)'s score is: \(score) and grade is a F"
    case 60...70:
        return "\(student)'s score is: \(score) and grade is a D"
    case 71...80:
        return "\(student)'s score is: \(score) and grade is a C"
    case 81...90:
        return "\(student)'s score is: \(score) and grade is a B"
    case 91...100:
        return "\(student)'s score is: \(score) and grade is an A"
    default:
        return "Invalid score please re-enter a score between 0-100"

    }

}

var sample = studentNameAndGrade(studentName: "Rael", studentGrade: 99)
sample

Nice, yes. That does the job. One thing - remember D.R.Y? "Don't Repeat Yourself" is a good coding rule.

So to tidy that up a bit we could do something like:

func studentNameAndGrade(#studentName: String, #studentGrade: Int) -> String{

    var student = studentName
    var score = studentGrade
    var grade: String

    switch studentGrade {
    case 0...59:
        grade = "F"
    case 60...70:
        grade = "D"
    case 71...80:
        grade = "C"
    case 81...90:
        grade = "B"
    case 91...100:
        grade = "A"
    default:
        return "Invalid score please re-enter a score between 0-100"

    }
    return "\(student)'s score is: \(score) and grade is a \(grade)"
}

Just looks a little neater, perhaps.

lance samaria
lance samaria
3,642 Points

Your absolutely right. I was thinking about it earlier. Thank very much for your help. I'm sure I'm going to have many more questions.

Talk to you soon!

āœŒāœŒ

No problem - ask away!

Talk soon, yes.

Steve.