The Restaurant Randomizer

Mark Nair
Mark Nair

Table of Contents

It sounds like a fun night out with a good meal is in our evening’s future, but then comes the important and devastating question: where do you want to go?

I thought this time I’d be a clever monkey with some technological magic and make a program to do my bidding. So today welcome to an adventure in randomizing elements in a Swift array.

Setting up the Array

First, the rules:

  1. I want the program to find a random restaurant from a list of places I already like.
  2. I don’t want to see the same restaurant twice before exhausting all the possibilities.

That’s it so far, so let’s start and fumble through things.

I’m going to make this in Xcode Playgrounds. Later, if I want a nice app with a decent UI, I’ll make a real project.

First, since I want to find a random restaurant from a list of places, I’ll make a list of them. If Swift lingo, that means using an array.

let restaurants: [String] = [
    "Yellow City Street Food",
    "Ramen Del Barrio",
    "Garbos",
    "Con Todo",
    "Usta Kababgy",
    "House of Three Gorges",
    "Komé",
    "Asti Trattoria",
    "L'Oca d'Oro"]

Of course, since Swift knows this array is full of string elements, I don’t need to particularly say this: let restaurants: [String] . I’ll just dump the : [String] part and end with this:

let restaurants = [
    "Yellow City Street Food",
    "Ramen Del Barrio",
    "Garbos",
    "Con Todo",
    "Usta Kababgy",
    "House of Three Gorges",
    "Komé",
    "Asti Trattoria",
    "L'Oca d'Oro"]

Randomization

Now my first sliver of intuition is to say, hmm, I bet I can use a random number to pluck out a restaurant’s position from the array. Since I have nine restaurants, I’ll simply find a random number between one and nine and assign that to a new variable, like this:

let randomNumber = Int.random(in: 1...9)

Oh, but hold on. Array positions start at zero, not one. So in my restaurant array, “Yellow City Street Food” is really in position zero. Ok, that’s an easy fix:

let randomNumber = Int.random(in: 0...9)

And this looks perfectly fine, but I might not be too happy with just nine restaurants in the array. What if I wanted to add a few more? With this method, I’d need to keep changing the range of the random number. Or what if I make an app and someone using it wants to add their own restaurants? Arg! I’ll never know the end of the random range. It could be 1000, which is quite a few restaurants and necessitates the need to cook at home for a while.

Fortunately, this is an easy fix too. I’ll just ask Swift to make the random range go from zero to the end of the array by using the array count method, like this:

let randomNumber = Int.random(in: 0...restaurants.count)

This says, find a random number (that’s an integer because I don’t want fractions) from zero to the total count of elements in the restaurants’ array. In our case the count is nine. I can check that by simply running this:

print(restaurants.count)

Ta da! It’s nine.

So far so good. But I’m suspicious and want to see if this will really work, so I’m going to print the result:

let restaurants = [
    "Yellow City Street Food",
    "Ramen Del Barrio",
    "Garbos", 
    "Con Todo",
    "Usta Kababgy",
    "House of Three Gorges",
    "Komé",
    "Asti Trattoria",
    "L'Oca d'Oro"]

let randomNumber = Int.random(in: 0...restaurants.count)

print(restaurants[randomNumber])

This should find a random number and then print the name of the restaurant that’s at the randomNumber location.

Looping

It seems to work okay, but I’m still wary, so I’m going to force the issue and print this 100 times. I’ll do this in a loop with a range of one to 100, like this:

for _ in 1...100 {
	let randomNumber = Int.random(in: 0...restaurants.count)
	print(restaurants[randomNumber])
}

This loop should run 100 times, but take a look at this:

