A+ will be down for a version upgrade on Thursday October 17th 2024 at 09:00-12:00.
This course has already ended.

The latest instance of the course can be found at: O1: 2024

Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.

Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.

Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 5.1: Logic, Blood, and Shopping

About This Page

Questions Answered: How do I write “If either that or that condition is met?” Or “If both conditions are met?” Can I write Scala classes independently already?

Topics: Logical operators are new. The programming assignments combine assorted earlier topics.

What Will I Do? Program, mostly. There’s a bit of reading at the beginning.

Rough Estimate of Workload:? Two or three hours? And quite a bit more if you do all the optional assignments, too.

Points Available: A120.

Related Projects: FlappyBug, Miscellaneous, AuctionHouse1 (new).

../_images/person081.png

Intro: Let’s Make FlappyBug Harder

In Chapter 3.3, you wrote isLost, a method that defines a Game of FlappyBug to be over if the obstacle touches the bug.

def isLost = this.obstacle.touches(this.bug)
Recap: touches returns true or false. Consequently, isLost also return one of the two Booleans.

As things stand, it’s too easy for the bug to advance simply by lurking at the top or bottom. Let’s edit touches so that the player loses if an obstacle hits or or the bug is at the top or bottom of the playing area.

Here’s one way to do it:

def isLost =
  if (this.obstacle.touches(this.bug))
    true
  else if (this.pos.y <= 0)
    true
  else
    this.pos.y >= GroundY
The game is lost if the bug hits an obstacle.
Or if it didn’t hit an obstacle but is too high.
Or if it isn’t too high either but is too low.

That works. But it’s a very complicated way of expressing something rather simple. We’d much rather write: “If that condition or that other condition is met.” Perhaps you’ve already yearned after such an expression in the earlier chapters.

In this chapter, you’ll learn to combine Boolean values with logical operators such as “and” and “or”. We’ll get back to FlappyBug after a general introduction to these operators.

Logical Operators

The Scala language supplies us with several logical operators (logiikkaoperaattori).

Operator Name Example Meaning
&& and someClaim && otherClaim “Are both Booleans true?”
|| or someClaim || otherClaim “Is at least one of the Booleans true?”
^ exclusive or (xor) someClaim ^ otherClaim “Is exactly one of the Booleans true?”
! not (negation) !someClaim “Is the Boolean false?”

In O1, you’ll mostly find use for &&, ||, and !. The last of those you already know from earlier chapters.

The logical operators take in Boolean values and also produce Booleans:

val number = 50number >= 0 && number <= 5res0: Boolean = false
if (number >= 0 && number <= 5) "a valid grade" else "an invalid grade"res1: String = "an invalid grade"
number == 100 || number == 50res2: Boolean = true
!(number >= 0)res3: Boolean = false
The “and” operator && produces true if and only if both subexpressions are true. Here, the first subexpression is true but the second isn’t, so the whole expression evaluates to false.
The “or” operator || produces true if either of the two subexpressions is true (or both are). Here, the first subexpression isn’t true but the second is, so the whole expression evaluates to true.
Terminological note: the subexpressions that an operator works on are often called operands (operandi). Of the logical operators introduced here, the negation operator ! takes only a single operand while the others take two.

Non-strict evaluation of logical operators

The operators && and || are evaluated non-strictly (väljästi). What this means for these operators is essentially that the left-hand operand is evaluated first and if it alone determines the value of the logical operation, the right-hand operand isn’t evaluated at all. (We will take a broader look at non-strict evaluation in Chapter 7.1.)

This non-strictness has practical significance, as illustrated below.

First, let’s define a couple of variables. Here, we use literals, but you can easily imagine the numbers being read from the user’s keyboard, for instance.

val dividend = 50000dividend: Int = 50000
var divisor = 100divisor: Int = 100

Let’s divide one number with the other and see what we get. As we do so, let’s play things safe and ensure that divisor doesn’t equal zero. We can use either && or || to do that:

divisor != 0 && dividend / divisor < 10res4: Boolean = false
divisor == 0 || dividend / divisor >= 10res5: Boolean = true

Assuming the divisor is not zero, we can swap the operands and still get the same result:

dividend / divisor >= 10 || divisor == 0res6: Boolean = true

But what if the divisor is zero?

divisor = 0divisor: Int = 0
dividend / divisor >= 10 || divisor == 0java.lang.ArithmeticException: / by zero
...

The computer performs the division as it evaluates the first operand. We get a runtime error. We never even reached the expression divisor == 0.

On the other hand, if we check the divisor in the first operand, things work out fine:

