summaryrefslogtreecommitdiff
path: root/README.html
blob: b5511b25db3383be36a82c56b121b4039693f9e8 (plain)
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
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <meta name="author" content="Lars Wirzenius" />
  <title>OSO work sample—MAX</title>
  <style type="text/css">
      code{white-space: pre-wrap;}
      span.smallcaps{font-variant: small-caps;}
      span.underline{text-decoration: underline;}
      div.column{display: inline-block; vertical-align: top; width: 50%;}
  </style>
  <style type="text/css">
a.sourceLine { display: inline-block; line-height: 1.25; }
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
a.sourceLine:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode { white-space: pre; position: relative; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
code.sourceCode { white-space: pre-wrap; }
a.sourceLine { text-indent: -1em; padding-left: 1em; }
}
pre.numberSource a.sourceLine
  { position: relative; left: -4em; }
pre.numberSource a.sourceLine::before
  { content: attr(title);
    position: relative; left: -1em; text-align: right; vertical-align: baseline;
    border: none; pointer-events: all; display: inline-block;
    -webkit-touch-callout: none; -webkit-user-select: none;
    -khtml-user-select: none; -moz-user-select: none;
    -ms-user-select: none; user-select: none;
    padding: 0 4px; width: 4em;
    color: #aaaaaa;
  }
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa;  padding-left: 4px; }
div.sourceCode
  {  }
