You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This exercise tests iteration, logic and error handling.
4
+
The Flower Field exercise is designed to practice iteration, boolean logic and raising errors with error messages.
5
+
It also provides ample opportunities for working with `lists`, `list-indexing`, `comprehensions`, `tuples`, and `generator-expressions`.
5
6
6
-
## General considerations
7
7
8
-
It is possible to break the exercise down into a series of sub-tasks, with plenty of scope to mix and match approaches within these.
8
+
## General considerations and guidance for the exercise
9
+
10
+
It is possible (_and potentially easier_) to break the problem down into a series of sub-tasks, with plenty of scope to mix and match strategies within these sections:
9
11
10
12
- Is the board valid?
11
13
- Is the current square a flower?
12
14
- What are the valid neighboring squares, and how many of them contain flowers?
13
15
14
-
Core Python does not support matrices, nor N-dimensional arrays more generally, though these are at the heart of many third-party packages such as NumPy.
16
+
Core Python does not support matrices nor N-dimensional arrays, though these are at the heart of many third-party packages such as NumPy.
17
+
Due to this limitation, the input board and final result for this exercise are implemented in the tests as a `list` of strings; one string per "row" of the board.
18
+
19
+
20
+
Intermediate processing for the problem is likely to use lists of lists with a final `''.join()` for each "row" in the returned single `list`, although other strategies could be employed.
21
+
Helpfully, Python considers both [lists][ordered-sequences] and [strings][text-sequences] as [sequence types][common-sequence-operations], and can iterate over/index into both in the same fashion.
15
22
16
-
Thus, the input board and the final result are implemented as lists of strings.
17
-
Intermediate processing is likely to use lists of lists, plus a final `''.join()` for each row in the `return` statement.
18
23
19
-
Helpfully, Python can iterate over strings exactly like lists.
24
+
## Validating boards
20
25
21
-
## Valid boards
26
+
The "board" or "field" must be rectangular: essentially, all rows must be the same length as the first row.
27
+
This means that any board can be invalidated using the built-ins `all()` or `any()` to check for equal lengths of the strings in the `list` (_see an example below_).
22
28
23
-
The board must be rectangular: essentially, all rows must be the same length as the first row.
29
+
Perhaps surprisingly, both row and column lengths **can be zero/empty**, so an apparently "non-existent board or field" is considered valid and needs special handling:
24
30
25
-
Perhaps surprisingly, the row and column lengths can be zero, so an apparently non-existent board is valid and needs special handling.
26
31
27
32
```python
28
33
rows =len(garden)
29
34
if rows >0:
30
35
cols =len(garden[0])
31
36
else:
32
37
return []
38
+
33
39
ifany([len(row) != cols for row in garden]):
34
40
raiseValueError('The board is invalid with current input.')
35
41
```
36
42
37
-
Additionally, the only valid entries are a space `' '` or an asterisk `'*'`. All other characters should raise an error.
43
+
Additionally, the only valid entries for the board/field are a space `' '` (_position empty_) or an asterisk `'*'` (_flower in position_).
44
+
All other characters are _invalid_ and should `raise` an error with an appropriate error message.
45
+
The exercise [tests][flower-field-tests] check for specific error messages including punctuation, so should be read or copied carefully.
46
+
47
+
Some solutions use regular expressions for these checks, but there are simpler (_and more performant_) options:
38
48
39
-
Some solutions use regular expressions for this test, but there are simpler options:
40
49
41
50
```python
42
51
if garden[row][col] notin ('', '*'):
43
52
# raise error
44
53
```
45
54
46
-
Depending on how the code is structured, it may be possible to combine the tests.
47
-
48
-
More commonly, the board dimensions are checked at the beginning.
49
-
Invalid characters are then detected while iterating through the board.
50
-
51
-
## Processing squares
55
+
Depending on how the code is structured, it may be possible to combine the checks for row length with the checks for valid characters.
56
+
More commonly, board/field dimensions are checked at the beginning.
57
+
Invalid characters are then detected while iterating through the rows of the board/field.
52
58
53
-
Squares containing a flower are easy: just copy `'*'` to the corresponding square in the result.
54
59
55
-
For empty squares, the challenge is to count how many flowers are in the adjacent squares.
60
+
## Processing squares and finding occupied neighbors
56
61
57
-
*How many squares are adjacent?* In the middle of a reasonably large board there will be 8, but this is reduced for squares at the edges or corners.
62
+
Squares containing a flower are straightforward: you can copy `'*'` to the corresponding square in the results `list`.
58
63
59
-
### 1. Nested `if..elif` statements
64
+
Empty squares present a challenge: count how many flowers are in all the squares _adjacent_ to it.
65
+
But *How many squares are adjacent to the current position?*
66
+
In the middle of a reasonably large board there will be 8 adjacent squares, but this is reduced for squares at edges or corners.
60
67
61
-
This can be made to work, but quickly becomes very verbose.
62
68
63
-
### 2. Explicit coordinates
64
-
65
-
```python
66
-
defcount_adjacent(r, c):
67
-
adj_squares = (
68
-
(r-1, c-1), (r-1, c), (r-1, c+1),
69
-
(r, c-1), (r, c+1),
70
-
(r+1, c-1), (r+1, c), (r+1, c+1),
71
-
)
72
-
73
-
# which are on the board?
74
-
neighbors = [garden[r][c] for r, c in adj_squares
75
-
if0<= r < rows and0<= c < cols]
76
-
# how many contain flowers?
77
-
returnlen([adj for adj in neighbors if adj =='*'])
78
-
```
79
-
80
-
This lists all the possibilities, then filters out any squares that fall outside the board.
69
+
### Some square processing methods
81
70
82
71
Note that we only want a _count_ of nearby flowers.
83
72
Their precise _location_ is irrelevant.
84
73
85
-
### 3. Use a comprehension or generator
86
74
87
-
A key insight is that we can work on a 3x3 block of cells, because we already ensured that the central cell does *not* contain a flower that would affect our count.
88
-
89
-
```python
90
-
squares = ((row + row_diff, col + col_diff)
91
-
for row_diff in (-1, 0, 1)
92
-
for col_diff in (-1, 0, 1))
93
-
```
94
-
95
-
We can then filter and count as in the previous code.
96
-
97
-
### 4. Use complex numbers
98
-
99
-
A particularly elegant solution is to treat the board as a portion of the complex plane.
100
-
101
-
In Python, [complex numbers][complex-numbers] are a standard numeric type, alongside integers and floats.
102
-
103
-
*This is less widely known than it deserves to be.*
The constructor for a complex number is `complex(x, y)` or (as here) `x + y * 1j`, where `x` and `y` are the real and imaginary parts, respectively.
115
-
116
-
There are two properties of complex numbers that help us in this case:
117
-
118
-
- The real and imaginary parts act independently under addition.
119
-
- The value `complex(0, 0)` is the complex zero, which like integer zero is treated as False in Python conditionals.
120
-
121
-
A tuple of integers would not work as a substitute, because `+` behaves as the concatenation operator for tuples:
122
-
123
-
```python
124
-
>>>complex(1, 2) +complex(3, 4)
125
-
(4+6j)
126
-
>>> (1, 2) + (3, 4)
127
-
(1, 2, 3, 4)
128
-
```
129
-
130
-
Note also the use of the ["walrus" operator][walrus-operator]`:=` in the definition of `offset` above.
131
-
132
-
This relatively recent addition to Python simplifies variable assignment within the limited scope of an if statement or a comprehension.
133
-
134
-
## Putting it all together
135
-
136
-
The example below is an object-oriented approach using complex numbers, included because it is a particularly clear illustration of the various topics discussed above.
75
+
1. Nested `if..elif` statements
76
+
77
+
This can be made to work, but can quickly become very verbose or confusing if not thought out carefully:
raiseValueError("The board is invalid with current input.")
89
+
flowerfield[index_i] = temp_row
90
+
```
91
+
92
+
2. Explicit coordinates
93
+
94
+
List all the possibilities then filter out any squares that fall outside the board:
95
+
96
+
```python
97
+
defcount_adjacent(row, col):
98
+
adj_squares = (
99
+
(row-1, col-1), (row-1, col), (row-1, col+1),
100
+
(row, col-1), (row, col+1),
101
+
(row+1, col-1), (row+1, col), (row+1, col+1),
102
+
)
103
+
104
+
# which are on the board?
105
+
neighbors = [garden[row][col] for row, col in adj_squares
106
+
if0<= row < rows and0<= col < cols]
107
+
# how many contain flowers?
108
+
returnlen([adj for adj in neighbors if adj =='*'])
109
+
```
110
+
111
+
3. Using a comprehension or generator expression
112
+
113
+
```python
114
+
# Using a list comprehension
115
+
squares = [(row + row_diff, col + col_diff)
116
+
for row_diff in (-1, 0, 1)
117
+
for col_diff in (-1, 0, 1)]
118
+
119
+
# Using a generator expression
120
+
squares = ((row + row_diff, col + col_diff)
121
+
for row_diff in (-1, 0, 1)
122
+
for col_diff in (-1, 0, 1))
123
+
```
124
+
125
+
A key insight here is that we can work on a 3x3 block of cells: we already ensured that the central cell does *not* contain a flower that would affect our count.
126
+
We can then filterand count asin the `count_adjacent` function in the previous code.
127
+
128
+
4. Using complex numbers
129
+
130
+
```python
131
+
defneighbors(cell):
132
+
"""Yield all eight neighboring cells."""
133
+
for x in (-1, 0, 1):
134
+
for y in (-1, 0, 1):
135
+
if offset := x + y *1j:
136
+
yield cell + offset
137
+
```
138
+
139
+
A particularly elegant solution is to treat the board/field as a portion of the complex plane.
140
+
In Python, [complex numbers][complex-numbers] are a standard numeric type, alongside integers and floats.
141
+
*This is less widely known than it deserves to be.*
142
+
143
+
The constructor for a complex number is`complex(x, y)`or (as here) `x + y *1j`, where `x`and`y` are the real and imaginary parts, respectively.
144
+
145
+
There are two properties of complex numbers that help us in this case:
146
+
- The real and imaginary parts act independently under addition.
147
+
- The value `complex(0, 0)`is the complex zero, which like integer zero is treated asFalsein Python conditionals.
148
+
149
+
A tuple of integers would not work as a substitute, because `+` behaves as the concatenation operator for tuples:
150
+
151
+
```python
152
+
>>>complex(1, 2) +complex(3, 4)
153
+
(4+6j)
154
+
>>> (1, 2) + (3, 4)
155
+
(1, 2, 3, 4)
156
+
```
157
+
158
+
Note also the use of the ["walrus" operator][walrus-operator] `:=`in the definition of `offset` above.
159
+
This relatively recent addition to Python simplifies variable assignment within the limited scope of an if statement or a comprehension.
160
+
161
+
162
+
## Ways of putting it all together
163
+
164
+
The example below takes an object-oriented approach using complex numbers, included because it is a particularly clear illustration of the various topics discussed above.
137
165
138
166
All validation checks are done in the object constructor.
139
167
140
168
```python
141
169
"""Flower Field."""
142
170
143
-
# The import is only needed for type annotation, so can be considered optional.
0 commit comments