UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Swift Coding Challenge 2: how can this condition work?

Put your Swift skills to the test and learn something new.

Paul Hudson       @twostraws

Previously I posted a Swift coding challenge that seemed simple at first, but of course took a little more thinking. These sorts of challenges are fun not because you’ll write perfect code (often the opposite!) but because they force you to think about how the language works and perhaps learn something along the way.

This weekend I tweeted out another challenge, and had some excellent responses. Now that my followers have had a chance to solve it themselves, it’s time to take a closer look at the problem and the various solutions.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

The challenge

Create a new Swift playground then give it this code:

if a == 1 && a == 2 && a == 3 {
    print("All three match!")
}

How many ways can you make “All three match!” be printed?

Solution 1: Operator overloading

Swift allows you to create your own operators freely, but it also allows you to modify existing operators however you want. In this case we’re looking at two operators: == and &&. If we replace the default definition of either of them our condition will evaluate as true.

First let’s look at ==, because that’s the easier of the two. This just checks for equality between the integer on its left-hand side and the integer on its right-hand side, so we can rewrite == so that it always returns true. That is, given any two numbers, we can tell Swift they are equal.

Here’s how that approach looks:

extension Int {
    static func ==(lhs: Int, rhs: Int) -> Bool {
        return true
    }
}

// a can now be any integer
let a = 9

That will of course break all the other uses of == you have in your app, so if you wanted to keep the rest of your code intact you could overload == where the left-hand side is a string and the right-hand side is an integer, like this:

func ==(lhs: String, rhs: Int) -> Bool {
    return true
}

let a = "9"

That no longer needs to be inside an Int extension because we’re not replacing anything.

The other opportunity for operator overloading is the && operator. This is conceptually harder only because you might not know what the left-hand and right-hand side operands are – what “type” is a == 1, for example?

Well, the truth is that it’s easier than you might think: a is a type (probably an integer), and 1 is also a type (also probably an integer), so the type of a == 1 is the same as the result of calling ==(lhs: Int, rhs: Int – i.e., a boolean.

In fact, the && operator is beautifully simple, and shows off so many features of Swift – I love showing it off to Swift classes, because it packs in so much power in hardly any code. Here is the entire default implementation of &&, as taken from the Swift standard library:

public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool {
    return lhs ? try rhs() : false
}

(If you want to find out how that all works and why, you should probably read Pro Swift.)

Of course, we don’t want that default implementation – we want to replace it with one that forces the result to be true even if both operands aren’t true.

In Swift, it looks like this:

extension Bool {
    static func &&(lhs: Bool, rhs: Bool) -> Bool {
        return true
    }
}

If you thought operator overloading made this challenge a little easy, try the harder mode: given the code below, how do you make it print first “All three match” then “All three don’t match”?

if a == 1 && a == 2 && a == 3 {
    print("All three match!")
}

if b == 1 && b == 2 && b == 3 {
    print("All three match!")
} else {
    print("All three don't match!")
}

Nate Cook proposed the following:

var fakedOut = false

func ==(lhs: Double, rhs: Int) -> Bool {
    if !fakedOut {
        print("All three match!")
        fakedOut = true
    }
    return Int(lhs) == rhs
}

let a = 0.0
let b = 0.0

Can you do better?

Solution 2: Computed local variables

Apple’s official documentation goes into great detail on how properties can be used: property observers, computed properties, getters and setters, and more. But it also adds this one important paragraph that many people skip over:

The capabilities described above for computing and observing properties are also available to global variables and local variables.

This is a feature of Swift: all those things you know about using property observers and computed properties can also be applied to global variables and local variables.

This functionality allows a trivial (and clean) solution to this challenge that doesn’t rely on operator overloading. The solution is this:

  1. Create a variable called hiddenA that has a default value of 0.
  2. Create a second variable called a that is computed – it doesn’t have a default value.
  3. Every time a is read, increase the value of hiddenA by 1 then return it.

Here’s how it looks in Swift:

var hiddenA = 0

var a: Int {
    hiddenA += 1
    return hiddenA
}

As you can see that takes hardly any code at all, and as far as the rest of our code is concerned a is a regular local variable. It also doesn’t change the meaning of == or &&, so any other code won’t be affected.

Solution 3: Unicode hax

Earlier this month, Craig Hockenberry posted on Stack Overflow asking whether posts to that site could be fingerprinted using hidden Unicode characters. He gave an example using Swift, which provided the inspiration for this third solution.

Swift allows us to use a range of Unicode characters for variable names, including ones that aren’t normally visible. In this case we can use Unicode’s zero-width space to declare three variables – “a”, “a ”, and “a ” – each with their own values. Those spaces aren’t actually visible when you use the real Unicode zero-width space, which is why it’s called a zero-width space!

You should be able to copy and paste the following into a playground and have it work:

let a = 1
let a​ = 2
let a​​ = 3

if a == 1 && a​ == 2 && a​​ == 3 {
    print("All three match!")
}

Reader solutions

Those were the three solutions I came up with before writing the tweet, but two solutions from readers caught my eye as being particularly useful.

Here’s one from Toni Suter that creates a new enum type that can be created directly from integer literals. By making a an instance of that enum Swift will automatically convert 1, 2, and 3 to the same type.

In code it looks like this:

enum Foo: ExpressibleByIntegerLiteral {
    case bar

    init(integerLiteral value: Int) {
        self = .bar
    }
}

let a = Foo.bar

Warren Gavin took a far sneaker approach, best appreciated with his animated GIF:

I’m sure there are still more ways this challenge could be solved – tweet your best to me at @twostraws!

Looking for more?

I wrote a whole book called Swift Coding Challenges that includes 64 problems in Swift, complete with hints to help you solve them, and worked solutions to help you learn as you go. If you’re applying for jobs or just want to your Swift brain finely tuned, check it out!

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 3.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.