• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 27/04/2023

Enhance your functions with parameters

Using functions, as we can see, clearly improved our code. In some cases, however, it appears a bit inflexible.

We can improve our functions by making it more general and more flexible. This technique is called abstraction: a way to make a part of the code less dependent on a particular case.

In the previous chapter we created a function that calculates the profit from coffee sales. We can use the exact values we know apply to coffee: its cost and its price.

It's good that Jenny just wants to play with some average data for her coffee drinks. What if she decided to get more into detail and separate the profit for various types of coffee drinks - espresso, cappuccino, macchiato? We would end up with an endless list of functions :waw:.

How can we overcome this lack of flexibility?

To solve this we would need a function that can calculate profit independently from particular numbers - cost and price. Even the category of items shouldn't matter. After all the profit calculation is the same for coffee or cake, right?

With functions, we can achieve that using parameters.

Parameters of a function

Parameters are values that we can pass to a function to utilize within the code of that function.

Parameters' syntax

To define parameters for a function we need to declare them within the parentheses that follow the name of a function in its declaration.

Parameters are variables and as we know, to declare variables, we need to specify a combination of name and type:

func functionName(label: Type) -> Type {
    // Instructions
}

And then call that function:

var b = functionName(label: Value) 

Labeling parameters improves readability when calling a function. This way, we know what that parameter refers to instead of just providing plain values.

Let's create a function to increment an integer value:

// declaring
func increment(a: Int) -> Int {
    return a + 1
}

// calling
print(increment(a: 5)) // 6

It's important to note that parameters appear as  constants and cannot be modified within a function. Attempting to  modify it will generate an error:

func increment(a: Int) -> Int {
    a += 1 // Error
    return a
}

If we need more than one parameter per function, we simply list them all separated by commas:

// declaring
func max(a: Int, b: Int) -> Int {
    if a > b {
        return a
    }
    return b
}

// calling
print(max(a: 2, b: 7)) // 7

The examples above all return a value. Functions with parameters are not required to return any value:

// declaring
func greet(person: String) {
    print("Hi, \(person)!")
}

// calling
greet(person: "Jenny") // Hi, Jenny!

When calling a function with parameters, only parameters of the declared type can be passed to that function. Attempting to pass values of different types will result in an error. For example, the following will not work:

greet(person: 11) // Error

What type of parameters can we use? 

Parameters can be of any type! Including more complex ones like arrays and dictionaries:

// declaring
func introduce(people: [String]) {
    for person in people {
        print("This is \(person)!")
    }
}

// calling
introduce(people: ["Jenny", "Livia", "Paul"])

That would generate this output in console:

This is Jenny!
This is Livia!
This is Paul! 

Creating labels

Labels are like variable names need to be descriptive. When you create labels for parameters, it has the additional benefit of appearing next to the name of the function. This means you can read them together. This is especially true for the first parameter.

For example, we can use generic naming:

func goToMovie(name: String) {
    print("I will fo to a movie with " + name)
}

goToMovie(name: "Bob") // "I will fo to a movie with"

Or, we can make it more contextual:

func goToMovie(with: String) {
    print("I will fo to a movie with " + with)
}

goToMovie(with: "Bob") // "I will fo to a movie with"

Both approaches are valid. It's up to you to choose the most appropriate one in any particular situation.

 

Time to practice again! Create a function to calculate the sum of total hours a student spends studying per week. Imagine we know how many hours a particular student studies each day during a given week.  Present the results for 3 students in the form of a phrase such as "Jenny spends X hours studying per week" : 

// Try on your own!
















































func totalWeeklyHours(weeklyHours: [Int]) -> Int {
    var total = 0
    for dailyHours in weeklyHours {
        total += dailyHours
    }
    return total
}

print("Jenny spends \(totalWeeklyHours(weeklyHours: [5, 6, 3, 4, 8, 1, 2])) hours studying per week")
print("Livia spends \(totalWeeklyHours(weeklyHours: [3, 4, 8, 1, 2, 0, 0])) hours studying per week")
print("Paul spends \(totalWeeklyHours(weeklyHours: [0, 6, 3, 4, 8, 10, 2])) hours studying per week")

