Lists and Compass
Post
Cancel

# Lists and Compass

As we further expand our toolbox for programming, we’ll look at how we can store a bunch of variables at once, and use that alongside a compass sensor!

## Why lists?

At this point, hopefully you agree that we can do a lot of cool and different things with our programs.

In the last post, we learnt how to make code repeat certain instructions for an arbitrarily long time (Repeat this until this condition is true). What we are missing, however, is a similar thing for variables.

Consider a previous example of computing the square numbers smaller than 50:

1 2 3 4 5 6 7 x = 1 # Print x * x while it is <= 50. while x * x <= 50: square = x * x print(f"{x}^2 = {square}") x = x + 1 
1 2 3 4 5 6 7 8 ### Output ### >>> 1^2 = 1 >>> 2^2 = 4 >>> 3^2 = 9 >>> 4^2 = 16 >>> 5^2 = 25 >>> 6^2 = 36 >>> 7^2 = 49 

The main problem we run into is: After this loop has run, how can we remember all of the square numbers we calculated? We can’t create a new variable for each, because we don’t necessarily now how many times this loop will run. That’s where the list comes into play!

### Creating

A list does what it says on the tin: It is a list of other bits of data, and it starts and ends with square brackets []. Creating a list in python is super simple, you just write out the list as data:

1 2 3 my_number = 3 # This is a list, with 3 items. my_list = [1, "two", my_number] 

We can find the length of a list (how many items are in it) by writing len(my_list).

You can also add to a list, with .append(). So we can rewrite our old program to store the squares:

1 2 3 4 5 6 7 8 9 10 11 12 13 x = 1 # Create an empty list squares = [] # Print x * x while it is <= 50. while x * x <= 50: square = x * x # Add square to the list squares.append(square) x = x + 1 print(f"There are {len(squares)} squares <= 50") print(squares) 
1 2 3 ### Output ### >>> There are 7 squares <= 50 >>> [1, 4, 9, 16, 25, 36, 49] 

### Changing and Accessing

Creating a list is cool and all, but storing data is only useful if you can later modify or retrieve it. For example, in the code above, we might want to ask “What is the third square?”. To do this, we write squares[2]. squares[0] is the first element (1), squares[1] is the second element (4), and squares[2] is the third element (9). This continues until the end of the list.

squares[0],squares[1],squares[2]... are essentially just variables, you can read and change their values in the same way. Because of this, we can also change the list. Writing squares[4] = 0 will change the 5th item in the list from 25 to 0.

Write some code that will print the sum of all numbers in a list (So for the list my_list = [1, 2, 3], the program would print 1 + 2 + 3 = 6)

1 2 my_list = [1, 2, 3] # ... your code goes here. 

Use a while loop to access each item in the list (You might find len(my_list) useful here).

Use a variable to store the sum of the items as you go along (my_sum = my_sum + my_list[index]).

You can also use negative numbers as an index. With negative numbers, you go from the right of the list to the left of the list (so my_list[-1] is the last item in the list, and my_list[-2] is the 2nd last item).

1 2 3 4 5 my_list = [1, 2, 3, 4, 5] print(my_list[-3]) print(my_list[2]) print(my_list[-1]) print(my_list[4]) 
1 2 3 4 5 ### Output ### >>> 3 >>> 3 >>> 5 >>> 5 

### Strings and Splitting / Joining

One place Python already has introduced you to something like a list is strings. Strings are like lists of characters, and you can index them too:

1 2 3 my_string = "Testing" print(my_string[2]) print(my_string[6]) 
1 2 3 ### Output ### >>> s >>> g 

Even more useful, we can break a string up with the split function. split takes some character in between the brackets, and will break up the original string into a list, based on this character. If no character is given in the brackets, then any empty space will break up the string.

1 2 3 4 my_underline_string = "Beep_Boop_Blargh" my_space_string = "Hello Testing 1234" print(my_underline_string.split("_")) print(my_space_string.split()) 
1 2 3 ### Output ### >>> ["Beep", "Boop", "Blargh"] >>> ["Hello", "Testing", "1234"] 

Martin has written a program so that he can give his robot a sequence of commands, each specifying a direction (Left, Right, or Straight) and a distance (in cm).

Currently, however, it has 2 problems which cause errors in his code. Your task is to fix the robot so that it follows the commands correctly.

Both bugs are in the bottom half of the code given. One should immediately raise errors when you try running the program.

The other bug is with the line i=1. Why is this a problem / what should it be instead?

If you want to go back from a list of strings, to one big long string, you can use the join method:

1 2 3 4 my_split_underline_string = ["Beep", "Boop", "Blargh"] my_split_space_string = ["Hello", "Testing", "1234"] print("_".join(my_split_underline_string)) print(" ".join(my_split_space_string)) 
1 2 3 ### Output ### >>> Beep_Boop_Blargh >>> Hello Testing 1234 

a.join([x, y, z]) will print xayaz (Joining the items in the list, with a in between).

## The Compass Sensor

So far we’ve just been turning left/right/up/down by remembering our current direction, and rotating accordingly. However, this is bad for a few reasons:

