-
Notifications
You must be signed in to change notification settings - Fork 163
/
Copy path04_MaxCounters.py
167 lines (129 loc) · 5.51 KB
/
04_MaxCounters.py
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
"""
# MaxCounters
Calculate the values of counters after applying all alternating operations:
increase counter by 1; set value of all counters to current maximum.
You are given N counters, initially set to 0, and you have two possible
operations on them:
* increase(X) - counter X is increased by 1,
* max counter - all counters are set to the maximum value of any counter.
A non-empty array A of M integers is given.
This array represents consecutive operations:
* if A[K] = X, such that 1 <= X <= N, then operation K is increase(X),
* if A[K] = N + 1 then operation K is max counter.
For example, given integer N = 5 and array A such that:
A[0] = 3
A[1] = 4
A[2] = 4
A[3] = 6
A[4] = 1
A[5] = 4
A[6] = 4
the values of the counters after each consecutive operation will be:
(0, 0, 1, 0, 0)
(0, 0, 1, 1, 0)
(0, 0, 1, 2, 0)
(2, 2, 2, 2, 2)
(3, 2, 2, 2, 2)
(3, 2, 2, 3, 2)
(3, 2, 2, 4, 2)
The goal is to calculate the value of every counter after all operations.
Write a function:
def solution(N, A)
that, given an integer N and a non-empty array A consisting of M integers,
returns a sequence of integers representing the values of the counters.
Result array should be returned as an array of integers.
For example, given:
A[0] = 3
A[1] = 4
A[2] = 4
A[3] = 6
A[4] = 1
A[5] = 4
A[6] = 4
the function should return [3, 2, 2, 4, 2], as explained above.
Write an efficient algorithm for the following assumptions:
N and M are integers within the range [1..100,000];
each element of array A is an integer within the range [1..N + 1].
Copyright 2009–2022 by Codility Limited. All Rights Reserved.
Unauthorized copying, publication or disclosure prohibited.
---
# My Notes
The straight-forward solution, of updating all the counters every time
there is a 'max counter' instruction, only gets you a 60% score. If every
instruction is a max-counter it will pass over the whole counter array M
times! O(n*m)
For 100%, optimize the max-counter operation by, to use a tidal analogy,
tracking the "low" and "high" "water" marks across all the counters.
With each counter update, check the low-water mark and, if necessary,
raise this counter to it. Now increment. And, before you leave, if necessary,
reset the high water mark. This way, you maintain context of what is happening
in all the counters, without needing to visit them repeatedly.
Before sending back the results, a final pass over the counters is necessary to
ensure the low-water mark applies to any counters which have not been in focus
since it last changed.
Thus the solution requires just two passes over the whole array of counters. O(n+m)
"""
import unittest
import random
def solution(N, A):
"""
:param N: [int] The number of counters.
:param A: list[int] A sequence of instructions.
:return: list[int] The list of final counter values.
Create the list of counters and two marker values for max high and min low.
For every operation
if a "raise counter" operation, then
first, raise that counter to the "low water" mark
next, increment the counter
finally, update the "high water", if necessary
if a "max counter" operation then
raise the "low water" mark to the "high water" mark
For every counter
raise any counter below the "low water" mark to the "low water" mark.
"""
counters = [0] * (N + 1)
high_water = low_water = 0
for index in A:
if index > N: # 'Max-Counter' operation
low_water = high_water
else: # 'Increase-Counter' operation
counters[index] = max(counters[index], low_water)
counters[index] += 1
high_water = max(counters[index], high_water)
for index, value in enumerate(counters):
counters[index] = max(low_water, value)
return counters[1:] # Trim away the extraneous counter for zero values.
class TestExercise(unittest.TestCase):
"""
example: given
single: only one counter
small_random1: small random test 6 max_counter operations
small_random2: small random test, 10 max_counter operations
medium_random1: medium random test, 50 max_counter operations
medium_random2: medium random test, 500 max_counter operations
large_random1: large random test, 2120 max_counter operations
large_random2: large random test, 10000 max_counter operations
extreme_small: all max_counter operations
extreme_large: all max_counter operations
"""
def test_example(self):
self.assertEqual(solution(5, [3, 4, 4, 6, 1, 4, 4]), [3, 2, 2, 4, 2])
def test_singles(self):
self.assertEqual(solution(1, [1]), [1])
self.assertEqual(solution(1, [1, 2]), [1])
self.assertNotEqual(solution(1, [1, 2, 2]), [2])
self.assertEqual(solution(1, [1, 1, 2, 1, 1]), [4])
def test_doubles(self):
self.assertEqual(solution(2, [1, 1, 1, 2]), [3, 1])
self.assertEqual(solution(2, [1, 1, 1, 3]), [3, 3])
self.assertEqual(solution(2, [3, 1, 1, 1]), [3, 0])
self.assertEqual(solution(2, [3, 1, 1, 2]), [2, 1])
self.assertEqual(solution(2, [1, 2, 3, 1, 2]), [2, 2])
self.assertEqual(solution(2, [1, 1, 3, 1, 1]), [4, 2])
self.assertEqual(solution(2, [1, 1, 3, 2, 2]), [2, 4])
def test_extreme(self):
# Ten thousand counters, 90 thousand operations.
arr = [random.randint(1, 9999) for _ in range(90000)]
print(solution(9999, arr))
if __name__ == "__main__":
unittest.main()