Observe the output in console:

Jenny spends 29 hours studying per week
Livia spends 18 hours studying per week
Paul spends 33 hours studying per week

Well, you may have gotten your version much cleaner, but I clearly need to improve mine - I'm not a fan of those multiple print functions :colere:.

How can this be improved?

Let me try improving it my using a dictionary to store the initial data and then just walk through it to present the results:

var students = [String:[Int]]()
students["Jenny"] = [5, 6, 3, 4, 8, 1, 2]
students["Livia"] = [3, 4, 8, 1, 2, 0, 0]
students["Paul"] = [0, 6, 3, 4, 8, 10, 2]

for (student, hours) in students {
    print("\(student) spends \(totalWeeklyHours(weeklyHours: hours)) hours studying per week")
}

Looks much better now! Let's move on!

So, how can parameters improve our profit calculation function?

Here's our original version:

func coffeeProfit() -> Double {
    let cost = 0.50 + 1.00
    let price = 2.70
    let profit = price - cost
    return profit
}

There are two numbers that are specific to coffee at the moment: cost and price. To abstract our function, we need to make it independent of these specifics. We can do that by turning them into parameters. Since our new version is going to be more general, we can rename it in order to reflect this change.  We can also remove the line where we store profit as a variable and can return it right away:

func profit(cost: Double, price: Double) -> Double {
    return cost - price
}

Then calling our new function:

var coffeeProfit = profit(cost: 2.70, price: 0.50 + 1.00)
print ("Profit for one portion of coffee is $\(coffeeProfit)") 

And observing the result in console:

Profit for one portion of coffee is $1.2

All this is wonderful! Our function is now extremely abstracted.

How do we know which numbers to pass?

We need to determine which category we are working with at each moment - coffee, dessert, food, or maybe even another new one.

In case Jenny decides to go into the details of each category, we will need to determine which item within each category we are calculating it for - espresso, cappuccino and so on.. who knows what other fancy drinks Jenny will come up with?!

We already know one trick that could address this need - using if/else - if/else statements and chain them together as many times as the cases we need to cover.

This could be a solution, but it's not the optimal one. This is like how arrays were not quite the perfect solution for our needs earlier.

To create a more elegant solution we will learn a new control flow: Switch.

Switch control flow

Just a reminder of the control flows we know:

  • for-in loop

  • while loop

  • if /else statement

  • and now switch

Switch uses a condition similar to an if/else statement. It checks if a specified value is equal to one of the options within the switch and executes a part of code associated with the matching option.

If nothing matches, it executes the default option. This is just like the final else clause in an if/else statement.

There's a difference with if/else structure; Switch can function using just an if part without any else part at all. With Switch, we must have a default option.

Switch syntax

Here's the switch syntax:

switch variable {
    case value1:
    // Instructions 1
    case value2:
    // Instructions 2
    case value3:
    // Instructions 3
    default:
    // Default instructions
}

Let's see how it functions:

  • The  variable   is compared to    value1 , and if they are equal, a block with Instructions 1 is executed and then continues to the code that follows the whole switch section. No other blocks of instructions get executed.

  • If the first value doesn't match the variable, then the variable is compared to  value2 , then processed in the same fashion as the case for value1 if it matches. Alternatively, if it doesn't match again, the program moves on to the  value3  .

  • etc...

If none of the specified values match the variable, then the  default  section is executed.

If we were to achieve the same result using if/else statements, the code would look like this:

if variable == value1 {
    // Instructions 1
} else if variable == value2 {
    // Instructions 2
} else if variable == value3 {
    // Instructions 3
} else {
    // Default nstructions
}

Almost done! With the second challenge of our SmartBean project, we are approaching finish line!

Finalizing the SmartBean project

Let's remind ourselves where we left off:

var profits = ["coffee": 0.0, "dessert": 0.0, "food": 0.0]