• In real life, we can’t get exactly straight in one direction. After a series of turns, we will slowly drift off course.
• Storing this memory of which direction we are facing gets a bit annoying.
• It is even harder to do with any general rotation, not just the four cardinal directions.

For this reason we introduce the Compass Sensor. The Compas Sensor has a pretty simple purpose - it tells us what rotation we have, relative to some starting point. We use it in a very similar way to the infrared sensor:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import time from ev3dev2.sensor import Sensor, INPUT_1 # Connect to the sensor compass = Sensor(INPUT_1, driver_name="ht-nxt-compass") # Calibrate the compass (Tell it what north is) compass.command = "BEGIN-CAL" compass.command = "END-CAL" while True: # This ranges from 0 to 359 (Clockwise rotation from starting position). bearing = compass.value() print(f"We are at {bearing} degrees") time.sleep(0.5) 

After calibrating, the compass should return value of 0. This means that it is facing North (or what the robot thinks is North). Then, if we rotate it counter-clockwise 90 degrees, you should find that the compass.value() returns about 90. With this, we can tell our current rotation based on when we calibrated.

Martin is back with a new robot this time, but he hasn’t written any program yet!

This robot should listen to Martin explain how many locations there are and where they are, followed by a specific location Martin wants to move to. This location is measured in distance, as well as degrees required to rotate (Where 0 degrees is to the right, and positive degree = counter-clockwise movement).

Can you help him? You can download the puzzle here.

You can use very similar code from the Sequence of Commands puzzle to split the input strings into words, and get the information you need (How many locations are there, Where are they, Where does Martin want to go?)

Next, we just need to rotate on the spot until compass.value() is about equal to the rotation of the location that Martin wants to go to. You might want to multiply the speed of rotation by (wanted_value - compass.value()), so that you rotate slower as you close in on the wanted_value. Then when the distance between compass.value() and wanted_value is small enough, you can start moving forward.

So, after reading in Martin’s locations and appending them to a list, you can search through this list for the location Martin wants to go to:

1 2 3 4 5 6 7 8 # Name, Distance, Degrees. my_locations = [("Hospital", 15, 250), ("Market", 24, 45)] # Martin wants to go to the market. martin_goes = "Market" index = 0 while index < len(my_locations): if my_locations[index] == martin_goes: # Now we can move to that location! 

## List Splicing

### Simple splicing

One other cool thing we can do with lists is splice them. Splicing is similar to indexing, although rather than just retrieving one item from the list, you can retrieve multiple. The code is very similar to indexing.

To get the 3rd, 4th and 5th items from a list, you can write the following:

1 2 my_list = [1, 2, 3, 4, 5, 6, 7] print(my_list[2:5]) 
1 2 ### Output ### >>> [3, 4, 5] 

The left number is where to start the splice (2 means 3rd item, just like indexing), and the right number is where to end the splice (5 means stop before the 6th item).

If you leave these numbers blank, then they default to the start and end of the list.

1 2 3 my_list = [1, 2, 3, 4, 5, 6, 7] print(my_list[:3]) print(my_list[3:]) 
1 2 3 ### Output ### >>> [1, 2, 3] >>> [4, 5, 6, 7] 

### Negatives and Jumps

Splice indicies can also be negative, so my_list[-3:-1] would get the 3rd and 2nd last elements.

Lastly, a third number can be given in a slice, to change the distance between items selected:

1 2 3 4 5 my_list = [1, 2, 3, 4, 5, 6, 7] # Start at the start of the list. Jump 2 every time until the end. print(my_list[::2]) # 2nd until the 7th element. Jump 3 every time. print(my_list[1:6:3]) 
1 2 3 ### Output ### >>> [1, 3, 5, 7] >>> [2, 5] 

The distance given can also be negative, to reverse the order of elements! (Have a play around).

Anna has collected some data about the weather at her house for the past month.

From this data, she’d like to print the weather for

• The second, third, and fourth days.
• The last two days.
• The first two Fridays.
• The 2nd, 3rd and 4th last days (in that order).

As you can see the sample code already does the reading of the weather for you. All you need to do is modify lines 12, 15, 18 and 23 to use splices of days rather than ["Sunny", "Snow", "Rain"].

• The second last splice will need a jump size of 7, to ensure we only print Fridays. (What should the starting index be?)
• The final splice will need to use a negative jump, since we are going backwards through the list.

## Fast Looping

Last week we covered the while loop, a bit of code that runs a variable (variable in that it varies) amount of times. And this week we covered lists, a data type whose length varies. Because of this, we often end up writing very similar bits of code to loop over the items in a list, or something similar:

1 2 3 4 5 6 my_list = [...] counter = 0 while counter < len(my_list): # Do something with my_list[counter] counter = counter + 1 

However, there’s a lot of things we can miss here, like:

• counter = counter + 1 (Or your code just runs forever)
• Creating the variable counter = 0.
• Using the correct statement counter < len(my_list)