for _ in 1...100 {

It looks weird. What’s the _ mean?

In this example, I don’t need to keep count of the loop. I just want it to print the restaurant names 100 times. The _ just means I don’t need to store the number of times the loop runs. If I wanted to, I’d use something like this:

for number in 1...100 {
	let randomNumber = Int.random(in: 0...restaurants.count)
	print("\(number) \(restaurants[randomNumber])")
}

And this would print the list of restaurants to the console with the current loop number next to each restaurant, looking like this:

1 Asti Trattoria

2 Komé

3 L'Oca d'Oro

4 House of Three Gorges

5 L'Oca d'Oro

6 House of Three Gorges

7 House of Three Gorges

8 Asti Trattoria

But I don’t need this for my test, so I’ll just revert back to this:

for _ in 1...100 {
let randomNumber = Int.random(in: 0...restaurants.count)
print(restaurants[randomNumber])
}

And . . . it works until it crashes. Ugh. This is the ugly crash message:

### error: Execution was interrupted, reason: EXC_BREAKPOINT (code=1, subcode=0x192ffed38).

Very unhelpful. The console gives me a better clue:

Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range

Out of Range

The problem I’m having is the array’s position starts at zero. Even though the array’s count is nine, the ninth element, which is “"L'Oca d'Oro" is really in position eight. We start counting array positions with zero.

So all I have to do is tell Swift to find a random number from zero to the count of the array minus one:

let randomNumber = Int.random(in: 0..<restaurants.count)

Problem solved!

Well, sorta.

I’m still seeing duplicate restaurants, and this whole find-a-number-thing seems a little overly complicated. And I thought this Swift language plan was to make things easier for people.

Let’s try a simpler technique and use the built-in randomElement() method like this:

print(restaurants.randomElement())

That’s so much easier. But my first result is a little weird. It looks like this:

Optional("Garbos")

It’s giving me this result because Swift is taking the safe road and wants to make sure I know what I’m doing. Since Swift doesn’t know if my array will end up empty at some point, it’s telling me this is an optional value. I can quickly fix this several ways, this brute force way using this exclamation mark is one way:

print(restaurants.randomElement()!)

Using Shuffle()

Which is fine, but not exactly proper. Instead, I have a new idea. What if I just mix up the array from the very start? I can do this with the shuffle() method, like this:

var restaurants = ["Yellow City Street Food", "Ramen Del Barrio", "Garbos", "Con Todo", "Usta Kababgy", "House of Three Gorges", "Komé", "Asti Trattoria", "L'Oca d'Oro"]


restaurants.shuffle()

print(restaurants[0])

Since I’m shuffling the elements of the array, I need to make the array a var instead of a let constant so I can change it up.

Now I’m getting somewhere. I’ll try something trickier. I’ll get a value from the array and delete that element so it won’t pop up as a duplicate. I’ll tidy this up by tossing it into a new function:

var restaurants = ["Yellow City Street Food", "Ramen Del Barrio", "Garbos", "Con Todo", "Usta Kababgy", "House of Three Gorges", "Komé", "Asti Trattoria", "L'Oca d'Oro"]

func randomizeRestaurants() {
	restaurants.shuffle()
	for restaurant in restaurants {
    	let restaurantChoice = restaurants.removeFirst()
    	if restaurants.isEmpty {
        	print("There are no restaurants left.")
    	} else {
        	print(restaurantChoice)
    }
}

When I call the function randomizeRestaurants(), this is what happens:

  1. The restaurant names in the restaurants array shuffle places.
  2. The function then goes to the first restaurant name in the array, stuffs it into the variable called restaurantChoice, and then removes the restaurant name from the array.
  3. The function checks if the array is empty. If it is, it prints “There are no restaurants left.” to the console.
  4. Otherwise, the function prints whatever name it stuffed into the variable called restaurantChoice.
  5. Then it does all this all over again with the next name in the array until it runs out of restaurant names.

So far so good. But the result is a list of randomized restaurant names, which is fine if I’m planning restaurant visits weeks ahead of time. Instead, I’d like to call the function and get one answer at a time (so later on I can make a button that calls the function). And I don’t particularly like how I’m wiping out my array every time. I need a way to reset the array so I can keep randomizing my choices again and again.

Using an Empty Array

I know! I’ll make a new array I can change and leave my original array untouched:

let restaurants = ["Yellow City Street Food", "Ramen Del Barrio", "Garbos", "Con Todo", "Usta Kababgy", "House of Three Gorges", "Komé", "Asti Trattoria", "L'Oca d'Oro"]

var shuffledRestaurants: [String] = []

func randomizeRestaurant() -> String {
	if shuffledRestaurants.isEmpty {
    	shuffledRestaurants = restaurants.shuffled()
	}

	return shuffledRestaurants.removeFirst()
}

let dinnerTonight = randomizeRestaurant()
print("Let's go to \(dinnerTonight)!")

Looking good. I have a new array I can freely changed, and if I loop through all this with the following, I can see the shuffledRestaurants array shrink as the loop works. Nice.

for _ in 1...20 {
	let randomEat = randomizeRestaurant()
	print(randomEat)
	print(shuffledRestaurants)
}

Now it’s time to make it a tiny app, but first I should encapsulatethis business into something I can reuse, especially if I’m going to add things like buttons. I should also go a bit overboard with some safety checks.

I’ll do all that in Part II.

SwiftTech

Mark Nair

I like riding my bicycle.

Comments