// profit calculation for 2 months
for day in 1 ... 60 {
    if (day % 7 == 5) { // if Saturday
        profits["coffee"]! += (2.70 - (0.50 + 1.00)) * 450 // coffee
        profits["dessert"]! += (5.50 - (3.50 + 0.00)) * 180 // dessert
        profits["food"]! += (15.0 - (7.00 + 5.00)) * 120 // food
    }
    else if (day % 7 != 6) { // other days of the week excluding Sundays
        profits["coffee"]! += (2.70 - (0.50 + 1.00)) * 560 // coffee
        profits["dessert"]! += (5.50 - (3.50 + 0.00)) * 240 // dessert
        profits["food"]! += (15.0 - (7.00 + 5.00)) * 80 // food
    }
}

// find out the most profitable category
if (profits["coffee"]! > profits["dessert"]! && profits["coffee"]! > profits["food"]!) { // compare the first element to the rest
    print("Coffee is the most profitable category")
}
else if (profits["dessert"]! > profits["coffee"]! && profits["dessert"]! > profits["food"]!) { // compare the second element to the rest
    print("Dessert is the most profitable category")
}
else if (profits["food"]! > profits["coffee"]! && profits["food"]! > profits["dessert"]!) { // compare the third element to the rest
    print("Food is the most profitable category")
}

// initial total profit
var totalProfit = 0.0

// put together profits for all categories
for (_, profit) in profits {
    totalProfit += profit
}

// preset the final result
print("The total profit for 2 months is $\(totalProfit)")

Our profit calculating function:

func profit(cost: Double, price: Double) -> Double {
    return price - cost
}

What improvements can we implement here?

I recommend the following:

  • Use dictionaries to define initial data: costs and prices and units sold on weekdays and weekends.

  • Create a function to calculate the cost using the initial data: cost of ingredients and cost of service. We can call it  cost .

  • Utilize the  profit  and  cost   functions to calculate the profits for items and incorporate dictionaries. 

  • Optimize repeated pieces of code and use variables (which could also be dictionaries) to store them until the pieces of code can be utilized in conditional sections. For example, you may have to calculate the profits for any category, despite the day of the week, and then multiply the result by required number of units in a conditional statement for weekdays and weekends.

  • Use a for-in loop to walk the elements of the profits dictionary to calculate profit values for each element.

  • Bonus-improvement: Use a switch instead of an if/else when making a distinction for different days of the week. 

  • Use a for-in loop on a profits dictionary to determine the most profitable category.

  • Use a switch to display the result for the determined final "most profitable" category.

As usual, take your time to try to improve your code incorporating the newly learned techniques.

Meanwhile, I will present the recommended improvements here below.

Take your time...
Take your time...

Implementing recommended improvements

1. Use dictionaries to define initial data

For this we need to store two pieces of information: price and cost. Our cost, in turn, is a calculated value, we can use the cost of ingredients and the cost of service to calculate it. In this case it's reasonable to store the components instead:

// product info
var price = ["coffee": 2.70, "dessert": 5.50, "food": 15.0]
var costOfIngredients = ["coffee": 0.50, "dessert": 3.50, "food": 7.00]
var costOfService = ["coffee": 1.0, "dessert": 0.0, "food": 5.0]

// sales
var unitsWeekdays = ["coffee": 560, "dessert": 240, "food": 80]
var unitsWeekend = ["coffee": 450, "dessert": 180, "food": 120]

2. Create a function to calculate the cost

func cost(costOfIngredients: Double, costOfService: Double) -> Double {
    return costOfIngredients + costOfService
}

3.  Utilize functions and incorporate dictionaries

// profit calculation for 2 months
for day in 1 ... 60 {
    if (day % 7 == 5) { // if Saturday
        profits["coffee"]! += profit(cost: cost(costOfIngredients: costOfIngredients["coffee"]!, costOfService: costOfService["coffee"]!), price: price["coffee"]!) * Double(unitsWeekends["coffee"]!)
        profits["dessert"]! += profit(cost: cost(costOfIngredients: costOfIngredients["dessert"]!, costOfService: costOfService["dessert"]!), price: price["dessert"]!) * Double(unitsWeekends["dessert"]!)
        profits["food"]! += profit(cost: cost(costOfIngredients: costOfIngredients["food"]!, costOfService: costOfService["food"]!), price: price["food"]!) * Double(unitsWeekends["food"]!)
    }
    else if (day % 7 != 6) { // other days of the week excluding Sundays
        profits["coffee"]! += profit(cost: cost(costOfIngredients: costOfIngredients["coffee"]!, costOfService: costOfService["coffee"]!), price: price["coffee"]!) * Double(unitsWeekdays["coffee"]!)
        profits["dessert"]! += profit(cost: cost(costOfIngredients: costOfIngredients["dessert"]!, costOfService: costOfService["dessert"]!), price: price["dessert"]!) * Double(unitsWeekdays["dessert"]!)
        profits["food"]! += profit(cost: cost(costOfIngredients: costOfIngredients["food"]!, costOfService: costOfService["food"]!), price: price["food"]!) * Double(unitsWeekdays["food"]!)
    }
}