Which all sounds simple, but in the middle of a large program you can definitely forget! Luckily Python has a few more keywords designed specifically for looping over lists and similar objects.

### On lists

If we want to loop over the items in a list, we can use the following code:

1 2 3 my_list = [1, 2, 3] for list_item in my_list: print(list_item) 
1 2 3 4 ### Output ### >>> 1 >>> 2 >>> 3 

The for loop acts like a while loop, where it repeats indented code. Instead of a condition to stop looping, you specify a variable (list_item), and a collection of items (my_list).

The for loop then repeats the indented code, once for every item in the collection, setting list_item to that particular item.

You can also do this with strings, since they can be viewed as a collection of characters!

1 2 3 my_string = "abcd" for my_char in my_list: print("Next character is:", my_char) 
1 2 3 4 5 ### Output ### >>> Next character is: a >>> Next character is: b >>> Next character is: c >>> Next character is: d 

### On numbers

One bad thing about these for loops so far is that we can’t modify the lists at the same time. For example we couldn’t really translate this block of code into using a for loop:

1 2 3 4 5 6 my_list = [1, 2, 3] counter = 0 while counter < len(my_list): my_list[counter] = my_list[counter] + 4 counter = counter + 1 print(my_list) 
1 2 ### Output ### >>> [5, 6, 7] 

However, rather than trying to loop over the collection of items in the list, what if we could instead loop over the indexes of the list? Well you’re in luck!

1 2 3 4 5 6 7 my_list = [1, 2, 3] for counter in range(0, len(my_list)) print(counter) my_list[counter] = my_list[counter] + 4 print(my_list) 
1 2 3 4 5 ### Output ### >>> 0 >>> 1 >>> 2 >>> [5, 6, 7] 

You can think of range exactly like a spliced collection of numbers. You can specify a start and an end point (Where you include the start but not the end point). So range(4, 9) = [0, 1, 2, 3, ...][4:9].

Just like a splice you can also include a third number, to specify the jump distance (With negative values working too!). Additionally, if you specify just one number like range(a), it is the same as range(0, a).

1 2 for x in range(3): print(x) 
1 2 3 4 ### Output ### >>> 0 >>> 1 >>> 2 
1 2 for x in range(3, 9, 3): print(x) 
1 2 3 ### Output ### >>> 3 >>> 6 

You are playing a rather simple card game with Sydney. In this game, all you have to do is spot a specific group of 3 cards with the same face value. you’ve decided you want to demolish Sydney with a program that runs super fast.

Your program will first take as input a number $$n$$ - the number of cards in the hand, followed by $$n$$ lines of input, which is the face value of each card.

If there is a triple, print the indicies of these cards in the hand. Otherwise, print “No triples!” or something similar.

First start by creating a list, which contains all the card’s face values. We want to find 3 of a kind (double duplicates? triplicates?) in this list.

To do this, you can use a for loop, inside a for loop, inside a for loop!

Consider this following snippet, which finds a two of a kind.

1 2 3 4 5 6 7 cards = [2, 9, 6, 9, ...] for card1_index in range(0, len(cards)): # \/ Avoid any indexes at or before the first card. for card2_index in range(card1_index+1, len(cards)): if cards[card1_index] == cards[card2_index]: print("There's a duplicate!") 

## Project

Your friend Rebecca is trying to tell you the directions home. That being said, she has an interesting way of telling you these directions. Not only are they not in order, but she might also get things wrong! Luckily by the end of her speech, everything she says makes sense, but before then, she might correct herself later on. Can you make it home?

You will receive all of what Rebecca says through input. There are three difficulties, each of which introduce a new pattern of speech:

• Easy: For everything I’ve said - I meant the opposite. (All previous directions should flip between North/South and East/West)
• Medium: For everything I’ve said - They were to go from home to here! (Step 1 becomes Step 10, Step 2 becomes Step 9, and they change direction)
• Hard: For all steps from x to y, I meant the opposite. (Like the first version, but only applies to Steps x to y (inclusive))

Start by using .split to find how many steps this path contains. Use a list to store the direction you need to move for each of these steps. Then, ignoring the complicated lines described above, just translate the directions that Rebecca gives you into items in the list.

The actual control of the robot, you can mostly take inspiration from the previous sample code using compasses.

Provided you got that working, the next thing you need to worry about is the easy variant - Opposites.

So to do this, you need to write a loop which goes through the list, and wherever East is stored, make it West. And wherever West is stored, make it East, and so on.

For the medium variant - Reversal, think about what needs to happen to the list of directions.

For reversing, something like this will work:

1 2 3 4 5 6 7 8 9 10 directions = [...] new_directions = [] # Use negative jumps in range to move backwards. for a in range(len(directions)-1, -1, -1): # Add the last element to new_directions, then the second last, and so on. # Before that make the opposite direction though. if directions[a] == "North": new_directions.append("South") # and so on... 

And you can do a similar approach for the opposites code, changing range(len(directions)-1, -1, -1) to range(len(directions)), so as not to reverse the order.

For the hard condition, this is very similar, we just need to only do the reverse if x <= a <= y.