divisor == 0 || dividend / divisor >= 10res7: Boolean = true

Since divisor equalled zero, the expression divisor == 0 evaluated to true. It becomes unnecessary to evaluate the rest of the logical expression given that true || anythingAtAll is always true. Because of the non-strictness of these operators, the computer doesn’t even evaluate the second operand. Execution order matters!

Suppose you have the variables divisor and dividend as above and divisor equals zero. Below are a number of claims about the expression divisor != 0 && dividend / divisor < 10. Which of them are correct? Select all that apply.

Combining logical operators

You can use rounds brackets to group logical expressions just like arithmetic and other expressions.

if ((number > 0 && number % 2 == 0) || number < 20) {
  println("Either both positive and even, or less than 20.")
}
if (number > 0 && (number % 2 == 0 || number < 20)) {
  println("Positive; moreover, either even or less than 20.")
}

Practice on logical operators

Study the following piece of code. Carefully consider what happens as it runs. You might wish to make notes on a piece of paper.

val first = 20
val second = 10
var third = 50

if (first > second && first + second < third) {
  third = 30
  println("One")
}

val result = first > second && first + second < third

if (first < second || result) {
  println("Two")
}

if (first > second || second == third) {
  println("Three")
}

third = 10

if (!(result && third >= 50)) {
  println("Four")
} else {
  println("Five")
}
In the second if above, the || operator in first < second || result has two operands. What happens when this expression is evaluated?
In the third if above, the || operator in first > second || second == third has two operands. What happens when this expression is evaluated?
Which lines of text does the above code print out?

Further improvements to the vending machine

Logical operators are a convenient replacement for multiple ifs.

Recall the sellBottle method from Chapter 3.5. Here is the original Int-returning version:

def sellBottle() = {
  if (this.isSoldOut) {
    -1
  } else if (!this.enoughMoneyInserted) {
    -1
  } else {
    this.earnedCash = this.earnedCash + this.bottlePrice
    this.bottleCount = this.bottleCount - 1
    val changeGiven = this.insertedCash - this.bottlePrice
    this.insertedCash = 0
    changeGiven
  }
}

Here are two attempts to reimplement the method with logical operators:

def sellBottle() = {
  if (this.isSoldOut || !this.enoughMoneyInserted) {
    -1
  } else {
    this.earnedCash = this.earnedCash + this.bottlePrice
    // Etc. As above.
  }
}
def sellBottle() = {
  if (this.enoughMoneyInserted && !this.isSoldOut) {
    this.earnedCash = this.earnedCash + this.bottlePrice
    // Etc. As above.
  } else {
    -1
  }
}
Assess the two shorter implementations.

Assignment: FlappyBug (Part 15 of 17: Deadly Edges)

Task description

Make FlappyBug harder as suggested at the top of the chapter. Do it as follows.

  1. Add an effect-free, parameterless method isInBounds in class Bug. It should return a Boolean that indicates whether the bug is within the legal playing area, that is, between the zero coordinate and the ground level (excluding those border values). Use a logical operator to implement the method.
  2. Modify isLost in class Game so that it also considers a game to be lost if the bug is out of bounds. Use logical operators and the isInBounds method you just wrote.

Submission form

A+ presents the exercise submission form here.

Assignment: Blood Types

Task description

The project Miscellaneous contains the package o1.blood, which in turn contains the class BloodType. Implement that class so that it matches the documentation that comes with the project.

Instructions and hints

  • This example shows how the class should work:

    import o1.blood._import o1.blood._
    val myBlood = new BloodType("AB", true)myBlood: o1.blood.BloodType = AB+
    val yourBlood = new BloodType("A", true)yourBlood: o1.blood.BloodType = A+
    val theirBlood = new BloodType("O", false)theirBlood: o1.blood.BloodType = O-
    myBlood.canDonateTo(yourBlood)res8: Boolean = false
    yourBlood.canDonateTo(myBlood)res9: Boolean = true
    theirBlood.canDonateTo(yourBlood)res10: Boolean = true
    
  • You can also run the given BloodTest app to try out all the possible combinations of blood types.

  • Once again, you don’t have to attend to special cases or erroneous input unless specifically instructed to. For example, you can count on any user of class BloodType to pass in only meaningful strings as constructor parameters.

  • Try to implement the methods as simply as you can and without duplicating the same piece of code.

    • Use logical operators. Can you make do without if and match altogether (at least outside toString)?
    • Call the other methods of the same class. Further tip: you can pass this as a parameter to a method.

Submission form

A+ presents the exercise submission form here.