Wait a minute, isn't it harder to read than before?

It may seem like it's not as clear as our original version,  since the code itself has become visibly longer. However, I can assure you that it's just for the moment :ange:!

4. Optimize repeated pieces of code 

// profit calculation for 2 months
for day in 1 ... 60 {
    
    var units: [String: Int]
    
    if (day % 7 == 5) { // if Saturday
        units = unitsWeekends
    }
    else if (day % 7 != 6) { // other days of the week excluding Sundays
        units = unitsWeekdays
    }
    else {
        units = ["coffee": 0, "dessert": 0, "food": 0]
    }
    
    profits["coffee"]! += profit(cost: cost(costOfIngredients: costOfIngredients["coffee"]!, costOfService: costOfService["coffee"]!), price: price["coffee"]!) * Double(units["coffee"]!)
    profits["dessert"]! += profit(cost: cost(costOfIngredients: costOfIngredients["dessert"]!, costOfService: costOfService["dessert"]!), price: price["dessert"]!) * Double(units["dessert"]!)
    profits["food"]! += profit(cost: cost(costOfIngredients: costOfIngredients["food"]!, costOfService: costOfService["food"]!), price: price["food"]!) * Double(units["food"]!)
}

Much better now!

5. Use a for-in loop to calculate the profit

// profit calculation for 2 months
for day in 1 ... 60 {
    
    var units: [String: Int]
    
    if (day % 7 == 5) { // if Saturday
        units = unitsWeekends
    }
    else if (day % 7 != 6) { // other days of the week excluding Sundays
        units = unitsWeekdays
    }
    else {
        units = ["coffee": 0, "dessert": 0, "food": 0]
    }
    
    for (category, _) in profits {
        profits[category]! += profit(cost: cost(costOfIngredients: costOfIngredients[category]!, costOfService: costOfService[category]!), price: price[category]!) * Double(units[category]!)
    }
}

Looks even more elegant now!

6. Bonus-improvement: Use switch to optimize the distinction between weekdays and weekends

The code I have works fine (not too complicated), however it would be much more readable using switch instead of if/else:

switch day % 7 {
    case 5: // Saturdays
        units = unitsWeekends
    case 6: // Sundays
        units = ["coffee": 0, "dessert": 0, "food": 0]
    default: // Weekdays
        units = unitsWeekdays
    }

 7. Use a for-in loop to determine the most profitable category

This improvement makes our logic independent (abstract) from the number of categories as well as the category names (dictionary keys):

// find out the most profitable category
var key = ""
var maxProfit = 0.0

for (category, profit) in profits {
    if (profit > maxProfit) {
        maxProfit = profit
        key = category
    }
}

8. Use switch to display the final-winner (the most profitable category)!

We could technically use our dictionary key to incorporate it into the final announcement. However, it's best to keep components used in the logic separate from what's presented to the user in case we need to change the presentation. Changes like this could include translation into a different language or simply saying "Delicious coffee" instead of "coffee". Let's do just that! Also, let's alter the final presentation a bit by including the maxProfit for the winning category:

var categoryTitle: String
switch key {
case "coffee":
    categoryTitle = "Delicious coffee"
case "dessert":
    categoryTitle = "Divine desserts"
case "coffee":
    categoryTitle = "Gourmet food"
default:
    categoryTitle = "Unknown"
}

