Blog of Endle
2024-03-18T16:37:03+00:00
https://blog.zhenbo.pro
LeetCode 2948 题解 (又是 Sliding Window)
2024-03-12T00:00:00+00:00
https://blog.zhenbo.pro/2024/03/12/leetcode-2948-solution-yet-another-sliding-window
<p>题目链接 <a href="https://leetcode.com/problems/apply-operations-to-maximize-frequency-score/description/">https://leetcode.com/problems/apply-operations-to-maximize-frequency-score/description/</a></p>
<h3 id="题目描述">题目描述</h3>
<p>在一个一维坐标轴上有 N 个村庄 (0-indexed),坐标 <code class="language-plaintext highlighter-rouge">A[i]</code> 为正整数。 给定最大交通成本 <code class="language-plaintext highlighter-rouge">k</code>.<br />
我们准备设立一个邮局。选择一个区间 <code class="language-plaintext highlighter-rouge">[L,R]</code> 和邮局坐标 <code class="language-plaintext highlighter-rouge">x</code>, 定义交通成本 $c=\sum_{i=L}^{R}|x-A_i|$ 。 在满足 $c<=k$ 的前提下,找到最大的区间。</p>
<h3 id="题目分析">题目分析</h3>
<p>我们先考虑一个子问题:对于给定的区间 <code class="language-plaintext highlighter-rouge">[L,R]</code>,我们如何求出最低的交通成本。对此,我们先引入三个引理:</p>
<ol>
<li>$A_L <= x_{opt} <= A_R$ (Too Trivial)</li>
<li>最优的邮局坐标一定和某个村庄(M-th)重合,即 $x_{opt}=A_M$.</li>
<li>如果区间长度为奇数,则 $M_{opt}$ 就是最中心的一个村庄。如果为偶数,那最中间的两个村庄都是最优的选择.</li>
</ol>
<p>在此基础上,我们可以通过如下变换,利用前缀和,在常数时间内求出特定区间的交通成本 $c$</p>
\[\begin{aligned}
c &= c_L + c_R \\
c_L &= \sum_{i=L}^{M} A_M - A_i \\
&= (M-L+1)A_M - \sum_{i=L}^{M}A_i \\
&= (M-L+1)A_M - ( \sum_{i=0}^{M}A_i - \sum_{i=0}^{L-1} A_i)\\
c_R &= \sum_{i=M}^{R} A_i - A_M \\
&= \sum_{i=0}^{R} A_i - \sum_{i=0}^{M-1}A_i - (R-M+1)A_M
\end{aligned}\]
<h3 id="算法实现">算法实现</h3>
<p>有了如上的分析,实现 Sliding Window 算法就非常容易了。将所有坐标排序后,先求出前缀和 $P_V = \sum_{i=0}^{V} A_i$</p>
<p>我们将初始的 Window 设置为 $L=R=0$. 显然,此时 $c=0$,是一种合法的情况。按照通常的 Sliding Window 的做法,我们不断移动右边界 $R$,在 $c>k$ 时移动左边界 $L$. 我们的贪心算法会找出最优解。</p>
<h3 id="引理证明">引理证明</h3>
<h4 id="lemma-2">Lemma 2</h4>
<p>使用反证法,假设 $A_M < x^* < A_{M+1} $, 此时交通成本<br />
\(\begin{aligned}
c &= c_L + c_R \\
&= \sum_{i=L}^{M}( x^* - A_i )+ \sum_{i=M+1}^{R} (A_i - x^*)\\
\end{aligned}\)</p>
<p>我们将邮局向左移动至 $A_M = x’ < x^* < A_{M+1} $, $x^*-x’=d>0$</p>
<p>左侧的 <code class="language-plaintext highlighter-rouge">(M-L+1)</code> 个村庄的成本会下降 <code class="language-plaintext highlighter-rouge">(M-L+1)d</code>, 而右侧的 <code class="language-plaintext highlighter-rouge">(R-M)</code> 个村庄的成本会上升 <code class="language-plaintext highlighter-rouge">(R-M)d</code>.</p>
<p>这样,我们分三种情况讨论: </p>
<ol>
<li>如果 $x^*$ 左侧的村庄更多,即 $M-L+1 > R-M$, 则 $x’=A_M$ 优于 $x^*$</li>
<li>如果两侧的村庄一样多,移动至 $x’=A_M$ 不会得到更差的结果 </li>
<li>如果右侧的村庄更多,易证 $x’'=A_{M+1}$ 优于 $x^*$</li>
</ol>
<p>Lemma 2 得证。 </p>
<h4 id="lemma-3">Lemma 3</h4>
<p>可以沿用类似 Lemma 2 的计算方法。区间长为奇数时,根据引理,<code class="language-plaintext highlighter-rouge">M = (R-L) / 2 + L</code>。 如果我们要向左移动到 $A_{M-1}$, $A_M - A_{M-1}=d>0$。 左侧的村庄 <code class="language-plaintext highlighter-rouge">[L, M-1]</code> 的成本会下降,右侧 <code class="language-plaintext highlighter-rouge">[M+1, R]</code> 的成本会上升。这两部分抵消后,村庄 M 的交通成本会增加 <code class="language-plaintext highlighter-rouge">d</code>,得到一个更差的结果。</p>
<p>如果区间长为偶数,用同样的方法可以得到,在最中间的两个村庄之间移动邮局的 $\Delta=0$.</p>
<h3 id="后记">后记</h3>
<p>这道题几个月前被丢到了我的 list上。我最开始没有意识到 Lemma 3, 而是试图维护一个三元数组 <code class="language-plaintext highlighter-rouge">(L, M, R)</code>,在Sliding的过程中动态调整邮局的位置。顺着这个思路想下去,就是越想越偏离正解。 看了答案以后,意识到最优的邮局地点是固定的,也学到了借助前缀和来求特定区间的交通成本,从而避免了手动维护当前Window的成本。</p>
<p>在写题解的过程中,发现 Lemmy 2&3 的证明过程其实可以合并。我之前做这类题目时,对证明过程理解不到位。写这篇题解让我的认识深入了很多,也发现完全可以用很简明的方式证明。</p>
<p>LeetCode 原题里没有提到坐标,我借用 <a href="https://www.luogu.com.cn/problem/solution/P4767">IOI2000 邮局</a> 的题目背景,感觉直观了不少。</p>
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script type="text/javascript" id="MathJax-script" defer="" src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js">
</script>
TopCoder SRM 848 AlternateParity 题解
2023-08-06T00:00:00+00:00
https://blog.zhenbo.pro/2023/08/06/srm-848-alternateparity-solution
<p>2023年8月3日,我参加了 <a href="https://community.topcoder.com/stat?c=round_overview&rd=19668">TopCoder SRM 848</a>,结果 Div I 第一题就没做出来,零分完赛。赛后看了<a href="https://community.topcoder.com/stat?c=problem_solution&cr=90029704&rd=19668&pm=18085">其他选手的代码</a>,意识到这其实是一个比较基础的组合数学问题。</p>
<h2 id="alternate-parity-题目描述">Alternate Parity <a href="https://community.topcoder.com/stat?c=problem_statement&pm=18085&rd=19668&rm=&cr=90029704">题目描述</a></h2>
<p>相比于原有的 <a href="https://community.topcoder.com/stat?c=problem_statement&pm=18085&rd=19668&rm=&cr=90029704">题目描述</a>, 我决定将其转换为如下形式: 给定两个正整数 <code class="language-plaintext highlighter-rouge">X</code> <code class="language-plaintext highlighter-rouge">L</code>, 生成一个长度为 <code class="language-plaintext highlighter-rouge">L</code> 的数列,要求:</p>
<ol>
<li>数列严格单调递增</li>
<li>相邻的两个数字奇偶性不同</li>
<li>数列的每个元素为正整数</li>
<li>数列最后一个元素 $a_L=X$</li>
</ol>
<p>求所有合法的序列数量,并对某大质数取模。</p>
<h2 id="解答">解答</h2>
<p>可以通过若干步变换,将它转化为一个<a href="https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)">隔板法(Stars and bars)</a>问题。</p>
<h3 id="a1-的奇偶性">a1 的奇偶性</h3>
<p>对 X 和 L 的奇偶性分类讨论,一共有四种情况。易证 $(X+L)\%2==0$ <=> $a_1\%2==0$</p>
<h3 id="构建辅助序列-b">构建辅助序列 b</h3>
<p>数列 a 要求相邻的两个数字奇偶性不同,也就是相邻的元素的差为基数。通过求差值,我们可以定义一个辅助数列 <code class="language-plaintext highlighter-rouge">b</code>,使序列中的每一个元素都为偶数。</p>
<h4 id="a1-为奇数">a1 为奇数</h4>
<p>\(b = \left\{ \\
\begin{aligned}
& b_1 = a_1 - 1 \\
& b_i = a_i - a_{i-1} - 1
\end{aligned}
\right.\)</p>
<p>$\sum_{i=1}^{L}b_i = a_L-L = X-L$</p>
<h4 id="a1-为偶数">a1 为偶数</h4>
<p>\(b = \left\{ \\
\begin{aligned}
& b_1 = a_1 - 2 \\
& b_i = a_i - a_{i-1} - 1
\end{aligned}
\right.\)<br />
$\sum_{i=1}^{L}b_i = a_L-L -1= X-L-1$</p>
<h4 id="数列b的性质">数列b的性质</h4>
<p>无论 $a_1$ 的奇偶性,数列b都符合: <br />
$b_i \% 2 == 0$<br />
$b_i \geq 0$</p>
<h3 id="构建辅助数列-c-以便用隔板法求解">构建辅助数列 c 以便用隔板法求解</h3>
<p>定义 $c_i = \dfrac{b_i}{2} + 1$, 则 \(c_i \in \mathbb{R}+\)</p>
\[\sum_{i=1}^{L}c_i = L + \dfrac{\sum_{i=1}^{L}b_i}{2}\]
\[\sum_{i=1}^{L}c_i = \left\{ \\
\begin{aligned}
& \dfrac{X+L}{2}, \text{same_parity} \\
& \dfrac{X+L-1}{2}, \text{diff_parity}
\end{aligned}
\right.\]
<p>求数列c一共有多少种合法的情况,正是 <a href="https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)">隔板法</a> 的标准模型。</p>
<h3 id="对组合数取模">对组合数取模</h3>
<p>这个基础问题触及了我的知识盲区。查到的两份资料(<a href="https://xienaoban.github.io/posts/36480.html#n%E7%9B%B8%E5%AF%B9%E5%B0%8F%E6%96%B9%E4%BE%BF%E6%89%93%E8%A1%A8p%E5%8F%AF%E4%BB%A5%E5%BE%88%E5%A4%A7p%E8%A6%81%E6%B1%82%E4%B8%BA%E7%B4%A0%E6%95%B0">A</a>,<a href="https://cp-algorithms.com/algebra/module-inverse.html#finding-the-modular-inverse-using-binary-exponentiation">B</a>)提到,可以使用<a href="https://en.wikipedia.org/wiki/Fermat%27s_little_theorem">费马小定理 Fermat’s little theorem</a> 解决这一问题。</p>
<h2 id="比赛复盘">比赛复盘</h2>
<p>比赛时,我先写出了朴素的 DP 代码 <code class="language-plaintext highlighter-rouge">f[L,X] = f[L-1,X-1] + f[L-1,X-3] +...</code>,并把这个表格显示了出来,试图找它的规律。初看起来,这和杨辉三角的形态比较类似,但我没有顺着这个方向思考下去,而试图去优化 DP 方程。赛后看到其他选手的代码,才发现是组合数学的问题。</p>
<p>我的代码存档: <a href="https://gist.github.com/Endle/1381aa72eb07fe1c92a5de2ff1cb83f6">https://gist.github.com/Endle/1381aa72eb07fe1c92a5de2ff1cb83f6</a><br />
抢在<a href="https://www.topcoder.com/blog/tag/srm/">官方题解</a>发布前写完了本文,希望搜索引擎们能给我送一些流量。</p>
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script type="text/javascript" id="MathJax-script" defer="" src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js">
</script>
Codeforces 885A Vika and Her Friends (1848A) 题解
2023-07-22T00:00:00+00:00
https://blog.zhenbo.pro/2023/07/22/codeforces-885a-vika-and-her-friends-1848a-solution
<p>2023年7月16日,我参加了 <a href="https://codeforces.com/blog/entry/118293">Codeforces 885</a>,没想到 A 题就碰了钉子。从赛后的<a href="https://codeforces.com/blog/entry/118333">官方题解</a> 看,这道题分析起来有难度,但最终的结论非常简单。除了单纯写这篇题解,我也想写一下我比赛时的心路历程,看一看我落入的思维陷阱。</p>
<h2 id="题目描述"><a href="https://codeforces.com/contest/1848/problem/A">题目描述</a></h2>
<p>Vika 和她的 k 个朋友们散布在一个有限大的国际棋盘上。在每个回合 t,Vika 和朋友都 <strong>必须同时</strong> 移动到相邻的四个格子里。虽然每次移动是同时的,但朋友们可以看到 Vika 的决策后再行动。</p>
<p>给定足够长的时间,问 Vika 是否可以躲开她的朋友们。</p>
<h2 id="解答">解答</h2>
<p>这道题存在这个性质:</p>
<p>Vika is safe <=> None of Vika’s friends is on a cell of same colour as Vika</p>
<h3 id="证明-">证明 (<=)</h3>
<p>每一次移动时,每个人脚下的颜色都必然翻转。因此,如果 <strong>None of Vika’s friends is on a cell of same colour as Vika</strong> 在时刻 <code class="language-plaintext highlighter-rouge">i</code> 成立,那也会在任何一个时刻成立。既然颜色不重合,也就抓不到 Vika。</p>
<h3 id="证明--1">证明 (=>)</h3>
<p>我的证明和官方题解略有(措辞上的)区别。既然我们要证明 <strong>Vika is safe => None of Vika’s friends is on a cell of same colour as Vika</strong>,那我们就可以证明其逆否命题(Contraposition):</p>
<p><strong>Alice, one of Vika’s friends is on a cell of same colour as Vika => Vika is not safe</strong></p>
<p>分情况讨论如下:</p>
<h4 id="vika-和-alice-在同一行或同一列a">Vika 和 Alice 在同一行或同一列(A)</h4>
<p>如果 Alice 和 Vika 在同一横行,也就是 Vika=<code class="language-plaintext highlighter-rouge">(x,y)</code>, Alice=<code class="language-plaintext highlighter-rouge">(p,y)</code>. 不失一般性,令 <code class="language-plaintext highlighter-rouge">p<x</code>.</p>
<h5 id="vika-横向移动a1">Vika 横向移动(A1)</h5>
<p>如果 Vika 向右移动到 <code class="language-plaintext highlighter-rouge">(x+1,y)</code>, 那 Alice 同时移动到 <code class="language-plaintext highlighter-rouge">(p+1),y</code>. 两人间距离不变。如果重复这一步骤,Vika 会先撞墙。</p>
<p>如果 Vika 向左移动 <code class="language-plaintext highlighter-rouge">(x-1,y)</code>, 因为两人初始位置同色,所以间隔至少为2. Alice 向右移动,<code class="language-plaintext highlighter-rouge">p+1<=x-1</code>. 两人间距缩小,肯定不会错开。</p>
<h5 id="vika-纵向移动a2">Vika 纵向移动(A2)</h5>
<p>如果 Vika 向右移动到 <code class="language-plaintext highlighter-rouge">(x+1,y)</code>,那 Alice 移动到 <code class="language-plaintext highlighter-rouge">(p+1),y</code>. 两人间的 taxicab distance 保持不变,但转换为了不在同一行或同一列的情况</p>
<h4 id="vika-和-alice-的行列均不相同b">Vika 和 Alice 的行列均不相同(B)</h4>
<p>此时 Vika=<code class="language-plaintext highlighter-rouge">(x,y)</code>, Alice=<code class="language-plaintext highlighter-rouge">(p,q)</code>. 我们认为两人的位置构成了一个矩形 S,且 S 包含了两人当前的位置。</p>
<h5 id="vika-下一步的位置在-s-外b1">Vika 下一步的位置在 S 外(B1)</h5>
<p>那 Alice 可以同向移动。这样,矩形 S 向棋盘边界平移了一格。</p>
<h5 id="vika-下一步的位置在-s-内b2">Vika 下一步的位置在 S 内(B2)</h5>
<p>Alice 下一步的位置同样也在 S 内。如果 Vika 横向移动,Alice 就纵向移动,反之亦然。这样的回合后,两人的 taxicab distance 会下降2. 因为最初色块相同,所以 taxicab distance 在任何时刻,都是2的倍数(包含0)</p>
<h4 id="距离会不断下降">距离会不断下降</h4>
<p>Vika 重复 B1 可以保持两人距离相同,但因为棋盘有界,Vika 会先撞到边缘,导致 Vika 不得不采取行动 B2,缩短两人距离。</p>
<p>如果 Vika 采取 A1,那也会不可避免地先撞墙。如果 Vika 执行 A2,会将局面转化为 B,但两人距离并没有拉长,最终也会因为 B2 而被 Alice 抓住。</p>
<p>现在 <code class="language-plaintext highlighter-rouge">(<=)</code> 和 <code class="language-plaintext highlighter-rouge">(=>)</code> 都已经证明完毕,原命题得证。</p>
<h2 id="错误总结">错误总结</h2>
<h3 id="vika-和朋友必须移动">Vika 和朋友必须移动</h3>
<p>我不假思索地采纳了一个推论:如果朋友的数量多于棋盘的列数,那就可以朋友们站成一排,像国际象棋的兵线一样推进,Vika 也就无法逃生了。</p>
<p>但是,这个判断是错误的。如果所有的朋友都站在白格上,那无论怎么排,也是无法拉成一条直线的。</p>
<h3 id="如何理解同时行动">如何理解同时行动</h3>
<p>在比赛时,我没能理解同时行动的含义。所以,我想当然地认为,如果出现了下图中的这种“困兽斗”的情况,Vika 是无法离开的。但我们在 <code class="language-plaintext highlighter-rouge">(<=)</code> 里证明,这种情况下,Vika 可以躲开所有的朋友。甚至于,如果 Vika 主动想和朋友们碰面也是做不到的。</p>
<p><img src="/images/2023/codeforces/cf885A.png" alt="An example of Vika being trapped in a corner" /></p>
<p>因为这两个错误的推论,我没有对 Vika 和朋友们脚下的色块深入思考,也就没机会自己找出文首的命题。</p>
<h3 id="证明追逐不会无限制地持续">证明追逐不会无限制地持续</h3>
<p>在写本篇题解时,我本来想用下面一段话来证明 <code class="language-plaintext highlighter-rouge">(=>)</code></p>
<p><em>虽然 Alice 和 Vika 要同时行动,但 Alice 可以看到 Vika 的决策。这样,如果 Vika 选择远离 Alice,Alice 可以只同向行动,就能保持两人的taxicab distance不变。因为地图是有界的,Vika 总会被 Alice 追到。</em></p>
<p>这句话看起来很符合直觉,但是我写下来的时候,发现这省略了一个问题:如果 Alice 一直在追逐 Vika,但无法缩短两人的 taxicab distance 怎么办?我只好花了不少时间,换了一种论证的方式。当然,这样的话又有些啰嗦。如果我一开始就使用矩形 S 来论证,可能就会精简一些。</p>
为 Rust 项目的 Github Action 启用 Sccache - 2023 年的新办法
2023-01-27T00:00:00+00:00
https://blog.zhenbo.pro/2023/01/27/rust-github-action-enable-sccache
<p>受到 <a href="https://xuanwo.io/reports/2023-04/">Xuanwo 的博客</a> 的鼓动,我决定给自己的 Rust 项目
<a href="https://github.com/Endle/fireSeqSearch">fireSeqSearch</a> 加上 <a href="https://github.com/mozilla/sccache">Sccache</a>。</p>
<h4 id="sccache-概述">sccache 概述</h4>
<p><a href="https://github.com/mozilla/sccache">Sccache</a> 可以简单看成 <a href="https://ccache.dev/">ccache</a> 的 Rust
版。它有很多激动人心的特性,但那不在本文的范畴里。我在本地编译原有的项目时,只需运行
<code class="language-plaintext highlighter-rouge">RUSTC_WRAPPER="sccache" cargo build</code> 即可启用 sccache。默认的缓存路径为
<code class="language-plaintext highlighter-rouge">~/.cache/sccache</code></p>
<h4 id="缓存-cargo-registry">缓存 Cargo Registry</h4>
<p><a href="https://github.com/Endle/fireSeqSearch/blob/6e760731e1f91df5f647f0bd551aceb5d83bfcbb/.github/workflows/rust.yml#L35">我原先的代码里</a> 缓存了 <code class="language-plaintext highlighter-rouge">~/.cargo/registry</code>,但这只是编译前的 <a href="https://doc.rust-lang.org/cargo/reference/registries.html">crate.io registry</a>, 而不是编译产生的目标代码,加速 CI 效果有限。</p>
<h4 id="2022-年的笨办法">2022 年的笨办法</h4>
<p>最开始,我仅仅是将 <code class="language-plaintext highlighter-rouge">~/.cache/sccache</code>
添加到了缓存路径里。<a href="https://github.com/Endle/fireSeqSearch/blob/0e26622b8d794a2dbc83afaeb6fca1fa48cb7d01/.github/workflows/rust.yml">代码如下</a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> - name: Cache cargo registry and sccache
uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/registry
~/.cache/sccache
</code></pre></div></div>
<p><a href="https://github.com/Endle/fireSeqSearch/commit/0e26622b8d794a2dbc83afaeb6fca1fa48cb7d01">我的这个改动</a>
误解了 <a href="https://github.com/mozilla/sccache-action">sccache-action</a>
的用法,并没有发挥它真正的强大之处。使用笨办法,大概 600M 的 <code class="language-plaintext highlighter-rouge">~/.cache/sccache</code> 都会被 GitHub Action
缓存。随着时间推移,缓存文件的体积会膨胀,在 <code class="language-plaintext highlighter-rouge">restore/save</code>
时浪费时间。<a href="https://gist.github.com/Endle/efe07ca76b6e148c4682e101ff9a6731">我自己的经验</a>,在 macOS instance
上,接收 150M 的缓存就用了 3 分钟 (当然,这也与服务器实际运行情况有关)。</p>
<h4 id="2023-年的新办法">2023 年的新办法</h4>
<p>感谢 <a href="https://xuanwo.io/">Xuanwo</a> 本人的答疑,我将 <code class="language-plaintext highlighter-rouge">~/.cache/sccache</code> 移出了
<code class="language-plaintext highlighter-rouge">actions/cache@v3</code>。<a href="https://github.com/Endle/fireSeqSearch/commit/969950f7fdb794eab1b57880d0334b5285bb404f">改动</a>后的<a href="https://github.com/Endle/fireSeqSearch/blob/969950f7fdb794eab1b57880d0334b5285bb404f/.github/workflows/rust.yml">代码如下</a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
SCCACHE_GHA_ENABLED: "true"
--- snip ---
- name: Run sccache-cache
uses: Xuanwo/sccache-action@c94e27bef21ab3fb4a5152c8a878c53262b4abb0
with:
version: "v0.4.0-pre.6"
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
</code></pre></div></div>
<h4 id="最直接的优点细粒度缓存">最直接的优点:细粒度缓存</h4>
<p>相比旧办法,使用 <a href="https://github.com/mozilla/sccache-action">sccache-action</a>
并不会将 <code class="language-plaintext highlighter-rouge">~/.cache/sccache</code> 作为整体上传到 GitHub Action Cache 里,而是会细粒度地将每个对象上传。</p>
<p>在
<a href="https://github.com/Endle/fireSeqSearch/actions/caches">https://github.com/Endle/fireSeqSearch/actions/caches</a> 里可以看到,大小从几K到几百K,乃至20M的对象被存入了 GHA。这很好地避免了旧方法中缓存文件夹膨胀的问题。</p>
<p><img src="/images/2023/rust/Screenshot 2023-01-28 GitHub Action Cache.png" alt="Screenshot of Github Action Cache" /></p>
<h4 id="后记">后记</h4>
<p>在撰写完本文大概两周后,发现 <a href="https://xxchan.me/about/">xxchan</a> 写了一篇博文 <a href="https://xxchan.me/cs/2023/02/11/optimize-rust-comptime.html">我如何动动小手就让 CI 时间减少了 10 分钟</a> 比本文更详细、更完整。</p>
从零写一个 Beancount CSV Importer
2023-01-21T00:00:00+00:00
https://blog.zhenbo.pro/2023/01/21/write-beancount-csv-importer-from-scratch
<h4 id="缘由">缘由</h4>
<p>在 2020 年,我曾经尝试过使用 Beancount 进行记账。但当时,我每一笔开销都是手动记录的。在最初的兴趣消退后,就再没有兴致去记账了。最近,感觉需要统计一下自己的开销比例,就重新翻出了 Beancount。当然,我要吸取上次的教训,选择直接导入信用卡账单,而非手动记录开销。最终的结果,是我在除夕忙了一整天,得到了一个初步可用的 python 程序,和这篇博客文章。</p>
<h4 id="为何撰写本文">为何撰写本文</h4>
<p>Amex CA 可以直接从网页上下载 CSV 格式的账单,内容非常简单,脱敏示例如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>12/14/2022,"Reference: 003"," 44.05","SHOPPERS DRUG MART","",
12/14/2022,"Reference: 002"," 6.76","SOBEYS","",
12/16/2022,"Reference: 001"," 12.99","MEMBERSHIP FEE INSTALLMENT","",
</code></pre></div></div>
<p>可是,我在网上找了很多圈,处理这种 CSV 好像都是 too trivial case, 包括<a href="https://beancount.github.io/docs/importing_external_data.html">官方文档</a>在内,对这个环节的描述都是凤毛麟角。</p>
<p>文章看了很多,工具也尝试了好几个,但最后,还是要自己撸代码,写一个 Importer。</p>
<h4 id="代码实现">代码实现</h4>
<p>参考 <a href="https://mterwill.com/">Matt Terwilliger</a> 的 <a href="https://gist.github.com/mterwill/7fdcc573dc1aa158648aacd4e33786e8">Gist</a>, 我写出了一个简单的 Importer。</p>
<p><a href="https://gist.github.com/Endle/1033eb36135b50e19ea64ccc39be5ca7">https://gist.github.com/Endle/1033eb36135b50e19ea64ccc39be5ca7</a></p>
<p>基类 <code class="language-plaintext highlighter-rouge">importer.ImporterProtocol</code> 只有两个必须实现的函数</p>
<h5 id="identify">identify()</h5>
<p>返回 bool,判断是否要处理当前文件。有些人写的 <code class="language-plaintext highlighter-rouge">config.py</code> 比较完善,只要运行 <code class="language-plaintext highlighter-rouge">bean-extract config.py ofx.csv ~/Downloads/statements/*</code>, <code class="language-plaintext highlighter-rouge">bean-extract</code> 就会根据 <code class="language-plaintext highlighter-rouge">identify()</code> 的结果,确定要用哪一个 Importer。在这里,我的代码比较简单。</p>
<h5 id="extract">extract()</h5>
<p>这个函数处理 CSV 账单。<a href="https://blog.sy-zhou.com/">哓哓</a> 的<a href="https://blog.sy-zhou.com/%E7%94%A8%E4%BA%8E%E6%94%AF%E4%BB%98%E5%AE%9D%E5%92%8C%E5%BE%AE%E4%BF%A1%E8%B4%A6%E5%8D%95%E7%9A%84beancount-import/">文章里</a>介绍了如何沿袭 CSV Importer 的结构写 Importer,但在起步阶段,我没有遵循这个结构,而是手动读 CSV 以后逐行处理。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> def extract(self, f):
entries = []
with open(f.name) as f:
for index, row in enumerate(csv.reader(f)):
txn = self._process_row(index, row)
entries.append(txn)
return entries
</code></pre></div></div>
<h4 id="如何在-windows-下安装-beancount-cygwin-vs-wsl">如何在 Windows 下安装 Beancount (Cygwin vs WSL)</h4>
<p>买一台新电脑的计划还是搁置状态,所以我只好在 Windows 笔记本上先凑合用。<a href="https://beancount.github.io/docs/installing_beancount.html">官方手册</a>上说,<em>It’s a breeze if you use Cygwin</em>. 但我在执行 <code class="language-plaintext highlighter-rouge">python3 -m pip install beancount-import smart_importer</code> 时,总会在安装依赖 <code class="language-plaintext highlighter-rouge">scikit-learn</code> 时卡住。</p>
<p>接下来,我就换到 WSL 里运行了。我在执行 <code class="language-plaintext highlighter-rouge">sudo pip3 install m3-cdecimal</code> 时失败了,但跳过这一步我也没看到有什么影响。</p>
<h4 id="后记">后记</h4>
<p>我也为这是一个很简单的需求,但没想到,真做起来还是花了不少的时间。有很多人上传了自己实现的 importer,但在不了解 context 和设计思路的情况下,想要拿来直接用很困难。<a href="https://plaintextaccounting.org/#data-importconversion">这个页面</a> 列举了很多 Importer,比如 Clojure 实现的 <a href="https://github.com/PaNaVTEC/csv2beancount">csv2beancount</a>. 还有 <a href="http://gaocegege.com/Blog/">高策</a> 等人用 Go 实现的 <a href="https://github.com/deb-sig/double-entry-generator">double-entry-generator</a>.</p>
<p>此外,<a href="https://plaintextaccounting.org/#data-importconversion">这个页面</a>还有一些输出到 Ledger 和 HLedge 的工具。<a href="https://beancount.github.io/docs/a_comparison_of_beancount_and_ledger_hledger.html">这篇文章</a> 介绍了一些区别。</p>
2022 年的总结
2022-12-31T00:00:00+00:00
https://blog.zhenbo.pro/2022/12/31/2022-year-review
<p>看到我社交网络里不少人都在发 2022 年的总结,我也有点心痒,准备写一篇流水账,总结一下我的 2022.</p>
<h4 id="pl-门外汉">PL 门外汉</h4>
<p>给一个没有系统学过编程语言那套理论的人介绍一点点 PL 知识会发生什么呢?答案就是 <a href="https://github.com/Endle/ironCamel">ironCamel</a>. 靠着三脚猫的知识,总算实现了自己人生中的第一个编译器,达成了程序员的一大浪漫。后面,还高强度地看了一大批 PL 的论文,虽然收获也仅仅是略懂皮毛而已。有点遗憾的是,自己在自己的热情耗尽前,没能读完 <a href="https://www.worldcat.org/title/types-and-programming-languages/oclc/51958338">TAPL</a></p>
<p>注:也不太能算写完了编译器。自己并没有实现后端代码生成的部分,而是直接执行了 AST</p>
<h4 id="笔记软件大升级">笔记软件大升级</h4>
<p><a href="https://twitter.com/ZhenboLi1/status/1583837511557984256">在 2021 年 10 月 22 日</a>,我开始使用 <a href="https://github.com/logseq/logseq/">LogSeq</a> 作为自己的主力笔记软件。除了给 <a href="https://github.com/logseq/logseq/">LogSeq</a> 打广告,更重要的是给我自己的开源项目 <a href="https://github.com/Endle/fireSeqSearch">fireSeqSearch</a> 做广告。</p>
<video src="https://user-images.githubusercontent.com/3221521/168455012-e1183f62-4682-4230-84e7-8a461d8985a0.mp4" data-canonical-src="https://user-images.githubusercontent.com/3221521/168455012-e1183f62-4682-4230-84e7-8a461d8985a0.mp4" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:640px; max-width: 100%;">
</video>
<p>这个项目花掉了我非常多的精力(拖延的一大理由),但<a href="https://fediscience.org/@zhenboli/109419358200038482">也让我很开心</a>。希望看到这里的朋友可以考虑尝试一下 <a href="https://github.com/logseq/logseq/">LogSeq</a>, 用开放格式将全部数据存储在本地的开源笔记软件。</p>
<h4 id="社交帐户的迁移">社交帐户的迁移</h4>
<p>感谢一位马姓天才,我<a href="https://fediscience.org/@zhenboli">顺利迁移到了长毛象</a>. 我由衷希望,看到这里的朋友,考虑一下 <a href="https://en.wikipedia.org/wiki/ActivityPub">ActivityPub</a>. 这一波告别推特的风潮,很可能是 <a href="https://www.usg.edu/galileo/skills/unit07/internet07_02.phtml">1983 年发明互联网</a> 以来,建立一个去中心化的社交平台最好的机会了。就像星战衍生剧安多的剧情,逃出工厂,现在就是最好的,也是最后的机会。</p>
<p>另外,之前买的域名也派上了用场。现在我的博客<a href="https://fediscience.org/@zhenboli/109351164744707704">迁移</a> 到了 <a href="https://blog.zhenbo.pro/">https://blog.zhenbo.pro/</a>,不过还是 Github Hosted。替换掉 Jekyll 的计划也只停留在了纸面上。</p>
<p>一个小任务,<a href="https://fediscience.org/@zhenboli/109609779087692196">换掉 Google Analytics</a>, 被拖延到了 2023 年。当然,这个任务没机会被拖延到 2024 了:<a href="https://support.google.com/analytics/answer/11583528?hl=en">G家自己把旧的 Analytics 扬了</a>.</p>
<h4 id="video-games">Video Games</h4>
<p>现在是,<a href="https://twitter.com/ZhenboLi1/status/1503416807046144009">美梦</a>成真时间!在2022年,终于抢到了 PS5。靠着 <a href="https://psnine.com/topic/35604">PS Collection</a>,体验了不少经典游戏。如果让我选择的话,《战地一》是我心目中的 Game of the Year (以我玩的时间为准)。</p>
<p>期待已久的维多利亚三终于发布了,不过我只玩了一点点。我现在非常羡慕那些说着 “不知道玩什么游戏” 的朋友。我现在的一大苦恼,就是我想玩的游戏,远远超过我可以花在游戏上的时间。</p>
<h4 id="music">Music</h4>
<p>Spotify 的 2022 个人精选集出来了。排名第一的是 Christopher Tin 的 Sogno di Volare。虽然我没玩过文明六,但这首歌我是真的很喜欢。在这里先预祝 Tin 能赢得 2023 年的格莱美奖。(<a href="https://www.paloaltoonline.com/news/2022/12/04/around-town-composer-christopher-tin-collects-two-grammy-nominations">获得两项提名</a>)</p>
<p>排名第二的是《潘多拉之心》的插曲 <a href="https://mochijun.fandom.com/wiki/Everytime_you_kissed_me">Everytime You Kissed Me</a>. 这部番我大概是15年左右看的。我对番剧内容的评价有所保留,但我认为音乐非常优秀。这首歌我不知道我重复播放了多少次。</p>
<p>第三名是《你的名字》 插曲 Sparkle English Ver. 我觉得英文版的歌曲更胜日文版,它带来了一种截然不同的体验。我听到这首歌,仿佛看到一位高中女生,开着父亲的皮卡,从旧金山开到了纽约,穿过了沙漠与山谷。可能是我对这首歌脑补过度吧。</p>
<h4 id="bottom-end">Bottom End</h4>
<p>引述一下<a href="https://twitter.com/ZhenboLi1/status/1478567278186676228">我在年初发的一条推</a><br />
<em>感觉应该隔一段时间就学一门新的编程语言,看看不同的设计思路。就像天堂电影院的台词,如果你不出去走走,就会以为眼前的就是全世界。</em></p>
<p>这句话里的 PL,应该替换成 <code class="language-plaintext highlighter-rouge">template <class T></code>,适用于生活的方方面面。越是自己(自以为)了解的方面,就越会有未曾想到的可能性。</p>
<p><img src="/images/2022/2022_summary_sfo2nyc.png" alt="Image SFO to NYC" /></p>
Codeforces 282B Painting Eggs 题解与贪心算法的证明
2022-06-21T00:00:00+00:00
https://blog.zhenbo.pro/2022/06/21/cf-282b-painting-eggs-solution-how-to-prove-the-greedy-algorithm
<p>题目链接: <a href="https://codeforces.com/problemset/problem/282/B">https://codeforces.com/problemset/problem/282/B</a></p>
<p>官方题解: <a href="https://codeforces.com/blog/entry/6999">https://codeforces.com/blog/entry/6999</a></p>
<p><a href="https://codeforces.com/contest/282/submission/3314492">官方代码</a></p>
<p>CF 282B 我思考了很久,也没有什么头绪。看了官方题解,发现可以用一个很简单的贪心算法求解。证明的过程要用到数学归纳法(Mathematical induction),我按照我的理解,把证明过程详细地写下来。</p>
<p>贪心算法:对于每一个鸡蛋,尝试让 A 喷绘。如果这会导致让 A 的收入高于 G 的收入,且超出了 500 的幅度,则把这个鸡蛋交给 G。</p>
<p>引理1:对于合法的输入数据,一定存在至少一种方法,使得两人的收入和之差
\(|S_a - S_g| \leq 500\)</p>
<p>求证:使用贪心算法,可以得到正确解。</p>
<p>Base case is trivial.</p>
<p>递推步骤,假设对于前 <code class="language-plaintext highlighter-rouge">i-1</code> 个鸡蛋,我们已经找到了一种方法,满足
\(|S_a - S_g| \leq 500\)
。不失一般性 (WLOG),我们假定
\(S_a - S_g = D\)
且
\(0 \leq D \leq 500\)</p>
<p>现在,我们尝试将第 <code class="language-plaintext highlighter-rouge">i</code> 个鸡蛋分配给 A. 如果
\(D + a_i \leq 500\)
仍然成立,那我们就得到了满足前 <code class="language-plaintext highlighter-rouge">i</code> 个鸡蛋的合法方案。</p>
<p>反之,我们将第 <code class="language-plaintext highlighter-rouge">i</code> 个鸡蛋分配给 G。此时,
\(a_i > 500 - D \iff 1000 - g_i > 500 - D \iff g_i < 500 + D\)</p>
<p>新的差值是
\(D_i = (S_g + g_i) - S_a = g_i - D\)
显然
\(-D \leq D_i < 500\)
我们就得到了满足前 <code class="language-plaintext highlighter-rouge">i</code> 个鸡蛋的合法方案。</p>
<p>引理1 也由此得到了证明。Base case 选择一个报酬不超过 500 元的人,接着每次扩展,都不会打破 500 元工资差的限制。</p>
<h4 id="弯路">弯路</h4>
<p>看答案之前,我没有想到引理1. 我猜到这是一道贪心的题目,但题目里的“无法满足时输出-1”迷惑了我,让我一直在想,我的贪心策略会不会让一个可行的输入被误认为不可能。</p>
<p>我一直试图寻找一个给输入数据排序的方法,接着用类似对对碰的方式进行贪心匹配。我一直试图找一个例子,证明错误的贪心顺序会导致错误。最终当然是找不到了。</p>
<p>这道题给定的限制条件是不超过 500 的工资差,而两人的工资和则恰巧是 1000 元。这其实给了我很大的提示,两人的工资差可能是钟摆式的摆动,但我没有顺着这个思路思考下去。</p>
<p>我在做这道题的时候,一度试图把 500 元的条件拿掉,去找一个让两人工资差最低的方案。我成功地给自己找了一个困难得多的问题。</p>
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script type="text/javascript" id="MathJax-script" defer="" src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js">
</script>
Fedora Silverblue 35 设置为家庭服务器
2022-05-09T00:00:00+00:00
https://blog.zhenbo.pro/2022/05/09/fedora-silverblue-35-as-homeserver
<p>几周前,我领到了新的笔记本。从 2017 年为我工作的 Dell XPS 也光荣地退居二线了。我构思了很久的搭建家庭服务器的行动,也终于得以付诸实施了。写一篇流水账,记录一下我的操作流程。</p>
<h4 id="安装-fedora-silverblue-35-以及-xfce">安装 Fedora Silverblue 35 以及 XFCE</h4>
<h4 id="配置-ssh-登录">配置 SSH 登录</h4>
<p>Server 的 <code class="language-plaintext highlighter-rouge">$HOME/.SSH</code> 目录需要设置为 <code class="language-plaintext highlighter-rouge">700</code> 权限。</p>
<h4 id="自动挂载automount-移动硬盘">自动挂载(Automount) 移动硬盘</h4>
<p>在这里,我没能找到用 XFCE 的 GUI 工具设置自动挂载的方式。我的解决方案是,切换到 Gnome 3 里,选择 nautilus -> disk,进而手动选择挂载点。</p>
<h4 id="flatpak-应用的自动启动">Flatpak 应用的自动启动</h4>
<p>使用 Flatpak 安装的应用的 <code class="language-plaintext highlighter-rouge">.desktop</code> 文件会存放在 <code class="language-plaintext highlighter-rouge">/var/lib/flatpak/exports/share/applications</code> 。打开后,就可以知道如何在命令行中运行某个应用。</p>
<h4 id="安装-nvidia-闭源驱动-失败">安装 Nvidia 闭源驱动 (失败)</h4>
<p>我参考 <a href="https://web.archive.org/web/20231211123426/https://nudesystems.com/how-to-install-nvidia-drivers-in-fedora-silverblue/">https://nudesystems.com/how-to-install-nvidia-drivers-in-fedora-silverblue/</a> 一文,使用 rpmfusion 的包安装 NVidia 闭源驱动。虽然软件包安装成功,但 <code class="language-plaintext highlighter-rouge">nvidia-smi</code> 等工具并不能正常运行。因为也不打算用这台家庭服务器玩游戏,就没有深究这个问题。</p>
<h4 id="安装-teamviewer">安装 Teamviewer</h4>
<p>参考 <a href="https://community.teamviewer.com/English/kb/articles/30664-use-the-tar-package-for-linux">https://community.teamviewer.com/English/kb/articles/30664-use-the-tar-package-for-linux</a>,可以从 <a href="https://www.teamviewer.com/en-us/download/linux/">https://www.teamviewer.com/en-us/download/linux/</a> 下载 tar 包。运行 <code class="language-plaintext highlighter-rouge">./tv-setup checklibs</code> 提示缺少 <code class="language-plaintext highlighter-rouge">libminizip.so.1</code> 和若干 QT 库。运行 <code class="language-plaintext highlighter-rouge">rpm-ostree install qt5-qtquickcontrols qt5-qtquickcontrols2 minizip-compat</code> 后,teamviewer 可以顺利运行了。</p>
<!--This is a comment. Comments are not displayed in the browser
- Set up home server
- 1. Install Fedora Silverblue 35
2. Install XFCE
3. Config httpd failed
4. login with ssh pubkey failed. set ~/.ssh to 700 on target machine!
5. set auto mount with remote media (it didn't work)
6. set auto mount with gnome disk (nautilus then disk), specify mount point
7. set auto start, check /var/lib/flatpak/exports/share/applications
8. add rpmfusion, and upgrade (https://nudesystems.com/how-to-install-nvidia-drivers-in-fedora-silverblue/)
9. rpm-ostree install akmod-nvidia xorg-x11-drv-nvidia
10. also add xorg-x11-drv-nvidia-cuda
11. nvidia driver still not good
12. download tar from https://www.teamviewer.com/en-us/download/linux/
13. https://community.teamviewer.com/English/kb/articles/30664-use-the-tar-package-for-linux
14. ./tv-setup checklibs
- Analyzing dependencies ...
libminizip.so.1 => not found
- The libraries listed above seem to be missing.
Please find and install the corresponding packages.
Then, run this command again.
- QtQuickControls seems to be missing
- The following command may be helpful:
'libdbus-1.so.3()(64bit)' 'libQt5Gui.so.5()(64bit)' 'libQt5Widgets.so.5()(64bit)' 'libQt5Qml.so.5()(64bit)' 'libQt5Quick.so.5()(64bit)' 'libQt5WebKitWidgets.so.5()(64bit)' 'libQt5X11Extras.so.5()(64bit)' qt5-qtdeclarative qt5-qtquickcontrols
- dnf provides */libminizip.so.1
- minizip-compat
- rpm-ostree install qt5-qtquickcontrols qt5-qtquickcontrols2 minizip-compat
-->
My LaTex Cheatsheet
2021-10-28T00:00:00+00:00
https://blog.zhenbo.pro/2021/10/28/my-latex-cheatsheet
<h2 id="table-表格">Table 表格</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\begin{center}
\begin{tabular}{ |c|c|c| }
\hline
cell1 & cell2 & cell3 \\
cell4 & cell5 & cell6 \\
cell7 & cell8 & cell9 \\
\hline
\end{tabular}
\end{center}
</code></pre></div></div>
<h2 id="pseudo-code-插入代码">Pseudo-code 插入代码</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\usepackage{listings}
\begin{lstlisting}
F(S, T, s[1..n], t[1..n])
a + b
\end{lstlisting}
</code></pre></div></div>
<p>Set tab size</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\lstset{
numbers=left,
breaklines=true,
tabsize=2,
}
</code></pre></div></div>
<h2 id="insert-picture-插入图片">Insert picture 插入图片</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\begin{figure}[h]
\centering
\includegraphics[width=.3\textwidth]{a.jpg}
\end{figure}
</code></pre></div></div>
<h2 id="insert-pdf-插入pdf">Insert PDF 插入PDF</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%https://stackoverflow.com/a/2739710/1166518
\usepackage{pdfpages}
\includepdf[pages=-]{q1_x20.pdf}
</code></pre></div></div>
<h2 id="多行的大括号">多行的大括号</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$$ W[i,j]= max \left\{ \\
\begin{aligned}
P[i,j], \\
max_{1\leq x\leq i,1\leq y \leq j} W[x,y] + max(
W[x,j-y]+W[i-x,j],
& W[i-x,y]+W[i.j-y])
\end{aligned}
\right.
$$
</code></pre></div></div>
<h2 id="matrix-矩阵">Matrix 矩阵</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\begin{equation*}
A=\begin{bmatrix}
10 & -4 & 18\\
11 & 0 & 0\\
0 & 16 & -3
\end{bmatrix}
\end{equation*}
</code></pre></div></div>
<h2 id="limit-at-infinity-趋近于无穷的极限">Limit at Infinity 趋近于无穷的极限</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$$\lim_{n \to \infty} y_n$$
</code></pre></div></div>
<h2 id="reference-to-equations-内链公式">Reference to equations 内链公式</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\begin{equation}
\label{eqn:o3}
0 = f(x^*) = f(x_k) + f'(x_k)(x^*-x_k) + \dfrac{1}{2}f''(x_k)(x^*-x_k)^2 + \dfrac{1}{6}f'''(u_1)(x^*-x_k)^3
\end{equation}
previous equation \ref{eqn:o3}
</code></pre></div></div>
<h2 id="禁止图片浮动">禁止图片浮动</h2>
<p>如果希望禁止浮动,可以使用 float 宏包,结合 H 选项。
参考了 <a href="https://www.zhihu.com/question/25082703/answer/30038248">孟晨的回答 - 知乎</a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\usepackage{float}
\begin{figure}[H]
\begin{table}[H]
</code></pre></div></div>
<h2 id="不进行字符转义--write-in-plain-text-mode">不进行字符转义 write in plain text mode</h2>
<p><a href="https://tex.stackexchange.com/a/422207/81692">https://tex.stackexchange.com/a/422207/81692</a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\newenvironment{simplechar}{
\catcode`\$=12
这里应该还有一个列表,但是我没配置好 Jekyll 的字符转义
}{}
\begin{simplechar}
This is a paragraph with \textbf{bold} and \emph{emphasized} text, but special characters are treated normally
</code></pre></div></div>
自己动手为 Fedora 打包的几个小技巧
2021-09-01T00:00:00+00:00
https://blog.zhenbo.pro/2021/09/01/packaging-rpm-upload-to-copr
<p>这几天<a href="https://news.ycombinator.com/item?id=27893303">被人推荐</a>了 <a href="https://github.com/ahrm/sioyek">sioyek</a>,据说是一个很适合读论文的 PDF 阅读器。但是,软件还比较新,截止到 2021 年 9 月没有 Fedora 的包。在 <a href="https://zh.fedoracommunity.org/about/">FZUG 群</a> 里有人提过,自己手动编译软件时避免 <code class="language-plaintext highlighter-rouge">make install</code>,而是打成 RPM 包。这样自己的编译和运行环境更干净,便于后期维护,也也能在 copr 上和人分享。忙了两天,总算编译成功了,总结一下自己踩过的几个小坑。</p>
<h4 id="准备-spec">准备 spec</h4>
<p>spec 文件的规范我就不赘述了,我找到的最详细的文档是 <a href="http://ftp.rpm.org/max-rpm/s1-rpm-build-creating-spec-file.html">Maximum RPM: Taking the RPM Package Manager to the Limit. Chapter 11. Building Packages: A Simple Example</a></p>
<p>在实际操作上,我主要参考了 <a href="https://developer.fedoraproject.org/deployment/rpm/about.html">RPM Packaging</a> 一文。照着这个例子,运行如下命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo dnf install fedora-packager rpmdevtools gcc
$ rpmdev-setuptree
$ cd ~/rpmbuild/SOURCES
$ wget -O v0.31.6.tar.gz https://github.com/ahrm/sioyek/archive/refs/tags/v0.31.6.tar.gz
$ cd ~/rpmbuild/SPECS
$ rpmdev-newspec --macros sioyek.spec
</code></pre></div></div>
<p>并修改 spec 文件即可。</p>
<p>更详细的流程,可以参考 <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html-single/packaging_and_distributing_software/index">Packaging and distributing software</a></p>
<h4 id="使用-mock-编译-srpm">使用 mock 编译 SRPM</h4>
<p><a href="https://github.com/rpm-software-management/mock/wiki">Mock wiki</a> 介绍的很详细。我配置好 mock 以后,直接运行</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rpmbuild -bs ~/rpmbuild/SPECS/sioyek.spec && mock --enable-plugin=tmpfs --enable-plugin=yum_cache ~/rpmbuild/SRPMS/sioyek-0.31.6-1.fc34.src.rpm
</code></pre></div></div>
<h4 id="使用-container-podmandocker-的查错技巧">使用 Container (Podman/Docker) 的查错技巧</h4>
<p>刚写好的 spec 编译错误很正常。为了使用 interactive shell,我决定在 podman 里进行测试。每次启动时,都要浪费大量的时间在 <code class="language-plaintext highlighter-rouge">dnf upgrade/install</code> 上。受到 <a href="https://sysadmin.prod.acquia-sites.com/sysadmin/overlay-mounts">https://sysadmin.prod.acquia-sites.com/sysadmin/overlay-mounts</a> 一文的启发,我用如下命令启动容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo dnf install --downloadonly harfbuzz-devel # 这里可以替换成其他可能用到的软件包
$ trash /dev/shm/src && mkdir /dev/shm/src && cp ~/rpmbuild/SOURCES/* /dev/shm/src && cd /dev/shm/src && tar xf v0.31.6.tar.gz
$ cp -r /var/cache/dnf /dev/shm/cache_dnf
$ podman run --rm -it -v /dev/shm/cache_dnf:/var/cache/dnf:z -v /dev/shm/src:/src:z docker.io/fedora:34 bash
</code></pre></div></div>
<h4 id="检查链接错误">检查链接错误</h4>
<p>我在编译时,出现了若干链接错误</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/11/../../../../lib64/libmupdf.a(pdf-font-add.o): undefined reference to symbol 'FT_Get_Postscript_Name'
/usr/lib64/libfreetype.so.6: error adding symbols: DSO missing from command line
</code></pre></div></div>
<p>虽然 BuildRequires 里有了必要的依赖,但还是要使用 <code class="language-plaintext highlighter-rouge">-lfreetype</code> 这一命令显式链接。其他库同理。</p>
<p>可以使用 <code class="language-plaintext highlighter-rouge">nm</code> 查看一个库的符号表 (symbols),如</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nm -D /usr/lib64/libfontconfig.so | grep FT_Get_Postscript_Name
nm -D /usr/lib64/libfreetype.so | grep FT_Get_Postscript_Name
</code></pre></div></div>
<h4 id="及时更新">及时更新</h4>
<p>copr 编译完成后,本地的缓存更新可能也许是要时间。为了避免重复下载所有的 metadata, 我会运行</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dnf --disablerepo='*' --enablerepo='copr*' --refresh upgrade sioyek
</code></pre></div></div>
<p>最后,再次感谢 <a href="https://zh.fedoracommunity.org/about/">FZUG 群</a> 的朋友们,无私地解答了我许多问题。</p>
自动向 Crates.io 发布新版本
2021-08-19T00:00:00+00:00
https://blog.zhenbo.pro/2021/08/19/rust-automatically-publish-cratesio-inspired-by-maven-release-plugin
<h4 id="缘起">缘起</h4>
<p>之前写 Java 时,自己所在的组遵循这样的 workflow:</p>
<ol>
<li>每当 master branch 有新 commit 时,会使用 <a href="http://maven.apache.org/maven-release/maven-release-plugin/">Maven Release Plugin</a> 修改 <code class="language-plaintext highlighter-rouge">pom.xml</code> 内的版本号</li>
<li>Bot 会将新版本的 Maven Package 上传到 JFrog 内。</li>
</ol>
<p>最近,我在尝试维护一个 <a href="https://github.com/Endle/rust-bundler-cp">Rust 包 rust_bundler_cp</a>,想着复刻上文的 Maven Workflow,每当有新 commit 时自动向 crates.io 发布新版本。摸索一段时间后成功在 Github Action 上实现了这个功能。</p>
<h4 id="实现">实现</h4>
<p>首先,需要一个修改 <code class="language-plaintext highlighter-rouge">Cargo.toml</code> 的工具。虽然自己写一个脚本识别版本号只需要几行,但我还是用了现有的软件包 <a href="https://github.com/wraithan/cargo-bump">cargo-bump</a>. 使用非常简单,不再赘述了。</p>
<p><a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows">GitHub Action on</a> 可以设置触发条件。但是,我没有找到如何设置在不同的触发条件下执行不同的任务。在<a href="https://github.com/Endle/rust-bundler-cp/blob/176f9f22cdbdcaa874c0ee0943dfe5ac810fa868/.github/workflows/rust.yml#L5">原有的代码中</a>,Pull Request 和新 commit 都会触发相同的任务。我不打算对原有的 workflow 做过多的修改,因此,我写了一个每次都会被执行的 Python 脚本,用它进行必要的操作。</p>
<p>首先,我在 <a href="https://github.com/Endle/rust-bundler-cp/blob/176f9f22cdbdcaa874c0ee0943dfe5ac810fa868/bump_version.py#L31">脚本</a> 内判断当前是否是在 master branch 执行 CICD 任务。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> branch_name = shell_call("git branch --show-current")
if branch_name not in ['master']:
print("Current branch ({}) is not for release. Exiting".format(branch_name))
return
</code></pre></div></div>
<p>可以使用如下命令创建新的 commit</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def bump_version():
def extract_ver(s)->str:
with_quotes = s.split("=")[1].strip()
wo_q = with_quotes.replace('"', '')
return wo_q
shell_call("cargo bump patch")
diff = shell_call("git diff Cargo.toml| grep version | egrep ^[+-]").split("\n")
versions = [extract_ver(v) for v in diff]
return versions[0], versions[1]
# Snip
(old_ver, new_ver) = bump_version()
version_change_info = " From {} To {}".format(old_ver, new_ver)
new_commit_message = MESSAGE_FLAG + version_change_info
git_commit_cmd = "git add Cargo.toml && git commit -m '{}'".format(new_commit_message)
subprocess.run(git_commit_cmd, shell=True)
</code></pre></div></div>
<p>在执行 <code class="language-plaintext highlighter-rouge">git commit</code> 前,需要先设置作者的姓名和 email. 我将<a href="https://github.com/Endle/rust-bundler-cp/blob/176f9f22cdbdcaa874c0ee0943dfe5ac810fa868/.github/workflows/rust.yml#L100">这一步放到了 <code class="language-plaintext highlighter-rouge">rust.yml</code> 中</a>,在 Python 脚本前运行。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git config --global user.name 'Endle'
git config --global user.email 'Endle@users.noreply.github.com'
git branch --show-current
python3 --version
python3 bump_version.py
</code></pre></div></div>
<p>在 crates.io 上注册帐号后,需要创建自己的 Token. 接着,在 Github Project->Settings->Secrets 里存储该token, 如图所示:</p>
<p><img src="/images/github/github_action_rust_crates_io.png" alt="Github Screenshot" width="100%" /></p>
<p>这样,在 <code class="language-plaintext highlighter-rouge">rust.yml</code> 中,就可以使用如下命令上传到 crates.io:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> - name: Push to Github and crates
env:
CRATES_SECRET: $
run: |
git push origin master
cargo login "$CRATES_SECRET"
cargo publish -v
</code></pre></div></div>
<h4 id="后记">后记</h4>
<p>使用 <a href="https://github.com/marketplace/actions/checkout">Github actions/checkout@v2</a>,向原有的 repo 执行 <code class="language-plaintext highlighter-rouge">git push</code> 不需要手动设置 token。可以参考 <a href="https://stackoverflow.com/a/58393457/1166518">https://stackoverflow.com/a/58393457/1166518</a></p>
<p>我在我的 Python 脚本中加入了对上一个 commit message 的判断,从而避免 Workflow 被重复触发。但实践中发现,bot 创建的 commit 并没有触发 GitHub Actions. 我还不太清楚这是什么机制导致的。我在撰写本文是意识到,我应该在 commit message 中加入 <code class="language-plaintext highlighter-rouge">[skip ci]</code> 作为标识。</p>
<p>如果设置了 crates.io 的镜像,如</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = "mirror"
[source.mirror]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index/"
</code></pre></div></div>
<p>在运行 <code class="language-plaintext highlighter-rouge">cargo publish</code> 前,需要将 <code class="language-plaintext highlighter-rouge">replace-with = "mirror"</code> 暂时注释掉。在网络条件不好的环境下,使用 Github Action 发布,可以省下不少时间。</p>
线段树作者是谁
2021-08-07T00:00:00+00:00
https://blog.zhenbo.pro/2021/08/07/who-invented-segment-tree
<p>省流助手:<a href="https://en.wikipedia.org/wiki/Jon_Bentley_(computer_scientist)">Jon Louis Bentley</a>, 于 1977 年发明.</p>
<p>线段树是 OI 中常用的基础算法。出于好奇,我简单考证了线段树的诞生过程。个人能力所限,疏漏在所难免,恳请朋友们不吝赐教。</p>
<p>在 1977 年,<a href="https://en.wikipedia.org/wiki/Victor_Klee">Victor L. Klee, Jr</a> 发表了 <a href="http://www.jstor.org/stable/2318871">Can the Measure of U Ai, Bi Be Computed in Less Than O(n Log N) Steps?</a>. 在同年,卡内基梅隆大学的 <a href="https://en.wikipedia.org/wiki/Jon_Bentley_(computer_scientist)">Bentley</a> 撰写了 Algorithms for Klee’s rectangle problems. 这篇文章并没有发表,我也没能在 2021 年的互联网上找到这篇文章的副本。不过,这份 Unpublished notes 被同期的多篇文章引用,如 Bentley 本人在 1980 年发表的 <a href="https://ieeexplore.ieee.org/document/1675628">An Optimal Worst Case Algorithm for Reporting Intersections of Rectangles</a>.</p>
<p><a href="https://doi.org/10.1007/978-3-540-77974-2">Computational Geometry</a> 在 Chapter 10 收录了 Interval Tree 和 Segment Tree. <a href="https://en.wikipedia.org/wiki/Segment_tree">维基百科</a> 引用了 <a href="https://doi.org/10.1007/978-3-540-77974-2">Computational Geometry</a>. 部分近期发表的论文,如 <a href="https://www.sciencedirect.com/science/article/pii/S2215016119300391">Wang, Lei, and Xiaodong Wang. “A Simple and Space Efficient Segment Tree Implementation.”</a> 也引用了<a href="https://doi.org/10.1007/978-3-540-77974-2">此书</a>。</p>
<h4 id="参考资料">参考资料</h4>
<ul>
<li>Klee, Victor. “Can the Measure of U[Ai, Bi] Be Computed in Less Than O(n Log N) Steps?” <i>The American Mathematical Monthly</i> 84, no. 4 (1977): 284-85. Accessed August 7, 2021. doi:10.2307/2318871.</li>
<li>Wang, Lei, and Xiaodong Wang. “A Simple and Space Efficient Segment Tree Implementation.” MethodsX 6 (January 1, 2019): 500–512. https://doi.org/10.1016/j.mex.2019.02.028.</li>
<li>Bentley, Jon Louis, and Derick Wood. 1980. “An Optimal Worst Case Algorithm for Reporting Intersections of Rectangles.” IEEE Transactions on Computers C-29 (7): 571–77. https://doi.org/10.1109/TC.1980.1675628.</li>
<li>de Berg, Mark, Cheong, Otfried, van Kreveld, Marc, and Overmars, Mark. Computational Geometry. Third Edition. Berlin, Heidelberg: Springer Berlin / Heidelberg, 2008. https://doi.org/10.1007/978-3-540-77974-2.</li>
<li>Wu, Y., & Wang, J. (2018). Algorithm Design Practice for Collegiate Programming Contests and Education (1st ed.). CRC Press. https://doi-org.proxy.lib.uwaterloo.ca/10.1201/9780429401855</li>
<li><a href="http://poj.org/problem?id=2828">POJ 2828 Buy Tickets - Monthly, 2006.05.28, Zhu Zeyuan</a></li>
</ul>
使用 git shallow clone 下载并编译 Thunderbird
2021-08-03T00:00:00+00:00
https://blog.zhenbo.pro/2021/08/03/git-shallow-clone-build-thunderbird
<p>最近在尝试编译 Thunderbird. <a href="https://developer.thunderbird.net/thunderbird-development/getting-started">官方的手册</a> 的建议是</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hg clone https://hg.mozilla.org/mozilla-central source/
cd source/
hg clone https://hg.mozilla.org/comm-central comm/
</code></pre></div></div>
<p>因为我网络情况不好,硬盘空间也有些捉襟见肘,就只想下载最新的版本。可是,<a href="https://stackoverflow.com/a/4205246/1166518">Mercurial HG 并不支持</a>.</p>
<p>Mozilla 已经在 GitHub 上有了实验性的 Mirror. 因此,我使用如下的方式下载 Thunderbird 的代码。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># My personal habit
cd ~/src/mozilla
git clone --depth=1 https://github.com/mozilla/gecko-dev.git mozilla-central
git clone --depth=1 https://github.com/mozilla/releases-comm-central comm-central
cp -R --reflink=auto comm-central/ mozilla-central/comm
</code></pre></div></div>
<p>我会使用如下代码进行更新。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd mozilla-central && git pull origin master && trash comm && cd ..
cd comm-central && git pull origin master && cd ..
cp -R --reflink=auto comm-central/ mozilla-central/comm
cd mozilla-central
</code></pre></div></div>
Codeforces #723 1526E Oolimry and Suffix Array 题解
2021-07-15T00:00:00+00:00
https://blog.zhenbo.pro/2021/07/15/solution-codeforces-723-1526e-oolimry-and-suffix-array
<h4 id="题目描述">题目描述</h4>
<p>给定一个<a href="https://en.wikipedia.org/wiki/Suffix_array">后缀数组 Suffix Array</a> 和词典大小(alphabet size),求总共有多少种不同的可能性。<a href="https://codeforces.com/contest/1526/problem/E">题目链接 1526 E</a></p>
<p>这道题的描述很简单,但思维量很大。<a href="https://codeforces.com/blog/entry/91195">官方 Solution</a> 又写的非常简洁。忙了一个星期,我总算完成了这道题。</p>
<p>这道题可以拆分成两问</p>
<h4 id="1-给定后缀数组使用最小的字典生成合法的字符串">1. 给定后缀数组,使用最小的字典生成合法的字符串</h4>
<p>对于非空的字符串 S 和对应的后缀数组 SA,我们有如下性质</p>
<ol>
<li>\( 0 \leq SA_i \leq n-1 \)</li>
<li>SA is a permutation of <code class="language-plaintext highlighter-rouge">[0, n-1]</code></li>
<li>SA 对应的后缀字符串严格递增(lexical order)</li>
<li>对于本问,上界为 n, 下界为 1</li>
</ol>
<p>根据后缀数组的定义,我们有</p>
\[i < j \Leftrightarrow S[SA_i,n-1] < S[SA_j,n-1]\]
<p>我们将 \(S[SA_i,n-1]\) 拆成 \(xY\),\(x\) 表示一个合法的字符,\(Y\) 则表示一个可能为空的字符串。同理,将 \(S[SA_j,n-1]\) 拆分成 \(aB\),如下图</p>
<p><img src="/images/codeforces/723e.jpg" alt="xyab pic" /></p>
<p>注意,在这里我们不清楚 \(Y B\) 的长度。因为 \(xY \neq aB\),可知\(Y \neq B\)</p>
<p>显然,在这里 \(x \leq a \). 想要使用尽可能小的字典, 我们希望尽可能地使用相同的字符。那么,我们能得到</p>
<ul>
<li>\(Y < B\ \Rightarrow x = a\)</li>
<li>\(Y > B\ \Rightarrow x < a\)</li>
</ul>
<p>当出现后者的情况时,我们就需要将一个新的字符插入字典,从而满足后缀数组 SA 的要求。因为 SA 是严格升序的, \(\forall i \in [0,n-2], j=i+1 \Rightarrow S[SA_i,n-1] < S[SA_j,n-1] \) 即可保证 SA 合法。</p>
<p>现在,我们需要高效地比较 \(Y B\)。因为后缀数组 SA 已知,这个问题非常简单。我们可以定义函数 <code class="language-plaintext highlighter-rouge">Rank(U)</code>,表明对于字符串 <code class="language-plaintext highlighter-rouge">S</code> 的后缀 <code class="language-plaintext highlighter-rouge">U</code>在所有后缀中的排名。为了方便,我们定义空串 <code class="language-plaintext highlighter-rouge">Rank($)=-1</code>.</p>
<p><a href="https://codeforces.com/blog/entry/91195">官方 Solution</a> 使用了 <code class="language-plaintext highlighter-rouge">pos</code> 这一宽泛的名称,不易理解。<a href="https://blog.csdn.net/qq_42101694/article/details/117388502">OneInDark 的博文</a> 准确地称之为 <code class="language-plaintext highlighter-rouge">rnk</code>,我也沿用了这一名词。</p>
<p>根据后缀数组的定义,我们可以使用下标 \(u\) 表示字符串 \(U = S[u, n-1]\). 那么,<code class="language-plaintext highlighter-rouge">Rank(n)=-1</code>. 这一问的代码实现如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fn calculate_strict_increasing_pairs(sa: &Vec<i32>) -> usize {
let mut rank = vec![0; sa.len()+1];
for i in 0..sa.len() {
rank[ sa[i] as usize ] = i as i32;
}
// rank[n] = -1
// means an empty string $ is lexicographically smaller than all non-empty strings
rank[sa.len()] = -1;
let mut cnt = 0;
for i in 0..(sa.len()-1) {
let j = i + 1;
// xY = [SAi, n-1]
// aB = [SAj, n-1]
// xY < aB
// if Y < B, x may be equal to a
let y:usize = sa[i] as usize + 1;
let b:usize = sa[j] as usize + 1;
if rank[y] < rank[b] {
()
} else {
cnt += 1;
}
}
cnt
}
</code></pre></div></div>
<p>这里的 <code class="language-plaintext highlighter-rouge">cnt</code> 指,为了满足要求我们需要多少次换用新的字符。也就是说,我们需要一个大小为 <code class="language-plaintext highlighter-rouge">cnt+1</code> 的字典。当且仅当 \(k<=cnt\) 时,不存在任何一个合法的字符串 <code class="language-plaintext highlighter-rouge">S</code>满足<code class="language-plaintext highlighter-rouge">SA</code>。</p>
<h4 id="2-隔板法stars-and-bars求可行的方案数">2. <a href="https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)">隔板法(Stars and bars)</a>求可行的方案数</h4>
<p>定义非负整数数列 \(X\), 令 \(x_i=0\) 表示字符串 \(S\) 中 \(S_i = S_{i-1} \). 如果 \(x_i \geq 1\),那么 \(S_i\) 相比于 \(S_{i-1} \) 要向前推 \(x_i\) 个字符。这样我们将题目转化成了,数列 \(X\) 有多少种合法的可能。</p>
<p>定义一个由整数 \(0\) \(1\) 组成的数列 \(Y\). 如果第一问中 \(S_i > S_{i-1} \), 那么 \(y_i = 1\). 否则,\(y_i=0\). 数列 \(Y\) 仅由第一问中的 \(SA\) 确定。数列 \(X\) 合法的充要条件是 \(\forall i: x_i \geq y_i\)</p>
<p>定义非负整数数列 \(Z = X - Y\). 求数列 \(X\) 有多少种合法的可能性,等价于求数列 \(Z\) 有多少种合法的可能。</p>
<p>令 \(extra = k-1-cnt\), 表示我们有多少个额外的字母。 如果 \(cnt=k-1\),那唯一合法的数列 \(Z\) 为 \(\forall i: z_i = 0\),我们没有任何一个字母可以浪费。可以枚举 \(w \in [0, extra] \), 令 \(w = \sum Z\), 可以使用 <a href="https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)">隔板法(Stars and bars) Theorem Two</a> 求解。</p>
<p>但是,枚举 \(w\) 效率过低。我们可以在 \(Z\) 的末尾添加一个元素,得到数列 \(Z^{\star}\). 这个额外元素的值表示 \(extra - w\). 那么,\( \sum Z^{\star} = extra\). 以 \(k = 26, n = 10\) 举例,如果我们希望字符串 \(S\) 的第一个元素是 <code class="language-plaintext highlighter-rouge">c</code>,我们就令 \(z_0^{\star} = 2\). 如果字符串 \(S\) 的第末尾元素是 <code class="language-plaintext highlighter-rouge">y</code>,我们就令 \(z^{\star}_{last} = 1\)</p>
<p>现在,我们就将前文的 枚举 \(w \in [0, extra] \) 转化为了使用 <a href="https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)">隔板法(Stars and bars) Theorem Two</a> 求数列 \(Z^{\star}\) 的可能数。数列长度为 \(n+1\),数列和为 \( extra = k-1-cnt\). 答案要取余,这要用到 <a href="https://en.wikipedia.org/wiki/Modular_multiplicative_inverse">Modular multiplicative inverse</a> 的知识。可以参考 <a href="https://www.geeksforgeeks.org/multiplicative-inverse-under-modulo-m/">Geeksforgeeks</a> 的实现。</p>
<h4 id="后记">后记</h4>
<p><a href="https://codeforces.com/blog/entry/91195">官方 Solution</a> 写的过于简洁。<a href="https://blog.csdn.net/qq_42101694/article/details/117388502">OneInDark 的博文</a> 详细了不少,为我提供了很大的帮助。<br />
写题解时发现了 \( \LaTeX \) <a href="https://oeis.org/wiki/List_of_LaTeX_mathematical_symbols">速查表</a>,记录在此以备将来使用。<br />
意外读到了 <a href="https://yihui.org/cn/vitae/">谢益辉</a> 的<a href="https://yihui.org/cn/2017/04/mathjax-markdown/">博文: MathJax 与 Markdown 的究极融合</a>,感觉当前自己的 Jekyll + MathJax 的组合的确不是很方便。未来可能会寻找一个更好的写作方法。<br />
这道题目有两个主要的思维点,每一个都让我感觉很吃力。从前到后忙了得有一周,总算有了个比较圆满的结果。<br />
最近我在<a href="https://codeforces.com/blog/entry/92796">尝试使用 Rust 打 Codeforces</a>,欢迎大家提出建议。</p>
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script type="text/javascript" id="MathJax-script" defer="" src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js">
</script>
<!--
令 \\(x_i=0\\)表示字符串 `S` 中 \\(S_i = S_{i-1} \\). 如果 \\(x_i = 1\\),那么 \\(S_i > S_{i-1} \\) . 数列 \\(X\\) 为确定量。
定义非负整数数列 `Z`, \\(\forall i : z_i \geq \x_i\\). \\(S_i\\) 相比于 \\(S_{i-1} \\) 要向前推 \\(Z_i\\) 个字符。根据题意,我们也要保证 \\( \sum Z_i \leq (k-1) \\). 我们要求出在限制条件下,数列 `Z` 有多少种可能性。\\(Z = X + Y\\).
-->
初探 Z function 处理字符串
2021-06-20T00:00:00+00:00
https://blog.zhenbo.pro/2021/06/20/string-z-function-intro
<p>之前参加了 <a href="https://codeforces.com/blog/entry/91381">Codeforces Round #726</a>,E2 这道题方法很多,推荐的方法是 Z Function. 我也借机学习一个新算法。</p>
<h4 id="函数定义">函数定义</h4>
<p>本文中,所有的数组下标均为 0 开始。如无说明,所有的区间均为闭区间。对于字符串 S, <code class="language-plaintext highlighter-rouge">S[a, b]</code> 表示选取一个长度为 <code class="language-plaintext highlighter-rouge">b - a + 1</code>,范围是由 a 到 b 的子串。</p>
<p>函数 Z 的定义是,给定一个字符串 S. 对于每个下标 i,寻找一个最长的子串使 <code class="language-plaintext highlighter-rouge">S[i,i+Zi - 1] = S[0, Zi - 1]</code>. <code class="language-plaintext highlighter-rouge">Z[0]</code> 未被定义。如果 <code class="language-plaintext highlighter-rouge">S[i] != S[0]</code>,我们有 <code class="language-plaintext highlighter-rouge">Z[i] = 0</code></p>
<p>直观一些的理解是,一个序列的前缀 (prefix) 在可能在这个序列中重复出现。例如,<a href="https://en.wikipedia.org/wiki/Open_reading_frame">Open reading frame</a> 中,特定的密码子会在碱基序列的起始和前端重复出现。Z-function 反应了重复出现的情况。</p>
<p>我手绘了一张示意图,相同颜色表示重复的元素。向下延伸的部分,表示 <code class="language-plaintext highlighter-rouge">S[i, i+Zi - 1] = S[0, Zi - 1]</code>,也就是与前缀重合的部分。</p>
<p><img src="/images/algorithm/strings/z_function_drawing.png" alt="z-function-drawing" width="100%" /></p>
<h4 id="线性时间求解">线性时间求解</h4>
<p>根据定义,可以容易地写出一个 Brute-force 的算法,求出序列 S 对应的 Z. 但当序列 S 的前缀在序列中多次重复出现时,耗时会快速增加。极端情况下,<code class="language-plaintext highlighter-rouge">aaaaa</code> 这类序列,会达到 Brute-force 算法的最坏时间复杂度,<code class="language-plaintext highlighter-rouge">O(n^2)</code>.</p>
<p>通常提到 Z Function 的时候,都是要在线性时间内求出结果。我们可以使用 Sliding Window Technique,维护一个与 S 的前缀相同的 window. 其范围是 <code class="language-plaintext highlighter-rouge">[L, R]</code> 。也就是说,这个 window:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">L > 0</code></li>
<li><code class="language-plaintext highlighter-rouge">S[0, R - L] = S[L, R]</code></li>
</ol>
<p>L 与 R 均保持递增,易证算法的时间复杂度为 <code class="language-plaintext highlighter-rouge">O(n)</code></p>
<p>求 <code class="language-plaintext highlighter-rouge">Z[i]</code> 时,</p>
<ol>
<li>若 <code class="language-plaintext highlighter-rouge">i > R</code>,则这个 window 没有为我们提供已知的信息。令 <code class="language-plaintext highlighter-rouge">L = R = i</code>,再向右尽可能地延伸 R</li>
<li>若 <code class="language-plaintext highlighter-rouge">i <= R</code>,我们将 <code class="language-plaintext highlighter-rouge">S[L, R]</code> 拆分成 <code class="language-plaintext highlighter-rouge">S[L, i]</code> 和 <code class="language-plaintext highlighter-rouge">S[i, R]</code> (有意重叠<code class="language-plaintext highlighter-rouge">S[i]</code>)
<ul>
<li>令 <code class="language-plaintext highlighter-rouge">k = i - L</code>. 因为 <code class="language-plaintext highlighter-rouge">S[0, R-L] = S[L, R]</code>, 我们可证 <code class="language-plaintext highlighter-rouge">S[0, k] = S[L, i]</code>。</li>
<li>同理,<code class="language-plaintext highlighter-rouge">S[k, R - L] = S[i, R]</code>. 这样,我们就用上了 window 内的信息。</li>
<li>如果 <code class="language-plaintext highlighter-rouge">Z[k] < R - i + 1</code> ,就说明在子串 <code class="language-plaintext highlighter-rouge">S[k, R - L]</code> 中存在 <code class="language-plaintext highlighter-rouge">p >= Z[k]</code>, 使 <code class="language-plaintext highlighter-rouge">S[k+p] != S[0+p]</code>。 这也就是说, <code class="language-plaintext highlighter-rouge">S[i+p] != S[p]</code>。同样,对于任意 <code class="language-plaintext highlighter-rouge">q < Z[k]</code>,我们有 <code class="language-plaintext highlighter-rouge">S[i+q] = S[k+q] = S[q]</code>。因此,<code class="language-plaintext highlighter-rouge">Z[i] = Z[k]</code></li>
<li>如果 <code class="language-plaintext highlighter-rouge">Z[k] >= R - i + 1</code>,我们在保持 L 不变的基础上,尽可能延伸 R</li>
</ul>
</li>
</ol>
<p>使用 C++ 实现如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void extend_window(const char *str, int str_len, int left, int &right) {
// S[0:right-left] == S[left:right] closed interval
// if S[0] != S[right], resulting in left+1==right, indicating an empty range
while (right < str_len && str[right - left] == str[right]) {
++right;
}
--right;
}
void z_function(const char *str, int str_len, int Z[]) {
Z[0] = 0;
int left = -1;
int right = -1;
for (int i=1; i < str_len; ++i) {
if (i > right) {
left = right = i;
extend_window(str, str_len, left, right);
Z[i] = right - left + 1;
} else {
int k = i - left;
// We know S[0:right-left] == S[left:right] =>
// 1. S[0:k] == S[left:i]
// 2. S[k, right-left] == S[i:right]
if (Z[k] < right - i + 1) {
// exist p>=0, S[k+p] != S[p] => S[i+p] != S[p]
Z[i] = Z[k];
} else {
left = i;
extend_window(str, str_len, left, right);
Z[i] = right - left + 1;
}
}
}
}
</code></pre></div></div>
<p><a href="https://catalog.utdallas.edu/2019/graduate/courses/cs6333">UT Dallas CS 6333</a> 提供了一个直观的<a href="https://personal.utdallas.edu/~besp/demo/John2010/z-algorithm.htm">网页演示</a>。</p>
<h4 id="后记">后记</h4>
<p>介绍 Z Function 的英文文章不少,但我个人感觉 <a href="https://www.hackerearth.com/practice/algorithms/string-algorithm/z-algorithm/tutorial/">HackerEarth</a> 的描述最为清晰。</p>
<p>Z Function 的英文定义可以参考 <a href="https://contest.cs.cmu.edu/295/tutorials/z-string-matching.pdf">CMU 295 z-string-matching Page 7</a></p>
<p><a href="https://cp-algorithms.com/string/z-function.html">e-maxx</a> 提供了若干道例题,如 <a href="https://codeforces.com/problemset/problem/126/B">Codeforces - Password</a>. <a href="https://discuss.codechef.com/t/z-algorithm-tutorial/64274">Codechef 的教程</a>也举出了两道例题。</p>
<p><a href="https://github.com/resilar">resilar</a> 提供了<a href="https://gist.github.com/resilar/e65745cf7a80ef364df034e96cfcc86d#file-z-c-L82">更为精简的实现</a>。</p>
<p><a href="https://personal.utdallas.edu/~besp/demo/John2010/z-algorithm.htm">UT Dallas 的课程页面</a> 和 <a href="https://github.com/raywenderlich/swift-algorithm-club/blob/2fdd8b8be1b3fcd17ad0394053e672f2bd1d3076/Z-Algorithm/README.markdown">Matteo Dunnhofer 的文章</a> 都提到了 <a href="https://www.worldcat.org/title/algorithms-on-strings-trees-and-sequences-computer-science-and-computational-biology/oclc/910017234">Gusfield, Dan. Algorithms on Strings, Trees, and Sequences: Computer Science and Computational Biology</a>. 但我手头并没有这本书,并没有验证 Gusfield 的书上是否提到了这一算法。</p>
<p>非常感谢 ITX351 对本文提供的宝贵意见。</p>
Tail Sum Formula 的学习笔记
2021-06-14T00:00:00+00:00
https://blog.zhenbo.pro/2021/06/14/tail-sum-formula-notes
<p>最近看书时,接触到了 Tail-Sum Formula. 公式的定义如下</p>
\[E(X) = \sum_{x=1}^{\infty} P(X \geq x)\]
<p>它也有一个等价形式</p>
\[E(X) = \sum_{x=0}^{\infty} P(X \gt x)\]
<h4 id="公式证明">公式证明</h4>
<p>在 <a href="https://inst.eecs.berkeley.edu/~cs70/su16/static/su16/extra_note/sinho_cs_70_notes.pdf">Sinho Chewi 的笔记</a> 上,可以看到这一公式的证明。</p>
<p><img src="/images/statistics/tailsum_formula/tailsum_formula_proof.png" alt="proof" /></p>
<p>红框内的这步变换比较跳跃,我阅读时,在这里卡顿了很久,发现这里其实很简单。<br />
对于原式</p>
\[E(X) = \sum_{x=1}^{\infty} \sum_{k=1}^{k=x} P(X=x)\]
<p>我们将每一行的结果 <code class="language-plaintext highlighter-rouge">Row(X=x)</code> 拆出来</p>
\[Row(X=x) = \sum_{k=1}^{k=x} P(X=x)\]
\[E(X) = \sum_{x=1}^{\infty} Row(X=x)\]
<p>示意图如下<br />
<img src="/images/statistics/tailsum_formula/schematic_diagram.png" alt="示意图" /></p>
<p>我们如果竖向看这个示意图(红圈),那就可以得到另外一种形式</p>
\[E(X) = \sum_{k=1}^{\infty} Column(K=k)\]
\[Column(K=k) = \sum_{x=k}^{\infty} P(X=x)\]
<p>这也就是 Sinho Chewi 所提的</p>
\[E(X) = \sum_{k=1}^{\infty} \sum_{x=k}^{\infty} P(X=x)\]
<h4 id="公式应用">公式应用</h4>
<p>在 <a href="https://www.worldcat.org/title/probability-for-statistics-and-machine-learning-fundamentals-and-advanced-topics/oclc/706920643&referer=brief_results">Probability for Statistics and Machine Learning</a> 一书中,对此定理有一个有趣的例题:</p>
<p>一对夫妇准备生若干个孩子,直到子女中既有男孩也有女孩。令生男孩的概率为 <code class="language-plaintext highlighter-rouge">p</code>,求期望的子女数。</p>
<p>有了 Tail Sum Formula,我们只需求解 \(P(X > n)\),也就是 <code class="language-plaintext highlighter-rouge">前 n 个孩子的性别相同(男或女)</code>。那么,可以得到</p>
\[P(X > n) = p^n + (1-p)^n \quad \textrm{if} \quad n \geq 2\]
<p>注意,\(P(X = 0) = P(X = 1) = 1\)</p>
<p>现在套用公式,</p>
\[E(X) = \sum_{x=0}^{\infty} P(X \gt x)\]
\[E(X) = P(X = 0) + P(X = 1) + \sum_{x=2}^{\infty} P(X \gt x)\]
\[E(X) = 2 + \sum_{x=2}^{\infty} [p^n + (1-p)^n]\]
<p>等比数列求和时,我们有</p>
\[\sum_{n=2}^{\infty} a^n = (\sum_{x=1}^{\infty} a^n) - a\]
\[\sum_{n=1}^{\infty} a^n = \frac{a}{1-a} \quad (-1 \lt a \lt 1)\]
\[\sum_{n=2}^{\infty} a^n = \frac{a^2}{1-a} \quad (-1 \lt a \lt 1)\]
<p>带入原式,</p>
\[E(X) = 2 + \frac{p^2}{1-p} + \frac{(1-p)^2}{1-(1-p)}\]
\[E(X) = 2 + \frac{p^3 + (1-p)^3}{p(1-p)}\]
<p>根据 <a href="https://math.stackexchange.com/a/1861172/729703">binomial expansion</a></p>
\[p^3 + (1-p)^3 = p^3 + 1 - 3p + 3p^2-p^3 = 3p^2-3p+1\]
\[E(X) = \frac{2p-2p^2}{p(1-p)} + \frac{3p^2-3p+1}{p(1-p)}\]
\[E(X) = \frac{p(p-1)+1}{p(1-p)}\]
<p>最终,得到结论</p>
\[E(X) = \frac{1}{p(1-p)} - 1\]
<h4 id="后记">后记</h4>
<p>一个非常简单的公式,在书上只用了一页不到,但我却忙了一下午才搞清楚。既然如此,不如更进一步,写一篇笔记出来,也借机迫使自己把每一步的推导弄清楚。</p>
<p>感谢老同学 <a href="https://www.linkedin.com/in/%E7%9D%BF%E5%85%8B-%E6%AF%95-61407698/">毕睿克</a> 为本文草稿提出的意见。</p>
<p><a href="/images/statistics/tailsum_formula/schematic_diagram.png">示意图</a> 我是用 <a href="/resources/statistics/tailsum_formula/image_base.ods">LibreOffice Calc</a> 制作的,步骤繁琐且效率差。如果有人了解如何绘制类似图形,恳请您不吝赐教。</p>
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script type="text/javascript" id="MathJax-script" defer="" src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js">
</script>
Fcitx 5 在分辨率改变后候选框过大
2021-05-30T00:00:00+00:00
https://blog.zhenbo.pro/2021/05/30/fcitx-5-preview-too-large-after-resolution-change
<p>我当前使用的是 Fedore34+Gnome 40+Fcitx 5.0.8。今天早上为了用 Wine 玩星际争霸,把屏幕分辨率设置到 800x600,并将 Display Mode 设置为 Mirror。游戏结束后,我将分辨率改回了 1920x1080,但 Fcitx 的候选框就变得特别大,如该视频所示</p>
<video width="400" controls="">
<source src="/resources/fcitx/font_size_error.mp4" type="video/mp4" />
</video>
<p>尝试了重启(Fcitx, 系统)和恢复初始设置,都没有效果。把可能的选项试了一圈后,发现修复该问题的方法是关掉 <code class="language-plaintext highlighter-rouge">Use Per Screen DPI</code>,该选项在 <code class="language-plaintext highlighter-rouge">Addon->Classic User Interface</code> 下,如图 <br />
<img src="/resources/fcitx/dpi_option.png" alt="Use Per Screen DPI" /></p>
<p>关掉该选项后,我的界面恢复了正常。</p>
<p>后记:ffmpeg 技巧两则<br />
<a href="https://superuser.com/q/268985/295652">去除视频文件音轨</a><br />
<a href="https://askubuntu.com/a/396906/134171">mkv2mp4</a></p>
从 Python Bytecode 的角度看 List Comprehension 生成 Tuple
2021-03-28T00:00:00+00:00
https://blog.zhenbo.pro/2021/03/28/python-bytecode-list-comprehension-list-vs-tuple
<p><a href="https://www.w3schools.com/python/python_lists_comprehension.asp">Python List Comprehension</a> 使用非常广泛。通常认为,在生成的 <a href="https://docs.python.org/3/glossary.html#term-sequence">sequence</a> 定长的情况下,应该生成 Tuple 而非 List。出于好奇,我简单研究一下这两者在 Bytecode 的区别。</p>
<p>示例代码如下,很简单的 List Comprehension。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def transform(x:int)->int:
return (x * 2) + 5
original_data = [3, 7, 6, 5, 4, 4, 8]
result_1 = [transform(x) for x in original_data]
result_1b = list(transform(x) for x in original_data)
result_2 = tuple(transform(x) for x in original_data)
</code></pre></div></div>
<p>首先,看一下最常见的写法 <code class="language-plaintext highlighter-rouge">[transform(x) for x in original_data]</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import dis
dis.dis("result_1 = [transform(x) for x in original_data]")
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x7fe3345ea3a0, file "<dis>", line 1>)
2 LOAD_CONST 1 ('<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_NAME 0 (original_data)
8 GET_ITER
10 CALL_FUNCTION 1
12 STORE_NAME 1 (result_1)
14 LOAD_CONST 2 (None)
16 RETURN_VALUE
Disassembly of <code object <listcomp> at 0x7fe3345ea3a0, file "<dis>", line 1>:
1 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (x)
8 LOAD_GLOBAL 0 (transform)
10 LOAD_FAST 1 (x)
12 CALL_FUNCTION 1
14 LIST_APPEND 2
16 JUMP_ABSOLUTE 4
>> 18 RETURN_VALUE
</code></pre></div></div>
<p>这里的 <a href="https://github.com/python/cpython/blob/master/Lib/dis.py">dis module</a> 仅仅是 <a href="https://docs.python.org/3/library/functions.html#compile">built-in function compile</a> 的 wrapper。</p>
<p>如果稍稍写的臃肿一些,写成 <code class="language-plaintext highlighter-rouge">list(transform(x) for x in original_data)</code> 我们会发现字节码有小幅的变动。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dis.dis("result_1b = list(transform(x) for x in original_data)")
1 0 LOAD_NAME 0 (list)
2 LOAD_CONST 0 (<code object <genexpr> at 0x7fe3344dda80, file "<dis>", line 1>)
4 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
8 LOAD_NAME 1 (original_data)
10 GET_ITER
12 CALL_FUNCTION 1
14 CALL_FUNCTION 1
16 STORE_NAME 2 (result_1b)
18 LOAD_CONST 2 (None)
20 RETURN_VALUE
Disassembly of <code object <genexpr> at 0x7fe3344dda80, file "<dis>", line 1>:
1 0 LOAD_FAST 0 (.0)
>> 2 FOR_ITER 14 (to 18)
4 STORE_FAST 1 (x)
6 LOAD_GLOBAL 0 (transform)
8 LOAD_FAST 1 (x)
10 CALL_FUNCTION 1
12 YIELD_VALUE
14 POP_TOP
16 JUMP_ABSOLUTE 2
>> 18 LOAD_CONST 0 (None)
20 RETURN_VALUE
</code></pre></div></div>
<p>第一种方式 <code class="language-plaintext highlighter-rouge">[]</code> 调用了 <code class="language-plaintext highlighter-rouge">code object <listcomp></code>,第二种方式 <code class="language-plaintext highlighter-rouge">list()</code> 则是用了 <code class="language-plaintext highlighter-rouge">code object <genexpr></code>,并多了一次 <code class="language-plaintext highlighter-rouge">CALL_FUNCTION</code>。在 <a href="https://github.com/python/cpython/blob/a81fca6ec8e0f748f8eafa12fb12cf9e12df465c/Python/compile.c#L4733">Python/compile.c</a> 里,我们能看到这两者的实现几乎是一样的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>static int
compiler_genexp(struct compiler *c, expr_ty e)
{
static identifier name;
if (!name) {
name = PyUnicode_InternFromString("<genexpr>");
if (!name)
return 0;
}
assert(e->kind == GeneratorExp_kind);
return compiler_comprehension(c, e, COMP_GENEXP, name,
e->v.GeneratorExp.generators,
e->v.GeneratorExp.elt, NULL);
}
static int
compiler_listcomp(struct compiler *c, expr_ty e)
{
static identifier name;
if (!name) {
name = PyUnicode_InternFromString("<listcomp>");
if (!name)
return 0;
}
assert(e->kind == ListComp_kind);
return compiler_comprehension(c, e, COMP_LISTCOMP, name,
e->v.ListComp.generators,
e->v.ListComp.elt, NULL);
}
</code></pre></div></div>
<p>这两者都会交由 <a href="https://github.com/python/cpython/blob/a81fca6ec8e0f748f8eafa12fb12cf9e12df465c/Python/compile.c#L4635"><code class="language-plaintext highlighter-rouge">compiler_comprehension</code> 来处理</a>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>static int
compiler_comprehension(...){
--------------- snip ---------------
if (type != COMP_GENEXP) {
int op;
switch (type) {
case COMP_LISTCOMP:
op = BUILD_LIST;
break;
case COMP_SETCOMP:
op = BUILD_SET; break;
case COMP_DICTCOMP:
op = BUILD_MAP; break;
default:
PyErr_Format(PyExc_SystemError,
"unknown comprehension type %d", type);
goto error_in_scope;
}
ADDOP_I(c, op, 0);
}
if (!compiler_comprehension_generator(c, generators, 0, 0, elt,
val, type))
goto error_in_scope;
if (type != COMP_GENEXP) {
ADDOP(c, RETURN_VALUE);
}
--------------- snip ---------------
return 1;
error_in_scope: error:
--------------- snip: error handling ---------------
}
</code></pre></div></div>
<p>到这里,我可以判断 <code class="language-plaintext highlighter-rouge">code object <listcomp></code> 的返回值已经是 <code class="language-plaintext highlighter-rouge">list object</code>。而 <code class="language-plaintext highlighter-rouge">code object <genexpr></code> 的返回值需要额外的一组指令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1 0 LOAD_NAME 0 (list)
14 CALL_FUNCTION 1
</code></pre></div></div>
<p>到这里,我们再看一下尝试生成 tuple 的 Bytecode。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dis.dis("result_2 = tuple(transform(x) for x in original_data)")
1 0 LOAD_NAME 0 (tuple)
2 LOAD_CONST 0 (<code object <genexpr> at 0x7ff4af1ed450, file "<dis>", line 1>)
4 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
8 LOAD_NAME 1 (original_data)
10 GET_ITER
12 CALL_FUNCTION 1
14 CALL_FUNCTION 1
16 STORE_NAME 2 (result_2)
18 LOAD_CONST 2 (None)
20 RETURN_VALUE
Disassembly of <code object <genexpr> at 0x7ff4af1ed450, file "<dis>", line 1>:
1 0 LOAD_FAST 0 (.0)
>> 2 FOR_ITER 14 (to 18)
4 STORE_FAST 1 (x)
6 LOAD_GLOBAL 0 (transform)
8 LOAD_FAST 1 (x)
10 CALL_FUNCTION 1
12 YIELD_VALUE
14 POP_TOP
16 JUMP_ABSOLUTE 2
>> 18 LOAD_CONST 0 (None)
20 RETURN_VALUE
</code></pre></div></div>
<p>与上文的 <code class="language-plaintext highlighter-rouge">result_1b</code> 非常类似,<code class="language-plaintext highlighter-rouge">code object <genexpr></code> 的结果会被</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1 0 LOAD_NAME 0 (tuple)
14 CALL_FUNCTION 1
</code></pre></div></div>
<p>处理,并将结果绑定在变量名 <code class="language-plaintext highlighter-rouge">result_2</code> 上。</p>
<p>我们同样可以讲 <code class="language-plaintext highlighter-rouge">code object <genexpr></code> 的结果直接绑定在某个名称上,而非调用 <code class="language-plaintext highlighter-rouge">list()</code> 或 <code class="language-plaintext highlighter-rouge">tuple()</code> 。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>>>>result_3 = (transform(x) for x in original_data)
>>>type(result_3)
<class 'generator'>
>>>dis.dis("result_3 = (transform(x) for x in original_data)")
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x7ff4af0e0c90, file "<dis>", line 1>)
2 LOAD_CONST 1 ('<genexpr>')
4 MAKE_FUNCTION 0
6 LOAD_NAME 0 (original_data)
8 GET_ITER
10 CALL_FUNCTION 1
12 STORE_NAME 1 (result_3)
14 LOAD_CONST 2 (None)
16 RETURN_VALUE
Disassembly of <code object <genexpr> at 0x7ff4af0e0c90, file "<dis>", line 1>:
-----snip----
</code></pre></div></div>
<p>但是,这样创建出的 <code class="language-plaintext highlighter-rouge">generator</code> 会在遍历时被消耗掉(consume)。如果有访问这些元素的需求,还是要第一时间将其转换为 <a href="https://docs.python.org/3/glossary.html#term-sequence">sequence</a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>>>> result_3 = (transform(x) for x in original_data)
>>> sum(result_3)
109
>>> sum(result_3)
0
</code></pre></div></div>
<p>运行环境</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>>>> import sys
>>> sys.version
'3.9.2 (default, Feb 20 2021, 00:00:00) \n[GCC 10.2.1 20201125 (Red Hat 10.2.1-9)]'
</code></pre></div></div>
<p>推荐阅读:</p>
<ul>
<li><a href="https://opensource.com/article/18/4/introduction-python-bytecode">An introduction to Python bytecode</a></li>
<li><a href="https://realpython.com/cpython-source-code-guide/">Your Guide to the CPython Source Code</a></li>
<li><a href="https://leanpub.com/insidethepythonvirtualmachine">Inside the Python Virtual Machine</a></li>
</ul>
如何让 Git Submodule 恢复初始状态
2021-02-09T00:00:00+00:00
https://blog.zhenbo.pro/2021/02/09/git-submodule-reset-all
<p>这几天在尝试编译 <a href="https://github.com/ValveSoftware/Proton">Proton</a> ,不小心把 submodule 搞乱了。如下图所示,submodule 对应的是 <code class="language-plaintext highlighter-rouge">origin/master/HEAD</code>,而不是应有的 commit。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>proton$ git status
On branch proton_5.13
Your branch is up to date with 'origin/proton_5.13'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: FAudio (new commits)
modified: OpenXR-SDK (new commits)
modified: fonts/liberation-fonts (new commits)
modified: gst-orc (new commits)
modified: gst-plugins-base (new commits)
modified: gst-plugins-good (new commits)
modified: gstreamer (new commits)
modified: vkd3d-proton (new commits)
</code></pre></div></div>
<p><a href="https://stackoverflow.com/questions/7882603/how-to-revert-a-git-submodule-pointer-to-the-commit-stored-in-the-containing-rep">参考这个回答</a>,使用 <code class="language-plaintext highlighter-rouge">git submodule update --init</code> 修复。</p>
<p>因为网络问题,我的 openvr 最开始没能下载成功。反复尝试 fetch 以后,发现我的 <code class="language-plaintext highlighter-rouge">git module</code> 损坏了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git status
fatal: not a git repository: openvr/../.git/modules/openvr
$ git submodule
aa158544b6402e6a37517c0ffa142a5edae927b0 FAudio (20.12)
5197afbf199c026eca82a47a8573ed10b0c6fa4e OpenXR-SDK (release-1.0.13)
85c70ad5db0863ffbc4afee2ca57a6e6e92e8ef6 dxvk (experimental-dxvk-5.13-20210115)
9510ebd130bcb4dfc76b053b438d8a97a3ed4600 fonts/liberation-fonts (2.00.3-2-g9510ebd)
9901a96eaff271c2d3b595214213f6805ff803c8 gst-orc (0.4.31)
9d3581b2e6f12f0b7e790d1ebb63b90cf5b1ef4e gst-plugins-base (1.16.0-91-g9d3581b2e)
ce0723527aa37d5f4d19ef8021c0b2eb8f83b08d gst-plugins-good (1.16.0-48-gce0723527)
129493687793cbc109d6211bb0e465218e383e9d gstreamer (1.16.0-58-g129493687)
fatal: not a git repository: openvr/../.git/modules/openvr
</code></pre></div></div>
<p>我删除了 <code class="language-plaintext highlighter-rouge">.git/config</code>和 <code class="language-plaintext highlighter-rouge">.gitmodules</code> 中 openvr 的记录。接着就可以运行</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rm -rf openvr
$ git status
On branch proton_5.13
Your branch is up to date with 'origin/proton_5.13'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .gitmodules
deleted: openvr
</code></pre></div></div>
<p>到这里,就可以还原 <code class="language-plaintext highlighter-rouge">.gitmodules</code>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git restore .gitmodules
git status
On branch proton_5.13
Your branch is up to date with 'origin/proton_5.13'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: openvr
</code></pre></div></div>
<p>有趣的是,我尝试 <code class="language-plaintext highlighter-rouge">submodulle update</code> 会遇到网络问题,而 <code class="language-plaintext highlighter-rouge">git clone</code> 则可以正常执行。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Fail
git submodule update --depth=1 openvr
# Success
git clone https://github.com/ValveSoftware/openvr.git --depth=1
</code></pre></div></div>
<p>用<a href="https://endle.github.io/2021/02/01/git-submodule-fetch-from-local/">上一篇文章的技巧</a>,我也成功初始化了 openvr。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/ValveSoftware/openvr.git --depth=1
git fetch origin master --deepin=20
git submodule set-url openvr /dev/shm/openvr
git submodule update --init openvr
git restore . --recurse-submodules
</code></pre></div></div>
<p>最后,使用 Vagrant 编译代码时,可能会遇到网络问题。参考<a href="https://blog.csdn.net/haiyanghan/article/details/107168972">这篇 CSDN 的文章</a>,可以使用支持断点续传的 <code class="language-plaintext highlighter-rouge">wget</code> 下载镜像。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> proton$ vagrant up
Platform: 4 CPUs, 15979 MB memory
Bringing machine 'debian10' up with 'libvirt' provider...
==> debian10: Box 'generic/debian10' could not be found. Attempting to find and install...
debian10: Box Provider: libvirt
debian10: Box Version: >= 0
==> debian10: Loading metadata for box 'generic/debian10'
debian10: URL: https://vagrantcloud.com/generic/debian10
==> debian10: Adding box 'generic/debian10' (v3.2.2) for provider: libvirt
debian10: Downloading: https://vagrantcloud.com/generic/boxes/debian10/versions/3.2.2/providers/libvirt.box
==> debian10: Box download is resuming from prior download progress
Download redirected to host: vagrantcloud-files-production.s3.amazonaws.com
Progress: 0% (Rate: 46507/s, Estimated time remaining: 14:32:45)^C==> debian10: Waiting for cleanup before exiting...
==> debian10: Box download was interrupted. Exiting.
debian10: Calculating and comparing box checksum...
The checksum of the downloaded box did not match the expected
value. Please verify that you have the proper URL setup and that
you're downloading the proper file.
Expected: 76118312e5a2f227544660566b7d0f6ad3d6bf50ff1fc0b92602cda66d9cf3a6
Received: 9d80712a57190fb2821f11ac46e677be1c467afc15e02d3d29f84c069d4f9e8e
wget -c https://vagrantcloud.com/generic/boxes/debian10/versions/3.2.2/providers/libvirt.box
</code></pre></div></div>
<p>下载成功后,再执行 <code class="language-plaintext highlighter-rouge">vagrant box add</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~$ sha256sum libvirt.box
76118312e5a2f227544660566b7d0f6ad3d6bf50ff1fc0b92602cda66d9cf3a6 libvirt.box
vagrant box add generic/debian10 libvirt.box
Platform: 4 CPUs, 15979 MB memory
Bringing machine 'debian10' up with 'libvirt' provider...
==> debian10: Uploading base box image as volume into Libvirt storage...
==> debian10: Creating image (snapshot of base box volume).
==> debian10: Creating domain with the following settings...
</code></pre></div></div>
Git Submodule 从本地源初始化
2021-02-01T00:00:00+00:00
https://blog.zhenbo.pro/2021/02/01/git-submodule-fetch-from-local
<h4 id="背景介绍">背景介绍</h4>
<p>在使用 Wine 运行 <a href="https://appdb.winehq.org/objectManager.php?sClass=version&iId=37229">Magic: The Gathering Arena</a> 的过程中,我想自行编译 <a href="https://github.com/madewokherd/wine-mono">Wine-Mono</a>。<a href="https://github.com/madewokherd/wine-mono">Wine-Mono</a> 用 <code class="language-plaintext highlighter-rouge">git submodule</code> 将 Mono 的 codebase 囊括了进来。</p>
<p>在此之前,我在电脑上已经下载过了 Mono 的代码 (<code class="language-plaintext highlighter-rouge">git clone https://github.com/mono/mono.git --depth=1</code>)。与其重复访问 GitHub,我希望能从本地的代码库初始化 submodule。</p>
<h4 id="使用本地源">使用本地源</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/madewokherd/wine-mono.git
cd wine-mono
git submodule init
git submodule set-url mono /home/lizhenbo/src/mono
git submodule update mono
</code></pre></div></div>
<p>在完成初始化后,再还原该设置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git submodule set-url mono https://github.com/madewokherd/wine-mono.git
</code></pre></div></div>
<h4 id="切换回上游代码">切换回上游代码</h4>
<p>因为硬盘空间紧缺,我删掉了原有的 <code class="language-plaintext highlighter-rouge">/home/lizhenbo/src/mono</code> 。想要编辑上游代码,可以运行</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd wine-mono/mono
git remote add upstream https://github.com/mono/mono.git
git fetch upstream master
git checkout upstream/master
git submodule update --recursive --depth=1
</code></pre></div></div>
<p>在工作完成后,可以再<a href="https://stackoverflow.com/a/61751340/1166518">恢复 submodule 配置</a>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git restore . --recurse-submodules
</code></pre></div></div>
<h4 id="番外获取更深的历史记录">番外:获取更深的历史记录</h4>
<p>初次下载项目代码使用了 <code class="language-plaintext highlighter-rouge">--depth=1</code> 。如果想要看稍微久一些的历史记录,不需要 <code class="language-plaintext highlighter-rouge">git fetch --unshallow</code> 下载完整内容,而可以用 <code class="language-plaintext highlighter-rouge">git fetch origin master --deepen=10</code>。</p>
<p>我尝试了 <code class="language-plaintext highlighter-rouge">shallow-exclude</code>,但遇到了如下问题</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git fetch origin master --shallow-exclude=fad8c19d8600b34e46984ba6dbb600f9343cd773 -v
POST git-upload-pack (306 bytes)
POST git-upload-pack (400 bytes)
error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: Unknown error code (err 2)
fatal: error reading section header 'acknowledgments'
# https://stackoverflow.com/a/59474908/1166518
$ git config http.version HTTP/1.1
$ git fetch origin master --shallow-exclude=fad8c19d8600b34e46984ba6dbb600f9343cd773 -v
POST git-upload-pack (306 bytes)
POST git-upload-pack (400 bytes)
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: error reading section header 'acknowledgments'
</code></pre></div></div>
经SSH将Fedora 30升级至31
2020-01-01T00:00:00+00:00
https://blog.zhenbo.pro/2020/01/01/upgrade-fedora-30-to-31-via-ssh
<p>之前提到,我的 <a href="https://endle.github.io/2018/09/10/distcc-to-speedup/">客厅里有了一台服务器</a>。随着 Fedora 31 发布,原有的 Fedora 30 系统得到的更新变得很少,我也有了更新系统的打算。搜索了一些资料,有<a href="https://unix.stackexchange.com/a/58724/258214">在2012年的回答</a>说需要使用单独的ssh daemon进行F17升F18的操作。不过,也有人提到,新版本的 <code class="language-plaintext highlighter-rouge">dnf-plugin-system-upgrade</code> 插件可以正确处理这种情况。在做好重装系统的准备后,我决定试一试<code class="language-plaintext highlighter-rouge">dnf</code>的能力。</p>
<p>按照<a href="https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/">官方手册</a>,顺次在 ssh 中执行如下命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dnf upgrade --refresh
sudo dnf install dnf-plugin-system-upgrade
sudo dnf system-upgrade download --refresh --releasever=31
</code></pre></div></div>
<p>执行这些内容不会影响 ssh 连接。在运行成功后,可以进行重启并更新</p>
<p><code class="language-plaintext highlighter-rouge">sudo dnf system-upgrade reboot
</code></p>
<p>这一次重启的耗时会非常长,在我的电脑上持续了至少一小时。因为我在服务器上设定了 DHCP,所以系统升级后被分配了一个新的 IP 地址。在修改笔记本的 <code class="language-plaintext highlighter-rouge">~/.ssh/config</code> 以及 <code class="language-plaintext highlighter-rouge">known_hosts</code> 以后,就可以顺利访问自己的服务器了。如果当初配置时没有偷懒,而是分配了固定的IP,那系统升级就能无缝完成了。</p>
Fedora 删除 swap 后修复 Kernel Parameters
2019-07-06T00:00:00+00:00
https://blog.zhenbo.pro/2019/07/06/fedora-remove-swap-and-fix-kernel-parameters
<p>笔者近日为自己的笔记本电脑升级到了 32G 内存,就想着删掉 swap 分区,为自己捉襟见肘的 SSD 释放一些空间。参考着 <a href="https://wiki.archlinux.org/index.php/LVM">Arch Wiki</a>,整体而言比较顺利,但也有一点小小的插曲。</p>
<p>首先,运行 <code class="language-plaintext highlighter-rouge"># lvs</code> 查看分区情况。我的系统里,Volume Group 是 <code class="language-plaintext highlighter-rouge">fedora_zhenbo</code>。 删除 swap 分区不需要运行 <code class="language-plaintext highlighter-rouge"># umount /<mountpoint></code> 。相反,<a href="https://serverfault.com/a/684792">运行 <code class="language-plaintext highlighter-rouge">swapoff -a</code> 即可</a>。 接下来,删除 LV <code class="language-plaintext highlighter-rouge"># lvremove <volume_group>/<logical_volume></code>,并修改 <code class="language-plaintext highlighter-rouge">etc/fstab</code>。</p>
<p>可是,重启后,系统并没有正确启动,而是进入了 dracut 环境,提示 <code class="language-plaintext highlighter-rouge">fedora_zhenbo/swap</code> 未找到。重新检查了一遍,发现 Kernel parameters 里有一行 <code class="language-plaintext highlighter-rouge">rd.lvm.lv=fedora_zhenbo/swap</code>。将其删去后,可以顺利启动。<a href="https://web.archive.org/web/20221226031847/https://docs.fedoraproject.org/en-US/fedora/rawhide/system-administrators-guide/kernel-module-driver-configuration/Working_with_the_GRUB_2_Boot_Loader/">尝试运行 <code class="language-plaintext highlighter-rouge">grub2-mkconfig</code></a>,生成的 <code class="language-plaintext highlighter-rouge">grub.cfg</code> 里依旧有 swap。把 <code class="language-plaintext highlighter-rouge">/etc/grub.d/</code> 翻了一遍,也没有任何头绪。</p>
<p>幸运的是,<a href="https://orenbell.com/">网友 Oren Bell</a> 在 2017 年<a href="https://unix.stackexchange.com/questions/412149/grub2-mkconfig-isnt-generating-correct-mount-paths-and-also-how-do-i-get-rid-o">遇到了一样的问题</a>。LVM 分区被硬编码到了 <code class="language-plaintext highlighter-rouge">/etc/default/grub</code>。编辑该文件的 <code class="language-plaintext highlighter-rouge">GRUB_CMDLINE_LINUX=</code> 一栏,删掉 <code class="language-plaintext highlighter-rouge">rd.lvm.lv=fedora_zhenbo/swap</code> 后重新 <code class="language-plaintext highlighter-rouge">grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg</code> 即可。</p>
<p>最后,参考 <a href="https://wiki.archlinux.org/index.php/LVM">Arch Wiki</a>,把释放出来的空间添加到 home</p>
<p><code class="language-plaintext highlighter-rouge"># lvresize -l +100%FREE --resizefs fedora_zhenbo/home</code></p>
跟着 clang 的 libcxx 学习二分查找
2018-10-26T00:00:00+00:00
https://blog.zhenbo.pro/2018/10/26/clang-binary-search
<p>哪个算法简单到初学编程的人都能轻松实现,但有多年编程经验的人也可能会写出严重的 bug 呢?没错,正是二分查找。既然普通人的二分查找容易写错,那专业人士会如何实现二分查找呢?不妨参考一下 clang 7.0 的实现。</p>
<h3 id="lower_bound-在-stl-的定义"><code class="language-plaintext highlighter-rouge">lower_bound</code> <a href="https://en.cppreference.com/w/cpp/algorithm/lower_bound">在 STL 的定义</a></h3>
<p>给定有序区间 <code class="language-plaintext highlighter-rouge">[first, last)</code>, <code class="language-plaintext highlighter-rouge">val</code> 和 <code class="language-plaintext highlighter-rouge">operator <</code>。寻找第一个元素 <code class="language-plaintext highlighter-rouge">p</code> 使得 <code class="language-plaintext highlighter-rouge">A[p] <
val</code> 为<strong>假</strong>。</p>
<p>我们也可以这样理解,对于任意 <code class="language-plaintext highlighter-rouge">i < j</code>,若 <code class="language-plaintext highlighter-rouge">A[i]<val</code> 为假,则 <code class="language-plaintext highlighter-rouge">A[j]<val</code>
必为假。如下图,我们要寻找第一个绿色的元素</p>
<p><img src="/images/binary_search/lower_bound.png" alt="lower_bound" /></p>
<h3 id="clang-70-的实现">Clang 7.0 的实现</h3>
<p>让我们看一看 <a href="https://github.com/llvm-mirror/libcxx/blob/dffe9e0f1dde084f2aab8010345aeb1b7c8f7d4c/include/algorithm#L4190">lower_bound 的实现</a></p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">__lower_bound</span><span class="p">(</span><span class="n">_ForwardIterator</span> <span class="n">__first</span><span class="p">,</span> <span class="n">_ForwardIterator</span> <span class="n">__last</span><span class="p">,</span> <span class="k">const</span> <span class="n">_Tp</span><span class="o">&</span> <span class="n">__value_</span><span class="p">,</span> <span class="n">_Compare</span> <span class="n">__comp</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">typedef</span> <span class="k">typename</span> <span class="n">iterator_traits</span><span class="o"><</span><span class="n">_ForwardIterator</span><span class="o">>::</span><span class="n">difference_type</span> <span class="n">difference_type</span><span class="p">;</span>
<span class="n">difference_type</span> <span class="n">__len</span> <span class="o">=</span> <span class="n">_VSTD</span><span class="o">::</span><span class="n">distance</span><span class="p">(</span><span class="n">__first</span><span class="p">,</span> <span class="n">__last</span><span class="p">);</span>
<span class="k">while</span> <span class="p">(</span><span class="n">__len</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">difference_type</span> <span class="n">__l2</span> <span class="o">=</span> <span class="n">__len</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">_ForwardIterator</span> <span class="n">__m</span> <span class="o">=</span> <span class="n">__first</span><span class="p">;</span>
<span class="n">_VSTD</span><span class="o">::</span><span class="n">advance</span><span class="p">(</span><span class="n">__m</span><span class="p">,</span> <span class="n">__l2</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">__comp</span><span class="p">(</span><span class="o">*</span><span class="n">__m</span><span class="p">,</span> <span class="n">__value_</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">__first</span> <span class="o">=</span> <span class="o">++</span><span class="n">__m</span><span class="p">;</span>
<span class="n">__len</span> <span class="o">-=</span> <span class="n">__l2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="n">__len</span> <span class="o">=</span> <span class="n">__l2</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">__first</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// This file is dual licensed under the MIT and the UIUC license.</span></code></pre></figure>
<h3 id="这段代码的要点">这段代码的要点</h3>
<ol>
<li>定义:<code class="language-plaintext highlighter-rouge">first</code> 是第一个<strong>有可能</strong>使 <code class="language-plaintext highlighter-rouge">*first<val</code> 为 <strong>假</strong>的元素。或者说,是第一个可能为绿色的元素。</li>
<li>迭代:<code class="language-plaintext highlighter-rouge">l2 < len</code> 恒成立,因此 <code class="language-plaintext highlighter-rouge">first+l2</code> 就不会越界。同时,每次迭代时 <code class="language-plaintext highlighter-rouge">len</code> 严格递减</li>
<li>迭代结束:<code class="language-plaintext highlighter-rouge">l2 == 0</code> 当且仅当 <code class="language-plaintext highlighter-rouge">len == 1</code>。这是我们最后一次进行迭代</li>
<li>特殊情况:如果所有的元素都为假,那最后一次迭代时, <code class="language-plaintext highlighter-rouge">++m</code> 的结果正是 <code class="language-plaintext highlighter-rouge">last</code></li>
</ol>
<p>相比我之前写的不时丢弃真值、出现死循环的二分查找,clang 的代码可是高到不知道哪里去了。</p>
<p><a href="https://github.com/llvm-mirror/libcxx/blob/dffe9e0f1dde084f2aab8010345aeb1b7c8f7d4c/include/algorithm#L4238">upper_bound 的实现</a> 看起来是相反的,不过从布尔值的角度看,与 <code class="language-plaintext highlighter-rouge">lower_bound</code> 是一样的,我就不复制代码了。</p>
<h3 id="后记">后记</h3>
<p>上文中的示意图使用 R 语言绘制,参考了 <a href="https://stackoverflow.com/a/50438532/1166518">Stack Overflow 上画棋盘的讨论</a>。代码如下</p>
<figure class="highlight"><pre><code class="language-r" data-lang="r"><span class="n">b</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">matrix</span><span class="p">(</span><span class="n">nrow</span><span class="o">=</span><span class="m">1</span><span class="p">,</span><span class="n">ncol</span><span class="o">=</span><span class="m">6</span><span class="p">)</span><span class="w">
</span><span class="n">colorindex</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="nf">rep</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">4</span><span class="p">),</span><span class="w"> </span><span class="nf">rep</span><span class="p">(</span><span class="m">1</span><span class="p">,</span><span class="w"> </span><span class="m">2</span><span class="p">))</span><span class="w">
</span><span class="c1"># for each square</span><span class="w">
</span><span class="n">colors</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="s2">"red"</span><span class="p">,</span><span class="w"> </span><span class="s2">"green"</span><span class="p">)[</span><span class="n">colorindex</span><span class="m">+1</span><span class="p">]</span><span class="w"> </span><span class="c1"># choose colors</span><span class="w">
</span><span class="n">side</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="m">1</span><span class="o">/</span><span class="m">8</span><span class="w"> </span><span class="c1"># side of one square</span><span class="w">
</span><span class="n">ux</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">col</span><span class="p">(</span><span class="n">b</span><span class="p">)</span><span class="o">*</span><span class="n">side</span><span class="w"> </span><span class="c1"># upper x values</span><span class="w">
</span><span class="n">lx</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">ux</span><span class="o">-</span><span class="n">side</span><span class="w"> </span><span class="c1"># lower x values</span><span class="w">
</span><span class="n">uy</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">row</span><span class="p">(</span><span class="n">b</span><span class="p">)</span><span class="o">*</span><span class="n">side</span><span class="w"> </span><span class="c1"># upper y</span><span class="w">
</span><span class="n">ly</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">uy</span><span class="o">-</span><span class="n">side</span><span class="w"> </span><span class="c1"># upper y</span><span class="w">
</span><span class="n">plot.new</span><span class="p">()</span><span class="w"> </span><span class="c1"># initialize R graphics</span><span class="w">
</span><span class="n">rect</span><span class="p">(</span><span class="n">lx</span><span class="p">,</span><span class="w"> </span><span class="n">ly</span><span class="p">,</span><span class="w"> </span><span class="n">ux</span><span class="p">,</span><span class="w"> </span><span class="n">uy</span><span class="p">,</span><span class="w"> </span><span class="n">col</span><span class="o">=</span><span class="n">colors</span><span class="p">,</span><span class="w"> </span><span class="n">asp</span><span class="o">=</span><span class="m">1</span><span class="p">)</span><span class="w"> </span><span class="c1"># draw the board</span></code></pre></figure>
<h4 id="更新于-2023-08-20">更新于 2023-08-20</h4>
<p>这几天翻出了这篇文章,照着 clang 的代码,怎么写都不对。重读了一遍,才发现我在 2018
年撰写本文时,对<code class="language-plaintext highlighter-rouge">真/假</code>的使用有些混乱。抽出周末的时间,对文章进行了订正。</p>
用 distcc 加快编译 续篇-clang
2018-09-15T00:00:00+00:00
https://blog.zhenbo.pro/2018/09/15/-distcc-to-speedup-expansion
<p><a href="https://endle.github.io/2018/09/10/distcc-to-speedup/">上一篇文章</a> 简单介绍了使用 <code class="language-plaintext highlighter-rouge">distcc</code> 编译 wine 代码。有人在朋友圈里问我,能不能用 <code class="language-plaintext highlighter-rouge">clang</code> 编译。我当时的回答是,为什么不能呢?这周在摸鱼的时候,我完成了测试。</p>
<h4 id="理论分析">理论分析</h4>
<p>我看了一下 <a href="https://github.com/distcc/distcc/blob/24f73c5cd8f839bd520eb52e91d0d26e07689373/src/distcc.c#L249"><code class="language-plaintext highlighter-rouge">distcc</code> 的代码</a>,发现其机制还是比较简单的。<code class="language-plaintext highlighter-rouge">distcc</code> 后的一个参数会被当成编译器名称。理论上,不管是 gcc 还是 clang,都应该可以用类似的机制在多台电脑间分发。</p>
<h4 id="实战演练">实战演练</h4>
<p>因为这里仅仅是为了测试,而不是实际运行,我就不用麻烦地在 32-bit 和 64-bit下编译 wine 两次了。workflow如下</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="k">function </span>mkwine_clang<span class="o">()</span>
<span class="o">{</span>
<span class="nb">export </span><span class="nv">CC</span><span class="o">=</span><span class="s2">"clang-6.0"</span>
<span class="nb">export </span><span class="nv">CXX</span><span class="o">=</span><span class="s2">"clang-6.0"</span>
<span class="nb">export </span><span class="nv">CFLAGS</span><span class="o">=</span><span class="s2">"-O0"</span>
<span class="nb">export </span><span class="nv">JOBS</span><span class="o">=</span>8
<span class="nb">mkdir</span> <span class="nt">-p</span> ~/src/wine_n_extras/wine-clang
<span class="nb">cd</span> ~/src/wine_n_extras/wine-clang
../wine/configure <span class="nt">--enable-win64</span>
make <span class="nt">-j</span><span class="nv">$JOBS</span>
<span class="o">}</span></code></pre></figure>
<p><a href="https://wiki.winehq.org/Clang">wine 项目组对 clang 投入的精力</a> 是有回报的。我没有做任何额外的设置,就在本机成功编译了 wine</p>
<p><a href="http://apt.llvm.org/">在 Ubuntu 上安装 clang</a> 非常简单。很有趣的是,在我的 Ubuntu 14 工作站上,<code class="language-plaintext highlighter-rouge">clang</code> 默认是指向 <code class="language-plaintext highlighter-rouge">clang-3.3</code> 到 <code class="language-plaintext highlighter-rouge">clang-3.5</code> 的软链接。不过,在工作站和笔记本上,<code class="language-plaintext highlighter-rouge">/usr/bin/clang-6.0</code> 都是可用的。因此,我们可以对编译的 workflow 稍加修改</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="k">function </span>mkwine_clang<span class="o">()</span>
<span class="o">{</span>
<span class="nb">export </span><span class="nv">CC</span><span class="o">=</span><span class="s2">"distcc clang-6.0"</span>
<span class="nb">export </span><span class="nv">CXX</span><span class="o">=</span><span class="s2">"distcc clang-6.0"</span>
<span class="nb">export </span><span class="nv">CFLAGS</span><span class="o">=</span><span class="s2">"-O0"</span>
<span class="nb">export </span><span class="nv">JOBS</span><span class="o">=</span>8
<span class="nb">export </span><span class="nv">DISTCC_HOSTS</span><span class="o">=</span><span class="s2">"@beddp,lzo"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> ~/src/wine_n_extras/wine-clang
<span class="nb">cd</span> ~/src/wine_n_extras/wine-clang
../wine/configure <span class="nt">--enable-win64</span>
make <span class="nt">-j</span><span class="nv">$JOBS</span>
<span class="o">}</span></code></pre></figure>
<p>在我的电脑上,这一流程非常的顺利。理论分析和实践演练同时成功的情景真是太少了。</p>
<p><em>In theory, theory and practice are the same. In practice, they are not. -Anonymous</em></p>
<h4 id="不仅仅是c">不仅仅是C</h4>
<p>上文提到,<code class="language-plaintext highlighter-rouge">distcc</code> 判断编译器的方法很简单。那么,能不能用它来分发其他语言呢?很抱歉,这可能不行。<a href="https://github.com/distcc/distcc/blob/5de24577858687106c27dce1c1ae53edac2f6a6f/src/arg.c#L130"><code class="language-plaintext highlighter-rouge">dcc_scan_args</code> 函数会尝试解析编译选项</a>。如果要使用其他的编译器,个人猜测,必要条件包括使用与 <code class="language-plaintext highlighter-rouge">gcc</code>,<code class="language-plaintext highlighter-rouge">clang</code> 相同的编译选项。[Rust 社区在 2017 年年末进行了讨论](https://users.rust-lang.org/t/contract-opportunity-mozilla-distributed-compilation-cache-written-in-rust/13898),但好像并没有得出太好的解决方案。<br />
不过,这也称不上太大的损失。人生苦短,在 C/C++/Rust 以外,我们也不再需要一门编译超慢的语言了,不是吗?</p>
用 distcc 加快编译
2018-09-10T00:00:00+00:00
https://blog.zhenbo.pro/2018/09/10/distcc-to-speedup
<p>我的卧室迎来了新成员 - 拥有双 Xeon E5649 的工作站。在安顿好后,我就第一时间开始研究用 <code class="language-plaintext highlighter-rouge">distcc</code> 来加快编译速度了。毕竟,与其让我的 XPS 15 被折磨,不如把这个工作交给局域网内的工作站。官网提供了一份 <a href="https://cdn.rawgit.com/distcc/distcc/9a09372bd3f420cdd7021e52eda14fa536a3c10e/doc/web/index.html">60-second instructions</a> ,不妨一试。如果你是在60秒内完成了配置的幸运儿,那请关掉本页面。如果你遇到了阻碍,那么请看下一节。</p>
<h3 id="server--slaves--工作站设置">Server / Slaves / 工作站设置</h3>
<p>为了方便地调试,我选择在工作站上运行 <code class="language-plaintext highlighter-rouge">distccd --no-detach --daemon --allow 192.168.1.0/24 --log-stderr --verbose -j 1</code><br />
在新版本的 <code class="language-plaintext highlighter-rouge">distcc</code> 里,<code class="language-plaintext highlighter-rouge">--allow</code>参数已经是必选项了。在完成了调试后,我在生产环境中设置成了 <code class="language-plaintext highlighter-rouge">-j 10</code>,避免将 12 核都跑满后,ssh访问工作站会有一定延迟。另一种解决方法是,通过设置 <code class="language-plaintext highlighter-rouge">nice</code> 避免吃光资源。</p>
<h3 id="本地设置">本地设置</h3>
<h4 id="支线任务---ssh-name">支线任务 - SSH name</h4>
<p>通过<code class="language-plaintext highlighter-rouge">~/.ssh/config</code> 可以给自己局域网中的电脑分配一个简单易记的名字。我将我的工作站命名为了 <code class="language-plaintext highlighter-rouge">beddp</code>。不设置不影响使用。如果第一次接触这一概念,可以阅读 <a href="https://serverfault.com/a/215027">https://serverfault.com/a/215027</a></p>
<h4 id="主线任务">主线任务</h4>
<p>首先要设置环境变量 <code class="language-plaintext highlighter-rouge">export DISTCC_HOSTS="@beddp,lzo,cpp"</code><br />
<code class="language-plaintext highlighter-rouge">@beddp</code> 的意思是,通过 ssh 连接到名为 <code class="language-plaintext highlighter-rouge">beddp</code> 的主机。<code class="language-plaintext highlighter-rouge">,lzo,cpp</code> 这两个设置是为了开启 pump模式。详情可以阅读 <a href="https://wiki.archlinux.org/index.php/Distcc#For_use_without_makepkg">Arch Wiki</a></p>
<p>测试代码选择</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf"><stdio.h></span><span class="cp">
</span><span class="kt">int</span> <span class="nf">main</span><span class="p">(){</span>
<span class="n">puts</span><span class="p">(</span><span class="n">__VERSION__</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>在本地运行 <code class="language-plaintext highlighter-rouge">gcc k.c -o local.out && ./local.out</code> 的结果是 <code class="language-plaintext highlighter-rouge">8.1.1 20180712 (Red Hat 8.1.1-5)</code><br />
在服务器运行 <code class="language-plaintext highlighter-rouge">gcc k.c -o server.out && ./server.out</code> 的结果是 <code class="language-plaintext highlighter-rouge">4.8.4</code></p>
<p>gcc 版本的差异可能会导致各类潜在的问题。但现阶段,可以以此测试 <code class="language-plaintext highlighter-rouge">distcc</code> 是否正常工作</p>
<p>在本机运行 <code class="language-plaintext highlighter-rouge">DISTCC_VERBOSE=1 pump distcc gcc -c k.c -o k.o && gcc k.o -o server.out && ./server.out</code> 结果是 <code class="language-plaintext highlighter-rouge">4.8.4</code>。简单来说,添加了 <code class="language-plaintext highlighter-rouge">pump</code> 指令,意味着测试文件和涉及到的头文件会被发送到了工作站上,进行预处理以及编译,并在本地完成了最终的链接。我们也可以运行 <code class="language-plaintext highlighter-rouge">distcc gcc -c k.c -o k.o && gcc k.o -o server_nopump.out && ./server_nopump.out</code> 结果就变为了 <code class="language-plaintext highlighter-rouge">8.1.1 20180712 (Red Hat 8.1.1-5)</code>。 可以看到,没有了 <code class="language-plaintext highlighter-rouge">pump</code> 指令,预处理工作是在本地处理的</p>
<p>在本机也可以运行 <code class="language-plaintext highlighter-rouge">distccmon-text 2</code>。这会启动一个两秒钟刷新一次的监视器,显示任务的分配状态。</p>
<h3 id="项目实战">项目实战</h3>
<p>让我们开一瓶香槟,选一个项目实战一下。按照教程,我们只需要把运行 <code class="language-plaintext highlighter-rouge">make -j8 CC=distcc</code>即可。不过,很遗憾,我在 wine 项目上的测试失败了。即便 <a href="https://wiki.winehq.org/Gcc">Wine Wiki</a> 显示 gcc 4.8.4 应当可以成功编译 wine,<code class="language-plaintext highlighter-rouge">distcc</code> 也没有顺利地接过工作。相反,它不停地显示 <code class="language-plaintext highlighter-rouge">(dcc_build_somewhere) Warning: failed to distribute, running locally instead</code>。<br />
我在工作站上编译安装了 gcc 8。<code class="language-plaintext highlighter-rouge">distccd</code>命令支持指定 PATH,但我选择了笨办法:在本机和工作站上,都设置了一个软链接 <code class="language-plaintext highlighter-rouge">/usr/local/bin/gcc-8</code>。在编译前,设置 <code class="language-plaintext highlighter-rouge">export CC="ccache distcc gcc-8"</code>。在 ccache 缓存失败时,<code class="language-plaintext highlighter-rouge">distcc</code> 会将任务转交给工作站进行处理。我并没有统计具体的编译耗时,但个人体验是有断崖式提升的:在配置前,如果代码变化量大,编译代码时我的 XPS 机身会变得滚烫,同时风扇狂转。而配置好 <code class="language-plaintext highlighter-rouge">distcc</code> 后,即便在 ccache 缓存清空的情况下编译,自己的笔记本依旧凉爽安静。</p>
<p>最后附上我用 <code class="language-plaintext highlighter-rouge">distcc</code> 编译 wine 的 workflow,比较复杂,个别地方的写法也有待商榷。欢迎大家在评论区,或是向我发送邮件进行讨论。</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="k">function </span>cfgwine64<span class="o">()</span>
<span class="o">{</span>
<span class="nb">cd</span> ~/src/wine_n_extras/wine64-build <span class="o">&&</span> ../wine/configure <span class="nt">--enable-win64</span>
<span class="o">}</span>
<span class="k">function </span>cfgwine32<span class="o">()</span>
<span class="o">{</span>
<span class="nb">cd</span> ~/src/wine_n_extras/wine32-build <span class="o">&&</span><span class="se">\</span>
<span class="nv">PKG_CONFIG_PATH</span><span class="o">=</span>/usr/lib/pkgconfig <span class="se">\</span>
../wine/configure <span class="nt">--with-wine64</span><span class="o">=</span>../wine64-build
<span class="o">}</span>
<span class="k">function </span>mkwine<span class="o">()</span>
<span class="o">{</span>
<span class="nb">export </span><span class="nv">DISTCC_HOSTS</span><span class="o">=</span><span class="s2">"@beddp,lzo,cpp"</span>
<span class="nb">export </span><span class="nv">CC</span><span class="o">=</span><span class="s2">"ccache distcc gcc-8"</span>
<span class="nb">export </span><span class="nv">CFLAGS</span><span class="o">=</span><span class="s2">"-O0"</span>
<span class="nb">export </span><span class="nv">JOBS</span><span class="o">=</span>16
<span class="nb">export </span><span class="nv">DISTCC_HOSTS</span><span class="o">=</span><span class="s2">"@beddp,lzo"</span>
cdwine
cfgwine64
make <span class="nt">-j</span><span class="nv">$JOBS</span>
cfgwine32
make <span class="nt">-j</span><span class="nv">$JOBS</span>
<span class="o">}</span></code></pre></figure>
在 C++ 中使用 GLPK 求解线性规划
2018-02-12T00:00:00+00:00
https://blog.zhenbo.pro/2018/02/12/cpp-glpk-linear-programming-kit
<p>最近,参加了一个提交答案类的编程比赛,有一道题可用线性规划解决。搜索发现,<a href="https://www.gnu.org/software/glpk">GLPK (GNU Linear Programming Kit)</a> 是一个免费的线性规划计算库,可以方便地被 C/C++ 代码调用。现将基本使用方法整理如下:</p>
<h2 id="准备工作">准备工作</h2>
<h4 id="安装-glpk">安装 GLPK</h4>
<p>多数的发行版都应该提供了 <a href="https://www.gnu.org/software/glpk">GLPK</a> 的包。在 Fedora 下,只需运行<br />
<code class="language-plaintext highlighter-rouge">sudo dnf install glpk glpk-devel</code></p>
<h4 id="编译-glpk">编译 GLPK</h4>
<p>Fedora 将 <code class="language-plaintext highlighter-rouge">glpk.h</code> 存在了 <code class="language-plaintext highlighter-rouge">/usr/include/glpk.h</code>,因此,不需添加指令即可找到头文件。如果你是手动编译安装的 <a href="https://www.gnu.org/software/glpk">GLPK</a>,那可能需要使用 <code class="language-plaintext highlighter-rouge">-I</code> 指定头文件目录。链接的指令为 <code class="language-plaintext highlighter-rouge">-lglpk</code>。如果使用的是 C++ 的话,在调用头文件时,记得声明 <code class="language-plaintext highlighter-rouge">extern</code>。你可以试着使用 <code class="language-plaintext highlighter-rouge">g++ -lglpk test.cpp</code> 编译如下代码</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf"><iostream></span><span class="cp">
</span><span class="k">extern</span> <span class="s">"C"</span><span class="p">{</span>
<span class="cp">#include</span> <span class="cpf">"glpk.h"</span><span class="cp">
</span><span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="n">glp_version</span><span class="p">()</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>在我撰写本文时(2018年2月初),<a href="https://www.gnu.org/software/glpk">GLPK</a> 的版本应当为 4.61。</p>
<h2 id="实战演练">实战演练</h2>
<p><a href="https://www.gnu.org/software/glpk">GLPK</a> 官方手册上的例子有点让人混淆。在这里,我将采用更精简的例子。<br />
Maximize
\[z=10x_1+6x_2\]
Subject to
\[x_1+x_2\leqslant200\]
\[x_1+2x_2\geqslant10\]
\[3x_1+x_2\leqslant275.5\]
where all variables are non-negative
\[x_1\geqslant 0, x_2\geqslant 0\]</p>
<p>对于三个约束条件,我们可以创建三个辅助变量(auxiliary variables),将问题转化为如下形式:<br />
Maximize
\[z=10x_1+6x_2\]
Subject to
\[p=x_1+x_2\]
\[q=x_1+2x_2\]
\[r=3x_1+x_2\]
where all variables are non-negative
\[x_1\geqslant 0, x_2\geqslant 0\]
\[0\leqslant p\leqslant200,q\geqslant10,0\leqslant r\leqslant275.5\]</p>
<p>现在,可以将我们的问题输入程序了。<a href="https://www.gnu.org/software/glpk">GLPK</a> 将各辅助变量看作行(row),将原有的变量看作列(column),用一个矩阵表示辅助变量和原有变量的关系。</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf"><cstdio></span><span class="cp">
</span><span class="k">extern</span> <span class="s">"C"</span><span class="p">{</span>
<span class="cp">#include</span> <span class="cpf">"glpk.h"</span><span class="cp">
</span><span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nl">initialize:</span>
<span class="n">glp_prob</span> <span class="o">*</span><span class="n">lp</span><span class="p">;</span>
<span class="n">lp</span> <span class="o">=</span> <span class="n">glp_create_prob</span><span class="p">();</span>
<span class="n">glp_set_obj_dir</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="n">GLP_MAX</span><span class="p">);</span>
<span class="nl">auxiliary_variables_rows:</span>
<span class="n">glp_add_rows</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="n">glp_set_row_name</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"p"</span><span class="p">);</span>
<span class="n">glp_set_row_bnds</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">GLP_DB</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">200</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="n">glp_set_row_name</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s">"q"</span><span class="p">);</span>
<span class="n">glp_set_row_bnds</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">GLP_LO</span><span class="p">,</span> <span class="mi">10</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="n">glp_set_row_name</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="n">glp_set_row_bnds</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="n">GLP_DB</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">275</span><span class="p">.</span><span class="mi">5</span><span class="p">);</span>
<span class="nl">variables_columns:</span>
<span class="n">glp_add_cols</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="n">glp_set_col_name</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"x1"</span><span class="p">);</span>
<span class="n">glp_set_col_bnds</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">GLP_LO</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="n">glp_set_col_name</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s">"x2"</span><span class="p">);</span>
<span class="n">glp_set_col_bnds</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">GLP_LO</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="nl">to_maximize:</span>
<span class="n">glp_set_obj_coef</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="n">glp_set_obj_coef</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">6</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="nl">constrant_matrix:</span>
<span class="kt">int</span> <span class="n">ia</span><span class="p">[</span><span class="mi">7</span><span class="p">],</span> <span class="n">ja</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
<span class="kt">double</span> <span class="n">ar</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
<span class="n">ia</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">ja</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">ar</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">ia</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">ja</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">ar</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// p = x1 + x2</span>
<span class="n">ia</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">ja</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">ar</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">ia</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">ja</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">ar</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="c1">// q = x1 + 2x2</span>
<span class="n">ia</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span> <span class="n">ja</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">ar</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="n">ia</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span> <span class="n">ja</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">ar</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// r = 3x1 + x2</span>
<span class="n">glp_load_matrix</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="n">ia</span><span class="p">,</span> <span class="n">ja</span><span class="p">,</span> <span class="n">ar</span><span class="p">);</span>
<span class="nl">calculate:</span>
<span class="n">glp_simplex</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="nl">output:</span>
<span class="kt">double</span> <span class="n">z</span><span class="p">,</span> <span class="n">x1</span><span class="p">,</span> <span class="n">x2</span><span class="p">;</span>
<span class="n">z</span> <span class="o">=</span> <span class="n">glp_get_obj_val</span><span class="p">(</span><span class="n">lp</span><span class="p">);</span>
<span class="n">x1</span> <span class="o">=</span> <span class="n">glp_get_col_prim</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">x2</span> <span class="o">=</span> <span class="n">glp_get_col_prim</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"z = %lf, x1 = %lf, x2 = %lf</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">x1</span><span class="p">,</span> <span class="n">x2</span><span class="p">);</span>
<span class="nl">cleanup:</span>
<span class="n">glp_delete_prob</span><span class="p">(</span><span class="n">lp</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/*
GLPK Simplex Optimizer, v4.61
3 rows, 2 columns, 6 non-zeros
0: obj = -0.000000000e+00 inf = 1.000e+01 (1)
1: obj = 3.000000000e+01 inf = 0.000e+00 (0)
* 4: obj = 1.351000000e+03 inf = 0.000e+00 (0)
OPTIMAL LP SOLUTION FOUND
z = 1351.000000, x1 = 37.750000, x2 = 162.250000
*/</span></code></pre></figure>
<p>第一次看到示例代码,对于 <code class="language-plaintext highlighter-rouge">constrant_matrix</code> 部分可能会比较费解。在这里,<code class="language-plaintext highlighter-rouge">ia</code> 表示行号(第几个辅助变量),<code class="language-plaintext highlighter-rouge">ja</code> 表示列号(第几个变量),而 <code class="language-plaintext highlighter-rouge">ar</code> 的类型是 <code class="language-plaintext highlighter-rouge">double</code>,表示 constrant matrix 中的系数。将这些条件成功导入后,使用 <code class="language-plaintext highlighter-rouge">glp_simplex</code> 就可以求解了。</p>
<p>一个常见的需求是,需要求出对应的<strong>整数解</strong>。使用 glpk,这一问题也很好解决。</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="nl">set_variables_to_integer:</span>
<span class="n">glp_set_col_kind</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">GLP_IV</span><span class="p">);</span>
<span class="n">glp_set_col_kind</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">GLP_IV</span><span class="p">);</span>
<span class="n">calculate</span><span class="o">:</span>
<span class="n">glp_simplex</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">glp_intopt</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">output</span><span class="o">:</span>
<span class="kt">double</span> <span class="n">z</span><span class="p">,</span> <span class="n">x1</span><span class="p">,</span> <span class="n">x2</span><span class="p">;</span>
<span class="n">z</span> <span class="o">=</span> <span class="n">glp_mip_obj_val</span><span class="p">(</span><span class="n">lp</span><span class="p">);</span>
<span class="n">x1</span> <span class="o">=</span> <span class="n">glp_mip_col_val</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">x2</span> <span class="o">=</span> <span class="n">glp_mip_col_val</span><span class="p">(</span><span class="n">lp</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"z = %lf, x1 = %lf, x2 = %lf</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">x1</span><span class="p">,</span> <span class="n">x2</span><span class="p">);</span>
<span class="cm">/*
GLPK Simplex Optimizer, v4.61
3 rows, 2 columns, 6 non-zeros
0: obj = -0.000000000e+00 inf = 1.000e+01 (1)
1: obj = 3.000000000e+01 inf = 0.000e+00 (0)
* 4: obj = 1.351000000e+03 inf = 0.000e+00 (0)
OPTIMAL LP SOLUTION FOUND
GLPK Integer Optimizer, v4.61
3 rows, 2 columns, 6 non-zeros
2 integer variables, none of which are binary
Integer optimization begins...
+ 4: mip = not found yet <= +inf (1; 0)
Solution found by heuristic: 1346
+ 6: >>>>> 1.348000000e+03 <= 1.348000000e+03 0.0% (1; 1)
+ 6: mip = 1.348000000e+03 <= tree is empty 0.0% (0; 3)
INTEGER OPTIMAL SOLUTION FOUND
z = 1348.000000, x1 = 37.000000, x2 = 163.000000
*/</span></code></pre></figure>
<p>在运行过<code class="language-plaintext highlighter-rouge">glp_simplex</code>后,再运行<code class="language-plaintext highlighter-rouge">glp_intopt</code>即可得到整数解。但要注意的是,提取整数解的命令是 <code class="language-plaintext highlighter-rouge">glp_mip_obj_val</code> /
<code class="language-plaintext highlighter-rouge">glp_mip_col_val</code>。</p>
<p>PS 写这篇 blog 时,我第一次使用了 <a href="http://www.gastonsanchez.com/visually-enforced/opinion/2014/02/16/Mathjax-with-jekyll/">mathjax</a>。只能说,写作体验超乎想象地好。</p>
<p style="color:grey">讲一个逸闻吧。之前我曾把线性规划和高斯消元的时间复杂度弄混了,以至于我成功将大量NP问题转化为了线性规划问题后得到了“多项式”的解。</p>
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
<script type="text/javascript" id="MathJax-script" defer="" src="https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js">
</script>
用牛刀 LaTeX 完成简单的排版任务
2015-09-20T00:00:00+00:00
https://blog.zhenbo.pro/2015/09/20/use-latex-for-boring-job
<p>假期里,我们被布置了一篇报告作为作业。排版要求如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>正文为宋体、小四号、1.25倍行距。标题号第一层用一、二……第二层用1、2……第三层用(1)、(2)……。
</code></pre></div></div>
<p>同时,我们的报告要求写在特定纸张上。换句话说,是要用特定的页眉页脚。如图:
<img src="/images/LaTeX/for-use-latex-for-boring-job/example.png" alt="Example" /></p>
<p>在布置作业时,我们也得到了一个<a href="/downloads/LaTeX/example/template-for-use-latex-for-boring-job.docx">Word 模板</a>。刚刚读完 <a href="http://www.amazon.cn/gp/product/B00D1APK0G/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B00D1APK0G&linkCode=as2&tag=blo-23">《LaTex 入门》</a>,就想着用 LaTeX 来完成这项工作。</p>
<h4 id="自定义纸张">自定义纸张</h4>
<p>自定义纸张可以用 <a href="https://www.ctan.org/pkg/wallpaper">wallpaper 宏包</a> 来处理。这个包的功能很简单,刚好符合本例的需求。将原有的模板导出成 pdf,然后使用该宏包加载即可。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\CenterWallPaper{1}{background.pdf}
</code></pre></div></div>
<h4 id="自定义章节标题格式">自定义章节标题格式</h4>
<p>新的 <a href="https://www.tug.org/texlive//Contents/live/texmf-dist/doc/latex/ctex/ctex.pdf">CTeX 套件</a> 已经能完善地支持标题格式。本例中,可以使用</p>
<figure class="highlight"><pre><code class="language-latex" data-lang="latex"><span class="k">\ctexset</span> <span class="p">{</span>
section = <span class="p">{</span>
name = <span class="p">{</span>,、<span class="p">}</span>,
number = <span class="k">\chinese</span><span class="p">{</span>section<span class="p">}</span>
<span class="p">}</span>,
subsection = <span class="p">{</span>
name = <span class="p">{</span>,、<span class="p">}</span>,
number = <span class="k">\arabic</span><span class="p">{</span>subsection<span class="p">}</span>,
<span class="p">}</span>,
subsubsection = <span class="p">{</span>
name = <span class="p">{</span>(,)、<span class="p">}</span>,
numbering = true,
number = <span class="k">\arabic</span><span class="p">{</span>subsubsection<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>不过,这一功能比较新。如果使用的是较旧的套件,可能就无法正确编译。因此,建议首先升级至 texlive 2015.</p>
从 OI 到世界
2015-06-07T00:00:00+00:00
https://blog.zhenbo.pro/2015/06/07/from-oi-to-world
<p>又到了一年的高考季,而我,当年泡在机房里的高中生,已经毕业了两年。在参加 OI 比赛时,侧重点都在算法上,因而忽视了很多 C 语言的特性。在刚参与开源项目时,也多走了不少弯路。所以,我整理了一个小小的列表,希望这些问题能对你起到些许帮助。</p>
<h2 id="预处理">预处理</h2>
<h3 id="预处理的潜在风险">预处理的潜在风险</h3>
<p>C 语言中,预处理与编译是独立的阶段。很多时候,这会引入额外的风险。</p>
<ol>
<li>举出至少两个可能带来风险的,在 OI 中较为常用的宏,说明其危险性</li>
<li>用 <code class="language-plaintext highlighter-rouge">inline</code>, <code class="language-plaintext highlighter-rouge">const</code> 等方式重写,在不增加额外代价的情况下解决该问题</li>
</ol>
<h3 id="常用的宏">常用的宏</h3>
<p>虽然宏很危险,但很多时候,宏有其不可替代的作用。</p>
<ol>
<li>举出至少一个例子,说明宏的必要性</li>
<li>解释 <code class="language-plaintext highlighter-rouge">__FILE__</code>, <code class="language-plaintext highlighter-rouge">__FUNCTION__</code>, <code class="language-plaintext highlighter-rouge">__LINE__</code> 等宏的作用</li>
</ol>
<h2 id="链接器">链接器</h2>
<p>与许多带有 <code class="language-plaintext highlighter-rouge">import</code> 的语言不同,C 语言中链接器与编译器是分开的。链接器的机制,并不是三言两语就可以说清的。所以,在这里,我只是提几个常见的基础问题。</p>
<ol>
<li>C 语言中,什么是函数的声明?什么是函数的定义?缺失了其中的一个会发生什么?</li>
<li><code class="language-plaintext highlighter-rouge">#include</code> 时发生了什么?为什么有时没有包含 <code class="language-plaintext highlighter-rouge">stdio.h</code> 也可以使用 <code class="language-plaintext highlighter-rouge">printf</code> 函数?</li>
<li>什么是动态链接?有什么好处?如何使用?</li>
<li>头文件中的 <code class="language-plaintext highlighter-rouge">extern "C"</code> 有什么用?如果没有,会发生什么?</li>
</ol>
<h2 id="实战演练">实战演练</h2>
<p>此部分没有固定答案,希望大家的思路不要被拘束。</p>
<h3 id="带模版的-qsort">“带模版”的 qsort</h3>
<p><code class="language-plaintext highlighter-rouge">stdlib.h</code> 中的 <code class="language-plaintext highlighter-rouge">qsort</code> 有四个参数。它们的意义都是什么?为什么要如此定义?<br />
尝试自己实现一个 <code class="language-plaintext highlighter-rouge">my_qsort</code> 函数,要求:</p>
<ol>
<li>具有普适性</li>
<li>在平均情况下为 <code class="language-plaintext highlighter-rouge">O(n lgn)</code> 的复杂度。</li>
</ol>
<h3 id="better-string">Better String</h3>
<p>C 语言中并没有内置的 <code class="language-plaintext highlighter-rouge">string</code>,而只有 <code class="language-plaintext highlighter-rouge">char *</code>。请举若干例子,说明这导致了哪些问题。<br />
尝试在不使用编译器扩展特性的情况下,实现一个字符串类型。要求如下:</p>
<ol>
<li>求字符串长度的时间复杂度为 <code class="language-plaintext highlighter-rouge">O(1)</code></li>
<li>与 <code class="language-plaintext highlighter-rouge">string.h</code> 的 <code class="language-plaintext highlighter-rouge">strlen</code>, <code class="language-plaintext highlighter-rouge">strstr</code>, <code class="language-plaintext highlighter-rouge">strcat</code>, <code class="language-plaintext highlighter-rouge">strcpy</code> 兼容,或是重写对应的函数</li>
<li>额外的时间和空间代价必须是常数级的</li>
</ol>
<p><br />
<br />
<br /></p>
<h2 id="参考信息">参考信息</h2>
<p>我并不会对这些问题给出标准答案。如果你感觉没什么头绪,可以参考如下信息</p>
<h4 id="举出至少一个例子说明宏的必要性">举出至少一个例子,说明宏的必要性</h4>
<ol>
<li>Windows API 中的 <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms221627%28v=vs.85%29.aspx">VARIANT</a> (<a href="https://gitcafe.com/WineZH/wine/blob/master/include/oleauto.h#L102">Wine 中的实现</a>)</li>
<li><a href="/2013/05/06/c_list/">用 C 语言实现链表</a> 等数据结构</li>
</ol>
<h4 id="c-语言中什么是函数的声明什么是函数的定义缺失了其中的一个会发生什么">C 语言中,什么是函数的声明?什么是函数的定义?缺失了其中的一个会发生什么?</h4>
<p>参见 <a href="http://www.amazon.cn/gp/product/B003BVBOOQ/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B003BVBOOQ&linkCode=as2&tag=blo-23">C 语言程序设计 现代方法</a> 第九章 函数</p>
<h4 id="什么是动态链接有什么好处如何使用">什么是动态链接?有什么好处?如何使用?</h4>
<p>参见 <a href="http://www.amazon.cn/gp/product/B0027VSA7U/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B0027VSA7U&linkCode=as2&tag=blo-23">程序员的自我修养 链接、装载与库</a> 第7章(Linux) 第9章(Windows)</p>
<h4 id="头文件中的-extern-c-有什么用如果没有会发生什么">头文件中的 <code class="language-plaintext highlighter-rouge">extern "C"</code> 有什么用?如果没有,会发生什么?</h4>
<p>参见 <a href="http://www.amazon.cn/gp/product/B0027VSA7U/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B0027VSA7U&linkCode=as2&tag=blo-23">程序员的自我修养 链接、装载与库</a> 3.5.4 <br />
<a href="http://en.wikipedia.org/wiki/Name_mangling">Wikipedia Name Mangling</a></p>
<h4 id="带模版的-qsort-1">“带模版”的 qsort</h4>
<p>参见 <a href="http://www.amazon.cn/gp/product/B003BVBOOQ/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B003BVBOOQ&linkCode=as2&tag=blo-23">C 语言程序设计 现代方法</a> 17.7.2 指针的高级应用</p>
<h4 id="better-string-1">Better String</h4>
<p>主要有两种思路:</p>
<ol>
<li>使用结构体封装,如 <a href="https://github.com/websnarf/bstrlib/blob/master/bstrlib.txt#L60">bstrlib</a></li>
<li>在指针前附加信息,如 <a href="https://github.com/antirez/sds">SDS</a>, <a href="https://msdn.microsoft.com/en-us/library/ms221069.aspx">BSTR</a></li>
</ol>
<p>SDS 项目给出了<a href="https://github.com/antirez/sds/blob/master/README.md#advantages-and-disadvantages-of-sds">两种实现方式的对比</a></p>
<h3 id="书籍推荐">书籍推荐</h3>
<p><a href="http://www.amazon.cn/gp/product/B00FF1Y53C/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B00FF1Y53C&linkCode=as2&tag=blo-23">一站式学习C编程</a>
<a href="http://www.amazon.cn/gp/product/B0027VSA7U/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B0027VSA7U&linkCode=as2&tag=blo-23">程序员的自我修养:链接、装载与库</a>
<a href="http://www.amazon.cn/gp/product/B003BVBOOQ/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B003BVBOOQ&linkCode=as2&tag=blo-23">C语言程序设计:现代方法(第2版)</a>
<a href="http://www.amazon.cn/gp/product/B0012UMPBY/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B0012UMPBY&linkCode=as2&tag=blo-23">C陷阱与缺陷</a></p>
Qt5 中使用 log4qt 输出日志
2015-02-20T00:00:00+00:00
https://blog.zhenbo.pro/2015/02/20/qt5-with-log4qt
<p>最近在写一个 QT 的程序,需要打印日志。搜索了一下,选定了 <a href="http://log4qt.sourceforge.net/">Log4Qt</a>。不过,原项目已经长期不更新了,而 <a href="http://www.devbean.net/">DevBean</a> 维护了一个 <a href="https://github.com/devbean/log4qt">Qt5 的版本</a>。所以,我执行了 <code class="language-plaintext highlighter-rouge">git submodule add https://github.com/devbean/log4qt.git</code> 。在我撰写本文时(2015年2月),另一个团队也在孜孜不倦地 <a href="https://web.archive.org/web/20140808035437/https://gitorious.org/log4qt">维护着 Log4qt</a>。可惜,在2023年,他们的网站已经无法访问了。</p>
<p>官方提供了一个比较详细的<a href="http://log4qt.sourceforge.net/">使用介绍</a>,但也隐藏了几个小坑,整理如下:</p>
<h4 id="step-1">Step 1</h4>
<p>将这句话加入 .pro 文件即可
<code class="language-plaintext highlighter-rouge">include(<unpackdir>/src/log4qt/log4qt.pri)</code></p>
<h4 id="step-2">Step 2</h4>
<p>在预编译头文件里加入</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf">"log4qt/consoleappender.h"</span><span class="cp">
#include</span> <span class="cpf">"log4qt/logger.h"</span><span class="cp">
</span><span class="c1">//我添加的内容</span>
<span class="cp">#include</span> <span class="cpf">"log4qt/logmanager.h"</span><span class="cp">
#include</span> <span class="cpf">"log4qt/patternlayout.h"</span><span class="cp">
</span><span class="c1">//被我移除的头文件</span>
<span class="c1">//#include "log4qt/ttcclayout.h"</span></code></pre></figure>
<h4 id="step-3">Step 3</h4>
<p>初始化 Log4Qt,我是在 <code class="language-plaintext highlighter-rouge">QApplication::exec()</code> 之前执行的这段代码
为了能灵活地处理 Layout,我小幅修改了原有的代码。</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="c1">//Configure a logger to generate output. The example uses the root logger</span>
<span class="c1">//Create a layout</span>
<span class="n">Log4Qt</span><span class="o">::</span><span class="n">LogManager</span><span class="o">::</span><span class="n">rootLogger</span><span class="p">();</span>
<span class="c1">//TTCCLayout *p_layout = new TTCCLayout();</span>
<span class="c1">//使用 PatternLayout,自定义输出格式</span>
<span class="n">p_layout</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Log4Qt</span><span class="o">::</span><span class="n">PatternLayout</span><span class="p">(</span><span class="s">"%-5p [%c] %m%n"</span><span class="p">);</span>
<span class="n">p_layout</span><span class="o">-></span><span class="n">setName</span><span class="p">(</span><span class="n">QLatin1String</span><span class="p">(</span><span class="s">"My Layout"</span><span class="p">));</span>
<span class="n">p_layout</span><span class="o">-></span><span class="n">activateOptions</span><span class="p">();</span>
<span class="c1">// Create an appender</span>
<span class="n">ConsoleAppender</span> <span class="o">*</span><span class="n">p_appender</span> <span class="o">=</span> <span class="n">new</span> <span class="nf">ConsoleAppender</span><span class="p">(</span><span class="n">p_layout</span><span class="p">,</span> <span class="n">ConsoleAppender</span><span class="o">::</span><span class="n">STDOUT_TARGET</span><span class="p">);</span>
<span class="n">p_appender</span><span class="o">-></span><span class="n">setName</span><span class="p">(</span><span class="n">QLatin1String</span><span class="p">(</span><span class="s">"My Appender"</span><span class="p">));</span>
<span class="n">p_appender</span><span class="o">-></span><span class="n">activateOptions</span><span class="p">();</span>
<span class="c1">// Set appender on root logger</span>
<span class="n">Log4Qt</span><span class="o">::</span><span class="n">Logger</span><span class="o">::</span><span class="n">rootLogger</span><span class="p">()</span><span class="o">-></span><span class="n">setAppender</span><span class="p">(</span><span class="n">p_appender</span><span class="p">);</span></code></pre></figure>
<p>关于 Layout 的详细内容,可以参考 <a href="https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html">Log4j 的一篇文档</a>,使用方法与 C 的格式化字符串是一样的。</p>
<h4 id="step-4">Step 4</h4>
<p>然后,就可以直接输出 log 了,例如官方示例<br />
<code class="language-plaintext highlighter-rouge">Log4Qt::Logger::logger(QLatin1String("My Logger"))->info("Hello World!");</code></p>
<p>为了简化代码,我在预编译头文件里又加入了这段内容</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cp">#define INITIATE_LOG4QT(ClassName) LOG4QT_DECLARE_STATIC_LOGGER(LOG4QT_LOGGER, ClassName)
</span>
<span class="cp">#define ERROR(s) LOG4QT_LOGGER()->error(QString(s))
#define WARN(s) LOG4QT_LOGGER()->warn(QString(s))
#define INFO(s) LOG4QT_LOGGER()->info(QString(s))</span></code></pre></figure>
<p>而在定义类的头文件里,只要初始化一下</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">class</span> <span class="nc">MyClass</span>
<span class="p">{</span>
<span class="nl">private:</span>
<span class="n">INITIATE_LOG4QT</span><span class="p">(</span><span class="n">MyClass</span><span class="p">);</span>
<span class="nl">public:</span>
<span class="n">MyClass</span><span class="p">();</span>
<span class="o">~</span><span class="n">MyClass</span><span class="p">();</span>
<span class="p">};</span></code></pre></figure>
<p>就可以直接在类的成员函数里用 <code class="language-plaintext highlighter-rouge">ERROR()</code> 这样的宏了。</p>
<h4 id="step-5">Step 5</h4>
<p>Log4Qt 的功能非常强大。例如,四个 Level 用于输出不同的信息,可以在启动时动态确定输出哪些信息。同样,也可以使用条件编译,在 Release 里将 <code class="language-plaintext highlighter-rouge">INFO</code> 编译成 <code class="language-plaintext highlighter-rouge">(void)0</code> 来减少性能损失。不过,Log4Qt 的性能没有测试,希望了解的朋友贴出自己的经验 :-)</p>
<h4 id="last">Last</h4>
<p>从我学 Qt 的第一天起就没少看 DevBeans 的博客,也在这里推荐一下他的书籍
<a href="http://www.amazon.cn/gp/product/B00SALSVVG/ref=as_li_ss_tl?ie=UTF8&camp=536&creative=3132&creativeASIN=B00SALSVVG&linkCode=as2&tag=blo-23">《Qt 5编程入门》</a></p>
如何教人编程
2014-02-07T00:00:00+00:00
https://blog.zhenbo.pro/2014/02/07/how-to-teach-programming
<p>这学期头脑发热,辅导了两个人编程(第一个 Python,第二个 C++),真的是很难忘的经历。<br />
学习编程是一件有挑战的事情。相比之下,教一个人编程更加困难。第一个障碍,就是思维方式的改变(针对大学生而言)。</p>
<p>有了高中的经历,学习者很希望能得到一个 “知识体系”,一个树状的知识结构图,就像这样
<img src="/images/thinking/biology_sample.gif" alt="biology" /><br />
(原链接已失效)</p>
<p>问题是,编程并不是要回答原有的问题。我喜欢将编程比作乐高积木:给定若干规则,你要按照你的想法想起组合在一起。来 <em>搭建</em> 你所需要的东西。如果不能让他们掌握新的思维方式,那你就可以打 GG 然后 Alt + Q 了。</p>
<p>回头想想,有几点建议,还是留给自己吧:</p>
<h4 id="指出错误要直接">指出错误要直接</h4>
<p>我并不时说我们要像 Linus Torvalds 那样 <em>“直白”</em>,但对于学习者的错误,要果断的指出。如果发现对概念有错误的理解,一定要第一时间指出。如果因为他们看上去很努力,就给了不客观的鼓励,那会是非常不负责任的行为。</p>
<h4 id="避免使用自然语言">避免使用自然语言</h4>
<p><img src="/images/thinking/project_need.png" alt="need description" /></p>
<p>大家应该很熟悉这幅漫画吧。一个团队内的成员,尚且会相互误解,更何况在一个半斤八两的教授者,和一个一片空白的学习者间呢?<br />
相比于含糊的自然语言,流程图是一种更可靠的交流方式。而且,还能帮助他们形成正确的思路。</p>
<h4 id="不要相信我听懂了">不要相信“我听懂了”</h4>
<p>很多时候,“懂了”的意思,是“我知道了”(参见图一)。学习者经验少,不自觉地自欺欺人,如果你也被忽悠了,那结果将会是悲惨的。</p>
<h4 id="不要在练习上吝啬时间">不要在练习上吝啬时间</h4>
<p><a href="http://book.douban.com/subject/4923179/">《我编程,我快乐》</a> 一书中,作者将编程与音乐做了类比。虽然两者间的区别很大(比方说没有维也纳新年编程会),但个人愚见,有一点是共同的:需要大量的练习。贸然跳过一些自认为“简单”的练习,很可能埋下某些“定时炸弹”。今天坑挖的越深,明天掉进去的时候摔的就会越痛。</p>
<h3 id="不要轻易挖坑">不要轻易挖坑</h3>
<p style="margin-bottom: 0cm"><FONT COLOR="#c0c0c0">但趁着年轻,多做一些有挑战性的事情吧</FONT></p>
CentOS 使用 ELRepo 安装最新的 Linux Kernel
2014-01-30T00:00:00+00:00
https://blog.zhenbo.pro/2014/01/30/centos-new-linux-kernel
<p>回家给台式机装上了 CentOS 6.5,发现很多软件包都太旧,尤其是 kernel,还停留在 2.6 的时代。找了一些介绍,多数说要自己编译 kernel,实在太麻烦。不过,有一篇文章介绍了用 <a href="http://elrepo.org/tiki/tiki-index.php">ELRepo</a> 安装新内核的方法,我只想说:</p>
<blockquote>
<p>太伟大了!</p>
</blockquote>
<p>具体的步骤很简单:</p>
<ol>
<li>添加源<br />
<code class="language-plaintext highlighter-rouge">rpm -Uvh http://www.elrepo.org/elrepo-release-6-5.el6.elrepo.noarch.rpm</code></li>
<li>安装kernel<br />
<code class="language-plaintext highlighter-rouge">yum --enablerepo=elrepo-kernel install kernel-ml</code><br />
(解释一下,ml 是指 MainLine, kernel-lt 就是 Long-Term)</li>
<li>编辑 grub 的默认启动项<br />
这里我就懒了,直接 编辑的 <code class="language-plaintext highlighter-rouge">/boot/grub/menu.lst</code>,好孩子们不要学我啊</li>
</ol>
DeleteFileA 的 testcase 的代码阅读笔记
2013-12-18T00:00:00+00:00
https://blog.zhenbo.pro/2013/12/18/deletefilea-testcase-code
<p>为了处理 <a href="http://bugs.winehq.org/show_bug.cgi?id=34324">Wine bug 34324</a>,我看了 <code class="language-plaintext highlighter-rouge">kernel32.dll</code> 里 <code class="language-plaintext highlighter-rouge">DeleteFileA()</code> 和其 testcase 的代码,发现埋藏了不少的坑。考虑到最近我会比较忙,就先做一些简单的记录,回头再进一步的处理。</p>
<h2 id="坑有哪些">坑有哪些</h2>
<p>个人以为,这段代码历史 <strong>悠久</strong> 是问题的关键。</p>
<ol>
<li>Windows API 的变化<br />
很多组 testcase 是 2002 年左右加上去的。当时 XP 刚刚面世。这么多个版本过来,很多 Win API 的行为都变了,尤其是非法情况下的返回值。</li>
<li>Wine 的代码质量<br />
翻了 git log,发现 Wine 的一些旧代码的质量真不敢恭维。欠缺了很多的注释,git commit 时写的 comment 也不准确,这给后来的工作带来了一些障碍。</li>
<li>Windows API 的诡异行为<br />
可能是我对 Windows 的了解太少,我做测试的时候,发现 <code class="language-plaintext highlighter-rouge">DeleteFile("nul")</code> 和 <code class="language-plaintext highlighter-rouge">DeleteFile("a.exe")</code> 遇到文件不存在的问题后,<code class="language-plaintext highlighter-rouge">GetLastError()</code> 的返回值是不一样的。看来,我最初的推测并不准确。</li>
</ol>
<h2 id="如何去做">如何去做</h2>
<p>我的设想是这样的:</p>
<ol>
<li>DeleteFileA 的 testcase</li>
<li>DeleteFileA (要把很多组 todo_wine 解决掉)</li>
<li>hack SHFileOperation (in shell32.dll)</li>
</ol>
<h2 id="阅读笔记">阅读笔记</h2>
<p>好了,现在到正题了。我看到的是这段代码</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"> <span class="n">ret</span> <span class="o">=</span> <span class="n">DeleteFileA</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span>
<span class="n">ok</span><span class="p">(</span><span class="o">!</span><span class="n">ret</span> <span class="o">&&</span> <span class="p">(</span><span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_INVALID_PARAMETER</span> <span class="o">||</span>
<span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_PATH_NOT_FOUND</span><span class="p">),</span>
<span class="s">"DeleteFileA(NULL) returned ret=%d error=%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">ret</span><span class="p">,</span><span class="n">GetLastError</span><span class="p">());</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">DeleteFileA</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
<span class="n">ok</span><span class="p">(</span><span class="o">!</span><span class="n">ret</span> <span class="o">&&</span> <span class="p">(</span><span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_PATH_NOT_FOUND</span> <span class="o">||</span>
<span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_BAD_PATHNAME</span><span class="p">),</span>
<span class="s">"DeleteFileA(</span><span class="se">\"\"</span><span class="s">) returned ret=%d error=%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">ret</span><span class="p">,</span><span class="n">GetLastError</span><span class="p">());</span></code></pre></figure>
<p>这来自于 e948ad1fc7e18a2,由 Francois Gouget 在 2002 年 12 月提交。</p>
<p>还有这段</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"> <span class="n">ret</span> <span class="o">=</span> <span class="n">DeleteFileA</span><span class="p">(</span><span class="s">"nul"</span><span class="p">);</span>
<span class="n">ok</span><span class="p">(</span><span class="o">!</span><span class="n">ret</span> <span class="o">&&</span> <span class="p">(</span><span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_FILE_NOT_FOUND</span> <span class="o">||</span>
<span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_INVALID_PARAMETER</span> <span class="o">||</span>
<span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_ACCESS_DENIED</span> <span class="o">||</span>
<span class="n">GetLastError</span><span class="p">()</span> <span class="o">==</span> <span class="n">ERROR_INVALID_FUNCTION</span><span class="p">),</span>
<span class="s">"DeleteFileA(</span><span class="se">\"</span><span class="s">nul</span><span class="se">\"</span><span class="s">) returned ret=%d error=%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">ret</span><span class="p">,</span><span class="n">GetLastError</span><span class="p">());</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">ERROR_INVALID_PARAMETER</code> 来自 c49b9485,由 Jakob Eriksson 在 2004 年 4 月提交。 至于 <code class="language-plaintext highlighter-rouge">ERROR_INVALID_FUNCTION</code> 来自 6cb97534 Jacek Caban, 在 2005 年 6 月提交。</p>
<p>PS <code class="language-plaintext highlighter-rouge">git blame file.c > /dev/shm/log</code> 真神器。</p>
SHFileOperation 删除文件的小测试
2013-12-14T00:00:00+00:00
https://blog.zhenbo.pro/2013/12/14/shfileoperation-delete-file
<p>这段时间处理 <a href="http://bugs.winehq.org/show_bug.cgi?id=34324">Wine bug 34324: QQ2013 SP1 can’t install</a>, 发现问题没有想象的简单。把之前取得的成果先记录下来,希望能给同样遇到这个问题的人一点提示,也算给自己留下一点笔记。</p>
<p><strong>注意: 以下的所有结果是通过包括 <a href="http://wiki.winehq.org/WineTestBot">Wine Testbot</a> 等黑箱测试得到的,没有任何反汇编的内容。如果你对 Wine 开发感兴趣,也请不要参考类似的资料</strong></p>
<h2 id="bug-简介">bug 简介</h2>
<p>QQ2013 SP1 安装时,会调用 <code class="language-plaintext highlighter-rouge">SHFileOperation</code> 删除 <code class="language-plaintext highlighter-rouge">C:\\Program Files\\qqtest</code>。但如果该目录不存在,这次函数调用就会失败。<br />
在 WinXP 下,返回值时 <code class="language-plaintext highlighter-rouge">0x402</code>。而在 Vista 及以后版本中,返回值就是 <code class="language-plaintext highlighter-rouge">ERROR_FILE_NOT_FOUND</code> (<code class="language-plaintext highlighter-rouge">0x2</code>)。<br />
但是,在当前的版本(wine-1.7.8)中,返回值为 <code class="language-plaintext highlighter-rouge">ERROR_PATH_NOT_FOUND</code>(<code class="language-plaintext highlighter-rouge">0X3</code>)。所以,在 QQ2013 Installer 看来,这不符合预期,就拒绝继续安装。</p>
<h2 id="测试进程">测试进程</h2>
<p>为了完成这个 hack,我研究了一下 Wine 原有的 testcase (<code class="language-plaintext highlighter-rouge">dlls/shell32/tests/shlfileop.c</code>, <code class="language-plaintext highlighter-rouge">commit a1762ba8a46eca5c7ef1e</code>)<br />
在 <code class="language-plaintext highlighter-rouge">test_delete()</code> 函数里,主要的情况是:</p>
<ol>
<li>delete a nonexistent file<br />
事实上,如果找不到文件,系统是无法区分 file 和 dictionary 的。在 XP 下,返回值是 <code class="language-plaintext highlighter-rouge">0x402</code>,而 Vista 及以后,返回值都是 <code class="language-plaintext highlighter-rouge">ERROR_FILE_NOT_FOUND</code></li>
<li>delete a nonexistent file in an existent dir or a nonexistent dir<br />
对于前者(existend dir),结果与 delete nonexistent file 是一样的。<br />
对于后者,XP 的返回值依旧是<code class="language-plaintext highlighter-rouge">0x402</code>,而 Vista 及以后的版本中,返回值是 <code class="language-plaintext highlighter-rouge">DE_INVALIDFILES</code>(<code class="language-plaintext highlighter-rouge">0x7c</code>)。</li>
<li>delete a dir, and then a file inside the dir<br />
这是一个比较旧的 tetscase,当时 Vista 是最新的 Windows 版本。有人留下了说明, <em>Vista would throw up a dialog box that we can’t suppress</em>。但他的 hack 方式 <code class="language-plaintext highlighter-rouge">ret != ERROR_FILE_NOT_FOUND</code> 会忽略掉 Win 7/8。我提交了一个 <a href="http://source.winehq.org/patches/data/100895">patch</a>,在经过 Wine-devel 中的 <a href="http://www.winehq.org/pipermail/wine-devel/2013-December/102211.html">讨论</a> 后,这个 patch 还是被拒绝了。Qian Hong 的解释是:<br />
<em>根据Windows版本来区分api的特性是不靠谱的,因为Microsoft会不断地发布service pack,每次发布都会修复一些bug,表面上看,某个特性可能在Windows XP和Windows 7上是不同的,可是经过更新打上某个service pack之后,Windows XP的特性和Windows 7可能就相同了。因此,要根据feature来决定要不要skip某个test。</em></li>
</ol>
<p>附上我的补丁核心部分:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="o">+</span> <span class="n">ok</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ERROR_PATH_NOT_FOUND</span> <span class="o">||</span> <span class="cm">/* XP */</span>
<span class="o">+</span> <span class="n">broken</span><span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">ERROR_SUCCESS</span><span class="p">)</span> <span class="o">||</span> <span class="cm">/* NT4 */</span>
<span class="o">+</span> <span class="n">ret</span> <span class="o">==</span> <span class="n">DE_INVALIDFILES</span><span class="p">,</span> <span class="cm">/* Win 7 or 8 */</span>
<span class="o">+</span> <span class="s">"Expected ERROR_PATH_NOT_FOUND or DE_INVALIDFILES, got 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span></code></pre></figure>
<p>我还做了一个测试(补丁找不到了):删除同一个文件两次,运行结果和情况 3 是一样的。</p>
<h2 id="接下来的任务">接下来的任务</h2>
<p>在 Wine 的实现里,<code class="language-plaintext highlighter-rouge">SHFileOperation</code> 会调用 <code class="language-plaintext highlighter-rouge">BOOL DeleteFileW</code>。而 <code class="language-plaintext highlighter-rouge">DeleteFileW</code> 是靠 <code class="language-plaintext highlighter-rouge">SetLastError()</code> 设置错误代码的。(<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363915%28v=vs.85%29.aspx">MSDN: DeleteFile function</a>) 我看了一下 kernel32 的相关测试,覆盖的并不是很好。我的 <a href="http://source.winehq.org/patches/data/101039">patch 101039</a> 被 reject 掉了,<a href="http://www.winehq.org/pipermail/wine-devel/2013-December/102265.html">相关讨论</a> 里提到,kernel32 的很多 testcase 比较旧。接下来,我打算花时间完善一下这些 testcase。</p>
为 wine 添加 testcase
2013-11-16T00:00:00+00:00
https://blog.zhenbo.pro/2013/11/16/wine-add-testcase
<p>之前处理一个 <a href="http://bugs.winehq.org/show_bug.cgi?id=34324">QQ2013安装程序无法完成的 bug</a>,经过分析,问题锁定在了 <code class="language-plaintext highlighter-rouge">SHFileOperation</code> 函数上。<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/bb762164%28v=vs.85%29.aspx">MSDN 的函数介绍</a></p>
<p>QQ 的安装程序会尝试删去 <code class="language-plaintext highlighter-rouge">C:\Program Files\qqtest</code>。文件不存在时,<code class="language-plaintext highlighter-rouge">SHFileOperation</code> 函数会有一个返回值(XP 和 Vista 及以后不同)。但是 wine 并没有正确的提供改返回值,所以 QQ 的安装程序就认为发生了错误,而停止了安装。</p>
<p>###Wine Test
要保证 Wine 的函数实现和 windows 下的表现一样,就需要一些测试。进入 wine 的源代码目录下的 dlls 文件夹,然后选定一个 dll(本例是 shell32),然后里面会有一个 tests 文件夹。里面,就有了一些 test case。</p>
<p>例如 <code class="language-plaintext highlighter-rouge">shlfileop.c</code>,里面有许多 static 函数。在函数内,大量的调用了 <code class="language-plaintext highlighter-rouge">ok</code> 宏。它了用法很简单,就像 <code class="language-plaintext highlighter-rouge">assert</code> 宏一样。如果某句假定不成立,那就可以在前面一行加上 <code class="language-plaintext highlighter-rouge">todo_wine</code>。这样的话,该 ok 宏会在 windows 下测试,而不会在 wine 下测试。</p>
<p>编辑好后,在 tests 目录下运行 <code class="language-plaintext highlighter-rouge">make test</code>。然后,就能看到对 wine 的运行情况的反馈。</p>
<p>####Test bot
一个人要测试 windows API 在不同版本下的表现是很繁琐的,所以,有了 <a href="http://wiki.winehq.org/WineTestBot">WineTestBot</a>。把自己的补丁提交到 <newtestbot.winehq.org> 上,就会自动进行测试,并在结束后把结果发送到你的邮箱里(为什么想到了OJ?)</newtestbot.winehq.org></p>
<p>附:<a href="http://www.freelists.org/post/wine-zh/SHFileOperationW-about-Bug-34324">我们在邮件列表里进行的讨论</a></p>
BOINC 的源代码阅读笔记 2
2013-10-26T00:00:00+00:00
https://blog.zhenbo.pro/2013/10/26/boinc--2
<p>想研究一下 BOINC 客户端获取任务的方式,望文生义,找到了 <code class="language-plaintext highlighter-rouge">client/cs_scheduler.cpp</code> 里的 <code class="language-plaintext highlighter-rouge">request_work_fetch(const char *)</code>。这个函数做的很少,只是修改了 <code class="language-plaintext highlighter-rouge">CLIENT_STATE</code> 的一个 <code class="language-plaintext highlighter-rouge">private</code> 变量: <code class="language-plaintext highlighter-rouge">must_check_work_fetch</code>。</p>
<p>接下来要看的是一个关键函数:<code class="language-plaintext highlighter-rouge">client/cs_scheduler.cpp</code> 里的 <code class="language-plaintext highlighter-rouge">scheduler_rpc_poll()</code> 。它首先会尝试上报任务,然后再申请任务。如果两次申请的时间差别不大,那它是不会申请任务的。但如果 <code class="language-plaintext highlighter-rouge">must_check_work_fetch</code> 为真,那就不用担心时间间隔的问题了。</p>
<p>但 <code class="language-plaintext highlighter-rouge">scheduler_rpc_poll()</code> 的目的是从全局考虑,保证电脑“有饭吃”。因此,它每次只会通过 <code class="language-plaintext highlighter-rouge">work_fetch.choose_project()</code> 找一个项目来申请。</p>
<p>这里的 <code class="language-plaintext highlighter-rouge">work_fetch</code> 在 <code class="language-plaintext highlighter-rouge">client/work_fetch.cpp</code> 文件中,是定义在全局的 <code class="language-plaintext highlighter-rouge">WORK_FETCH</code> 变量。他会对显卡和处理器分别讨论,调用 <code class="language-plaintext highlighter-rouge">cpu_work_fetch</code> 和 <code class="language-plaintext highlighter-rouge">cuda_work_fetch</code>。我们以 cpu 为例。</p>
<p>首先,是遍历一遍所有的项目,找到最合适的,存储到 <code class="language-plaintext highlighter-rouge">PROJECT* pbst</code> 里。然后,根据不同的需求,调用 <code class="language-plaintext highlighter-rouge">RSC_WORK_FETCH::set_request(PROJECT*, double)</code> 来执行这项工作(说实话,这里的机制我理解的还不好)。</p>
<p>如果想要强行获取任务的话,不停把 <code class="language-plaintext highlighter-rouge">must_check_work_fetch</code> 设置成真应该是一个办法。但这种对于全局的操作,可能不适合对某个项目进行屯包。所以,接下来,我应该对 <code class="language-plaintext highlighter-rouge">PROJECT</code> 类研究一下。</p>
BOINC 源代码的阅读笔记
2013-09-25T00:00:00+00:00
https://blog.zhenbo.pro/2013/09/25/boinc-code
<p>在论坛上跟人聊天,提到了修改 BOINC 客户端,以便于屯包的设想。在开学前没什么事,就挖下了这个坑。我修改后的版本,可以在 [这里][gcrepo] 看到。当然,现在还没有什么可用性。</p>
<h2 id="获取代码">获取代码</h2>
<p>BOINC 的代码可以从官方网站 [下载][offi],也可以从我在 GitCafe 上留的镜像 [下载][master](注意是 master 分支)。可以参照 [官方手册][man] 编译安装。
[gcrepo]: https://gitcafe.com/endle/BOINCc/tree/dev
[offi]: http://boinc.berkeley.edu/trac/wiki/SourceCodeGit
[master]: https://gitcafe.com/endle/BOINCc/tree/master
[man]: http://boinc.berkeley.edu/wiki/Compiling_the_core_client</p>
<h2 id="系统机制">系统机制</h2>
<p>软件最基本的是两部分:<code class="language-plaintext highlighter-rouge">boinc</code> 和 <code class="language-plaintext highlighter-rouge">boinccmd</code>。<code class="language-plaintext highlighter-rouge">boinc</code> 使用 <code class="language-plaintext highlighter-rouge">recv()</code> 函数接收来自 <code class="language-plaintext highlighter-rouge">boinccmd</code> 的消息,并维护一个消息队列。而 <code class="language-plaintext highlighter-rouge">boinccmd</code> 负责跟用户沟通,将用户输入的指令转化成 xml 格式,然后用 <code class="language-plaintext highlighter-rouge">send()</code> 发送给 <code class="language-plaintext highlighter-rouge">boinc</code>。</p>
<h2 id="入手点">入手点</h2>
<p>第一个思路,就是找 <code class="language-plaintext highlighter-rouge">main</code> 函数。在 <code class="language-plaintext highlighter-rouge">client/boinc_cmd.cpp</code> 里的 main 函数,会对用户输入的命令进行初步的检查。接着,<code class="language-plaintext highlighter-rouge">boinccmd</code> 会通过<code class="language-plaintext highlighter-rouge">retval = rpc.init(hostname, port)</code>尝试与 HOST 建立连接,并调用 <code class="language-plaintext highlighter-rouge">RPC_CLIENT</code> 类的方法。</p>
<h2 id="rpc_client">RPC_CLIENT</h2>
<p><a href="http://boinc.berkeley.edu/trac/wiki/GuiRpc">官方wiki</a> 上给的解释是:The BOINC client provides a set of RPCs (<strong>remote procedure calls</strong>) for control and state interrogation. This enables the development of GUI (graphical user interface) programs. These RPCs <em>send XML request</em> and reply messages over a <strong>TCP</strong> connection. The XML formats are not documented, but can be inferred from the source code.</p>
<p><code class="language-plaintext highlighter-rouge">RPC_CLIENT</code> 的定义在 <code class="language-plaintext highlighter-rouge">lib/gui_rpc_client.h</code> 里。可以看出,这是一个负责与 HOST 进行沟通的模块。以 <code class="language-plaintext highlighter-rouge">lib/gui_rpc_client_ops.cpp</code> 中的 <code class="language-plaintext highlighter-rouge">project_op</code> 函数为例(部分精简):</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="kt">int</span> <span class="n">RPC_CLIENT</span><span class="o">::</span><span class="n">project_op</span><span class="p">(</span><span class="n">PROJECT</span><span class="o">&</span> <span class="n">project</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">op</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">retval</span><span class="p">;</span>
<span class="n">SET_LOCALE</span> <span class="n">sl</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">512</span><span class="p">];</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">tag</span><span class="p">;</span>
<span class="n">RPC</span> <span class="n">rpc</span><span class="p">(</span><span class="n">this</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="s">"reset"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">tag</span> <span class="o">=</span> <span class="s">"project_reset"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="s">"detach"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">tag</span> <span class="o">=</span> <span class="s">"project_detach"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="s">"update"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">tag</span> <span class="o">=</span> <span class="s">"project_update"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="s">"suspend"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">tag</span> <span class="o">=</span> <span class="s">"project_suspend"</span><span class="p">;</span>
<span class="n">project</span><span class="p">.</span><span class="n">suspended_via_gui</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="s">"resume"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">tag</span> <span class="o">=</span> <span class="s">"project_resume"</span><span class="p">;</span>
<span class="n">project</span><span class="p">.</span><span class="n">suspended_via_gui</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">sprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span>
<span class="s">"<%s></span><span class="se">\n</span><span class="s">"</span>
<span class="s">" <project_url>%s</project_url></span><span class="se">\n</span><span class="s">"</span>
<span class="s">"</%s></span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">tag</span><span class="p">,</span>
<span class="n">project</span><span class="p">.</span><span class="n">master_url</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span>
<span class="n">tag</span>
<span class="p">);</span>
<span class="n">retval</span> <span class="o">=</span> <span class="n">rpc</span><span class="p">.</span><span class="n">do_rpc</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">retval</span><span class="p">)</span> <span class="p">{</span>
<span class="n">retval</span> <span class="o">=</span> <span class="n">rpc</span><span class="p">.</span><span class="n">parse_reply</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">retval</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>用户的指令会被包装成 xml 格式,然后使用 <code class="language-plaintext highlighter-rouge">send_request(const chap *p)</code> 函数发送给 <code class="language-plaintext highlighter-rouge">boinc</code></p>
<h2 id="接收命令">接收命令</h2>
<p><code class="language-plaintext highlighter-rouge">client/gui_rpc_server_ops.cpp</code> 的 <code class="language-plaintext highlighter-rouge">GUI_RPC_CONN::handle_rpc()</code> 用 <code class="language-plaintext highlighter-rouge">recv</code> 或 <code class="language-plaintext highlighter-rouge">read</code> 来接收消息,并进行处理。它通过调用 <code class="language-plaintext highlighter-rouge">lib/parse/h</code> 的 <code class="language-plaintext highlighter-rouge">match_tag(const char *, const char *)</code> 来解析 xml,并调用对应的函数。
在进行初步的匹配后,用户指令会以字符串的形式发送给 <code class="language-plaintext highlighter-rouge">handle_project_op</code>。对于多数的命令,程序会通过 gstate 的方式来处理。</p>
<h2 id="下一步工作">下一步工作</h2>
<p>折腾了一段时间,对 BOINC 的程序框架(尤其是用户交互部分)有了些许了解。接下来,就要啃啃 <code class="language-plaintext highlighter-rouge">client/client_state.h</code> 里的 <code class="language-plaintext highlighter-rouge">class CLIENT_STATE</code>,与 BOINC 的核心部分打交道了。</p>
Vim 自动加载 cscope.out
2013-09-01T00:00:00+00:00
https://blog.zhenbo.pro/2013/09/01/vim-auto-cscope
<p>这两天看 BOINC 的代码,需要 find usage 的功能。花了一上午,摸索出了让 vim 自动加载生成的 cscope.out 的方法。</p>
<p>在 .vimrc 中插入如下代码即可:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="s2">"Configure for cscope
set nocscopeverbose
set cscopequickfix=s-,c-,d-,i-,t-,e-
set cst
function LoadCscope(path)
"</span>防止无限递归
<span class="k">if </span>a:path <span class="o">==</span> <span class="nv">$HOME</span>
<span class="k">return
</span>endif
<span class="k">if</span> <span class="o">(</span>executable<span class="o">(</span><span class="s2">"cscope"</span><span class="o">)</span> <span class="o">&&</span> has<span class="o">(</span><span class="s2">"cscope"</span><span class="o">))</span>
<span class="nb">let </span>l:outfile<span class="o">=</span>a:path.<span class="s2">"/cscope.out"</span>
<span class="nb">let </span>l:outpath<span class="o">=</span>a:path
<span class="k">if </span>filereadable<span class="o">(</span>outfile<span class="o">)</span>
cs reset
exe <span class="s2">"cs add"</span> outfile outpath
<span class="k">else</span>
<span class="s2">"递归
let l:newpath=a:path."</span>/..<span class="s2">"
let newpath=resolve(newpath)
"</span><span class="nb">echo </span>newpath
call LoadCscope<span class="o">(</span>newpath<span class="o">)</span>
endif
endif
endfunction
call LoadCscope<span class="o">(</span>getcwd<span class="o">())</span></code></pre></figure>
<p>这是我 <em>第一次</em> 写的 vimrc 的函数。思路很简单,就是递归找父目录。但有几处障碍让我花费了一个上午。</p>
<p><code class="language-plaintext highlighter-rouge">if a:path == $HOME</code></p>
<p>vim 中,使用函数传入的参数时要加前缀 a,这一点跟 C,python 之类的就不太一样</p>
<p><code class="language-plaintext highlighter-rouge">let newpath=resolve(newpath)</code></p>
<p>如果不加这句话,vim 脚本是无法正确的识别文件链接 <code class="language-plaintext highlighter-rouge">..</code> 的</p>
<p><code class="language-plaintext highlighter-rouge">set nocscopeverbose</code></p>
<p>有的时候,vim 会自动加载 cscope.out。这个时候,就会发生冲突。加上这句话就能解决该问题。</p>
Fedora 19 64bit 解决开源 AMD 显卡驱动问题
2013-07-08T00:00:00+00:00
https://blog.zhenbo.pro/2013/07/08/fedora-19-64bit--amd-driver
<p>自从装上了 64 位的 Fedora,就没能用 wine 带起过 3D 游戏。设置上<code class="language-plaintext highlighter-rouge">LIBGL_DEBUG=verbose</code>,用 crossover 测试,有这样的提示</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="nl">libGL:</span> <span class="nl">OpenDriver:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">tls</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span><span class="o">:</span> <span class="n">OpenDriver</span><span class="o">:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">dlopen</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span> <span class="n">failed</span> <span class="p">(</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span><span class="o">:</span> <span class="n">cannot</span> <span class="n">open</span> <span class="n">shared</span> <span class="n">object</span> <span class="n">file</span><span class="o">:</span> <span class="n">No</span> <span class="n">such</span> <span class="n">file</span> <span class="n">or</span> <span class="n">directory</span><span class="p">)</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">unable</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">driver</span> <span class="n">pointer</span> <span class="n">missing</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">failed</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">r600</span>
<span class="n">libGL</span><span class="o">:</span> <span class="n">OpenDriver</span><span class="o">:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">tls</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span><span class="o">:</span> <span class="n">OpenDriver</span><span class="o">:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">dlopen</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span> <span class="n">failed</span> <span class="p">(</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span><span class="o">:</span> <span class="n">cannot</span> <span class="n">open</span> <span class="n">shared</span> <span class="n">object</span> <span class="n">file</span><span class="o">:</span> <span class="n">No</span> <span class="n">such</span> <span class="n">file</span> <span class="n">or</span> <span class="n">directory</span><span class="p">)</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">unable</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">failed</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">swrast</span></code></pre></figure>
<p>我的电脑上,只有 <code class="language-plaintext highlighter-rouge">/usr/lib64/dri/r600_dri.so</code>。很显然,我缺的是 32 位的驱动。第一反应,是建一个软链接。但这次,提示变成了</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">dlopen</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span> <span class="n">failed</span> <span class="p">(</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span><span class="o">:</span> <span class="n">cannot</span> <span class="n">open</span> <span class="n">shared</span> <span class="n">object</span> <span class="n">file</span><span class="o">:</span> <span class="n">Too</span> <span class="n">many</span> <span class="n">levels</span> <span class="n">of</span> <span class="n">symbolic</span> <span class="n">links</span><span class="p">)</span></code></pre></figure>
<p>那我拷贝过去呢?很遗憾,没这么简单。</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">dlopen</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span> <span class="n">failed</span> <span class="p">(</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span><span class="o">:</span> <span class="n">wrong</span> <span class="n">ELF</span> <span class="n">class</span><span class="o">:</span> <span class="n">ELFCLASS64</span><span class="p">)</span></code></pre></figure>
<p>这里,我就只好用一个笨方法了:我从网上下载了 Fedora 19 32bit 的 ISO,然后装到了虚拟机里,再把这个文件拷贝了出来(后面我会提供链接)。</p>
<p>问题是,最后还是出了一点小问题</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="nl">libGL:</span> <span class="nl">OpenDriver:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">tls</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span><span class="o">:</span> <span class="n">OpenDriver</span><span class="o">:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">dlopen</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span> <span class="n">failed</span> <span class="p">(</span><span class="n">libLLVM</span><span class="o">-</span><span class="mi">3</span><span class="p">.</span><span class="mi">3</span><span class="p">.</span><span class="n">so</span><span class="o">:</span> <span class="n">cannot</span> <span class="n">open</span> <span class="n">shared</span> <span class="n">object</span> <span class="n">file</span><span class="o">:</span> <span class="n">No</span> <span class="n">such</span> <span class="n">file</span> <span class="n">or</span> <span class="n">directory</span><span class="p">)</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">unable</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">r600_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">driver</span> <span class="n">pointer</span> <span class="n">missing</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">failed</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">r600</span>
<span class="n">libGL</span><span class="o">:</span> <span class="n">OpenDriver</span><span class="o">:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">tls</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span><span class="o">:</span> <span class="n">OpenDriver</span><span class="o">:</span> <span class="n">trying</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">dlopen</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span> <span class="n">failed</span> <span class="p">(</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dri</span><span class="o">/</span><span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span><span class="o">:</span> <span class="n">cannot</span> <span class="n">open</span> <span class="n">shared</span> <span class="n">object</span> <span class="n">file</span><span class="o">:</span> <span class="n">No</span> <span class="n">such</span> <span class="n">file</span> <span class="n">or</span> <span class="n">directory</span><span class="p">)</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">unable</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">swrast_dri</span><span class="p">.</span><span class="n">so</span>
<span class="n">libGL</span> <span class="n">error</span><span class="o">:</span> <span class="n">failed</span> <span class="n">to</span> <span class="n">load</span> <span class="n">driver</span><span class="o">:</span> <span class="n">swrast</span></code></pre></figure>
<p>到这里,问题就简单多了。<code class="language-plaintext highlighter-rouge">yum install llvm.i686</code>,搞定。</p>
<p>32 位驱动下载链接:<a href="https://skydrive.live.com/redir?resid=902E5583A63BC02B!120">https://skydrive.live.com/redir?resid=902E5583A63BC02B!120</a></p>
<p>sha1sum: <code class="language-plaintext highlighter-rouge">86351d2d73e63c7088e90d442969643f7ad79111</code>
<br />
<br />
P.S. DOTA里的神灵果然是一个(虐AI)强大的英雄。</p>
用 ssh 安装 Linux
2013-06-26T00:00:00+00:00
https://blog.zhenbo.pro/2013/06/26/-ssh--linux
<p>这两天不知道为啥抽风,又打算在虚拟机里装 Gentoo 了。据说为了复制粘贴命令方便,用 ssh 安装是更好的选择。摸索了一下,总算学会了。</p>
<h4 id="在-guest-里启动-ssh">在 Guest 里启动 ssh</h4>
<p>参考 <a href="http://www.gentoo.org/doc/zh_cn/handbook/handbook-x86.xml?part=1&chap=2">Gentoo 文档</a> ,在 Guest 里运行 <code class="language-plaintext highlighter-rouge">/etc/init.d/sshd start</code></p>
<h4 id="配置-ssh-帐号">配置 ssh 帐号</h4>
<p>在这里,我偷懒了,没有在 gentoo 里多建立一个用户,而是直接 <code class="language-plaintext highlighter-rouge">passwd root</code>。</p>
<h4 id="主机登录-ssh">主机登录 ssh</h4>
<p>首先,在 Guest 里运行 <code class="language-plaintext highlighter-rouge">ifconfig</code>,看一下 IP 地址。然后,在主机的终端里运行 <code class="language-plaintext highlighter-rouge">ssh root@ip_address</code>,输入刚才设置的 root 密码就可以了。如果无法登陆,应该检查一下 <code class="language-plaintext highlighter-rouge">.ssh/known_hosts</code>。</p>
<h4 id="后记">后记</h4>
<p>ssh 是一个很强大的工具,功能远不仅仅是给 Guest 装系统。我的设置,也感觉有安全隐患。至于 ssh 的深入挖掘,恩,反正我是不搞了 T_T</p>
给 Wine 报 bug 的错题本
2013-06-07T00:00:00+00:00
https://blog.zhenbo.pro/2013/06/07/-wine-bug
<p>查了一下,我保送后第一次在 <a href="http://bugs.winehq.org">http://bugs.winehq.org</a> 上报 bug, 已经是13年1月的事了。尝试着参与了一些 debug 工作,也犯了一些错误。写下这篇短文,权当笔记吧。</p>
<h2 id="避免重复">避免重复</h2>
<p>在上报了一个 bug,结果被 <code class="language-plaintext highlighter-rouge">CLOSED DUPLICATE</code>,是很令人沮丧的。而且,这也不能给开发人员提供有效的帮助。除了明显的重复(比如 [bug 32751][32751]),还有一些不容易发现的重复,比如 [bug 33433][33433]。
[32751]: http://bugs.winehq.org/show_bug.cgi?id=32751
[33433]: http://bugs.winehq.org/show_bug.cgi?id=33433</p>
<p>我在搜索 <em>Fetion</em> 的时候,只找到了 [Bug 29769 - Fetion IM Client: crashes when clicking on profile icon ][29769],所以我就上报了 [Bug 33433 - Fetion IM crashed after opening a chat dialog][33433]。 但实际上,因为 backtrace 相同, [bug 29769#3][] 就把 [Bug 29770 - Fetion IM Client: crashes when opening a chat window][29770] 关掉了。所以,虽然表现不同,但这都是同一个 bug。
[29769]: http://bugs.winehq.org/show_bug.cgi?id=29769
[33433]: http://bugs.winehq.org/show_bug.cgi?id=33433
[bug 29769#3]: http://bugs.winehq.org/show_bug.cgi?id=29769#c3
[29770]: http://bugs.winehq.org/show_bug.cgi?id=29770</p>
<h2 id="只讨论一个-bug">只讨论一个 bug</h2>
<p>我不知道怎么表述更好;在这里,我有两个例子。</p>
<p>首先是 [29769#12][f-12] 。我在安装的时候出现了错误提示的对话框。Jactry Zeng 和 Qian Hong 都提醒我,我应当新建一个 bug。
[f-12]: http://bugs.winehq.org/show_bug.cgi?id=29769</p>
<p>其次,大家在 bugzilla 上研究 workaround,只是针对该 bug,而不是为了让软件能正常运行。比如 <a href="http://bugs.winehq.org/show_bug.cgi?id=29638#c7">bug 29638#7</a>, Qian Hong 的回复是 <em>There are two crashing bug related to QQ, winetricks -q riched20 is for Bug 29636.</em> 一口气 winetricks 一串,只会让问题复杂化。</p>
<h2 id="中文显示的问题">中文显示的问题</h2>
<p>[Bug 33666 - YY6 Installer can’t show Chinese][33666] 是我最近报的一个 bug。这类 bug,多数的原因都是字体缺失。在 [文泉驿][wen] 之类的的字体屈指可数的情况下, [FontReplacement][fr] 就是最有效的解决方法了。所以,遇到类似问题时,最好先检查一下,自己是否打好了 [补丁][patch]。
[33666]: http://bugs.winehq.org/show_bug.cgi?id=33666
[wen]: http://wenq.org/wqy2/index.cgi
[fr]: http://wiki.winehq.org/FontReplacements
[patch]: https://gitcafe.com/endle/FontReplacements–TestingPatch
<br />
在学习报 bug 的过程中,非常感谢 [wine-zh][] 列表中大家对我的帮助,特别是 Qian Hong,回答了我大量的问题。有关于 Wine 的疑问,不妨试试 Google <code class="language-plaintext highlighter-rouge">site:http://www.freelists.org/archive/wine-zh/</code> ,你会有意想不到的收获的。
[wine-zh]: www.freelists.org/list/wine-zh</p>
Linux 下利用网盘同步 Firefox 扩展
2013-05-28T00:00:00+00:00
https://blog.zhenbo.pro/2013/05/28/linux--firefox-
<p>不知道为什么,最近我的 Firefox 加载一些网页总是有问题。参考了 [Bugzilla 上我收到的回复][bug],我打算重置我的个人配置。</p>
<p>但这么做,就意味着我要重新安装一下自己的扩展,和积累的一票油猴脚本。尤其是 [ForTrick][fox],还要手动去下载 .xpi 文件。网上有人推荐了 [FEBE][febe],但不知道为什么,我就是没装上。所以,就打算试试手动备份。
[bug]: https://bugzilla.mozilla.org/show_bug.cgi?id=851867
[febe]: http://www.williamlong.info/archives/2217.html
[fox]: http://www.foxtrick.org</p>
<h2 id="linux-下的配置文件">Linux 下的配置文件</h2>
<p>自己摸索了一下,Linux 把 FF 的配置文件保存在了 <code class="language-plaintext highlighter-rouge">~/.mozilla/firefox</code> 里。 <code class="language-plaintext highlighter-rouge">profiles.ini</code> 则决定了加载哪个用户的配置文件。默认情况下,会有一个名字很奇怪的文件夹(我的是 <code class="language-plaintext highlighter-rouge">hptzpdkk.default</code>),作为默认文件夹。</p>
<h2 id="建立新的配置文件">建立新的配置文件</h2>
<p>关掉 Firefox,输入 <code class="language-plaintext highlighter-rouge">firefox -p</code>(本人测试不区分大小写),然后会出现一个很和谐的窗口。
<img src="/images/firefox/sync.png" alt="screen" /></p>
<p>新建一个用户,然后指定新的文件夹即可。</p>
<h2 id="设置软链接">设置软链接</h2>
<p>关于 Linux 下软链接的知识,可以阅读 <a href="http://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-104-6/">创建和更改硬链接和符号链接</a>,但如果没有耐心,也没必要读完这篇文章。</p>
<p>我平时使用 <a href="https://jianguoyun.com/">坚果云</a> 来同步我的文件。在创建了新的用户配置以后,首先备份 <code class="language-plaintext highlighter-rouge">profiles.ini</code>,我使用的代码是 <code class="language-plaintext highlighter-rouge">ln -s ~/nutstore/firefox/profiles.ini ~/.mozilla/firefox/profiles.ini</code></p>
<p><code class="language-plaintext highlighter-rouge">ln -s</code> 后的两个路径,第一个是文件实际存放的位置(坚果云自动同步文件夹),第二个是链接的位置(相当于快捷方式)。用同样的方法,备份 <code class="language-plaintext highlighter-rouge">gm_scripts</code> 和 <code class="language-plaintext highlighter-rouge">extensions</code> 两个文件夹就可以了。</p>
<p><strong>试了半天,没有找到扩展的配置文件的位置,只好重新设置一遍了。如果你有解决方案,请告诉我,谢谢</strong></p>
我的TED精选集(一)
2013-05-22T00:00:00+00:00
https://blog.zhenbo.pro/2013/05/22/ted-1
<p>之前零零散散的看了不少 TED 的视频,就整理一个精选集吧,权当自己的笔记了。</p>
<p>####<a href="http://v.youku.com/v_show/id_XNDcyMTY5NDAw.html">康拉德·沃尔夫拉姆Conrad Wolfram:用计算机教导孩子真正的数学</a>
<strong>大力推荐!!</strong> 我看了一些 TED 的视频,但没看到哪个有这么大的信息量!简单摘录一下:</p>
<ol>
<li>数学不等于计算 <em>(math != calculate)</em></li>
<li>计算不是数学的基础</li>
<li>学习的顺序,不一定要遵循发明的顺序</li>
<li>人应当从繁重而无意义的工作(计算)中解脱出来</li>
</ol>
<p>####<a href="http://v.youku.com/v_show/id_XNDYwMDQ0MjY4.html">达恩·迈尔Dan Meyer:数学课需要改革</a>
看到了这个视频,我第一个想到了CSDN上 <a href="http://blog.csdn.net/myan/article/details/647511">孟岩关于线性代数</a> 的这一段话</p>
<blockquote>
<p>我们不认为直觉性与抽象性一定相互矛盾,特别是在数学教育中和数学教材中,帮助学生建立直觉,有助于它们理解那些抽象的概念,进而理解数学的本质。</p>
</blockquote>
<p>惭愧地说,我还没真正接触线性代数、集合论等东西,对这些高度抽象的东西,没什么发言的资格。而原视频中过于简单的例子(往桶里倒水),更像是对小学生(无贬义)的启发,而不是严格的数学教学。所以,如何平衡直觉与严谨,各位见仁见智了。</p>
<p>但这段视频里提出了一点:为什么人们不会像推荐一首好歌一样推荐数学?Dan Meyer 努力培养学生对数学的爱,从这个角度讲,他是当之无愧的 <em>优秀</em> 的数学教师。</p>
<p>####<a href="http://v.youku.com/v_show/id_XNDYwMDI0NDEy.html">Ken Robinson:认为学校扼杀创造力</a>
美帝的教育制度在 TED 上中枪早就不新鲜了,我朝也不必把人家当成正面典型学。整个视频其实就一句话:<strong>教育的目的是什么?</strong> 老调重提,但仍值得我们深思。</p>
<p>####<a href="http://v.youku.com/v_show/id_XNTIzNTUwNjEy.html">帕旺辛哈:大脑如何学习辨识物体</a>
虽然这个视频没少讲对视觉障碍儿童的治疗,但真正让我激动的,还是对机器人的研究(职业病?)</p>
<p>从对机器学习的研究,我们是否也能窥探,我们出生后,是如何走出懵懂,认识到这个世界?</p>
<p>很 13 的再扯一句,我们有没有办法,想孩童一样,无比好奇地,探索这个壮丽的世界?</p>
<p>####<a href="http://v.youku.com/v_show/id_XNDIyOTU3NTI0.html">Salman Khan:视频重塑教育</a>
这个视频的道理很好理解:你看到我的这篇文章,就能知道,额,30% 吧。</p>
<p>不要把这个视频当成简单的广告。可汗先生提到的功能太强大了:通过统计学生按暂停的位置,来统计课程中的难点。进而,对有困难的学生,提供更精准的帮助。</p>
<p>恩,老师们就要逆天了。</p>
<p>####<a href="http://v.youku.com/v_show/id_XNTIzNDY1NDYw.html">Robert Full:谈生物进化启发工程设计</a>
可能因为我对工程学不感兴趣,这个视频没给我留下太深的印象。只是最后,开发出的翻越障碍能力极强的机器人,真的要让人怀疑,多足生物近乎完美的腿部结构,背后,是否藏着上帝的智慧?</p>
<p><em>简单就整理了这么几个,大家尽情吐槽(优酷是不是得给我点广告费?)</em></p>
C语言的通用链表
2013-05-06T00:00:00+00:00
https://blog.zhenbo.pro/2013/05/06/c_list
<p>C++ 里的 STL 能方便的定义一个 <code class="language-plaintext highlighter-rouge">list</code> 类,而 C 语言里,没有对模板的支持,想要写出通用的库,难度显然要大不少。但可以肯定的是,一定有一个 <em>可靠</em> 的解决办法的。</p>
<p>在 Google 上搜索时,找到了一篇文章 <a href="http://www.cnblogs.com/wwang/archive/2010/11/28/1889281.html">玩转C链表</a>。这篇文章写的很好,但我第一眼看的时候,却没找到入手点。琢磨了一个下午,总算有了一点思路。</p>
<p>相比于传统的链表</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">int_node</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">val</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">int_node</span> <span class="o">*</span><span class="n">next</span><span class="p">,</span> <span class="o">*</span><span class="n">prev</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>
<p>Linux Kernel 把两个指针拆了出来。</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">list_head</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">next</span><span class="p">,</span> <span class="o">*</span><span class="n">prev</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">struct</span> <span class="n">int_node</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">val</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">list_head</span> <span class="n">list</span><span class="p">;</span>
<span class="p">};</span></code></pre></figure>
<p>这样,对链表的操作,可以只针对于 <code class="language-plaintext highlighter-rouge">struct list_head</code>,而有了通用的写法。下一步的问题,就是由 <code class="language-plaintext highlighter-rouge">struct list_head</code> 来获得 <code class="language-plaintext highlighter-rouge">struct int_node</code> 中存储的数据 <code class="language-plaintext highlighter-rouge">val</code>。</p>
<p>因为结构体的数据在内存中是连续的,所以,可以用指针的偏移量来存取 val。内核中有这样的代码</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cm">/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/</span>
<span class="cp">#define container_of(ptr, type, member) (type *)((char *)ptr -offsetof(type,member))</span></code></pre></figure>
<p>传入的三个参数是:指向当前的 <code class="language-plaintext highlighter-rouge">struct list_head</code> 的指针,整个容器的类型(本例中 <code class="language-plaintext highlighter-rouge">struct int_node</code>),和 <code class="language-plaintext highlighter-rouge">list_head</code> 的名称(本例中 <code class="language-plaintext highlighter-rouge">list</code>)。而这个宏所返回的,就是指向整个容器的指针(本例中 <code class="language-plaintext highlighter-rouge">struct int_node *</code>)。</p>
<p>现在,就要求偏移量 <code class="language-plaintext highlighter-rouge">offsetof</code> 了。kernel 中的实现很巧妙,但要实现这个功能方法不少(比如 <code class="language-plaintext highlighter-rouge">sizeof(struct int_node) - sizeof(struct list_head)</code>)。至于哪种方法更好。。。我觉得这个世界上能写出比 Linux Kernel 质量更好的代码的人为数不多吧。</p>
<p>解决了这些,插入和删除的操作就难度不大了(参数类型都是 <code class="language-plaintext highlighter-rouge">struct list_head *</code>)。略有不足的是,内存分配与回收的细节没有被隐藏起来。不过总的来说,通用链表的问题,算是比较优雅的解决了吧。</p>
几个让人头疼的代码习惯
2013-04-04T00:00:00+00:00
https://blog.zhenbo.pro/2013/04/04/c-plusplus-code-quality
<p>之前,我写代码并不是很重视代码风格,觉得自己看着舒服就好。毕竟,自己写的解题报告不多,看过的代码也很少。这两天跟同学一起写小游戏,花了心思去看别人的代码,才意识到,不好的习惯,很让人头疼。</p>
<h2 id="难看的表达式">难看的表达式</h2>
<p><em>C/C++</em> 里的表达式是相当灵活了,但灵活是有代价的,包括让人难以阅读(尤其是阅读者的水平也是半斤八两)。个人的经验,有这几种常见情况。</p>
<h4 id="不加空格的习惯">不加空格的习惯</h4>
<p>例如这句代码 <code class="language-plaintext highlighter-rouge">if(k1<k2||!pos) pos=i;</code> 这是我在 2011 年的时候写的,看着不舒服吧?后来经人介绍,在 [清澄][1] 上刷了几道水题,被 [风格分][2] 蹂躏了一顿,就养成了加空格的习惯。(当然,不要迷信风格分)
[1]: http://www.tsinsen.com/
[2]: http://www.tsinsen.com/Help.page#ss</p>
<h4 id="太复杂的表达式">太复杂的表达式</h4>
<p>我承认,我阅读代码的能力很差。但写代码的时候,也不能要求每个人都是天才。例如</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for(i=(dy>0?floor((j-now.x)*df):ceil((j-now.x)*df))+now.y;(i!=(dy<0?floor((j-now.x+dx)*df):ceil((j-now.x+dx)*df))+now.y);i+=dy)
</code></pre></div></div>
<p>一个很 <em>简单</em> 的 for 循环,我觉得,我看不懂,主要的责任并不在我。</p>
<h4 id="不合适的技巧">不合适的技巧</h4>
<p><code class="language-plaintext highlighter-rouge">current_player=++current_player%player_num;</code> 在编译这句话的时候,gcc 给出了 warning。虽然巧用 <code class="language-plaintext highlighter-rouge">++</code> 能让代码简洁不少,但以我这个半吊子水平看来,在这里多写两句话,不是什么坏事。</p>
<h2 id="太长的行">太长的行</h2>
<p>这个问题其实在上面已经涉及到了。每行 80 的限制已经不适应现在的情况,但避免让某一行的代码太复杂,还是一个值得遵守的习惯。</p>
<h2 id="神秘常量">神秘常量</h2>
<p>C 语言里的 <code class="language-plaintext highlighter-rouge">define</code> 和 <code class="language-plaintext highlighter-rouge">const</code> 很方便,可以很好的回避这样的情况</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> if(temp==0xff00A2E8) plyr[player-1].check_1();
if(temp==0xffED1C24) plyr[player-1].check_2();
if(temp==0xffB5E61D) plyr[player-1].bypass();
</code></pre></div></div>
<p>第一眼看,这三个常量就像是天书一样。</p>
<h2 id="区分类名与变量名">区分类名与变量名</h2>
<p>还是看一个例子 <code class="language-plaintext highlighter-rouge">player ply[4];</code> 有的编辑器(比如 Geany)能高亮用户定义的类名。但当代码长了,维护起来的单独就大了。如果规模小,也不必严格遵守大工程里的命名原则。把首字母大写,很多问题都能得到解决。</p>
<p>说了一点点,但要承认,对于代码风格,我也是一知半解。如果有哪里说的不对,也恳请大家指正。</p>
<p style="margin-bottom: 0cm"><FONT COLOR="#cccccc">我会说我是看了<FONT FACE="Liberation Serif, serif"><FONT SIZE="3"><span lang="en-US">SEO</span></FONT></FONT>介绍“网站要保持更新频率”才写着篇文章凑数的?</FONT></p>
Fedora 下安装 mingw32 以交叉编译
2013-02-05T00:00:00+00:00
https://blog.zhenbo.pro/2013/02/05/fedora--mingw32-
<p>Qian Hong 在 <a href="http://www.freelists.org/list/wine-zh">wine-zh 邮件列表</a> 里发表了<a href="http://www.freelists.org/post/wine-zh/-WIne">分享: 如何入门 Wine 的开发调试?</a>,提到了要尝试使用 mingw 交叉编译代码为 exe,然后尝试 <a href="http://wiki.winehq.org/DebugChannels">debug channel</a>。兜了一个圈子,总算在我的 Fedora 16上装好了 mingw.</p>
<p>首先,要去 <a href="https://apps.fedoraproject.org/packages/">fedora packages</a> 搜索 mingw 的包。搜完以后,把看着舒服的装上吧(我不太清楚哪些必要就装了一大堆。。。反正 mingw-gcc 肯定是必要的)。</p>
<p>装好后,我尝试输入 <code class="language-plaintext highlighter-rouge">mingw</code>, <code class="language-plaintext highlighter-rouge">mingw32</code>, <code class="language-plaintext highlighter-rouge">gcc-mingw</code> 等,但都提示“找不到命令”。最后解决的方法很简单: 输入 <code class="language-plaintext highlighter-rouge">ls /usr/bin | grep mingw</code> , 在里面找到需要的命令就可以了。在我的电脑上,就是 <code class="language-plaintext highlighter-rouge">i686-pc-mingw32-gcc</code>。</p>
<p>接着,就是 Hello World 的时间了:</p>
<p>$ i686-pc-mingw32-gcc hello.c -o hello.exe</p>
<p>$ wine hello.exe</p>
<p>Hello, MingW!</p>
<font size="0" color="#F3F3F3">距上一次写日志已经很久很久了吧。。。</font>
C语言生成随机数的小实验
2013-01-30T00:00:00+00:00
https://blog.zhenbo.pro/2013/01/30/crand
<p>C语言的随机函数 <code class="language-plaintext highlighter-rouge">rand()</code>, 理论上会在[1, RAND_MAX]之间生成均匀的随机数。但通常情况下,我们需要一个范围很小的随机数(例如随机化快排)。</p>
<p>多数人的做法(包括之前的我),直接采用 <code class="language-plaintext highlighter-rouge">rand() % N</code> 的做法。但根据 <a href="http://c-faq.com/lib/rand.html">C-faq 13.15</a> 的介绍,这并不是一个好方法。相比之下,提供了另外两种方案:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">(int)((double)rand() / ((double)RAND_MAX + 1) * N)</code></li>
<li><code class="language-plaintext highlighter-rouge">rand() / (RAND_MAX / N + 1)</code></li>
</ol>
<p>为了验证哪种方式生成的随机数更均匀,我就打算做一个小小的测试。</p>
<p>实验步骤:</p>
<ol>
<li>使用<code class="language-plaintext highlighter-rouge">rand()</code> 生成 10^7 个随机数。</li>
<li>对于每个随机数,分别带入3个函数中来生成随机数,并将结果输出到文件中。</li>
<li>使用R语言,利用 <code class="language-plaintext highlighter-rouge">hist(x, breaks=range)</code> 来生成图像(感谢SHLUG上的朋友们)。</li>
</ol>
<p>注:使用 <code class="language-plaintext highlighter-rouge">rand()%N</code> 方案生成的随机数被存储到了向量 r0 中,另外两种被存储到了 r1 和 r2 中。经过测试, r1 与 r2 绝大多数元素都是一样的。所以,接下来我仅对比 r0 和 r1</p>
<p>我把范围设到了5000,最后统计的结果让我很震惊:r0 和 r1 的频率分布都很均匀</p>
<p><img src="/images/c-lang/rand-a-r0.jpg" alt="a0" />
<img src="/images/c-lang/rand-a-r1.jpg" alt="a1" /></p>
<p>考虑到可能由于数据量太大抹掉了一些区别,我将数据范围缩减至 10^4</p>
<p><img src="/images/c-lang/rand-b-r0.jpg" alt="b0" />
<img src="/images/c-lang/rand-b-r1.jpg" alt="b1" /></p>
<p>依旧看不到明显的区别。
我将随机数范围缩减到 <code class="language-plaintext highlighter-rouge">[0,100)</code></p>
<p><img src="/images/c-lang/rand-c-r0.jpg" alt="c0" />
<img src="/images/c-lang/rand-c-r1.jpg" alt="c1" /></p>
<p>直观上看,仍然没有本质上的区别。
在 <a href="http://c-faq.com/lib/notveryrand.html">C-faq 13.18</a> 上提到,使用 <code class="language-plaintext highlighter-rouge">rand() % N</code> 会让随机数具有周期性。但是我将范围设置为2,也没有找到明显的周期。</p>
<p>由此看来,<code class="language-plaintext highlighter-rouge">rand() % N</code> 并没有 <em>显著</em> 的缺陷。但为什么除了 C-FAQ,<a href="http://book.douban.com/subject/1148265/">C语言的科学与艺术</a> 也这么说呢?看来,找寻答案的道路还很漫长啊。</p>
<p style="margin-bottom: 0cm"><FONT COLOR="#e6e6e6">折腾这么久得到这样的一个结论,实在是太让人沮丧了。。。</FONT></p>
重学quicksort
2013-01-20T00:00:00+00:00
https://blog.zhenbo.pro/2013/01/20/quicksort
<p>前两天写一道水题,想尝试一下手写快排,可没想到花了一个多小时都没有搞定。看来, <strong>The devil is in the detail</strong> ,细节的地方埋藏了许多知识盲点。看来,有的草,是迟早要花时间除的。</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">void</span> <span class="nf">quicksort</span><span class="p">(</span><span class="kt">int</span> <span class="n">A</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">p</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">p</span> <span class="o">>=</span> <span class="n">r</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">q</span><span class="p">;</span>
<span class="n">q</span> <span class="o">=</span> <span class="n">partition</span><span class="p">(</span><span class="n">A</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="n">quicksort</span><span class="p">(</span><span class="n">A</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">q</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">quicksort</span><span class="p">(</span><span class="n">A</span><span class="p">,</span> <span class="n">q</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>为了保证 O(nlgn)的复杂度,partition 需要达到O(n)的复杂度。算法导论上,提供了这样一种实现</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">int</span> <span class="nf">partition</span><span class="p">(</span><span class="kt">int</span> <span class="n">A</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">p</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">,</span> <span class="n">x</span><span class="p">;</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">A</span><span class="p">[</span><span class="n">r</span><span class="p">];</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">p</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">j</span> <span class="o">=</span> <span class="n">p</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="n">r</span><span class="p">;</span> <span class="o">++</span><span class="n">j</span><span class="p">){</span>
<span class="k">if</span> <span class="p">(</span><span class="n">A</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o"><=</span> <span class="n">x</span><span class="p">){</span>
<span class="o">++</span><span class="n">i</span><span class="p">;</span>
<span class="n">SWAP</span><span class="p">(</span><span class="n">A</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">A</span><span class="p">[</span><span class="n">j</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">SWAP</span><span class="p">(</span><span class="n">A</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">],</span> <span class="n">A</span><span class="p">[</span><span class="n">r</span><span class="p">]);</span>
<span class="k">return</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>这个算法可以这样理解:</p>
<ol>
<li>选取主元</li>
<li>让 <code class="language-plaintext highlighter-rouge">j</code> 扫描一遍 <code class="language-plaintext highlighter-rouge">A[p, r - 1]</code> ,找出所有比 <code class="language-plaintext highlighter-rouge">x</code> 小的元素。这样,当循环结束时,<code class="language-plaintext highlighter-rouge">A[p, i]</code> 中存放的元素都不会比主元大。通过 <code class="language-plaintext highlighter-rouge">SWAP(A[i+1], A[r])</code> ,把主元交换到了 <code class="language-plaintext highlighter-rouge">i+1</code> 的位置。
显然,<code class="language-plaintext highlighter-rouge">partition()</code> 过程为线性复杂度。我们有个一个不到20行的高效的排序算法。</li>
</ol>
<p>但在我重学快排时,看到了[v_JULY_v][1] 写的 [快速排序算法的深入分析][2] ,觉得自己正如他所说
[1]: http://my.csdn.net/v_july_v/message
[2]: http://blog.csdn.net/v_july_v/article/details/6211155</p>
<blockquote>
<p>只知其表,不知其里,只知其用,不知其本质。很多东西,都是可以从本质看本质的。而大部分人没有做到这一点。从而看了又忘,忘了再看,如此,在对知识的一次一次重复记忆中,始终未能透析本质,从而形成不好的循环。
看来,除草之路漫漫啊。</p>
</blockquote>
hello world
2013-01-17T00:00:00+00:00
https://blog.zhenbo.pro/2013/01/17/hello-world
<p>折腾了将近一周,总算把jekyll架了起来。要是一开始就老老实实照着Jekyll Bootstrap做,就费不了这么多事吧。。。</p>
<p>现在看来,我是得学markdown了。看了一下语法,也不是很复杂。但不大算花太块时间去学了。从现在开始,争取每周更新一篇文章,在写博客的过程中学习markdown吧。</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include</span> <span class="cpf"><stdio.h></span><span class="cp">
</span><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Hello, Jekyll World!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>