@media screen {
a.sourceLine::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
  </style>
</head>
<body>
<header>
<h1 class="title">OSO work sample—MAX</h1>
<p class="author">Lars Wirzenius</p>
<p class="date">2021-05-29 22:26</p>
</header>
<nav id="TOC">
<ul>
<li><a href="#work-sample-for-oso"><span class="toc-section-number">1</span> Work sample for OSO</a><ul>
<li><a href="#re-statement-of-problem"><span class="toc-section-number">1.1</span> Re-statement of problem</a></li>
<li><a href="#client-request-start-computation"><span class="toc-section-number">1.2</span> Client request: start computation</a></li>
<li><a href="#server-response-request-computation"><span class="toc-section-number">1.3</span> Server response: request computation</a></li>
<li><a href="#client-request-computation-result"><span class="toc-section-number">1.4</span> Client request: computation result</a></li>
<li><a href="#server-response-result"><span class="toc-section-number">1.5</span> Server response: result</a></li>
</ul></li>
<li><a href="#example"><span class="toc-section-number">2</span> Example</a></li>
<li><a href="#assumptions"><span class="toc-section-number">3</span> Assumptions</a></li>
<li><a href="#remarks"><span class="toc-section-number">4</span> Remarks</a></li>
<li><a href="#acceptnace"><span class="toc-section-number">5</span> Acceptance criteria</a><ul>
<li><a href="#find-max-of-a-list-of-one"><span class="toc-section-number">5.1</span> Find max of a list of one</a></li>
<li><a href="#find-max-of-a-list-of-two"><span class="toc-section-number">5.2</span> Find max of a list of two</a></li>
<li><a href="#find-max-of-a-list-of-three"><span class="toc-section-number">5.3</span> Find max of a list of three</a></li>
</ul></li>
<li><a href="#what-i-did"><span class="toc-section-number">6</span> What I did</a><ul>
<li><a href="#the-client"><span class="toc-section-number">6.1</span> The client</a></li>
<li><a href="#the-server"><span class="toc-section-number">6.2</span> The server</a></li>
<li><a href="#the-testing"><span class="toc-section-number">6.3</span> The testing</a></li>
</ul></li>
</ul>
</nav>
<h1 id="work-sample-for-oso"><span class="header-section-number">1</span> Work sample for OSO</h1>
<p>This is the work sample for my job application for a developer position for OSO.</p>
<p>This document explains the work I’ve done and verifies that the code I wrote works together with the client.</p>
<h2 id="re-statement-of-problem"><span class="header-section-number">1.1</span> Re-statement of problem</h2>
<p>To clarify the problem for myself, I am re-stating it. This will also work to make sure I’ve understood it in the intended way when we discuss this.</p>
<p>The goal is to write a server, which communicates with a client using messages over HTTP. The client has a list of integers, and the asks the server to figure out what is the largest integer in the list. The crux is that the client does not send the server the whole list, but only small messages and the server needs to, effectively, ask the client to do pairwise comparisons of integers.</p>
<p>The possible messages sent by the client and the server are listed below, as examples. Communication is started by client by a “start computation” message. Server responds with a suitable message, which causes the client to make a new HTTP request with the response to the server’s message. This continues until the server sends a message with result it has come up with.</p>
<h2 id="client-request-start-computation"><span class="header-section-number">1.2</span> Client request: start computation</h2>
<p>Client starts the protocol by asking the server to compute something about a list of numbers the client holds. It tells the server what computation is requested, and how long the list is.</p>
<p>The supported computations are, for now, <code>compute_max</code> (find index of the largest integer) and <code>compute_min</code> (similar, but smallest integer).</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode json"><code class="sourceCode json"><a class="sourceLine" id="cb1-1" title="1"><span class="fu">{</span></a>
<a class="sourceLine" id="cb1-2" title="2">    <span class="dt">&quot;type&quot;</span><span class="fu">:</span> <span class="st">&quot;compute_max&quot;</span><span class="fu">,</span></a>
<a class="sourceLine" id="cb1-3" title="3">    <span class="dt">&quot;length&quot;</span><span class="fu">:</span> <span class="dv">2</span></a>
<a class="sourceLine" id="cb1-4" title="4"><span class="fu">}</span></a></code></pre></div>
<h2 id="server-response-request-computation"><span class="header-section-number">1.3</span> Server response: request computation</h2>
<p>Server ask the client to report the result of a less-than comparison operation between arbitrary list items.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode json"><code class="sourceCode json"><a class="sourceLine" id="cb2-1" title="1"><span class="fu">{</span></a>
<a class="sourceLine" id="cb2-2" title="2">    <span class="dt">&quot;type&quot;</span><span class="fu">:</span> <span class="st">&quot;compare&quot;</span><span class="fu">,</span></a>
<a class="sourceLine" id="cb2-3" title="3">    <span class="dt">&quot;left&quot;</span><span class="fu">:</span> <span class="dv">0</span><span class="fu">,</span></a>
<a class="sourceLine" id="cb2-4" title="4">    <span class="dt">&quot;right&quot;</span><span class="fu">:</span> <span class="dv">1</span><span class="fu">,</span></a>
<a class="sourceLine" id="cb2-5" title="5">    <span class="dt">&quot;request_id&quot;</span><span class="fu">:</span> <span class="dv">7</span></a>
<a class="sourceLine" id="cb2-6" title="6"><span class="fu">}</span></a></code></pre></div>
<h2 id="client-request-computation-result"><span class="header-section-number">1.4</span> Client request: computation result</h2>
<p>The client reports the result of a comparison. The server should respond by another comparison request, or the result of the computation.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode json"><code class="sourceCode json"><a class="sourceLine" id="cb3-1" title="1"><span class="fu">{</span></a>
<a class="sourceLine" id="cb3-2" title="2">    <span class="dt">&quot;type&quot;</span><span class="fu">:</span> <span class="st">&quot;comp_result&quot;</span><span class="fu">,</span></a>
<a class="sourceLine" id="cb3-3" title="3">    <span class="dt">&quot;answer&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></a>
<a class="sourceLine" id="cb3-4" title="4">    <span class="dt">&quot;request_id&quot;</span><span class="fu">:</span> <span class="dv">7</span></a>
<a class="sourceLine" id="cb3-5" title="5"><span class="fu">}</span></a></code></pre></div>
<h2 id="server-response-result"><span class="header-section-number">1.5</span> Server response: result</h2>
<div class="sourceCode" id="cb4"><pre class="sourceCode json"><code class="sourceCode json"><a class="sourceLine" id="cb4-1" title="1"><span class="fu">{</span></a>
<a class="sourceLine" id="cb4-2" title="2">    <span class="dt">&quot;type&quot;</span><span class="fu">:</span> <span class="st">&quot;done&quot;</span><span class="fu">,</span></a>
<a class="sourceLine" id="cb4-3" title="3">    <span class="dt">&quot;result&quot;</span><span class="fu">:</span> <span class="dv">2</span></a>
<a class="sourceLine" id="cb4-4" title="4"><span class="fu">}</span></a></code></pre></div>
<h1 id="example"><span class="header-section-number">2</span> Example</h1>
<p>In this example, assume the client has the following list:</p>
<blockquote>
<p>5, 6, 7, 5</p>
</blockquote>
<p>Note that the list is not in order and numbers aren’t unique. The sequence diagram below shows the messages the go between the client and the server.</p>
<p>For simplicity, request ids are not shown.</p>
<p><img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U2NyaXB0VHlwZT0iYXBwbGljYXRpb24vZWNtYXNjcmlwdCIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiIGhlaWdodD0iNTkzcHgiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiIHN0eWxlPSJ3aWR0aDo2MzBweDtoZWlnaHQ6NTkzcHg7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA2MzAgNTkzIiB3aWR0aD0iNjMwcHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzPjxmaWx0ZXIgaGVpZ2h0PSIzMDAlIiBpZD0iZml6ZmF5ZXk4c2EyaSIgd2lkdGg9IjMwMCUiIHg9Ii0xIiB5PSItMSI+PGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgc3RkRGV2aWF0aW9uPSIyLjAiLz48ZmVDb2xvck1hdHJpeCBpbj0iYmx1ck91dCIgcmVzdWx0PSJibHVyT3V0MiIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIC40IDAiLz48ZmVPZmZzZXQgZHg9IjQuMCIgZHk9IjQuMCIgaW49ImJsdXJPdXQyIiByZXN1bHQ9ImJsdXJPdXQzIi8+PGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iYmx1ck91dDMiIG1vZGU9Im5vcm1hbCIvPjwvZmlsdGVyPjwvZGVmcz48Zz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogNS4wLDUuMDsiIHgxPSIyMDQiIHgyPSIyMDQiIHkxPSIzOC4yOTY5IiB5Mj0iNTUyLjk1MzEiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogNS4wLDUuMDsiIHgxPSI0MDEiIHgyPSI0MDEiIHkxPSIzOC4yOTY5IiB5Mj0iNTUyLjk1MzEiLz48cmVjdCBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZml6ZmF5ZXk4c2EyaSkiIGhlaWdodD0iMzAuMjk2OSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjU7IiB3aWR0aD0iNTEiIHg9IjE3NyIgeT0iMyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjM3IiB4PSIxODQiIHk9IjIyLjk5NTEiPmNsaWVudDwvdGV4dD48cmVjdCBmaWxsPSIjRkVGRUNFIiBmaWx0ZXI9InVybCgjZml6ZmF5ZXk4c2EyaSkiIGhlaWdodD0iMzAuMjk2OSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjU7IiB3aWR0aD0iNTEiIHg9IjE3NyIgeT0iNTUxLjk1MzEiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIzNyIgeD0iMTg0IiB5PSI1NzEuOTQ4MiI+Y2xpZW50PC90ZXh0PjxyZWN0IGZpbGw9IiNGRUZFQ0UiIGZpbHRlcj0idXJsKCNmaXpmYXlleThzYTJpKSIgaGVpZ2h0PSIzMC4yOTY5IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuNTsiIHdpZHRoPSI1NyIgeD0iMzcxIiB5PSIzIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNDMiIHg9IjM3OCIgeT0iMjIuOTk1MSI+c2VydmVyPC90ZXh0PjxyZWN0IGZpbGw9IiNGRUZFQ0UiIGZpbHRlcj0idXJsKCNmaXpmYXlleThzYTJpKSIgaGVpZ2h0PSIzMC4yOTY5IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuNTsiIHdpZHRoPSI1NyIgeD0iMzcxIiB5PSI1NTEuOTUzMSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjQzIiB4PSIzNzgiIHk9IjU3MS45NDgyIj5zZXJ2ZXI8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIzODkuNSw5My4xMjg5LDM5OS41LDk3LjEyODksMzg5LjUsMTAxLjEyODksMzkzLjUsOTcuMTI4OSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMjA0LjUiIHgyPSIzOTUuNSIgeTE9Ijk3LjEyODkiIHkyPSI5Ny4xMjg5Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTU5IiB4PSIyMTEuNSIgeT0iOTIuMDYzIj5jb21wdXRlX21heCwgbGVuZ3RoPTQ8L3RleHQ+PHBhdGggZD0iTTgsNTMuMjk2OSBMOCwxMjMuMjk2OSBMMTk2LDEyMy4yOTY5IEwxOTYsNjMuMjk2OSBMMTg2LDUzLjI5NjkgTDgsNTMuMjk2OSAiIGZpbGw9IiNGQkZCNzciIGZpbHRlcj0idXJsKCNmaXpmYXlleThzYTJpKSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHBhdGggZD0iTTE4Niw1My4yOTY5IEwxODYsNjMuMjk2OSBMMTk2LDYzLjI5NjkgTDE4Niw1My4yOTY5ICIgZmlsbD0iI0ZCRkI3NyIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTQ2IiB4PSIxNCIgeT0iNzAuMzYzOCI+Y2xpZW50IHRlbGxzIHNlcnZlciB0aGVyZTwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxNjciIHg9IjE0IiB5PSI4NS40OTY2Ij5hcmUgNCBpbnRlZ2VzIGFuZCB0aGF0IHRoZTwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxNTEiIHg9IjE0IiB5PSIxMDAuNjI5NCI+c2VydmVyIHNob3VsZCBmaWd1cmUgb3V0PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijk3IiB4PSIxNCIgeT0iMTE1Ljc2MjIiPnRoZSBsYXJnZXN0IG9uZTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjIxNS41LDE3MC4wOTM4LDIwNS41LDE3NC4wOTM4LDIxNS41LDE3OC4wOTM4LDIxMS41LDE3NC4wOTM4IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIyMDkuNSIgeDI9IjQwMC41IiB5MT0iMTc0LjA5MzgiIHkyPSIxNzQuMDkzOCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijg3IiB4PSIyMjEuNSIgeT0iMTY5LjAyNzgiPmNvbXBhcmUoMCwxKTwvdGV4dD48cGF0aCBkPSJNNDA2LDEzNy44MjgxIEw0MDYsMTkyLjgyODEgTDU2MywxOTIuODI4MSBMNTYzLDE0Ny44MjgxIEw1NTMsMTM3LjgyODEgTDQwNiwxMzcuODI4MSAiIGZpbGw9IiNGQkZCNzciIGZpbHRlcj0idXJsKCNmaXpmYXlleThzYTJpKSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHBhdGggZD0iTTU1MywxMzcuODI4MSBMNTUzLDE0Ny44MjgxIEw1NjMsMTQ3LjgyODEgTDU1MywxMzcuODI4MSAiIGZpbGw9IiNGQkZCNzciIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjEzNiIgeD0iNDEyIiB5PSIxNTQuODk1Ij5zZXJ2ZXIgd2FudHMgY2xpZW50IHRvPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjEzMCIgeD0iNDEyIiB5PSIxNzAuMDI3OCI+Y29tcGFyZSBpbnRlZ2VycyBhdDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMDEiIHg9IjQxMiIgeT0iMTg1LjE2MDYiPmluZGV4ZXMgMCBhbmQgMTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjM4OS41LDI0Ny4wNTg2LDM5OS41LDI1MS4wNTg2LDM4OS41LDI1NS4wNTg2LDM5My41LDI1MS4wNTg2IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIyMDQuNSIgeDI9IjM5NS41IiB5MT0iMjUxLjA1ODYiIHkyPSIyNTEuMDU4NiIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjE2OSIgeD0iMjExLjUiIHk9IjI0NS45OTI3Ij5jb21wX3Jlc3VsdCwgYW5zd2VyPXRydWU8L3RleHQ+PHBhdGggZD0iTTM3LDIwNy4yMjY2IEwzNywyNzcuMjI2NiBMMTk1LDI3Ny4yMjY2IEwxOTUsMjE3LjIyNjYgTDE4NSwyMDcuMjI2NiBMMzcsMjA3LjIyNjYgIiBmaWxsPSIjRkJGQjc3IiBmaWx0ZXI9InVybCgjZml6ZmF5ZXk4c2EyaSkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxwYXRoIGQ9Ik0xODUsMjA3LjIyNjYgTDE4NSwyMTcuMjI2NiBMMTk1LDIxNy4yMjY2IEwxODUsMjA3LjIyNjYgIiBmaWxsPSIjRkJGQjc3IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMTQiIHg9IjQzIiB5PSIyMjQuMjkzNSI+Y2xpZW50IHJlcG9ydHMgdGhhdDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMjUiIHg9IjQzIiB5PSIyMzkuNDI2MyI+dGhlIGludGVnZXIgYXQgaW5kZXg8L3RleHQ+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTM3IiB4PSI0MyIgeT0iMjU0LjU1OTEiPjAgaXMgbGVzcyB0aGFuIHRoZSBvbmU8L3RleHQ+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNjMiIHg9IjQzIiB5PSIyNjkuNjkxOSI+YXQgaW5kZXggMTwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjIxNS41LDMxNi40NTcsMjA1LjUsMzIwLjQ1NywyMTUuNSwzMjQuNDU3LDIxMS41LDMyMC40NTciIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjIwOS41IiB4Mj0iNDAwLjUiIHkxPSIzMjAuNDU3IiB5Mj0iMzIwLjQ1NyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9Ijg3IiB4PSIyMjEuNSIgeT0iMzE1LjM5MTEiPmNvbXBhcmUoMSwyKTwvdGV4dD48cGF0aCBkPSJNNDA2LDI5MS43NTc4IEw0MDYsMzMxLjc1NzggTDYwOSwzMzEuNzU3OCBMNjA5LDMwMS43NTc4IEw1OTksMjkxLjc1NzggTDQwNiwyOTEuNzU3OCAiIGZpbGw9IiNGQkZCNzciIGZpbHRlcj0idXJsKCNmaXpmYXlleThzYTJpKSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHBhdGggZD0iTTU5OSwyOTEuNzU3OCBMNTk5LDMwMS43NTc4IEw2MDksMzAxLjc1NzggTDU5OSwyOTEuNzU3OCAiIGZpbGw9IiNGQkZCNzciIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjE1MyIgeD0iNDEyIiB5PSIzMDguODI0NyI+bWF4IHNvIGZhciBpcyBhdCBpbmRleCAxOzwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxODIiIHg9IjQxMiIgeT0iMzIzLjk1NzUiPnNlcnZlciBhc2tzIGlzIGxpc3RbMV0gJmx0OyBsaXN0WzJdPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMzg5LjUsMzYzLjE1NjMsMzk5LjUsMzY3LjE1NjMsMzg5LjUsMzcxLjE1NjMsMzkzLjUsMzY3LjE1NjMiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjIwNC41IiB4Mj0iMzk1LjUiIHkxPSIzNjcuMTU2MyIgeTI9IjM2Ny4xNTYzIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTY5IiB4PSIyMTEuNSIgeT0iMzYyLjA5MDMiPmNvbXBfcmVzdWx0LCBhbnN3ZXI9dHJ1ZTwvdGV4dD48cGF0aCBkPSJNNjQsMzQ2LjAyMzQgTDY0LDM3MS4wMjM0IEwxOTUsMzcxLjAyMzQgTDE5NSwzNTYuMDIzNCBMMTg1LDM0Ni4wMjM0IEw2NCwzNDYuMDIzNCAiIGZpbGw9IiNGQkZCNzciIGZpbHRlcj0idXJsKCNmaXpmYXlleThzYTJpKSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PHBhdGggZD0iTTE4NSwzNDYuMDIzNCBMMTg1LDM1Ni4wMjM0IEwxOTUsMzU2LjAyMzQgTDE4NSwzNDYuMDIzNCAiIGZpbGw9IiNGQkZCNzciIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjExMCIgeD0iNzAiIHk9IjM2My4wOTAzIj5jbGllbnQgcmVwb3J0cyB5ZXM8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyMTUuNSw0MDkuODU1NSwyMDUuNSw0MTMuODU1NSwyMTUuNSw0MTcuODU1NSwyMTEuNSw0MTMuODU1NSIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMjA5LjUiIHgyPSI0MDAuNSIgeTE9IjQxMy44NTU1IiB5Mj0iNDEzLjg1NTUiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSI4NyIgeD0iMjIxLjUiIHk9IjQwOC43ODk2Ij5jb21wYXJlKDIsMyk8L3RleHQ+PHBhdGggZD0iTTQwNiwzODUuMTU2MyBMNDA2LDQyNS4xNTYzIEw2MDksNDI1LjE1NjMgTDYwOSwzOTUuMTU2MyBMNTk5LDM4NS4xNTYzIEw0MDYsMzg1LjE1NjMgIiBmaWxsPSIjRkJGQjc3IiBmaWx0ZXI9InVybCgjZml6ZmF5ZXk4c2EyaSkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxwYXRoIGQ9Ik01OTksMzg1LjE1NjMgTDU5OSwzOTUuMTU2MyBMNjA5LDM5NS4xNTYzIEw1OTksMzg1LjE1NjMgIiBmaWxsPSIjRkJGQjc3IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxNTMiIHg9IjQxMiIgeT0iNDAyLjIyMzEiPm1heCBzbyBmYXIgaXMgYXQgaW5kZXggMjs8L3RleHQ+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTgyIiB4PSI0MTIiIHk9IjQxNy4zNTYiPnNlcnZlciBhc2tzIGlzIGxpc3RbMl0gJmx0OyBsaXN0WzNdPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMzg5LjUsNDU2LjU1NDcsMzk5LjUsNDYwLjU1NDcsMzg5LjUsNDY0LjU1NDcsMzkzLjUsNDYwLjU1NDciIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjIwNC41IiB4Mj0iMzk1LjUiIHkxPSI0NjAuNTU0NyIgeTI9IjQ2MC41NTQ3Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTczIiB4PSIyMTEuNSIgeT0iNDU1LjQ4ODgiPmNvbXBfcmVzdWx0LCBhbnN3ZXI9ZmFsc2U8L3RleHQ+PHBhdGggZD0iTTcwLDQzOS40MjE5IEw3MCw0NjQuNDIxOSBMMTk1LDQ2NC40MjE5IEwxOTUsNDQ5LjQyMTkgTDE4NSw0MzkuNDIxOSBMNzAsNDM5LjQyMTkgIiBmaWxsPSIjRkJGQjc3IiBmaWx0ZXI9InVybCgjZml6ZmF5ZXk4c2EyaSkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxwYXRoIGQ9Ik0xODUsNDM5LjQyMTkgTDE4NSw0NDkuNDIxOSBMMTk1LDQ0OS40MjE5IEwxODUsNDM5LjQyMTkgIiBmaWxsPSIjRkJGQjc3IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMDQiIHg9Ijc2IiB5PSI0NTYuNDg4OCI+Y2xpZW50IHJlcG9ydHMgbm88L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyMTUuNSw1MTAuODIwMywyMDUuNSw1MTQuODIwMywyMTUuNSw1MTguODIwMywyMTEuNSw1MTQuODIwMyIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IiB4MT0iMjA5LjUiIHgyPSI0MDAuNSIgeTE9IjUxNC44MjAzIiB5Mj0iNTE0LjgyMDMiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxMDQiIHg9IjIyMS41IiB5PSI1MDkuNzU0NCI+ZG9uZSwgYW5zd2VyPTI8L3RleHQ+PHBhdGggZD0iTTQwNiw0NzguNTU0NyBMNDA2LDUzMy41NTQ3IEw2MTgsNTMzLjU1NDcgTDYxOCw0ODguNTU0NyBMNjA4LDQ3OC41NTQ3IEw0MDYsNDc4LjU1NDcgIiBmaWxsPSIjRkJGQjc3IiBmaWx0ZXI9InVybCgjZml6ZmF5ZXk4c2EyaSkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxwYXRoIGQ9Ik02MDgsNDc4LjU1NDcgTDYwOCw0ODguNTU0NyBMNjE4LDQ4OC41NTQ3IEw2MDgsNDc4LjU1NDcgIiBmaWxsPSIjRkJGQjc3IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxOTEiIHg9IjQxMiIgeT0iNDk1LjYyMTYiPnNlcnZlciBrbm93cyBpdCBoYXMgY29tcGFyZWQ8L3RleHQ+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMTgwIiB4PSI0MTIiIHk9IjUxMC43NTQ0Ij5hbGwgbGlzdCBpdGVtcyBhbmQgdGhhdCBtYXggaXM8L3RleHQ+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iNjMiIHg9IjQxMiIgeT0iNTI1Ljg4NzIiPmF0IGluZGV4IDI8L3RleHQ+PCEtLQpAc3RhcnR1bWwNCg0KcGFydGljaXBhbnQgImNsaWVudCIgYXMgYw0KcGFydGljaXBhbnQgInNlcnZlciIgYXMgcw0KDQpjIC0+IHMgOiBjb21wdXRlX21heCwgbGVuZ3RoPTQNCm5vdGUgbGVmdA0KY2xpZW50IHRlbGxzIHNlcnZlciB0aGVyZSANCmFyZSA0IGludGVnZXMgYW5kIHRoYXQgdGhlDQpzZXJ2ZXIgc2hvdWxkIGZpZ3VyZSBvdXQNCnRoZSBsYXJnZXN0IG9uZQ0KZW5kIG5vdGUNCg0KYyA8LSBzIDogY29tcGFyZSgwLDEpDQpub3RlIHJpZ2h0DQpzZXJ2ZXIgd2FudHMgY2xpZW50IHRvDQpjb21wYXJlIGludGVnZXJzIGF0DQppbmRleGVzIDAgYW5kIDENCmVuZCBub3RlDQoNCmMgLT4gcyA6IGNvbXBfcmVzdWx0LCBhbnN3ZXI9dHJ1ZQ0Kbm90ZSBsZWZ0DQpjbGllbnQgcmVwb3J0cyB0aGF0DQp0aGUgaW50ZWdlciBhdCBpbmRleA0KMCBpcyBsZXNzIHRoYW4gdGhlIG9uZQ0KYXQgaW5kZXggMQ0KZW5kIG5vdGUNCg0KYyA8LSBzIDogY29tcGFyZSgxLDIpDQpub3RlIHJpZ2h0DQptYXggc28gZmFyIGlzIGF0IGluZGV4IDE7DQpzZXJ2ZXIgYXNrcyBpcyBsaXN0WzFdIDwgbGlzdFsyXQ0KZW5kIG5vdGUNCg0KYyAtPiBzIDogY29tcF9yZXN1bHQsIGFuc3dlcj10cnVlDQpub3RlIGxlZnQNCmNsaWVudCByZXBvcnRzIHllcw0KZW5kIG5vdGUNCg0KYyA8LSBzIDogY29tcGFyZSgyLDMpDQpub3RlIHJpZ2h0DQptYXggc28gZmFyIGlzIGF0IGluZGV4IDI7DQpzZXJ2ZXIgYXNrcyBpcyBsaXN0WzJdIDwgbGlzdFszXQ0KZW5kIG5vdGUNCg0KYyAtPiBzIDogY29tcF9yZXN1bHQsIGFuc3dlcj1mYWxzZQ0Kbm90ZSBsZWZ0DQpjbGllbnQgcmVwb3J0cyBubw0KZW5kIG5vdGUNCg0KYyA8LSBzIDogZG9uZSwgYW5zd2VyPTINCm5vdGUgcmlnaHQNCnNlcnZlciBrbm93cyBpdCBoYXMgY29tcGFyZWQNCmFsbCBsaXN0IGl0ZW1zIGFuZCB0aGF0IG1heCBpcw0KYXQgaW5kZXggMg0KZW5kIG5vdGUNCg0KQGVuZHVtbA0KClBsYW50VU1MIHZlcnNpb24gMS4yMDE4LjEzKE1vbiBOb3YgMjYgMTk6MTE6NTEgRUVUIDIwMTgpCihHUEwgc291cmNlIGRpc3RyaWJ1dGlvbikKSmF2YSBSdW50aW1lOiBPcGVuSkRLIFJ1bnRpbWUgRW52aXJvbm1lbnQKSlZNOiBPcGVuSkRLIDY0LUJpdCBTZXJ2ZXIgVk0KSmF2YSBWZXJzaW9uOiAxMS4wLjExKzktcG9zdC1EZWJpYW4tMWRlYjEwdTEKT3BlcmF0aW5nIFN5c3RlbTogTGludXgKT1MgVmVyc2lvbjogNC4xOS4wLTE2LWFtZDY0CkRlZmF1bHQgRW5jb2Rpbmc6IFVURi04Ckxhbmd1YWdlOiBlbgpDb3VudHJ5OiBVUwotLT48L2c+PC9zdmc+" /></p>
<p>This is very simple computation. Given the server can’t assume the list is ordered, it has to compare all list elements to the largest one it has found so far.</p>
<h1 id="assumptions"><span class="header-section-number">3</span> Assumptions</h1>
<ul>
<li><p>The server will abort if the client doesn’t use the request id from the latest server message. That is, the client and server do not need to handle multiple outstanding comparison requests.</p></li>
<li><p>The server does not need to handle the case of the client having an empty list of integers, because there messages as given do no indicate a way to signal a result of “no result”.</p></li>
<li><p>The list in the client doesn’t change during the computation: items stay in the same order, and are not added, deleted, or changed.</p></li>
<li><p>The client responds truthfully.</p></li>
</ul>
<h1 id="remarks"><span class="header-section-number">4</span> Remarks</h1>
<ul>
<li>Given the list the client has is unordered, O(n) comparisons is the best we can do. If the client can make some guarantees, faster algorithms are possible. If list can be ordered, a binary search would be optimal.</li>
</ul>
<h1 id="acceptnace"><span class="header-section-number">5</span> Acceptance criteria</h1>
<h2 id="find-max-of-a-list-of-one"><span class="header-section-number">5.1</span> Find max of a list of one</h2>
<p>This scenario verifies that the server finds the maximum integer in a list of one.</p>
<div class="line-block"><em>given</em> server<br />
<em>when</em> I run <strong>max-client.py</strong><strong> 1</strong><br />
<em>then</em> answer is <strong>1</strong></div>
<h2 id="find-max-of-a-list-of-two"><span class="header-section-number">5.2</span> Find max of a list of two</h2>
<p>These scenarios verify that the server finds the maximum integer in a list of two. There is a separate scenario for every possible list of two elements.</p>
<div class="line-block"><em>given</em> server<br />
<em>when</em> I run <strong>max-client.py</strong><strong> 5 5</strong><br />
<em>then</em> answer is <strong>5</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 5 6</strong><br />
<em>then</em> answer is <strong>6</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 6 5</strong><br />
<em>then</em> answer is <strong>6</strong></div>
<h2 id="find-max-of-a-list-of-three"><span class="header-section-number">5.3</span> Find max of a list of three</h2>
<p>These scenarios verify that the server finds the maximum integer in a list of two. There is a separate scenario for every possible list of two elements.</p>
<div class="line-block"><em>given</em> server<br />
<em>when</em> I run <strong>max-client.py</strong><strong> 5 5 5</strong><br />
<em>then</em> answer is <strong>5</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 5 5 6</strong><br />
<em>then</em> answer is <strong>6</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 5 6 5</strong><br />
<em>then</em> answer is <strong>6</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 6 5 5</strong><br />
<em>then</em> answer is <strong>6</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 5 6 7</strong><br />
<em>then</em> answer is <strong>7</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 5 7 6</strong><br />
<em>then</em> answer is <strong>7</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 6 5 7</strong><br />
<em>then</em> answer is <strong>7</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 6 7 5</strong><br />
<em>then</em> answer is <strong>7</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 7 5 6</strong><br />
<em>then</em> answer is <strong>7</strong><br />
<em>when</em> I run <strong>max-client.py</strong><strong> 7 6 5</strong><br />
<em>then</em> answer is <strong>7</strong></div>
<h1 id="what-i-did"><span class="header-section-number">6</span> What I did</h1>
<h2 id="the-client"><span class="header-section-number">6.1</span> The client</h2>
<p>I modified slightly the <code>max-client.py</code> file:</p>
<ul>
<li>it is now an executable Python script, and formatted with Black</li>
<li>the user can invoke it with a list of numbers, and tell it whether to ask the server to find the min or the max number</li>
</ul>
<p>I made these changes so that I could use it when verifying the acceptance criteria defined in this document. The original code tested only two cases, which I found to be inadequate for my purposes.</p>
<p>The Python code is not entirely to current Python best practices. For example, it doesn’t use type annotations. I have not started using those yet: at work I’ve only used old versions of Python that don’t support type annotations, and in my free time, I don’t write anything of significant size in Python anymore.</p>
<h2 id="the-server"><span class="header-section-number">6.2</span> The server</h2>
<p>The server is in <code>server.py</code>, and is a Python program using <code>bottle.py</code>. I chose Python, because for something small and simple like this it’s easy. I chose <code>bottle.py</code> because it’s familiar for me.</p>
<p>I could have chosen Rust, and probably the <code>warp</code> crate for the HTTP API, but it would have required much more implementation work, and probably more than is warranted for this exercise.</p>
<p>The code is a little simplistic in that it doesn’t do much in terms of error handling, logging, or such. At the same time it’s overly complicated, because I wanted to make sure it allows for more than just the “max” algorithm. “min” is implemented, and the same structure should be usable for, say, finding the second largest element. More interesting algorithms would require changes to the messages: if, for example, one wanted the server to find out if the list is ordered, the “done” message would need to be able to express the result.</p>
<h2 id="the-testing"><span class="header-section-number">6.3</span> The testing</h2>
<p>I have used the <a href="https://subplot.liw.fi/">Subplot</a> program to verify that my server works. Subplot documents the acceptance criteria and how they are verified. That is this document. The section <a href="#acceptance">Acceptance criteria</a> documents the acceptance criteria using <em>scenarios</em> consisting of given/when/then steps.</p>
<p>Subplot produces two typeset documents (one in HTML, one in PDF), and a self-standing test program, which can be run to verify the system under test fulfills the acceptance criteria. To avoid requiring you to have Subplot installed, the test program is included in the git repository as <code>test.py</code>. You can run it like this:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode sh"><code class="sourceCode bash"><a class="sourceLine" id="cb5-1" title="1">$ <span class="ex">python3</span> test.py --log test.log</a>
<a class="sourceLine" id="cb5-2" title="2"><span class="ex">srcdir</span> /home/liw/pers/oso/work-sample</a>
<a class="sourceLine" id="cb5-3" title="3"><span class="ex">datadir</span> /tmp/tmpcom39rm7</a>
<a class="sourceLine" id="cb5-4" title="4"><span class="ex">scenario</span>: Find max of a list of one</a>
<a class="sourceLine" id="cb5-5" title="5">  <span class="ex">step</span>: given server</a>
<a class="sourceLine" id="cb5-6" title="6">  <span class="ex">step</span>: when I run max-client.py 1</a>
<a class="sourceLine" id="cb5-7" title="7">  <span class="ex">step</span>: then answer is 1</a>
<a class="sourceLine" id="cb5-8" title="8">  <span class="ex">cleanup</span>: given server</a>
<a class="sourceLine" id="cb5-9" title="9"><span class="ex">scenario</span>: Find max of a list of two</a>
<a class="sourceLine" id="cb5-10" title="10">  <span class="ex">step</span>: given server</a>
<a class="sourceLine" id="cb5-11" title="11">  <span class="ex">step</span>: when I run max-client.py 5 5</a>
<a class="sourceLine" id="cb5-12" title="12">  <span class="ex">step</span>: then answer is 5</a>
<a class="sourceLine" id="cb5-13" title="13">  <span class="ex">step</span>: when I run max-client.py 5 6</a>
<a class="sourceLine" id="cb5-14" title="14">  <span class="ex">step</span>: then answer is 6</a>
<a class="sourceLine" id="cb5-15" title="15">  <span class="ex">step</span>: when I run max-client.py 6 5</a>
<a class="sourceLine" id="cb5-16" title="16">  <span class="ex">step</span>: then answer is 6</a>
<a class="sourceLine" id="cb5-17" title="17">  <span class="ex">cleanup</span>: given server</a>
<a class="sourceLine" id="cb5-18" title="18"><span class="ex">scenario</span>: Find max of a list of three</a>
<a class="sourceLine" id="cb5-19" title="19">  <span class="ex">step</span>: given server</a>
<a class="sourceLine" id="cb5-20" title="20">  <span class="ex">step</span>: when I run max-client.py 5 5 5</a>
<a class="sourceLine" id="cb5-21" title="21">  <span class="ex">step</span>: then answer is 5</a>
<a class="sourceLine" id="cb5-22" title="22">  <span class="ex">step</span>: when I run max-client.py 5 5 6</a>
<a class="sourceLine" id="cb5-23" title="23">  <span class="ex">step</span>: then answer is 6</a>
<a class="sourceLine" id="cb5-24" title="24">  <span class="ex">step</span>: when I run max-client.py 5 6 5</a>
<a class="sourceLine" id="cb5-25" title="25">  <span class="ex">step</span>: then answer is 6</a>
<a class="sourceLine" id="cb5-26" title="26">  <span class="ex">step</span>: when I run max-client.py 6 5 5</a>
<a class="sourceLine" id="cb5-27" title="27">  <span class="ex">step</span>: then answer is 6</a>
<a class="sourceLine" id="cb5-28" title="28">  <span class="ex">step</span>: when I run max-client.py 5 6 7</a>
<a class="sourceLine" id="cb5-29" title="29">  <span class="ex">step</span>: then answer is 7</a>
<a class="sourceLine" id="cb5-30" title="30">  <span class="ex">step</span>: when I run max-client.py 5 7 6</a>
<a class="sourceLine" id="cb5-31" title="31">  <span class="ex">step</span>: then answer is 7</a>
<a class="sourceLine" id="cb5-32" title="32">  <span class="ex">step</span>: when I run max-client.py 6 5 7</a>
<a class="sourceLine" id="cb5-33" title="33">  <span class="ex">step</span>: then answer is 7</a>
<a class="sourceLine" id="cb5-34" title="34">  <span class="ex">step</span>: when I run max-client.py 6 7 5</a>
<a class="sourceLine" id="cb5-35" title="35">  <span class="ex">step</span>: then answer is 7</a>
<a class="sourceLine" id="cb5-36" title="36">  <span class="ex">step</span>: when I run max-client.py 7 5 6</a>
<a class="sourceLine" id="cb5-37" title="37">  <span class="ex">step</span>: then answer is 7</a>
<a class="sourceLine" id="cb5-38" title="38">  <span class="ex">step</span>: when I run max-client.py 7 6 5</a>
<a class="sourceLine" id="cb5-39" title="39">  <span class="ex">step</span>: then answer is 7</a>
<a class="sourceLine" id="cb5-40" title="40">  <span class="ex">cleanup</span>: given server</a>
<a class="sourceLine" id="cb5-41" title="41"><span class="ex">OK</span>, all scenarios finished successfully</a>
<a class="sourceLine" id="cb5-42" title="42">$</a></code></pre></div>
<p>I hope that is satisfactory.</p>
</body>
</html>