//display the winner category
print("\(categoryTitle) is the most profitable category that generated $\(maxProfit) in 2 months")

And, FINALLY, putting it all together:

// SmartBean challenge #2. FINAL implementation
func profit(cost: Double, price: Double) -> Double {
    return price - cost
}

func cost(costOfIngredients: Double, costOfService: Double) -> Double {
    return costOfIngredients + costOfService
}

var profits = ["coffee": 0.0, "dessert": 0.0, "food": 0.0]

// product info
var price = ["coffee": 2.70, "dessert": 5.50, "food": 15.0]
var costOfIngredients = ["coffee": 0.50, "dessert": 3.50, "food": 7.00]
var costOfService = ["coffee": 1.0, "dessert": 0.0, "food": 5.0]

// sales
var unitsWeekdays = ["coffee": 560, "dessert": 240, "food": 80]
var unitsWeekends = ["coffee": 450, "dessert": 180, "food": 120]

// profit calculation for 2 months
for day in 1 ... 60 {
    
    var units: [String: Int]
    
    switch day % 7 {
    case 5: // Saturdays
        units = unitsWeekends
    case 6: // Sundays
        units = ["coffee": 0, "dessert": 0, "food": 0]
    default: // Weekdays
        units = unitsWeekdays
    }
    
    for (category, _) in profits {
        profits[category]! += profit(cost: cost(costOfIngredients: costOfIngredients[category]!, costOfService: costOfService[category]!), price: price[category]!) * Double(units[category]!)
    }
}

// find out the most profitable category
var key = ""
var maxProfit = 0.0

for (category, profit) in profits {
    if (profit > maxProfit) {
        maxProfit = profit
        key = category
    }
}

var categoryTitle: String
switch key {
case "coffee":
    categoryTitle = "Delicious coffee"
case "dessert":
    categoryTitle = "Divine desserts"
case "coffee":
    categoryTitle = "Gourmet food"
default:
    categoryTitle = "Unknown"
}

//display the winner category
print("\(categoryTitle) is the most profitable category that generated $\(maxProfit) in 2 months")

// initial total profit
var totalProfit = 0.0

// put together profits for all categories
for (_, profit) in profits {
    totalProfit += profit
}

// preset the final result
print("The total profit for 2 months is $\(totalProfit)")

Congrats on implementing your version of abstracting and improving the code!

Our latest optimization has resulted in using too many dictionaries; it's easy to get lost. It may also create a complication down the road. For instance, if Jenny added another category, we would need to update all the dictionaries separately.

Can we optimize it even further?

We can definitely make further improvements! However, not in this course :p.

Code review

It's totally cool if your thought process and code doesn't match exactly what's presented here. The more complex the logic gets, the more different, suitable solutions become possible.

The important part is that it achieves the same results and is implements it in its own elegant way.

Let's Recap!

  • Functions can accept parameters, which can be utilized within the function's body.

  • Using parameters allows for the  creation of more flexible abstract functions.

  • The variable scope of parameters is the body of that function. Parameters in this context operate as though they were declared with that function:

    func functionName (Param1: Type1, param2: Type2) {
        // Instructions using Param1 and Param 2 like they were local variables
    }

    After declaring a function with parameters, the function can be called with specific values:

    functionName (param1: value1, param2: value2)
  • Parameters have labels that become variable names within the function and provide for better readability when calling a function. If labels are obvious they can be omitted when calling the function.

  • When calling a function with parameters, parameters of the declared types must be provided. A function cannot accept parameters of any alternative types.

  • A function can have any number of parameters, including ZERO (a function without parameters).

  • The  switch    is a control flow that uses following syntax: 

    switch variable {
        case value1:
        // Instructions 1
        case value2:
        // Instructions 2
        case value3:
        // Instructions 3
        default:
        // Default instructions
    }

     

What's NeXT?

And now we have Happy Jenny equipped with all the knowledge necessary to develop a strategy to grow her business. Doesn't it feel satisfying to be able to help someone out :zorro:?!

What’s NeXT for you now?

I encourage you to continue learning the Swift programming language and to take the next course on Object Oriented Programming.

Exemple de certificat de réussite
Exemple de certificat de réussite