Assignment: FixedPriceSale

The Scaladocs in project AuctionHouse1 describe several classes for representing items that have been put up for sale in an imaginary online auction house. In this assignment, we’ll focus on a single way to sell an item: simply giving it a fixed price. Later assignments will features auctions and changing prices.

Task description

Fetch the project AuctionHouse1 and view its Scaladocs. Implement class FixedPriceSale of package o1.auctionhouse so that its behavior matches the documentation.

Instructions and hints

  • Start by creating a new file for the class. Here’s a recap:

    • Right-click the package in Package Explorer and select New.
    • Ensure that the package name o1.auctionhouse appears in the Name field where you enter the new class’s name.
    • A new Scala file should appear within the package and have the appropriate package definition at the top.
  • You may wish to use an instance variable of type Option, with an initial value of None. If you do, remember that you need to annotate the variable with a data type (as in Chapter 4.3). Like this: nameOfVariable: Option[Type].

  • The Scaladocs list the class’s members in alphabetical order, but that’s probably not the most convenient order for you to implement them (nor do the methods need to be in that order in your code). You are free to choose the order in which you implement the methods, but here’s one suggestion: daysLeft, toString, buyer, isExpired, isOpen, advanceOneDay, buy.

  • Study the docs with care. Notice, for instance, that “open” and “expired” aren’t exact opposites even though an item cannot be both at the same time.

  • You can use the app object FixedPriceSaleTest for testing. Use it.

    • Note that the app object initially generates error messages, which is simply because you haven’t yet implemented the methods that the app calls. The errors will go away once you do the assignment. (Remember: the cause of an error is not always where the message points to.)
    • You can initially “comment out” part of the test app if you want to test a partial version of your class.
  • You can also run TestApp, a program in o1.auctionhouse.gui that lets you create and manipulate FixedPriceSale objects in a graphical window that looks like this:

    ../_images/auctionhouse1_gui.png

Submission form

A+ presents the exercise submission form here.

Optional Practice: Moment and Interval

The rest of the chapter consists of practice tasks that are voluntary but highly recommended. Take them as an opportunity to build up fluency. If the weekly deadline is ominously close, you might wish to do the other chapters first before returning here. The assignments below will probably take several hours to complete.

Introduction to o1.time

This problem will let you apply the logical operators more extensively and freely than the preceding ones. It also provides practice on a number of other concepts.

Moments and intervals

Imagine we have an application (or several) that needs to represent time intervals: an interval begins at a particular moment of time and continues until another moment. We intend to model these concepts as the classes Moment and Interval. Each Interval object is associated with two Moment objects that represent the interval’s first and last moment.

Project Miscellaneous contains a package o1.time and therein a partial implementation for class Moment. Interval is missing entirely.

The given code for Moment is explained below.

import scala.math.abs

class Moment(private val time: Int) {

  override def toString = this.time.toString

  def distance(another: Moment) = abs(another.time - this.time)

  def isLaterThan(another: Moment) = this.time > another.time

  def later(another: Moment) = if (this.isLaterThan(another)) this else another

  def earlier(another: Moment) = if (this.isLaterThan(another)) another else this

}
As we create a Moment instance, we specify the moment in time that it stands for. Our implementation doesn’t commit to any particular unit of time (days, years, nanoseconds, etc.); any integer will do.
A Moment delegates its whole toString method to the integer it stores. Notice that you can call a method on an Int! An Int’s toString method returns the characters that correspond to the number; e.g., for the integer 123 we get the string "123". (We will discuss the methods on Ints further in the next chapter, 5.2.)
There’s a distance method for determining how far apart two moments are.
isLaterThan, later, and earlier compare moments in different ways.

General hint: make use of abstractions

One of this problem’s main themes is to learn to make use of the other methods in the classes that you write. Sure, you’ve already done that in ealier assignments, too, but this one particularly highlights the matter.

If you write the required methods completely independently of each other, you’ll find yourself duplicating code. Don’t do that. Consider what other methods you’ve created that you could call. Try to keep your code DRY. Non-redundant code is easier to modify and less vulnerable to bugs.

In fancier words: Every method is an abstraction (Chapter 1.6). You should build new abstractions atop existing ones. For example, the method isLaterThan is as an abstraction of its concrete implementation, which takes the values of two instance variables and uses an operator to compare the two: this.time > another.time. The other methods that need to compare two Moments can simply use this method and don’t need to deal with the variables directly.

A task in four phases

Implement Interval and Moment so that they meet the specification. Below is a recommended workflow that you can either follow or ignore.

