-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path04_Lists.Rmd
739 lines (529 loc) · 35.3 KB
/
04_Lists.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
# Lists {#lists}
```{python}
message = ["Hello,"]
message.append("world")
print(" ".join(message))
```
In chapter 2 Variables, you encountered the four basic data types of the Python programming language: *integers*, *floats*, *Strings* and *Booleans*. You also learned that apart from these four basic data types, Python knows numerous specialized types. Now it is time to encounter one group of these specialized data types: *collections*. You will learn about three types of collections, *lists*, *tuples*, and *dictionaries* in this chapter. Together, they make up a toolbox for storing information in a computer program.
## Lists
```{python, eval=F}
a = [10, 5, 15, 25]
b = ["eggs", "bacon", "cheese"]
c = [4, "hello", 7.3, False]
```
Lists are one type of collection. The easiest way of creating a list is surrounding some values with cornered brackets `[]` and separating the values of the list with commas `,`. The values that make up a list are called *items* or *elements*. Lists can contain any type of value or variable. From this it follows that lists can also contain other lists! A list that contains another list is called a *list of lists*, or *nested* list (you may also think of this phenomenon as *listception*, if you ever watched Christopher Nolan's *Inception* (2010)).
```{python}
a = 2
b = [a, 2*a]
c = [b, 6]
print(c)
```
A special type of list is the so-called *empty list*. An empty list is a list with no elements. Empty lists are denoted with `[]`.
```{python, eval=F}
a = []
```
In a sense, lists are similar to *Strings*. Strings are basically ordered collections of *characters*. For example, the word `Hello` is an ordered collection of five characters: `H`,`e`, `l`, `l`, and `o`.
In contrast to Strings, however, the elements of lists can be of *any* type. Ordered collections such as strings and lists are also called *sequences*.
Lists are *ordered* collections of values. However, a list being *ordered* has nothing to do with how humans may be inclinded to order the elements of a list. For example, you may want to order a list `a` with three elements `"A", "B",` and `"C"` according to their alphabetic order: `a = ["A", "B", "C"]`. However, for lists, the value of an element is irrelevant for its position in the list. Lists are **not** *ordered* because they grasp some underlying semantics about their elements according to which they come up with an order. In fact, element values are arbitrary for a list. Instead, lists are called *ordered* because they allocate a certain position in memory to each element. Each list element occupies a unique position in memory. Elements of a list are accessed by querying the list at specified positions, so-called *indices*. Accessing a list at a specified index, the value located at that position in memory is returned. The *index operator* `[]` is used to access list elements at specified indices, like so
```{python}
a = [1, 2, 3]
print(a[0])
print(a[1])
```
The *expression* inside the brackets of the index operator (in this case the integers `0` and `1`) specifies the *index* of the to-be selected element. Each element inside a list has a unique index, an *integer* describing the element's position in the list. One major cause of errors surrounding lists is that indices start at `0`, not at `1`, as you might expect. After all, to us it feels natural to start counting at `1`. As you can see below, this means that the first element of a list is accessed by indexing `0`.
```{python}
a = ["Hello", "World", "!"]
print(a[0])
print(a[2])
```
Why do indices start at 0, instead of 1? As you might already expect, it has to do with how computers store information in memory. A list serves as a pointer, a reference to a memory location. The expression `list[i]` then refers to a memory position i-steps away from the starting element. This means that the index is used as an offset. The first element of a list is located at exactly the position the list refers to (which means that the offset is 0). Hence, the first element of a list is located at index 0.
### Lists are mutable
In contrast to Strings, lists are *mutable* sequences. This means that even after a variable is assigned a list as value, specific elements of this list can be changed without reassigning a completely new list! You can use the *index operator* to mutate specific list items.
```{python}
shoppinglist = ["appels", "bananas", "tomatoes"]
shoppinglist[2] = "peaches"
print("Updated shopping list: " + ", ".join(shoppinglist))
```
```{python}
a = [1,2]
a[0] = 10
print(a)
```
Using the index operator, you can also access (and change!) several elements at a time using so-called *list slices*:
```{python}
a = [1, "a", 2, "b", 3, "c"]
print(a[1:4])
print(a[0:2])
a[0:2] = "d"
print(a)
```
The indices around the `:` colon indicate the starting and ending index of the selected list slice. Note that a list slice will always include the element located at the starting index, but exclude the element located at the ending index. To include `2` in the second list slice you would thus need to access `a[0:3]`, or simplified `a[:3]`. The latter meaning "every element up until, but excluding index 3".
Note that the statement `a[0:2] = d` mutates both elements, `1` *and* `"a"` into a single new item `"d"`. This means that the statement did not only change some elements of `a`, but also `a`'s total number of elements!
When no starting or ending index is specified, the `:` expression selects all elements of a list and can be used for *cloning* lists.
```{python}
a = [1, "a", 2, "b", 3, "c"]
aCopy = a[:]
print(aCopy)
```
But watch out, you **cannot** use the index operator to add new elements to a list. To do so, you would need to access a new index, an index not yet claimed by an existing list element. Trying to access an index outside of the current range of indices will produce an index out of range error.
```{python error = T}
a = [1,2]
a[2] = 10
print(a)
```
Luckily, there are other ways of adding elements to a list.
```{python}
a = []
a.append(1)
print(a)
a.append(2)
print(a)
```
The *append* method adds the specified element at the end of a given list. You can use the *extend* method to add *multiple* values of another list (or other type of collection) to your list.
```{python}
a = [1,2]
b = [3,4]
a.extend(b)
print(a)
```
Note that there is a difference between appending a list and extending your list with another list.
```{python}
a = [1,2]
b = [3,4]
a.append(b)
print(a)
```
In the above example, **extending** `a` with `b` causes the elements stored in `b` to be appended to `a` sequentially, each of them constituting a new element in `a`. In contrast, the whole list `b` is regarded as an element of `a` when `b` is **appended** to `a`.
Because lists are mutable, you can sequentially fill them with elements, one by one. This is very handy for storing values you generate throughout your program.
###A word about class specific functions
By now, you have seen a few examples of class-specific *functions*, such as `append()` for lists or `join()` for strings. Even though a detailed explanation of *functions* (also called *methods*) is postponed to Chapter \@ref(functions), a quick introduction to the terminology of objects, classes, and class-specific functions will help you make the most of your lists and other collections. For now, you can see functions as pieces of reusable code that specify what to do with some input values.
When you create a variable `a` and assign a value to it, say `[4,5]`, you can think of it as creating an *object* (i.e. an instance) of the Python *class* *list*. The *list* class has been programmed by the developers of the Python language, same as any other built-in Python class, such as integers and strings. Classes are blueprints, a specification of how to build certain objects. Engineers use blueprints to build cars, buildings, bridges and more. Chefs use recipes to put together meals. Python classes are the same in that they specify how objects of a certain type are programmed internally when you create one of them. A class has built-in *methods* that can be used by *any* object that has been created using the class' blueprint. And this is what you basically do whenever you surround a number of values with cornered brackets. You create a list according to the blueprint of the Python class `list`. It is very useful that your list inherits *functions* from its blueprint. It means that without any effort on your part your lists have a variety of functionality at their disposal. They can immediately use a range of built-in methods such as the `append()` method.
```{python}
a = [4,5]
a.append(6)
print(a)
```
The `append()` method adds the value specified between the brackets to the end of the list. Typing `help(list)` in your console will give you a complete list of built-in functions of the class `list` and what they do.
What is the take-home message of this excursion to object-orientation? Well, make use of built-in class functions as much as you can! They provide a tremendous amount of functionality which you are likely going to need and want, written in neat code which makes them very efficient to use, too!
The interested reader can find more information on Python classes [here](https://docs.python.org/2/tutorial/classes.html).
## Tuples
Much like lists, *tuples* are ordered collections of values, but with less exentensive functionality and their unique characteristic being that they are *immutable*. Once you created a *tuple* in memory, you cannot change its elements.
```{python error = T}
thorndike = ("Edward", "Lee", "Thorndike", 1874, "Law of effect", "Law of exercise")
thorndike[4] = "Law of recency"
```
Tuples are usually used when you assume that a collection of values will not change in the course of your program. They are *records* of related information, chunked together so that we can use them as a single entity.
In the Stroop Task program for instance, we saw that tuples were used to chunk together RGB combinations that make up a color. The Pygame module used these tuples to generate color in the Stroop Task.
Tuples are immutable. However, if you really need to change the contents of an already existing tuple, you can always assign a new value to the variable which holds the tuple as a value.
```{python}
thorndike = ("Edward", "Lee", "Thorndike", 1874, "Law of effect", "Law of exercise")
thorndike = thorndike[:] + ("Law of recency",)
print(thorndike)
```
Note the trailing comma in the second line `("Law of recency",)`. Without the comma at the end of the parentheses, Python treats the content of a one-element tuple such as `("Law of recency")` as belonging to the data type of the element (in this case a `str`). As a result, a *type error* is thrown when you try to concatenate the `thorndike` tuple and `("Law of recency")`.
```{python error = TRUE}
thorndike = ("Edward", "Lee", "Thorndike", 1874, "Law of effect", "Law of exercise")
thorndike = thorndike[:] + ("Law of recency")
```
Python has a very powerful *tuple assignment* feature that allows a tuple of variable names on the left of the assignment operator `=` to be assigned to a tuple of values, like so
```{python}
thorndike = ("Edward", "Lee", "Thorndike", 1874)
(name, middle_name, surname, born) = thorndike
print(name)
```
```{python}
a, b = "Hello", "World"
print(a)
```
The brackets `()` are optional in a tuple assignment. Using tuple assignment, you can easily perform several variable assignments in just one line!
## Dictionaries
*Dictonaries* share similarities with real-life address books where you can find a person's contact details by looking up his or her name.
```{python}
address_book = {"studyadviser-PSY": "studyadviser-psy[at]utwente.nl",
"studentservices" : "studentservices[at]utwente.nl",
"ICT-helpdesk" : "servicedesk-ict[at]utwente.nl"
}
print(address_book["studyadviser-PSY"])
```
Dictionaries associate *keys* (e.g. a name) with *values* (e.g. contact details). The `key:value` pairs of a dictionary are separated by commas. Each pair contains a *key* and a *value* separated by a colon `:`. `key:value` pairs are always one-to-one mappings. This means that you cannot map the same key to several values, like so
```{python}
address_book = {"studyadviser-PSY": "studyadviser-psy[at]utwente.nl",
"studyadviser-PSY": "Cubicus, room C116"
}
print(address_book["studyadviser-PSY"])
print("Number of key:value pairs in address_book: %d" % len(address_book.keys()))
```
As you can see, the first mapping of the key `"studyadviser-PSY"` to `"studyadviser-psy[at]utwente.nl"` has been overwritten by the second mapping of the key to `"Cubicus, room C116"`. In total, `address_book` now only contains a single `key:value` pair. As with lists and tuples, values are arbitrary for dictionaries. This means that while you cannot map the same key to several values, you can map the same value to multiple keys.
```{python}
address_book = {"studyadviser-PSY": "studyadviser-psy[at]utwente.nl",
"studyadviser-PSY_email": "studyadviser-psy[at]utwente.nl"
}
print("Number of key:value pairs in address_book: %d" % len(address_book.keys()))
```
Dictionaries require their keys to be *immutable* and *unique*. As you above, you cannot have two keys named `"studyadviser-PSY"` in the `address_book`. This also means that certain data types are unsuitable as dictionary keys, like lists.
```{python error = T}
d = {['a']: 'b'}
```
Whenever you try to use a mutable object (like lists) as a key, a `TypeError` will be thrown. Because lists are mutable (you can update their elements), they are *unhashable*. Without going into the details of hash functions here, a *hash* is another representation of an object. It is a transformation of the object's length sequence of bytes into a fixed length sequence. *Hash functions* are used in cryptographic algorithms, in digital signatures, manipulation detection, password storage and more. Hash functions are constructed so that calculating an object's hash representation is simple, but retrieving the original object is difficult without knowing the function used to hash the object in the first place. You can imagine that things start to get messy if the object itself can change after its hash has been calculated. A hash is a momentary snapshot of an object. It is impossible to retrieve the "latest version" of a changable object from its hash representation, even if you know its hash function. Dictionaries themselves are also mutable (and thus, unhashable) and cannot be used as *keys*, by the way.
Dictionaries are *unordered* collections. Dictionaries do not have a sequential representation of their elements. Unlike sequences, such as lists and tuples, the elements of a dictionary cannot be accessed using indices. Values stored in a dictionary can be accessed and changed through their respective *keys*, like so
```{python}
address_book = {"studyadviser-PSY": "studyadviser-psy[at]utwente.nl",
"studentservices" : "studentservices[at]utwente.nl",
"ICT-helpdesk" : "servicedesk-ict[at]utwente.nl"
}
address_book["studentservices"] = ("studentservices[at]utwente.nl", "0534892124")
print("The telephone number of the student services is", str(address_book["studentservices"][1]))
```
Apart from updating existing dictionary entries, you can obviously also add and remove entries of a dictionary. Use the `del` statement to remove dictionary entries.
```{python}
address_book = {"studyadviser-PSY": "[email protected] ",
"studentservices" : "[email protected]",
"ICT-helpdesk" : "[email protected]"
}
address_book["studentUnion"] = "[email protected]"
print(address_book)
del address_book["ICT-helpdesk"]
print(address_book)
```
## Putting collections to use
Lists, tuples and dictionaries are readily used in interactive programs, such as the Stroop Task program. Collections help structure user input and the subsequent storage of user input in computer memory. Structuring user input makes later retrieval of information easier. Below you see a code snippet from the Stroop Task program.
```{python, eval=F}
KEYS = {"red": K_b,
"green": K_n,
"blue": K_m}
RT = []
trial_number = 0
while True:
pygame.display.get_surface().fill(BACKGR_COL)
# Changing states
for event in pygame.event.get():
if STATE == "welcome":
if event.type == KEYDOWN and event.key == K_SPACE:
STATE = "present_trial"
print(STATE)
if STATE == "present_trial":
trial_number = trial_number + 1
this_word = pick_color()
this_color = pick_color()
time_when_presented = time()
STATE = "wait_for_response"
print(STATE)
if STATE == "wait_for_response":
if event.type == KEYDOWN and event.key in KEYS.values():
time_when_reacted = time()
this_reaction_time = time_when_reacted - time_when_presented
RT.append(this_reaction_time)
this_correctness = (event.key == KEYS[this_color])
STATE = "feedback"
print(STATE)
if STATE == "feedback":
if event.type == KEYDOWN and event.key == K_SPACE:
if trial_number < 20:
STATE = "present_trial"
else:
STATE = "goodbye"
print(STATE)
if event.type == QUIT:
STATE = "quit"
```
Note how the variable `RT` is used to keep track of the user's reaction times throughout the trials. The variable `this_reaction_time` is overwritten in each trial. This means, that at the end of the program, when all trials are completed, `this_reaction_time` only contains the last value assigned to it during the 20th trial. All other reaction times were long erased from computer memory. But wait, what if we want to calculate a user's mean reaction time once the experiment is finished? In order to do so, we need some way of saving the reaction times from trial to trial. They need to persist until the last trial is completed. This is exactly where collections step into the picture. Collections are capable of storing multiple values at the same time and they can be mutated iteratively (one reaction time per trial). Hence, we `append` the calculated reaction time to a list in each trial.
In the end, you as a programmer still need to decide how to proceed with the collected reaction times. Do you want to print their mean to the screen, export them out of the program, or combine them with data gathered from other experimental subjects? It is up to you. Using collections, at least you have a user's reaction times at your disposal, even after they completed the experiment.
## Common errors
* The by far most occurring errors involving lists are `index out of range` errors. I think it is fair to say that they make up a large amount of all programming errors. An `index out of range error` is thrown when you try to access an index that does not exist in a sequence.
```{python eval = F}
shoppinglist = ["milk", "eggs", "bread", "appels"]
print shoppinglist[4]
```
```{r echo = F}
print("Error: Missing parentheses in call to 'print'. Did you mean print(shoppinglist[4])? (<string>, line 2)")
```
You will usually encounter this error as a result of confusing the starting index `0` with `1`. Against all intuition, in a list of four elements, the last element is stored at index `3`, not at index `4`. It helps to keep in mind that the indices of your sequences always have a range one shorter than the number of elements in your list. For a list with four elements, the positive index range is `0` to 4 minus 1, thus `3`.
```{python}
shoppinglist = ["milk", "eggs", "bread", "appels"]
print(shoppinglist[3])
```
* Another common mistake surrounding collections concerns the immutability of *tuples*. In particular, trying to change the content of a *tuple* will result in a `TypeError`. Tuples are immutable, meaning that you cannot change their content after their initialization. You can, however, re-assign the variable name of an existing tuple to a new tuple containing different values.
```{python error = T}
a = (1,2,4)
b[2] = 3
```
```{python}
a = (1,2,4)
print(a)
b = (1,2,3)
print(b)
```
* Concerning dictionaries, you will most probably not escape one or another `KeyError`. A `KeyError` is thrown when you try to access a `key` that does not exist in the dictionary.
```{python error = T}
d = {}
print(d['a'])
```
The same is likely to occur when you try to use indices to access the entries of a dictionary
```{python error = T}
d = {'a': 1}
print(d[0])
```
Dictionaries are the only *unordered* type of collection you encountered in this chapter. It occurs now and then that one assumes that a dictionary can be treated as other common types of collections, such as lists or tuples.
## Debugging
Here are some tips and tricks on how to debug errors that potentially originate from your collections.
1. Regularly checking the `length` of your collections can be very helpful to confirm that your code does indeed what you expect it to do. Take the example of `RT`, the list we made to keep track of a participant's reaction times throughout the Stroop Task program. At the end of the experiment, you would expect the list to contain 20 elements, given that `RT` was initialized as an empty list and that the experiment had 20 trials. By checking the `length` of `RT`, you can immediately see whether your code indeed stored one reaction time per trial. `RT` should have a `length` of 20 after the experiment. You can check the length of a collection using the `len()` method, like so
```{python}
a = [3, 4]
print(len(a))
```
2. Dictionaries have a very useful built-in method that represents their `keys` as a list.
```{python}
d = {'a': 1,
'b': 2}
print(d.keys())
```
This comes in handy when you keep getting a `KeyError` while you are convinced that your dictionary should contain a certain key.
```{python error = T}
d = {'a ': 1,
'b': 2}
print(d['a'])
```
You can use `in` to check whether a candidate `key` is among the keys of the dictionary
```{python}
d = {'a ': 1,
'b': 2}
print('a' in d.keys())
```
In this case, a typing error is the culprit. Instead of `'a'`, `'a '` with a whitespace was set as the first key of `d`.
Alternatively, you can use a simple conditional constructions and `not in` to make sure that you do not overwrite any existing keys
```{python}
d = {'a': 1,
'b': 2}
if 'a' not in d.keys():
d['a'] = 0
print(d)
```
Finally, you should know that the output list produced by the `keys()` method can be confusing at times. It is possible that your Python interpreter returns a different list of keys than the same code snippet `d.keys()` produces on another system. This has to do with how dictionaries are stored internally in computer memory. Here as well *hash functions* are utilized for efficiency reasons. As a result, the order of the keys in a dictionary is unspecified (which is why you cannot use the index operator to access elements of a dictionary). In practice, it means that `'a'` is not necessarily the first element in a list produced by `d.keys()`. Rest assured, in practice, this peculiarity is rather insignificant.
## Exercises
### Exercise 1. Indexing
What is the output of the following code snippets?
```{python, eval=F}
a = [6, 7]
b = [4, 5]
b.append(a)
print(b)
```
```{python, eval=F}
d = {1: 'a',
'a': 1}
print(d[1])
```
```{python, eval=F}
a = [1]
b = ('a', 2)
a.extend(b)
print(a[1])
```
```{python, eval=F}
a, b = (['a', 'd'], ['b', 'c'])
print(b[0])
```
```{python, eval=F}
a = [1, 2, 3]
print(a[-1])
```
```{python, eval=F}
a = (1, 2, [3, 4], 5)
print(a[2][1])
```
### Exercise 2. Debugging
Will the following code snippets throw an error? If anything goes wrong, why does it go wrong?
```{}
a, b = 'Sigmund', 'Freud', (1856, 1939)
```
* No error is thrown, `a` and `b`are assigned the values `'Sigmund'` and `'Freud'` respectively, and `(1856, 1939)` will not persist in computer memory.
* A `ValueError` is thrown. There are too many values on the right side of the equals sign `=` to unpack.
* A `NameError` is thrown. The variables `a` and `b` have not been assigned yet and can therefore not be compared with other values.
```{}
a = [1, 2]
b = (3, 4)
a.append(b)
print(a[3])
```
* Nothing goes wrong, the output is `4` because `4` is located at index `3` after `b` is appended to `a`.
* A `TypeError` is thrown. `b` cannot be appended to `a` because lists cannot have tuples as elements.
* An `IndexError` is thrown. After appending `b`, `a` has three elements. `3` is not a valid index.
```{}
a = ('a', [1, 2])
a[1].append(3)
```
* A `TypeError` is thrown because tuples are immutable and do not support item assignment.
* No error is thrown, `3` is appended to `[1, 2]`.
* An `AttributeError` is thrown, tuples do not have an `append` method.
```{}
a = {('a', 'b'): 3}
print(a.keys())
```
* No error is thrown, the list of keys contains one element, `('a', 'b')`.
* A `TypeError` is thrown. Tuples cannot be *hashed* because they are changable and therefore unsuitable as dictionary keys.
* No error is thrown. By means of *tuple assignment*, the dictionary has two keys, `a` and `b`.
### Exercise 3. Mini programs
What is the output of the following mini programs?
```{}
a = {}
print(a.keys())
```
* `[]`
* `0`
* A `KeyError` because `a` is empty and has no keys.
```{}
a = []
b = ([1, 2], [3, 4], [5, 6])
a.extend(b)
print(a[1])
```
* `[1, 2]`
* `2`
* `[3, 4]`
```{}
a = ('a', ('b', 'c'))
print('b' in a)
```
* `True`
* `False`
* A `TypeError` is thrown because the `in` operator cannot be used with tuples.
```{}
a = [{'a': 1 }, {'a': 2}]
if 'a' in a[0]:
print(a[0]['a'])
elif 'a' in a[1]:
print(a[1]['a'])
```
* `1`
* `2`
* `1` and `2`
### Exercise 4. Shopping list
```{python, eval=F}
shoppinglist = ["bread","cheese","jam","milk","oatmeal","blueberries","oranges","apples"]
shoppinglist[0] = "buns"
shoppinglist = shoppinglist + ["chocolate"]
del shoppinglist[2]
shoppinglist_mum = ["eggs","yoghurt","potatoes","salmon"]
shoppinglist += shoppinglist_mum
del shoppinglist[10]
ingredients_muffins = ["icing sugar","whipping cream","lemons","flower","vanilla sugar","eggs","mandarines","baking powder","sugar","margarine"]
shoppinglist.append(ingredients_muffins)
del shoppinglist[11][6]
shoppinglist[8:11] = [shoppinglist[8:11]]
del ingredients_muffins[7]
```
The above shopping list has been subject to several changes since it has been written. Standing in the supermarket, you want to know the contents of the final shopping list. Which groceries do you have to buy?
You can check your answer afterwards by running the code.
### Exercise 5. Dictionaries
```{python, eval=F}
famous_psychologists = {"Freud":("Sigmund", "Freud", (1856,1939), "Psychoanalysis"),
"Piaget":("Jean", "Piaget", (1896,1980), "Theory of Cognitive Development"),
"Skinner":(("Burrhus", "Frederic"), "Skinner", (1904,1990), "Operant Conditioning"),
}
erikson = ("Erik", "Erikson", (1902,1994), "Theory of Psychological Development")
famous_psychologists["Erikson"] = erikson
more_on_Skinner = {"Schedules of Reinforcement":("positive reinforcement", "negative reinforcement", "extinction"),
"Skinner Box":("pigeon","lever","food"),
}
famous_psychologists["Skinner"] = famous_psychologists["Skinner"][:3] + ("Behaviorism",) + (famous_psychologists["Skinner"][3],) + (more_on_Skinner,)
```
Using the dictionary `famous psychologists` and the above changes made to the dictionary:
* Try to understand the changes made to the original dictionary by writing down the content of the following print statements
```{python, eval=F}
print(famous_psychologists["Erikson"][:3])
print(famous_psychologists["Piaget"][3:])
print(famous_psychologists["Freud"][2][0])
print(famous_psychologists["Skinner"][0][1])
print(famous_psychologists["Skinner"][5].keys()[1])
word = famous_psychologists["Skinner"][5].keys()[0]
print(famous_psychologists["Skinner"][5][word][0])
```
* Afterwards, you may check your answers by running the code. Pay attention that your own answers match exactly.
### Exercise 6. Element selection
For the following data structures, give the code by which you can access the specified element.
```{python, eval=F}
dinner = ["minced beef", "tomatoes", "pasta", "onions", "pasta sauce", "courgette"]
shoppingList = [dinner, "bread", "milk", "cornflakes", "cheese"]
```
```{python,eval=F}
grades = {"p1": ("Judith",21,"female","B-PSY",(5.6,7.7,7.3)),
"p2": ("Menno",19,"male","B-CREATE",(8.6,6.9,7.4)),
"p3": ("Jan",19,"male","B-CREATE",(3.9,4.5,8.9)),
"p4": ("Eva",20,"female","B-MATH",(8.0,7.0,9.1)),
"p5": ("Sophia",22,"female","M-PSY",(4.9,6.7,7.6)),
"p6": ("Jeroen",25,"male","M-BME",(8.5,8.5,8.5)),
"p7": ("Max",18,"male","B-IBA",(7.9,2.0,6.5))
}
```
1. `milk` in `shoppingList`
2. `minced beef` in `shoppingList`
3. The study of `Jan` in `grades`
4. The age of `Sophia` in `grades`
5. The name of participant 6 in `grades`
6. Judith's grade on the third partial exam in `grades`
7. The grade of participant 5 on the first partial exam in `grades`
8. The maximum grade achieved on the second partial exam
### Exercise 7. Adjust a dictionary data set
The data set below originates from a (fictive) language course given at the University of Twente. During the course, the following information was stored about the participants: age, gender, nationality, study programme, for each lesson, whether the participant was present or not (`True` indicates presence during the lesson and `False` absence), and the paricipant's score on the final exam.
```{python, eval=F}
dataset = {'p1':[21,"Female","Dutch","B-Psychology",[True,False,True,False,False],5.2],
'p2':[20,"Female","Dutch","B-Psychology",[True,True,True,False,True],8.4],
'p3':[21,"Female","Dutch","B-Applied_Mathematics",[False,True,True,False,True],7.8],
'p4':[23,"Male", "German","B-Communication_Science",[True,True,"n/a","n/a",True],8.8],
'p5':["n/a","n/a","Dutch","M-Business_Administration",[False,False,True,True,True],7.6],
'p6':[19,"Male","Swedish","B-Computer_Science",[True,False,False,True,False],7.5],
'p7':[19,"Male","German","B-Communication_Science",[True,True,False,True,True],5.4]
}
```
The lecturer has to perform some last minute changes to the data set before handing it in for statistical examination. Perform the following changes to the data set by accessing the relevant elements.
1. After taking a resit, participant 1 and participant 7 did pass the course. Participant 1 scored a 6.1 on the resit and participant 7 a 7.4.
2. Participant 5 forgot to put her age and gender on the registration form. Participant 5 is female and 23 years old.
3. The docent forgot to sign the presence of participant 4 during two lessons. Participant 4 was present during all lessons.
4. During the course, participant 2 changed her study programme from B-Psychology to B-Health Sciences.
5. After the final examination, it was revealed that participant 4 cheated during the exam. The examination board decided to void the student's achievements on the exam. In such a case, the additional note `expelled` is added to the student's record, indicating the student's misbehaviour.
### Exercise 8. A dictionary data set
Fill in the participant information given in the table below in a Python dictionary with the following structure:
```{python, eval=F}
participants = {participant_no:(gender, age, nationality, profession)}
```
Participant No.|Gender|Age|Nationality|Profession
-|-|-----|-------|------|----------
1|Male|19|Dutch|Student
2|Male|47|Dutch|Pharmacist
3|Male|31|Italian|PhD Student
4|Female|22|German|Student
5|Female|46|Dutch|Florist
6|Male|27|Dutch|Student
7|Female|22|Dutch|Police trainee
8|Female|26|Indian|Architect
9|Male|18|American|Student
10|Male|20|Chinese|Student
Using Python and by accessing the relevant elements in the dataset
1. Calculate the *average age* of the participants
2. Calculate the *standard deviation* of the age variable
3. Calculate the *maximum* and *minimum* of the age variable
*Hint*: Libraries provide built-in methods beyond the repertoire of the standard Python language. For instance, `NumPy` provides all kinds of methods for statistical operations in Python. You can import a library by typing `import (library name)` and make use of library-specific methods like this `(library name).(method)`. Take a look at the documentation of the `NumPy` library.
### Exercise 9. Stroop extension
In this exercise you will implement a conditional execution of the interactive Stroop program. Imagine that you want to examine whether the number of colors influences the reaction time of participants in the Stroop experiment. In particular, you have gathered ten participants whom you randomly assigned to either a three or a five word version of the Stroop task. The table below summarizes which participant is assigned to which condition.
Participant No.|Condition
-|-|
1|Stroop 3
2|Stroop 3
3|Stroop 5
4|Stroop 3
5|Stroop 5
6|Stroop 5
7|Stroop 5
8|Stroop 3
9|Stroop 5
10|Stroop 3
For this exercise, a modified version of the Stroop task is given. In `ch4ex6Stroop.py`, a rudimentary implementation of user console input is provided. In two additional `STATES`, the user enters his participant number and the entered number is stored for the rest of the program run. The modified script has a function `pickColor5()`, which implements the random selection of words and colors for the five-color version.
In order to implement the comparative Stroop version program (three vs. five colors) program, consider the following steps:
* Open `ch4ex6Stroop.py` and scan the changes to the original code. Try to understand the changes performed to the code.
* Incorporate the information contained in the table above on the condition assignment of the ten participants. Which data structure will you use for storing this information?
* Prepare the code for both Stroop task versions, a version with three words, three colors, and three buttons and the same for the five-word version. Choose any two additional colors.
* Think about which places in the program need to be adapted and which of them need adaptation depending on the condition.
## Think further
* For more detailed information on *lists* beyond the scope of this chapter, you are advised to take a look at [Wentworth, P., Elkner, J., Downey, A. B., & Meyer, C. (2015). How to think like a computer scientist: Learning with Python 3. Chapter 11. Lists](http://openbookproject.net/thinkcs/python/english3e/lists.html)
* For more information on *dictionaries*, including explanation on useful built-in functions of the dictionary class, you are invited to take a look at [Wentworth, P., Elkner, J., Downey, A. B., & Meyer, C. (2015). How to think like a computer scientist: Learning with Python 3. Chapter 20. dictionaries](http://openbookproject.net/thinkcs/python/english3e/dictionaries.html)
* More information on *tuples* can be found in [Wentworth, P., Elkner, J., Downey, A. B., & Meyer, C. (2015). How to think like a computer scientist: Learning with Python 3. Chapter 9. Tuples](http://openbookproject.net/thinkcs/python/english3e/tuples.html)