-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
618 lines (380 loc) · 377 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Andimeo's Blog</title>
<subtitle>今夕是何年</subtitle>
<link href="https://andimeo.us.kg/atom.xml" rel="self"/>
<link href="https://andimeo.us.kg/"/>
<updated>2024-11-03T14:08:14.173Z</updated>
<id>https://andimeo.us.kg/</id>
<author>
<name>Andimeo</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>1到n的异或和</title>
<link href="https://andimeo.us.kg/2024/11/03/1%E5%88%B0n%E7%9A%84%E5%BC%82%E6%88%96%E5%92%8C/"/>
<id>https://andimeo.us.kg/2024/11/03/1%E5%88%B0n%E7%9A%84%E5%BC%82%E6%88%96%E5%92%8C/</id>
<published>2024-11-03T12:49:32.000Z</published>
<updated>2024-11-03T14:08:14.173Z</updated>
<content type="html"><![CDATA[<p>最近做codeforces的比赛时,发现一道<a href="https://codeforces.com/contest/2036/problem/F">题目</a>。题意是求区间$[l, r]$内,满足$x \not\equiv k \pmod {2^i}$的数的异或和。</p><p>整体思路是假设我们有函数$f(n)$表示$[0, n]$中所有数的异或和,有函数$g(n)$表示$[0, n]$中所有模$2^i$等于k的数的异或和,那么最终答案即为:</p><p>$$ f(r) \oplus f(l-1) \oplus g(r) \oplus g(l-1) $$</p><h2 id="函数f">函数f</h2><p>函数f的计算其实非常简单,</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">(<span class="type">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (n % <span class="number">4</span> == <span class="number">0</span>) <span class="keyword">return</span> n;</span><br><span class="line"> <span class="keyword">if</span> (n % <span class="number">4</span> == <span class="number">1</span>) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (n % <span class="number">4</span> == <span class="number">2</span>) <span class="keyword">return</span> n + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但为什么这样做是正确的,我找到一篇比较好的<a href="https://cs.stackexchange.com/a/157354">解释</a>。</p><span id="more"></span><h3 id="证明">证明</h3><p>对于一个偶数$x$而言,$x \oplus (x+1) = 1$,因为两个数只有最后一位不同。因此我们可以对$0 \oplus 1 \oplus 2 \oplus ... \oplus n$进行配对,$$0 \oplus 1 = 1 $$$$2 \oplus 3 = 1 $$$$4 \oplus 5 = 1 $$$$...$$</p><p>这里我们有$\lfloor\frac{n+1}{2}\rfloor$个异或值为$1$的配对,而如果n为偶数则$n$没有被配对。</p><p>若$n \equiv 0 \pmod 4$,$n$没有被配对,而已经配对的$n$个数就构成了$\frac{n}{2}$组,其异或和为$\frac{n}{2}$个$1$的异或和,即为0,因为$\frac{n}{2}$为偶数。再加上最后没被配对的n,答案即为$n$。</p><p>若$n \equiv 1 \pmod 4$,则$f(n-1) = n-1$,再注意到$n-1$是个偶数,故答案即为$(n-1) \oplus n = 1$。</p><p>若$n \equiv 2 \pmod 4$,则$n$没有被配对,且$f(n-1)=1$,注意到$n$是一个偶数,故答案为$1 \oplus n$即为$n+1$。</p><p>最后,若$n \equiv 3 \pmod 4$,$f(n-1) = n$,故答案为$n \oplus n = 0$。</p><h2 id="函数g">函数g</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">g</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span> i, <span class="type">int</span> k)</span> </span>{</span><br><span class="line"> <span class="type">int</span> u = n >> i;</span><br><span class="line"> <span class="type">int</span> cnt = u + <span class="number">1</span>;</span><br><span class="line"> <span class="type">int</span> v = <span class="built_in">f</span>(u);</span><br><span class="line"> <span class="keyword">if</span> ((u << i) + k > n) {</span><br><span class="line"> cnt--;</span><br><span class="line"> v = <span class="built_in">f</span>(u - <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (cnt % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> v << i;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">return</span> (v << i) | k;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里,我们要找出$[0, n]$中满足$\pmod {2^i}$为$k$的数的异或和。这些数可以写为 $a*2^i+k$。当我们将其2进制表示中的后i位与前边分开,即得到如下数列,$$000000\ \underbrace{abcde}_\text{length of i} $$$$000001\ abcde $$$$000010\ abcde $$$$...$$ 其中abcde是k的二进制表示。这些数的异或和分为两个部分计算,假设这个数列有cnt个数,那么前半部分的结果即为$f(cnt)$,后半部分的结果取决于cnt的奇偶,若cnt为奇数,结果为k,否则为0。</p><p>前面的代码就是实现了这个逻辑,其中需要注意的就是当$n / 2^i * 2^i + k > n$时,$cnt=n/2^i-1$。</p>]]></content>
<summary type="html"><p>最近做codeforces的比赛时,发现一道<a href="https://codeforces.com/contest/2036/problem/F">题目</a>。题意是求区间$[l, r]$内,满足$x \not\equiv k \pmod {2^i}$的数的异或和。</p>
<p>整体思路是假设我们有函数$f(n)$表示$[0, n]$中所有数的异或和,有函数$g(n)$表示$[0, n]$中所有模$2^i$等于k的数的异或和,那么最终答案即为:</p>
<p>$$ f(r) \oplus f(l-1) \oplus g(r) \oplus g(l-1) $$</p>
<h2 id="函数f">函数f</h2><p>函数f的计算其实非常简单,</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">(<span class="type">int</span> n)</span> </span>&#123;</span><br><span class="line"> <span class="keyword">if</span> (n % <span class="number">4</span> == <span class="number">0</span>) <span class="keyword">return</span> n;</span><br><span class="line"> <span class="keyword">if</span> (n % <span class="number">4</span> == <span class="number">1</span>) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (n % <span class="number">4</span> == <span class="number">2</span>) <span class="keyword">return</span> n + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>但为什么这样做是正确的,我找到一篇比较好的<a href="https://cs.stackexchange.com/a/157354">解释</a>。</p></summary>
<category term="Algorithm" scheme="https://andimeo.us.kg/categories/Algorithm/"/>
<category term="XOR" scheme="https://andimeo.us.kg/tags/XOR/"/>
</entry>
<entry>
<title>XOR-hashing</title>
<link href="https://andimeo.us.kg/2024/09/23/XOR-hashing/"/>
<id>https://andimeo.us.kg/2024/09/23/XOR-hashing/</id>
<published>2024-09-23T11:46:00.000Z</published>
<updated>2024-09-23T16:21:14.418Z</updated>
<content type="html"><![CDATA[<p>最近在做 <a href="https://codeforces.com/contest/2014">Codeforces Round #974</a>时,碰到一个题目,给定一个数组,对于任意区间 $[l, r]$ ,两个人玩一个游戏,A先取一个数,B再取一个数,交替进行直到结束,结束后谁取得的数之和较大谁胜利,问B可否不输。已知两人都会尽力获胜,故而每个人在其轮次都会取当前可取的最大值。</p><p>经过简单分析可知,若 $r-l+1$ 为奇数个数,则A必胜,若为偶数,那么必须区间中的所有元素都出现偶数次,B才可以不输(即平局)。故而问题转化为,对于任意区间 $[l, r]$ ,求区间中元素是否都出现偶数次。</p><p>XOR-hashing就可以很好的解决问题。</p><span id="more"></span><h2 id="xor-hashing">XOR-hashing</h2><p>关于XOR-hashing,这里有一篇不错的教程:<a href="https://codeforces.com/blog/entry/85900">XOR Hashing [TUTORIAL] </a></p><p>大体做法就是我们将每一个值映射到一个64 bit的随机数,我们将这个映射用 $f$ 表示。然后用 XOR 后的值来表征一个集合。比如我们需要查找一个区间 $[l, r]$ 是否是一个permutation,那么我们只需要计算 $[l, r]$ 的 XOR 值,这可以使用 prefix-sum 很容易的得到。然后我们只需要判断$$f(1) \oplus f(2) \oplus ... \oplus f(r-l+1) = f(l) \oplus f(l+1) \oplus ... \oplus f(r)$$是否成立即可。</p><p>另外比如一开始提到的那个问题,提前算好 prefix_sum 后 (用$ps$表示),只要判断$$ps(r) \oplus ps(l-1) = 0$$是否成立即可。</p><p>有些人看到这个算法,可能会担心即使 $[l, r]$ 的 XOR 值为零,也不能确定$[l, r]$内的每个值都出现偶数次。因为 XOR 值为0是 $[l, r]$ 内每个元素都出现偶数次的充分<strong>不必要</strong>条件</p><p>没错,但是我们一开始对每个数都做了一个映射,映射到了一个64 bit的整数。那这里有一个关于 hash collision 的定理,在 d 的 hash space中,n 个元素出现碰撞的概率为$$1-exp(-\frac{n(n-1)}{2d})$$</p><p>那么在我们的算法中 n 一般不大于 $10^6$,而 $d=2^{64}$,所以碰撞概率约为 $5.42^{-8}$,所以我们一般可以认为不会出现碰撞。如果你仍有疑虑,那么可以使用128 bit的随机数,使用两个 64 bit 的数来表示即可。</p><p>我们可以再试一下当$d=2^{32}$时,碰撞概率就等于1了。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span><span class="keyword">def</span> <span class="title function_">f</span>(<span class="params">n, d</span>):</span><br><span class="line"><span class="meta">... </span> <span class="keyword">import</span> math</span><br><span class="line"><span class="meta">... </span> <span class="keyword">return</span> <span class="number">1</span> - math.exp(-n*(n-<span class="number">1</span>)/<span class="number">2</span>**d)</span><br><span class="line">...</span><br><span class="line"><span class="meta">>>> </span>f(<span class="number">10</span>**<span class="number">6</span>, <span class="number">32</span>)</span><br><span class="line"><span class="number">1.0</span></span><br><span class="line"><span class="meta">>>> </span>f(<span class="number">10</span>**<span class="number">6</span>, <span class="number">64</span>)</span><br><span class="line"><span class="number">5.4210052891079386e-08</span></span><br></pre></td></tr></table></figure><h2 id="题目">题目</h2><p>在那篇教程中,作者列了几个使用 XOR-hashing 的题目,评论区中也有人提名了一些好的题目。这里我们分别讲一下这几个题目的思路。我先把所有题目列在下面,如果你有兴趣,也可以提前自己尝试一下。</p><ul><li><a href="https://codeforces.com/contest/1418/problem/G">Three occurences</a></li><li><a href="https://codeforces.com/contest/1175/problem/F">The number of subpermutation</a></li><li><a href="https://codeforces.com/contest/869/problem/E">The untended antiquity</a></li><li><a href="https://codeforces.com/contest/895/problem/C">Square subsets</a></li><li><a href="https://codeforces.com/contest/2014/problem/H">Robin hood archery</a></li><li><a href="https://www.hackerrank.com/contests/hourrank-17/challenges/number-game-on-a-tree/problem">Number game on a tree</a></li><li><a href="https://atcoder.jp/contests/abc250/tasks/abc250_e">Prefix equality</a></li></ul><h3 id="three-occurences">Three occurences</h3><p><a href="https://codeforces.com/contest/1418/problem/G">Problem</a></p><p>这个题目的意思是给定一个数组,求其中有多少个子数组其中的元素刚好出现三次。我们对于$[l, r]$做如下编码,$\sum_{distinct_v}{(cnt(v) \mod 3) * f(v)}$,$f$仍为随机化映射函数。如此编码后,若$[1..i]$和$[1..j]$的编码相同,那么$[i+1..j]$的元素则必出现三的倍数次。</p><p>而且我们可以线性的算出所有的前缀编码,那使用hashmap就可以快速得到跟当前位置编码一致的前缀个数。</p><p>这里还有一个问题就是题目要求必须得是三次,而其余三的倍数次是不合法的。所以我们还需要当某个数的个数达到4时,我们需要把首次出现位置前的所有前缀从hashmap中删除,这里有点类似sliding window的思想。</p><details> <summary><u style="color:Tomato;"><b>Solution</b></u></summary> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><bits/stdc++.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> int long long</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> all(x) x.begin(),x.end()</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> pb push_back</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> tc int tt, qq = 0; cin >> tt; while(qq++ < tt)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> cs cout << <span class="string">"Case "</span> << qq << <span class="string">": "</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> INF 1000000000000000000LL <span class="comment">// Never use 1e(s) mara khawa sure, mone rakhba :)</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);</span></span><br><span class="line"></span><br><span class="line"><span class="function">mt19937_64 <span class="title">rng</span><span class="params">(chrono::steady_clock::now().time_since_epoch().count())</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">solve</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> n;</span><br><span class="line"> cin >> n;</span><br><span class="line"> <span class="keyword">using</span> <span class="type">hash_t</span> = <span class="type">uint64_t</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">hash_values</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i <= n; i++) {</span><br><span class="line"> hash_values[i] = <span class="built_in">rng</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> ans = <span class="number">0</span>;</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">freq</span><span class="params">(n + <span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">prefix_hashes</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line"> unordered_map<<span class="type">hash_t</span>, <span class="type">int</span>> hash_freq;</span><br><span class="line"> hash_freq[prefix_hashes[<span class="number">0</span>]]++;</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> start = <span class="number">0</span>;</span><br><span class="line"> vector<queue<<span class="type">int</span>> > <span class="built_in">indices</span>(n + <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= n; i++) {</span><br><span class="line"> <span class="type">int</span> a;</span><br><span class="line"> cin >> a;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (indices[a].<span class="built_in">size</span>() >= <span class="number">3</span>) {</span><br><span class="line"> <span class="type">int</span> remove = indices[a].<span class="built_in">front</span>();</span><br><span class="line"> indices[a].<span class="built_in">pop</span>();</span><br><span class="line"> <span class="keyword">while</span> (start < remove) {</span><br><span class="line"> hash_freq[prefix_hashes[start]]--;</span><br><span class="line"> start++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> before = freq[a];</span><br><span class="line"> freq[a] = (freq[a] + <span class="number">1</span>) % <span class="number">3</span>;</span><br><span class="line"> prefix_hashes[i] = prefix_hashes[i<span class="number">-1</span>] + (freq[a] - before) * hash_values[a];</span><br><span class="line"> ans += hash_freq[prefix_hashes[i]];</span><br><span class="line"> hash_freq[prefix_hashes[i]]++;</span><br><span class="line"> indices[a].<span class="built_in">push</span>(i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ans;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">signed</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> speed</span><br><span class="line"> cout << <span class="built_in">solve</span>() << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure></details><h3 id="the-number-of-subpermutation">The number of subpermutation</h3><p><a href="https://codeforces.com/contest/1175/problem/F">Problem</a></p><p>这个题目的意思是给定一个数组,求其中有多少个子数组是一个permutation。给定一个子数组,判断其是否为一个permutation是很简单的事情,而要快速的找出所有符合条件的子数组则还要一些技巧。</p><p>这里我们使用 divide-and-conquer 的思想。我们用 $f(l, r)$ 表示 $[l, r]$ 中有多少个子数组是一个permutation。则我们先找到 $[l, r]$ 的最大数,及其所在的位置,我们用 $max_v$ 和 $max_i$ 来表示。然后我们判断包含 $max_i$ 所处元素的长度为 $max_v$的子串是否为一个permutation即可。这个复杂度为$O(n)$。然后分别对 $[l, max_i-1]$ 和 $[max_i+1, r]$ 进行同样的操作即可。</p><p>我们有 $T(n) = 2T(n/2) + O(n)$,故复杂度为 $O(n \log n)$。特别注意到当最大值在区间的端点处时,枚举包含此元素的子区间为 $O(1)$,所以在最坏情况下,也不会退化为 $O(n^2)$。然而我们在求最大值时线性的算法则会导致最坏情况下时间复杂度退化,所以我们需要一个数据结构能够快速求得某段区间的最大值及位置,比如线段树。</p><details> <summary><u style="color:Tomato;"><b>Solution</b></u></summary> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><bits/stdc++.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> int long long</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> all(x) x.begin(),x.end()</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> pb push_back</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> tc int tt, qq = 0; cin >> tt; while(qq++ < tt)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> cs cout << <span class="string">"Case "</span> << qq << <span class="string">": "</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> INF 1000000000000000000LL <span class="comment">// Never use 1e(s) mara khawa sure, mone rakhba :)</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);</span></span><br><span class="line"><span class="function">mt19937_64 <span class="title">rng</span><span class="params">(chrono::steady_clock::now().time_since_epoch().count())</span></span>;</span><br><span class="line"><span class="keyword">using</span> <span class="type">hash_t</span> = <span class="type">uint64_t</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SegmentTree</span> {</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> vector<<span class="type">int</span>> tree;</span><br><span class="line"> vector<<span class="type">int</span>> indices;</span><br><span class="line"> <span class="type">int</span> n;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">build</span><span class="params">(<span class="type">const</span> vector<<span class="type">int</span>>& arr, <span class="type">int</span> v, <span class="type">int</span> tl, <span class="type">int</span> tr)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (tl == tr) {</span><br><span class="line"> tree[v] = arr[tl];</span><br><span class="line"> indices[v] = tl;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="type">int</span> tm = (tl + tr) / <span class="number">2</span>;</span><br><span class="line"> <span class="built_in">build</span>(arr, v*<span class="number">2</span>, tl, tm);</span><br><span class="line"> <span class="built_in">build</span>(arr, v*<span class="number">2</span><span class="number">+1</span>, tm<span class="number">+1</span>, tr);</span><br><span class="line"> <span class="keyword">if</span> (tree[v*<span class="number">2</span>] >= tree[v*<span class="number">2</span><span class="number">+1</span>]) {</span><br><span class="line"> tree[v] = tree[v*<span class="number">2</span>];</span><br><span class="line"> indices[v] = indices[v*<span class="number">2</span>];</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> tree[v] = tree[v*<span class="number">2</span><span class="number">+1</span>];</span><br><span class="line"> indices[v] = indices[v*<span class="number">2</span><span class="number">+1</span>];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function">pair<<span class="type">int</span>, <span class="type">int</span>> <span class="title">query</span><span class="params">(<span class="type">int</span> v, <span class="type">int</span> tl, <span class="type">int</span> tr, <span class="type">int</span> l, <span class="type">int</span> r)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (l > r)</span><br><span class="line"> <span class="keyword">return</span> {INT_MIN, <span class="number">-1</span>};</span><br><span class="line"> <span class="keyword">if</span> (l == tl && r == tr)</span><br><span class="line"> <span class="keyword">return</span> {tree[v], indices[v]};</span><br><span class="line"> <span class="type">int</span> tm = (tl + tr) / <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">auto</span> [v1, i1] = <span class="built_in">query</span>(v*<span class="number">2</span>, tl, tm, l, <span class="built_in">min</span>(r, tm));</span><br><span class="line"> <span class="keyword">auto</span> [v2, i2] = <span class="built_in">query</span>(v*<span class="number">2</span><span class="number">+1</span>, tm<span class="number">+1</span>, tr, <span class="built_in">max</span>(l, tm<span class="number">+1</span>), r);</span><br><span class="line"> <span class="keyword">if</span> (v1 >= v2) {</span><br><span class="line"> <span class="keyword">return</span> {v1, i1};</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> {v2, i2};</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">SegmentTree</span>(<span class="type">const</span> vector<<span class="type">int</span>>& arr) {</span><br><span class="line"> n = arr.<span class="built_in">size</span>();</span><br><span class="line"> tree.<span class="built_in">resize</span>(<span class="number">4</span> * n);</span><br><span class="line"> indices.<span class="built_in">resize</span>(<span class="number">4</span> * n);</span><br><span class="line"> <span class="built_in">build</span>(arr, <span class="number">1</span>, <span class="number">1</span>, n - <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function">pair<<span class="type">int</span>, <span class="type">int</span>> <span class="title">query</span><span class="params">(<span class="type">int</span> l, <span class="type">int</span> r)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">query</span>(<span class="number">1</span>, <span class="number">1</span>, n - <span class="number">1</span>, l, r);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">(vector<<span class="type">int</span>> &a, vector<<span class="type">hash_t</span>> &prefix_hashes, vector<<span class="type">hash_t</span>> &perm_hashes, SegmentTree& st, <span class="type">int</span> l, <span class="type">int</span> r)</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> [n, max_i] = st.<span class="built_in">query</span>(l, r);</span><br><span class="line"> <span class="built_in">assert</span>(max_i >= l && max_i <= r);</span><br><span class="line"> <span class="type">int</span> ans = <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="built_in">max</span>(l, max_i - n + <span class="number">1</span>); i + n - <span class="number">1</span> <= r && i <= max_i; i++) {</span><br><span class="line"> <span class="type">int</span> start = i, end = i + n - <span class="number">1</span>;</span><br><span class="line"> <span class="type">hash_t</span> hash = prefix_hashes[end] ^ prefix_hashes[start - <span class="number">1</span>];</span><br><span class="line"> <span class="keyword">if</span> (hash == perm_hashes[n]) {</span><br><span class="line"> ans++;</span><br><span class="line"> }</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (l <= max_i - <span class="number">1</span>) ans += <span class="built_in">f</span>(a, prefix_hashes, perm_hashes, st, l, max_i - <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">if</span> (max_i + <span class="number">1</span> <= r) ans += <span class="built_in">f</span>(a, prefix_hashes, perm_hashes, st, max_i + <span class="number">1</span>, r);</span><br><span class="line"> <span class="keyword">return</span> ans;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">solve</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> n;</span><br><span class="line"> cin >> n;</span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">hashes</span><span class="params">(n<span class="number">+1</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i <= n; i++) hashes[i] = <span class="built_in">rng</span>();</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">a</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">prefix_hashes</span><span class="params">(n + <span class="number">1</span>)</span>, <span class="title">perm_hashes</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= n; i++) {</span><br><span class="line"> cin >> a[i];</span><br><span class="line"> prefix_hashes[i] = prefix_hashes[i<span class="number">-1</span>] ^ hashes[a[i]];</span><br><span class="line"> perm_hashes[i] = perm_hashes[i<span class="number">-1</span>] ^ hashes[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="function">SegmentTree <span class="title">st</span><span class="params">(a)</span></span>;</span><br><span class="line"> cout << <span class="built_in">f</span>(a, prefix_hashes, perm_hashes, st, <span class="number">1</span>, n) << endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">signed</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">speed</span></span><br><span class="line"><span class="function"> <span class="title">solve</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></details><h3 id="the-untended-antiquity">The untended antiquity</h3><p><a href="https://codeforces.com/contest/869/problem/E">Problem</a></p><p>这个题目的意思是给定一个二维平面,每次的操作可以划定一个矩形的barrier,各个barrier不会重叠,也可以撤消掉barrier。若干操作后会询问点A是否可以到达点B而不经过任何barrier。</p><p>这个思想是若A和B可达当且仅当A和B出现在同样的barriers中。所以我们对于每个barrier编码,用编码的异或来表征一个barriers的集合,然后能迅速找出A点和B点的编码是否相等即可。是否相等这里使用二维树状数组来实现 (这个二维树状数组是claude帮我写的 ^_^')。</p><details> <summary><u style="color:Tomato;"><b>Solution</b></u></summary> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><bits/stdc++.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="comment">// #define int long long</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> all(x) x.begin(),x.end()</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> pb push_back</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> tc int tt, qq = 0; cin >> tt; while(qq++ < tt)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> cs cout << <span class="string">"Case "</span> << qq << <span class="string">": "</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> INF 1000000000000000000LL <span class="comment">// Never use 1e(s) mara khawa sure, mone rakhba :)</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);</span></span><br><span class="line"><span class="function">mt19937_64 <span class="title">rng</span><span class="params">(chrono::steady_clock::now().time_since_epoch().count())</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FenwickTree2D</span> {</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="type">int</span> rows, cols;</span><br><span class="line"> std::vector<std::vector<<span class="type">int</span>>> bit;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">lsb</span><span class="params">(<span class="type">int</span> x)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> x & (-x);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">FenwickTree2D</span>(<span class="type">int</span> r, <span class="type">int</span> c) : <span class="built_in">rows</span>(r), <span class="built_in">cols</span>(c) {</span><br><span class="line"> bit.<span class="built_in">resize</span>(rows + <span class="number">1</span>, std::<span class="built_in">vector</span><<span class="type">int</span>>(cols + <span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y, <span class="type">int</span> val)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = x; i <= rows; i += <span class="built_in">lsb</span>(i)) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> j = y; j <= cols; j += <span class="built_in">lsb</span>(j)) {</span><br><span class="line"> bit[i][j] ^= val;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">query</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> </span>{</span><br><span class="line"> <span class="type">int</span> result = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = x; i > <span class="number">0</span>; i -= <span class="built_in">lsb</span>(i)) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> j = y; j > <span class="number">0</span>; j -= <span class="built_in">lsb</span>(j)) {</span><br><span class="line"> result ^= bit[i][j];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">update_range</span><span class="params">(<span class="type">int</span> x1, <span class="type">int</span> y1, <span class="type">int</span> x2, <span class="type">int</span> y2, <span class="type">int</span> val)</span> </span>{</span><br><span class="line"> <span class="comment">// XOR at (x1, y1)</span></span><br><span class="line"> <span class="built_in">update</span>(x1, y1, val);</span><br><span class="line"> <span class="comment">// XOR at (x1, y2+1)</span></span><br><span class="line"> <span class="built_in">update</span>(x1, y2 + <span class="number">1</span>, val);</span><br><span class="line"> <span class="comment">// XOR at (x2+1, y1)</span></span><br><span class="line"> <span class="built_in">update</span>(x2 + <span class="number">1</span>, y1, val);</span><br><span class="line"> <span class="comment">// XOR at (x2+1, y2+1)</span></span><br><span class="line"> <span class="built_in">update</span>(x2 + <span class="number">1</span>, y2 + <span class="number">1</span>, val);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">query_range</span><span class="params">(<span class="type">int</span> x1, <span class="type">int</span> y1, <span class="type">int</span> x2, <span class="type">int</span> y2)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">query</span>(x2, y2) ^ <span class="built_in">query</span>(x1 - <span class="number">1</span>, y2) ^ <span class="built_in">query</span>(x2, y1 - <span class="number">1</span>) ^ <span class="built_in">query</span>(x1 - <span class="number">1</span>, y1 - <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">solve</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> n, m, q;</span><br><span class="line"> cin >> n >> m >> q;</span><br><span class="line"> <span class="keyword">using</span> <span class="type">hash_t</span> = <span class="type">uint64_t</span>;</span><br><span class="line"> <span class="function">FenwickTree2D <span class="title">st</span><span class="params">(n<span class="number">+1</span>, m<span class="number">+1</span>)</span></span>;</span><br><span class="line"> map<vector<<span class="type">int</span>>, <span class="type">hash_t</span>> range2hash;</span><br><span class="line"> <span class="keyword">while</span> (q--) {</span><br><span class="line"> <span class="type">int</span> op, x1, y1, x2, y2;</span><br><span class="line"> cin >> op >> x1 >> y1 >> x2 >> y2;</span><br><span class="line"> vector<<span class="type">int</span>> key = {x1, y1, x2, y2};</span><br><span class="line"> <span class="keyword">if</span> (op == <span class="number">1</span>) {</span><br><span class="line"> range2hash[key] = <span class="built_in">rng</span>();</span><br><span class="line"> st.<span class="built_in">update_range</span>(x1, y1, x2, y2, range2hash[key]);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (op == <span class="number">2</span>) {</span><br><span class="line"> st.<span class="built_in">update_range</span>(x1, y1, x2, y2, range2hash[key]);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (st.<span class="built_in">query</span>(x1, y1) == st.<span class="built_in">query</span>(x2, y2)) {</span><br><span class="line"> cout << <span class="string">"Yes"</span> << endl;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> cout << <span class="string">"No"</span> << endl;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">signed</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">speed</span></span><br><span class="line"><span class="function"> <span class="title">solve</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure></details><h3 id="square-subsets">Square subsets</h3><p><a href="https://codeforces.com/contest/895/problem/C">Problem</a></p><p>这个题目是说给定一个数组,求有多少子集的元素乘积是一个完全平方数。其中元素的大小很小,不大于70。</p><p>一个数是完全平方数当且仅当分解素因子后,每个因子的幂都是偶数。</p><p>我们首先给1到70中的所有素数分配一个随机码,然后对1到70中的每个数进行编码。编码方式为奇数次的那些素因子对应的编码的异或。然后我们统计每个数在数组中出现多少次,假设出现c次,那么取偶数个的方法数为 $N(even) = C[c][2] + C[c][4] + ...$,对应的取奇数个的方法数为 $N(odd) = C[c][1] + C[c][3] + ...$。取偶数个时,当前元素的编码即为0,若前面出现过编码为0的组合数为p的话,就有$p \cdot N(even)$种方法。取奇数个时,当前元素的编码为h,若前面出现过编码为h的组合数为q的话,就有$q \cdot N(odd)$种方法。每次计算完答案后,再更新用来计数的hashmap即可。</p><p>这个题目主要要注意到元素很小,然后来遍历元素的范围即可。</p><details> <summary><u style="color:Tomato;"><b>Solution</b></u></summary> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><bits/stdc++.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> int long long</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> all(x) x.begin(),x.end()</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> pb push_back</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> tc int tt, qq = 0; cin >> tt; while(qq++ < tt)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> cs cout << <span class="string">"Case "</span> << qq << <span class="string">": "</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> INF 1000000000000000000LL <span class="comment">// Never use 1e(s) mara khawa sure, mone rakhba :)</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);</span></span><br><span class="line"><span class="function">mt19937_64 <span class="title">rng</span><span class="params">(chrono::steady_clock::now().time_since_epoch().count())</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">70</span>, MOD = <span class="number">1000000007</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">pow</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>{</span><br><span class="line"> <span class="type">int</span> res = <span class="number">1</span>;</span><br><span class="line"> a %= MOD;</span><br><span class="line"> <span class="keyword">while</span> (b > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (b & <span class="number">1</span>) res = (res * a) % MOD;</span><br><span class="line"> a = (a * a) % MOD;</span><br><span class="line"> b >>= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">inverse_mod</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> m)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (m == <span class="number">1</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">pow</span>(a, m<span class="number">-2</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">solve</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">using</span> <span class="type">hash_t</span> = <span class="type">uint64_t</span>;</span><br><span class="line"> <span class="type">int</span> n;</span><br><span class="line"> cin >> n;</span><br><span class="line"> vector<<span class="type">int</span>> primes = {<span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">11</span>, <span class="number">13</span>, <span class="number">17</span>, <span class="number">19</span>, <span class="number">23</span>, <span class="number">29</span>, <span class="number">31</span>, <span class="number">37</span>, <span class="number">41</span>, <span class="number">43</span>, <span class="number">47</span>, <span class="number">53</span>, <span class="number">59</span>, <span class="number">61</span>, <span class="number">67</span>};</span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">hashes</span><span class="params">(N<span class="number">+1</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> p : primes) {</span><br><span class="line"> hashes[p] = <span class="built_in">rng</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">hashes2</span><span class="params">(N<span class="number">+1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i <= N; i++) {</span><br><span class="line"> <span class="type">int</span> j = <span class="number">0</span>, m = i;</span><br><span class="line"> <span class="keyword">while</span> (m > <span class="number">1</span> && j < primes.<span class="built_in">size</span>()) {</span><br><span class="line"> <span class="type">int</span> cnt = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (m % primes[j] == <span class="number">0</span>) {</span><br><span class="line"> m /= primes[j];</span><br><span class="line"> cnt++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (cnt % <span class="number">2</span> == <span class="number">1</span>) {</span><br><span class="line"> hashes2[i] ^= hashes[primes[j]];</span><br><span class="line"> }</span><br><span class="line"> j++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> unordered_map<<span class="type">hash_t</span>, <span class="type">int</span>> hash_cnt;</span><br><span class="line"> hash_cnt[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line"> <span class="type">int</span> ans = <span class="number">0</span>;</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">cnt</span><span class="params">(N + <span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="type">int</span> x;</span><br><span class="line"> cin >> x;</span><br><span class="line"> cnt[x]++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i <= N; i++) {</span><br><span class="line"> <span class="type">int</span> c = cnt[i], even = <span class="number">0</span>, odd = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">0</span>) <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="number">1</span>) {</span><br><span class="line"> even = <span class="number">0</span>;</span><br><span class="line"> odd = <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="type">int</span> l = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">2</span>; j <= c; j+=<span class="number">2</span>) {</span><br><span class="line"> l = ((l * (c - j + <span class="number">1</span>)) % MOD * (c - j + <span class="number">2</span>) % MOD) * <span class="built_in">inverse_mod</span>(j * (j - <span class="number">1</span>), MOD) % MOD;</span><br><span class="line"> even = (even + l) % MOD;</span><br><span class="line"> }</span><br><span class="line"> odd = c, l = c;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">3</span>; j <= c; j+=<span class="number">2</span>) {</span><br><span class="line"> l = ((l * (c - j + <span class="number">1</span>)) % MOD * (c - j + <span class="number">2</span>) % MOD) * <span class="built_in">inverse_mod</span>(j * (j - <span class="number">1</span>), MOD) % MOD;</span><br><span class="line"> odd = (odd + l) % MOD;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ans = (ans + even * hash_cnt[<span class="number">0</span>] % MOD) % MOD;</span><br><span class="line"> ans = (ans + odd * hash_cnt[hashes2[i]] % MOD) % MOD;</span><br><span class="line"> vector<<span class="type">hash_t</span>> keys;</span><br><span class="line"> vector<<span class="type">int</span>> vals;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">auto</span> &[key, val] : hash_cnt) {</span><br><span class="line"> keys.<span class="built_in">push_back</span>(key);</span><br><span class="line"> vals.<span class="built_in">push_back</span>(val);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j < keys.<span class="built_in">size</span>(); j++) {</span><br><span class="line"> <span class="type">hash_t</span> newkey = keys[j] ^ hashes2[i];</span><br><span class="line"> hash_cnt[newkey] = (hash_cnt[newkey] + vals[j] * odd % MOD) % MOD;</span><br><span class="line"> hash_cnt[keys[j]] = (hash_cnt[keys[j]] + vals[j] * even % MOD) % MOD;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> cout << ans << endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">signed</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">speed</span></span><br><span class="line"><span class="function"> <span class="title">solve</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure></details><h3 id="robin-hood-archery">Robin hood archery</h3><p><a href="https://codeforces.com/contest/2014/problem/H">Problem</a></p><p>这个就是文初说的那个判断一个子区间是否元素全部出现偶数次的题目。直接使用前缀和求区间异或值是否为0即可。</p><details> <summary><u style="color:Tomato;"><b>Solution</b></u></summary> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><bits/stdc++.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> int long long</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> all(x) x.begin(),x.end()</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> pb push_back</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> tc int tt, qq = 0; cin >> tt; while(qq++ < tt)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> cs cout << <span class="string">"Case "</span> << qq << <span class="string">": "</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> INF 1000000000000000000LL <span class="comment">// Never use 1e(s) mara khawa sure, mone rakhba :)</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);</span></span><br><span class="line"><span class="function">mt19937_64 <span class="title">rng</span><span class="params">(chrono::steady_clock::now().time_since_epoch().count())</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="type">hash_t</span> = <span class="type">uint64_t</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">1000001</span>;</span><br><span class="line"><span class="function">vector<<span class="type">hash_t</span>> <span class="title">hashes</span><span class="params">(N + <span class="number">1</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i <= N; i++) {</span><br><span class="line"> hashes[i] = <span class="built_in">rng</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">solve</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> n, q;</span><br><span class="line"> cin >> n >> q;</span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">prefix_hashes</span><span class="params">(n + <span class="number">1</span>, <span class="number">0</span>)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i <= n; i++) {</span><br><span class="line"> <span class="type">int</span> x;</span><br><span class="line"> cin >> x;</span><br><span class="line"> prefix_hashes[i] = prefix_hashes[i<span class="number">-1</span>] ^ hashes[x];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < q; i++) {</span><br><span class="line"> <span class="type">int</span> l, r;</span><br><span class="line"> cin >> l >> r;</span><br><span class="line"> <span class="keyword">if</span> ((r - l + <span class="number">1</span>) % <span class="number">2</span> == <span class="number">1</span> || (prefix_hashes[r] ^ prefix_hashes[l<span class="number">-1</span>]) != <span class="number">0</span>) {</span><br><span class="line"> cout << <span class="string">"NO"</span> << endl;</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> cout << <span class="string">"YES"</span> << endl;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">signed</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">speed</span></span><br><span class="line"><span class="function"> <span class="title">init</span><span class="params">()</span></span>;</span><br><span class="line"> tc {</span><br><span class="line"> <span class="built_in">solve</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure></details><h3 id="number-game-on-a-tree">Number game on a tree</h3><p><a href="https://www.hackerrank.com/contests/hourrank-17/challenges/number-game-on-a-tree/problem">Problem</a></p><p>这个题目是说给定一颗树,树的每条边上有一个值,求有多少对$(u, v)$,其路径上的数都出现偶数次。其实题面是一个博弈论的问题,但很容易转化到这个问题。</p><p>很显然给定一个$(u, v)$对,那路径上的数(随机化编码后)的异或为0则满足条件,但如何快速判断所有的$(u, v)$对呢?</p><p>思路是假设我们从1开始遍历这棵树时u, v的最近公共祖先是w,那么$xor(1, u) = xor(1, w) \oplus xor(w, u)$,同理$xor(1, v) = xor(1, w) \oplus xor(w, v)$。而$xor(1, w) \oplus xor(1, w) = 0$,所以$$xor(u, v) = xor(u, w) \oplus xor(w, v) = xor(1, u) \oplus xor(1, v)$$</p><p>那我们就在遍历的过程中,在点u,只需要找到所有等于$xor(1, u)$的节点,这些点到u的路径上的数就都出现偶数次了。</p><details> <summary><u style="color:Tomato;"><b>Solution</b></u></summary> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><bits/stdc++.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> int long long</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> all(x) x.begin(),x.end()</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> pb push_back</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> tc int tt, qq = 0; cin >> tt; while(qq++ < tt)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> cs cout << <span class="string">"Case "</span> << qq << <span class="string">": "</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> INF 1000000000000000000LL <span class="comment">// Never use 1e(s) mara khawa sure, mone rakhba :)</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);</span></span><br><span class="line"><span class="function">mt19937_64 <span class="title">rng</span><span class="params">(chrono::steady_clock::now().time_since_epoch().count())</span></span>;</span><br><span class="line"><span class="keyword">using</span> <span class="type">hash_t</span> = <span class="type">uint64_t</span>;</span><br><span class="line"></span><br><span class="line">unordered_map<<span class="type">int</span>, <span class="type">hash_t</span>> hashes;</span><br><span class="line">vector<vector<<span class="type">int</span>>> adj;</span><br><span class="line">vector<vector<<span class="type">int</span>>> weights;</span><br><span class="line"><span class="type">int</span> ans;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">int</span> node, <span class="type">int</span> p, <span class="type">hash_t</span> h, unordered_map<<span class="type">hash_t</span>, <span class="type">int</span>>& paths)</span> </span>{</span><br><span class="line"> ans += paths[h];</span><br><span class="line"> paths[h]++;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < adj[node].<span class="built_in">size</span>(); i++) {</span><br><span class="line"> <span class="type">int</span> child = adj[node][i];</span><br><span class="line"> <span class="keyword">if</span> (child == p) <span class="keyword">continue</span>;</span><br><span class="line"> <span class="built_in">f</span>(child, node, h^hashes[weights[node][i]], paths);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">solve</span><span class="params">()</span> </span>{</span><br><span class="line"> ans = <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> n;</span><br><span class="line"> cin >> n;</span><br><span class="line"></span><br><span class="line"> hashes.<span class="built_in">clear</span>();</span><br><span class="line"> adj.<span class="built_in">clear</span>(), adj.<span class="built_in">resize</span>(n, <span class="built_in">vector</span><<span class="type">int</span>>());</span><br><span class="line"> weights.<span class="built_in">clear</span>(), weights.<span class="built_in">resize</span>(n, <span class="built_in">vector</span><<span class="type">int</span>>());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i < n; i++) {</span><br><span class="line"> <span class="type">int</span> u, v, w;</span><br><span class="line"> cin >> u >> v >> w;</span><br><span class="line"> u--, v--;</span><br><span class="line"> adj[u].<span class="built_in">push_back</span>(v);</span><br><span class="line"> adj[v].<span class="built_in">push_back</span>(u);</span><br><span class="line"> weights[u].<span class="built_in">push_back</span>(w);</span><br><span class="line"> weights[v].<span class="built_in">push_back</span>(w);</span><br><span class="line"> hashes[w] = <span class="built_in">rng</span>();</span><br><span class="line"> }</span><br><span class="line"> unordered_map<<span class="type">hash_t</span>, <span class="type">int</span>> ps;</span><br><span class="line"> <span class="built_in">f</span>(<span class="number">0</span>, <span class="number">-1</span>, <span class="number">0</span>, ps);</span><br><span class="line"> cout << n * (n - <span class="number">1</span>) / <span class="number">2</span> - ans << endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">signed</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> speed</span><br><span class="line"> tc {</span><br><span class="line"> <span class="built_in">solve</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure></details><h3 id="prefix-equality">Prefix equality</h3><p><a href="https://atcoder.jp/contests/abc250/tasks/abc250_e">Problem</a></p><p>这个题目是说,给定两个数组A和B,对于任意x和y,求A[1..x]和B[1..y]构成的集合是否相同。</p><p>经过前面的题目,这个就很简单了,先随机化编码,然后求前缀和,当然如果某个元素出现过了,则该位置的前缀和等于前一个位置的。然后判断A在x位置的前缀和是否等于B在y位置的前缀和即可。</p><details> <summary><u style="color:Tomato;"><b>Solution</b></u></summary> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><bits/stdc++.h></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> int long long</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> all(x) x.begin(),x.end()</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> pb push_back</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> tc int tt, qq = 0; cin >> tt; while(qq++ < tt)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> cs cout << <span class="string">"Case "</span> << qq << <span class="string">": "</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> INF 1000000000000000000LL <span class="comment">// Never use 1e(s) mara khawa sure, mone rakhba :)</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);</span></span><br><span class="line"><span class="function">mt19937_64 <span class="title">rng</span><span class="params">(chrono::steady_clock::now().time_since_epoch().count())</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">solve</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> n;</span><br><span class="line"> cin >> n;</span><br><span class="line"> <span class="keyword">using</span> <span class="type">hash_t</span> = <span class="type">uint64_t</span>;</span><br><span class="line"> unordered_map<<span class="type">int</span>, <span class="type">hash_t</span>> hashes;</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">a</span><span class="params">(n)</span>, <span class="title">b</span><span class="params">(n)</span></span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> cin >> a[i];</span><br><span class="line"> <span class="keyword">if</span> (hashes.<span class="built_in">find</span>(a[i]) == hashes.<span class="built_in">end</span>()) {</span><br><span class="line"> hashes[a[i]] = <span class="built_in">rng</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> cin >> b[i];</span><br><span class="line"> <span class="keyword">if</span> (hashes.<span class="built_in">find</span>(b[i]) == hashes.<span class="built_in">end</span>()) {</span><br><span class="line"> hashes[b[i]] = <span class="built_in">rng</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="function">vector<<span class="type">hash_t</span>> <span class="title">prefix_hash_a</span><span class="params">(n)</span>, <span class="title">prefix_hash_b</span><span class="params">(n)</span></span>;</span><br><span class="line"> unordered_set<<span class="type">hash_t</span>> seen_a, seen_b;</span><br><span class="line"> <span class="type">hash_t</span> a_hash = <span class="number">0</span>, b_hash = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="keyword">if</span> (seen_a.<span class="built_in">find</span>(hashes[a[i]]) == seen_a.<span class="built_in">end</span>()) {</span><br><span class="line"> a_hash ^= hashes[a[i]];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (seen_b.<span class="built_in">find</span>(hashes[b[i]]) == seen_b.<span class="built_in">end</span>()) {</span><br><span class="line"> b_hash ^= hashes[b[i]];</span><br><span class="line"> }</span><br><span class="line"> prefix_hash_a[i] = a_hash;</span><br><span class="line"> prefix_hash_b[i] = b_hash;</span><br><span class="line"> seen_a.<span class="built_in">insert</span>(hashes[a[i]]);</span><br><span class="line"> seen_b.<span class="built_in">insert</span>(hashes[b[i]]);</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> q;</span><br><span class="line"> cin >> q;</span><br><span class="line"> <span class="keyword">while</span> (q--) {</span><br><span class="line"> <span class="type">int</span> x, y;</span><br><span class="line"> cin >> x >> y;</span><br><span class="line"> x -= <span class="number">1</span>;</span><br><span class="line"> y -= <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (prefix_hash_a[x] == prefix_hash_b[y]) {</span><br><span class="line"> cout << <span class="string">"Yes\n"</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> cout << <span class="string">"No\n"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">signed</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">speed</span></span><br><span class="line"><span class="function"> <span class="title">solve</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure></details><h2 id="结语">结语</h2><p>经过这几道题目,相信就能掌握 XOR-hashing 这个技术了。个人认为这个技术的最大好处在于,可以在碰撞概率忽略不计的情况下,用一个值来表征一个集合。但是这个一般也不会被单独考察,往往都是和其他算法结合在一起的。</p>]]></content>
<summary type="html"><p>最近在做 <a href="https://codeforces.com/contest/2014">Codeforces Round #974</a>时,碰到一个题目,给定一个数组,对于任意区间 $[l, r]$ ,两个人玩一个游戏,A先取一个数,
B再取一个数,交替进行直到结束,结束后谁取得的数之和较大谁胜利,问B可否不输。已知两人都会尽力获胜,故而每个人在其轮次都会取当前可取的最大值。</p>
<p>经过简单分析可知,若 $r-l+1$ 为奇数个数,则A必胜,若为偶数,那么必须区间中的所有元素都出现偶数次,B才可以不输(即平局)。故而问题转化为,对于任意区间 $[l, r]$ ,求区间中元素是否都出现偶数次。</p>
<p>XOR-hashing就可以很好的解决问题。</p></summary>
<category term="Algorithm" scheme="https://andimeo.us.kg/categories/Algorithm/"/>
<category term="hashing" scheme="https://andimeo.us.kg/tags/hashing/"/>
</entry>
<entry>
<title>2016 AMA</title>
<link href="https://andimeo.us.kg/2016/12/25/2016-AMA/"/>
<id>https://andimeo.us.kg/2016/12/25/2016-AMA/</id>
<published>2016-12-25T09:17:22.000Z</published>
<updated>2024-09-07T08:31:27.518Z</updated>
<content type="html"><![CDATA[<p>2016 AMA 今天终于要揭幕了。</p><p>But don't confuse it with the American Music Awards. Here it is <code>Andimeo's Music Awards</code> :)</p><h1 id="honor-award">Honor Award</h1><p>首先怀念一发Kesha,之前只听过Tik Toc和Your Love is My Drug等几首。</p><p>讲真并没有真的很喜欢钱妞,直到最近在Youtube上被推了这首Timber。</p><p>突然发现钱妞已经淡出视线快两年了,查了下才发现钱妞在2014年开始和自己的经纪人在打官司而且输了。因为输了官司在合同到期之前不仅不能签约别的唱片公司,也不能公开演出Sony拥有版权的所有热单。</p><span id="more"></span><p>资本主义的冷血暴露无疑,Twitter上也发起过#FreeKesha的话题,Taylor Swift还捐了20万刀给Kesha。</p><p>然而结局就是这么令人唏嘘,现在钱妞的身材也已经走样,一颗星星就这么陨落。</p><p><strong>So the Honor Award comes to Kesha for her amazing performance in Timber with Pitbull.</strong></p><iframe src="https://open.spotify.com/embed/track/1zHlj4dQ8ZAtrayhuDDmkY" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>这里顺便科普一下Timber的意思,看到很多歌词里过于"意译",实在对不起钱妞这首充满双关意味的歌曲。</p><blockquote><p>"To yell Timber!" is to deliver a warning that can mean life or death. Lumberjacks work in the forest cutting down trees. It's dangerous and repetitive work. Men have been killed when a tree falls and pins them underneath. A falling tree doesn't make much noise, and yelling "Timber!" is a heads-up to get out of the way!</p></blockquote><h1 id="music-awards">Music Awards</h1><h2 id="no.-10">NO. 10</h2><p>NO. 10 goes to <code>Changing by SIGMA</code>.</p><p>粗犷的嗓音,没想到是那么一个娇小的身躯发出的。</p><iframe src="https://open.spotify.com/embed/track/0FqVdhGQ2TGhA1tlONxdHC" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><blockquote><p>I've been frozen in<br>Tryina find a better view<br>This ain't real, this ain't cool<br>This ain't what I signed up too<br>This ain't right, it's no good<br>No good, oh </p></blockquote><h2 id="no.-9">NO. 9</h2><p>NO. 9 goes to <code>Fast Car by Jonas Blue ft. Dakota</code>.</p><iframe src="https://open.spotify.com/embed/track/6OZh916QF8XNunWaP97WEZ" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>跟Changing的感觉很像,女主骑着摩托在荒漠上驰骋。</p><blockquote><p>You got a fast car<br>Is it fast enough so you can fly away?<br>You gotta make a decision<br>Leave tonight or live and die this way </p></blockquote><h2 id="no.-8">NO. 8</h2><p>NO. 8 goes to <code>She by Jami Soul</code>.</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=5307435&auto=0&height=66"></iframe><p>唯一一首韩国歌,兄弟你下去,我只想听🎷,LOL...</p><h2 id="no.-7">NO. 7</h2><p>NO. 7 goes to <code>You Don't Know Me by Ofenbach ft. Brodie Barclay</code>.</p><iframe src="https://open.spotify.com/embed/track/4q0snbJ69gaEbxTf9glk6l" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>对于前奏中的口哨,从来都不能抵抗。</p><blockquote><p>I'll show you all the stars<br>I used to hide<br>No one ever seem to realize<br>But baby you will know me<br>But baby you will know </p></blockquote><p>又一首婚礼上代替知心爱人的歌曲。</p><h2 id="no.-6">NO. 6</h2><p>NO. 6 goes to <code>Home We'll Go by Walk off the Earth</code>.</p><iframe src="https://open.spotify.com/embed/track/6UN1vcxx6OejXncNZonTPF" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>跨越物种的禁忌之恋,女主试探又害怕的目光,电子乐的鼓点,穿插的口哨,彷佛在整个洱海只有你和心中的她。</p><blockquote><p>Don't let your head hang low<br>You've seen the darkest skies I know<br>Let your heart run child like horses in the wild<br>So take my hand and home we'll go<br>The sun it glows like gold<br>Feeling warm as a burning coal<br>Let your soul shine bright like diamonds in the sky<br>So take my hand and home we'll go </p></blockquote><h2 id="no.-5">NO. 5</h2><p>NO. 5 goes to <code>Rather Be by Pentatonix</code>.</p><iframe src="https://open.spotify.com/embed/track/4hxemf0pE0mSzubgsfRLWu" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><ul><li>没钱请伴奏系列</li><li>不插电.无公害.口活好.有机产品.低碳环保</li></ul><blockquote><p>We're a thousand miles from comfort,<br>we have traveled land and sea<br>But as long as you are with me, there's no place I'd rather be<br>I would wait forever, exulted in the seam<br>As long as I am with you, my heart continues to beat<br>With every step we take, Kyoto to The Bay<br>Strolling so casually </p></blockquote><h2 id="no.-4">NO. 4</h2><p>从这首开始,真的是这一年积累的真爱。</p><p>NO. 4 goes to <code>Shots(Broiler Remix) by Imagine Dragons and Broiler</code>.</p><iframe src="https://open.spotify.com/embed/track/4XLm8FNvaTlmTAZmSrrV82" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>有没有那么一刻,你想断绝身边所有的关系,整个世界都是陌生人。<br>有没有那么一刻,你想打碎身旁所有的珍藏,打碎你在这个世界存在过的一切证据。</p><blockquote><p>I'm sorry for everything oh everything I've done<br>From the second that I was born it seems I had a loaded gun<br>And then I shot shot shot a hole through everything I love<br>Oh I shot shot shot a hole through every single thing that I loved </p></blockquote><h2 id="no.-3">NO. 3</h2><p>NO. 3 goes to <code>I Took A Pill in Ibize(SeeB remix) by Mike Posner and SeeB</code>.</p><iframe src="https://open.spotify.com/embed/track/1MtUq6Wp1eQ8PC6BbPCj8P" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>有一天,我要在洛杉矶发票圈 <strong>I'm living out in LA. I drive a sports car just to prove...</strong></p><blockquote><p>I took a pill in Ibiza<br>To show Avicii I was cool<br>And when I finally got sober, felt 10 years older<br>But fuck it, it was something to do<br>I'm living out in LA<br>I drive a sports car just to prove<br>I'm a real big baller cause I made a million dollars<br>And I spend it on girls and shoes </p></blockquote><h2 id="no.-2">NO. 2</h2><p>NO. 2 goes to <code>Hero by Family of the Year</code>.</p><iframe src="https://open.spotify.com/embed/track/1B10XgaxSXRLAFq967oMpF" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>男主开着皮卡,离开家,穿过荒漠,这首歌随之响起。</p><p>母亲在家中抱怨没想到男主去上大学的这天这么高兴,而她却感觉自己的人生已经结束。结婚,生孩子,离婚,以为孩子有语言障碍,教孩子骑车,又离婚,拿到硕士学位,终于拿到想要的工作,送姐姐上大学,现在送男主上大学。接下来的是什么,"It's my fucking funeral!"</p><blockquote><p>Let me go<br>I don't wanna be your hero<br>I don't wanna be a big man<br>Just wanna fight with everyone else </p></blockquote><blockquote><p>Your masquerade<br>I don't wanna be a part of your parade<br>Everyone deserves a chance to<br>Walk with everyone else </p></blockquote><blockquote><p>While holding down<br>A job to keep my girl around<br>And maybe buy me some new strings<br>And her a night out on the weekends </p></blockquote><h2 id="no.-1">NO. 1</h2><p>NO. 1 goes to <code>Moonlight by Rameses B</code>.</p><iframe src="https://open.spotify.com/embed/track/5ZBemWNJFt6Ne1ay2vjVJD" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><blockquote><p>We go about our daily lives understanding almost nothing of the world.<br>We give little thought of the machinery that generates the sunlight and makes life possible.<br>To the gravity that glues us to the earth that would otherwise send us spinning off into space.<br>Or to the atoms that which we are made.. and on who's stability we fundamentally depend.<br>Few of us spend much time wondering why nature is the way it is.<br>Where the cosmos came from.<br>Whether it was always there.<br>If time will one day flow backward.<br>Or whether there are ultimate limits to what humans can know.<br>What is the smallest piece of matter.<br>Why we remember the past and not the future.<br>And why there is a universe. </p></blockquote><h1 id="non-awarding-but-still-amazing-melodies">Non-awarding but still amazing melodies</h1><p>Shower(D!avolo Remix) by D!avolo and Becky G</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=34999457&auto=0&height=66"></iframe><p>Bring Back the Summer by Raid Man and OLY </p><iframe src="https://open.spotify.com/embed/track/1FwqiPfW7VaE0fzwjl5Tf4" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>If by 丁可</p><iframe src="https://open.spotify.com/embed/track/2PHXKJM2WiRxoIcZGFIK5O" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>The Days by Bergs and Avicii</p><iframe src="https://open.spotify.com/embed/track/5Iy2Jj87Ha0C0IBlNE1I4y" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>The Moments We Shared ... I Miss Them by Jacoo</p><iframe src="https://open.spotify.com/embed/track/7cbR7P644hga3wumuhbwlW" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>Summertime by K-391</p><iframe src="https://open.spotify.com/embed/track/5Q1PaSUhNcFRjlwmbiC0Om" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>The Tower by 史逸欣</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=19639724&auto=0&height=66"></iframe><p>90 by Pompeya</p><iframe src="https://open.spotify.com/embed/track/37BBHdzggCsHnBhkiL0Yyd" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>Paralyse by Polarheart</p><iframe src="https://open.spotify.com/embed/track/6aDCSjGyjmYQWt0FCETQCc" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>Drive by Oh Wonder</p><iframe src="https://open.spotify.com/embed/track/2kJqNHHGOzLNahukdvlDWN" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>Turnin' by Young Rising Sons</p><iframe src="https://open.spotify.com/embed/track/7om9AA7nbASF0vzS2u5cDz" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>Wake by Hillsong Young and Free</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=27795934&auto=0&height=66"></iframe><p>Boom Clap by Charli XCX </p><iframe src="https://open.spotify.com/embed/track/7982sGZuvo5OXsY6REzgIn" width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe><p>2016 AMA 颁奖结束,明年见!</p>]]></content>
<summary type="html"><p>2016 AMA 今天终于要揭幕了。</p>
<p>But don&#39;t confuse it with the American Music Awards. Here it is <code>Andimeo&#39;s Music Awards</code> :)</p>
<h1 id="honor-award">Honor Award</h1><p>首先怀念一发Kesha,之前只听过Tik Toc和Your Love is My Drug等几首。</p>
<p>讲真并没有真的很喜欢钱妞,直到最近在Youtube上被推了这首Timber。</p>
<p>突然发现钱妞已经淡出视线快两年了,查了下才发现钱妞在2014年开始和自己的经纪人在打官司而且输了。因为输了官司在合同到期之前不仅不能签约别的
唱片公司,也不能公开演出Sony拥有版权的所有热单。</p></summary>
<category term="Music" scheme="https://andimeo.us.kg/categories/Music/"/>
<category term="心情" scheme="https://andimeo.us.kg/tags/%E5%BF%83%E6%83%85/"/>
<category term="总结" scheme="https://andimeo.us.kg/tags/%E6%80%BB%E7%BB%93/"/>
<category term="音乐" scheme="https://andimeo.us.kg/tags/%E9%9F%B3%E4%B9%90/"/>
</entry>
<entry>
<title>在Google Cloud上搭建VPN</title>
<link href="https://andimeo.us.kg/2016/12/25/%E5%9C%A8Google-Cloud%E4%B8%8A%E6%90%AD%E5%BB%BAVPN/"/>
<id>https://andimeo.us.kg/2016/12/25/%E5%9C%A8Google-Cloud%E4%B8%8A%E6%90%AD%E5%BB%BAVPN/</id>
<published>2016-12-25T07:34:20.000Z</published>
<updated>2024-09-07T07:25:33.406Z</updated>
<content type="html"><![CDATA[<p>之前在Google Cloud上申请过一个VPS,在Free trial期间使用了一个月,后来就关掉了。当时发现自己搭建VPN好容易啊。直到这次,想重新激活上次那个instance来做VPN的时候,VPN已链接,但是data plane的traffic却始终发送不出去。于是重新搜索了教程,才搞定这件事情。这里主要针对Google Cloud总结一下一些重要步骤。</p><p>关于StrongSwan的配置部分主要参考下面这篇文章:<a href="http://hjc.im/shi-yong-strongswanda-jian-ipsecikev2-vpn/">使用Strongswan搭建IPSec/IKEv2 VPN</a></p><h1 id="google-cloud配置">Google Cloud配置</h1><p>建立好Compute Engine后,申请一个永久静态地址,然后配置SSH登录证书等内容。这里不再赘述,Google自己的文档里已经交代的很清楚怎么做。</p><span id="more"></span><p>主要配置是在Google的Firewall rules里,这里决定了什么包可以访问到内部的instance,其后才是instance内部的iptables的配置。</p><p>这里我们建立一条新的rule,source filter填写<code>0.0.0.0/0</code>,代表任何地方发出的包,除非你只想在特定IP上访问,否则就填写这个。然后在Allowed protocols and ports里添加<code>udp:500;udp:4500;esp</code>。</p><ul><li>这里<code>udp:500</code>是<code>IKE</code>协议指定的端口,会通过这个端口来发送Control plane的包</li><li><code>udp:4500</code>是当source或者destination在<code>NAT</code>后时转而使用的端口,另外当<code>NAT</code>被检测到后,Data plane的数据包也会被使用端口,这个时候<code>ESP</code>的数据包会被包含在<code>UDP</code>包中,端口也是这个</li><li>最后<code>esp</code>就是允许Data plane的数据包通过</li></ul><p>另外需要勾选允许<code>ip forwarding</code>。</p><p>至此Google Cloud的配置就完成了。</p><h1 id="安装strongswan">安装StrongSwan</h1><p>这里假设使用的是Ubuntu作为操作系统,其他OS请参考相应教程。</p><h2 id="准备工作">准备工作</h2><p>安装PAM库和SSL库,以及make和gcc</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">apt-get update</span><br><span class="line">apt-get install libpam0g-dev libssl-dev make gcc</span><br></pre></td></tr></table></figure><h2 id="下载strongswan的源码并编译">下载StrongSwan的源码并编译</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">#下载</span><br><span class="line">wget http://download.strongswan.org/strongswan.tar.gz</span><br><span class="line">tar xzf strongswan.tar.gz</span><br><span class="line">cd strongswan-*</span><br><span class="line"></span><br><span class="line">#编译</span><br><span class="line">./configure --enable-eap-identity --enable-eap-md5 \</span><br><span class="line">--enable-eap-mschapv2 --enable-eap-tls --enable-eap-ttls --enable-eap-peap \</span><br><span class="line">--enable-eap-tnc --enable-eap-dynamic --enable-eap-radius --enable-xauth-eap \</span><br><span class="line">--enable-xauth-pam --enable-dhcp --enable-openssl --enable-addrblock --enable-unity \</span><br><span class="line">--enable-certexpire --enable-radattr --enable-tools --enable-openssl --disable-gmp --enable-kernel-libipsec</span><br><span class="line">make</span><br><span class="line"></span><br><span class="line">#安装</span><br><span class="line">make install # if not root please prepend sudo</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>完成后执行<code>ipsec version</code>查看是否安装成功。</p><h1 id="生成证书">生成证书</h1><h2 id="生成ca私钥">生成CA私钥</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipsec pki --gen --outform pem > ca.pem </span><br></pre></td></tr></table></figure><h2 id="利用私钥签名ca证书">利用私钥签名CA证书</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipsec pki --self --in ca.pem --dn "C=com, O=myvpn, CN=VPN CA" --ca --outform pem >ca.cert.pem</span><br></pre></td></tr></table></figure><h2 id="生成server端私钥">生成server端私钥</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipsec pki --gen --outform pem > server.pem</span><br></pre></td></tr></table></figure><h2 id="用ca证书签发server端证书">用CA证书签发server端证书</h2><p>这里需要将下面的地址更换为我们刚才申请到的永久IP地址。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">SERVER_ADDR="[REPLACE_WITH_YOUR_OWN_IP_ADDRESS]"</span><br><span class="line">ipsec pki --pub --in server.pem | ipsec pki --issue --cacert ca.cert.pem \</span><br><span class="line">--cakey ca.pem --dn "C=com, O=myvpn, CN=$SERVER_ADDR" \</span><br><span class="line">--san="$SERVER_ADDR" --flag serverAuth --flag ikeIntermediate \</span><br><span class="line">--outform pem > server.cert.pem</span><br></pre></td></tr></table></figure><h2 id="生成client端私钥">生成client端私钥</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipsec pki --gen --outform pem > client.pem</span><br></pre></td></tr></table></figure><h2 id="利用ca证书签发client端证书">利用CA证书签发client端证书</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipsec pki --pub --in client.pem | ipsec pki --issue --cacert ca.cert.pem --cakey ca.pem --dn "C=com, O=myvpn, CN=VPN Client" --outform pem > client.cert.pem</span><br></pre></td></tr></table></figure><h2 id="生成client端p12证书">生成client端p12证书</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl pkcs12 -export -inkey client.pem -in client.cert.pem -name "client" -certfile ca.cert.pem -caname "VPN CA" -out client.cert.p12</span><br></pre></td></tr></table></figure><h2 id="安装证书">安装证书</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># if not root probably you need to prepend sudo in front of the following commands</span><br><span class="line">cp -r ca.cert.pem /usr/local/etc/ipsec.d/cacerts/</span><br><span class="line">cp -r server.cert.pem /usr/local/etc/ipsec.d/certs/</span><br><span class="line">cp -r server.pem /usr/local/etc/ipsec.d/private/</span><br><span class="line">cp -r client.cert.pem /usr/local/etc/ipsec.d/certs/</span><br><span class="line">cp -r client.pem /usr/local/etc/ipsec.d/private/</span><br></pre></td></tr></table></figure><h1 id="配置strongswan">配置StrongSwan</h1><h2 id="配置ipsec.conf">配置ipsec.conf</h2><p>将<code>/usr/local/etc/ipsec.conf</code>替换为如下内容:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">config setup</span><br><span class="line"> uniqueids=never</span><br><span class="line"></span><br><span class="line">conn iOS_cert</span><br><span class="line"> keyexchange=ikev1</span><br><span class="line"> # strongswan version >= 5.0.2, compatible with iOS 6.0,6.0.1</span><br><span class="line"> fragmentation=yes</span><br><span class="line"> left=%defaultroute</span><br><span class="line"> leftauth=pubkey</span><br><span class="line"> leftsubnet=0.0.0.0/0</span><br><span class="line"> leftcert=server.cert.pem</span><br><span class="line"> right=%any</span><br><span class="line"> rightauth=pubkey</span><br><span class="line"> rightauth2=xauth</span><br><span class="line"> rightsourceip=10.31.2.0/24</span><br><span class="line"> rightcert=client.cert.pem</span><br><span class="line"> auto=add</span><br><span class="line"></span><br><span class="line">conn android_xauth_psk</span><br><span class="line"> keyexchange=ikev1</span><br><span class="line"> left=%defaultroute</span><br><span class="line"> leftauth=psk</span><br><span class="line"> leftsubnet=0.0.0.0/0</span><br><span class="line"> right=%any</span><br><span class="line"> rightauth=psk</span><br><span class="line"> rightauth2=xauth</span><br><span class="line"> rightsourceip=10.31.2.0/24</span><br><span class="line"> auto=add</span><br><span class="line"></span><br><span class="line">conn networkmanager-strongswan</span><br><span class="line"> keyexchange=ikev2</span><br><span class="line"> left=%defaultroute</span><br><span class="line"> leftauth=pubkey</span><br><span class="line"> leftsubnet=0.0.0.0/0</span><br><span class="line"> leftcert=server.cert.pem</span><br><span class="line"> right=%any</span><br><span class="line"> rightauth=pubkey</span><br><span class="line"> rightsourceip=10.31.2.0/24</span><br><span class="line"> rightcert=client.cert.pem</span><br><span class="line"> auto=add</span><br><span class="line"></span><br><span class="line">conn windows7</span><br><span class="line"> keyexchange=ikev2</span><br><span class="line"> ike=aes256-sha1-modp1024!</span><br><span class="line"> rekey=no</span><br><span class="line"> left=%defaultroute</span><br><span class="line"> leftauth=pubkey</span><br><span class="line"> leftsubnet=0.0.0.0/0</span><br><span class="line"> leftcert=server.cert.pem</span><br><span class="line"> right=%any</span><br><span class="line"> rightauth=eap-mschapv2</span><br><span class="line"> rightsourceip=10.31.2.0/24</span><br><span class="line"> rightsendcert=never</span><br><span class="line"> eap_identity=%any</span><br><span class="line"> auto=add</span><br></pre></td></tr></table></figure><h2 id="配置strongswan.conf">配置strongswan.conf</h2><p>将<code>/usr/local/etc/strongswan.conf</code>替换为如下内容:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">charon {</span><br><span class="line"> load_modular = yes</span><br><span class="line"> duplicheck.enable = no</span><br><span class="line"> compress = yes</span><br><span class="line"> plugins {</span><br><span class="line"> include strongswan.d/charon/*.conf</span><br><span class="line"> }</span><br><span class="line"> dns1 = 8.8.8.8</span><br><span class="line"> dns2 = 8.8.4.4</span><br><span class="line"> nbns1 = 8.8.8.8</span><br><span class="line"> nbns2 = 8.8.4.4</span><br><span class="line"> }</span><br><span class="line"> include strongswan.d/*.conf</span><br></pre></td></tr></table></figure><h2 id="配置ipsec.secrets">配置ipsec.secrets</h2><p>将<code>/usr/loca/etc/ipsec.secrets</code>内容替换为如下内容:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">: RSA server.pem</span><br><span class="line">: PSK "mykey"</span><br><span class="line">: XAUTH "mykey"</span><br><span class="line">[用户名] %any : EAP "[密码]"</span><br></pre></td></tr></table></figure><p>注意将PSK、XAUTH处的"mykey"编辑为唯一且私密的字符串,并且将[用户名]改为自己想要的登录名,[密码]改为自己想要的密码([]符号去掉),可以添加多行,得到多个用户。</p><h1 id="配置iptables">配置iptables</h1><h2 id="修改sysctrl.conf">修改sysctrl.conf</h2><p>打开<code>/etc/sysctl.conf</code>,然后uncomment包含<code>net.ipv4.ip_forward=1</code>的这一行。</p><p>保存后,执行<code>sysctl -p</code>。</p><h2 id="修改iptables">修改iptables</h2><p>将<code>INF</code>替换为自己的网络接口.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">INF="Your own network interface"</span><br><span class="line">iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT</span><br><span class="line">iptables -A FORWARD -s 10.31.0.0/24 -j ACCEPT</span><br><span class="line">iptables -A FORWARD -s 10.31.1.0/24 -j ACCEPT</span><br><span class="line">iptables -A FORWARD -s 10.31.2.0/24 -j ACCEPT</span><br><span class="line">iptables -A INPUT -i $INF -p esp -j ACCEPT</span><br><span class="line">iptables -A INPUT -i $INF -p udp --dport 500 -j ACCEPT</span><br><span class="line">iptables -A INPUT -i $INF -p tcp --dport 500 -j ACCEPT</span><br><span class="line">iptables -A INPUT -i $INF -p udp --dport 4500 -j ACCEPT</span><br><span class="line"># for l2tp</span><br><span class="line">iptables -A INPUT -i $INF -p udp --dport 1701 -j ACCEPT</span><br><span class="line"># for pptp</span><br><span class="line">iptables -A INPUT -i $INF -p tcp --dport 1723 -j ACCEPT</span><br><span class="line">iptables -A FORWARD -j REJECT</span><br><span class="line">iptables -t nat -A POSTROUTING -s 10.31.2.0/24 -o $INF -j MASQUERADE</span><br><span class="line">iptables -t nat -A POSTROUTING -s 10.31.2.1/24 -o $INF -j MASQUERADE</span><br><span class="line">iptables -t nat -A POSTROUTING -s 10.31.2.2/24 -o $INF -j MASQUERADE</span><br></pre></td></tr></table></figure><h2 id="保存iptables且开机自动启动">保存iptables且开机自动启动</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">iptables-save > /etc/iptables.rules</span><br><span class="line">cat > /etc/network/if-up.d/iptables<<EOF</span><br><span class="line">#!/bin/sh</span><br><span class="line">iptables-restore < /etc/iptables.rules</span><br><span class="line">EOF</span><br><span class="line">chmod +x /etc/network/if-up.d/iptables</span><br></pre></td></tr></table></figure><h2 id="重启ipsec服务">重启ipsec服务</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipsec restart</span><br></pre></td></tr></table></figure><p>至此大功告成。</p><p>WP8.1手机安装<code>ca.cert.pem</code>,进入设置<code>VPN</code>添加<code>IKEv2</code>连接,地址为证书中的地址或IP,通过用户名-密码连接。Windows连接也是一样,但注意将证书导入本地计算机而不是当前用户的“受信任的证书颁发机构”。iOS/Android/Mac OS X设备添加<code>Cisco IPSec PSK</code>验证方式,预共享密钥是<code>/usr/local/etc/ipsec.secrets</code>中PSK后的字符串(不含引号),用户名密码同上,可以通过任意域名或IP连接,不需要证书.</p><p>接下来有时间我会试图把整个安装过程配置为一个Docker Build file,这样以后配置新的instance就跟方便了。</p>]]></content>
<summary type="html"><p>之前在Google Cloud上申请过一个VPS,在Free trial期间使用了一个月,后来就关掉了。当时发现自己搭建VPN好容易啊。
直到这次,想重新激活上次那个instance来做VPN的时候,VPN已链接,但是data plane的traffic却始终发送不出去。
于是重新搜索了教程,才搞定这件事情。这里主要针对Google Cloud总结一下一些重要步骤。</p>
<p>关于StrongSwan的配置部分主要参考下面这篇文章:
<a href="http://hjc.im/shi-yong-strongswanda-jian-ipsecikev2-vpn/">使用Strongswan搭建IPSec/IKEv2 VPN</a></p>
<h1 id="google-cloud配置">Google Cloud配置</h1><p>建立好Compute Engine后,申请一个永久静态地址,然后配置SSH登录证书等内容。
这里不再赘述,Google自己的文档里已经交代的很清楚怎么做。</p></summary>
<category term="VPS" scheme="https://andimeo.us.kg/categories/VPS/"/>
<category term="VPN" scheme="https://andimeo.us.kg/tags/VPN/"/>
<category term="Google Cloud" scheme="https://andimeo.us.kg/tags/Google-Cloud/"/>
<category term="IPsec" scheme="https://andimeo.us.kg/tags/IPsec/"/>
<category term="StrongSwan" scheme="https://andimeo.us.kg/tags/StrongSwan/"/>
</entry>
<entry>
<title>美国之行</title>
<link href="https://andimeo.us.kg/2016/09/04/%E7%BE%8E%E5%9B%BD%E4%B9%8B%E8%A1%8C/"/>
<id>https://andimeo.us.kg/2016/09/04/%E7%BE%8E%E5%9B%BD%E4%B9%8B%E8%A1%8C/</id>
<published>2016-09-04T08:29:20.000Z</published>
<updated>2024-09-07T07:27:42.163Z</updated>
<content type="html"><![CDATA[<p>美国之行已经结束快2个月了,今天才来补上这篇游记。</p><h2 id="抵达">抵达</h2><p>加入Luminate三个月,获得了一次出行美国的机会。与两个同伴一起从北京机场出发,经过约12小时的飞行,到达地球的另一端SFO。算是完成了一直以来的一个梦想。</p><p>到达SFO时第一次经过海关,看着荷枪实弹的警察,还是有一种震撼,一个完全不一样的国度。入境检查的海关工作人员,不那么友好,不顾我Good Afternoon的问候,直接又简单的问了三个问题就让我通过了,一直都面无表情。也许不管哪里的公务员都时这样吧。</p><span id="more"></span><p>在SFO租到车,老司机在Google Map的指引下,载着我们经过约40分钟到达了目的地Cupertino,在公司取了钥匙就回了公司为我们租的公寓。</p><p><img src="/img/Hampton.jpg" alt="Hampton Cupertino California"></p><p>据说Hampton是Cupertino租住最贵的小区。Cupertino是苹果的大本营,而我们小区的大门就正对着Apple Campus,可惜现在仍处于施工阶段。</p><p><img src="/img/IMG_0813.jpg" alt="Apple Campus"></p><p>第一天晚上在小区里停车,由于忘记向公司HR要停车位的信息,在小区里转了很久后,终于发现一个unoccupied的车位,好像也没有编号,我们就停在了那里。结果第二天竟然被善良的美帝人民贴条了😳。</p><p><img src="/img/IMG_0784.jpg" alt="Ticket"></p><p>第二天发现条子的时候,才发现车位入口的横梁上贴着一个号😂。这家还有一个现在也不知道得了什么病的孩子和一个阿尔茨海默症的老人。。。</p><h2 id="las-vegas">Las Vegas</h2><p>第一周准备组内的一个presentation,浪费了一个周六,最后结果还跟💩一样,心痛啊。不过接下来的周末,连上美国的独立日假期,三天罪恶之城之旅开启。</p><p><img src="/img/IMG_0884.jpg" alt="Overlook of Las Vegas"></p><p>一下飞机,迎面就一股赌城的味道。</p><p><img src="/img/IMG_0880.jpg" alt="Airport at LV."></p><p>没错机场里就摆满了各式的赌博机。</p><p>我们提前定了一个四星的酒店,五星的好贵,星级再低的又害怕不安全。不过好在我们三个要定两个房,而单独住一间房的机会由抢红包决定,这次人品爆发,两次我都抢的最少,于是两晚我都一个人住,咩哈哈。我们酒店的名字也是够文艺的,叫Renaissance。</p><p><img src="/img/IMG_0886.jpg" alt="Renaissance Hotel"></p><h3 id="赌场">赌场</h3><p>来Vegas不赌两把实在是不能算来了。这次来Vegas的主要活动是看两个show,这两个show的地点都在MGM Grand,所以我们大部分赌博的时间也都放在了MGM Grand地下的赌场里。据说这里的赌场地下都是连通的,从Caesar Palace到New York! New York!,再到MGM Grand。</p><p><img src="/img/IMG_0906.jpg" alt="Casino"></p><p>这里的赌博机虽多,但大部分都是21点,百家乐,还有种压奇偶或者大小的游戏。除此之外还有很多老虎机,然而我们完全不知道玩法,看着旁边的老头激动的大跳大叫,我们一脸懵逼。。。</p><p><img src="/img/IMG_0938.jpg" alt="Casino"></p><hr><p><img src="/img/IMG_0947.jpg" alt="Casino Board"></p><p>下面该晒出赢钱的图片了,哈哈</p><p><img src="/img/IMG_0899.jpg" alt="Win"></p><p>赢了一分钱。。。</p><p><img src="/img/IMG_0927.jpg" alt="One cent"></p><p>赢了22.6刀</p><p>第一天完美收官,赢了20多刀,哈哈哈</p><p>然而第二天玩老虎机输了80刀,衰!</p><h3 id="街景">街景</h3><p>Vegas处于沙漠之中,比起西海岸的湾区确实热了不少,街边种满了棕榈树,街上充满了各色人种,黑人、白人、拉丁裔、亚裔都有。</p><p>先上我们呆的时间最长的MGM Grand的图。<img src="/img/IMG_0929.jpg" alt="MGM Grand"></p><p>酒店里看到的Vegas全景。<img src="/img/IMG_0928.jpg" alt="Vegas Full View"></p><p>下面是Vegas的街景。</p><p><img src="/img/DSC_0052.jpg" alt="Street View1"></p><hr><p><img src="/img/DSC_0027.jpg" alt="Street View2"></p><hr><p><img src="/img/DSC_0070.jpg" alt="Street View3"></p><p>恰巧赶上国庆节,Vegas也是熙熙攘攘,人头攒动<img src="/img/DSC_0075.jpg" alt="Street View4"></p><hr><p><img src="/img/DSC_0077.jpg" alt="Coke Cola"></p><p>看到上面这张图,不由得要强调,要可乐要说coke,不要说cola。。。</p><h3 id="show">Show</h3><p>Vegas的支柱产业是赌博业和色情业,但是来Vegas如果不看两场大show,那也算是白来了。这里闻名全球的有太阳马戏团,其中以KA和O最为出名。KA show其实很早就和公众见面了,但是由于几年前表演人员不幸发生演出事故,才导致接下来的几年这场show没有再演,直到去年才开始又登上舞台。O show以水闻名,融合科技的力量,在舞台上带给你水下的体验。除此之外,Vegas还有很多曾经大牌的歌星会在淡出一线后来Vegas捞金,我在的那几天就有席琳迪翁和布兰妮的show,不过我对她们都不感冒,所以也没有去。</p><p>另外各种成人show,脱衣舞秀,甚至更加大胆的表演,都是这里的特色。这次我们选择了观看太阳马戏团的KA还有大卫科波菲尔的一场show。</p><p>太阳马戏团的剧场是为show特意定制的,不同的show在不同的剧场,下面这个是KA的剧场。</p><p><img src="/img/IMG_0921.jpg" alt="KA Theater"></p><hr><p><img src="/img/IMG_0925.jpg" alt="KA Thearter2"></p><p>科技与艺术的完美结合,全场只有开篇时的几句交代,其余便全靠表演了。</p><p><img src="/img/IMG_0930.jpg" alt="David Copperfield"></p><p>David Copperfield的show我还是有点失望的,炒冷饭,卖情怀。不过一天4场的演出,为了捞金也是拼了。</p><p><img src="/img/IMG_0941.jpg" alt="David Copperfield"></p><h3 id="夜景">夜景</h3><p>罪恶之城当然要等到太阳落山后才能真正领略到它的味道。</p><p><img src="/img/DSC_0091.jpg" alt="Vegas1"></p><hr><p><img src="/img/DSC_0098.jpg" alt="Vegas2"></p><hr><p><img src="/img/DSC_0101.jpg" alt="Vegas3"></p><hr><p><img src="/img/DSC_0107.jpg" alt="Vegas4"></p><hr><p><img src="/img/DSC_0110.jpg" alt="Vegas5"></p><hr><p><img src="/img/DSC_0127.jpg" alt="Vegas6"></p><p>街边上的皮条客,也不知道操着哪国的口音,再向我们兜售色情服务,虽然听不太清,naked girls什么的还是让我们明白他们是干什么的。</p><p><img src="/img/DSC_0129.jpg" alt="Vegas7"></p><p>Vegas的广场舞😄<img src="/img/DSC_0134.jpg" alt="Street Dance"></p><h3 id="caesar-palace">Caesar Palace</h3><p>凯撒宫下面传说有着全球最好吃的自助餐,一人60刀的价格。其中还碰到一个黑人大哥再给人切牛排,我们排队到了后,这个大哥竟然跟我的同伴说你要瘦肉?哈哈。在我们的询问之下,他说他会说一点中文,去过中文。他会说瘦肉,牛肉,一点点,谢谢。看来学外语对谁都很难啊,我们在这个时候不说瘦肉,说猪肉的啊,哥们!这哥们心宽体胖,比起海关的工作人员,开心多了,一直咧着嘴漏出自己闪亮的白牙😁</p><p><img src="/img/IMG_0939.jpg" alt="Caesar Palace"></p><hr><p><img src="/img/IMG_0936.jpg" alt="Bacchanai"></p><hr><p><img src="/img/IMG_0932.jpg" alt="pickle"></p><p>果然我们还是战斗力差,那哥们已经什么都按照我们的要求给了一点点,还是吃不完。<img src="/img/IMG_0934.jpg" alt="Buffet"></p><p>最后中国餐馆乱入<img src="/img/IMG_0937.jpg" alt="Chinese Restaurant"></p><h2 id="旧金山">旧金山</h2><p>第三天一早从Vegas飞回旧金山,短短的几个小时,领略了这座海滨城市。</p><p>雾气缭绕中的金门大桥</p><p><img src="/img/DSC_0204.jpg" alt="Golden Bridge1"></p><hr><p><img src="/img/DSC_0241.jpg" alt="Golden Bridge"></p><p>恶魔岛。没错,就是勇闯夺命岛那个地方<img src="/img/DSC_0250.jpg" alt="Devil Island"></p><p>旧金山和青岛给我的感觉很像,都是海滨城市,道路都非常陡峭,30度的坡比比皆是,在上坡尽头的红灯处,还是很考验司机的水平和心智。</p><p><img src="/img/DSC_0299.jpg" alt="House"></p><hr><p><img src="/img/DSC_0320.jpg" alt="Street SF"></p><hr><p><img src="/img/DSC_0336.jpg" alt="Street SF2"></p><p>收废品的大爷<img src="/img/DSC_0317.jpg" alt="Old man"></p><p>本次旅途中最满意的作品</p><p><img src="/img/DSC_0329.jpg" alt="Best"></p><h2 id="stanford">Stanford</h2><p>最后临走之前,去了stanford和Sand Hill Road,膜拜了一把。</p><p><img src="/img/DSC_0365.jpg" alt="stanford"></p><hr><p><img src="/img/DSC_0366.jpg" alt="stanford2"></p><hr><p><img src="/img/DSC_0369.jpg" alt="stanford3"></p><hr><p><img src="/img/DSC_0401.jpg" alt="Stanford4"></p><p>比尔盖茨捐的楼<img src="/img/DSC_0414.jpg" alt="Gates Buiding"></p><hr><p><img src="/img/DSC_0429.jpg" alt="stanford5"></p><p>Tesla乱入</p><p><img src="/img/DSC_0447.jpg" alt="Tesla"></p><p>沙丘路路标</p><p><img src="/img/DSC_0482.jpg" alt="Sand Hill Road"></p><p>KPCB<img src="/img/DSC_0462.jpg" alt="KPCB"></p><hr><p><img src="/img/DSC_0471.jpg" alt="KPCB2"></p><p>SEQUOIA</p><p><img src="/img/DSC_0494.jpg" alt="sequoia"></p><p>松鼠乱入<img src="/img/DSC_0508.jpg" alt="squirrel"></p><h2 id="结语">结语</h2><p>美国之行结束,哥还会回来的!</p>]]></content>
<summary type="html"><p>美国之行已经结束快2个月了,今天才来补上这篇游记。</p>
<h2 id="抵达">抵达</h2><p>加入Luminate三个月,获得了一次出行美国的机会。与两个同伴一起从北京机场出发,经过约12小时的飞行,到达地球的另一端SFO。算是完成了一直以来的一个梦想。</p>
<p>到达SFO时第一次经过海关,看着荷枪实弹的警察,还是有一种震撼,一个完全不一样的国度。入境检查的海关工作人员,不那么友好,不顾我Good Afternoon的问候,直接又简单的问了三个问题就让我通过了,一直都面无表情。也许不管哪里的公务员都时这样吧。</p></summary>
<category term="游记" scheme="https://andimeo.us.kg/categories/%E6%B8%B8%E8%AE%B0/"/>
<category term="US" scheme="https://andimeo.us.kg/tags/US/"/>
<category term="Las Vegas" scheme="https://andimeo.us.kg/tags/Las-Vegas/"/>
<category term="Cupertino" scheme="https://andimeo.us.kg/tags/Cupertino/"/>
<category term="California" scheme="https://andimeo.us.kg/tags/California/"/>
<category term="Bay area" scheme="https://andimeo.us.kg/tags/Bay-area/"/>
</entry>
<entry>
<title>Certificate</title>
<link href="https://andimeo.us.kg/2016/06/11/Certificate/"/>
<id>https://andimeo.us.kg/2016/06/11/Certificate/</id>
<published>2016-06-11T10:17:53.000Z</published>
<updated>2024-09-07T07:25:33.407Z</updated>
<content type="html"><![CDATA[<p>在Luminate里接手的第一个项目,是做一个IPsec的客户端,来测试已有的IPsec gateway。在这个项目中,已经快把<a href="https://tools.ietf.org/html/rfc7296">RFC7296</a>翻烂了,在这中间学习了C++,学习了emacs。接下来要做的支持在IKE negotiation中的Certificate Payload,之前对证书等加密的概念都是一知半解,今天发现一篇<a href="https://support.ssl.com/Knowledgebase/Article/View/19/0/der-vs-crt-vs-cer-vs-pem-certificates-and-how-to-convert-them">文章</a>阐述了各种证书格式的异同,翻译于此。</p><h1 id="证书与编码">证书与编码</h1><p>X.509本质上是按照RFC 5280编码或签名的一个数字文件。</p><p>实际上,X.509这个词是指X.509 v3标准的IETF’s PKIX Certificate and CRL Profile,在RFC 5280中说明,通常被称作PKIX for Public Key Infrastructure (X.509)</p><span id="more"></span><h1 id="x509-文件扩展名">X509 文件扩展名</h1><p>首先我们要弄明白每一种扩展名对应的是哪种类型。DER、PEM、CRT和CER这些扩展名给我们带来了很多困惑,很多人都错误的认为这些可以随意使用,彼此等价。然而只有在某些情况下,其中的一些才可以交换,而最好的办法就是知道你的证书是什么编码,然后用合适的扩展名标记它。正确命名的证书更容易使用。</p><h2 id="编码(扩展名)">编码(扩展名)</h2><ul><li><code>.DER</code> = 扩展名DER用于按照DER格式编码的二进制证书。这些文件也可以被命名为CER或CRT。正确的说法是“我有一个DER编码的证书”,而不是“我有一个DER证书”。</li><li><code>.PEM</code> = 扩展名PEM用于不同类型的X.509 v3文件。这些证书包含ASCII(Base64)的数据,以“--BEGIN ...” 开头。</li></ul><h2 id="通用扩展">通用扩展</h2><ul><li><code>.CRT</code> = 扩展名CRT用于证书。证书的内容可能是DER格式的二进制数据,也可能是ASCII的PEM数据。CER和CRT几乎是一样的意思,而且在*nix系统中最常见。</li><li><code>.CER</code> = 扩展名CER是CRT的另一个形式,是微软的习惯。你可以用MS把CRT转换为CER,不论是DER格式还是PEM格式。.CER扩展名的文件可以被IE识别为一个执行微软cryptoAPI的命令,IE会弹出一个对话框供你导入或查看证书。</li><li><code>.KEY</code> = 扩展名KEY同时用于PKCS#8的公钥和私钥。这些公钥私钥既可能是DER格式的,也可能是PEM格式的。</li></ul><h1 id="openssl的证书操作">OpenSSL的证书操作</h1><p>一般而言有四种类型的证书操作:查看,转换,组合和提取。</p><h2 id="查看">查看</h2><p>即使PEM文件是ASCII的,他们仍然不是供人阅读的。下面是一些让你以人类可读的方式输出证书内容的一些命令。</p><h3 id="查看pem格式证书">查看PEM格式证书</h3><p>将下面命令中的证书替换为你自己的对应扩展名的证书:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">openssl x509 -in cert.pem -text -noout</span><br><span class="line">openssl x509 -in cert.cer -text -noout</span><br><span class="line">openssl x509 -in cert.crt -text -noout</span><br></pre></td></tr></table></figure><p>如果你碰到下面的错误,那以为着你在试图查看一个DER格式的证书,DER格式的证书在下面一节说明。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">unable to load certificate</span><br><span class="line">12626:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:647:Expecting: TRUSTED CERTIFICATE</span><br></pre></td></tr></table></figure><h3 id="查看der格式证书">查看DER格式证书</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl x509 -in certificate.der -inform der -text -noout</span><br></pre></td></tr></table></figure><p>如果你碰到下面的错误,那以为着你在试图查看一个PEM格式的证书,DER格式的证书在下面一节说明。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">unable to load certificate</span><br><span class="line">13978:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1306:</span><br><span class="line">13978:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:tasn_dec.c:380:Type=X509</span><br></pre></td></tr></table></figure><h2 id="转换">转换</h2><p>转换可以把一种编码格式的证书变为另一种格式的编码,如把PEM格式变为DER格式。</p><h3 id="pem-to-der">PEM to DER</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl x509 -in cert.crt -outform der -out cert.der</span><br></pre></td></tr></table></figure><h3 id="der-to-pem">DER to PEM</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl x509 -in cert.crt -inform der -outform pem -out cert.pem</span><br></pre></td></tr></table></figure><h2 id="组合">组合</h2><p>在某些情况下,我们需要把多个X509的片段放到一个证书文件中。一个例子就是我们需要把公钥和私钥放在同一个证书中。</p><p>最简单的办法是把多个证书转换为PEM格式,然后复制到一个新的文件中。Apache就会需要这种格式的证书。</p><h2 id="提取">提取</h2><p>一些证书是上节提到的组合后的格式。一个文件可能包含证书、私钥、公钥、签名、CA或Authority Chain。</p>]]></content>
<summary type="html"><p>在Luminate里接手的第一个项目,是做一个IPsec的客户端,来测试已有的IPsec gateway。在这个项目中,已经快把<a href="https://tools.ietf.org/html/rfc7296">RFC7296</a>翻烂了,在这中间学习了C++,学习了emacs。接下来要做的支持在IKE negotiation中的Certificate Payload,之前对证书等加密的概念都是一知半解,今天发现一篇<a href="https://support.ssl.com/Knowledgebase/Article/View/19/0/der-vs-crt-vs-cer-vs-pem-certificates-and-how-to-convert-them">文章</a>阐述了各种证书格式的异同,翻译于此。</p>
<h1 id="证书与编码">证书与编码</h1><p>X.509本质上是按照RFC 5280编码或签名的一个数字文件。</p>
<p>实际上,X.509这个词是指X.509 v3标准的IETF’s PKIX Certificate and CRL Profile,在RFC 5280中说明,通常被称作PKIX for Public Key Infrastructure (X.509)</p></summary>
<category term="密码学" scheme="https://andimeo.us.kg/categories/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
<category term="Certificate" scheme="https://andimeo.us.kg/tags/Certificate/"/>
<category term="DER" scheme="https://andimeo.us.kg/tags/DER/"/>
<category term="PEM" scheme="https://andimeo.us.kg/tags/PEM/"/>
<category term="OpenSSL" scheme="https://andimeo.us.kg/tags/OpenSSL/"/>
</entry>
<entry>
<title>从Tango到Luminate</title>
<link href="https://andimeo.us.kg/2016/06/11/%E4%BB%8ETango%E5%88%B0Luminate/"/>
<id>https://andimeo.us.kg/2016/06/11/%E4%BB%8ETango%E5%88%B0Luminate/</id>
<published>2016-06-11T07:29:02.000Z</published>
<updated>2024-09-07T07:25:33.408Z</updated>
<content type="html"><![CDATA[<p>写Blog这件事总是时断时续,距离上一篇Blog已经过去半年了。最近godaddy又开始催收域名费用了,大概120人民币,奇怪的是一年前觉得这个贵到不可接受,现在却觉得略便宜。人就是这样在潜移默化中一点点改变的,消费观、人生观乃至世界观。</p><p>加入Luminate Wireless已经快两个月了,以为会像离开MicroStrategy时一样写一篇吐槽的帖子,再展望一下未来,然而并没有。如同我姐上次在微信中问我新公司怎么样,我说还不错啊,能学到不少新东西。她说你在上个公司不也是这么说么。想了想的确如此,呵呵。在Tango这一年零两个月的时间里,确实看到了不少东西,也学到了不少东西,认识了不少的牛人。也对在这个圈子里怎么找到自己的安身立命之本有一点点顿悟。有人网络知识精通,只用Linux自身的工具就可以创造自己的协议,游刃于UDP与IPsec之间。有人熟习Git各种使用场景,设计模式信手拈来。有人博学多闻,轻松可以出任一个规模略小公司的CTO。</p><span id="more"></span><h1 id="tango">Tango</h1><p>话既然说到了这里,还是总结一下自己在Tango的收获吧。</p><h2 id="git">Git</h2><p>对Git的理解更深一步,借此对Continue Integration也有了认识。以至于现在对svn,perforce等centralized的版本控制工具都嗤之以鼻。凡是碰到有现有工具解决不了的问题,回头想想Git,总能找到一个比较好的解决方法。列举一二心得于此:</p><ul><li>在开发中,当我们提交了一个change后,常常需要等待peer review。这个过程在跨时区的影响下会变得令人难以忍受。因为接下来的工作往往会依赖于已经提交的change。这如果在Git的环境中,我们可以发布一个pull request之后,继续在新的branch上进行自己的工作。如果上游通过最好,我们直接删除本地的那个分支即可。如若不通过,则更改后提交,同时本地进行新的开发,当然merge还是需要你来做,不过只是本地两个分支的merge,会轻松很多,也不会影响其他人。如若新的branch上的开发已经结束,可以直接追加到原油的pull request上。因为commit级别的分离在本地已经完成,一个pull request中包含了很多commits,所以并不会导致会有一个超大的change list被提交。而svn,perforce这样的工具无法很好的解决问题。</li></ul><hr><ul><li>假设我现在提交了一个change,对现有主干逻辑进行refactor。但是review之后,发现需要大改。然而这个改动会影响很多人现有的工作。你如果无法尽快完成改变,review通过,提交。你滞后的每一分钟都可能给你的合作者将来发布change时带去硕大的麻烦。因为他们是在现有的架构上开发的,提交时需要首先sync远端。这就导致他们的改动越多,到时候需要merge的地方就越多。这时往往会选择先暂时不提交这个change,等大家的工作完成后,融合到现有的架构中。然后自己再去做refactor。这对于git来说,只要把这次的refactor至于本地的分支中即可。而对于svn和perforce,如果你不想放弃这次的改动,那么需要重新下载整个代码库到另一个地方,才能继续工作。It sucks! 总结一下就是,Local branch and commits in the git world rocks! 在Git的学习道路上必须要感谢Jacky! You are an awesome engineer!</li></ul><h2 id="设计模式">设计模式</h2><p>在Tango重构了一个同事写的商品爬虫,说是重构其实是重写了。不知道怎么回事,每一次重构对我而言,都会演变成一次重写。也许是因为只有需要改变的东西如此critical,我才会动重构的念想。而对于一般的重构,我只会认为是一个瑕疵,一个不足,只是一次改进。</p><p>这次重构,几乎成了这次找工作时面对每个公司我都必说的case,也几乎是在tango做的唯一能拿得出的工作。除了这件事,在tango的日常开发,无非是一些API的开发,测试,我忘的比我写的还要快。</p><p>这次重构主要解决一个问题,之前那哥们写的代码实在过于猛糙快。里面有过多的if-else,这样的结果是经常会看到数百行的函数。并且需要添加一个新的partner时,从入口到出口的逻辑需要从头改到尾,因为这些判断都是通过if-else来决定的。OO世界中,尤其是Java中的继承与多态哭了。</p><p>所以我的策略是将主流程抽象出来定义为一个模块,叫做spider-framework。里面并不涉及具体的partner,并且开放一些虚接口供子类实现,来满足不同实现的特殊需求。这个东西后来在看设计模式的书时,发现应该是所谓的策略模式。</p><p>这次看了《Head First Design Patterns》这本书后,又想起之前看到的别人在这个主题上的评论。那个人说其实设计模式人人都在用,只是很多人根本不知道那个名字。Agree!跟Luminate的一个同事也讨论过这个问题,我的看法是,设计模式并不值得学习,但是应该了解一下。两个特点导致设计模式并不值得学习:</p><ul><li>设计模式中的术语并不具有明确的指示意义。也许有人期望谈到某个模式的时候,大家明白他在说什么,然而在一个特定的环境下,讲真,说明白你的人他的想法一定与你不同,或大或小。在这点上设计模式并达不到这个作用,尤其是一些复杂的模式。</li><li>设计模式并不值得死记硬背,看设计模式的书也可以看到,同样的做法在不同的情境下,叫的名字就会不一样。因为设计模式的命名不都是按照结构,很大一部分是按照目的命名的。如Decorator和Proxy完全可以有一样的实现,但是你的着眼点不同,你就可以叫他不同的名字。</li></ul><h2 id="技术之外">技术之外</h2><p>在Tango经历了yixin的离职,Yue的上任,Uri回以色列弄孙为乐,shop的失败。对于选择,也有了一些与之前不同的感受。Yixin离职时和我one-on-one的时候说,他在Tango已经呆了6年多,他的付出也得到了公司的反馈,北京的site manager,公司级别的VP,他还能怎样呢,总不能在Tango呆一辈子吧。在Yixin在Tango最后的日子里,开会时他总会和我们分享一些资本市场的态度,business insider几乎成了每次会议上slides里必有的内容。他说他去Baihe也是希望在资本层面学习一些新的东西,看看资本究竟是如何影响技术世界的,资本世界究竟是如何看待这个世界,选择一种hard模式,继续升级!对于yixin,我的评价是,True Leader,not boss!Best wishes!</p><p>在我们得到公司结束的消息后,开始找工作的日子里,大家也会各有不同的想法。我认为有趣的事情,在别人眼里也许并不值得一提,各种思想会有碰撞,也蛮有趣的。有人去创业,有人去大公司,有人继续去创业公司,还有人去传统行业。Tango的群里在这一年多的时间里,几乎只有红包可以引出人,但是这一个多月里,非常的活跃。似乎分别的时刻有些人之间的交流比一年内说的所有话还要多。大家这时似乎才承认当初HR跟我们说的Tango像是一家人的感觉。</p><h1 id="luminate-wireless">Luminate Wireless</h1><p>不想再说为什么会加入Luminate了,无非也就是家庭、薪水、未来等等。谈谈这接近两个月的一些感悟吧。Kevin也说过Luminate是一个老年人成立的公司,后来我才发现我加入公司的平均年龄已经越来越大了。MicroStrategy里面毕业不久的新瓜蛋子占了绝大多数,Tango里平均年龄大概已经超过了30岁,而Luminate,40应该止不住,哈哈。</p><p>在这一个多月里,从Java转到了C++,虽然还是很多东西不懂,但是已经不影响正常的工作了。了解了更多后,反倒对C++有了偏见。我觉得Java有Java的好处,C也有也的方便。然后C++放在中间反倒容易使人掣肘。</p><ul><li>C可以重新解释内存,<code>reinterpret_cast</code>的好处不用多说,这在Java的世界中不可想象</li><li>Java的所有对象都是引用计数,传值和返回都是引用,在函数中所有对象几乎都在堆里,在栈里只能看到基础类型的身影,这得益于GC。起初觉得这理所当然,回到C++才发现这是多么难得且珍贵的特性</li><li>C++中函数里声明的对象,返回时要格外小心,因为内存的ownership随时可能不吻合你的直觉。而且能在栈里分配的内存绝不要在堆里分配,这是C++的best practice。因为C++没有GC,过多小的内存申请与回收,会导致内存碎片问题。</li></ul><p>在Luminate还学到了一个很好的玩意儿<code>emacs</code>,在Java的世界中,eclipse太优秀了,以至于没有别的东西可以撼动,也没有给vim、emacs、sublime和Atom任何机会。但是在C++的世界中,情况相反,除了win下的VS,没有好用的IDE。所以这个问题每个人都会有自己的选择,我一开始想打造自己的vim环境来完成这个任务,后来发现即使用clang的YouCompleteMe,vim也会被托的很慢,和eclipse无法同日而语。其实emacs也无法很好的完成这些任务,但是在emacs下,会发现有些需求其实是不需要的。比如自动补全,其实不需要那么复杂的基于语义的补全,emacs对buffer中基于tag的补全足够好用了。另外emacs中内嵌可以执行各种命令,更是一个神器,几乎所有事情你都可以不离开emacs,就可以完成。gdb调试,bazel编译,都可以。据说emacs海可以煮咖啡,哈哈!虽说如此,emacs仍然不是完美的,在实现和定义之间的跳转,查看一个变量的call hirarchy等,在emacs中仍然无法满足。以至于我在更改我不熟悉的别人的模块时,还是会去eclipse下操作。但是对于从头写的东西,emacs就太爽了。尤其结合公司内部已有的一些YASnippets,可以避免很多boiler plate。现在的这篇博客就是在emacs中写的,markdown-mode。</p><hr><p>Luminate来到北京,据说是因为硅谷超不到合适的人。因为在硅谷的用人环境与国内恰好相反。硅谷的大公司如Google和Facebook之流给人才的薪水,是创业公司望尘莫及的,尤其像Luminate这种融资不过数千万美元的公司。而在国内,创业公司更舍得给钱,希望招到更优秀的人来restructure the market。国内的大公司占据了垄断地位,就开始剥削人才,强调loyalty了。Ugly!</p><p>这边有Google出来的软件工程师,Cisco出来的网络工程师,也有传统通信行业出来的。与他们的工作中,发现互联网行业的公司更强调Coding Style,Review Process,写出漂亮的代码,而不只是能工作的代码。而传统行业的人,在设计上的sense真的只有cs大二大三的水平。他们也懂很多,说的时候可以头头是道,但是在做的上面,则不尽如人意。</p><p>Ming经常会不抑制的发脾气,他们也会接受,再修改,但难得要领。甚至怪罪于,Ming将自己个人的preference强加于他们之上。是的,这些东西总是可以用taste来解释,但是好的taste会产出好的效果。就好像是内力,无形却有力。</p><p>其实除却技术之外,这些人身上会夹杂着浓浓的揣摩圣意的意图。Ming不满意,他们不会去思考整理自己哪里的不对,Ming为什么会要求他们那样。甚至不愿去争辩自己认为是对的东西。时时刻刻把对上级的揣摩与猜测挂在嘴边,放在心里。这样做仍然无法满足上级,因为上级要的不是这个。</p><p>我也可以感受到,我对Ming的一些做法有质疑时的直言,让Ming对我有一些惺惺相惜之感。的确,其实互联网行业出来的人,往往不会炫耀自己的资历,只会用能力去和别人对话。我做的不对,你就说怎么样是对的,以此达到共识。能碰到一个能真正交流,又能学习到东西的同事,是一件很难得的事情。彼此珍惜。我很庆幸我在这样的一个行业中,在外企中,在一个允许、鼓励员工畅所欲言的环境中,每个人都彰显自己的个性,求同存异,共同进步。办公室政治不可避免,尽量出淤泥而不染吧。</p><p>本来说不会写,结果开了个头就很难停下来,还是写了这么一个东西。最后,哥要去Cupertino了,哈哈,下周一!<img src="/img/Cupertino_City_Center.jpg" alt="Cupertino Center, Calif."></p>]]></content>
<summary type="html"><p>写Blog这件事总是时断时续,距离上一篇Blog已经过去半年了。最近godaddy又开始催收域名费用了,大概120人民币,奇怪的是一年前觉得这个贵到不可接受,现在却觉得略便宜。人就是这样在潜移默化中一点点改变的,消费观、人生观乃至世界观。</p>
<p>加入Luminate Wireless已经快两个月了,以为会像离开MicroStrategy时一样写一篇吐槽的帖子,再展望一下未来,然而并没有。如同我姐上次在微信中问我新公司怎么样,我说还不错啊,能学到不少新东西。她说你在上个公司不也是这么说么。想了想的确如此,呵呵。在Tango这一年零两个月的时间里,确实看到了不少东西,也学到了不少东西,认识了不少的牛人。也对在这个圈子里怎么找到自己的安身立命之本有一点点顿悟。有人网络知识精通,只用Linux自身的工具就可以创造自己的协议,游刃于UDP与IPsec之间。有人熟习Git各种使用场景,设计模式信手拈来。有人博学多闻,轻松可以出任一个规模略小公司的CTO。</p></summary>
<category term="心情" scheme="https://andimeo.us.kg/categories/%E5%BF%83%E6%83%85/"/>
<category term="Tango" scheme="https://andimeo.us.kg/tags/Tango/"/>
<category term="Luminate" scheme="https://andimeo.us.kg/tags/Luminate/"/>
</entry>
<entry>
<title>切尔诺贝利的回忆</title>
<link href="https://andimeo.us.kg/2015/12/05/%E5%88%87%E5%B0%94%E8%AF%BA%E8%B4%9D%E5%88%A9%E7%9A%84%E5%9B%9E%E5%BF%86/"/>
<id>https://andimeo.us.kg/2015/12/05/%E5%88%87%E5%B0%94%E8%AF%BA%E8%B4%9D%E5%88%A9%E7%9A%84%E5%9B%9E%E5%BF%86/</id>
<published>2015-12-05T15:50:11.000Z</published>
<updated>2024-09-07T07:25:33.406Z</updated>
<content type="html"><![CDATA[<p>最近读完了<a href="https://en.wikipedia.org/wiki/Svetlana_Alexievich">Svetlana Alexievich</a>的《切尔诺贝利的回忆:核灾难口述史》,了解了发生在我出生前两年的那场人祸。最新版改名为《我不知道该说什么,关于死亡还是爱情》,初时是被原书名吸引到的,也认为原书名相对新名更加振聋发聩。读完后才发现,任何一场事件中最值得书写与纪念的都是经历过此事的人。</p><blockquote><ul><li>多少士兵没有配套的防护设施,仅凭着对伟大领袖的崇拜冲赴灾难现场</li><li>多少文化程度不高的平民在被疏散时,却不忍离开世代生活栖息的土地</li><li>多少妻子不顾政府的禁令,想尽各种办法接触从前线回来的丈夫,不管他的丈夫变成什么,即使已被辐射折磨的没有人样</li></ul></blockquote><blockquote><ul><li>多少亲属不愿再接触前线回来的亲人</li><li>多少长官坐在办公室里,用一个个谎言维持稳定</li><li>多少村庄留守到最后的只有猫狗</li></ul></blockquote><p>将读书过程中的标注分类列在这里,成一文,记一事。</p><span id="more"></span><h2 id="核辐射">核辐射</h2><p>切尔诺贝利事故后你什么都可以吃,不过你得用<strong>铅</strong>把自己的排泄物埋起来。</p><p>伏特加对消除压力很有帮助,难怪战争时他们发给大家每人一百克伏特加。然后就像在老家一样——醉酒的交通警察开罚单给喝醉酒的司机。</p><p>“切尔诺贝利电厂为什么发生故障?有人说是科学家的错。他们抓上帝的胡子,现在他笑了,却是我们付出代价。”</p><p>一九八六年四月二十九日,波兰、德国、奥地利和罗马尼亚都检测到高剂量辐射。四月三十日,瑞士和意大利北部,五月一日、二日,法国、比利时、荷兰、英国和希腊北部,五月三日,以色列、科威特和土耳其,也陆续检测到辐射。辐射粒子飘散到全球:五月二日,日本,五月五日,印度,五月五日、六日,美国和加拿大,都陆续检测到辐射。不到一个星期,切尔诺贝利就成为全世界的问题。</p><p>目前用石棺封住的四号反应炉炉心,仍有大约二十吨核燃料,没有人知道里面的情况究竟如何。 石棺制作精良且构造独特,圣彼得堡的工程设计师也许该感到自豪。但是石棺并非在反应炉现场由人力所建,板块是借助机器人和直升机搭建的,因此一开始就有裂缝。根据数据显示,现在总共有超过两百平方米的漏洞和裂痕,放射性粒子持续外泄…… 石棺会不会崩塌?没人能回答这个问题,因为至今我们仍无法进入石棺或接缝处检视。但是每个人都知道,如果石棺崩塌,后果会比一九八六年严重。</p><p>屋里的灶台在炸猪油,你用辐射探测仪一量,发现那不是炉子,而是一座小反应炉。</p><h2 id="苦难">苦难</h2><p>我从报纸上剪了一篇关于当晚在核电厂值班的操作员列昂尼德·塔普托诺夫的报道。爆炸前几分钟,他按下红色紧急按钮,可是无济于事。他们把他带到莫斯科的医院,医生说:“我们需要另一个身体才能治疗他。”他全身上下只有背上一个小点没有辐射。他们把他埋在迈汀斯卡亚的公墓,和其他人一样,先用金属薄片把棺材隔绝起来,再倒半米混凝土,加上铅盖。他的父亲站在那里哭,走过他身旁的人说:“都是你的王八蛋儿子害它爆炸的!”</p><p>我从阿富汗回来时,知道自己可以活下去;而这里正好相反,它在你回家后才把你杀死。</p><p>回家之后,我脱掉所有在那里穿过的衣服,丢进垃圾滑运槽。我把帽子送给我的小儿子,因为他真的很想要,他每时每刻都戴着那顶帽子。 两年后,他们诊断出他长了脑瘤……剩下的你自己写,我不想讲了。</p><p>他一天排便二十五到三十次,伴随着血液和黏液。手臂和双腿的皮肤开始龟裂,全身长疮。只要一转头,就可以看到一簇头发留在枕头上。我开玩笑说:“这样很方便,你不需要梳子了。”</p><p>她出生时不是婴儿,而是一个小袋子,除了眼睛之外,没有任何开口。病历卡上写着:“女孩,多重先天异常。肛门发育不全,阴道发育不全,左肾发育不全。”</p><p>一名老妇人走过去问:“你们是什么人?” “我们是科学家。” “哦,科学家。你看他打扮成什么德行!你看那个面罩!那我们呢?”她拿棍子追他。 我有时候想,他们有一天可能会追杀科学家,就像中世纪时有人把医生捉起来淹死一样。</p><p>我很害怕,不敢谈恋爱。我有未婚夫,我们已经注册了。你有没有听过日本广岛的“被爆者”?他们是原子弹生还者,只能彼此通婚。这里没有人写这件事,没有人谈论,但是我们存在——我们是切尔诺贝利的“被爆者”。</p><p>我拍摄开花的苹果树,看到大黄蜂嗡嗡作响,苹果花白得像新娘的白纱。人们还在工作,花园里花朵绽放。我拿着摄影机,总觉得哪里不对劲。曝光正常,画面漂亮,但就是不对劲,过了一阵子,我恍然大悟——我闻不到任何气味。花朵盛开,可是没有味道! 后来我才知道,有时身体对高剂量辐射的反应是阻断某些器官的功能。我当时想到,我七十四岁的母亲也闻不到任何气味,我以为我和她一样。</p><p>这世界已被一分为二:我们,是切尔诺贝利人;你们,是其他所有人。有人注意到了吗?在这里,没人会说自己是俄罗斯人、白俄罗斯人,或乌克兰人。我们都自称为切尔诺贝利人。</p><p>起初,问题在于谁该负起责任。然后,当我们懂得更多时,我们会开始想,我们该做什么?我们该如何保护自己?自从知道这一事件的影响不只是一两年,而是好几代人时,我们便开始回顾过去。</p><p>我们之间还流传着一些笑话。比如说这个:一个美国机器人上屋顶作业五分钟,然后就发生故障了。日本机器人也上去作业了五分钟,然后也发生故障了。俄罗斯机器人来了,一上去就是两个小时!这时扩音器里传来了命令:“二等兵伊凡诺夫!再过两个小时,你就可以下来休息,抽根烟了。”哈哈!(大笑)</p><h2 id="谎言">谎言</h2><p>但是我不能再等二三十年,我要告他们,告政府。他们说我疯了,还嘲笑我,说古希腊也有这种孩子。一个政府官员骂我:“你想得到切尔诺贝利特权!你想得到切尔诺贝利受害者基金!”我不知道我怎么没在他办公室昏倒。</p><p>我记得事故发生后没几天,图书馆所有关于辐射、广岛或长崎,甚至关于X光的书都消失了。有些人说那是上级的指示,这样民众才不会惊慌。还有人开玩笑说,如果切尔诺贝利在巴布亚新几内亚附近爆炸,全世界的人都会很担心,只有巴布亚人不会害怕。</p><p>为什么每个人都对切尔诺贝利保持沉默?为什么我们的作家不书写关于切尔诺贝利的事?他们描述战争和集中营,但是对于这里,他们很沉默。为什么?你觉得那是意外吗?如果我们战胜切尔诺贝利或了解切尔诺贝利,人们就会谈论、书写它,但是我们不了解其中的意义,无法把它放入人类的经验或时间的框架中。 所以怎样比较好?记得还是遗忘?</p><p>党中央会派人到城镇来跟工厂与人们商谈,但是来的人却不知道如何去除辐射活性,不知道如何保护孩童,也不知道食物里渗入了多少放射线系数。他们不知道什么是阿尔法、贝塔、伽马射线,也全然不懂放射生物学、离子化辐射,更不用说懂同位素了。对他们而言,这些东西就像从外星来的一样。他们只知道颂扬苏联人民的勇敢,讲述军队的英勇事迹和揭露西方间谍组织的阴谋。当我在党内集会中提出怀疑时,他们却告诉我要取消我的党籍。</p><p>“你在开玩笑吗?”与我们一起前来的上尉笑着说,“二十五天?你们要被派去切尔诺贝利六个月。” 我真不敢相信,然后开始感到愤怒。他们开始说服我们:在反应炉二十公里外工作,可以拿到两倍的薪水,十公里外可以拿到三倍,而在反应炉周围工作的人可以拿到六倍的钱。</p><p>我是个有硕士学位的化学工程师,我在一家大工厂担任实验室的主管。而他们竟然叫我做什么呢?他们拿了一把铲子给我——这是我唯一的工具。我们很快就想出了一个口号:“用铲子对抗原子!”</p><h2 id="土地">土地</h2><p>我记得一座被精心照顾的菜园,主人走出房子,看到我们。 “年轻人,不要嚷嚷,我们的表格已经交出去了——春天就离开。” “那你为什么翻菜园的土?” “可是那是秋天的工作。</p><p>当时就是这样。人们把所有拿得走、抬得动的东西都偷光了,隔离区的物品都被运回这里,你可以在市场、当铺或一般人的度假小屋看到。留在铁丝网里的只有土地和坟墓,还有我们的健康和信仰,或是我的信仰。</p><p>“《圣经》描述的情况都发生了,关于我们的集体农场和戈尔巴乔夫,《圣经》说会出现一个有胎记的领导人,一个伟大的帝国瓦解,然后世界末日来临,所有住在城里的人都死光,只有一个住在村子里的人活下来。那个人看到人的脚印好开心!不是看到人,只是人的脚印。”</p><p>很多年前,我的祖母看到《圣经》里描述说,世界上会有一段时间,万物都欣欣向荣、开花结果,河里有很多鱼,森林有很多动物,但是人类无法使用那些资源,无法繁衍后代,不能传宗接代。古老的预言就像可怕的童话,我当时不相信有那种事。</p><p>我们戴着手套和防毒面具,穿着手术袍,艳阳高照,我们像恶魔般出现在他们的院子,他们不明白我们为什么要埋他们的菜园,撕碎看起来很寻常的大蒜和白菜。老妇人边画十字边问我们:“年轻人,这是怎么回事?世界末日来了吗?”</p><h2 id="荣誉">荣誉</h2><p>两个伞兵拒绝去,他们的妻子很年轻,还没生小孩。他们遭到羞辱和惩罚,不会有前途了。去那里也关乎男子气概和荣誉!那是吸引我去的一个原因——他不去,所以我去。可是现在我会从不同的角度看这件事,经过九次手术,两度心脏病发作,我不会批评他们了,我了解他们的想法,他们还很年轻。可是无论如何我都会去,这点我很肯定。他不能去,我去,那才算男子汉。</p><p>即使他们准许我讲话,我要跟什么人讲?我在工厂工作,老板说:“不许再生病,不然我们要裁掉你。” 后来他们真的把我裁掉了。我去找厂长说:“你没有权力这样做,我去过切尔诺贝利,我救了你们,保护你们!” 他说:“我们又没派你去。”</p><p>我听过一个笑话:男人下班回家告诉妻子:“他们说我明天不是去切尔诺贝利,就是交出党证。”“可是你不是党员。”“对啊,所以我在想怎么在明早之前弄到一张党证。”</p><p>我平常的薪水是四百卢布,在那里可以领到一千卢布,后来有人说:“他们领了一大堆钱,现在回来可以买他们的第一辆车和添购家具了。”听到那种话你当然难过,因为我们不是为了钱才去那里。</p><p>我怕人,但也想见人,我想见到好人。这里不是藏匿的盗匪,就是像我一样的战士。</p><p>官员念着报上的声明,说我们因为有“高度政治觉悟以及精心策划”,所以灾变后仅仅四天,红旗已经在四号反应炉上飘扬。那面红旗一个月后就被辐射吞噬,于是他们又派人插上另一面旗,一个月后又得再插一面。 我想象那些士兵走上屋顶换旗子时的感受,那是自杀任务。是信奉苏联,还是奋勇牺牲?不过老实说,如果他们给我那面旗,叫我爬上去,我也会那么做。为什么?我说不上来,也许我当时不怕死,我的妻子连一封信都没写给我,六个月,一封信都没有。(沉默)</p><p>我们在隔离区待了两个月,那一带大部分村子都疏散了。一共几十座村子:巴布契、特果维契……我们第一次进去的时候,狗都绕着房子跑,守护房子,等待主人回家。它们看到我们很高兴,朝我们的声音跑过来。 我们到房屋、谷仓、院子射杀它们,然后拖到路边,丢上卡车,实在不是很愉快的工作。它们不了解我们为什么要杀它们。那些动物很容易杀,它们是宠物,不怕枪也不怕人,它们听到我们的声音会跑过来。</p><h2 id="感情">感情</h2><p>我永远不会忘记洗衣服的妇人,没有人带洗衣机,只好用手洗。那些洗衣妇都很老,双手结痂、长满疖子,衣服不只脏,还有几十伦琴的辐射。“年轻人,来吃东西。”“年轻人,去睡一下。”“你们还年轻,要小心。”她们觉得我们很可怜,替我们感到难过。</p><p>我们开车到不同村庄测量辐射,没有一个女人请我吃苹果;男人不太怕,他们会走过来,请我们喝伏特加或吃猪油。拒绝他们虽然尴尬,但是我们当然不想吃有铯的食物。所以我喝酒,但不吃东西。</p><p>回家后我去跳舞,遇到喜欢的女孩,我说:“我们交往吧。” “有什么用?你是切尔诺贝利人了,我不敢和你生小孩。”</p><p>我没有告诉父母我被派到切尔诺贝利。我的弟弟在《消息报》上看到我的照片,拿去给妈妈看。“你看,”他说,“他是英雄!”我的母亲哭了。</p><p>我刚到这里找不到工作,后来才去市议会打扫,洗地板,从前的生活已经过去,我没有力气展开新生活。有人同情我们,也有人讨厌我们,他们说:“那些难民会趁晚上偷挖我们的马铃薯。”</p><p>我们陪她在医院住了四年,我们不能把她单独留在那里,所以她不知道人应该住在家里。我们如果回家一两个月,她会问:“我们什么时候回医院?”她的朋友都在那里,她从小在那里长大。</p><p>她哀号了好一阵,然后留下了她丈夫的奖章和奖状。唉,这些物品会被陈列在博物馆里。人们会看到这些展品,但她的哭泣声却只有我一个人能听见。每当我摆设这些奖状时,我就会想起她的哭声。</p><p>我们本可以离开这里,但我跟我丈夫考虑之后决定留下来。我们不敢离开。在这里,我们都是切尔诺贝利人,我们不会害怕彼此;在这里,如果有人拿家里种的苹果或小黄瓜给你吃,你会欣然接受,不会遮遮掩掩地把东西藏到口袋或皮包里,然后拿出去丢掉。</p><p>我带着女儿逃到明斯克,去找我妹妹。我的亲妹妹不让我进门,说她家里还有一个嗷嗷待哺的婴儿。你能想象这种事吗?我们后来只能在火车站里过夜。</p><h2 id="misc">Misc</h2><p>我在杜尚别是火车站的副站长,另一个副站长是塔吉克人,我们的孩子从小一起长大、一起上学,每逢元旦假期或五一劳动节,两家人都聚在一起喝啤酒,吃塔吉克焖饭。他叫我:“姐姐,我的俄罗斯姐姐。”我们共享一间办公室。突然有一天,他走进办公室,在我的桌前对我破口大骂:“你什么时候回俄罗斯?这里是我们的地盘!”我气疯了,跳起来问他:“你的大衣是哪里来的?” “列宁格勒。”他惊讶地说。 “脱掉你的俄罗斯大衣,你这个王八蛋!”我扒掉他的大衣。“你的帽子从哪里来?你跟我说是西伯利亚寄来的!拿掉!还有衬衫!裤子!都是莫斯科做的!也是俄罗斯的!”我剥到他只剩内衣。他很高大,我只到他的肩膀,但是我脱掉他全身上下的衣物。人群开始聚集,他哭着说:“滚开,你疯了!”</p><p>就在这个时候,分娩的女人发出开心的叫声,接着就听到宝宝的哭声。我弯身看了一下那个连名字都还没取的宝宝,甚至不记得是男孩还是女孩。那些人问:“是库利亚布人还是帕米尔人?” 他们不是问男孩或女孩,而是库利亚布人还是帕米尔人!没有人回答。他们大喊:“到底是哪里人?” 还是没有人说话。只见他们抓住小宝宝——刚出世五分钟,也许才十分钟的婴儿,一把扔出窗户。我是护士,从来没有看过婴儿死掉,现在……我不应该记得这种事的(开始哭)。遇到这种事,你以后怎么生活?怎么生小孩?(哭泣)</p><p>河的另一头的那条街,所有女人都没有男人,男人都死光了。我们这条街只剩我的爷爷和另一个男人还活着。上帝为什么先带走男人?没有人知道答案。不过世上如果只剩男人,没有我们女人,的确没什么好处。他们会因为悲伤过度而拼命喝酒!这里的女人都很空虚,据说有三分之一的女人的生殖器官受损,无论老少都是,不是所有人都来得及生小孩。仔细想想,事情就这样过去了,好像从来没发生。</p><p>我还记得小时候,女人带我们去桑拿浴池,我看到她们的子宫都掉出来(即使还是小孩,我们也知道那是什么),用碎布捆绑。子宫掉出来是因为女人做太多粗活,男人不是在前线打仗就是加入游击队,再加上没有马,女人都得搬运重物,耕种自家菜园和集体农场的田地。我长大后,每次和女人有亲密接触,都会想起我在桑拿浴池看到的画面。</p><p>他的姐姐柳达当时二十八岁,是护士,很了解移植骨髓的过程,但是她愿意移植,她说:“只要他能活下去。” 我透过手术室的大窗观看手术过程。他们躺在并排的手术台上,手术一共历时两小时。结束之后,柳达看起来比他还虚弱。他们在她胸前刺了十八个洞,麻药几乎退不掉。她从前是健康漂亮的姑娘,现在却体弱多病,一直没结婚。我在他们的病房间穿梭,他不再住普通病房了,而是住特殊的生物室,躺在透明帷幕里,没有人可以进去。</p><p>我曾拍摄待过集中营的人,他们都尽量避免和对方见面,我了解那种感觉,聚在一起回想战争,会有些不自在。共同经历过那种屈辱,目睹过人在最恶劣情况下是什么模样的人,会避免和对方见面。我不太想讲我在切尔诺贝利感受到的一些事,例如所有人道主义的概念都是相对的,在极端状态下,人的表现不会像书里描述的,而是正好相反,人不是英雄。</p><p>昨天我父亲过了八十岁生日。我们一家人聚在餐桌边。我看着父亲,想起他这一生的许多经历:古拉格集中营,奥斯维辛集中营,还有切尔诺贝利。他这一代人全都碰上了。</p><h2 id="读后感">读后感</h2><blockquote><ul><li>当人们面对灾难时讲的笑话令人痛心。</li><li>政府为了维稳而欺瞒人民令人愤怒。</li><li>亲人阴阳两隔,受尽折磨令人疾首。</li></ul></blockquote><p>人们说朝鲜是三十年前的中国,读完此书我觉得不久前抑或现今的中国就是三十年前的苏联。</p><p>面对灾难时,才能看出一个国家的文明程度,是人民的国家抑或国家的人民。</p>]]></content>
<summary type="html"><p>最近读完了<a href="https://en.wikipedia.org/wiki/Svetlana_Alexievich">Svetlana Alexievich</a>的《切尔诺贝利的回忆:核灾难口述史》,了解了发生在我出生前两年的那场人祸。
最新版改名为《我不知道该说什么,关于死亡还是爱情》,初时是被原书名吸引到的,也认为原书名相对
新名更加振聋发聩。读完后才发现,任何一场事件中最值得书写与纪念的都是经历过此事的人。</p>
<blockquote>
<ul>
<li>多少士兵没有配套的防护设施,仅凭着对伟大领袖的崇拜冲赴灾难现场</li>
<li>多少文化程度不高的平民在被疏散时,却不忍离开世代生活栖息的土地</li>
<li>多少妻子不顾政府的禁令,想尽各种办法接触从前线回来的丈夫,不管他的丈夫变成什么,即使已被辐射折磨的没有人样</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>多少亲属不愿再接触前线回来的亲人</li>
<li>多少长官坐在办公室里,用一个个谎言维持稳定</li>
<li>多少村庄留守到最后的只有猫狗</li>
</ul>
</blockquote>
<p>将读书过程中的标注分类列在这里,成一文,记一事。</p></summary>
<category term="书摘" scheme="https://andimeo.us.kg/categories/%E4%B9%A6%E6%91%98/"/>
<category term="切尔诺贝利" scheme="https://andimeo.us.kg/tags/%E5%88%87%E5%B0%94%E8%AF%BA%E8%B4%9D%E5%88%A9/"/>
<category term="核灾难" scheme="https://andimeo.us.kg/tags/%E6%A0%B8%E7%81%BE%E9%9A%BE/"/>
<category term="历史" scheme="https://andimeo.us.kg/tags/%E5%8E%86%E5%8F%B2/"/>
</entry>
<entry>
<title>一个商品排序搜索问题</title>
<link href="https://andimeo.us.kg/2015/06/28/%E4%B8%80%E4%B8%AA%E5%95%86%E5%93%81%E6%90%9C%E7%B4%A2%E6%8E%92%E5%BA%8F%E9%97%AE%E9%A2%98/"/>
<id>https://andimeo.us.kg/2015/06/28/%E4%B8%80%E4%B8%AA%E5%95%86%E5%93%81%E6%90%9C%E7%B4%A2%E6%8E%92%E5%BA%8F%E9%97%AE%E9%A2%98/</id>
<published>2015-06-27T16:35:24.000Z</published>
<updated>2024-09-07T07:25:33.406Z</updated>
<content type="html"><![CDATA[<p>上周被经理召集过去,大家一起开了个会,讨论如何结合商品的销售排名来提升搜索效果这件事。因为这边自己建立搜索而不用合作方提供的搜索API相关的事情,从一开始的snapdeal到现在的lazada都是我在负责,所以这件事情当然也交给了我。在会上,大家拍脑门,提出将商品的match score和rank score线性组合,然后用logistic regression来训练参数,最后用MAP来评价结果。今天仔细一想,发现这根本是不可能的。</p><p>因为对于一组参数,首先要获得在这组参数下的商品排序结果,然后再计算MAP值。因为这里有排序这个步骤,所以back propagation是做不到的。这里我突然想到我那篇讲引文推荐的文章里也涉及参数调优,究竟当时是怎么做的呢。不得已,又翻了翻曾经的论文。发现那里关于smooth parameter和self boost parameter的调优完全是试出来的。这也相当的make sense,因为叫parameter tuning么,自然就是小一点大一点的tuning。若能建立一个框架,自动计算出最优的参数选择,那应该就进入了learning to rank的领域。这里发现曾经的东西对现在也有指导作用,还是很开心的。</p><span id="more"></span><p>那篇文章在这里,<a href="http://dl.acm.org/citation.cfm?id=2063879">Recommending Citations with Translation Model</a>要是你恰好在看我的博客,又对推荐引文这件事感兴趣,可以翻来看看。</p><p>综上所述,今天最后我想出了三套方案,记录于此,周一去盒经理再讨论讨论。拍脑袋想点子总是不靠谱啊,devil exists in the details.</p><h2 id="rank-score">Rank score</h2><p>其实这里最复杂的事情莫过于rank值如何变为一个rank score。这个rank score又以什么形式与match score结合。即我们的hypothesis是什么,这里只能拍脑门想几种,然后取MAP值最大的了。</p><h2 id="第一套方案:logistic-regression">第一套方案:logistic regression</h2><p>既然是回归,只需要明确样本的feature是什么,回归的目标变量是什么即可。这里我们从将snapdeal官网的搜索结果作为ground truth。针对一个query Q,考虑其前n个搜索结果。</p><ul><li>将这n个商品标识为相关结果,其余商品标识为无关结果,目标变量就只在{0, 1}中取值,这种方法最符合logistic regression最常见的用法,是或者不是</li><li>将这n个商品当作相关,但不是完全相关,相关程度和其在官方搜索结果中的位置有关,比如所在位置的倒数等,用来标识这个商品相关的概率。logistic regression最终学得的参数作用在样本上,得到的结果也就是这个样本是正例的概率而已。所以这种设定应该也可以接受</li></ul><p>在这之后,将所有Query下的所有商品各看作一个样本,这个样本就会有两个特征分数,match score和rank score,对这两个变量做回归就完全满足logistic regression的输入输出了。</p><p>这样有一个问题就在于,这两个参数的最优值,往往对不同类型的Query也是不同的,这样将所有Query揉合在一起,最终训练出的结果,非常依赖于Query的分布。同时也依赖于每个Query下商品的个数,假设有的Query下只有很少的商品,那么这种Query会被低估很多。</p><h2 id="第二套方案:map-+-调参">第二套方案:MAP + 调参</h2><p>将从snapdeal得到的前n个商品看作相关商品。</p><p>对于一组参数,对商品进行排序,然后算出MAP值。枚举参数,找出使得MAP最优的参数。</p><p>这里的问题在于,MAP天生的忽略商品相关的程度,只考虑相关的文档出现在哪个位置上而已。而没有将越相关的文档排在越前面考虑在内。</p><h2 id="第三套方案:ndcg-+-调参">第三套方案:NDCG + 调参</h2><p>这个方案和第二种类似,只是可以根据snapdeal给出的结果,为前n个搜出的产品赋予一个相关的程度。</p><h2 id="总结">总结</h2><p>用machine learning的方法做搜索还是应该诉诸于learning to rank,而不是拍脑袋用下logistic regression就能搞得定的。</p><p>另外本任务中的参数实在是有点多,如从snapdeal中获取的搜索结果的个数,rank score的计算方式,rank score和match score的结合方式,NDCG中相关度的设定等。</p><p>刚才又看了一眼,发现之前理解的MAP都是错的,只看书不实践果然没有什么卵用啊。</p>]]></content>
<summary type="html"><p>上周被经理召集过去,大家一起开了个会,讨论如何结合商品的销售排名来提升搜索效果这件事。因为这边自己建立搜索而不用合作方提供的搜索API相关的事情,从一开始的snapdeal到现在的lazada都是我在负责,所以这件事情当然也交给了我。在会上,大家拍脑门,提出将商品的match score和rank score线性组合,然后用logistic regression来训练参数,最后用MAP来评价结果。今天仔细一想,发现这根本是不可能的。</p>
<p>因为对于一组参数,首先要获得在这组参数下的商品排序结果,然后再计算MAP值。因为这里有排序这个步骤,所以back propagation是做不到的。这里我突然想到我那篇讲引文推荐的文章里也涉及参数调优,究竟当时是怎么做的呢。不得已,又翻了翻曾经的论文。发现那里关于smooth parameter和self boost parameter的调优完全是试出来的。这也相当的make sense,因为叫parameter tuning么,自然就是小一点大一点的tuning。若能建立一个框架,自动计算出最优的参数选择,那应该就进入了learning to rank的领域。这里发现曾经的东西对现在也有指导作用,还是很开心的。</p></summary>
<category term="搜索" scheme="https://andimeo.us.kg/categories/%E6%90%9C%E7%B4%A2/"/>
<category term="商品搜索" scheme="https://andimeo.us.kg/tags/%E5%95%86%E5%93%81%E6%90%9C%E7%B4%A2/"/>
<category term="信息检索" scheme="https://andimeo.us.kg/tags/%E4%BF%A1%E6%81%AF%E6%A3%80%E7%B4%A2/"/>
<category term="Information Retrieval Evaluating" scheme="https://andimeo.us.kg/tags/Information-Retrieval-Evaluating/"/>
</entry>
<entry>
<title>Java Generics</title>
<link href="https://andimeo.us.kg/2015/05/12/Java%20Generics/"/>
<id>https://andimeo.us.kg/2015/05/12/Java%20Generics/</id>
<published>2015-05-12T13:39:00.000Z</published>
<updated>2024-09-07T12:50:18.985Z</updated>
<content type="html"><![CDATA[<p>今天看了下Java的官方教程中关于泛型的部分。泛型引起我的注意是因为微博上一篇比较<code>List<?></code>和<code>List<Object></code>的文章。最近看Lucene的代码,其中Util部分大量的使用了泛型,今天刚好浮生修得一日闲便全面的学习一下Java的泛型。其中很多内容都与直觉相符,所以我觉得不值得通篇翻译,这里仅就我觉得有趣也反直觉的地方做一个总结。</p><p><a href="http://docs.oracle.com/javase/tutorial/java/generics/index.html">The Java™ Tutorials Lesson: Generics</a></p><h2 id="命名规则">命名规则</h2><p>C++和Java中都学习过泛型,可是从来觉得写T就跟写for循环中的i一样,也就是约定俗成,谁知这里还是有一些规则的。</p><ul><li>E - Element (used extensively by the Java Collections Framework)</li><li>K - Key</li><li>N - Number</li><li>T - Type</li><li>V - Value</li><li>S, U, V etc. - 2nd, 3rd, 4th types</li></ul><span id="more"></span><h2 id="the-diamond">The Diamond</h2><p>在Java 7及以后的版本中,当调用泛型类的构造函数时,可以省去类型参数,只使用一组空的尖括号,只要在编译期类型可以被编译器确定或者推导出来。这对尖括号,就叫作diamond。比如我们可以像如下这样声明一个链表:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List<String> list = new ArrayList<>();</span><br></pre></td></tr></table></figure><p>diamond在英文中可以指扑克牌中的方片,两个尖括号放在一起<>,的确像一个方片。</p><h2 id="multiple-bounds">Multiple Bounds</h2><p><code>List<? extends A></code> 和 <code>List<? super A></code>代表类型为A的子类和A的父类的链表类型。这里的语法称为upper bound和lower bound。这些都是非常基础的知识了。但是类型参数可以有多个bounds,这样的写法并不常见。如:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><T extends A & B & C></span><br></pre></td></tr></table></figure><p>需要注意的是,如果A、B、C中有一个为类,其余为接口的话,类必须写到第一个的位置,否则会在编译时报错。</p><h2 id="unbounded-wildcard">Unbounded Wildcard</h2><p>unbounded wildcard在两种情形下很有用:</p><ul><li>当你在写一个只需要类Object提供的功能就能够完成的方法时</li><li>当使用的泛型类中的方法并不依赖于泛型参数时。比如<code>List.clear</code>或<code>List.size</code>。实际上,我们经常使用<code>Class<?></code>,因为<code>Class<T></code>中的大部分方法都和T无关。</li></ul><p>考虑下面的方法:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">public static void printList(List<Object> list) {</span><br><span class="line"> for (Object elem : list)</span><br><span class="line"> System.out.println(elem + " ");</span><br><span class="line"> System.out.println();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>printList的目的是打印任意类型的列表,但是上面的函数却做不到。它只能打印Object的List,无法打印<code>List<Integer></code>,<code>List<String></code>或<code>List<Double></code>,因为它们都不是<code>List<Object></code>的子类。为了写一个泛型的printList,需要使用<code>List<?></code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">public static void printList(List<?> list) {</span><br><span class="line"> for (Object elem: list)</span><br><span class="line"> System.out.print(elem + " ");</span><br><span class="line"> System.out.println();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为对任何具体类型<code>A</code>,<code>List<A></code>是<code>List<?></code>的子类。</p><p>需要特别注意的是,<code>List<Object></code>和<code>List<?></code>是不一样的。你可以往<code>List<Object></code>插入<code>Object</code>和其他Object的子类。但是你只能往<code>List<?></code>中插入<code>null</code>。</p><h2 id="lower-bounded-wildcard">Lower Bounded Wildcard</h2><p>你可以为一个Wildcard指定Upper Bound或者Lower Bound,但是不能同时指定。</p><h2 id="wildcards-and-subtyping">Wildcards and Subtyping</h2><table><thead><tr><th>The common parent is List<?></th><th>A hierarchy of several generic List class declarations.</th></tr></thead><tbody><tr><td><img src="/img/generics-listParent.jpg" alt="generics-listParent" title="generics-listParent"></td><td><img src="/img/generics-wildcardSubtyping.jpg" alt="generics-wildcardSubtyping" title="generics-wildcardSubtyping"></td></tr></tbody></table><h2 id="guidelines-for-wildcard-use">Guidelines for Wildcard Use</h2><p>为了讨论的方便,我们假设一个变量提供下面的两种功能:</p><ul><li>"In"变量,为函数提供输入数据</li><li>"Out"变量,为函数提供输出</li></ul><p>Wildcard Guidelines:</p><ul><li>"In"变量用Upper Bounded Wildcard定义,使用extends关键字</li><li>"Out"变量用Lower Bounded Wildcard定义,使用super关键字</li><li>当"In"变量可以用Object中定义的方法访问时,使用Unbounded Wildcard</li><li>当变量同时作为"In"和"Out"时,不要使用Wildcard</li></ul><p>这份Guideline并不适用于函数的返回值类型,应该避免使用wildcard作为函数的返回值类型。</p><h2 id="泛型的限制">泛型的限制</h2><p><strong>不能用基本类型实例化泛型</strong></p><p><strong>不能创建类型参数的实例</strong></p><p>你不能创建类型参数的实例,但是可以使用反射创建。</p><p><strong>不能创建包含类型参数的static成员</strong></p><p>因为所有类的实例都共同拥有static成员,但是可以创建和类的类型参数不同的泛型static函数</p><p><strong>不能创建参数类型的数组</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed</span><br><span class="line">stringLists[0] = new ArrayList<String>(); // OK</span><br><span class="line">stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown,</span><br><span class="line"> // but the runtime can't detect it.</span><br></pre></td></tr></table></figure><p>如果允许创建类型参数的数组,上面的代码将会抛出<code>ArrayStoreException</code></p><p><strong>不能创建,catch或throw参数类型的对象</strong></p><p>泛型类不能直接或间接继承<code>Throwable</code>。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// Extends Throwable indirectly</span><br><span class="line">class MathException<T> extends Exception { /** ... **/ } // compile-time error</span><br><span class="line"></span><br><span class="line">// Extends Throwable directly</span><br><span class="line">class QueueFullException<T> extends Throwable { /** ... **/ // compile-time error</span><br></pre></td></tr></table></figure><p>一个方法不能catch类型参数的实例</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public static <T extends Exception, J> void execute(List<J> jobs) {</span><br><span class="line"> try {</span><br><span class="line"> for (J job : jobs)</span><br><span class="line"> // ...</span><br><span class="line"> } catch (T e) { // compile-time error</span><br><span class="line"> // ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然而,可以在<code>throws</code>子句中使用类型参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">class Parser<T extends Exception> {</span><br><span class="line"> public void parse(File file) throws T { // OK</span><br><span class="line"> // ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>不能拥有在Type Erasure之后签名一样的重载函数</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public class Example {</span><br><span class="line"> public void print(Set<String> strSet) { }</span><br><span class="line"> public void print(Set<Integer> intSet) { }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>今天看了下Java的官方教程中关于泛型的部分。泛型引起我的注意是因为微博上一篇比较<code>List&lt;?&gt;</code>和<code>List&lt;Object&gt;</code>的文章。最近看Lucene的代码,其中Util部分大量的使用了泛型,今天刚好浮生修得一日闲便全面的学习一下Java的泛型。其中很多内容都与直觉相符,所以我觉得不值得通篇翻译,这里仅就我觉得有趣也反直觉的地方做一个总结。</p>
<p><a href="http://docs.oracle.com/javase/tutorial/java/generics/index.html">The Java™ Tutorials Lesson: Generics</a></p>
<h2 id="命名规则">命名规则</h2><p>C++和Java中都学习过泛型,可是从来觉得写T就跟写for循环中的i一样,也就是约定俗成,谁知这里还是有一些规则的。</p>
<ul>
<li>E - Element (used extensively by the Java Collections Framework)</li>
<li>K - Key</li>
<li>N - Number</li>
<li>T - Type</li>
<li>V - Value</li>
<li>S, U, V etc. - 2nd, 3rd, 4th types</li>
</ul></summary>
<category term="Java" scheme="https://andimeo.us.kg/categories/Java/"/>
<category term="泛型" scheme="https://andimeo.us.kg/tags/%E6%B3%9B%E5%9E%8B/"/>
<category term="Generics" scheme="https://andimeo.us.kg/tags/Generics/"/>
<category term="Wildcard" scheme="https://andimeo.us.kg/tags/Wildcard/"/>
</entry>
<entry>
<title>几种教科书里没有的排序</title>
<link href="https://andimeo.us.kg/2015/05/03/%E5%87%A0%E7%A7%8D%E6%95%99%E7%A7%91%E4%B9%A6%E9%87%8C%E6%B2%A1%E6%9C%89%E7%9A%84%E6%8E%92%E5%BA%8F/"/>
<id>https://andimeo.us.kg/2015/05/03/%E5%87%A0%E7%A7%8D%E6%95%99%E7%A7%91%E4%B9%A6%E9%87%8C%E6%B2%A1%E6%9C%89%E7%9A%84%E6%8E%92%E5%BA%8F/</id>
<published>2015-05-03T14:04:56.000Z</published>
<updated>2024-09-23T13:08:08.417Z</updated>
<content type="html"><![CDATA[<p>今天开始看Lucene的源码了,从core模块的util开始看起。发现其中有几个排序的方法名字都没有听过,比如introsort、timsort。结合lucene的代码,又在网上进行了相关知识的搜索,发现在比较新的语言中,比如Java 7和python中,默认的排序算法已经不是教科书中的quicksort了。今天就记录一下新学到的这三个算法。</p><h2 id="inplace-merge-sort">Inplace Merge Sort</h2><p>还记得年初去Google面试的时候,一个北欧的面试官问过我,为什么大部分库函数实现排序时都选择了quicksort,而不是mergesort。当时临场反应,给出了面试官想要的答案,即quicksort可以in place执行,而mergesort需要额外的空间。可是今天看Lucene的代码时,在org.apache.lucene.util.Sorter中,看到了一个函数名叫mergeInPlace。难道被Google的面试官骗了,话说我面试别人的时候,还出过这道题。好的,看看Lucene是怎么做到in place的吧。</p><span id="more"></span><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">void mergeInPlace(int from, int mid, int to) {</span><br><span class="line"> if (from == mid || mid == to || compare(mid - 1, mid) <= 0) {</span><br><span class="line"> return;</span><br><span class="line"> } else if (to - from == 2) {</span><br><span class="line"> swap(mid - 1, mid);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> //1. from ... from' ... mid ... to ... to'</span><br><span class="line"> //Suppose [from, mid) and [mid, to) are two sorted segments.</span><br><span class="line"> //Obviously, after the two loops below, numbers in [from, from') are smaller than others,</span><br><span class="line"> //numbers in [to, to') are greater than others. So we only need to focus on [from', to')</span><br><span class="line"> while (compare(from, mid) <= 0) {</span><br><span class="line"> ++from;</span><br><span class="line"> }</span><br><span class="line"> while (compare(mid - 1, to - 1) <= 0) {</span><br><span class="line"> --to;</span><br><span class="line"> }</span><br><span class="line"> //2. Take the middle element of [from', mid) as a pivot named first_cut,</span><br><span class="line"> //then find the lower_bound of it in [mid, to'), named second_cut</span><br><span class="line"> int first_cut, second_cut;</span><br><span class="line"> int len11, len22;</span><br><span class="line"> if (mid - from > to - mid) {</span><br><span class="line"> len11 = (mid - from) >>> 1;</span><br><span class="line"> first_cut = from + len11;</span><br><span class="line"> second_cut = lower(mid, to, first_cut); //lower_bound</span><br><span class="line"> len22 = second_cut - mid;</span><br><span class="line"> } else {</span><br><span class="line"> len22 = (to - mid) >>> 1;</span><br><span class="line"> second_cut = mid + len22;</span><br><span class="line"> first_cut = upper(from, mid, second_cut); //upper_bound</span><br><span class="line"> len11 = first_cut - from;</span><br><span class="line"> }</span><br><span class="line"> //3. We can tell that [from', first_cut), [mid, second_cut) are smaller than [first_cut],</span><br><span class="line"> //while [first_cut, mid) and [second_cut, to') are greater than or equal to [first_cut].</span><br><span class="line"> //Of course, [from, from') are also smaller than [first_cut], the same to [to', to].</span><br><span class="line"> //So actually we partition the sequence into two parts, one is greater than the pivot,</span><br><span class="line"> //while the other one is smaller than the pivot. (Similar as the partition phase of quicksort)</span><br><span class="line"> rotate(first_cut, mid, second_cut); //reverse [first_cut, mid), [mid, second_cut), [first_cut, second_cut) respectively</span><br><span class="line"> final int new_mid = first_cut + len22;</span><br><span class="line"> mergeInPlace(from, first_cut, new_mid);</span><br><span class="line"> mergeInPlace(new_mid, second_cut, to);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>这段代码中用到了三个辅助函数,分别是lower、upper和rotate。lower和upper分别代表lower_boudn和upper_bound的意思,这里顺别复习了一下lower_bound和upper_bound的意义。lower_bound(l, r, val)是指[l, r)中大于等于val的第一个元素,而upper_bound(l, r, val)则指[l, r)中大于val的第一个元素。而rotate(l, m, r)则是分别反转[l, m), [m, r)和[l, r)三个区间各一次,典型的应用就是反转一个句子中的单词。</p><p>仔细品味这里用的lower_bound和upper_bound,可以发现这个merge过程是stable的,所以这种排序也就是一种稳定排序。</p><p>结合我在代码中穿插的注释,第一段代码,算是一个简单的优化,排除了一些已经就位的元素,接下来只需要对剩下的元素进行merge即可。</p><p>第二段代码,则将[from', mid)的中点作为一个pivot,然后再[mid, to')中寻找pivot的lower_bound,这样就将整个区间分为了四个部分。结合第三段代码,恰好反转这四段的中间两端,则左边的两段都小于pivot,右边的两段则都大于等于pivot。所以,接下来分别在两段上递归调用mergeInPlace,即可。这整个过程其实非常像quicksort的过程。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">举个例子:</span><br><span class="line"></span><br><span class="line">1 5 10 15 2 8 12 16</span><br><span class="line"></span><br><span class="line">from = 0, to = 8, mid = 5</span><br><span class="line">经过第一段代码后:</span><br><span class="line">from' = 1, to' = 7</span><br><span class="line">经过第二段代码后:</span><br><span class="line">first_cut = 2, second_cut = 6</span><br><span class="line">经过第三段代码后,序列变为:</span><br><span class="line">1 5 2 8 10 15 12 16</span><br><span class="line">然后分别对[1 5 2 8] 和 [10 15 12 16]进行递归调用。</span><br></pre></td></tr></table></figure><p>至此,in place的mergesort就完成了。但是我很难说服自己这仍然是mergesort,这更像是将merge的过程变成了quicksort。当然比起普通的那个归并排序而言,复杂度也上升了。merge过程本来是O($n$)的复杂度,现在变成了O($nlogn$),从而整体的复杂度也从O($nlogn$)变成了O($nlog^2n$)</p><h2 id="introsort">Introsort</h2><blockquote><p>Introsort or introspective sort is a hybrid sorting algorithm that provides both fast average performance and (asymptotically) optimal worst-case performance. It begins with quicksort and switches to heapsort when the recursion depth exceeds a level based on (the logarithm of) the number of elements being sorted. This combines the good parts of both algorithms, with practical performance comparable to quicksort on typical data sets and worst-case O($nlogn$) runtime due to the heap sort.</p></blockquote><p>上面是Wikipedia上对introsort的一段解释。简单的说,introsort是quicksort和heapsort的结合,当递归深度超过$logn$的时候,就不在继续递归,而转而执行heapsort,从而达到最坏情况O($nlogn$)的时间复杂度。下面就来看一下org.apache.lucene.util.IntroSorter中的实现。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">void quicksort(int from, int to, int maxDepth) {</span><br><span class="line"> if (to - from < THRESHOLD) {</span><br><span class="line"> insertionSort(from, to);</span><br><span class="line"> return;</span><br><span class="line"> } else if (--maxDepth < 0) {</span><br><span class="line"> //When the recursion depth reaches O(nlogn), stop recursion and sort with heapSort</span><br><span class="line"> heapSort(from, to);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> final int mid = (from + to) >>> 1;</span><br><span class="line"></span><br><span class="line"> if (compare(from, mid) > 0) {</span><br><span class="line"> swap(from, mid);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (compare(mid, to - 1) > 0) {</span><br><span class="line"> swap(mid, to - 1);</span><br><span class="line"> if (compare(from, mid) > 0) {</span><br><span class="line"> swap(from, mid);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> int left = from + 1;</span><br><span class="line"> int right = to - 2;</span><br><span class="line"></span><br><span class="line"> setPivot(mid);</span><br><span class="line"> for (;;) {</span><br><span class="line"> while (comparePivot(right) < 0) {</span><br><span class="line"> --right;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> while (left < right && comparePivot(left) >= 0) {</span><br><span class="line"> ++left;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (left < right) {</span><br><span class="line"> swap(left, right);</span><br><span class="line"> --right;</span><br><span class="line"> } else {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> quicksort(from, left + 1, maxDepth);</span><br><span class="line"> quicksort(left + 1, to, maxDepth);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>首先,请无视setPivot和comparePivot两个函数,因为IntroSorter是一个抽象类,所以其中没有数据成员储存pivot,故而将这个方法的具体实现交给了它的子类来完成。comparePivot(i)即是比较pivot和[i]的大小。我们可以看到在函数的一开始即判断了maxDepth是否已经到达$logn$,如果到达了则转而使用堆排序。其余部分的实现没有什么特别值得注意的地方,就是普通的quicksort的实现。不过这里用了很直观的实现方法,没有像有些实现中将pivot的位置换来换去,获得了更短的代码长度,但是却丧失了可读性。</p><p>综上,introsort似乎只是在quicksort的伤口处贴上了heapsort做的创可贴罢了,不过可以想到,quicksort的最坏情况出现的概率应该非常低,所以整体执行效率应该还是提升了的。Wikipedia上交代,c++的std::sort就是introsort,how astonishing it is!</p><h2 id="timsort">Timsort</h2><blockquote><p>Timsort is a hybrid sorting algorithm, derived from merge sort and insertion sort, designed to perform well on many kinds of real-world data. It was invented by Tim Peters in 2002 for use in the Python programming language. The algorithm finds subsets of the data that are already ordered, and uses that knowledge to sort the remainder more efficiently. This is done by merging an identified subset, called a run, with existing runs until certain criteria are fulfilled. Timsort has been Python's standard sorting algorithm since version 2.3. It is also used to sort arrays of non-primitive type in Java SE 7, on the Android platform, and in GNU Octave</p></blockquote><p>上面这段是Wikipedia上对Timsort的描述,从中我们可以知道Timsort是mergesort和insertionsort的结合。主要利用了原序列中已经有序的区间。而且python 2.3以后的标准排序算法和Java 7中非基本类型的排序算法都是用Timsort实现的。可见Timsort在工业界是很受追捧的,可是我竟然从来没有听过。。。汗</p><p>经过一个下午的研究,我对Timsort的理解大致如下。首先找出根据序列中的递增、递降的子序列,将递降的子序列反转,则得到了若干个已经有序的子串。此处其实是非严格递增和严格递降,因为该算法最终也是一个稳定排序算法。对于这些子串,根据子串的长度关系,两两归并。考虑长度关系,等价于控制mergesort中的递归深度。保证时间复杂度仍然是O($nlogn$)这个级别。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">public void sort(int from, int to) {</span><br><span class="line"> checkRange(from, to);</span><br><span class="line"> if (to - from <= 1) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> reset(from, to);</span><br><span class="line"> do {</span><br><span class="line"> ensureInvariants();</span><br><span class="line"> pushRunLen(nextRun());</span><br><span class="line"> } while (runEnd(0) < to);</span><br><span class="line"> exhaustStack();</span><br><span class="line"> assert runEnd(0) == to;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Timsort中的辅助函数非常多,我们先看看上面这段整个排序的骨干代码。checkRange用来保证from < to。而reset则用于初始化一个子串栈,栈中的每个元素,为已经有序的一个子串的结束下标。ensureInvariants则用于合并栈中元素, 即归并这个过程。nextRun用于在原串中找到下一个有序子串。并且通过pushRunLen压栈。最后exhaustStack清栈,保证栈中仅存一个元素,即已经排序完成。下面我们分别看看这些函数的内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">void reset(int from, int to) {</span><br><span class="line"> stackSize = 0;</span><br><span class="line"> Arrays.fill(runEnds, 0);</span><br><span class="line"> runEnds[0] = from;</span><br><span class="line"> this.to = to;</span><br><span class="line"> final int length = to - from;</span><br><span class="line"> this.minRun = length <= THRESHOLD ? length : minRun(length);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>首先来看reset函数干了什么。1.初始化栈,2. 设定minRun的值,这里一个run就是一个有序子串的意思,而minRun则限定了一个run的最小长度,如果长度不足minRun,则会通过binarySort来将长度为minRun的子串排序。这里的binarySort,我认为就是插入排序中,通过二分来找到要插入的位置而已。Wikipedia中也说Timsort是mergesort和insertionsort的结合,而lucene这里将其命名为binarySort。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">void ensureInvariants() {</span><br><span class="line">//runLen(i) returns the (stackSize - i - 1)th element</span><br><span class="line">//so runLen0 means the last element, runLen1 means the second to last element, same to runLen2</span><br><span class="line"> while (stackSize > 1) {</span><br><span class="line"> final int runLen0 = runLen(0);</span><br><span class="line"> final int runLen1 = runLen(1);</span><br><span class="line"></span><br><span class="line"> if (stackSize > 2) {</span><br><span class="line"> final int runLen2 = runLen(2);</span><br><span class="line"></span><br><span class="line"> if (runLen2 <= runLen1 + runLen0) {</span><br><span class="line"> // merge the smaller of 0 and 2 with 1</span><br><span class="line"> if (runLen2 < runLen0) {</span><br><span class="line"> mergeAt(1);</span><br><span class="line"> } else {</span><br><span class="line"> mergeAt(0);</span><br><span class="line"> }</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (runLen1 <= runLen0) {</span><br><span class="line"> mergeAt(0);</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面来看ensureInvariants,若栈中只有两个元素,若第一个的长度小于第二个的,则归并;弱栈中有超过两个元素,则考虑最后三个,将倒数第一个和倒数第三个中长度较小的那个和倒数第二个合并。直到栈中只有一个元素或两个元素。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">void mergeAt(int n) {</span><br><span class="line"> assert stackSize >= 2;</span><br><span class="line"> merge(runBase(n + 1), runBase(n), runEnd(n));</span><br><span class="line"> for (int j = n + 1; j > 0; --j) {</span><br><span class="line"> setRunEnd(j, runEnd(j-1));</span><br><span class="line"> }</span><br><span class="line"> --stackSize;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> void merge(int lo, int mid, int hi) {</span><br><span class="line"> if (compare(mid - 1, mid) <= 0) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> //Same trick as in in place merge</span><br><span class="line"> lo = upper2(lo, mid, mid);</span><br><span class="line"> hi = lower2(mid, hi, mid - 1);</span><br><span class="line"></span><br><span class="line"> if (hi - mid <= mid - lo && hi - mid <= maxTempSlots) {</span><br><span class="line"> mergeHi(lo, mid, hi);</span><br><span class="line"> } else if (mid - lo <= maxTempSlots) {</span><br><span class="line"> mergeLo(lo, mid, hi);</span><br><span class="line"> } else {</span><br><span class="line"> mergeInPlace(lo, mid, hi);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>下面来看看merge的过程,mergeAt(i)即是merge倒数第i个和倒数第i-1个元素。merge之后,再更新栈。merge(lo, mid, hi)是将[lo, mid)和[mid, hi)的函数。我们看到这里用到了和mergeInPlace中一样的小优化。若有一部分的长度小于maxTempSlots,则使用长度为maxTempSlots的临时缓冲区来进行merge。否则进行mergeInPlace操作,这里的mergeInPlace就是本文阐述的第一个算法。</p><blockquote><p>NOTE</b>:There are a few differences with the original implementation:<ul></p><li>The extra amount of memory to perform merges isconfigurable. This allows small merges to be very fast while large mergeswill be performed in-place (slightly slower). You can make sure that thefast merge routine will always be used by having <code>maxTempSlots</code>equal to half of the length of the slice of data to sort.</li><li>Only the fast merge routine can gallop (the one that doesn't runin-place) and it only gallops on the longest slice.</li></ul></blockquote><p>在org.apache.lucene.util.TimSorter的类注释中,有上面一段话,第二个bullet我们等会再说。我们先看第一个bullet,在这里我们用来归并的内存是可配的,而我们可以通过将其配置为整个序列长度的一半,来使得所有merge都可以快速的进行。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">void mergeLo(int lo, int mid, int hi) {</span><br><span class="line"> assert compare(lo, mid) > 0;</span><br><span class="line"> int len1 = mid - lo;</span><br><span class="line"> //copy [lo, lo+len1) to the temp storage</span><br><span class="line"> save(lo, len1);</span><br><span class="line"> //move [mid] to position lo. We can be sure that [mid] is the smallest element in this range,</span><br><span class="line"> //because of the optimistion trick performed in merge</span><br><span class="line"> copy(mid, lo);</span><br><span class="line"> int i = 0, j = mid + 1, dest = lo + 1;</span><br><span class="line"> outer: for (;;) {</span><br><span class="line"> //GALLOP</span><br><span class="line"> for (int count = 0; count < MIN_GALLOP; ) {</span><br><span class="line"> if (i >= len1 || j >= hi) {</span><br><span class="line"> break outer;</span><br><span class="line"> } else if (compareSaved(i, j) <= 0) {</span><br><span class="line"> restore(i++, dest++);</span><br><span class="line"> count = 0;</span><br><span class="line"> } else {</span><br><span class="line"> copy(j++, dest++);</span><br><span class="line"> ++count;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // galloping...</span><br><span class="line"> int next = lowerSaved3(j, hi, i);</span><br><span class="line"> for (; j < next; ++dest) {</span><br><span class="line"> copy(j++, dest);</span><br><span class="line"> }</span><br><span class="line"> restore(i++, dest++);</span><br><span class="line"> }</span><br><span class="line"> //TODO: The jump out condition must meet with j >= hi, since the first half are strictly</span><br><span class="line"> //greater than the last half. Because lo and hi are obtained by search [mid].</span><br><span class="line"> for (; i < len1; ++dest) {</span><br><span class="line"> restore(i++, dest);</span><br><span class="line"> }</span><br><span class="line"> assert j == dest;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>由于mergeLo和mergeHi的原理是一样的,所以我们这里只针对mergeLo进行说明。首先将第一段复制到临时存储区,然后将第二段的开头元素置于整个序列的开头,这里我们可以确定这个元素是两段中最小的一个。然后就是两个指针,一个指向临时存储区,一个指向序列的后半段,从mid+1开始,进行归并。不过需要注意的一点是,这里有一个count,一旦从序列第二段,即较长的那一段移到整个序列前端的元素个数达到MIN_GALLOP时,就对此时临时存储区中正在处理的元素,通过二分查找它在第二段元素中的lower_bound,然后将lower_bound前面的元素全部移到序列前端。</p><p>这也就是org.apache.lucene.util.TimSorter的类注释中的第二个bullet。只有fast merge才有GALLOP机制,mergeInPlace没有这个机制。GALLOP在英文中的意思是飞快、飞速,在这里是一种优化手段。下面是一张关于GALLOP的示意图:</p><p><img src="/img/galloping_mode_timsort.svg.png" alt="galloping mode timsort.svg" title="galloping mode timsort.svg"></p><h2 id="结语">结语</h2><p>至此三种见诸于教科书外的排序算法讲完。下表是Wikipedia上各种排序算法的时间及空间比较:</p><table><thead><tr><th>Algorithm</th><th>Timsort</th><th>Introsort</th><th>Mergesort</th><th>Quicksort</th><th>Insertion sort</th><th>Selection sort</th><th>Smoothsort</th></tr></thead><tbody><tr><td>Best Case</td><td>O($n$)</td><td></td><td>O($nlogn$)</td><td>O($nlogn$)</td><td>O($n$)</td><td>O($n^2$)</td><td>O($n$)</td></tr><tr><td>Average Case</td><td>O($nlogn$)</td><td>O($nlogn$)</td><td>O($nlogn$)</td><td>O($nlogn$)</td><td>O($n^2$)</td><td>O($n^2$)</td><td>O($nlogn$)</td></tr><tr><td>Worst Case</td><td>O($nlogn$)</td><td>O($nlogn$)</td><td>O($nlogn$)</td><td>O($n^2$)</td><td>O($n^2$)</td><td>O($n^2$)</td><td>O($nlogn$)</td></tr><tr><td>Space complexity</td><td>O($n$)</td><td></td><td>O($n$)</td><td>O($n$)</td><td>O($1$)</td><td>O($1$)</td><td>O($1$)</td></tr></tbody></table>]]></content>
<summary type="html"><p>今天开始看Lucene的源码了,从core模块的util开始看起。发现其中有几个排序的方法名字都没有听过,比如introsort、timsort。结合lucene的代码,又在网上进行了相关知识的搜索,发现在比较新的语言中,比如Java 7和python中,默认的排序算法已经不是教科书中的quicksort了。今天就记录一下新学到的这三个算法。</p>
<h2 id="inplace-merge-sort">Inplace Merge Sort</h2><p>还记得年初去Google面试的时候,一个北欧的面试官问过我,为什么大部分库函数实现排序时都选择了quicksort,而不是mergesort。当时临场反应,给出了面试官想要的答案,即quicksort可以in place执行,而mergesort需要额外的空间。可是今天看Lucene的代码时,在org.apache.lucene.util.Sorter中,看到了一个函数名叫mergeInPlace。难道被Google的面试官骗了,话说我面试别人的时候,还出过这道题。好的,看看Lucene是怎么做到in place的吧。</p></summary>
<category term="Algorithm" scheme="https://andimeo.us.kg/categories/Algorithm/"/>
<category term="Sorting" scheme="https://andimeo.us.kg/tags/Sorting/"/>
<category term="Lucene" scheme="https://andimeo.us.kg/tags/Lucene/"/>
</entry>
<entry>
<title>2014,再见!MicroStrategy 再见!</title>
<link href="https://andimeo.us.kg/2014/12/31/2014%EF%BC%8C%E5%86%8D%E8%A7%81%EF%BC%81MicroStrategy-%E5%86%8D%E8%A7%81%EF%BC%81/"/>
<id>https://andimeo.us.kg/2014/12/31/2014%EF%BC%8C%E5%86%8D%E8%A7%81%EF%BC%81MicroStrategy-%E5%86%8D%E8%A7%81%EF%BC%81/</id>
<published>2014-12-31T13:40:23.000Z</published>
<updated>2024-09-07T07:25:33.409Z</updated>
<content type="html"><![CDATA[<p>2014最后一天,天很蓝。</p><p>告别,在这一天,2014!MicroStrategy!</p><h1 id="初识microstrategy">初识MicroStrategy</h1><p>2012年的秋季,和所有毕业季的学生一样,我也踏上了找工作的旅程。海投简历、不分白天晚上的笔试、面试,似乎只有这样,自己的学生生涯才完满。大牛们横扫所有的公司,人人上各种日志在展示大牛们拿到了多少公司的Offer,拿到了多少Special Offer。但是不管怎样,最后只能去一家,只能让一家成为自己的First fulltime job。</p><p><img src="/img/MicroStrategy.jpg" alt="MicroStrategy" title="MicroStrategy"></p><span id="more"></span><p>其实我也曾天真的幻想,如果最后一个人的薪水是所有拿到Offer的公司开出的Offer之和会怎样。各个公司会如何博弈。这想法很可笑,但如果成真,至少给了疯狂的找工作一个理由。</p><p>想想自己从川大一路走来,择业观也发生了天翻地覆的变化。对公司的鄙视链也是几经扭转。当时在教主的影响下,一致认为百度是我等最牛逼的出路,18W的人民币,是我们可以攀登的极限。当然,那时我们并不知道,18K的月薪在交了五险一金和税后只有12.5K。后来在蓝色星空看到我们只见其代码从未谋其面的JB发的一封帖,上述他现就职于迈克罗索芙特公司,当我们悟出这并不是一家化妆品公司,而是大名鼎鼎的MicroSoft时,我们又一次折服了。这就是当时的我们心中远古存在的神。</p><p>所以当时的鄙视链大概如此,Google=Microsoft > Baidu > Tencent > 华为。</p><p>读研后,发现实验室的师兄们都对Baidu有一些或多或少的嗤之以鼻。那时Baidu在心中的地位就开始摇摆,但由于没有另外的公司来填充,所以仍然是心中的Dream Job。如愿以偿研一的暑期,瞒着导师找到了一份Baidu商务搜索部的职位。有趣的是二面的面试官恰好是我们实验室已经毕业的一位师兄。不知道当时是不是借了这个光。总之在Baidu度过了第一次没有回家的暑假。</p><p>后来在耳濡目染中,北大的师兄们告诉我除了Baidu,还有Google、Facebook,还有豌豆荚,还有阿里。研二的暑期拿到了Microsoft、Hulu的Offer,当时脑子秀逗,竟然去了Tencent,现在再想当时为何做出这个决定,仍然没有一个能让我信服的理由。</p><p>这时的鄙视链已经变为,Google=Facebook > Hulu > Alibaba > Amazon = Tencent = Microsoft > Baidu。这充分的说明周围的人对你影响还是非常巨大的。顺便插一句,我们寝室四个人,有三个当然都拒了Microsoft的Offer,去了Tencent,我们戏称我们寝室必须是当年燕园最脑残的寝室。</p><p>接下来正式进入找工作的节奏,其实拿到了不少Offer,人人、有道、搜狗、百度、微软、微策略、亚马逊。Google挂在女面试官手里,阿里挂在亲亲的女师姐手里,Facebook压根没投。Top的Offer一个没拿到,不过略逊一筹的也拿到不少。最后之所以选择微策略,原因有三。</p><ul><li>其一是BDWM上的一个Google招人的帖子,说偏喜欢在Hulu、MicroStrategy实习过或工作过的人。这直接把微策略放在了Google预科的位置上。在我看来是对这家公司莫大的肯定。</li><li>其二在于微策略入职后的长达一个月的培训,英文的交流环境(当然后来发现这一点是扯淡的,中国人和中国人互相说英文是很难的),纯英文的面试。其实当时人人给我的Offer是所有公司里最大的,但是那个组的Lead,不断的勾引我去实习,希望我尽早加入工作。说实话,我并不喜欢这么急迫的氛围,我还是希望能有点前戏的。没错,我指的就是微策略那一个月的培训。不过后来发现那只是教你使用微策略的产品而已,就好像微软培训你如何使用Office一样。</li><li>其三,是微策略的神秘。因为这公司进入我的视线不久,名字起的又高大上。又自称做商务智能。我也不知道从哪里yy出了微策略的一个case是帮助沃尔玛决策如何摆放货品来增加销量。这些都太合我的胃口。</li></ul><p>就这样,接下了Offer,没有和HR再讨价还价,也接受了户口可有可无的情况。不过最后当然还是拿到了。</p><h1 id="从bootcamp到usher">从Bootcamp到Usher</h1><p>加入微策略后,有一个月的时间都在做培训。如何做Report,如何做Dashboard,那时我也大概明白原来所谓商务智能就是做报表。期间接触过一个新加坡的老师,一个波兰的老师。对这个叫Powel的老师有无限好感。可惜没有他的照片了。</p><p>培训结束后,BTC的General Manager,开始找我们这一批入职的人,面对面的谈话,给我们分配小组,并看看我们的想法。我是第一个被叫去谈话的,刚开始给我的SQLEngine,是微策略的传统业务的核心之一吧,我根本就不了解,所以也就欣然同意了。可是从第二个人开始,大家几乎都对分配的职位不感兴趣,于是他们的职位又都是根据自己的兴趣来分配的。Ma总说我也得重新选一次,不然不公平,我就选了Usher后端的职位。当时Usher后端的职位并不开放,还是Ma总专门和HQ沟通后,才做的最后决定。</p><p>当时我为什么选Usher,因为Usher够酷,充当Identity Provider。如果这个能做成,凡是需要人的身份的地方,都需要Usher。至今我也认为单从产品概念上来说,前景非常之好。<img src="/img/Usher.jpg" alt="Usher" title="Usher"></p><p>进来做了不长时间,2014元旦过后。Usher就重构,从原来的server、client这种按照职能分组的模式变为按照feature来分组,比如Biometric,Discovery,Directory等等。这就是噩梦的开始。</p><h1 id="吐槽">吐槽</h1><p>从1月重构开始,Horizontal的team和Vertical的team就不断起冲突。原来server组之间的人可以互相backup,有人请假另一人可以顶上。现在每个Vertical team的server engineer几乎就只有一个人,完全没有backup,他休假了,别人根本不知道他做的工作是什么。就这么磨合了6个月,就开始裁员。7月1批、8月1批,直到国庆前宣布BTC closure。</p><p>吐槽微策略,吐槽Usher,我早就想做,可是一直拖到今天。那就随着2014,跟这些槽点一起说再见吧。</p><h2 id="管理人员能力不足">管理人员能力不足</h2><ul><li>脑洞太大,大到推出Vertical team这种模式。</li><li>对人员随意任命,我在这短短的几个月中。分别在server team,discovery team,biometric team,web seamless integration team都呆过。这能想象么?真的是觉得那里需要人,你手头又没什么事儿,那就你去做吧。结果是过去2个礼拜,还没清楚那边的逻辑,就又被拉到另一个组去了。</li><li>组内的leader想做的事儿很多,但是又不能拍板。多方牵制,权力严重受限。往往想做一件事,连相关调研都没有做完,就宣布不做这件事儿了。</li><li>人员配置有问题。以server组为例,不到20个engineer,HQ只有3-4个,剩下的都在BTC。但是Leader在HQ,BTC却没有一个lead。也就是说在北京这边虽然大家title有高有低,但是谁也不是谁的上级。大家对任务有疑问,只有问HQ。</li><li>沟通成本过高。美国的同事只要手下带几个人,晚上一般都会工作的很晚,工作日基本都得熬到快12点。为了和北京的同事开会。可是如上条所述,如果下午发现早上任务的问题,完全没有人可以来解答这个问题,只有等待第二天再问。</li></ul><h2 id="btc权力不足">BTC权力不足</h2><ul><li>BTC这边的local manager,想推行code review,unit test,pre checkin test等措施来提高代码质量。可是HQ的人根本没人鸟你,一个team,却隶属于两个集团,各行其政。想从上向下推行一项措施,阻力太大。除非从Usher组最高的一级推行。但是那一级推行的却是Vertical team这种操蛋的举措。不想说了,再说就太难听了。</li><li>这一点也许和外企的氛围有关。太民主了。在BTC内部推行一个code review,竟然要三番五次的向有些人解释这样做的意义。第一,这样的人是怎么招进来的。第二,这种事情真的还需要征求是否执行的意见么,难道不应该是标配,强制执行么?袁世凯所述民主即是无主绝非没有道理。</li></ul><h2 id="方向、方针有误">方向、方针有误</h2><ul><li>Xiao Peng来review BTC的工作时,就批评过前端的一些工作。你们把时间浪费在让用户去选择自己的Key是什么颜色的这种事情上有什么用,你提供的难道不应该是更fancy的feature么?</li><li>可是Xiao Peng这么说了,底下的中层仍然在走着这些路。Usher用来管理身份,并且初期是一个企业产品的定位。试想在监狱,给狱卒注射一个装有Usher的小硬件,那他们的自由活动,他们探亲,这些事情,随时都可以受到监视,可以解放出大批警力。是的,就是像Nikita里的那种跟踪硬件,我觉得才应该是Usher的方向。想想医院、政府机关,这样的使用场景数不胜数。而把Usher依附在手机上,作为一个App存在,始终都是一个玩具。</li><li>也许并不是想不到,而是没有能力做到吧。这又要说道一点,微策略在北京招的junior engineer太多,senior engineer太少,能做决策的senior engineer更是几乎没有。</li></ul><p>Usher这个概念,至今我都认为非常有前景。抓住身份这两个字,支付、安全、汽车、教育,几乎没有行业不是Usher的前沿阵地。可是以这样的执行力,这样的管理层,Usher这个产品的前景,我认为至少在可预见的一段时间内,只是一个玩具。说句俗的,这不是裁掉BTC可以挽救的,这就是老牌BI公司,老牌软件公司的基因,这个转型很痛苦。第一次阵痛就将BTC截肢了。</p><h1 id="2015前夕">2015前夕</h1><p>现在的时间是2014.12.31 23:26,还有半个多小时的时间就2015了。过完元旦的小长假,我也将去新公司工作了。也仍然等待Google的Offer,拿不到Google的Offer就好办了。万一拿到了又是一场纠结。</p><p>Whatever,2015年,27了。</p><ul><li>坚持每天学英语。首先是有道口语大师,然后开始学习新闻英语。</li><li>新公司报销健身费用,希望能够在同事的陪伴下,坚持work out。</li><li>如果能拿到Google美国的Offer,可能会去吧。</li><li>新公司管1日三餐,大概不会再做饭了。</li><li>Node js学起来吧,向全栈工程师前进。</li></ul><p>2014,再见!2015,你好!</p>]]></content>
<summary type="html"><p>2014最后一天,天很蓝。</p>
<p>告别,在这一天,2014!MicroStrategy!</p>
<h1 id="初识microstrategy">初识MicroStrategy</h1><p>2012年的秋季,和所有毕业季的学生一样,我也踏上了找工作的旅程。海投简历、不分白天晚上的笔试、面试,似乎只有这样,自己的学生生涯才完满。大牛们横扫所有的公司,人人上各种日志在展示大牛们拿到了多少公司的Offer,拿到了多少Special Offer。但是不管怎样,最后只能去一家,只能让一家成为自己的First fulltime job。</p>
<p><img src="/img/MicroStrategy.jpg" alt="MicroStrategy" title="MicroStrategy"></p></summary>
<category term="心情" scheme="https://andimeo.us.kg/categories/%E5%BF%83%E6%83%85/"/>
<category term="工作" scheme="https://andimeo.us.kg/tags/%E5%B7%A5%E4%BD%9C/"/>
</entry>
<entry>
<title>多线程写文件</title>
<link href="https://andimeo.us.kg/2014/11/24/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%99%E6%96%87%E4%BB%B6/"/>
<id>https://andimeo.us.kg/2014/11/24/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%99%E6%96%87%E4%BB%B6/</id>
<published>2014-11-24T07:31:22.000Z</published>
<updated>2024-09-07T07:25:33.406Z</updated>
<content type="html"><![CDATA[<p>最近在准备Tango的面试,猎头给的参考资料中有这么一道题目:</p><blockquote><p>四个线程,一线程输出1,二线程输出2,三线程输出3,四线程输出4。分别向四个文件输出,使文件的内容如下:</p><ul><li>file1:123412341234…………</li><li>file2:234123412341…………</li><li>file3:341234123412…………</li><li>file4:412341234123…………</li></ul></blockquote><p>后来查了一下,发现这又是一道早些年Google的经典面试题。</p><p>为了解决这道题目,也弥补一下自己稀缺的并发知识,于是在一周内啃完了《Java Concurrency in Practice》这本书。后来在面试中,和面试官聊到并发的话题时,随便扯扯书中说的CAS,面试官就不问什么了,呵呵。</p><p><img src="/img/javaconcurrencyinpractice.jpg" alt="javaconcurrencyinpractice" title="javaconcurrencyinpractice"></p><p>好了,言归正传,下面就来给出我对这道题目的三个解法:</p><span id="more"></span><h1 id="利用object内部的instrinsic-lock">利用Object内部的instrinsic lock</h1><p>第一种方法就是保证,对于线程i,要在文件j写i之前,要保证(i-1+4)%4 + 1已经写到了文件j中,即程序中的lastNums[index]=ln一定要成立。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">import java.io.FileNotFoundException;</span><br><span class="line">import java.io.PrintWriter;</span><br><span class="line"></span><br><span class="line">class WriteThreadv1 extends Thread {</span><br><span class="line">private int index;</span><br><span class="line">private int num;</span><br><span class="line">private PrintWriter[] writers;</span><br><span class="line">private int[] lastNums;</span><br><span class="line"></span><br><span class="line">public WriteThreadv1(int index, int num, PrintWriter[] writers, int[] lastNums) {</span><br><span class="line">this.index = index;</span><br><span class="line">this.num = num;</span><br><span class="line">this.writers = writers;</span><br><span class="line">this.lastNums = lastNums;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public void run() {</span><br><span class="line">int ln = (num - 2 + 4) % 4 + 1;</span><br><span class="line">for (int i = 0; i < 10; i++) {</span><br><span class="line">synchronized (writers[index]) {</span><br><span class="line">while (!(lastNums[index] == -1 && index == num - 1) && lastNums[index] != ln)</span><br><span class="line">try {</span><br><span class="line">writers[index].wait();</span><br><span class="line">} catch (InterruptedException e) {</span><br><span class="line">e.printStackTrace();</span><br><span class="line">}</span><br><span class="line">writers[index].write("" + num);</span><br><span class="line">lastNums[index] = num;</span><br><span class="line">writers[index].notifyAll();</span><br><span class="line">}</span><br><span class="line">index = (index - 1 + 4) % 4;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class FourThreadv1 {</span><br><span class="line">public static void main(String[] args) throws FileNotFoundException, InterruptedException {</span><br><span class="line">PrintWriter[] writers = new PrintWriter[4];</span><br><span class="line">int[] lastNums = new int[4];</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">writers[i] = new PrintWriter("D:/" + (i + 1) + ".txt");</span><br><span class="line">lastNums[i] = -1;</span><br><span class="line">}</span><br><span class="line">WriteThreadv1[] ts = new WriteThreadv1[4];</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">ts[i] = new WriteThread(i, i + 1, writers, lastNums);</span><br><span class="line">ts[i].start();</span><br><span class="line">}</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">ts[i].join();</span><br><span class="line">writers[i].close();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="利用cyclicbarrier">利用CyclicBarrier</h1><p>第二种方法直接利用了Java.util.concurrency中的CyclicBarrier,这个东西的用法就是,初始化一个值n,每一次调用这个barrier的await方法时,调用线程都会阻塞在此处,直到阻塞线程达到n个时,同时继续进行。然后再开始等待下一轮的await。</p><p>利用这个东西,那就很轻松了,每次写之前都await一下,然后所有线程一起写。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line">import java.io.FileNotFoundException;</span><br><span class="line">import java.io.PrintWriter;</span><br><span class="line">import java.util.concurrent.BrokenBarrierException;</span><br><span class="line">import java.util.concurrent.CyclicBarrier;</span><br><span class="line"></span><br><span class="line">class WriteThreadv2 extends Thread {</span><br><span class="line">private int index;</span><br><span class="line">private int num;</span><br><span class="line">private PrintWriter[] writers;</span><br><span class="line">private int[] lastNums;</span><br><span class="line">private CyclicBarrier barrier;</span><br><span class="line"></span><br><span class="line">public WriteThreadv2(int index, int num, PrintWriter[] writers, int[] lastNums, CyclicBarrier barrier) {</span><br><span class="line">this.index = index;</span><br><span class="line">this.num = num;</span><br><span class="line">this.writers = writers;</span><br><span class="line">this.lastNums = lastNums;</span><br><span class="line">this.barrier = barrier;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public void run() {</span><br><span class="line">for (int i = 0; i < 10; i++) {</span><br><span class="line">synchronized (writers[index]) {</span><br><span class="line">try {</span><br><span class="line">barrier.await();</span><br><span class="line">} catch (InterruptedException | BrokenBarrierException e) {</span><br><span class="line">e.printStackTrace();</span><br><span class="line">}</span><br><span class="line">writers[index].write("" + num);</span><br><span class="line">lastNums[index] = num;</span><br><span class="line">}</span><br><span class="line">index = (index - 1 + 4) % 4;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class FourThreadv2 {</span><br><span class="line">public static void main(String[] args) throws FileNotFoundException, InterruptedException {</span><br><span class="line">PrintWriter[] writers = new PrintWriter[4];</span><br><span class="line">int[] lastNums = new int[4];</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">writers[i] = new PrintWriter("D:/" + (i + 1) + ".txt");</span><br><span class="line">lastNums[i] = -1;</span><br><span class="line">}</span><br><span class="line">WriteThreadv2[] ts = new WriteThreadv2[4];</span><br><span class="line">CyclicBarrier barrier = new CyclicBarrier(4);</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">ts[i] = new WriteThreadv2(i, i + 1, writers, lastNums, barrier);</span><br><span class="line">ts[i].start();</span><br><span class="line">}</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">ts[i].join();</span><br><span class="line">writers[i].close();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="利用reentrant-lock和condition">利用Reentrant Lock和Condition</h1><p>这种方法与第一种方法没有本质区别,但是这里使用了ReentrantLock和Condition原语。第一种方法,在一次写入之后,需要notify所有wait的线程,而这种方法我们针对不同的文件不同的数字都有不同的Condition,所以当写了一个数字后,只需要signal等候当前数字的线程,理论上比第一种方法要好一些。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">import java.io.FileNotFoundException;</span><br><span class="line">import java.io.PrintWriter;</span><br><span class="line">import java.util.concurrent.locks.Condition;</span><br><span class="line">import java.util.concurrent.locks.Lock;</span><br><span class="line">import java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line">class WriteThreadv3 extends Thread {</span><br><span class="line">private int index;</span><br><span class="line">private int num;</span><br><span class="line">private PrintWriter[] writers;</span><br><span class="line">private int[] lastNums;</span><br><span class="line">private Lock lock;</span><br><span class="line">private Condition[][] cond;</span><br><span class="line"></span><br><span class="line">public WriteThreadv3(int index, int num, PrintWriter[] writers, int[] lastNums, Lock lock, Condition[][] cond) {</span><br><span class="line">this.index = index;</span><br><span class="line">this.num = num;</span><br><span class="line">this.writers = writers;</span><br><span class="line">this.lastNums = lastNums;</span><br><span class="line">this.lock = lock;</span><br><span class="line">this.cond = cond;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public void run() {</span><br><span class="line">int ln = (num - 2 + 4) % 4 + 1;</span><br><span class="line">for (int i = 0; i < 10; i++) {</span><br><span class="line">lock.lock();</span><br><span class="line">try {</span><br><span class="line">while (!(lastNums[index] == -1 && index == num - 1) && lastNums[index] != ln)</span><br><span class="line">cond[index][ln-1].await();</span><br><span class="line">writers[index].write("" + num);</span><br><span class="line">lastNums[index] = num;</span><br><span class="line">cond[index][num-1].signal();</span><br><span class="line">} catch (InterruptedException e) {</span><br><span class="line">e.printStackTrace();</span><br><span class="line">} finally {</span><br><span class="line">lock.unlock();</span><br><span class="line">}</span><br><span class="line">index = (index - 1 + 4) % 4;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public class FourThreadv3 {</span><br><span class="line">public static void main(String[] args) throws FileNotFoundException, InterruptedException {</span><br><span class="line">PrintWriter[] writers = new PrintWriter[4];</span><br><span class="line">int[] lastNums = new int[4];</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">writers[i] = new PrintWriter("D:/" + (i + 1) + ".txt");</span><br><span class="line">lastNums[i] = -1;</span><br><span class="line">}</span><br><span class="line">WriteThreadv3[] ts = new WriteThreadv3[4];</span><br><span class="line">Lock lock = new ReentrantLock();</span><br><span class="line">Condition[][] cond = new Condition[4][];</span><br><span class="line">for(int i = 0 ; i < 4; i++) {</span><br><span class="line">cond[i] = new Condition[4];</span><br><span class="line">for(int j = 0 ; j < 4; j++)</span><br><span class="line">cond[i][j] = lock.newCondition();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">ts[i] = new WriteThreadv3(i, i + 1, writers, lastNums, lock, cond);</span><br><span class="line">ts[i].start();</span><br><span class="line">}</span><br><span class="line">for (int i = 0; i < 4; i++) {</span><br><span class="line">ts[i].join();</span><br><span class="line">writers[i].close();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>最近在准备Tango的面试,猎头给的参考资料中有这么一道题目:</p>
<blockquote>
<p>四个线程,一线程输出1,二线程输出2,三线程输出3,四线程输出4。分别向四个文件输出,使文件的内容如下:</p>
<ul>
<li>file1:123412341234…………</li>
<li>file2:234123412341…………</li>
<li>file3:341234123412…………</li>
<li>file4:412341234123…………</li>
</ul>
</blockquote>
<p>后来查了一下,发现这又是一道早些年Google的经典面试题。</p>
<p>为了解决这道题目,也弥补一下自己稀缺的并发知识,于是在一周内啃完了《Java Concurrency in Practice》这本书。后来在面试中,和面试官聊到并发的话题时,随便扯扯书中说的CAS,面试官就不问什么了,呵呵。</p>
<p><img src="/img/javaconcurrencyinpractice.jpg" alt="javaconcurrencyinpractice" title="javaconcurrencyinpractice"></p>
<p>好了,言归正传,下面就来给出我对这道题目的三个解法:</p></summary>
<category term="Java" scheme="https://andimeo.us.kg/categories/Java/"/>
<category term="Concurrency" scheme="https://andimeo.us.kg/tags/Concurrency/"/>
</entry>
<entry>
<title>生成圆内随机点</title>
<link href="https://andimeo.us.kg/2014/11/07/%E7%94%9F%E6%88%90%E5%9C%86%E5%86%85%E9%9A%8F%E6%9C%BA%E7%82%B9/"/>
<id>https://andimeo.us.kg/2014/11/07/%E7%94%9F%E6%88%90%E5%9C%86%E5%86%85%E9%9A%8F%E6%9C%BA%E7%82%B9/</id>
<published>2014-11-07T09:10:00.000Z</published>
<updated>2024-09-07T07:25:33.406Z</updated>
<content type="html"><![CDATA[<p>今天小伙伴去网易游戏面试。有一个题目是要求在直径为1的圆内随机生成若干个点。他给出的算法是在边长为1的圆外接正方形内随机生成一个点,如果在圆内即采用,否则抛弃。面试官说要求一次成功。于是问题就来了。</p><p>一个直观的想法是采用极坐标表示圆内的任意一点,在$0-r$的范围内生成半径长度,在$0-2\pi$的范围内生成角度。那我们看一下用Mathematica模拟生成的这些点是不是符合直觉呢。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">{% raw %} </span><br><span class="line">r = 0.5;</span><br><span class="line">num = 500;</span><br><span class="line">tu[0] = ParametricPlot[{r*Sin[u], r*Cos[u]}, {u, 0, 2 Pi}];</span><br><span class="line">For[i = 1, i <= num, i++, u = r*Random[];</span><br><span class="line"> v = 2 Pi*Random[];</span><br><span class="line"> tu[i] = ListPlot[{{u*Sin[v], u*Cos[v]}}];];</span><br><span class="line">Show[Table[tu[i], {i, 0, num}], Axes -> None]</span><br><span class="line">{% endraw %}</span><br></pre></td></tr></table></figure><span id="more"></span><p><img src="/img/mathematica1.jpg" alt="mathematica1" title="mathematica1"></p><p>从图中可以看出,似乎圆中心部分的点比较密集,而圆外围的点则比较稀疏。我们不妨简单的分析一下。为了方便计算,我们将圆的半径设为1。那么如果我们选取一个点的时候,半径长度是均匀的,那么落在以$r$为半径的的圆内的概率就是$r$。假设现在我们有半径长度$r$和$2r$,那么落在面积为$\pi r^2$内的概率为r,落在面积为$4\pi r^2$内的概率为2r,两者相减可以知道,半径为$2r$的圆比半径为$r$的圆多出来的圆环面积为$3\pi r^2$,但是点落在其中的概率却和落在面积为$\pi r^2$的圆中一样。也就是说,越靠近圆心点越密集。</p><p>于是我们需要让$r$的生成概率与其对应的面积成正比,即首先根据$0-r^2$随机生成一个数,然后将这个数开平方作为半径长度。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">{% raw %} </span><br><span class="line">r = 0.5;</span><br><span class="line">num = 500;</span><br><span class="line">tu[0] = ParametricPlot[{r*Sin[u], r*Cos[u]}, {u, 0, 2 Pi}];</span><br><span class="line">For[i = 1, i <= num, i++, u = (r^2*Random[])^(1/2);</span><br><span class="line"> v = 2 Pi*Random[];</span><br><span class="line"> tu[i] = ListPlot[{{u*Sin[v], u*Cos[v]}}];];</span><br><span class="line">Show[Table[tu[i], {i, 0, num}], Axes -> None]</span><br><span class="line">{% endraw %}</span><br></pre></td></tr></table></figure><p><img src="/img/mathematica2.jpg" alt="mathematica2" title="mathematica2"></p><p>这次的图应该与我们的结果相符。</p><p>另外还有一种拍脑袋的想法,那就是先生成x坐标,再生成y坐标。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{% raw %} </span><br><span class="line">r = 0.5;</span><br><span class="line">num = 500;</span><br><span class="line">tu[0] = ParametricPlot[{r*Sin[u], r*Cos[u]}, {u, 0, 2 Pi}];</span><br><span class="line">For[i = 1, i <= num, i++,</span><br><span class="line"> u = 2 r * Random[] - r;</span><br><span class="line"> v = 2*(r^2 - u^2)^(1/2)*Random[] - (r^2 - u^2)^(1/2);</span><br><span class="line"> tu[i] = ListPlot[{{u, v}}];</span><br><span class="line"> ];</span><br><span class="line">Show[Table[tu[i], {i, 0, num}], Axes -> None]</span><br><span class="line">{% endraw %}</span><br></pre></td></tr></table></figure><p><img src="/img/mathematica3.jpg" alt="mathematica3" title="mathematica3"></p><p>经过前面的分析,我们应该能轻松的发现这种生成方法,会使得圆两端的密度比中间高,因为我们首先是按照$x$来随机挑选的,而$x$与其对应的面积却不是线性关系。上图也印证了我们的想法。</p><p>其实这个题目是一个十分Trivial的题目,之所以记录在这里是因为,刚听到这个题目的时候也脱口而出了那种错误的方法。另外也记录一下自己学习Mathematica的脚步。</p>]]></content>
<summary type="html"><p>今天小伙伴去网易游戏面试。有一个题目是要求在直径为1的圆内随机生成若干个点。
他给出的算法是在边长为1的圆外接正方形内随机生成一个点,如果在圆内即采用,否则抛弃。面试官说要求一次成功。
于是问题就来了。</p>
<p>一个直观的想法是采用极坐标表示圆内的任意一点,在$0-r$的范围内生成半径长度,在$0-2\pi$的范围内生成角度。那我们看一下用Mathematica模拟生成的这些点是不是符合直觉呢。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;% raw %&#125; </span><br><span class="line">r = 0.5;</span><br><span class="line">num = 500;</span><br><span class="line">tu[0] = ParametricPlot[&#123;r*Sin[u], r*Cos[u]&#125;, &#123;u, 0, 2 Pi&#125;];</span><br><span class="line">For[i = 1, i &lt;= num, i++, u = r*Random[];</span><br><span class="line"> v = 2 Pi*Random[];</span><br><span class="line"> tu[i] = ListPlot[&#123;&#123;u*Sin[v], u*Cos[v]&#125;&#125;];];</span><br><span class="line">Show[Table[tu[i], &#123;i, 0, num&#125;], Axes -&gt; None]</span><br><span class="line">&#123;% endraw %&#125;</span><br></pre></td></tr></table></figure></summary>
<category term="概率" scheme="https://andimeo.us.kg/categories/%E6%A6%82%E7%8E%87/"/>
<category term="Mathematics" scheme="https://andimeo.us.kg/tags/Mathematics/"/>
</entry>
<entry>
<title>Git工作模式</title>
<link href="https://andimeo.us.kg/2014/08/23/Git%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F/"/>
<id>https://andimeo.us.kg/2014/08/23/Git%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F/</id>
<published>2014-08-23T06:14:50.000Z</published>
<updated>2024-09-07T12:49:37.170Z</updated>
<content type="html"><![CDATA[<p>昨天晚上小伙伴问到我,如何回滚git repository的远程版本。折腾了近一个小时后,发现需要做的是,</p><ul><li>git reset --hard $version_number</li><li>git push -f</li></ul><p>找到答案之后,在stackoverflow上又看到这样一段对于git工作模式的描述,</p><blockquote><p>A typical distributed workflow using Git is for a contributor to fork a project, build on it, publish the result to her public repository, and ask the "upstream" person (often the owner of the project where she forked from) to pull from her public repository. Requesting such a "pull" is made easy by the git request-pull command.</p></blockquote><p>此时我不禁发现,对git的理解还是太浅薄。当初推荐小伙伴使用git时,我只能说出git比svn好,却不知为何更好。显然这样的表达是毫无说服力的。在网上一番搜索之后,发现一篇文章,探讨了四种常见的Git工作模式。我决定将本文翻译于此,算是对用了半年git的一个交代吧。</p><p>值得注意的是,如文章作者在一开始交代的,这四种方式只是四个典型的应用方式。我们不应该仅限于这五种方式,我们可以对这些方法进行融汇、修剪,创造出最适合自己团队的工作模式。</p><span id="more"></span><p><a href="https://www.atlassian.com/git/workflows">Git Workflows</a></p><h1 id="overview">Overview</h1><p>由于不知道Git可能的工作模式,刚刚在工作中接触Git的人可能会感到有些困难。本文描述了最常见的几种Git工作模式,希望以此作为新人探索Git世界的一个起点。</p><p>当你在阅读本文的时候,这些工作模式只是一些指导意见,而不是不可违背的规则。我们希望通过告诉你什么是可能的,让你能够根据个人的需求混合、订制自己的工作模式。</p><h1 id="centralized-workflow">Centralized Workflow</h1><p>转到一个分布式的版本控制系统看起来是一个令人畏惧的任务,但是你并不必改变现有的工作模式就可以享受Git带来的好处。你的团队可以按照与svn一样的模式进行工作。</p><p><img src="/img/central1.png" alt="central1" title="central1"></p><p>然而,相比svn而言,在你的工作流程中使用Git会带来几个好处。第一,每个开发者都是整个项目的本地备份。这种隔离的工作环境可以使每个开发者独立的工作,他们可以提交修改到自己的本地仓库,可以忽视忘记上游的开发过程。直到合适的时候再通知上游。</p><p>第二,Git提供了健壮的分支和合并模型。与svn不同,Git的分支为集成代码和分享改动采用了一种fail-safe的机制。</p><h2 id="how-it-works">How it works</h2><p>就像Subversion一样,Centralized-workflow使用一个中央仓库作为代码提交的唯一入口。Svn中使用trunk,而Git中默认的开发分支叫作mster,所有的改动都会提交到这个分支。这种工作模式master以外的其他任何分支。</p><p><img src="/img/central2.png" alt="central2" title="central2"></p><p>开发者首先clone这个中央仓库。在他们自己的本地备份中,他们修改文件、提交改动,就和在svn中一样。然而,这些提交仅仅储存在本地,它们和远程的那个中央仓库是完全隔离的。这就允许开发者可以在自己满意的一个时间点同步自己的代码到上游仓库中。</p><p>开发者通过push,将自己的master分支推送到中央仓库,来完成发布改动的过程。这和svn commit等价,除了git push会把所有本地提交里远程仓库中没有的都提交给中央仓库的master分支。</p><p><strong>Managing Conflicts</strong></p><p>中央仓库代表官方工程,所以它的提交历史应该是正式且不可变的。如果一个开发者的本地提交偏离了中央仓库,Git将会拒绝push他的改动,因为这会覆盖掉官方的提交。</p><p><img src="/img/central3.png" alt="central3" title="central3"></p><p>在开发者发布他们的修改之前,他们需要先取到中央仓库中本地没有的那些提交,将自己的改动建立在那些提交之上。这就好像是说,"我想把自己的改动添加到其他人已经做过的改动之上。"结果是一个绝对线性的提交历史,就跟传统的svn工作模式一样。</p><p>如果本地改动和上游的提交直接产生了冲突,Git将会暂停并让你手动的解决这些冲突。Git的好处之一是它使用了git status和git add同时用于生成提交和解决合并冲突。这使得新开发者更容易管理自己的合并。而且,如果他们陷入了麻烦,git就会停止合并过程,让开发者重试或者寻求帮助。</p><h2 id="example">Example</h2><p>让我们来一步一步的看看一个典型的小团队如何在这种模式下合作。我们将会看到两个开发者,John和Mary,通过一个中央仓库,分别进行两个feature的开发并且分享他们的贡献。</p><p><strong>Someone initializes the central repository</strong></p><p><img src="/img/central4.png" alt="central4" title="central4"></p><p>首先,有个人需要在一台服务器上创建中央仓库。如果这是一个新工程,你可以初始化一个空的仓库。否则,你需要导入一个已有的项目。</p><p>中央仓库应该是空的仓库,它不应该是一个已有的工作目录。我们可以这样创建,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ssh user@host</span><br><span class="line">git init --bare /path/to/repo.git</span><br></pre></td></tr></table></figure><p>确保user是正确的SSH Username,host是你的服务器的域名或者IP,还有你希望存放仓库的位置。</p><p><strong>Everybody clones the central repository</strong></p><p><img src="/img/central4.png" alt="central4" title="central4"></p><p>接下来,每个开发者创建一个整个工程的本地备份。我们可以通过命令git clone完成,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone ssh://user@host/path/to/repo.git</span><br></pre></td></tr></table></figure><p>当你克隆了一个仓库,Git将会自动为你添加一个叫作origin的标签,这个标签指回到父仓库,以便你今后和其进行交互。</p><p><strong>John works on his feature</strong></p><p><img src="/img/central6.png" alt="central6" title="central6"></p><p>在他的本地仓库中,John可以使用标准的Git提交流程进行开发,edit、stage、commit。如果你不熟悉工作目录,有一种方法可以让你指定提交的内容。这可以进行针对性的提交,即使你在本地进行了很多修改。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git status # View the state of the repo</span><br><span class="line">git add # Stage a file</span><br><span class="line">git commit # Commit a file</span><br></pre></td></tr></table></figure><p>记住,因为这些命令只完成了本地的提交工作,John可以重复这个过程,随意进行提交,而不必担心远程的中央仓库发生了什么。这对于大feature而言非常有用,因为这些大feature往往需要打碎成更多更简单、更小的部分。</p><p><strong>Mary works on her feature</strong></p><p><img src="/img/central7.png" alt="central7" title="central7"></p><p>于此同时,Mary也在她本地进行自己的feature开发,使用同样的edit、stage、commit流程。和John一样,她不必担心远程的中央仓库正在发生什么。而且也不担心John在他的本地仓库正在干什么,因为所有的本地仓库都是私有的。</p><p><strong>John publishes his feature</strong></p><p><img src="/img/central8.png" alt="central8" title="central8"></p><p>一旦John完成了他的feature,他就应该把自己的本地提交发布到中央仓库,使其他团队成员可以访问。他可以通过git push来完成,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin master</span><br></pre></td></tr></table></figure><p>记住,origin是当John clone的时候Git创建的指向中央仓库的远程连接。参数master告诉git,他希望让origin的master分支和他本地的master分支一样。因为中央仓库在John clone之后还没有被更新过,所以不会导致冲突,push将会顺利完成。</p><p><strong>Mary tries to publish her feature</strong></p><p><img src="/img/central9.png" alt="central9" title="central9"></p><p>我们来看看当John成功发布之后,Mary试图发布的时候会发生什么。她可以使用完全一样的命令,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin master</span><br></pre></td></tr></table></figure><p>但是,因为她本地的历史与中央仓库的历史发生了偏离,Git将会拒绝她的请求。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">error: failed to push some refs to '/path/to/repo.git'</span><br><span class="line">hint: Updates were rejected because the tip of your current branch is behind</span><br><span class="line">hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')</span><br><span class="line">hint: before pushing again.</span><br><span class="line">hint: See the 'Note about fast-forwards' in 'git push --help' for details.</span><br></pre></td></tr></table></figure><p>这阻止了Mary覆盖官方的提交。她需要首先将John的提交拿到本地,和本地的修改进行集成,然后重试。</p><p><strong>Mary rebases on top of John's commits</strong></p><p><img src="/img/central10.png" alt="central10" title="central10"></p><p>Mary可以使用git pull将上游的提交混合到本地。这个命令有点像svn update,它把上游的提交历史取到本地,并试图和本地的修改合并。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull --rebase origin master</span><br></pre></td></tr></table></figure><p>选项--rebase告诉Git在同步了中央仓库的改动之后,把Mary的改动移到master分支的顶部。如下图所示,</p><p><img src="/img/central11.png" alt="central11" title="central11"></p><p>如果你忘了那个选项,pull仍然可以工作,但是每次有人需要同步中央仓库的时候,都会出现很多"merge commit"。对于这种工作模式而言,最好rebase,而不要生成merge commit。</p><p><strong>Mary resolves a merge conflict</strong></p><p><img src="/img/central12.png" alt="central12" title="central12"></p><p>Rebase把本地的提交一次一个的放到更新过的master上。这意味着merge conflict只会发生在你的一次提交之上,而不是把你的所有提交作为整体进行合并。这可以使你的提交更有针对性,并且保持一个干净的提交历史。相应的,这使得找到bug是在哪里引进的更加容易,如果必要,可以对项目以最小代价进行回滚操作。</p><p>如果Mary和John分别工作在无关的feature上,rebase的过程不大可能会发生冲突。但是如果冲突发生了,Git将会在当前提交处暂停rebase的过程,并且输入如下信息,和一些相应的提示,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CONFLICT (content): Merge conflict in <some-file></span><br></pre></td></tr></table></figure><p><img src="/img/central13.png" alt="central13" title="central13"></p><p>Git的一个伟大之处在于,任何人都可以解决他们自己的合并冲突。在我们的例子中,Mary可以简单的执行git status看看问题发生在哪里。冲突文件将会出现在Unmerged path一节。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># Unmerged paths:</span><br><span class="line"># (use "git reset HEAD <some-file>..." to unstage)</span><br><span class="line"># (use "git add/rm <some-file>..." as appropriate to mark resolution)</span><br><span class="line">#</span><br><span class="line"># both modified: <some-file></span><br></pre></td></tr></table></figure><p>然后,她就可以把文件修改成自己需要的样子。一旦她修改完毕,她就可以将文件置入工作区,让git rebase继续做剩下的事情,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git add <some-file></span><br><span class="line">git rebase --continue</span><br></pre></td></tr></table></figure><p>这就是需要做的所有事情了。Git将会移动到下一个提交,对所有产生冲突的提交重复刚才的步骤。</p><p>如果你遇到了某种情况,而且解决不了时,不必惊慌。只需要执行下面的命令,你就可以回到执行git pull --rebase之前的状态。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase --abort</span><br></pre></td></tr></table></figure><p><strong>Mary successfully publishes her feature</strong></p><p><img src="/img/central14.png" alt="central14" title="central14"></p><p>当她完成与中央仓库的同步之后。Mary就可以成功发布她的改动了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin master</span><br></pre></td></tr></table></figure><h1 id="feature-branch-workflow">Feature Branch Workflow</h1><p><img src="/img/feature1.png" alt="feature1" title="feature1"></p><p>一旦你熟悉了Centralized Workflow,在你的开发过程中加入feature分支成了一种简单的促进开发者协作的方法。</p><p>Feature Branch Workflow的核心想法在于所有feature都应该在自己的分支中开发,而不是都在master分支中。这种封装使得多个开发者可以在不干扰主代码库的前提下开发自己的feature。这同时意味着master分支将永远不会包含不健全的代码,这对于不断进行的集成开发是一个非常大的好处。</p><p>这种对feature开发的封装,也使得我们可以在开发中利用pull requests,这是一种发起关于某个分支的讨论的方法。这给了其他开发者一个在代码被合并到主项目之前审查某个feature的机会。或者,当你卡在某个feature开发中时,你可以发起一个pull request向你的同事征求意见。总而言之,pull requests以一种非常简单的方式为你的团队提供了评论彼此工作的条件。</p><h2 id="how-it-works">How it works</h2><p>Feature Branch Workflow仍然使用中央仓库,master分支仍然代表官方工程的历史。但是,并不是直接提交到本地的master分支,开发者每次开始一个新feature时,首先需要创建一个新的分支。Feature分支应该有描述性的名称,比如animated-menu-items或者issue-#1061.这是为了给每个分支提供一个清晰、明确的目的。</p><p>Git对于master分支和feature分支没有本质上的区分,所以开发者可以edit、stage并且commit改动到feature分支,就和在Centralized Workflow中一样。</p><p>除此之外,feature分支可以被合并到中央仓库中。这就可以在不动官方代码的前提下在开发者之间共享一个feature。因为master是唯一的特殊分支,在中央仓库中储存多个feature分支并不会带来什么问题。当然,这也是一种备份本地提交的好办法。</p><p><strong>Pull Requests</strong></p><p>除了隔离feature的开发环境之外,分支使得我们可以通过pull requests来讨论代码改动。一旦某人完成了一个feature,他们不需要立刻合并到master中。他们会合并到feature分支,然后发起一个pull request要求合并他们的改动到master中。这给了其他开发者一个在其进入主代码库之前审查代码改动的机会。</p><p>代码审查时pull requests的一个好处,但是它却是被设计做一种讨论代码的通用方法。你可以认为pull requests是针对某个分支的讨论。这意味着也可以在开发流程中更早的阶段使用它。比如说,如果一个开发者需要帮助,他就可以发起一个pull request。相关的人会被自动通知,他们就可以看到提交下面的问题了。</p><p>一旦一个pull request被接受了,发布一个feature的过程和Centralized Workflow是一样的。首先,需要确保本地的master分支和上游的master进行了同步,然后,你将feature分支合并到master,再合并到中央仓库的master中。</p><p>一些代码管理工具可以帮助我们处理pull requests,如Bitbucker或Stash。</p><h2 id="example">Example</h2><p>下面的例子将演示如何将pull requests作为代码审查的一种形式,但是切记它还可以用于很多其他的目的。</p><p><strong>Mary begins a new feature</strong></p><p><img src="/img/feature2.png" alt="feature2" title="feature2"></p><p>在她开始开发一个feature之前,她需要一个隔离的分支。她可以新开一个分支,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b marys-feature master</span><br></pre></td></tr></table></figure><p>这检出了一个叫作marys-feature的分支,基于master。选项-b告诉Git如果这个分支不存在,就创建一个。在这个分支上,Mary edit、stage并且commit,按照通常的方式,提交多次后建立起了她的feature。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br><span class="line">git add <some-file></span><br><span class="line">git commit</span><br></pre></td></tr></table></figure><p><strong>Mary goes to lunch</strong></p><p><img src="/img/feature3.png" alt="feature3" title="feature3"></p><p>在早上Mary为她的分支进行了若干次提交。在她去吃午饭之前,应该把她的feature分支上传到中央仓库。这相当于进行了备份,但是如果Mary和其他开发者进行协作,那这使得其他开发者可以访问她的提交内容了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push -u origin marys-feature</span><br></pre></td></tr></table></figure><p>这个命令将marys-feature上传到中央仓库origin,选项-u将其添加为一个远程跟踪分支。设置好这个跟踪分支后,Mary可以直接执行git push,无需任何其他参数就可以上传feature了。</p><p><strong>Mary finishes her feature</strong></p><p><img src="/img/feature4.png" alt="feature4" title="feature4"></p><p>当Mary吃完午饭回来后,她完成了她的feature。在她将其合并入master之前,她需要发起一个pull request让组内其余的人知道她完成了。但是首先,她需要确保中央仓库已经有了她最新的提交,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push</span><br></pre></td></tr></table></figure><p>然后,她在她的Git GUI中发起了一个pull request,请求合并marys-feature到master,其他组员将会自动收到提醒。pull requests允许在提交的下面进行评论,所以这对于问问题、讨论来说非常简单。</p><p><strong>Bill receives the pull request</strong></p><p><img src="/img/feature5.png" alt="feature5" title="feature5"></p><p>Bill收到了pull request并且查看了marys-feature。他决定在集成到官方工程之前进行一些修改,然后他和Mary前前后后通过pull request进行了一番交流。</p><p><strong>Mary makes the changes</strong></p><p><img src="/img/feature6.png" alt="feature6" title="feature6"></p><p>Mary通过edit、stage、commits完成了修改,并且上传到了中央仓库。她所有的活动都在pull request中有所展现,Bill仍然可以进行评论。</p><p>如果他愿意,Bill可以拿到marys-feature,在自己的本地备份中工作。任何他的提交也会出现在pull request中。</p><p><strong>Mary publishes her feature</strong></p><p>一旦Bill决定要接受这个pull request了,某个人需要合并这个feature到稳定的工程中去,这个人既可以是Bill也可以使Mary。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git checkout master</span><br><span class="line">git pull</span><br><span class="line">git pull origin marys-feature</span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p>首先,不论是谁都要检出master分支,然后确保它是最新的。然后,git pull origin marys-feature将中央仓库中的marys-feature合并到本地的master。有也可以简单的使用git merge marys-feature,但是上面的命令确保你总是拿到最新的feature branch。最后,更新过的master需要上传到origin去。</p><p>一些GUI能够自动化pull request的接受过程,仅仅需要点击Accept按钮,就可以触发一系列命令完成工作。如果你的不行,它至少也可以在合并代码之后自动关闭这个pull request。</p><p><strong>Meanwhile, John is doing the exact same thing</strong></p><p>当Mary和Bill开发marys-feature,在Mary的pull request中讨论时,John也在做同样的事情。通过隔离feature到不同的分支,所有人都可以独立的工作,如果必要和其他开发者共享代码改动也是很轻松的事情。</p><h1 id="gitflow-workflow">Gitflow Workflow</h1><p><img src="/img/gitflow1.png" alt="gitflow1" title="gitflow1"></p><p>Gitflow Workflow源自Vincent Driessen在<a href="http://nvie.com/">nvie</a>的文章。</p><p>Gitflow Workflow定义了一个用于工程发布的严格的分支模型。比起Feature Branch Workflow稍微复杂一些,其为管理较大项目提供了一个可靠的框架。</p><p>这种工作模式在Feature Branch Workflow的基础上没有增加新的概念或者命令。它只是为不同的分支定义了明确的角色,并且定义它们之间何时以及如何交互。与Feature Branch Workflow相比,它为准备、维护、发布定义了自己的分支。当然,你也可以享受到所有Feature Branch Workflow拥有的好处:pull requests,隔离环境,和更加有效的协作。</p><h2 id="how-it-works">How it works</h2><p>Gitflow Workflow仍然使用一个中央仓库。与其他工作模式相同,开发者可以在本地工作,然后再将分支push到中央仓库中。唯一的不同在于工程的分支结构。</p><p><strong>Historical Branches</strong></p><p>与单一master分支不同,本工作模式使用两个分支来记录工程的历史。master分支存储官方发布的历史,develop分支用于集成feature。要为master分支的每一个提交打上一个版本号作为tag也是很方便的。</p><p><img src="/img/gitflow2.png" alt="gitflow2" title="gitflow2"></p><p>本工作模式的其余部分都在考虑让这两个分支进行交互。</p><p><strong>Feature Branches</strong></p><p>每一个新feature应该在自己的分支上,这个分支可以被push到中央仓库以便备份或者协作之用。但是,feature分支把develop分支作为父分支,而不是从master分支上分裂出来。当一个feature完成后,它会被合并会develop分支。Features永远都不应该直接和master交互。</p><p><img src="/img/gitflow3.png" alt="gitflow3" title="gitflow3"></p><p>注意feature分支和develop分支就是Feature Branch Workflow。但是Gitflow Workflow并不只是这样。</p><p><strong>Release Branches</strong></p><p><img src="/img/gitflow4.png" alt="gitflow4" title="gitflow4"></p><p>当develop收集到了足够一次发布的features时,或者一个预先约定的发布日期到达时,你就从develop分裂出一个分支。这个分支的创建就代表着一个发布周期的开始,所以这个时间点之后任何新feature都不能加进来,除了修复bug,文档生成,和其他发布相关的任务才能进入这个分支。当这个分支可以发布时,这个分支将会合并到master,并且用版本号打上一个tag。除此之外,还应该合并会develop分支,因为这个分支可能包含了在创建之初还没有的进展。</p><p>使用一个特定的分支来准备发布,就可以让一个团队继续优化当前版本,而另一个团队继续为下个版本开发新feature。它同时也创建了良好定义的开发术语,比如我们可以说,"这周我们准备4.0版的发布",实际上我们也可以在代码仓库中看到这个结构。</p><p><strong>Maintenance Branches</strong><img src="/img/gitflow5.png" alt="gitflow5" title="gitflow5"></p><p>Maintenance或者说hotfix分支用来对生产环境的版本进行快速修复。这是唯一可以直接从master分裂的分支。一旦修复完成,就应该马上被合并到master和develop分支中,而且master应该用更新版本号打上一个tag。</p><p>拥有一个特定的开发线路供修复bug使得你的团队可以在不干扰其他工作流程,也不用等待下个发布周期的前提下处理issue。你可以认为maintenance分支是一个直接和master分支交互的发布分支。</p><h2 id="example">Example</h2><p>下面的例子展示了这个工作模式如何处理一个发布周期。我们假设已经创建了一个中央仓库。</p><p><strong>Create a develop branch</strong></p><p><img src="/img/gitflow6.png" alt="gitflow6" title="gitflow6"></p><p>第一步是在默认master分支的基础上补全一个develop分支。一个简单的方法是在本地创建一个空的develop分支,然后push到服务器上,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git branch develop</span><br><span class="line">git push -u origin develop</span><br></pre></td></tr></table></figure><p>这个分支包含了整个工程的完整历史,而master只包含了一个削减过的版本。其他开发者现在应该clone中央仓库,并且创建一个develop的跟踪分支。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git clone ssh://user@host/path/to/repo.git</span><br><span class="line">git checkout -b develop origin/develop</span><br></pre></td></tr></table></figure><p>现在所有人都在本地建立起了一份历史分支的备份。</p><p><strong>Mary and John begin new features</strong></p><p><img src="/img/gitflow7.png" alt="gitflow7" title="gitflow7"></p><p>我们的例子开始于John和Mary要开发不同的feature。他们都需要创建各自的分支。他们不应该以master作为基础,而应该以develop作为基础,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b some-feature develop</span><br></pre></td></tr></table></figure><p>他们两个都通过edit、stage、commit的方法,向各自的feature分支进行提交。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br><span class="line">git add <some-file></span><br><span class="line">git commit</span><br></pre></td></tr></table></figure><p><strong>Mary finishes her feature</strong></p><p><img src="/img/gitflow8.png" alt="gitflow8" title="gitflow8"></p><p>在提交了若干次之后,Mary认为她的feature已经完成了。如果她的团队使用pull requests,这就是一个合适的发起一个pull request请求合并她的feature到develop的时间。否则,她可以合并到本地的develop然后push到中央仓库,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git pull origin develop</span><br><span class="line">git checkout develop</span><br><span class="line">git merge some-feature</span><br><span class="line">git push</span><br><span class="line">git branch -d some-feature</span><br></pre></td></tr></table></figure><p>第一个命令确保本地的develop分支是最新的。注意feature始终不应该直接合并到master中。冲突的解决方法和Centralized Workflow中描述的一样。</p><p><strong>Mary begins to prepare a release</strong></p><p><img src="/img/gitflow9.png" alt="gitflow9" title="gitflow9"></p><p>当John仍然在开发他的feature时,Mary开始准备项目的第一个官方发行。跟feature开发一样,她使用一个新的分支来封装发行的准备工作。这一步也是创建发行版本号的时候,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b release-0.1 develop</span><br></pre></td></tr></table></figure><p>这个分支用来整理、测试、更新文档,以及为下一次发行做一切准备工作。这就是像是一个为了优化发行版本的feature分支。</p><p>一旦Mary创建了这个分支,并且push到了中央仓库,这个发行就变成feature-frozen了。任何不在develop中的功能都要推迟到下个发布周期。</p><p><strong>Mary finishes the release</strong></p><p><img src="/img/gitflow10.png" alt="gitflow10" title="gitflow10"></p><p>当release准备好上线时,Mary将其合并回master和develop,然后删除这个发行分支。合并会develop是非常重要的,因为重要的修改可能被加入了这个发行分支,他们对于新的feature而言是有用的。如果Mary的组织强调代码审查,这也是一个理想的发起pull request的位置。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">git checkout master</span><br><span class="line">git merge release-0.1</span><br><span class="line">git push</span><br><span class="line">git checkout develop</span><br><span class="line">git merge release-0.1</span><br><span class="line">git push</span><br><span class="line">git branch -d release-0.1</span><br></pre></td></tr></table></figure><p>release分支好像是一个feature开发版本(develop)与公开发行版本(master)之间的缓冲区。不论什么时候你合并回master,你都应该为提交打上tag,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git tag -a 0.1 -m "Initial public release" master</span><br><span class="line">git push --tags</span><br></pre></td></tr></table></figure><p>Git提供了一些脚本,可以在代码仓库发生了一些特定的事件时触发。你可以配置,使得每当有master分支或者一个tag被push到中央仓库时,自动的创建一个公开发行版本。</p><p><strong>End-user discovers a bug</strong></p><p><img src="/img/gitflow11.png" alt="gitflow11" title="gitflow11"></p><p>在发布之后,Mary回到下个版本的feature开发中。直到一个终端用户在现有版本中发现了一个bug。为了处理这个bug,Mary从master创建了一个maintenance分支,修复bug,然后直接合并回master。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b issue-#001 master</span><br><span class="line"># Fix the bug</span><br><span class="line">git checkout master</span><br><span class="line">git merge issue-#001</span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p>和release分支一样,maintenance分支包括了重要的修改,这些修改也应该合并到develop中。然后,就可以删掉这个分支了,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git checkout develop</span><br><span class="line">git merge issue-#001</span><br><span class="line">git push</span><br><span class="line">git branch -d issue-#001</span><br></pre></td></tr></table></figure><h1 id="forking-workflow">Forking Workflow</h1><p>Forking Workflow和本文中谈到的其他工作模式都不相同。它并不是只有一个单独的服务器端的仓库来扮演中央代码库,在这个模式中,每个开发者都有一个自己的服务器端的仓库。这意味着每一个贡献者都有两个Git的仓库,一个私有的本地的,一个共有的服务器端的。</p><p><img src="/img/forking1.png" alt="forking1" title="forking1"></p><p>Forking Workflow最大的好处在于不用所有人都push到一个中央仓库。开发者可以push自己的服务器端仓库,只有项目的维护者才能push到官方的仓库。它允许维护者维护者接受其他开发者的提交,而不给他们对官方代码库的写权限。</p><p>这就构成了一种分布式的工作模式。为大型团队安全的协作提供了一种灵活的方式。</p><h2 id="how-it-works">How it works</h2><p>跟其他Git工作模式一样,Forking Workflow开始于服务器端的官方公开仓库。但是当一个新开发者想要在这个项目上工作时,他并不能呢个直接clone这个官方工程。</p><p>他需要先fork这个官方工程,在服务器上创建一份自己的备份。这个新的备份就是它的个人公开仓库,其他开发者都不允许push到这个仓库,但是他们可以pull这个仓库的改动到自己的代码中。当他们创建自己服务器端的备份后,开发者执行git clone在本地建立一个备份。这就是他们私人的开发环境,就跟别的工作模式一样。</p><p>当他们想要发布本地的提交时,他们push这个提交到他们自己的公开仓库中,而不是官方的那个。然后,他们发起一个pull request,让官方工程维护者知道有一个更新等待被集成。那个pull request也成为了一个关于提交代码讨论的地方。</p><p>有O把这个feature集成到官方代码库中,维护者pull贡献者的改到到本地,检查功能是否正常,是否打破工程,然后合并到本地master,然后push到服务器的公开仓库中。现在这个feature就是这个工程的一部分了,其他开发者也应该从官方仓库pull这些改动到本地来进行同步。</p><p><strong>The official repository</strong></p><p>在Forking Workflow中的"官方"一词只是一种习惯。从技术角度来说,Git并不认为官方的公共仓库和其他开发者的公共仓库有什么不同。实际上,唯一使得官方仓库官方的原因在于其实工程维护者的公开仓库。</p><p><strong>Branching in the Forking Workflow</strong></p><p>所有这些个人的公共仓库只是一种方便共享分支给其他开发者的方式。所有人仍然应该使用分支来隔离不同的feature,就像Feature Branch Workflow和Gitflow Workflow一样。唯一的区别是这些分支如何共享。在Forking Workflow中,他们被pull到另一个开发者的本地仓库中,而Feature Branch Workflow和Gitflow Workflow则是push到官方仓库中。</p><h2 id="example">Example</h2><p><strong><a href="https://github.com/">github</a></strong></p><p><strong><a href="https://gitcafe.com">gitcafe</a></strong></p>]]></content>
<summary type="html"><p>昨天晚上小伙伴问到我,如何回滚git repository的远程版本。折腾了近一个小时后,发现需要做的是,</p>
<ul>
<li>git reset --hard $version_number</li>
<li>git push -f</li>
</ul>
<p>找到答案之后,在stackoverflow上又看到这样一段对于git工作模式的描述,</p>
<blockquote>
<p>A typical distributed workflow using Git is for a contributor to fork a project, build on it, publish the result to her public repository, and ask the &quot;upstream&quot; person (often the owner of the project where she forked from) to pull from her public repository. Requesting such a &quot;pull&quot; is made easy by the git request-pull command.</p>
</blockquote>
<p>此时我不禁发现,对git的理解还是太浅薄。当初推荐小伙伴使用git时,我只能说出git比svn好,却不知为何更好。显然这样的表达是毫无说服力的。在网上一番搜索之后,发现一篇文章,探讨了四种常见的Git工作模式。我决定将本文翻译于此,算是对用了半年git的一个交代吧。</p>
<p>值得注意的是,如文章作者在一开始交代的,这四种方式只是四个典型的应用方式。我们不应该仅限于这五种方式,我们可以对这些方法进行融汇、修剪,创造出最适合自己团队的工作模式。</p></summary>
<category term="工具" scheme="https://andimeo.us.kg/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="译文" scheme="https://andimeo.us.kg/tags/%E8%AF%91%E6%96%87/"/>
<category term="Git" scheme="https://andimeo.us.kg/tags/Git/"/>
</entry>
<entry>
<title>Arrow's Impossibility Theorem</title>
<link href="https://andimeo.us.kg/2014/07/27/Arrow's-Impossibility-Theorem/"/>
<id>https://andimeo.us.kg/2014/07/27/Arrow's-Impossibility-Theorem/</id>
<published>2014-07-26T16:03:07.000Z</published>
<updated>2024-09-07T12:56:41.092Z</updated>
<content type="html"><![CDATA[<p>最近刚刚看完了曼昆N.Gregory Mankiw的经济学原理,微观经济学分册。这本书是典型的西式教科书,文中穿插着各种真实世界中的事例以及类似纽约时报等媒体在当时的评论。读起来很轻松,如同读故事一般,也许是其中涉及的数学并不很高深,所以不怎么困难就读完了。</p><p>整本书描写了很多如何使用需求与供给来分析各种经济活动,在书末的最后一章中,作者抛出了经济学前沿中的三个主题,分别是<em>不对称信息经济学</em>、<em>政治经济学</em>以及<em>行为经济学</em>。其中在政治经济学中阐述了一个定理,阿罗不可能性定理。这个定理在讨论是否存在一种完美的投票制度。</p><p><img src="/img/microeconomics.jpg" alt="microeconomics" title="microeconomics"></p><span id="more"></span><h1 id="阿罗不可能性定理">阿罗不可能性定理</h1><p>假设社会想设计出一种满足以下几个特征的投票方案:</p><ul><li><strong>确定性(Unanimity)</strong>: 如果每个人对A的偏好都大于B,那么,A就击败了B。</li><li><strong>传递性(Transitivity)</strong>: 如果A击败了B,B击败了C,那么,A一定击败C。</li><li><strong>其他不相关选择的独立性(Independence of Irrelevant Alternative, IIA)</strong>: 任何两个结果A和B之间的排序不应该取决于是否还可以得到某个第三种结果C。</li><li><strong>没有独裁者(No Dictator)</strong>: 无论其他每个人的偏好如何,没有一个人总能获胜。</li></ul><p>第三条有关IIA的描述有些拗口,我们这里用一个例子来说明。在一场投票中,共有三个选项A、B、C,其中有三种投票结果,如下表所示,每种投票结果所占的百分比也已给出。这里我们给排在第一名的选项积3分,第二名2分,第三名1分。我们将看到C的存在与否直接影响A与B在最终结果中的相对顺序。</p><table><thead><tr><th>排名</th><th>Type 1 <em>(35%)</em></th><th>Type 2 <em>(45%)</em></th><th>Type 3 <em>(20%)</em></th></tr></thead><tbody><tr><td>Rank 1</td><td>A</td><td>B</td><td>C</td></tr><tr><td>Rank 2</td><td>B</td><td>C</td><td>A</td></tr><tr><td>Rank 3</td><td>C</td><td>A</td><td>B</td></tr></tbody></table><p>首先我们去掉选项C,这时A的分数为 (35%+20%)*3+45%*2=<strong>2.55</strong> 分,B的分数为 45%*3+(35%+20%)*2=<strong>2.45</strong>分,所以 <strong>A > B</strong>。可是在C选项存在的时候,同理计算可得 <strong>B > A</strong>。这种投票方式就不满足IIA这条性质。</p><p>我们再回到定理上面来。所有这些看起来都是一种投票制度具有的合意特征。但是,阿罗在他的博士论文中,从数学上证明,<strong>没有任何一种投票制度能满足所有这些特征</strong>。这个令人惊讶的结果被称为<strong>阿罗不可能性定理</strong>。</p><p>曼昆的书中表示,该定理的证明所需的数学知识已经超过了该书的范围,于是没有给出证明。我后来Google了关于该定理的证明,发现有两种证明方法并没有用到艰涩、复杂的数学知识,只用到了基本的逻辑推理。在本文中,我将翻译两个关于该定理的证明,一个运用了和阿罗自己的证明方法一样的思想,从前三个假设出发,导出一定存在独裁者,另一个则是非常简短、精炼的证明。虽然这两个证明都属于Informal Proof,但是应该会有助于我们理解这个定理。</p><h1 id="找到独裁者">找到独裁者</h1><p><a href="http://www.ssc.wisc.edu/~dquint/econ698/lecture%202.pdf">证明原文</a></p><h2 id="extremal引理">Extremal引理</h2><p><strong>Extremal 引理</strong>: 对于任意的选项b,如果每个投票者都或者将b排在最好的位置、或者将b排在最差的位置,那么最终的结果中b要么排在最好的位置,要么排在最坏的位置。</p><p><strong>证明</strong>:</p><ul><li>我们用<strong>反证法</strong>来证明。</li><li>假设引理是错的。那么即使每个投票者或者把b排在最好的位置、或者把b排在最差的位置,也会存在选项a和c,使得最终结果中a>b>c。</li><li>现在我们按照下述规则来修改每个投票者的排序结果。<br>对于把b排在最好位置的投票者i,将c移动到第二好的位置上,那么现在c就会在a的前面。<br>对于把b排在最差位置的投票者i,将c移动到最好的位置上,那么c也会在a的前面。</li><li>这样做并没有改变每个投票者对a和b的相对喜好,如果b本来在最好的位置上,现在还在最好的位置上,如果b本来在最差的位置上,现在也还在最差的位置上。所以根据IIA,最终的结果中<strong>a>b</strong>。</li><li>类似地,这样做也并没有改变每个投票者对b和c的相对喜好。所以根据IIA,最终的结果中<strong>b>c</strong>。</li><li>通过传递性可知,已有a>b且b>c,所以有<strong>a>c</strong>。</li><li>但是现在,每个人都把c排在比a好的位置上,所以确定性将得出<strong>c>a</strong>。矛盾。</li><li>综上所述,<strong>最终结果中b要么在最好的位置上,要么在最差的位置上</strong>。</li></ul><h2 id="找到投票者中的pivotal,并给他一个名字">找到投票者中的pivotal,并给他一个名字</h2><ul><li>选择任意选项b。</li><li>根据确定性我们知道,如果每个人都把b放在最差的位置上,那么最终结果中b也在最差的位置;如果每个人把b放在最好的位置上,那么最终结果中b也在最好的位置。</li><li>从<strong>Extremal引理</strong>中,我们知道如果一些人把b放在最好的位置,而其余的人把b放在最差的位置,那么最终结果中b要么在最好的位置,要么在最差的位置。</li><li>我们从这样一种情况开始,所有人都把b放在最差的位置上,那么最终结果b一定也在最差的位置。</li><li>现在我们将投票者1的结果中b的位置从最差的位置移到最好的位置,因为这时所有投票者要么把b放在最好的位置,要么把b放在最差的位置,最终结果中b也要么在最好的位置,要么在最差的位置。</li><li>现在我们将投票者2的结果中b的位置从最差的位置移到最好的位置,同样根据Extremal引理,最终结果中b也要么在最好的位置,要么在最差的位置。</li><li>继续这样进行,直到我们把每个人的投票结果中b的位置都从最差位置移到最好位置。根据确定性,这时b在最终结果中也一定处于最好位置。</li><li>现在我们找到<strong>第一个使得最终结果中b的位置从最差变到最好</strong>的投票者(<em>Pivotal</em>)。我们叫他Bob。<br>也就是说,当所有编号在Bob之前的投票者把b放在最好位置,而Bob和编号在其之后的投票者把b放在最差位置时,最终结果中b在最差位置。当Bob将b从最差变到最好时,最终结果中b在最好位置。</li><li>我们把Bob之前的投票者把b放在最差位置,Bob和之后投票者把b放在最好位置称为<strong>情形1</strong>。类似我们把Bob和之前投票者把b放在最差位置,Bob之后投票者把b放在最好位置称为<strong>情形2</strong>。</li><li>证明的剩下部分将试图说明在满足IIA和传递性的情况下,Bob是一个<strong>独裁者</strong>。也就是说,不论其他人怎么投票,最终结果都和Bob的投票结果一致。</li></ul><h2 id="证明bob是一个独裁者">证明Bob是一个独裁者</h2><h3 id="首先证明,对于不是b的选项,bob是一个独裁者。">首先证明,对于不是b的选项,Bob是一个独裁者。</h3><ul><li>命题即是说,对于不是b的选项a和c而言,如果Bob认为a>c,那么无论别人如果对a、c排序,最终结果中都会有a>c。</li><li>对于Bob将a放在c前面的任何一种情形开始,我们称其为<strong>情形4</strong>。</li><li>如下所述进行修改:<br>把a放到Bob的投票结果中最好的位置,把b放在Bob的投票结果中次好的位置。<br>对于任何编号在Bob之前的投票者,把b放在最好的位置。<br>对于任何编号在Bob之后的投票者,把b放在最差的位置。<br>我们把这种情形称为<strong>情形3</strong>。</li><li>当我们从情形4改变为情形3时,我们并没有改变任何人对a和c的相对排序。所以根据IIA,对情形3和情形4而言,最终结果对a和c的相对偏好是一致的。<br>下面我们将证明在情形3时,最终结果中a>c。</li><li>在情形1中,最终结果把b放在最差位置,即a>b。<br>而且所有人对a和b的偏好都和情形3一致,所以根据IIA,<strong>情形3的最终结果中a>b</strong>。</li><li>在情形2中,最终结果把b放在最好位置,即b>c。<br>而且所有人对b和c的偏好都和情形3一致,所以根据IIA,<strong>情形3的最终结果中b>c</strong>。</li><li>根据传递性,情形3的最终结果中a>c。</li><li>所以在情形4的最终结果中a>c。</li><li>所以<strong>只要Bob的投票结果中有a>c,最终结果中也一定有a>c</strong>。</li></ul><h3 id="对于选项b而言,bob也是一个独裁者">对于选项b而言,Bob也是一个独裁者</h3><ul><li>这部分比较简单。我们只需要证明,如果Bob觉得b比a好,那么最终结果中b>a;如果Bob觉得a比b好,那么最终结果中a>b。</li><li>现在我们考虑一个不是a、b的选项c,然后重复我们之前做过的事情。</li><li>从Extremal引理开始,不断的将c从最差位置移到最好位置,找到pivotal,那么这个人对于不是c的任何两个选项而言都是一个独裁者。<br>至今,我们还不知道这个新的独裁者是Bob还是别人。这个人只是当c被排除在外时的独裁者。</li><li>但是我们已经知道Bob的偏好是会影响最终结果的。<br>-当我们将情形1变为情形2时,Bob对于b的偏好是唯一改变的东西。而且这将最终结果中b的位置从最差变为最好。</li><li>如果新独裁者不是Bob,就矛盾了。<strong>所以这个新独裁者也一定是Bob</strong>。</li><li>这就意味着Bob对于任意一对不包含b的选项的偏好和任意一对包含b的选项的偏好而言,都是独裁者。</li><li>所以Bob就是那个独裁者。证毕。</li></ul><h2 id="解释">解释</h2><ul><li>所以结论是,<strong>如果你想将个人的偏好汇集为整体的偏好,且这种汇集过程满足确定性、传递性及IIA,那么这一定会导致独裁者的出现</strong>。</li><li>对这个结论的一个解释是,<strong>IIA是一个太强的限制</strong>。<br>基本上,<strong>IIA排除了任何基于"基数(cardinal)"来对备选项排序的投票方法</strong>。<br>如果有100个备选项,并且你把a和b排在45和46,很大程度上说明你对a和b的区分并不明显。<br>另一方面,如果你把a排在1,把b排在100,这很可能说明你对a的偏好非常强。<br>IIA指出,对于这两种对a和b的排序,我们同等对待。我们能考虑的就是a>b而已。<br>假设了IIA,我们就排除了所有基于基数的投票方法。这使得这个过程变得难以执行。</li></ul><h1 id="简短证明">简短证明</h1><p><a href="http://www.socialchoiceandbeyond.com/scabpage36.html">证明原文</a></p><p>假设有m个选项,n个投票者。每个投票者根据自己的喜好对m个选项排序。社会福利函数(Social Welfare Function,SWF)会把这些数据转化为一个有序列表作为最终结果。</p><p><strong>对于任何一个SWF而言,只要我们找出一个例子,使得SWF产生的结果无法满足阿罗的所有假设,那这个SWF就是不合意的</strong>。所以我们来考虑m=n=3的情况。假设投票者1的结果是ABC,投票者2的结果是BCA,投票者3的结果是CAB。可能的最终结果将包括ABC,ACB,BAC,BCA,CAB,CBA。如果存在一个合适的SWF,那么这些结果中至少有一个满足阿罗的所有假设。</p><p>让我们来考虑阿罗的第三个假设IIA。这个假设表明如果SWF给出的结果是XYZ,那么如果Z被去除掉,结果将是XY,如果Y被去除掉,结果将是XZ,如果X被去除掉,结果将是XY。</p><p>假设SWF给出的结果是ABC,当C被去除掉时,三者的投票将是AB,BA,AB,最终结果将是AB。因此,在n=2的时候,一致性表明,如果三者的投票形如XY,XY,YX,那么最终结果必须是XY。现在我们考虑将B去除掉,最终结果为AC,但是三者的投票却是AC,CA,CA,这违反了一致性(此时XY=CA)。因此不存在SWF得到的最终结果为ABC的同时保证一致性。</p><p>同样的,对于其他可能的结果,我们都会发现SWF无法在得到这个结果的同时保证一致性。<strong>因此不存在SWF,满足IIA,同时在m=n=3且三者的投票为ABC,BCA,CAB的情况下给出结果</strong>。</p><p><strong>如果一个SWF有效,那么这个SWF对于任意m和n的组合都能够适用</strong>。而我们这里证明对于上述的这种特殊情况没有任何SWF可以产出结果,所以不存在满足阿罗假设的SWF,即投票制度存在。</p><p>命题得证。</p>]]></content>
<summary type="html"><p>最近刚刚看完了曼昆N.Gregory Mankiw的经济学原理,微观经济学分册。这本书是典型的西式教科书,文中穿插着各种真实世界中的事例以及类似纽约时报等媒体在当时的评论。读起来很轻松,如同读故事一般,也许是其中涉及的数学并不很高深,所以不怎么困难就读完了。</p>
<p>整本书描写了很多如何使用需求与供给来分析各种经济活动,在书末的最后一章中,作者抛出了经济学前沿中的三个主题,分别是<em>不对称信息经济学</em>、<em>政治经济学</em>以及<em>行为经济学</em>。其中在政治经济学中阐述了一个定理,阿罗不可能性定理。这个定理在讨论是否存在一种完美的投票制度。</p>
<p><img src="/img/microeconomics.jpg" alt="microeconomics" title="microeconomics"></p></summary>
<category term="经济学" scheme="https://andimeo.us.kg/categories/%E7%BB%8F%E6%B5%8E%E5%AD%A6/"/>
<category term="微观经济学" scheme="https://andimeo.us.kg/tags/%E5%BE%AE%E8%A7%82%E7%BB%8F%E6%B5%8E%E5%AD%A6/"/>
<category term="政治经济学" scheme="https://andimeo.us.kg/tags/%E6%94%BF%E6%B2%BB%E7%BB%8F%E6%B5%8E%E5%AD%A6/"/>
<category term="译文" scheme="https://andimeo.us.kg/tags/%E8%AF%91%E6%96%87/"/>
</entry>
<entry>
<title>四星德国</title>
<link href="https://andimeo.us.kg/2014/07/20/%E5%9B%9B%E6%98%9F%E5%BE%B7%E5%9B%BD/"/>
<id>https://andimeo.us.kg/2014/07/20/%E5%9B%9B%E6%98%9F%E5%BE%B7%E5%9B%BD/</id>
<published>2014-07-20T02:03:07.000Z</published>
<updated>2024-09-07T07:25:33.406Z</updated>
<content type="html"><![CDATA[<blockquote><p>2002年到2014年,四届世界杯,十二年,一次亚军,两次四强。从卡恩、巴拉克、坐冷板凳的光头扬克尔到克洛泽、拉姆、施魏因施泰格、波多尔斯基,诺伊尔,再到穆勒、格策,<strong>德国队</strong>,你欠我一个<strong>冠军</strong>。</p></blockquote><h2 id="1998年法国世界杯">1998年法国世界杯</h2><p>1998年的时候我还是一个10岁的小孩,和家附近的大孩子、小孩子们一起疯一起野的年纪。那年暑期,世界杯即将结束时很多小孩子买了巴西队的球衣,但是在那年的决赛上,法国挫败巴西。当时很多大孩子们,印象中还是一帮没有买球衣的大孩子们,开始戏谑那些买了巴西队队服的小孩子们,“小孩儿啥都不懂,就觉得巴西牛B是吧,让法国给干了吧,哈哈!”后来好像的确有些小孩子们的球衣又偷偷的从黄色的巴西变到了蓝色的法国,但这些都不重要,重要的是我从那年开始认识了世界杯。</p><span id="more"></span><h2 id="2002年韩日世界杯">2002年韩日世界杯</h2><p>2002年韩国和日本举办世界杯,这似乎是第一次在亚洲国家举办世界杯。由于东道主直接入选决赛阶段,于是中国队在米卢的执教下,杀入了世界杯。回想起来那是中国人的一次狂欢,我和小伙伴们在学校上了一节体育课,就逃课去同学家看球了。没错,就是中国打哥斯达黎加那一场,比赛似乎踢的比较无聊,上半场结束后,大家的重点也都不在电视上了。开始有大厨之子为我们准备麻辣土豆片,我们也开始自己的娱乐,只有在中国队两次被破门之后,假装惋惜的骂一句傻逼。<img src="/img/ChinaFootballTeam2002.jpg" alt="ChinaFootballTeam2002" title="ChinaFootballTeam2002"></p><p>那届世界杯,中国队0:2负于哥斯达黎加,0:3负于土耳其,0:4负于巴西。还不错,皇马也就灌了中国四个球而已么。印象深刻的是,那次班主任没有用他的必杀技招呼我们,而是睁一只眼闭一只眼没有追究。后来听说他也在比赛开始的时候,让班里的学生自习,自己跑去门房蹭那个12寸的电视去了。</p><p><img src="/img/GermanyTeamShirt.jpg" alt="GermanyTeamShirt" title="GermanyTeamShirt"></p><p>同样是那届世界杯,让我认识了德国队,一个黑白相间的队服,时而缀有红黄二色,队徽中的鸟类,我一直潜意识中称之为乌鸡。那时候的德国一帮老将,在世界范围内以硬汉足球闻名,因为没有南美人细腻的脚法,但是拥有绝对的身高优势,于是非常依赖角球和前场定位球造成的头球破门,当然还有巴拉克禁区外的远射。</p><p>那时恰好7、8个小伙伴决定从爸妈那儿骗一笔钱去买一身队服,大家一路上探讨到底买哪个队的呢,有的要买意大利,有的要买英格兰,有的要买巴西,有的因为98年法国夺冠要买法国。到了体育用品商店,德国队的球服吸引了我,于是在他们还在犹豫之中,我就决定了我的选择。在我挑选尺码的时候,他们也纷纷决定了要买德国队,是的,那一路的讨论中未曾出现过德国队,但是最后我们所有人都买了德国队的队服。不对,有一人买了英格兰的,因为这家店的德国队服库存不够了,于是那个小伙伴只能在其他国家的队服中挑选。直到现在我都觉得那个三狮标志像一坨屎。</p><p>如果说就是因为我的第一件队服是德国队,于是我开始支持德国队,也不尽然。支撑我支持德国队必不可少的还有那年小组赛中德国队8:0打穿沙特,克洛泽的首秀就上演了帽子戏法。用X君的话说,德国丫从来不打假球,黑起屁眼就是干,我就是喜欢这样的球队。</p><p><img src="/img/KlozeJump2002.jpg" alt="KlozeJump2002" title="KlozeJump2002"></p><p>那时克洛泽一下成为了身边小姑娘们YY的对象。进球后的空翻,跪地前划都是他的标志。</p><p>可那时我最喜欢的德国队球员是门神卡恩,没错,在卡恩时代,他就是门神。在金黄色的头发和粗糙的面部肌肉的映衬下,每每他朝向场内大声咆哮时,都让人觉得那是一头雄狮。</p><p><img src="/img/oliver-kahn-2002.jpg" alt="oliver-kahn-2002" title="oliver-kahn-2002"></p><p>最终德国与巴西在决赛相遇。那时是巴西的巅峰时刻,由3R领衔的巴西成功击败了德国,捧起了大力神杯。那时的罗纳尔多还是外星人,不是胖罗,那时候还有长相酷似农民的里瓦尔多,还有长发飘飘的罗纳尔迪尼奥。德国输了,但是输的并不遗憾。决赛结束后,才从《足球俱乐部》中读到,说卡恩在决赛前已经受伤,印象中是肋骨折断。听闻这一消息后,再回想当时里瓦尔多的那个射门,打到卡恩的怀中却脱手弹出,由后面跟上的罗纳尔多补射命中。当时埋怨卡恩没有处理好,后来觉得肋骨有伤,那一下该有多疼,弹出也是情理之中。</p><h2 id="2006年德国世界杯">2006年德国世界杯</h2><p>2006年的暑假,刚刚高考结束的我,继续支持心爱的德国队。那时候德国的主教练是克林斯曼,传说中的金色轰炸机,我对克林斯曼时代的足球一无所知。这个人对德国进行了大换血,更多新人纷纷登场,巴拉克退役后成为队长的拉姆,当时还不是主力的小猪,以及2006年的最佳新秀波多尔斯基。</p><p><strong>拉姆</strong><img src="/img/Lamu2006.jpg" alt="Lamu2006" title="Lamu2006"></p><p><strong>施魏因施泰格</strong><img src="/img/shiweiyinshitaige2006.jpg" alt="shiweiyinshitaige2006" title="shiweiyinshitaige2006"></p><p><strong>波多尔斯基</strong><img src="/img/boduoersiji2006.jpg" alt="boduoersiji2006" title="boduoersiji2006"></p><p>对这一届的世界杯印象比较淡,也许因为当时记忆力已经开始衰退,也许因为这并不是我看的第一届世界杯。但是我记得在半决赛德国对战意大利那一场开始之前,叮嘱我妈一定要叫我起来看球,可是果断我妈当时答应的十分爽快,但是后来却觉得睡觉更加重要。于是我错过了那场比赛,后来得知德国输了。于是将责任强加在我妈没有叫我起床,我没有看,于是德国输了。是这样的,我还因为这件事儿离家出走了大半天,晚上饿了后就又回去了。</p><h2 id="2010年南非世界杯">2010年南非世界杯</h2><p>这一届世界杯对德国的印象没有那么深,但是对shakira的waka waka至今都非常喜欢。This is Africa, we're all African.这种情怀让我非常感动,就如同Forget politics for 90 minutes一样令人感动。</p><p><img src="/img/ForgetAboutPoliticsFor90Minutes.jpg" alt="ForgetAboutPoliticsFor90Minutes" title="ForgetAboutPoliticsFor90Minutes"></p><p>另一个就是最后夺冠的西班牙队,所谓的tiki taka足球,一路1:0直到决赛获胜。谈到比赛的观赏性和胜负,我觉得任何一个球队都在两者之间寻找平衡,不必强加对错。但是毫无疑问,西班牙更看重后者。</p><p>在之后的2012年欧洲杯中,德国在半决赛负于意大利,我当时就发微博说,让布冯和卡西利亚斯在决赛去互扑点球吧。谁曾料想,西班牙竟然在决赛踢了意大利一个4:0,由此再一次深深的提醒我们赌球的目的地只有一个,就是天台。</p><h2 id="2014年巴西世界杯">2014年巴西世界杯</h2><p>本届世界杯,德国队小组赛二胜一平出现,淘汰葡萄牙,8强1:0送走法国,4强7:1<strong>狂屠</strong>巴西。没错,德国与巴西7:1的比分不仅碾压了巴西和巴西球迷,也让我的记忆仅仅停在了这场比赛上,以至于德国和法国的那场比赛我还要去百度一下才能想得起来。</p><p>这是本次世界杯,第一次半夜爬起来看球,之前都是看00:00那一场,然后就睡了。这场比赛一开始,巴西的进攻还是很虎的,德国队频频在中场丢球,直到11分钟,穆勒打进本次世界杯第4个进球时,巴西彻底崩溃了。随后克洛泽的世界杯第16粒进球,克洛斯的连续两粒进球,赫迪拉,许尔勒,完全停不下来了。那一夜2002年的沙特灵魂附体。</p><p>中场结束时,我拍下了当时日出中的北京,满天火红,似乎在欢庆德国的胜利。</p><p><img src="/img/BeijingSunrise2014.jpg" alt="BeijingSunrise2014" title="BeijingSunrise2014"></p><p>当时我发微博说,98年法国打巴西第一次看世界杯,从02年开始支持德国,12年的时间,4届世界杯。希望德国能在今夏捧起大力神杯。其实我真正想说的是,德国你欠我一个冠军。</p><p>决赛,对战阿根廷,格策113分钟的一个进球,德国成功夺冠,从此踏入四星之路。梅西的落寞却抢走了德国的风头,一个只差世界杯冠军就可以封神的天才少年,他的第三届杯赛。</p><p><img src="/img/messi2014.jpg" alt="messi2014" title="messi2014"></p><p>本届世界杯值得说的太多,哥伦比亚的J罗,哥斯达黎加小组赛三场全胜,送走英格兰、意大利,咬人的苏亚雷斯,射手榜第一的克洛泽,寻找新鲜食材的勒夫,乌贼刘,朱广沪,巴西球迷Clovis Fernandes……</p><p>又有一批老人要离开这片绿茵,又有一批新秀要踏上这片战场。微博上有这样一条,的确让人动容。</p><p><img src="/img/weibo2014kloze.jpg" alt="weibo2014kloze" title="weibo2014kloze"></p><p>Whatever! 四星德国,加油!</p><p><img src="/img/FourStarGermany.jpg" alt="FourStarGermany" title="FourStarGermany"></p>]]></content>
<summary type="html"><blockquote>
<p>2002年到2014年,四届世界杯,十二年,一次亚军,两次四强。从卡恩、巴拉克、坐冷板凳的光头扬克尔到克洛泽、拉姆、施魏因施泰格、波多尔斯基,诺伊尔,再到穆勒、格策,<strong>德国队</strong>,你欠我一个<strong>冠军</strong>。</p>
</blockquote>
<h2 id="1998年法国世界杯">1998年法国世界杯</h2><p>1998年的时候我还是一个10岁的小孩,和家附近的大孩子、小孩子们一起疯一起野的年纪。那年暑期,世界杯即将结束时很多小孩子买了巴西队的球衣,但是在那年的决赛上,法国挫败巴西。当时很多大孩子们,印象中还是一帮没有买球衣的大孩子们,开始戏谑那些买了巴西队队服的小孩子们,“小孩儿啥都不懂,就觉得巴西牛B是吧,让法国给干了吧,哈哈!”后来好像的确有些小孩子们的球衣又偷偷的从黄色的巴西变到了蓝色的法国,但这些都不重要,重要的是我从那年开始认识了世界杯。</p></summary>
<category term="心情" scheme="https://andimeo.us.kg/categories/%E5%BF%83%E6%83%85/"/>
<category term="回忆" scheme="https://andimeo.us.kg/tags/%E5%9B%9E%E5%BF%86/"/>
<category term="世界杯" scheme="https://andimeo.us.kg/tags/%E4%B8%96%E7%95%8C%E6%9D%AF/"/>
<category term="德国" scheme="https://andimeo.us.kg/tags/%E5%BE%B7%E5%9B%BD/"/>
</entry>
<entry>
<title>配置mod_cluster与tomcat</title>
<link href="https://andimeo.us.kg/2014/06/27/%E9%85%8D%E7%BD%AEmod_cluster%E4%B8%8Etomcat/"/>
<id>https://andimeo.us.kg/2014/06/27/%E9%85%8D%E7%BD%AEmod_cluster%E4%B8%8Etomcat/</id>
<published>2014-06-27T15:33:40.000Z</published>
<updated>2024-09-07T13:03:07.279Z</updated>
<content type="html"><![CDATA[<blockquote><p>mod_cluster 和mod_jk,mod_proxy类似,是一个基于httpd的负载均衡项目。能够代理请求给基于Tomcat的网络服务器集群(支持任何独立的Tomcat,JBoss Web或者JBoss AS)。mod_cluster与 mod_jk和mod_proxy的区别是,mod_cluster为web服务器和httpd服务器之间提供后台通道。web服务器使用后台通道给httpd端提供当前负载信息。 </p></blockquote><h1 id="配置mod_cluster">配置mod_cluster</h1><h2 id="下载mod_cluster">下载mod_cluster</h2><p>下载集成了mod_cluster模块的apache httpd server <a href="http://downloads.jboss.org/mod_cluster/1.2.6.Final/linux-x86_64/mod_cluster-1.2.6.Final-linux2-x64-ssl.tar.gz">mod_cluster-1.2.6.Final-linux2-x64-ssl.tar.gz</a></p><h2 id="安装步骤">安装步骤</h2><ul><li>mkdir -p /tmp/apache</li><li>tar -xzvf mod_cluster-1.2.6.Final-linux2-x64-ssl.tar.gz -C /tmp/apache</li><li>cd /tmp/apache</li><li>cp -r opt/* /opt</li></ul><span id="more"></span><h2 id="修改配置文件">修改配置文件</h2><ul><li>打开配置文件/opt/jboss/httpd/httpd/conf/httpd.conf</li><li>移动到文件末尾</li><li>修改标签<IfModule manager_module></IfModule>中的内容</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">IfModule</span> <span class="attr">manager_module</span>></span></span><br><span class="line"> Listen $ip:$port</span><br><span class="line"> ManagerBalancerName mycluster</span><br><span class="line"> <span class="tag"><<span class="name">VirtualHost</span> $<span class="attr">ip:</span>$<span class="attr">port</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Location</span> /></span></span><br><span class="line"> Order deny,allow</span><br><span class="line"> Deny from all</span><br><span class="line"> Allow from all</span><br><span class="line"> <span class="tag"></<span class="name">Location</span>></span></span><br><span class="line"></span><br><span class="line"> KeepAliveTimeout 300</span><br><span class="line"> MaxKeepAliveRequests 0</span><br><span class="line"> ServerAdvertise on</span><br><span class="line"> EnableMCPMReceive</span><br><span class="line"> AllowDisplay on</span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">Location</span> /<span class="attr">mod_cluster_manager</span>></span></span><br><span class="line"> SetHandler mod_cluster-manager</span><br><span class="line"> Order deny,allow</span><br><span class="line"> Deny from all</span><br><span class="line"> Allow from all</span><br><span class="line"> <span class="tag"></<span class="name">Location</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">VirtualHost</span>></span></span><br><span class="line"><span class="tag"></<span class="name">IfModule</span>></span></span><br></pre></td></tr></table></figure><ul><li>根据你的机器情况,更改ip和port</li></ul><h2 id="生成证书">生成证书</h2><ul><li>总共需要生成三个证书文件<ul><li>server.crt</li><li>server.key</li><li>server-ca.crt</li></ul></li><li>将这些证书文件置于/opt/jboss/httpd/httpd/conf/</li></ul><h2 id="配置ssl">配置SSL</h2><ul><li>打开/opt/jboss/httpd/httpd/conf/httpd.conf</li><li>移动至文件末尾</li><li>增加两个VirtualHost,其中一个用来将http请求redirect到https请求,另一个用来处理SSL连接<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Listen 80</span><br><span class="line"><VirtualHost *:80></span><br><span class="line">RewriteEngine on</span><br><span class="line">RewriteCond %{SERVER_PORT} 80</span><br><span class="line">RewriteRule ^{.*} https://%{SERVER_NAME}%{REQUEST_URI} [R,L]</span><br><span class="line"></VirtualHost></span><br><span class="line"></span><br><span class="line">Listen 443</span><br><span class="line"><VirtualHost *:443></span><br><span class="line"><Location /></span><br><span class="line">Order deny,allow</span><br><span class="line">Deny from all</span><br><span class="line">Allow from all</span><br><span class="line"></Location></span><br><span class="line"></span><br><span class="line">SSLEngine on</span><br><span class="line">SSLCertificateFile conf/server.crt</span><br><span class="line">SSLCertificateKeyFile conf/server.key</span><br><span class="line">SSLCACertificateFile conf/server-ca.crt</span><br><span class="line"></VirtualHost></span><br></pre></td></tr></table></figure></li></ul><h1 id="配置tomcat">配置Tomcat</h1><h2 id="下载jar包">下载jar包</h2><p>下载mod_cluster提供的java bundles <a href="http://downloads.jboss.org/mod_cluster/1.2.6.Final/linux-x86_64/mod_cluster-parent-1.2.6.Final-bin.tar.gz">mod_cluster-parent-1.2.6.Final-bin.tar.gz</a></p><h2 id="安装插件">安装插件</h2><ul><li>mkdir -p /tmp/bundles</li><li>tar -xzvf mod_cluster-parent-1.2.6.Final-bin.tar.gz -C /tmp/bundles</li><li>cd /tmp/bundles</li><li>cp JBossWeb-Tomcat/lib/*.jar $CATALINA_BASE/lib</li><li>cd $CATALINA_BASE/lib</li><li>删除无用jar包<ul><li>如果你的tomcat版本是6<br> rm mod_cluster-container-tomcat7-1.2.6.Final.jar</li><li>如果你的tomcat版本是7<br> rm mod_cluster-container-tomcat6-1.2.6.Final.jar</li></ul></li></ul><h2 id="修改配置文件">修改配置文件</h2><ul><li>打开$CATALINA_BASE/conf/server.xml</li><li>在标签<Server></Server>中,加入一个Listener<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><Listener className="org.jboss.modcluster.container.catalina.standalone.ModClusterListener" proxyList="$load_balancer_ip:$load_balancer_port" /></span><br></pre></td></tr></table></figure></li><li>根据mod_cluster宿主机器信息更改$load_balancer_ip和$load_balancer_port</li><li>通过在<Engine></Engine>标签中添加属性jvmRoute,可以指定该server的名称<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><Engine name="Catalina" defaultHost="localhost" jvmRoute="myTomcat"></span><br></pre></td></tr></table></figure></li></ul><h1 id="启动">启动</h1><h2 id="启动mod_cluster">启动mod_cluster</h2><p>/opt/jboss/httpd/sbin/apachectl start</p><h2 id="启动tomcat">启动tomcat</h2><p>$CATALINA_BASE/bin/startup.sh</p><h2 id="添加应用服务器">添加应用服务器</h2><p>如果你想为这个负载均衡器添加更多的应用服务器,即tomcat实例,你只需要简单的重复<strong>配置Tomcat</strong>一节的步骤。mod_cluster这边不需要做任何事情,它会自动检测到新添加的应用服务器</p><h2 id="检查运行状况">检查运行状况</h2><p>启动之后,可以在浏览器中输入$load_balancer_ip:load_balancer_port/mod_cluster_manager来查看整个系统的运行状况,如下图所示<img src="/img/mod_cluster.jpg" alt="mod cluster" title="mod cluster"></p>]]></content>
<summary type="html"><blockquote>
<p>mod_cluster 和mod_jk,mod_proxy类似,是一个基于httpd的负载均衡项目。能够代理请求给基于Tomcat的网络服务器集群(支持任何独立的Tomcat,JBoss Web或者JBoss AS)。mod_cluster与 mod_jk和mod_proxy的区别是,mod_cluster为web服务器和httpd服务器之间提供后台通道。web服务器使用后台通道给httpd端提供当前负载信息。 </p>
</blockquote>
<h1 id="配置mod_cluster">配置mod_cluster</h1><h2 id="下载mod_cluster">下载mod_cluster</h2><p>下载集成了mod_cluster模块的apache httpd server <a href="http://downloads.jboss.org/mod_cluster/1.2.6.Final/linux-x86_64/mod_cluster-1.2.6.Final-linux2-x64-ssl.tar.gz">mod_cluster-1.2.6.Final-linux2-x64-ssl.tar.gz</a></p>
<h2 id="安装步骤">安装步骤</h2><ul>
<li>mkdir -p /tmp/apache</li>
<li>tar -xzvf mod_cluster-1.2.6.Final-linux2-x64-ssl.tar.gz -C /tmp/apache</li>
<li>cd /tmp/apache</li>
<li>cp -r opt/* /opt</li>
</ul></summary>
<category term="Java" scheme="https://andimeo.us.kg/categories/Java/"/>
<category term="负载均衡" scheme="https://andimeo.us.kg/tags/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/"/>
<category term="mod_cluster" scheme="https://andimeo.us.kg/tags/mod-cluster/"/>
<category term="tomcat" scheme="https://andimeo.us.kg/tags/tomcat/"/>
<category term="分布式" scheme="https://andimeo.us.kg/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
</entry>
<entry>
<title>理解Node.js中的事件轮询</title>
<link href="https://andimeo.us.kg/2014/06/21/%E7%90%86%E8%A7%A3Node.js%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E8%BD%AE%E8%AF%A2/"/>
<id>https://andimeo.us.kg/2014/06/21/%E7%90%86%E8%A7%A3Node.js%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E8%BD%AE%E8%AF%A2/</id>
<published>2014-06-20T16:34:00.000Z</published>
<updated>2024-09-07T07:25:33.408Z</updated>
<content type="html"><![CDATA[<blockquote><p>本文翻译自Mixu的文章<a href="http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/">Understanding the Node.js event loop</a></p></blockquote><p><strong>Node.js的第一个基本观点是,I/O是昂贵的。</strong> </p><ul><li>L1-cache: 3 cycles</li><li>L2-cache: 14 cycles</li><li>RAM: 250 cycles</li><li>Disk: 41 000 000 cycles</li><li>Network: 240 000 000 cycles</li></ul><p>所以现在编程技术中最大的浪费来自对I/O的等待。我们有几种方法用来可以应对I/O:</p><ol><li>同步:按序执行每一个请求。优点:简单。缺点:任何一个请求都可能会阻塞其他请求。</li><li>进程:对每个请求,我们都开一个新的进程来处理。优点:容易。缺点:扩展性不好,有几百个请求的话,就需要几百个进程来处理。fork()是Unix程序员的万精油。因为它的使用,所有问题看起来都微不足道。但是往往并不需要这么“重”的解决方法。</li><li>线程:对每个请求,开一个新的线程进行处理。优点:容易,而且相比进程而言,对内核更加友好,因为线程使用的系统资源往往比进程要少。缺点:首先你的机器可能不支持线程。其次因为要考虑对共享资源的访问控制,很快线程编程就会变得非常复杂。</li></ol><span id="more"></span><p><strong>Node.js的第二个基本观点是,每个请求用一个线程来处理的方式会耗费过多的内存。</strong>(如同那张人尽皆知的图,与Nginx想比,Apache消耗了大量内存)</p><p><img src="/img/Memory_Apache_Nginx_Comparison.jpg" alt="Memory Apache Nginx Comparison" title="Memory Apache Nginx Comparison"></p><p>Apache是多线程的,它每次都会为新请求创建一个新线程(或者进程,这取决于怎么配置)。你可以看到随着并发连接数的增多,不仅消耗的内存越来越多,同时也需要更多的线程来服务这些同时发起请求的客户端。Ngnix和Node.js不是多线程的,因为线程和进程都会耗费过多的内存。Ngnix和Node.js是单线程的,但是是基于事件的。通过在一个线程中处理所有连接,从而消除因为创建多个线程或进程而消耗的系统资源。</p><h3 id="node.js只为你的代码保留一个线程">Node.js只为你的代码保留一个线程</h3><p>它真的只有一个线程,你不能并行执行代码。如果你“sleep”一秒钟,将会阻塞整个server一秒钟:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>() < now + <span class="number">1000</span>) {</span><br><span class="line"> <span class="comment">// do nothing</span></span><br><span class="line">}</span><br><span class="line"><span class="string">``</span><span class="string">` </span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">所以当上面的代码执行的时候,Node.js将不会响应任何来自客户端的请求,因为它只有一个线程在执行你的代码。或者假如你有一些CPU密集型的代码,比如图片缩放,这仍然会阻塞所有其他的请求。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">### 所有的代码都是并行执行的,除了你的代码</span></span><br><span class="line"><span class="string">其实在一个线程里让代码并行执行是不可能的。然而,所有的I/O都是基于事件和异步的,那么就不会阻塞server。 </span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">`</span><span class="string">``</span> javascript</span><br><span class="line">c.<span class="title function_">query</span>(</span><br><span class="line"> <span class="string">'SELECT SLEEP(20);'</span>,</span><br><span class="line"> <span class="keyword">function</span> (<span class="params">err, results, fields</span>) {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="keyword">throw</span> err;</span><br><span class="line"> }</span><br><span class="line"> res.<span class="title function_">writeHead</span>(<span class="number">200</span>, {<span class="string">'Content-Type'</span>: <span class="string">'text/html'</span>});</span><br><span class="line"> res.<span class="title function_">end</span>(<span class="string">'<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>'</span>);</span><br><span class="line"> c.<span class="title function_">end</span>();</span><br><span class="line"> }</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>如果在一个请求处理中这么做,那么在查询数据库的时候,其他请求仍然能够被正确处理。</p><h3 id="为什么这么做是好的?我们什么时候从同步跳到异步的执行中?">为什么这么做是好的?我们什么时候从同步跳到异步的执行中?</h3><p>同步执行有它的优点,因为这样简化了代码的编写。</p><p>在Node.js中,你不用担心后端发生了什么,只要在需要I/O的时候使用回调函数就行了。你的代码确保不会被打断,而且I/O不会阻塞其他的请求,也不会带来因为过多线程或进程导致的资源消耗。</p><p>让I/O异步执行是好的,因为I/O比大部分代码的执行更加昂贵,我们不应该仅仅在I/O的时候等在那里。<img src="/img/EventLoop.jpg" alt="EventLoop" title="EventLoop">事件轮询可以处理外部的事件,并且将他们转化为回调函数的调用。所以Node.js可以在I/O调用时从这个请求转向其他的请求。在I/O调用中,你的代码保存回调函数,并将控制权交给Node.js的运行时环境,回调函数将在I/O结束后被调用。</p><p>当然,在后端,有别的线程或者进程在执行I/O操作。然而这些并不会暴露在你的代码中,所以你并不用担心它们。但是你应该知道I/O操作,如数据库操作,或者其他的一些任务将会是异步执行的,这些线程的结果将通过事件轮询返回到每个请求的处理过程中。与Apache的模型相比,线程数和线程对资源的消耗下降了很多,因为我们并不需要为每个连接开一个线程。除非当你确实有些事情需要并行处理,才需要在此时开一个线程。而且在Node.js中,线程并不需要你来管理。</p><p>除了I/O调用外,Node.js希望所有的请求都能够快速返回,如:CPU密集型的工作应该交给另一个线程来处理。这个线程将会以事件的形式和你进行交互。这意味着,要想在后台有独立的线程存在,你只有使用事件这一种方法。基本上,所有会发布事件的对象都支持异步调用,而且你可以和这些阻塞中的代码进行交互,通过文件、socket或者子进程,所有的这些在Node.js中都叫做EventEmitter。我们可以使用这种方法来充分利用多核,另见:node-http-proxy。</p><p><strong>内部实现</strong><br>在内部,libev为Node.js提供事件轮询,libeio提供线程池来支持异步I/O。如果想学习更多,可以看看libev的文档。</p><h3 id="如何在node.js中进行异步操作">如何在Node.js中进行异步操作</h3><ul><li>我们将函数作为参数进行传递,在需要的地方执行</li><li>匿名函数,或称为闭包,当事件I/O中结束时执行</li><li>回调计数,对于事件回调,我们并不能保证这些事件完成的顺序。所以如果你需要等待多个事件执行结束,通常你只需要保存一个并行操作的计数器,然后检查是否所有事件都执行结束。比如在回调中记录DB查询返回的个数。</li><li>事件轮询,如之前所述,你可以把阻塞代码转变为一个事件,让另一个线程来执行,并当执行结束后将结果返回。</li></ul><p>就是这么简单!</p>]]></content>
<summary type="html"><blockquote>
<p>本文翻译自Mixu的文章<a href="http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/">Understanding the Node.js event loop</a></p>
</blockquote>
<p><strong>Node.js的第一个基本观点是,I/O是昂贵的。</strong> </p>
<ul>
<li>L1-cache: 3 cycles</li>
<li>L2-cache: 14 cycles</li>
<li>RAM: 250 cycles</li>
<li>Disk: 41 000 000 cycles</li>
<li>Network: 240 000 000 cycles</li>
</ul>
<p>所以现在编程技术中最大的浪费来自对I/O的等待。我们有几种方法用来可以应对I/O:</p>
<ol>
<li>同步:按序执行每一个请求。优点:简单。缺点:任何一个请求都可能会阻塞其他请求。</li>
<li>进程:对每个请求,我们都开一个新的进程来处理。优点:容易。缺点:扩展性不好,有几百个请求的话,就需要几百个进程来处理。fork()是Unix程序员的万精油。因为它的使用,所有问题看起来都微不足道。但是往往并不需要这么“重”的解决方法。</li>
<li>线程:对每个请求,开一个新的线程进行处理。优点:容易,而且相比进程而言,对内核更加友好,因为线程使用的系统资源往往比进程要少。缺点:首先你的机器可能不支持线程。其次因为要考虑对共享资源的访问控制,很快线程编程就会变得非常复杂。</li>
</ol></summary>
<category term="Node.js" scheme="https://andimeo.us.kg/categories/Node-js/"/>
<category term="译文" scheme="https://andimeo.us.kg/tags/%E8%AF%91%E6%96%87/"/>
<category term="Node.js" scheme="https://andimeo.us.kg/tags/Node-js/"/>
<category term="事件轮询" scheme="https://andimeo.us.kg/tags/%E4%BA%8B%E4%BB%B6%E8%BD%AE%E8%AF%A2/"/>
</entry>
<entry>
<title>大学之美</title>
<link href="https://andimeo.us.kg/2014/06/14/%E5%A4%A7%E5%AD%A6%E4%B9%8B%E7%BE%8E/"/>
<id>https://andimeo.us.kg/2014/06/14/%E5%A4%A7%E5%AD%A6%E4%B9%8B%E7%BE%8E/</id>
<published>2014-06-14T08:59:35.000Z</published>
<updated>2024-09-07T07:25:33.408Z</updated>
<content type="html"><![CDATA[<blockquote><p>今夜从矿大走过,看到学生们三两成群,或是刚下课,或是刚下自习。不禁让我想起了八年前刚到川大。刚刚报完名,还没开学的晚上,漫步在江安校区,恰巧在长桥东侧,碰到了学生们刚下自习。他们从图书馆接踵而出,走在长桥上。在长桥两侧的灯光映射下,说不出的美。也许大学之美皆在人们的憧憬与回忆之中。</p></blockquote><h2 id="题记">题记</h2><p>大学是什么?有人说大学是社会的预科,人际关系、左右逢迎都是这四年的必修课;有人说大学之大不在大楼,而在大师,大学是离大师最近的地方;有人说大学就是青春,是足球场上的汗水,是兄弟间的宿醉,是能够滋养爱情的盆栽。所有人都在说大学,大学却很少说什么,它就在那里,在那片土地等着送上门的学子,狠狠的在他们的生命里刻上几刀。</p><span id="more"></span><h2 id="初入象牙塔">初入象牙塔</h2><p>在我尚未进入大学之前,大学在我心中逃不开奖学金和社团。在军训即将结束时,辅导员给我们一个提问的机会,我便问到怎么才能拿奖学金。现在的我想都不用想也知道问出这种失心疯的话,辅导员会有什么样的表情。<br>当时的我雄心勃勃,希望能够三年毕业,并且保送清华。当各种社团招新的时候,向十几个社团提交了申请。现在想想,这种充满目标感的大学生活如果真的持续四年,我不知道我会多么后悔。而我之所以会有这些目标,恰好是因为我对大学的迷茫失措,不知道我想从大学得到什么,不知道大学能给我什么。</p><p>相信当时有很多人都认为大学是天堂,是逃离了地狱般高考的天堂。有人沉浸在各种班级、学院的组织活动中,觊觎着部长、主席的位置;有人天天泡在图书馆中,那里有数不尽的军事、历史、建筑园林史的书籍,努力的满足自己的兴趣爱好;有人开始寻觅身边有哪些搭的上话的美丽女生,开始给自己的大学涂上一抹鲜艳的色彩。当然也有人觉得大学也应该学习,因为他的表哥告诉他,有些人晚上因为学习,睡得比高三还要晚。</p><p>刚进入大学,迎面而来的就是军训。在每天的训练之余,除了吃饭,大多数时间我们都被要求反复叠自己的被子。当然没有人会那么傻,教官也不会一直盯着你。也许对我而言,因为大学是第一所寄宿式的学校,所以观察身边的陌生人就变成了当时的乐趣。H君经常目不转睛的盯着前排的女生,水平朝下30度角,他盯着女生,我盯着他,幸运的是女生和他都没有发现。当时心里对他有深深的鄙视,甚至溢于言表。后来知道他在高中的数学成绩之好,在数学竞赛上的斩获之多时,我心中的世界变了一点点。T君当时俨然一副大哥的样子,和教官坐在一起抽烟,目空一切。有个小伙伴Y君,我也不知道他当时是在偷偷的对我说,还是在喃喃自语:“要是能和他做朋友,就爽了,以后有人打我,就能叫上人了。” 后来他的确被人打了,他也的确是那位大哥的朋友、甚至是好兄弟了,但是打人者也是那位大哥的好兄弟,回想起他当时的愿景,真的有一点心疼。</p><h2 id="憧憬">憧憬</h2><p>未入大学之前,想必每个人都对大学有各种各样的幻想。我会幻想大家早上会一起晨读,像润之一样高吟《少年中国说》;会幻想没有那么繁重的功课,可以在图书馆带着耳机装一把小资;会幻想谈一场恋爱,而且要模仿《毕业那天我们一起失恋》;会幻想准时参加英语角,在毕业时把英语当成母语;会幻想平常可以打工,一个月赚个几千块轻轻松松;会幻想毕业后成为中国顶尖黑客,分分钟写个操作系统出来。</p><p>这些都很美,但也许能说的清楚的都不是至美。在即将进入大学生活之前,作为一个局外人,踏足这个校园,与校园里的人们擦肩而过,那时的感受才是大学的美之所在。</p><p>漫步在水上长廊两侧,有情侣坐在长廊两侧嬉水,那时会感觉大学好美;在食堂的人潮涌动中,学生都在有序的排队,那时会感觉大学好美。在草坪上,有人躺着晒太阳,有人坐着弹吉他,那时会感觉大学好美。</p><p>憧憬中的大学真的好美,那份感受无法言表,但好美。</p><h2 id="作为一个学长">作为一个学长</h2><p>在经历过大一的阵痛之后,大家都陆陆续续进入了自己的角色。那些努力在社团打拼的人,看不到成为部长、主席的希望,开始消极怠工,但同时仍然希望保住自己副部长的位置,以便毕业时能够多加一点儿社会实践分;那些认为大学就是向学习说再见的人,开始努力复习准备补考,再一次的挖掘自己在高考过后被埋藏起来的聪明才智;那些看厌了同年级女生的人开始将手伸向了新来的学妹,在各种迎新活动、社团招新活动中,努力的挥洒着自己的殷勤;有的人仿佛看穿了大学,就像看穿红尘一般,向学弟学妹们兜售自己的经验、阅历,这些人会被称为学长。</p><p>作为一个学长,大学似乎就没什么特别了。在平常,上上那些不点名会挂科的课,抄抄老师象征性布置的作业,躺在被窝里呼唤出去吃饭的室友带饭,晚上宿舍的人都到齐了,来两把全是后期的dota,有时还会交流一下最新的AV动态。</p><p>在考试前,图书馆一定人满为患,每天开馆前排个几百人是常态,但究其原因更多是因为考试季不是太热就是太冷,宿舍没有空调又没有暖气的成都,学长们都会去冬暖夏凉配有中央空调的图书馆占个座,继续睡觉。考试前的打印店也是令学长们回环往复,恋恋不舍的圣地,这些湖南店主据说占据了全国高校70%的打印生意,他们孜孜不倦的高价收购往年的试题,然后打印装订成册卖给本届的学生。其实一册试题比零散打印,也就贵个1、2毛钱,但是从经济学的角度,这种对需求的预判,直接提高了交易的效率,相当于降低了成本,真是生活处处皆学问。为什么这些试题这么备受青睐呢,因为他们的命中率或者说老师们出题的重复率可以达到70%。</p><p>经过一段时间的平淡,偶尔大家会参加一下班级组织的踏青活动。在收钱的时候,很多人开始权衡用这50块钱去支持某位干部的政绩是否值得,大家不由自主的会看着自己的小伙伴,给出X去我就去,X说Y去我就去,不知道是不是真的有环的情况出现过,但显然这说明了人是群居动物。在干部同志成功解开这些依赖关系之后,大家终于出发了。只是看上去并不像20多人的集体出游,更像是有5、6个3-4人的小组织恰好一起到了那里而已。这些三三两两的小组织不能光靠干部来丰富自己的生活,于是隔三差五文星镇上的自助火锅,小吃城楼顶天台上的冷锅鱼成了最好的去处。</p><p>作为一个学长,似乎都认同了这样的大学生活。大学就是这样,大学不过如此。大学仍然什么都不说,只是一笔一笔努力的刻着。</p><h2 id="回忆">回忆</h2><p>有位我的学长在QQ签名上写过,“研究生三年太短暂了,不努力抓紧就会一闪而过”。我想我理解“太短”是何意,却仍不知如何“抓紧”。毕业季如期而至,有哭的,有闹的,有假装懂别人为什么哭而劝慰的。仿佛毕业就该是这样,没有酒,没有泪,就不是毕业。X君毕业时,喝的满脸通红,前一分钟还在嬉笑,后一分钟就会把手里的半瓶酒掷在地上,破口大骂两年前的L君等人。L君和他的小伙伴也坐在饭桌对面,由于脸没有那么红,也不好意思对骂,只能保持一个安全的距离,承认自己的错误。有人说X君酒后吐真言,性情中人,大家都是好兄弟,中间有梁子毕业一吐为快,也就没什么了。有人说X君也许并非酒后吐真言,而是想吐真言的时候努力的喝了一些酒而已。</p><p>在毕业后的时间里,大家在B君建的群里天天喷来喷去,好像和没毕业一样。慢慢的有人成婚了,有人生子了,有人疲于应付研究生的论文,有人已经不知道还有这么一个群了。如果把时间当X轴,聊天频次当Y轴,一定可以画出一条底数小于1的对数函数。</p><p>经过一段时间之后,我们仿佛适应了一个人在那个属于或不属于你的城市里生活,就如同刚进入大学时一样。但是总是会时不时的想起大学,想起那群莫名其妙走在一起生活了四年的人。想起那时晚上能凑齐10个菜B一起dota;想起那时早上会被别人的闹铃吵醒,而他却能继续沉睡;想起那时中午只要还在被窝里就会有人给你带饭;想起那时晚上睡不着觉可以有人和你聊到天亮;想起那时候晚上组队打水;想起那时候周末去文星镇给一个宿舍的人带猪头肉和凉菜;想起地震时,大家轮流借一个人的电话给家里报平安;想起世界杯时,半夜有从隔壁寝室跑来的2B高喊着“伪球迷,哥来了,给哥开门”,来蹭电视。</p><p>Z君有个相册,名为《再也遇不到这样的基友了》。<img src="/img/XiongMaoJiDi.jpg" alt="XiongMaoJiDi" title="XiongMaoJiDi"></p><p>发现那个相册后,我问D君,“我们以前晚上打开水的时候都在聊什么?好像很开心的样子”。D君说,“只要聊聊教主,聊聊Z君,聊聊谷歌微软我们就很开心了”。</p><h2 id="后记">后记</h2><p>大学之美不在985、211,不在毕业后的工作,不在拿了多少奖学金,不在当了主席或部长。大学之美在身边的人,在岁月中的成长与历练,在四年中的点滴。</p><p><strong>大学之美皆在人们的憧憬与回忆之中。</strong></p>]]></content>
<summary type="html"><blockquote>
<p>今夜从矿大走过,看到学生们三两成群,或是刚下课,或是刚下自习。不禁让我想起了八年前刚到川大。刚刚报完名,还没开学的晚上,漫步在江安校区,恰巧在长桥东侧,碰到了学生们刚下自习。他们从图书馆接踵而出,走在长桥上。在长桥两侧的灯光映射下,说不出的美。也许大学之美皆在人们的憧憬与回忆之中。</p>
</blockquote>
<h2 id="题记">题记</h2><p>大学是什么?有人说大学是社会的预科,人际关系、左右逢迎都是这四年的必修课;有人说大学之大不在大楼,而在大师,大学是离大师最近的地方;有人说大学就是青春,是足球场上的汗水,是兄弟间的宿醉,是能够滋养爱情的盆栽。所有人都在说大学,大学却很少说什么,它就在那里,在那片土地等着送上门的学子,狠狠的在他们的生命里刻上几刀。</p></summary>
<category term="心情" scheme="https://andimeo.us.kg/categories/%E5%BF%83%E6%83%85/"/>
<category term="回忆" scheme="https://andimeo.us.kg/tags/%E5%9B%9E%E5%BF%86/"/>
<category term="大学" scheme="https://andimeo.us.kg/tags/%E5%A4%A7%E5%AD%A6/"/>
</entry>
</feed>