Phase 1 of 4: Getting started with Interval

  1. Read the introductions of both classes in the Scaladocs. Also read the docs for Interval’s methods length and toString, which you’ll implement first.
  2. Create a new file for class Interval.
  3. Define the constructor parameters. Since each parameter needs to be stored in an instance variable, define those, too, in the class header. Should these instance variables be private?
  4. Implement length. Use a method from class Moment.
    • Now and later: resist the temptation to add public members in class Moment beyond those request in the docs. In particular, don’t make the instance variable time public.
  5. Implement toString. It’s a bit more complicated. Here are some hints:
    • Use the methods you have at your disposal.
    • You can use an ifelse structure to cover the three different cases.
    • Remember that a string can be “multiplied”. For instance, "ho" * 3 yields "hohoho".
  6. See the starter code in TimeTest. Add commands there as you see fit: create Interval objects and call their methods.

When are two Moments equal?

If you use == or != to compare moments , what you get is an identity comparison (“Do these two references point to the same Moment object?”; see Chapter 3.3.) What you don’t get is a comparison of the Moment objects’ actual contents. These operators won’t tell you whether two distinct Moment objects represent the same moment in time, that is, whether their time variables have identical values.

In this assignment, identity comparison and those two operators are completely unnecessary. You don’t stand to gain anything from using them. Other methods will serve you better!

Phase 2 of 4: isLaterThan and overloading

The interval assignment was quite a tranformative experience.

I was already well on my way, writing an if statement in each method and not reusing methods, when a couple of formidable and eagle-eyed course assistants, who just happened to be in the room, pointed this out to me.

I didn’t dare defy the wise assistants so I racked my brain, sweating, to figure out how it could even be possible to manage without an if. Once I came up with a solution for isLaterThan(Interval) (helped by a hefty dose of hints from the assistant), I felt like I had just invented the wheel. It was ingenious!

  1. Add isLaterThan in class Interval. Note that the method name has been overloaded (Chapter 4.1): there are two methods with the same name. One takes in a Moment and the other an Interval. Implement the former first. Make use of an existing method.
  2. Implement the second isLaterThan method in Interval. Make use of the method you implemented just before. As you do so, remember a detail from Chapter 4.1: if an overloaded method calls its namesake, Scala requires an explicit type annotation for the calling method’s return type. That means you’ll need to annotate the type on at least one of the isLaterThan methods.
  3. Add more test code to TimeTest and run it. Confirm that both isLaterThan methods work as intended.
  4. Moment should also have an isLaterThan method that takes in an Interval parameter. Add that method to the class.

Phase 3 of 4: logical operations

Continue implementing methods on Interval and Moment:

  1. Of the two contains methods of Interval, implement the one that takes in a Moment. Use logical operators in combination with methods that you have previously implemented.
  2. Implement the other contains method. Again, use logical operators and the other methods. Also recall what was just said about overloaded methods.
  3. Implement isIn in class Moment. It essentially does “the same thing in reverse” as another method you already wrote, which gives you a very simple implementation for it.
  4. Implement overlaps in class Interval. Once again, you can find uses for logical operators and existing methods. Make sure you cover all the possible cases.
  5. Add more method calls in TimeTest and test your new methods.

Phase 4 of 4: combining instances to produce new ones

The methods union and intersection are still missing from class Interval. Implement them and you’re done.

Submitting

A+ presents the exercise submission form here.

How private is private?

Student question: How can that Moment class work? time is private!

Let’s take a moment to consider why the given code from the previous assignment doesn’t produce an error message.

class Moment(private val time: Int) {

  def isLaterThan(another: Moment) = this.time > another.time
time is private. It’s not available for external use. But what counts as external, exactly?
A Moment object can access its own time.
A Moment object can also access the time of another Moment object: we simply prefix the variable name with an expression that refers to another object of the same class. Here, that expression is the variable name another.

private indicates that a program component is for internal use within the same class. The private component isn’t accessible by only a single object but also by the class’s other instances.

In the words of the late Nobelist:

All human beings have three lives:
public, private, and secret.

—Gabriel García Márquez

In Scala, “secret” is written private[this]. Place that modifier in front of a variable definition, and the variable will be accessible by only an individual object. In O1, we won’t be using such “secret” variables.

Omitting val/var and private[this]

This optional section deals with a minor Scala-specific issue. Read or skip as you please.

We’ll find an answer to this good question from a student:

So, if you don’t turn a constructor parameter into an instance variable, the program will crash if later, when you use the object, you try to call a method that tries to access the constructor variables, right?

The answer isn’t as simple as one might hope. It involves a curious little feature of Scala, which we can explore through this simple example class:

class MyClass(val first: Int, second: Int) {
  val third = second * second
  def myMethod = this.first
}

In other words, the student’s question is:

What happens if the constructor parameter first wasn’t preceded by val or var but...
... a method in the class nevertheless tries to access first?
Or, as another example: what if the method were to access second, which has been marked as an instance variable?

The student suggested that this might crash the program at runtime. It might be even better if if such an attempt produced a compile-time error before the program can be run. Neither of those things is what actually happens according to Scala’s rules, though... which we’ll get to just as soon as we recap these facts about constructor parameters and instance variables in Scala:

