Arrays are data structures that store values of the same data type in a contiguous memory area. Thus, arrays are more memory-optimal and perform better than lists but are not supported natively in Python. However, they are supported by the array and numpy modules. In the following, you will look at the processing of data with the help of additional modules and deepen it with the help of exercises.
6.1 Introduction
NumPy almost feels like a built-in data type since many operations like slicing and index accesses or the standard function len() are possible. You’ll look at what to consider for multidimensional arrays later.
By providing arrays as a stand-alone module, an import is necessary. But arrays, unlike many built-in data types in other languages, are not just simple data containers but can do much more in terms of functionality than in Java or C++, for example.
NumPy offers various mathematical functionalities. I will go into more detail about NumPy specialties later. Upfront you will investigate one-dimensional and multidimensional arrays in this introduction and build a basic understanding of arrays.
6.1.1 One-Dimensional Arrays
As an introduction to processing data with arrays and to build knowledge of possible interview questions, let’s look at some examples.
Textual Output
Example 1: Swapping Elements
Please keep in mind that readability and understandability are the keys to correctness and maintainability. Besides, this often facilitates testability.
While the helper variable to save one assignment is pretty catchy here, there are definitely more elaborate traceable low-level optimizations in other use cases. They are usually more difficult to read and less comprehensible.
Example 2: Basic Functionality for Arrays
Please note the following: The Python built-in function len() returns the length of a list, so also for arrays, as long as they are one-dimensional. Alternatively, NumPy provides the attribute size on the array. For two-dimensional arrays, the values differ, but more about this later.
Example 3: Remove Duplicates
- 1.
Is it necessary to keep the order/sorting of the numbers?
- 2.
May a new array be created or must the actions be processed inplace—within the original array?
- 3.For inplace there are further questions:
- a.
What exactly should happen when removing/deleting?
- b.
What value represents no entry?
Solution 1 for Example 3: New array and sorted input Suppose you are to return a new array as a result when eliminating duplicates.
This example illustrates the advantages of programming small functionalities that are self-contained and follow the SRP (Single Responsibility Principle). Even more: Keeping public methods understandable and moving details to (preferably private) helper methods often allows you to keep subsequent changes as local as possible. By the way, I discuss the SRP in detail in my book Der Weg zum Java-Profi [Ind20].
- 1.
You should return the length of the valid range.
- 2.
You should delete the following positions with a special value, like -1 for primitive number types or for reference types often None. This value must not be part of the value set. Otherwise, irritations and inconsistencies are inevitable.
Interim conclusion The example illustrates several problematic issues. First, that it is often more complex to work inplace—that is, without creating new arrays, but directly within the original array. Second, to handle changes when values remain in the array but are no longer part of the result, you can either return a counter or erase the values with a neutral, special value. However, it is often more understandable and therefore recommended to use the variants shown, which create a new array.
As you can see, there is more to consider, even for apparently simple tasks. This is why requirements engineering and the correct coverage of requirements are a real challenge.
Example 4: Rotation by One or More Positions
Let’s look at another problem, namely rotating an array by n positions to the left or to the right, where the elements are then to be shifted cyclically at the beginning or the end, respectively, as visualized below, where the middle array is the starting point:
The algorithm for a rotation by one element to the right is simple: Remember the last element and then repeatedly copy the element that is one ahead in the direction of rotation to the one behind it. Finally, the cached last element is inserted at the foremost position.
In case you are wondering about the different console outputs, it should be noted that there is a difference between the formatting with __repr__() and __str__(). In the first case, you get the type info and then the values are comma-separated as output. In the second case, the output is the same as the standard of lists.
This solution is acceptable in principle, although not performant due to the frequent copy actions. How can it be more efficient?
There is one more small feature to consider. Namely, if n is larger than the length of the array, you don’t have to rotate all the time; you can limit this to what is actually needed by using the modulo operation i < n % len(values).
Here’s another hint: The function just presented for rotation can be suboptimal in terms of memory, especially if the value of n is very large and the array itself is also huge, but for our examples, this does not matter. Interestingly, the simple version would then be better in terms of memory, although probably rather slow due to the frequent copy actions.
6.1.2 Multidimensional Arrays
In this section, I will briefly discuss multidimensional arrays . Because it is more common in practice and easy to imagine visually, I will just discuss two-dimensional arrays.2
In Python, a two-dimensional array can be used for processing, which you can construct based on strings converted to lists as follows:
In the code above, you can see the helper function get_dimension(values) to determine the dimensions for both lists and NumPy arrays. This allows using one or the other without worrying. See subsection 6.1.4 for a broader explanation.
Introductory Example
You see that a 4 × 2 array turns into a 2 × 4 array.
Modeling Directions
Example: Random traversal To go a little deeper on processing with directions, let’s develop a traversal for a playfield. Whenever you hit array boundaries, you randomly choose a new direction not equal to the old one:
Using this trick, you always have eight adjacent cells. This helps to avoid special treatments in your programs. This is also true, for example, when walking through the array. Instead of checking for the array boundaries, you can restrict yourself to checking if you reach a boundary field. Sometimes it is handy to use a neutral element, such as the value 0, since this does not affect computations.
6.1.3 Typical Errors
Off-by-one: Sometimes you are off by one element when accessing because, for example, the index calculation contains an error, such as adding or subtracting 1 to correct the index or comparing positions with <, <=, >, or >=.
Array bounds: Similarly, the bounds of the array are sometimes inadvertently disregarded, for example, by incorrect use of <, <= or >, >= when comparing length or lower or upper bounds.4
Dimensions: As mentioned, how x and y are represented depends on the chosen flavor. This quickly causes x and y to be interchanged for two-dimensional arrays.
Rectangular property: Although an n × m array is assumed to be rectangular, this need not be the case in Python when using nested lists. You can specify a different length for each new row, but many of the examples below use rectangular arrays,5 especially because they are only supported by NumPy. The reason lies in the arrangement in memory for maximum performance.
Neutral element: What represents no value. Is it -1 or None? How do you deal with this if these are possible values?
6.1.4 Special Features
I would like to point out something extraordinary. Practically, almost all of our developed program modules can be used for NumPy arrays and lists without changing much in the algorithmic part of the functions. Often, all that is needed is the determination of the sizes shown below. This is a significant advantage in contrast to algorithms in, say, Java and C++, which must be developed specifically for lists and other types.
Special Treatment for Generalizations
6.1.5 Recapitulation: NumPy
So far, you have used NumPy in various examples without it being remarkably different in handling than lists. This is a big plus. Nevertheless, I would like to introduce a few things explicitly and point out others.
What is NumPy? NumPy stands for Numerical Python and is a module for processing arrays. Besides basic functionalities, there are mathematical extensions for linear algebra and matrices.
Creating NumPy Arrays Based on Lists
Creating NumPy Arrays with Particular Values
zeros()
ones()
empty()
The lists created in this way are not independent of each other, and changes have effects on the other lines.
Other Functionalities of NumPy Arrays
Previously I indicated that NumPy offers some mathematical functionalities out of the box. But not only that. There are various others, which you can explore in detail in https://numpy.org/doc/stable/reference/routines.array-manipulation.html. As an example, I’ll demonstrate the vertical and horizontal flipping of the contents of an array, which you are supposed to rebuild by hand in exercise 2.
Advantages of NumPy
As is well known, lists in Python are very convenient and provide an ordered and changeable sequence of values. The values stored can be of different types (heterogeneous) or contain only the same types (homogeneous). Multidimensional structures are possible by nesting lists. NumPy allows only homogeneous value assignments, which is often an advantage rather than a disadvantage.
NumPy arrays fit seamlessly and are easy to use.
NumPy arrays use (slightly) less memory.
NumPy arrays are (much) faster than lists for various use cases.
However, the last point only applies when processing enormous amounts of data, especially when performing mathematical operations such as matrix multiplication. Normal array accesses are sometimes even slower than indexed list accesses. I will show this with an example later.
Memory Consumption
You can see that (on my machine6) each element in a list occupied 24 bytes, but in NumPy, only 8 bytes. With NumPy the total size resulted from the number of elements, their size in bytes, and the number of bytes for the NumPy array as management:
100.000 ∗ 8 + 96 = 800.096
With lists, the output confused me. According to the number for a single element
100.000 ∗ 24 + x = 2.400.000
should be occupied, but surprisingly I got around 824.000.
Performance Comparison
Finally, let’s compare lists and arrays concerning their performance. I started with the basic functionality of indexed access to recognize that lists have a slight advantage here. However, when it comes to actions on all elements of an array, particularly complex mathematical operations like matrix multiplication, the picture reversed massively. NumPy clearly showed its strengths. Let’s have a closer look at this through examples in more detail.
6.2 Exercises
6.2.1 Exercise 1: Even Before Odd Numbers (★★✩✩✩)
Write function order_even_before_odd(numbers). This is supposed to rearrange a given array or a list of int values so that the even numbers appear first, followed by the odd numbers. The order within the even and odd numbers is not of relevance.
Examples
Input | Result |
---|---|
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | [2, 4, 6, 8, 10, 3, 7, 1, 9, 5] |
[2, 4, 6, 1, 8] | [2, 4, 6, 8, 1] |
[2, 4, 6, 8, 1] | [2, 4, 6, 8, 1] |
6.2.2 Exercise 2: Flip (★★✩✩✩)
Write generic functions for flipping a two-dimensional array horizontally with flip_horizontally(values2dim) and vertically with flip_vertically(values2dim). The array should be rectangular, so no line should be longer than another.
Examples
6.2.3 Exercise 3: Palindrome (★★✩✩✩)
Write function is_palindrome(values) that checks an array of strings for whether its values form a palindrome.
Examples
Input | Result |
---|---|
[“One”, “Test”, “ – ”, “Test”, “One”] | True |
[“Max”, “Mike”, “Mike”, “Max”] | True |
[“Tim”, “Tom”, “Mike”, “Max”] | False |
6.2.4 Exercise 4: Inplace Rotate (★★★✩✩)
Exercise 4a: Iterative (★★★✩✩)
In the introductory section, I showed how to rotate arrays. Now try this inplace without creating a new array. Your task is to rotate a two-dimensional, square-shaped array by 90 degrees clockwise. Write generic function rotate_inplace(values2dim) that iteratively implements this.
Example
Exercise 4b: Recursive (★★★✩✩)
Write recursive function rotate_inplace_recursive(values2dim) that implements the desired 90-degree clockwise rotation.
6.2.5 Exercise 5: Jewels Board Init (★★★✩✩)
Exercise 5a: Initialize (★★★✩✩)
Initialize a two-dimensional rectangular array with random-based numbers representing various types of diamonds or jewels as numerical values. The constraint is that initially there must not be three diamonds of the same type placed horizontally or vertically in direct sequence. Write function init_jewels_board(width, height, num_of_colors) to generate a valid array of the given size and quantity of different types of diamonds.
Example
Bonus: Diagonal Check (★★★✩✩) Add a check for diagonals. This should make the constellation from the example invalid, among other things, because of the diagonals marked in bold with the number 3 at the bottom right.
Exercise 5b: Validity Check (★★★✩✩)
In this subtask, you want to validate an existing playfield. As a challenge, a list of violations must be returned. Implement function check_board_validity(board2dim) for a rectangular array.
Example
6.2.6 Exercise 6: Jewels Board Erase Diamonds (★★★★✩)
Exercise 6a: Erase (★★★★✩)
Write function erase_chains(values2dim) that erases all rows of three or more contiguous diamonds in horizontal, vertical, and diagonal orientations from a rectangular playfield array.
Examples
Exercise 6b: Falling Down (★★★✩✩)
Write function fall_down(values2dim) working inplace that drops the diamonds from top to bottom, provided there is a space below their position.
Example
6.2.7 Exercise 7: Spiral Traversal (★★★★✩)
Write generic method spiral_traversal(values2dim) that traverses a two-dimensional rectangular array (or a nested list) in the form of a spiral and prepares it as a list. The start is in the upper left corner. First the outer layer is traversed and then the next inner layer.
Example
6.2.8 Exercise 8: Add One to an Array as a Number (★★✩✩✩)
Consider an array or a list of numbers representing the digits of a decimal number. Write function add_one(digits) that performs an addition by the value 1 and is only allowed to use arrays as a data structure for the solution.
Examples
Input | Result |
---|---|
[1, 3, 2, 4] | [1, 3, 2, 5] |
[1, 4, 8, 9] | [1, 4, 9, 0] |
[9, 9, 9, 9] | [1, 0, 0, 0, 0] |
6.2.9 Exercise 9: Sudoku Checker (★★★✩✩)
In this challenge, a Sudoku puzzle is examined to see if it is a valid solution. Let’s assume a 9 × 9 array with int values. According to the Sudoku rules, each row and each column must contain all numbers from 1 to 9. Besides, all numbers from 1 to 9 must, in turn, occur in each 3 × 3 subarray. Write function is_sudoku_valid(board)for checking.
Example
Bonus While it is nice to be able to check a Sudoku board that is completely filled with digits for its validity, it is even better to be able to predict for a board with gaps (i.e., missing digits) whether a valid solution can emerge from it. This is of particular interest if you want to develop an algorithm for solving a Sudoku puzzle.
Example
6.2.10 Exercise 10: Flood Fill (★★✩✩✩)
Exercise 10a (★★✩✩✩)
Write function flood_fill(values2dim, start_x, start_y) that fills all free fields in an array or a two-dimensional nested list with a specified value.
Example
Exercise 10b (★★✩✩✩)
Extend the function to fill any pattern passed as a rectangular array. However, spaces are not allowed in the pattern specification.
Example
6.2.11 Exercise 11: Array Min and Max (★★✩✩✩)
Exercise 11a: Min and Max (★✩✩✩✩)
Write two functions find_min(values) and find_max(values) that search for the minimum and maximum, respectively, of a given non-empty array using a self-implemented search, thus eliminating the usage of built-ins like min() and sort(). :-)
Example
Input | Minimum | Maximum |
---|---|---|
[2, 3, 4, 5, 6, 7, 8, 9, 1, 10] | 1 | 10 |
Exercise 11b: Min und Max Pos (★★✩✩✩)
Implement two helper functions find_min_pos(values, start, end) and find-_max_pos(values, start, end) that seek and return the position of the minimum and maximum, respectively. Again, assume a non-empty array and additionally an index range of left and right boundaries. In the case of several identical values for minimum or maximum, the first occurrence should be returned.
To find the minimum and maximum values, respectively, write two functions find_min_by_pos(values, start, end) and find_max_by_pos(values, start, end) that use the helper function.
Examples
Method | Input | Range | Result | Position |
---|---|---|---|---|
find_min_xyz() | [5, 3, 4, 2, 6, 7, 8, 9, 1, 10] | 0, 10 | 1 | 8 |
find_min_xyz() | [5, 3, 4, 2, 6, 7, 8, 9, 1, 10] | 0, 7 | 2 | 3 |
find_min_xyz() | [5, 3, 4, 2, 6, 7, 8, 9, 1, 10] | 2, 7 | 2 | 3 |
find_max_xyz() | [1, 22, 3, 4, 5, 10, 7, 8, 9, 49] | 0, 10 | 49 | 9 |
find_max_xyz() | [1, 22, 3, 4, 5, 10, 7, 8, 9, 49] | 0, 7 | 22 | 1 |
find_max_xyz() | [1, 22, 3, 4, 5, 10, 7, 8, 9, 49] | 2, 7 | 10 | 5 |
6.2.12 Exercise 12: Array Split (★★★✩✩)
Say you have an array (or list) of arbitrary integers. For this task, the data structure is to be reordered so that all values less than a special reference value are placed on the left. All values greater than or equal to the reference value are placed on the right. The ordering within the subranges is not relevant and may vary.
Examples
Input | Reference element | Sample result |
---|---|---|
[4, 7, 1, 20] | 9 | [1, 4, 7, 9, 20] |
[3, 5, 2] | 7 | [2, 3, 5, 7] |
[2, 14, 10, 1, 11, 12, 3, 4] | 7 | [2, 1, 3, 4, 7, 14, 10, 11, 12] |
[3, 5, 7, 1, 11, 13, 17, 19] | 11 | [1, 3, 5, 7, 11, 11, 13, 17, 19] |
Exercise 12a: Array Split (★★✩✩✩)
Write function array_split(values, reference_element) to implement the functionality described above. In this first part of the exercise, it is allowed to create new data structures, such as lists.
Exercise 12b: Array Split Inplace (★★★✩✩)
Write function array_split_inplace(values, reference_element) that implements the functionality described inside the source array (i.e., inplace). It is explicitly not desirable to create new data structures. To be able to include the reference element in the result, the creation of an array is allowed once for the result. Because this has to be returned, it is permitted to return a value for an inplace function; indeed, it operates only partially inplace here.
Exercise 12c: Array Split Quick Sort Partition (★★★✩✩)
For sorting according to Quick Sort, you need a partitioning functionality similar to the one just developed. However, often the foremost element of the array is used as the reference element.
Based on the two previously developed implementations that use an explicit reference element, your task is to create corresponding alternatives such as the functions array_split_qs(values) and array_split_qs_inplace(values).
Examples
Input | Reference element | Sample result |
---|---|---|
[9, 4, 7, 1, 20] | 9 | [1, 4, 7, 9, 20] |
[7, 3, 5, 2] | 7 | [2, 3, 5, 7] |
[7, 2, 14, 10, 1, 11, 12, 3, 4] | 7 | [2, 1, 3, 4, 7, 14, 10, 11, 12] |
[11, 3, 5, 7, 1, 11, 13, 17, 19] | 11 | [1, 3, 5, 7, 11, 11, 13, 17, 19] |
6.2.13 Exercise 13: Minesweeper Board (★★★✩✩)
The chances are high that you’ve played Minesweeper in the past. To remind you, it’s a nice little quiz game with a bit of puzzling. What is it about? Bombs are placed face down on a playfield. The player can choose any field on the board. If a field is uncovered, it shows a number. This indicates how many bombs are hidden in the neighboring fields. However, if you are unlucky, you hit a bomb field and you lose. Your task is about initializing such a field and preparing it for a subsequent game.
Solution 13a (★★✩✩✩)
Write function place_bombs_randomly(width, height, probability) that creates a playfield specified in size via the first two parameters, randomly filled with bombs, respecting the probability from 0.0 to 1.0 passed in.
Example
Exercise 13b (★★★✩✩)
Write function calc_bomb_count(bombs) that computes the number of adjacent fields based on the bomb fields passed in and returns a corresponding array.
Examples
Exercise 13c (★★✩✩✩)
Write function print_board(bombs, bomb_symbol, bomb_counts) that allows you to display a board as points and stars as well as numbers and B.
Example
6.3 Solutions
6.3.1 Solution 1: Even Before Odd Numbers (★★✩✩✩)
Write function order_even_before_odd(numbers). This is supposed to rearrange a given array or a list of int values so that the even numbers appear first, followed by the odd numbers. The order within the even and odd numbers is not of relevance.
Examples
Input | Result |
---|---|
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | [2, 4, 6, 8, 10, 3, 7, 1, 9, 5] |
[2, 4, 6, 1, 8] | [2, 4, 6, 8, 1] |
[2, 4, 6, 8, 1] | [2, 4, 6, 8, 1] |
A variation is to arrange all odd numbers before the even ones. Therefore, it is possible to write function order_odd_before_even(numbers) where again the ordering within the odd and even numbers is not important.
The algorithm is identical to that shown except for minimal differences in an inverted test. This modification is so simple that the function is not shown again here.
Optimized Algorithm: Improved Running Time
You recognize that your checks have quadratic running time, here O(n · m), because you should aim to reduce the running time of an algorithm to O(1) in the best case, preferably O(n) or at least O(n · log(n)), ideally without reducing readability, so nested loops are used. This is not quite so dramatic for pure computations and comprehensibility. For an introduction to the O-notation, please consult Appendix C.
In this case, reducing the running time to O(n) is actually fairly straightforward. As in many solutions to other problems, two position markers are used, here next_even and next_odd. In the beginning, it is assumed that the first element is even and the last odd. Now it is checked if the front number is really even, and the position marker is moved to the right. If the first odd number is encountered, it is swapped with the last element. Even if the last element were odd, it would be swapped again in the next step.
Optimized Algorithm: Less Copying
The previous optimization can be taken a little further. Instead of just skipping the even numbers from the left until you encounter an odd number, you can skip values starting two additional while loops. However, you still preserve a O(n) running time from both sides as long as they are even in the front and odd in the back. This is required since you are traversing the same elements and not performing steps more than once (this insight requires some experience).
Verification
6.3.2 Solution 2: Flip (★★✩✩✩)
Write generic functions for flipping a two-dimensional array horizontally with flip_horizontally(values2dim) and vertically with flip_vertically(values2dim). The array should be rectangular (i.e., no line should be longer than another).
Examples
Horizontal flipping algorithm Traverse inwards from the left and right side of the array. To do this, use two position markers leftIdx and rightIdx. At each step, swap the values referenced by these positions and move inward until the positions overlap. The termination occurs at leftIdx >= rightIdx. Repeat the procedure for all lines.
Algorithm for vertical flipping Move from the top and bottom towards the center until both positions overlap. Swap the values and repeat this for all columns. The implementation traverses the array in the x-direction and operates with two position markers on the vertical. After each swap, these position markers are moved towards each other until they cross. You then proceed with the next x-position.
Modified algorithm In fact, the implementation for flipping may be simplified a little bit. The number of steps can be directly computed in both cases: it is width/2 or height/2. For odd lengths, the middle element is not taken into account, resulting in a correct flip.
This optimization is not possible for NumPy arrays since they operate purely on a contiguous piece of memory. You can read therein row by row, but you can’t swap the references to these rows with each other. On the other hand, you can quickly turn a 4 × 4 array into a 2 × 8 array or 8 × 2 array with reshape().
Verification
Both other functions are tested in exactly the same way as the previous ones so the associated test functions are not shown here again.
6.3.3 Solution 3: Palindrome (★★✩✩✩)
Write function is_palindrome(values) that checks an array of strings for whether its values form a palindrome.
Examples
Input | Result |
---|---|
[“One”, “Test”, “ – ”, “Test”, “One”] | True |
[“Max”, “Mike”, “Mike”, “Max”] | True |
[“Tim”, “Tom”, “Mike”, “Max”] | False |
Please keep in mind that for this approach in the presumably rare case of very large amounts of data, an inverse variant of the original data is generated here and thus the memory is required twice.
Verification
6.3.4 Solution 4: Inplace Rotate (★★★✩✩)
Solution 4a: Iterative (★★★✩✩)
In the introductory section, I showed how to rotate arrays. Now try this inplace (i.e., without creating a new array). Your task is to rotate a two-dimensional square shaped array by 90 degrees clockwise. Write generic function rotate_inplace(values2dim) that iteratively implements this.
Example
Repeat the procedure layer by layer for all neighbors of TL until TR is reached (analogously for the neighbors of the other corners). Then move one position inwards at a time until BL and BR intersect. Let’s clarify the procedure again step by step.
Solution 4b: Recursive (★★★✩✩)
Write recursive function rotate_inplace_recursive(values2dim) that implements the desired 90-degree clockwise rotation.
Verification
I deliberately show several variants of how to convert a textual representation into a two-dimensional array. I prefer the second variant, especially if using the function to_list(text), which removes the spaces and then formats the string as a list.
6.3.5 Solution 5: Jewels Board Init (★★★✩✩)
Solution 5a: Initialize (★★★✩✩)
Initialize a two-dimensional rectangular array with random-based numbers representing various types of diamonds or jewels as numerical values. The constraint is that initially there must not be three diamonds of the same type placed horizontally or vertically in direct sequence. Write function init_jewels_board(width, height, num_of_colors), which will generate a valid array of the given size and quantity of different types of diamonds.
Example
However, the fact that a valid distribution is also available even for only two values gets obvious by the alternating distribution of the white and black squares of a chessboard. One way to fix the just-mentioned weakness is to choose a more powerful algorithm, such as one that uses backtracking.
There is another weak point: The generation of random numbers out of a small range of values often produces the same number several times, but this number has probably already been checked. This must be avoided. For this purpose, all previously selected random numbers can be stored in a set. Besides, you would have to check whether all expected and possible numbers have already been tried. This short list shows that it is much more complex than you might initially expect.
Now let’s move on to checking the horizontal and vertical. At first, you could assume that starting from the current position, you would have to check to the left and right as well as up and down. However, if you reread the assignment more carefully, it says that no chains of length three or longer are allowed. Because you fill the playfield from top to bottom and from left to right, no diamonds to be checked can exist on the right and below the current position. Thus, you can limit yourself to checking to the left and to the top. Furthermore, you do not need to check for longer chains since they cannot occur if you have identified a chain of three.
In this example, I follow the strategy of defining small helper functions, which on the one hand increases the amount of source code. On the other hand, functionalities can be described and tested very well in isolation. Moreover, this approach often allows expressing the source code on a comprehensible and conceptual level. In many cases, this allows extensions to be easily integrated.
Solution to the Bonus Task: Checking Diagonals (★★★✩✩)
Add a check for diagonals. This should make the constellation from the example invalid, among other things, because of the diagonals marked in bold with the number 3 at the bottom right.
Verification
Solution 5b: Validity Check (★★★✩✩)
In this subtask, you want to validate an existing playfield. As a challenge, a list of violations must be returned. Implement function check_board_validity(board2dim) for a rectangular array.
Example
Verification
6.3.6 Solution 6: Jewels Board Erase Diamonds (★★★★✩)
Solution 6a: Erase (★★★★✩)
Write function erase_chains(values2dim) that erases all rows of three or more contiguous diamonds in horizontal, vertical, and diagonal orientations from a rectangular playfield array.
Examples
A second idea is to modify the algorithm minimally by choosing an intermediate representation that symbolizes the deletion request, such as negative numbers, instead of deletion. After all entries in the array have been processed, the deletion takes place in a separate pass. Specifically, you remove all negative values from the array by replacing them with the numerical value 0.
I want to point out that the functionalities are solved using side effects. Here, you are operating directly on the passed data, so this is not bad because the data is not passed further out. Instead, it is all internal functionalities.
Verification
Solution 6b: Falling Down (★★★✩✩)
Write function fall_down(values2dim) working inplace that drops the diamonds from top to bottom, provided there is a space below their position.
Example
Algorithm At first, the task seems to be relatively easy to solve. However, the complexity increases due to a few special characteristics.
You recognize that propagation is missing, and thus the numbers do not continue to fall all the way down, even if there is an empty place below.
You now know that both of the variants discussed do not yet work quite correctly. Moreover, it was crucial to use the right set of test data to uncover just these specific problems.
Verification
Overall Verification
Implementing the supplementary processing based on characters is the subject of the following practical tip. You will probably also suddenly realize why a few seemingly unimportant auxiliary functions were created in the previous implementation.
Using the approach described ensures that the higher-level functions for determining which chains to delete, the actual deletion, and the falling of the diamonds don’t even need to be adjusted.
6.3.7 Solution 7: Spiral Traversal (★★★★✩)
Write generic method spiral_traversal(values2dim) that traverses a two-dimensional rectangular array (or a nested list) in the form of a spiral and prepare it as a list. The start is in the upper left corner. First, the outer layer is traversed, and then the next inner layer.
Example
Before proceeding with a solution, be sure to clarify any constraints or special requirements by asking questions. In this case, ask if the original data should be a rectangular. Assume that to be the case here.
After a complete traversal of one layer, you have to move the position pointer one position towards the center. This gets easily forgotten.
The algorithm presented works, but it requires quite a few special treatments.
Optimized algorithm Look at the figure again and then think a bit. You know that initially the whole array is a valid movement area. At each iteration, the outer layer is processed and you continues inwards. Now you can specify the valid range by four position markers as before. However, you proceed more cleverly when updating.
You notice that after moving to the right, the top line is processed so that you can increase the counter min_y by one. If you move down, then the rightmost side is traversed, and the counter max_x is decreased by one. Moving to the left, the bottom row is processed, and the counter max_y is decreased by one. Finally, when moving upwards, the counter min_x is increased by one. To detect when to increment, you implement utility function is_outside() or range checking.
Verification
6.3.8 Solution 8: Add One to an Array as a Number (★★✩✩✩)
Consider an array or a list of numbers representing the digits of a decimal number. Write function add_one(digits) that performs an addition by the value 1 and is only allowed to use arrays as data structure for the solution.
Examples
Input | Result |
---|---|
[1, 3, 2, 4] | [1, 3, 2, 5] |
[1, 4, 8, 9] | [1, 4, 9, 0] |
[9, 9, 9, 9] | [1, 0, 0, 0, 0] |
In the special case that the carry propagates all the way to the front, the array must be enlarged by one position to accommodate the new leading 1.
Verification
6.3.9 Solution 9: Sudoku Checker (★★★✩✩)
In this challenge, a Sudoku puzzle is examined to see if it is a valid solution. Let’s assume a 9 × 9 array with int values. According to the Sudoku rules, each row and each column must contain all numbers from 1 to 9. Besides, all numbers from 1 to 9 must, in turn, occur in each 3 × 3 subarray. Write function is_sudoku_valid(board)for checking.
Example
You might wonder whether it’s preferable to collect the values in a set. Although this is obvious and works well for fully filled Sudoku puzzles, collecting data in a set complicates subsequent checking if you permit empty fields as well.
I would like to explicitly point out the elegance of the helper function all_desired-_numbers(). It unifies various things in its brevity: actually, you need to check that the collected values do not contain duplicates and that there are exactly nine different digits. Due to your implementation, you don’t need to check the length. Still, you do it anyway to guard against careless errors with an exception. By converting the values into a set and comparing it to the set from the expected values, the process is nice and short.
Verification
As expected, you get four times the value True as a result.
Bonus
While it is nice to be able to check a Sudoku board that is completely filled with digits for its validity, it is even better to be able to predict for a board with gaps (i.e., still missing digits) whether a valid solution can emerge from it. This is of particular interest if you want to develop an algorithm for solving a Sudoku puzzle.
Example
The very best comes at the end. This function works for completely filled Sudoku puzzles and those containing blanks!
Verification
6.3.10 Solution 10: Flood Fill (★★✩✩✩)
Exercise 10a (★★✩✩✩)
Write function flood_fill(values2dim, start_x, start_y) that fills all free fields in an array or a two-dimensional nested list with a specified value.
Example
Verification
Solution 10b (★★✩✩✩)
Extend the function to fill any pattern passed as a rectangular array. Spaces are not allowed in the pattern specification.
Example
Verification
6.3.11 Solution 11: Array Min and Max (★★✩✩✩)
Solution 11a: Min and Max (★✩✩✩✩)
Write two functions find_min(values) and find_max(values) that search for the minimum and maximum, respectively, of a given non-empty array using a self-implemented search, thus eliminating the usage of built-ins like min() and sort(). :-)
Example
Input | Minimum | Maximum |
---|---|---|
[2, 3, 4, 5, 6, 7, 8, 9, 1, 10] | 1 | 10 |
Due to the boundary condition of a non-empty output array, you can always start with the first element as minimum or maximum.
Solution 11b: Min und Max Pos (★★✩✩✩)
Implement two helper functions find_min_pos(values, start, end) and find-_max_pos(values, start, end) that seek and return the position of the minimum and maximum, respectively. Again, assume a non-empty array and additionally an index range of left and right boundaries. In the case of several identical values for minimum or maximum, the first occurrence should be returned.
To find the minimum and maximum values, respectively, write two functions find_min_by_pos(values, start, end) and find_max_by_pos(values, start, end) that use the helper function.
Examples
Method | Input | Range | Result | Position |
---|---|---|---|---|
find_min_xyz() | [5, 3, 4, 2, 6, 7, 8, 9, 1, 10] | 0, 10 | 1 | 8 |
find_min_xyz() | [5, 3, 4, 2, 6, 7, 8, 9, 1, 10] | 0, 7 | 2 | 3 |
find_min_xyz() | [5, 3, 4, 2, 6, 7, 8, 9, 1, 10] | 2, 7 | 2 | 3 |
find_max_xyz() | [1, 22, 3, 4, 5, 10, 7, 8, 9, 49] | 0, 10 | 49 | 9 |
find_max_xyz() | [1, 22, 3, 4, 5, 10, 7, 8, 9, 49] | 0, 7 | 22 | 1 |
find_max_xyz() | [1, 22, 3, 4, 5, 10, 7, 8, 9, 49] | 2, 7 | 10 | 5 |
Verification
6.3.12 Solution 12: Array Split (★★★✩✩)
Say you have an array (or list) of arbitrary integers. The data structure must be reordered so that all values less than a special reference value are placed on the left. All values greater than or equal to the reference value are placed on the right. The ordering within the subranges is not relevant and may vary.
Examples
Input | Reference element | Sample result |
---|---|---|
[4, 7, 1, 20] | 9 | [1, 4, 7, 9, 20] |
[3, 5, 2] | 7 | [2, 3, 5, 7] |
[2, 14, 10, 1, 11, 12, 3, 4] | 7 | [2, 1, 3, 4, 7, 14, 10, 11, 12] |
[3, 5, 7, 1, 11, 13, 17, 19] | 11 | [1, 3, 5, 7, 11, 11, 13, 17, 19] |
Solution 12a: Array Split (★★✩✩✩)
Write function array_split(values, reference_element) to implement the functionality described above. In this first part of the exercise, it is allowed to create new data structures, such as lists.
Solution 12b: Array Split Inplace (★★★✩✩)
Write function array_split_inplace(values, reference_element) that implements the functionality described inside the source array (i.e., inplace). It is explicitly not desirable to create new data structures. To be able to include the reference element in the result, the creation of an array is allowed once for the result. Because this has to be returned, it is permitted to return a value for an inplace function; indeed, it operates only partially inplace here.
Solution 12c: Array Split Quick Sort Partition (★★★✩✩)
For sorting according to Quick Sort, you need a partitioning functionality similar to the one just developed. However, often the foremost element of the array is used as the reference element.
Based on the two previously developed implementations that use an explicit reference element, your task is to create corresponding alternatives such as the functions array_split_qs(values) and array_split_qs_inplace(values).
Examples
Input | Reference element | Sample result |
---|---|---|
[9, 4, 7, 1, 20] | 9 | [1, 4, 7, 9, 20] |
[7, 3, 5, 2] | 7 | [2, 3, 5, 7] |
[7, 2, 14, 10, 1, 11, 12, 3, 4] | 7 | [2, 1, 3, 4, 7, 14, 10, 11, 12] |
[11, 3, 5, 7, 1, 11, 13, 17, 19] | 11 | [1, 3, 5, 7, 11, 11, 13, 17, 19] |
Please remember that this function works inplace (meaning it operates directly on the passed data) and therefore does not return a result.
Verification
Due to the slightly different algorithm, the elements in the first variant remain in the order in which they appear in the original array. The inplace variants swap elements, and thus there is a reshuffle. However, all smaller values are still found to the left of the reference element and all larger ones to the right.
6.3.13 Solution 13: Minesweeper Board (★★★✩✩)
Chances are high that you’ve played Minesweeper in the past. To remind you, it’s a nice little quiz game with a bit of puzzling. What is it about? Bombs are placed face down on a playfield. The player can choose any field on the board. If a field is uncovered, it shows a number. This indicates how many bombs are hidden in the neighboring fields. However, if you are unlucky, you hit a bomb field and lose the game. Your task is about initializing such a field and preparing it for a subsequent game.
Solution 13a (★★✩✩✩)
Write function place_bombs_randomly(width, height, probability) that creates a playfield specified in size via the first two parameters, randomly filled with bombs, respecting the probability from 0.0 to 1.0 passed in.
Example
For many two-dimensional algorithms, it is necessary to perform special checks at the borders. In some cases, it is helpful to place a special artificial border of one position around the actual playfield. In particular, this often simplifies calculations with neighboring cells in all compass directions, as is the case here with the bombs. But you have to assign a neutral value to the border cells. Here this is simply the value 0. Sometimes, however, special characters like # can be used with str-based playfields.
Some calculations become easier with this artificial boundary cell. However, you must then note that the bounds range from 1 to len() - 1—an additional stumbling block to the treacherous off-by-one errors commonly made with arrays.
Verification
Let’s omit explicit testing here because, on the one hand, you are dealing with random numbers, and a unit test does not directly make sense for this. On the other hand, the algorithm is quite simple and the functionality is tested indirectly later.
Solution 13b (★★★✩✩)
Write function calc_bomb_count(bombs) that computes the number of adjacent fields based on the bomb fields passed and returns a corresponding array.
Examples
Verification
Let’s look again at the helper functions. First, you have a textual representation of the distribution of bombs, which is converted into the required array data structure using to_bool_array(). In doing so, you don’t have to worry about generating the boundary fields. The helper function to_int_array() goes one step further and converts the textual digits into the corresponding int values and takes into account the representation of bombs as B specifically.
These two helper functions enable the creation of test cases to be kept simple and understandable. This makes it more likely that someone will extend the tests. If writing unit tests is rather tedious or even difficult, hardly anyone will bother to extend them.
Solution 13c (★★✩✩✩)
Write function print_board(bombs, bomb_symbol, bomb_counts) that allows you to display a board as points and stars as well as numbers and B.
Example
Verification
Summary: What You Learned
Just like strings, arrays are basic building blocks in many programming languages. In Python, lists are often favored, since arrays are not nicely supported in the language. However, there is a valid alternative with NumPy, with which arrays can be easily defined and which can offer significant performance improvements compared to lists. Anyway, it is important to avoid tricky off-by-one errors. In this chapter, you created small helper functions that, when used appropriately, can make algorithms more understandable. For two-dimensional arrays or nested lists, you learned, among other things, how to model directions and how this helps fill areas with patterns. More challenging tasks were the spiral traversal as well as the deletion and filling of a Jewels or Minesweeper playfield.
This chapter concludes the treatment of essential Python language tools and data structures. Now you turn to more complex topics and start with advanced techniques for recursion.