  • If you write just val/var in front of a constructor parameter, that parameter’s value will be assigned to an instance variable. Unless you specify otherwise, the instance variable will be public.
  • If you add private, the instance variable won’t be accessible to the outside but the class’s own code can access that variable, no matter which of the class’s instances is involved.
  • If you write private[this] instead, the instance variable will be an individual instance’s “secret”. The class’s methods can only access that variable on the this object.
  • If you don’t prefix a constructor parameter with anything (no val, var, private, etc.), then that parameter is meant to be used only in the code that initializes the instance (the constructor). Unless you write val or var in front, you won’t get an instance variable; that’s the basic idea, anyway. But see below.
class MyClass( first: Int, second: Int) {
  val third = second * second
  def myMethod = this.first
}
This code is otherwise identical to the previous one but first isn’t marked as a val. Normally, a constructor parameter thus defined would be available only while creating instances. Its value wouldn’t be stored in an instance variable as part of the object and wouldn’t be accessible to the class’s methods. Except that...
... if you nevertheless do access such a non-val, non-var constructor parameter in a method, Scala implicitly defines a “secret” instance variable on your class.

The above code works exactly as if you had defined the parameter as private[this] val first: Int. In O1, we won’t be writing code like that.

Optional Practice: Auctions

Assignment: DutchAuction

Go back to AuctionHouse1. Implement class DutchAuction as specified.

Instructions and hints:

  • First and foremost: make sure you understand what “Dutch auctions” are! The details are in the Scaladocs. Unless you have a clear idea of what the class should do, implementing it will be tough going.
  • As you read the docs, you’ll notice that some parts of DutchAuction are identical to FixedPriceSale. You can copy parts of your earlier solution into your DutchAuction class.
  • You can again use TestApp from o1.auctionhouse.gui to experiment with your class. You can of course also write your own text-based program for testing.
  • If you have an Int and need a Double, you can either...
    • ... do what we did in Chapter 1.3: 1.0 * myInt
    • ... or do what we’ll do in Chapter 5.2: myInt.toDouble

A+ presents the exercise submission form here.

WETWET?

Are you a bit upset that you had to write the same methods again in class DutchAuction or at least copy them from one file to the other? Outstanding! That means that you’re motivated to reach Chapters 7.2 and 7.3 where you’ll learn about avoiding such code duplication.

Assignment: EnglishAuction

Stay with AuctionHouse1 and implement EnglishAuction.

Instructions and hints:

  • Again, first make sure you understand exactly how “English auctions” are expected to work.
  • A simple Bid has been provided, which you should find useful. There is also a bit of starter code for EnglishAuction.
  • Even though it’s possible to solve the assignment with a buffer that stores all bids placed on an item, you don’t actually need to do that. As it turns out, you don’t need to solve every last bid to fullfill the requirements...
  • Instead, you can use two variables to store just the two highest bids, as shown in the starter code. Notice these two variables’ initial values: when there are no actual bids yet, the variables refer to “empty” Bids equal to the starting price.
  • TestApp will again be of service.

A+ presents the exercise submission form here.

Summary of Key Points

  • Logical operators operate on Boolean values. You can use them to express meanings such as “X and Y” and “X or Y”.
  • Links to the glossary: logical operator; to overload; DRY.

Feedback

Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.

Credits

Thousands of students have given feedback that has contributed to this ebook’s design. Thank you!

Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.

Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.

The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.

The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, and Aleksi Vartiainen.

The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.

The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio have done the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.

The other diagrams and interactive presentations in the ebook are by Juha Sorva.

The O1Library software has been developed by Aleksi Lukkarinen and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.

The pedagogy behind O1Library’s tools for simple graphical programming (such as Pic) is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and Krishnamurthi and Picturing Programs by Stephen Bloch.

The course platform A+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.

For O1’s current teaching staff, please see Chapter 1.1.

Additional credits appear at the ends of some chapters.

a drop of ink
Posting submission...