Jekyll2023-01-27T09:01:53+00:00https://judes.me/feed.xmljude’s life生活,读书,编程
chong yun丈夫届2022-12-22T10:23:56+00:002022-12-22T10:23:56+00:00https://judes.me/reading/2022/12/22/jyoubu-todoke<p>江户幕府取得天下之后,为了稳固政权,想要削弱地方势力.</p>
<p>为此它推出一系列政策,其中之一是:封臣的家族继承人如果在 17 岁之前死亡,这个家族就会被视为绝后,其封地、待遇、家宅都会被幕府没收.</p>
<p>本来封臣的家族要是有男丁诞生,都会在出生时上报公家朝廷.因为婴儿夭折率非常高,这个政策一出来,封臣就不在出生时上报,而是等到婴儿长成小孩看上去能活下去的时候(有推迟到 5 岁的情况)上报.</p>
<p>上报的时候,在解释时也毫不掩饰,给出的理由就是刚出生的时候很虚弱,所以耽误了上报,现在身体变健壮了,所以上报.</p>
<p>连上报文书的标题也由出生届改成丈夫届.(届的意思是 通知, 丈夫的意思是 稳固/没问题)</p>
<p>除了延报,也有虚假申报年龄的.为了让继承人尽快到达 17 岁,上报的年龄比实际年龄大两三岁是常有的事,偶尔还有多报十岁的.</p>
<p>虚假申报年龄甚至已经成为所有人都知道的惯例.</p>
<p>人们将上报的年龄称为官年(官方年龄),并不把它当真,在结婚做媒时,媒人会问对方的真实出生年龄.</p>
<p>延报和虚报都还算好的,很多出生不久就夭折的孩子,因为没有上报,根本就不存在于官方的记录里.</p>
<p>不但隐瞒出生,有时候也隐瞒死亡.官方规定 17 岁之后才能收养子,有了养子就不会绝后.要是将要 17 岁的时候不幸死亡,家族只要隐瞒死亡一段时间,赶紧找养子,之后再上报死亡就能避免剥夺一切.</p>
<p>可能会有人问,为什么如此重大的造假,没有败露,反而成为惯例呢?</p>
<p>因为不只封臣的家族,封臣的家臣也同样适用这条规定.几乎所有涉及到的人都在同一个政策的支配下,大家都有潜在的造假需求.</p>
<p>作为幕府的话事人,将军也知道这一套行事以及背后的原理.每次召见封臣之后,如果对他们的年龄有所不满,也只是开玩笑说现在的人看起来长得真年轻啊!</p>chong yun17 岁带来的一系列问题试着讲逻辑(二)2022-07-22T07:20:28+00:002022-07-22T07:20:28+00:00https://judes.me/life/2022/07/22/logic-series-2<p>这个系列的标题“试着讲逻辑”,有两个意思,首先是我试着向读者讲解逻辑,然后是希望读者能试着像“说话讲道理”一样“讲逻辑”,运用逻辑去分析别人说的话。</p>
<p>出于以上两个目的,我不打算在逻辑的学术定义、细节上面有太多讲究(我也做不到),也希望读者记住尽量少一点的东西,不管记住多少,最重要的是尽量去用逻辑,因为逻辑不是一门应试科目,而是一个有力的工具,光记住而不去用,除了浪费时间,没有别的好处。</p>
<p>如果用一句话去概括逻辑,那就是:它是判断一个人的推理是否正确的科学。</p>
<p>它并不是用来证明一个人的最终目标是否正确,它只是用来判断一个人为了到达最终目标所走的路径是否正确。</p>
<p>整个路径能在很多地方出错,比如出发点错了、走的大方向不对、路径中间断开,或者不断地绕圈等等。</p>
<p>为了以后的表达方便和方便读者翻查对照其他资料,虽然有点对不住读者,但我要先定义几个术语(括号里是英文术语):</p>
<p>首先是陈述(statements),陈述是可以判断其是否正确的(true/false)表述,比如:</p>
<blockquote>
<p>我刚刚吃了午饭。</p>
</blockquote>
<p>如果我刚吃了午饭,那上述就是正确的(true),如果事实上我没吃午饭,那上述就是错误的(false)。</p>
<p>不是所有的表述都能判断正确与否,有些表述本身就没有正确与错误之分,比如疑问句、祈使句、感叹句:</p>
<ul>
<li>你吃饭了没?</li>
<li>多点菜吧!</li>
<li>明天要是放假就好了。</li>
</ul>
<p>学习运用逻辑的第一步,就是要学会分辨出哪些表述是陈述(statements)。</p>
<p>通常人们会用好几个陈述作为前提(premises),去试图推理出作为结论的新的陈述(coclusion)。我们把这个行为称为论证(arguments)。举例来说:</p>
<ul>
<li>陈述一:所有水果都是植物</li>
<li>陈述二:所有雪梨都是水果</li>
<li>以上面两个陈述为前提,推理出新陈述:所有雪梨都是植物</li>
</ul>
<p>学习运用逻辑的第二步,就是要找出论证。</p>
<p>运用逻辑的第三步,就在于分析论证的结构,找出、分清哪些部分是前提、哪些部分是结论。</p>
<p>至于怎样找,有个诀窍是先找结论.可以通过文章中的一些关键字,如:因此、可以得出、也就是说……来定位结论,前提会出现在结论的前面。</p>
<p>在复杂的论证中,从最初的前提推理出来的结论往往只是论证的中点,以中间结论为前提,多个中间结论可以推理出最终的结论。</p>
<p>这样整个论证就像一颗大树,从上往下看,前提就像叶子,中间的结论就像普通的树枝,这些中间结论最终汇聚成作为最终结论的树干。</p>
<p>复习一下,逻辑是用来判断一个人的推理是否正确的科学。要判断一个人的推理是否正确,首先要判断他所说的是不是陈述(statements);如果他所说的能分出对错,接下来要看他是否有在论证(arguments),有没有提供前提,再基于前提得出结论;最后是仔细分清有哪些前提、有哪些中间结论、最终的结论又是什么。</p>
<p>篇外</p>
<p>评判陈述的标准其实有很多,比如是否容易理解、有没有文采等等,但“是否正确”对实际生活的影响最大,所以逻辑最终采用这个标准。</p>
<p>类似的评判论证的标准也有很多,比如整个论证是否清晰、有趣等等,但最重要的一点是前提与结论之间是否有紧密的联系。
两者之间最紧密的联系是前提的真与假直接决定了结论的真假(Necessity),次一级的联系为前提的真与假能相应地增加结论真假的可能性(Probability)。</p>chong yun试着用逻辑对抗荒谬的世界试着讲逻辑(一)2022-05-02T07:20:28+00:002022-05-02T07:20:28+00:00https://judes.me/life/2022/05/02/logic-series-1<p><strong>前言</strong></p>
<p>我打算把关于逻辑的文章写成一个系列,开头是逻辑的理论学习,接下来是结合时事的运用。</p>
<p>不定期更新。</p>
<p><strong>正文</strong></p>
<p>《秦制两千年》里有一句话:</p>
<blockquote>
<p>韩非子的解释是“远仁义,去智能”,具体说来就是一手控制资讯,一手灌输错误逻辑,二者结合可以做到无往不利。</p>
</blockquote>
<p>韩非子是个伟大的预言家,在两千多年后的中文世界,有司的所作所为不正是“一手控制资讯,一手灌输错误逻辑”吗?</p>
<p>而且在大多数情况下,二者结合确实无往不利。</p>
<p>他们控制资讯的形式多种多样:用行政命令打压不同声音、阻断网络服务、有选择地报道新闻,最新甚至还歪曲外国报道的原文(比如将 unacceptable 翻译成 可接受)。</p>
<p>普通人需要掌握一定的外语能力(英语)才能绕过这些控制。而据我个人感受,英语只达到六级考试的水平是不够的,要有一万左右的词汇量才能通畅读懂英文报道。</p>
<p>而外语能力过硬只是第一道难关。很多国外的信源被阻断访问,普通人访问会走进法律的灰色地带,要是被抓了,已经有现成的罪名。</p>
<p>这两道难关大大提高了接触全面资讯的成本,理性的人会考虑到收益不大,而直接放弃尝试。</p>
<p>他们一手控制着资讯,另一手操纵手上的资讯达到想要的宣传效果,整个过程概括来说就是用各种资讯作为前提、引导观众同意他们所给出的结论。</p>
<p>怎样由前提推理出结论,是一门科学,叫做逻辑。</p>
<p>很多时候,要推理出正确的结论,要有正确且与结论强相关的前提,如果前提不正确、或者前提跟结论无关、弱关联,就很难认定结论是否正确。</p>
<p>我们说这种推理才是符合逻辑的。</p>
<p>但很多人——无论身处哪个国家、受教育程度多少——都没有稍微认真地学习过逻辑,也很少运用逻辑去处理前提和结论。</p>
<p>有意误导普通人并且研究过个中门道的人很容易能依靠不相关的甚至本身就错误的前提,通过一些手法,把普通人引导到他想要的结论上面。</p>
<p>这些手法,概括起来就是错误的逻辑。</p>
<p>每天都有各种各样的资讯传到普通人的眼里、耳边,但它们都必须透过每个人已经习得的处理资讯的方式才能影响他们的内心。</p>
<p>如果人被灌输了太多错误的逻辑,以致他逐渐习惯并学会了用同样错误的逻辑处理新的资讯,那么就算将来有一天放开对资讯的管制,他也很难接受与自己已经习惯的结论不一样的事实。</p>
<p>掌握了正确逻辑,无论怎样的资讯,只要发现存在逻辑错误,最终的结论都很可疑。当你发现一部分人经常运用错误逻辑引导他人相信其所给出的结论,你就会倾向怀疑这部分人所说的任何一句话。</p>
<p>从以上这些角度来看,学习逻辑比掌握全面的资讯更为重要,也是更可行的对抗手段。</p>
<p>我自己曾经尝试过系统学习逻辑,但是好几次都中途放弃。</p>
<p>或许是因为我选择的教材不对,里面充满各种概念,概念又很快变成符号,我记性不好,很经常会看到后面忘记前面;又或许是因为我努力的方向不对,我学习的逻辑很系统、但是一点都不实用,就算全程啃下来,感觉也找不到用处。</p>
<p>学习逻辑非常重要的两点就是:明确学习的目的,并找到合适自己的教材。</p>
<p>前段时间,我看到这个<a href="http://www.fallacyfiles.org/whatarff.html">网站</a>,正如它的网址所写,它本身是用来记录谬误的。除了谬误之外,它还提供一系列逻辑的教程,课程的入口就在网站的左侧,这是<a href="http://www.fallacyfiles.org/archive012007.html#01122007">第一课</a>。</p>
<p>我学习一遍过后,觉得教程本身非常好,里面有基本够用的理论,以及非常实用的指南。</p>
<p>如果英语过关的话,推荐直接阅读。</p>
<p>因为学习最好的方式是去教授别人。接下来我会参照上述教程,用自己的方式再讲一遍。</p>chong yun试着用逻辑对抗荒谬的世界新旅程的第一步2021-08-19T07:20:28+00:002021-08-19T07:20:28+00:00https://judes.me/life/2021/08/19/remote-working-1<p>很早就想写写最近的生活。</p>
<p>几个月前自己作了人生中一个重大的抉择,如标题所说,这将会开启一段新的人生旅程。</p>
<p>但是在自己甚至还没有踏出第一步,一切只有想像之前就对外说自己将要怎样怎样,总感觉不太好。</p>
<p>有句动漫梗叫做往自己身上插旗子,意思是在愿望实现之前就说出口,一定会失败的。我也担心自己会半途改变主意,或者会因为外界干预而放弃。</p>
<p>而在迈出第一步之后,又没有那个心思写,那时的心态就像一个失业大半年的中年大叔,看不到前方的路,一边怀疑自己,一边又在祈求上天眷顾。</p>
<p>在这种状态下,怎么好意思说自己心里那些美好的想像呢。</p>
<p>但如今已经算是站稳脚跟,能继续迈第二步了。我想这是个合适的时机写写那些路线,再晚了可能自己就淡忘了细节,再也提不起劲了。</p>
<p>而且我想这是一条少有人走的路,如果能留下一点坐标/指示,可能会对后来人有点作用。</p>
<p>如果你也有类似的想法,正犹豫要不要动身出发,不要害怕,虽说这是条羊肠小道,但绝对走得通的。</p>
<p>我说的是找一份国外的工作。</p>
<p>出国工作的目的,并不是想赚更多钱,而是跑出去,到国外生活。</p>
<p>记不清楚是什么时候因为什么而萌生要出国的想法。</p>
<p>在上大学之前,我还是个爱国青年。</p>
<p>上大学时逐渐发觉原来解释世界的理论并不止一直以来被灌输的那一套。感觉自己被骗了,而且那些骗你的人,他们自己也不相信那一套,只是他们找到了这套体系里自己能接受的位置。</p>
<p>就算是这样,那时我心里还想着绝对不让出这片土地给这些人。</p>
<p>后来出来工作,接受了社会的磨炼,年岁渐长血气渐减,书也读得更多了。越发有种感觉,传统是个很强大的力量,这片土地有它独特的长达两千年的思考、行事逻辑,个人妄想改变它,就跟蚍蜉撼树一样,不自量力只会换来被碾压的结局。</p>
<p>我改变不了别人,更不想改变自己。于是出路在哪里心里就有答案,只有跑路了。想来想去,日本是个合适的目标。</p>
<p>因为刚上大学就很喜欢看动漫和日本电视剧、电影,对日本有好感。</p>
<p>而且在四五年前自学了一点日语,目前大约 N3 到 N2 之间的水平。</p>
<p>(一想起当初没狠下心考证书就有点后悔,如今想要抢一个考试的座位都很难)</p>
<p>还去过两次日本自由行,亲身体验过在东京不用说一句话就能到处跑。如果工作不成问题的话,在大城市自己一个人能活下去。</p>
<p>我对日本真实生活、工作会遇到什么问题,心里也有准备,比如工作方面的加班多、年功序列等等,日常生活中的日本其实也没那么干净、安静等等。</p>
<p>至于怎样跑过去。如果我再年轻几岁,或许会选择去日本读研究生,以学生身份打工、学习日语、适应生活,再以毕业生的身份找工作。可惜啊,年龄不小了,再读书时间成本很大。</p>
<p>不过幸好自己干 IT 这一行,算是日本比较缺的职种,没准就算不会多少日语也能找到工作。</p>
<p>我不喜欢传统的日本公司,IT 算是不那么传统的行业。</p>
<p>综合考虑下来,最终瞄准的目标是在日本讲英语的 IT 公司。</p>
<p>在这个新冠大流行的时代,一个不怎么会日语的中国人、想通过远程的方式找一家在日本的讲英语的 IT 公司。是不是很异想天开的想法?</p>
<p>没错,让我作为第三者评价一下,我也会觉得过于不切实际。</p>
<p>不过推我往前走一步的动力,与其说是对未来美好的愿景,不如说是对现实、自己的不满。</p>
<p>出国这个想法,两年多前就越发清晰,我甚至会忍不住跟周围的同事说这是自己的梦想。但是两年快过去了,感觉一切还是停留在嘴上。</p>
<p>我越来越讨厌看上去只会打嘴炮的自己。我时常提醒自己,梦想就像一只蛋,如果久久不孵化,会发臭。</p>
<p>而真正触发我下决心行动的,是 19 年底爆发的疫情,如果说在此之前我还心存这个国家会变好的一点点希冀的话,在那之后就幻灭了。</p>
<p>一切都没有变。中国这辆车,它能向进开,也能往后退,普通人只是乘客而不是司机,什么都做不了,只能接受。</p>
<p>埋下头享受易脆的小日子。</p>
<p>思前想后,在 2020 年 8 月左右,我给自己订了个目标,一年内实现肉身翻墙。</p>
<p>当然了,一年后的今天我仍然在国内,这个目标算是没达成。</p>
<p>不过订目标的意义在于,有目标就可以将它分解成一个个的小步骤,设定相应的期限,在什么时间点该进行到什么程度心里就有底了。</p>
<p>最初我就觉得,自己面临最大的外部阻力,不是那些远离自己的疫情或者国家政治气候。而是家人。</p>
<p>无论怎样,都要先让家人知情,就算他们不同意,也要留出足够长的时间让他们接受。所以我设定了第一个小目标,在国庆回家的时候就跟家人坦白自己的打算。</p>
<p>那时我什么具体的计划都还没有,只是向他们透露自己未来的打算是移民出去。</p>
<p>我先是跟同辈沟通、再跟父母说。父亲能接受,但是母亲的反应跟我预料的差不多,强烈的反对。我没打算要说服她,对我来说,让她知道这件事将来会发生就已经达成目标了。</p>
<p>在跟家人通气之后,一直到 2021 年 3 月底,没有做其他事情。</p>
<p>那段时间我挺努力用心工作,拿下比较好的绩效,差一点就能升职了。拿好绩效、拿高一点的头衔,并不是想着要在这家公司、乃至在深圳长留,而是希望能把这些写进简历里,好找工作。</p>
<p>不过最终升职失败了,说实话,如果不是升职失败了,说不定我还会犹豫一下要不要在那个全球疫情爆发的节点辞职。</p>
<p>促成裸辞的另一个念头是自己想要釜底抽薪。我没有忘记那个一年内的目标,而半年快过去了,什么实质进展都没有。</p>
<p>提离职之后,上方领导们反应不一。顶头上司很支持我,甚至不是口头上支持,而是帮我联系他在东京的老同学,让他向我介绍那边疫情的情况、现在工作好不好找等等。</p>
<p>而上司的上司就觉得我的想法有实现难度,他想搞清楚我要走是不是因为晋升的问题,或许他能在这方面出力帮帮我。不过我很清楚他只是说说而已,有点后悔自己一瞬间跟他认真起来。</p>
<p>而上司的上司的上司,则花了不少时间解释我为什么没能升职,听下去是因为运气不好,真正原因是什么我已无所谓了。</p>
<p>跟人事离职谈话时,他说听过很多离职的理由,有回家当公务员的、有考研的、有出国读书的、有跳槽的,但是像我这种要出国工作的(而且还是离职后再找)非常少。</p>
<p>是的,我这么疯狂的人跟公司的同事都不在一个频道上。</p>
<p>提离职算是达成又一个小目标。之后要做些什么,那时列了一些项目,因为大目标是找日本的讲英语的 IT 工作,因此:</p>
<p>第一步要练习好英语口语,特别是面试时会用到的英语,比如介绍自己、比如描述代码的思路等等。</p>
<p>第二步是了解外国公司面试时考察的方向是什么,比如是跟中国公司一样考察项目、考察前端知识体系,还是纯看解题能力。</p>
<p>第三步是收集招聘信息,看看都有哪些招聘渠道。</p>
<p>第四步是准备好英文简历,英文简历的格式是什么都要了解一下。</p>
<p>还需要了解一下日本工作签证的要求、日本生活的具体注意事项。</p>
<p>我有预感,大概率在三个月内找不到工作,所以这段时间内怎样保持身心健康,也得考虑一下,比如作息时间、锻炼计划、保持开心要做点什么等等。</p>
<p>甚至还想过要不要考个证书、比如英语方面的托福,或者日语方面的 N2 。有一阵子我有点迷茫,好像有很多事情都应该做,它们都像是必需的,但我没有那么多时间精力。</p>
<p>好在我还记得那个大目标。</p>
<p>由此出发,订下了三个月拿下录用通知书这个小目标,在此基础上往前推一个月,也就是两个月内我就应该开始拿到面试邀请;再往前推一个月,我就应该做准备好面试要回答的问题、投简历。</p>
<p>于是很快我就总结出必须集中精力做的三件事:</p>
<ul>
<li>
<p>面试中会用到的英语口语。通过谷歌,我知道通常外国公司面试时会有一个叫做行为面试的环节,对方会问你很多方面的问题,总结下来,可能会有七八十个问题,这些问题不要说是用英语来回答,就算是由汉语来回答,你不事先准备一下也难回答得好。</p>
</li>
<li>
<p>外国公司面试程序员很简单、暴力。就是出题,像 leetcode 那种,做出来就能过,做不出来就淘汰。所以要刷足够多的题目,并且要能记住。</p>
</li>
<li>
<p>准备好英文简历以及收集招聘渠道</p>
</li>
</ul>
<p>练习口语,这个目标可能有点宽泛,就算具体到程序员面试中会用到的表达,也难以想像都会有些什么东西。我也是做了一番搜索和努力才找到点具体可行的东西。</p>
<p>一般外国公司面试时,会问一些行为类问题,这类问题用英语来说叫做 behavioural questions ,只要用 google 一搜 behavioural questions,就能看到很多人总结经验。你能收集很多个(大约 70 个)典型的行为类问题,以及回答的原则甚至回答范例。</p>
<p>我结合自身实际,写下自己的回答,再背下来,定期回顾一下,确保自己就算刚睡醒也能回答出来。</p>
<p>而在编程/工作中可能会用到的表达,我找到一个比较好的办法。就是去翻 Github 上热门的项目的 issues 列表,比如 React 。通常人们会在里面描述他们遇到的问题、尝试过的解决办法,而会有很多人出主意,最终问题解决之后还会有人表达感谢。这跟日常编程工作的场景很像,我把看到的有用的句式、表达抄下来,背熟。</p>
<p>你甚至可以去看项目的 code review 纪录,通常人们也会来回讨论很多方面的问题,也能学到很多英语关于疑问、担心、建议的表达方式。</p>
<p>而编程题,我刷 leetcode ,先按类型刷,比如数组、字符串、哈希表、堆栈、队列、链表、树、搜索、排序、回溯、贪心、动态规划。再按难度(从简单到困难)刷,一共刷了大约 160 题。</p>
<p>这 160 题里面,我能单独解决的可能不到 10 题,不过不用担心自己不会、继而怀疑这样做的意义。我相信这就跟读书考试一样,一个人只要智商正常、按照适合自己的方式学习,总会有很大收获的。</p>
<p>理由嘛,我是这样想的:</p>
<ol>
<li>大前提是面试题一般不会出竞赛级别的难度,</li>
<li>而且题目一眼能看出大约会用到什么技巧,也就是可以从分类入手,</li>
<li>只要各个分类的题目做多了,总会见过类似的题目。</li>
</ol>
<p>当然了,现在 leetcode 少说也有 2000 道题,只做了不到 10% 有点不太足够。</p>
<p>在面试时要是真没有思路,可以先用暴力解法、先解出来,再分析复杂度,然后再思考更好的解决,再不济也要向面试官讨点思路。</p>
<p>提到刷题,可能有人会有跟我一样的想法:明明这道题我做过,大体思路我也知道,但就是通过不了。</p>
<p>或是明明有印象做过但是记不起来大体思路了;或是知道大体思路,就是忘记了一些技巧;甚至代码都写出来了,就是跑不过测试用例,也不知道在哪里出问题。</p>
<p>你以为自己会了,但是实际动手写一遍,就在某处卡壳了。</p>
<p>今天会了,明天、后天就忘记了。</p>
<p>种种现象都表明复习刷过的题也很重要。</p>
<p>有思路、能写出代码、成功过关当然是最好的,没有思路、看了答案,记得要再默写一遍。</p>
<p>只默写一遍还不够,因为会忘,要定期复习。</p>
<p>具体方式可以参考背单词软件的思路,初期复习得频繁一点,如果都能写出正确的代码,复习的间隔就拉大一点,否则就重置复习间隔。</p>
<p>当我觉得为面试做准备做得差不多之后,就要为自己争取面试机会了。</p>
<p>首先要准备的是英文简历,跟中国的情况有所不同,外国还会要求附上求职信。</p>
<p>求职信的要点:</p>
<ul>
<li>首先要表明你符合公司要求且有相关能力、有信心胜任这个岗位</li>
<li>列举几点与公司文化符合的特性、技能</li>
<li>附上简历以及随时都可以联系的 email 地址</li>
<li>表达感谢并期望有后续联系</li>
</ul>
<p>接下来是收集各大招聘渠道,海投简历了。</p>
<p>招聘渠道有很多,比如平台: indeed/linkedin/stackoverflow ,国内也有一些远程工作平台,提供付费撮合劳资双方的服务,比如电鸭;也有一些邮件列表;你还可以关注一些自己专业内的 IM 群。</p>
<p>至于简历,最好有一份基础版本的简历,根据目标公司的具体要求,稍微修改一下再发出去。</p>
<p>海投简历很容易会忘了自己曾经投过哪家公司,为此可以用一个表格记录自己用什么简历投过什么公司、目前的进度是怎样了(被拒绝、约了面试、拿到录取)。</p>
<p>从你投简历到收到面试邀请之间会有一段时间,在此期间除了每天查看邮箱(特别要注意垃圾箱)之外,我觉得最好能找个外国人一起来一场模拟面试。只要用 developer mock interview 这个关键词去谷歌搜一下,就能找到好几个这样的网站,有付费的附加服务,也有免费的基础服务。</p>
<p>基础服务是两个将要参加真实面试的人轮流充当面试官面试对方。因为你的主要目的就是练习一下用英语向对方解释自己的解题思路,我觉得基础服务就够了。</p>
<p>在模拟面试中,能接触到来自全世界不同地方的人。只要在真正面试前多试几次,你就不用担心自己听不懂各种奇怪的口音了。</p>
<p>上面提到要每天查收一下邮箱,尤其注意一下垃圾箱。因为来自小公司的邮件,很容易会被 hotmail / gmail 认为是垃圾邮件过滤到垃圾箱里,我差点就因此丢掉两个面试机会。</p>
<p>说起面试,因为疫情关系,用 zoom 远程开会、面试的公司变得很多。</p>
<p>zoom 的免费服务在国内使用有限制,体现在不能在国内发起视频会议,但是除此之外并没有其他特别限制,比如你不需要翻墙就可以加入别人的会议,效果还不错的。</p>
<p>因为免费的 zoom 会议有 40 分钟时长的限制,可能有一些公司会选择用 google meeting ,在国内用 google meeting 毫无疑问是需要翻墙的,最好准备梯子。</p>
<p>最后,外国公司招聘节奏比较慢,从第一轮到最终给你录用通知,可能要花上一个月时间。</p>
<p>经过差不多三个月的努力,以及不少的运气,我最终拿到一家公司的录用。</p>
<p>战绩大约是,大半个月时间里投了 8 家公司,有一半的公司没理我,有两家第一轮出局,有两家到了最后一轮(纯粹就是跟老板闲聊),拿了其中一家的录用。</p>
<p>我入职的是一家在日本的做跨境生意的小公司,正社员的身份,但是因为疫情和办理工作签证需要很长一段时间,所以在动身去日本之前,会以 freelancer 的身份工作。</p>
<p>开发有 10 个人,分别来自好几个国家,开发的日常工作交流、开会用英语,跟其他部门的同事开会用日语。</p>
<p>目前开发都是远程工作,可能疫情过后也会是远程工作。技术栈是 AWS + salesforce + react ,整间公司的服务搭在 AWS 跟 salesforce 两大服务提供商上面,在加入这家公司之前,我从来没有想过公司的 IT 还能以这种方式运作。</p>
<p>我接触过的跟 IT 有点关系的公司,都在用开源工具加上自己的主机搭建自己的服务。</p>
<p>同事之间的关系比较简单,可能是因为远程工作的原因,听上级说,他跟另一个入职已经一年半的同事在线下只见过两次面。</p>
<p>公司里有大约十分之一是华人,而且上面提到的那个一年只跟上级见两次的同事也是中国人。</p>
<p>公司跟一般印象(日本 IT 守旧、水平低下,日本人工作狂、等级森严)不一样。</p>
<p>我上级是个日本人,但我觉得他技术不错,还组建一个讲英语的技术团队,这点很有创新的魄力。</p>
<p>在公司的 slack 群里,很少人(通常只有十分之一的人)会搭理 CEO 发的消息,搭理他的方式通常就是点个表情;在周末,除了 CEO 这个工作狂,没人会在线。</p>
<p>远程工作刚好也有两个月了,可能是自己的性格比较内向,喜欢独处,所以一点都感觉不到孤独。工作日每天都会开个 zoom 早会,能说上几句话,感觉说话量刚好,不至于连怎么说话都忘了。</p>
<p>工作内容有简单的,也有难的,除了 react ,其他东西之前都没有接触过,比如 Apex 这门类似 java 的语言。</p>
<p>公司的业务不大不小,有些功能涉及到整条业务流程,不了解清楚就容易出问题。而且开发人少,没有专职的测试人员,基本要自己开发、测试(部署的话由 circle-ci pineline 处理)。</p>
<p>公司也比较透明,比如会每个月开个不到 20 分钟的会公布一下业绩,跟预期的差距以及接下来一个朋的工作大方向和目标等等。</p>
<p>总的来说,这家公司有点意思,能学到不少东西(技术、语言、商业逻辑)。</p>
<p>站在当下的立足点,回头看走过来的路、当初自己订跑路目标和实现方式时,都觉得很大胆,不切实际。</p>
<p>但走过来才知道,很多看似不可能的事,也没多大难度。它的困难就在于迈出第一步。</p>
<p>坚持走下去,可能接下来会遇到很多跟预想不一样的事,有新的发现,或是走了岔路。</p>
<p>但就跟爬一座很高的山一样,只要时不时抬头看看山顶,再埋头朝它走路,总会到达目的地。</p>
<p>现在我还没正式跑出去,如果一切顺利,今年年底应该能成。到时候再写一篇吧。</p>chong yun我时常提醒自己,梦想就像一只蛋,如果久久不孵化,会发臭。马拉车2021-06-11T10:20:28+00:002021-06-11T10:20:28+00:00https://judes.me/tech/2021/06/11/manacher-algorithm<p>马拉车是一个能在线性时间复杂内计算字符串的最长回文子串长度的算法。</p>
<p>回文字符串是指从左往右读跟从右往左读都一样的字符串,比如 <code class="language-plaintext highlighter-rouge">aba</code>,<code class="language-plaintext highlighter-rouge">cddc</code>。</p>
<p>要算出一个字符串里最长回文子串的长度,我们要先知道怎样找出一个回文子串。</p>
<p>以 <code class="language-plaintext highlighter-rouge">aba</code>,<code class="language-plaintext highlighter-rouge">cddc</code> 为例,可以看到回文中的字符,一定是以某个位置为中心,左右对称的。</p>
<p>假设 <code class="language-plaintext highlighter-rouge">str</code> 是回文字符串, <code class="language-plaintext highlighter-rouge">mid</code> 是回文中心,<code class="language-plaintext highlighter-rouge">i</code> 是大于 0 且不大于 <code class="language-plaintext highlighter-rouge">mid</code> 的整数。</p>
<p>对每一个 <code class="language-plaintext highlighter-rouge">i</code> 会有:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">str</span><span class="p">[</span><span class="nx">mid</span> <span class="o">+</span> <span class="nx">i</span><span class="p">]</span> <span class="o">===</span> <span class="nx">str</span><span class="p">[</span><span class="nx">mid</span> <span class="o">-</span> <span class="nx">i</span><span class="p">]</span>
</code></pre></div></div>
<p>只是这个中心与回文字符串的长度是奇数还是偶数有关。</p>
<p>如果长度是奇数(len),那这个中心就是 <code class="language-plaintext highlighter-rouge">Math.floor(len / 2)</code>,上面的等式改写为:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">str</span><span class="p">[</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">len</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="nx">i</span><span class="p">]</span> <span class="o">===</span> <span class="nx">str</span><span class="p">[</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">len</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="o">-</span> <span class="nx">i</span><span class="p">]</span>
</code></pre></div></div>
<p>如果长度是偶数(len),中心会有两个,分别是 <code class="language-plaintext highlighter-rouge">len / 2</code> 和 <code class="language-plaintext highlighter-rouge">len / 2 - 1</code> ,这时上面的等式改写为:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nx">str</span><span class="p">[</span><span class="nx">len</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">+</span> <span class="nx">i</span><span class="p">]</span> <span class="o">===</span> <span class="nx">str</span><span class="p">[</span><span class="nx">len</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">-</span> <span class="nx">i</span><span class="p">]</span>
</code></pre></div></div>
<p>求一个字符串里最长回文子串的长度的一般解法,就是遍历字符串,假设当前正在遍历的字符串位置是回文中心,尝试对比以它为中心左右对称的字符,如果它们相等,回文子串长度增加 2 ,否则移动回文中心到下一个位置。</p>
<p>每一个字符串位置有可能是奇数回文子串的中心,也有可能是偶数回文子串靠左边的中心。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">const</span> <span class="nx">find</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">str</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">max</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">const</span> <span class="nx">findOdd</span> <span class="o">=</span> <span class="p">(</span><span class="nx">mid</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">1</span>
<span class="kd">let</span> <span class="nx">lenOdd</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">while</span><span class="p">(</span><span class="nx">mid</span> <span class="o">-</span> <span class="nx">j</span> <span class="o">>=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">mid</span> <span class="o">+</span> <span class="nx">j</span> <span class="o"><</span> <span class="nx">len</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">mid</span> <span class="o">-</span> <span class="nx">j</span><span class="p">]</span> <span class="o">===</span> <span class="nx">str</span><span class="p">[</span><span class="nx">mid</span> <span class="o">+</span> <span class="nx">j</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">lenOdd</span> <span class="o">+=</span> <span class="mi">2</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">j</span><span class="o">++</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">lenOdd</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">findEven</span> <span class="o">=</span> <span class="p">(</span><span class="nx">mid</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">lenEven</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">let</span> <span class="nx">left</span> <span class="o">=</span> <span class="nx">mid</span>
<span class="kd">let</span> <span class="nx">right</span> <span class="o">=</span> <span class="nx">mid</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">while</span><span class="p">(</span><span class="nx">left</span> <span class="o">>=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">right</span> <span class="o"><</span> <span class="nx">len</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">left</span><span class="p">]</span> <span class="o">===</span> <span class="nx">str</span><span class="p">[</span><span class="nx">right</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">lenEven</span> <span class="o">+=</span> <span class="mi">2</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">left</span><span class="o">--</span>
<span class="nx">right</span><span class="o">++</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">lenEven</span>
<span class="p">}</span>
<span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">max</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="nx">max</span><span class="p">,</span> <span class="nx">findOdd</span><span class="p">(</span><span class="nx">i</span><span class="p">),</span> <span class="nx">findEven</span><span class="p">(</span><span class="nx">i</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">max</span>
<span class="p">}</span>
</code></pre></div></div>
<p>可以看到这种解法有两重循环,所以时间复杂度是 O(n^2),虽然不算太高,但是也不低了。</p>
<p>抛开时间复杂度,单说算法实现时要分别处理两种情况,就有点复杂。有没有一种处理方式,可以屏蔽字符串长度的奇偶差异,然后用同一种算法处理呢?比如把字符串长度统一成奇数(或者偶数)再处理?</p>
<p>还真的有。我们可以用一个相同的字符分隔原字符串中各个字符,再往一头一尾放进这个字符。</p>
<p>这个字符是什么不重要,下文以 <code class="language-plaintext highlighter-rouge">#</code> 为例。</p>
<p>如果原字符串长度为奇数,因为会多出偶数个新字符,奇数与偶数相加,处理之后还是奇数。如 <code class="language-plaintext highlighter-rouge">aba</code> 变为 <code class="language-plaintext highlighter-rouge">#a#b#a#</code></p>
<p>如果原字符串长度为偶数,因为会多出奇数个新字符,偶数与奇数相加,处理之后是奇数。如 <code class="language-plaintext highlighter-rouge">cddc</code> 变为 <code class="language-plaintext highlighter-rouge">#c#d#d#c#</code></p>
<p>这样处理并不会将一个原本是回文的字符串变成不回文,只会增大回文长度到原来的两倍再加一,而且回文中心一定是在奇数位置。这样算法可以改写为:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">const</span> <span class="nx">find</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">str</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">max</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">newStr</span> <span class="o">=</span> <span class="p">[</span><span class="dl">'</span><span class="s1">#</span><span class="dl">'</span><span class="p">]</span>
<span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">newStr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
<span class="nx">newStr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">'</span><span class="s1">#</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">len</span> <span class="o">=</span> <span class="nx">newStr</span><span class="p">.</span><span class="nx">length</span>
<span class="kd">const</span> <span class="nx">findOdd</span> <span class="o">=</span> <span class="p">(</span><span class="nx">mid</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">1</span>
<span class="kd">let</span> <span class="nx">lenOdd</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">while</span><span class="p">(</span><span class="nx">mid</span> <span class="o">-</span> <span class="nx">j</span> <span class="o">>=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">mid</span> <span class="o">+</span> <span class="nx">j</span> <span class="o"><</span> <span class="nx">len</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">newStr</span><span class="p">[</span><span class="nx">mid</span> <span class="o">-</span> <span class="nx">j</span><span class="p">]</span> <span class="o">===</span> <span class="nx">newStr</span><span class="p">[</span><span class="nx">mid</span> <span class="o">+</span> <span class="nx">j</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">lenOdd</span> <span class="o">+=</span> <span class="mi">2</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">j</span><span class="o">++</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">lenOdd</span>
<span class="p">}</span>
<span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">max</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="nx">max</span><span class="p">,</span> <span class="nx">findOdd</span><span class="p">(</span><span class="nx">i</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">max</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这样处理后,算法还是有两重循环,时间复杂度还是 O(n^2),跟前一个算法相比,只是少写了几行代码。</p>
<p>很多时候,算法时间复杂度高是因为我们做了很多重复计算,如果高复杂度真的是因为这种情况,就可以用额外的空间记录之前的计算结果,避免重复计算。</p>
<p>说到底就是以空间换时间。</p>
<p>我们可以看看在上面的算法有没有重复计算。</p>
<p>以 <code class="language-plaintext highlighter-rouge">#a#b#a#</code> 为例,</p>
<p>我们的算法计算以第一个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文长度,以 <code class="language-plaintext highlighter-rouge">b</code> 为中心的回文长度,最后是第二个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文长度。最终选出其中最长的一个。</p>
<p>其实以第二个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文长度可以不用计算,可直接由前面两个结果以及自身的位置推算出来。(只是非常难想到)</p>
<p>原因在于两个 <code class="language-plaintext highlighter-rouge">a</code> 的回文子串都包含在 <code class="language-plaintext highlighter-rouge">b</code> 的回文子串之内,而且刚好是以 <code class="language-plaintext highlighter-rouge">b</code> 为中心左右对称。</p>
<p>假设我们能找到一个回文子串,并且知道所有位于这个回文子串中心左侧字串的回文信息,那么位于其右侧字串的回文信息就可以推算出来。</p>
<p>这里分两种情况。</p>
<p>情况一如下图,如果以第一个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串的最左侧的字符索引大于(或等于)以 <code class="language-plaintext highlighter-rouge">b</code> 为中心的回文子串的最左侧字符索引,也就是说以第一个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串长度小于(或等于)以 <code class="language-plaintext highlighter-rouge">b</code> 为中心的回文子串长度的一半减一,那么以第二个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串长度就等于以第一个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串长度。</p>
<p><img src="https://judes.me/assets/manacher_1.jpg" alt="manacher_1" /><em>manacher situation 1</em></p>
<p>情况二如下图,如果以第一个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串的最左侧的字符索引小于以 <code class="language-plaintext highlighter-rouge">b</code> 为中心的回文子串的最左侧字符索引,也就是说以第一个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串长度大于以 <code class="language-plaintext highlighter-rouge">b</code> 为中心的回文子串长度的一半减一。</p>
<p><img src="https://judes.me/assets/manacher_2.jpg" alt="manacher_2" /><em>manacher situation 2</em></p>
<p>那么以第二个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串长度就等于第一个 <code class="language-plaintext highlighter-rouge">a</code> 的位置索引减去以 <code class="language-plaintext highlighter-rouge">b</code> 为中心的回文子串的最左侧字符索引再加一。</p>
<p>因为两个 <code class="language-plaintext highlighter-rouge">a</code> 都包含在 <code class="language-plaintext highlighter-rouge">b</code> 的回文字符串之内,且以 <code class="language-plaintext highlighter-rouge">b</code> 为中心对称,以第二个 <code class="language-plaintext highlighter-rouge">a</code> 为中心的回文子串长度等于以 <code class="language-plaintext highlighter-rouge">b</code> 为中心的回文子串的最右侧字符索引减去第二个 <code class="language-plaintext highlighter-rouge">a</code> 的位置索引再加一。</p>
<p>在计算的时候不用去判断到底遇到两种情况中的哪一种,只要取两种情况中的较小值就行。</p>
<p>我们要设计一个算法,它做的事是</p>
<ul>
<li>记录下当前遍历过的字符串中的最长回文子串的回文中心(iMax),以及其回文半径(rMax)。回文半径就是回文长度除以二加一。</li>
<li>当得到 iMax 时,我们已经知道所有小于 iMax 的位置的回文信息</li>
<li>基于上面两步,求大于 iMax 的位置 i 的回文信息,</li>
<li>求出以 i 为回文中心的最大回文子串信息</li>
<li>用上一步得到信息,选择性地更新 iMax 以及 rMax</li>
<li>最后选出最大的 rMax ,最长回文子串的长度就是 rMax - 1</li>
</ul>
<p>可以用一个数组记录以位置 i 为中心的回文长度: <code class="language-plaintext highlighter-rouge">dp</code> 。初始状态下,因为回文长度最小是 1,对每一个 i , 都有 <code class="language-plaintext highlighter-rouge">dp[i] = 1</code> 。</p>
<p>若 <code class="language-plaintext highlighter-rouge">j</code> 与 <code class="language-plaintext highlighter-rouge">i</code> 关于 <code class="language-plaintext highlighter-rouge">iMax</code> 对称,可以知道 <code class="language-plaintext highlighter-rouge">j = 2 * iMax - i</code></p>
<p>下面是简单的程序实现:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">iMax</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">rMax</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">dp</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">length</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1">// dp[i] 记录以位置 i 为中心的回文长度</span>
<span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">str</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">i</span> <span class="o">></span> <span class="nx">iMax</span> <span class="o">&&</span> <span class="mi">2</span> <span class="o">*</span> <span class="nx">iMax</span> <span class="o">-</span> <span class="nx">i</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">dp</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="nx">iMax</span> <span class="o">-</span> <span class="nx">i</span><span class="p">],</span> <span class="nx">iMax</span> <span class="o">+</span> <span class="nx">rMax</span> <span class="o">-</span> <span class="nx">i</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// 尝试去找以 i 为中心的回文子串</span>
<span class="k">while</span><span class="p">(</span>
<span class="nx">i</span> <span class="o">+</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o"><</span> <span class="nx">str</span><span class="p">.</span><span class="nx">length</span>
<span class="o">&&</span> <span class="nx">i</span> <span class="o">-</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">>=</span> <span class="mi">0</span>
<span class="o">&&</span> <span class="nx">str</span><span class="p">[</span><span class="nx">i</span> <span class="o">-</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]]</span> <span class="o">===</span> <span class="nx">str</span><span class="p">[</span><span class="nx">i</span> <span class="o">+</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]])</span>
<span class="p">{</span>
<span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="o">++</span>
<span class="p">}</span>
<span class="c1">// 如果新的回文子串比原来的覆盖更远,就更新</span>
<span class="k">if</span><span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">></span> <span class="nx">iMax</span> <span class="o">+</span> <span class="nx">rMax</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">iMax</span> <span class="o">=</span> <span class="nx">i</span>
<span class="nx">rMax</span> <span class="o">=</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>虽然代码有两重循环,但是里层循环要满足回文条件。代码的时间复杂度平均来说是线性复杂度。</p>
<p>下面是完整代码:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">findLongestPalindrome</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">str</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">max</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">newStr</span> <span class="o">=</span> <span class="p">[</span><span class="dl">'</span><span class="s1">#</span><span class="dl">'</span><span class="p">]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">newStr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">str</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
<span class="nx">newStr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">'</span><span class="s1">#</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">len</span> <span class="o">=</span> <span class="nx">newStr</span><span class="p">.</span><span class="nx">length</span>
<span class="kd">let</span> <span class="nx">iMax</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">rMax</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">dp</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">(</span><span class="nx">len</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">i</span> <span class="o">></span> <span class="nx">iMax</span> <span class="o">&&</span> <span class="mi">2</span> <span class="o">*</span> <span class="nx">iMax</span> <span class="o">-</span> <span class="nx">i</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">dp</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="nx">iMax</span> <span class="o">-</span> <span class="nx">i</span><span class="p">],</span> <span class="nx">iMax</span> <span class="o">+</span> <span class="nx">rMax</span> <span class="o">-</span> <span class="nx">i</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">while</span> <span class="p">(</span>
<span class="nx">i</span> <span class="o">+</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o"><</span> <span class="nx">len</span>
<span class="o">&&</span> <span class="nx">i</span> <span class="o">-</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">>=</span> <span class="mi">0</span>
<span class="o">&&</span> <span class="nx">newStr</span><span class="p">[</span><span class="nx">i</span> <span class="o">-</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]]</span> <span class="o">===</span> <span class="nx">newStr</span><span class="p">[</span><span class="nx">i</span> <span class="o">+</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]])</span>
<span class="p">{</span>
<span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="o">++</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">></span> <span class="nx">iMax</span> <span class="o">+</span> <span class="nx">rMax</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">iMax</span> <span class="o">=</span> <span class="nx">i</span>
<span class="nx">rMax</span> <span class="o">=</span> <span class="nx">dp</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
<span class="p">}</span>
<span class="nx">max</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="nx">max</span><span class="p">,</span> <span class="nx">rMax</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">max</span> <span class="o">-</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="nx">findLongestPalindrome</span><span class="p">(</span><span class="dl">'</span><span class="s1">aba</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">findLongestPalindrome</span><span class="p">(</span><span class="dl">'</span><span class="s1">cddc</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">findLongestPalindrome</span><span class="p">(</span><span class="dl">'</span><span class="s1">abc</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">findLongestPalindrome</span><span class="p">(</span><span class="dl">'</span><span class="s1">usacdcuseless</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>
<p>参考:</p>
<ul>
<li><a href="https://www.jianshu.com/p/116aa58b7d81">Manacher算法的详细讲解</a></li>
</ul>chong yun一个算字符串的最长回文子串的天才算法React Native FlatList performance tuning2021-02-17T10:20:28+00:002021-02-17T10:20:28+00:00https://judes.me/frontend/2021/02/17/react-native-flatList-performance-tuning-en<p><strong>Background</strong></p>
<p>We found that 60% of the merchants have the habit of using our mobile APP, in order to facilitate these merchants to participate in marketing campaigns on the APP, we want to implement the “My Campaign” functionality on the mobile side similar to the existing PC side.</p>
<p>The number of campaigns displayed on the “Available Campaigns” page may be as many as several thousand. so it is necessary to consider performance issues before implementing functionality.</p>
<p><strong>Layout</strong></p>
<p>In web development, if the content of our page exceeds the size of the page vertically, a scroll bar will appear; unlike web-side, the scroll bar does not appear in mobile (RN), and the content that exceeds the page will be truncated directly.</p>
<p>To display more than one screen of content on the RN side, you need to choose a suitable “container”: ScrollView / SectionList / FlatList.</p>
<p>ScrollView renders all the content at once, which is suitable for not too much content.</p>
<p>SectionList and FlatList are like infinite scrolling technique on the web, rendering only part of the content, and are used to display a large amount of list content.</p>
<p>SectionList is suitable for grouped list data, such as contact lists, and FlatList is suitable for more general purposes.</p>
<p><strong>Performance Measurement</strong></p>
<p>If RN’s performance is affected, it will be shown on two threads: the JS thread and the main thread of the native application (UI thread). When you open RN’s developer menu and select Show Perf Monitor it will show you the impact of these two threads on the frame rate.</p>
<p><img src="https://judes.me/assets/Show_Perf_Monitor.png" alt="Show Perf Monitor" /><em>Show Perf Monitor</em>
<img src="https://judes.me/assets/Show_Perf_Monitor_Threads.png" alt="Show Perf Monitor Threads" /><em>Show Perf Monitor Threads</em></p>
<p>You can see that the frame rate of both the UI and JS in the above image is up to 60 when the page is stationary.</p>
<p>The impact of JS threads on frame rates is similar to that of web-side React applications, and locating issues in this area is similar to that of the web side: select Debug JS Remotely in the developer menu, open the developer console in a newly opened browser, and use the Performance tab to locate the issue.</p>
<p>UI threads are generally affected by image transforming and animation, and are usually not a bottleneck for performance, so problems are easy to locate.</p>
<p>Before measuring performance, first make sure your RN application is running in production mode. RN applications running in development mode have a significant impact on the performance of JS threads because of the overheads of developer-friendly error hints and type checking.</p>
<p><strong>FlatList Performance</strong></p>
<p>First focus on a few properties that have an impact on performance.</p>
<ul>
<li>getItemLayout , which gets the height and width of an item. Similar to infinite scrolling on the web, if the height of each item in the list is fixed, the FlatList eliminates the need to dynamically calculate the height of the item to be rendered (which is very performance intensive).</li>
<li>initialNumToRender, which indicates how many list items need to be rendered initially, defaults to 10. However, 10 is usually too large and the official recommendation is to set it to the number that fills the first screen, and the initial rendered items will not be unmounted. So setting a smaller number than 10 will improve performance.</li>
<li>maxToRenderPerBatch, which indicates how many list items are rendered at once each time the rendering is triggered; the default value is 10. If this value is set too small, a blank page will easily appear when scrolling fast; if it is set too large, memory consumption will go up and response speed will slow down.</li>
<li>updateCellsBatchingPeriod, this value sets the delay time for rendering new list items, the default value is 50 (milliseconds). In contrast to maxToRenderPerBatch, if it is set too large, a blank page will easily appear when scrolling fast, and if it is set too small, performance will be degraded.</li>
<li><a href="https://github.com/filipemerker/flatlist-performance-tips/blob/master/README.md">here</a> for more properties.</li>
</ul>
<p><strong>Optimization practices with business logic</strong></p>
<p>the following animation shows the requirements:</p>
<div class="video_container">
<video controls="true" allowfullscreen="true">
<source src="https://judes.me/assets/react-native-flatList-performance-tuning-1.mp4" type="video/mp4" />
</video>
</div>
<p>To summarize.</p>
<ul>
<li>The page has a header information display area</li>
<li>When scrolling to a certain position, the page will display and update the element stick to the top</li>
<li>The height of the list element is not fixed, and the user can collapse and expand the list element by clicking it</li>
<li>There is a “back to top” button in the bottom right corner</li>
<li>The page has two tabs, in the second of which user can select some list elements by ticking them.</li>
</ul>
<p><img src="https://judes.me/assets/react_native_flatlist_sec_1.png" alt="react_native_flatlist_sec_1" /><em>react native flatlist section 1</em>
<img src="https://judes.me/assets/react_native_flatlist_sec_2.png" alt="react_native_flatlist_sec_2" /><em>react native flatlist section 2</em>
<img src="https://judes.me/assets/react_native_flatlist_sec_3.png" alt="react_native_flatlist_sec_3" /><em>react native flatlist section 3</em></p>
<p>我们打开 Show Perf Monitor 看一下性能(in production mode & in iPhone SE(2nd) simulator):</p>
<p>Let’s open Show Perf Monitor to see the performance(in production mode & in iPhone SE(2nd) simulator).</p>
<div class="video_container">
<video controls="true" allowfullscreen="true">
<source src="https://judes.me/assets/react-native-flatList-performance-tuning-2.mp4" type="video/mp4" />
</video>
</div>
<p>You can see that the frame rate is very stable, rarely below 60. (The lag that you can see is a simulator problem, not a problem on the real machine)</p>
<p>A brief list of performance optimizations:</p>
<ol>
<li>when you need to render a list with FlatList, but the page contains headers and footers in addition to the list, you can use the ListHeaderComponent/ListFooterComponent property of FlatList to render these contents.</li>
<li>even if the height of the list element is not fixed, you can set the getItemLayout property to calculate the height of the rendered element for the FlatList, by using a map to record the height and width of each element when it is rendered, to avoid repeatedly calculation.</li>
<li>when the user clicks the expand/collapse button, the list element will be expanded/collapsed, and then the onLayout event will be triggered, and the map above will be updated in this event callback;</li>
<li>it can be seen that usually only two list elements are needed to fill in the first screen, so the initialNumToRender property is set to 3.</li>
<li>to avoid the element expanding/collapsing triggering the re-render of the entire FlatList, use react hooks in each element to manage their own expand/collapse state, then trigger the rendering.</li>
<li>although the FlatList has the property stickyHeaderIndices to set the sticky element, this property fixes the whole list element to the list top, and our need is to fix only a certain part of the element; so the way to implement it is to create a separate absolutely positioned element, places it with the FlatList element under the same container.</li>
<li>to avoid frequent re-rendering of the FlatList, all FlatList property values are immutable values or references to methods or objects, except for the data property.</li>
<li>to re-render the list when an element is checked (otherwise the checkbox would not show checked), use the extraData property to trigger re-rendering.</li>
<li>using React.memo wrapping for list elements and their descendants to avoid unnecessary rendering.</li>
<li>some values, which do not involve re-rendering, are not stored in the State but in the component’s private property.</li>
<li>If we find that some of the values stored in the State have not changed after some calculations, we do not call setState.</li>
</ol>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://archive.reactnative.dev/docs/performance">reactnative performance</a></li>
<li><a href="https://archive.reactnative.dev/docs/flatlist">reactnative flatlist</a></li>
<li><a href="https://github.com/filipemerker/flatlist-performance-tips/blob/master/README.md">flatlist-performance-tips</a></li>
<li><a href="https://stackoverflow.com/questions/43709142/scrolling-issues-with-flatlist-when-rows-are-variable-height">scrolling-issues-with-flatlist-when-rows-are-variable-height</a></li>
</ul>chong yunSome optimization practices and suggestionsReact Native FlatList 性能优化实践2021-01-27T10:20:28+00:002021-01-27T10:20:28+00:00https://judes.me/frontend/2021/01/27/react-native-flatList-performance-tuning<p><strong>背景</strong></p>
<p>统计发现有 60% 的商家有使用我司手机端 APP 的习惯,为方便这一部分商家在 APP 上报名参与营销活动,在手机端也实现与现有 PC 端相似的 My Campaign 功能。</p>
<p>其中“可报名活动”页面,展示的活动列表条数有可能会多达几百上千条,在实现功能之前有必要考虑性能问题。</p>
<p><strong>布局选择</strong></p>
<p>在 web 端开发,如果我们页面的内容在垂直方向上超出了页面的尺寸,滚动条会出现;与 web 端不同,手机端(RN)并不会出现滚动条,超出页面的内容会直接被截断。</p>
<p>要在 RN 端显示超过一屏的内容,就要选择合适的「容器」: ScrollView / SectionList / FlatList 。</p>
<p>ScrollView 会将全部内容一次性渲染出来,适用于不太多的内容;</p>
<p>SectionList 与 FlatList 就像 web 端的无限滚动,只会渲染部分内容,用于展示大量的列表内容,</p>
<p>SectionList适用于分组的列表数据,如联系人列表,FlatList 适用于更通用的场合。</p>
<p><strong>性能测量</strong></p>
<p>影响 RN 性能的问题会体现在两个线程上面: JS 线程和原生应用的主线程(UI 线程)。当你打开 RN 的 developer menu ,选择 Show Perf Monitor 就会显示这两个线程对帧率的影响:</p>
<p><img src="https://judes.me/assets/Show_Perf_Monitor.png" alt="Show Perf Monitor" /><em>Show Perf Monitor</em>
<img src="https://judes.me/assets/Show_Perf_Monitor_Threads.png" alt="Show Perf Monitor Threads" /><em>Show Perf Monitor Threads</em></p>
<p>可以看到页面静止时,上图无论 UI 还是 JS 的帧率最高都是 60 。</p>
<p>JS 线程对帧率的影响与 web 端的 React 应用类似,定位这方面的问题也与 web 端类似:在 developer menu 中选择 Debug JS Remotely ,在新打开的浏览器中打开开发者控制台,利用 Performance 标签中的功能定位问题。</p>
<p>而 UI 线程一般受图片形变、动画影响,通常不会是性能的瓶颈,出问题也很容易定位。</p>
<p>在测量性能之前,首先要确保你的 RN 应用运行在 production mode 。RN 应用运行在 development mode 时,因为要加入对开发友好的错误提示和类型检查,对 JS 线程的性能影响很大。</p>
<p><strong>FlatList 性能</strong></p>
<p>首先关注几个对性能有影响的属性:</p>
<ul>
<li>getItemLayout ,可以通过这个属性返回元素高度、宽度。与 web 端的无限滚动类似,如果列表中每个元素的高度都是固定的, FlatList 就可以省去动态计算要渲染的元素高度的步骤(非常耗费性能);</li>
<li>initialNumToRender,这个值表示初次需要渲染多少个列表元素,默认值是 10 。但是 10 通常太大,官方建议设置成填满首屏的数量即可,并且初次渲染的这些元素并不会被卸载。因此设置一个比 10 小的数字会提升性能;</li>
<li>maxToRenderPerBatch,这个值表示每次触发渲染新列表元素时一次性渲染多少个列表元素,默认值是 10 。这个值设置得太小,滚动得快时就容易出现空白页面;设置得太大,内存消耗会变大,响应速度也会变慢;</li>
<li>updateCellsBatchingPeriod,这个值设置渲染新列表元素的延迟时间,默认值是 50 (毫秒)。如果设置得太大,滚动时就容易出现空白,设置得太小性能亦会下降;</li>
<li>更多属性请参考<a href="https://github.com/filipemerker/flatlist-performance-tips/blob/master/README.md">这里</a>。</li>
</ul>
<p><strong>与业务结合的优化实践</strong></p>
<p>可以通过下面的动画了解需求:</p>
<div class="video_container">
<video controls="true" allowfullscreen="true">
<source src="https://judes.me/assets/react-native-flatList-performance-tuning-1.mp4" type="video/mp4" />
</video>
</div>
<p>总结一下:</p>
<ul>
<li>页面有一个头部信息展示区域</li>
<li>当滚动到一定位置时,页面会显示/更新置顶元素</li>
<li>列表元素的高度不固定、且用户可以折叠、展开列表元素</li>
<li>右下角有一个「一键返回顶部」的按钮</li>
<li>页面有两个 tab ,其中第二个 tab 中可以勾选列表元素</li>
</ul>
<p><img src="https://judes.me/assets/react_native_flatlist_sec_1.png" alt="react_native_flatlist_sec_1" /><em>react native flatlist section 1</em>
<img src="https://judes.me/assets/react_native_flatlist_sec_2.png" alt="react_native_flatlist_sec_2" /><em>react native flatlist section 2</em>
<img src="https://judes.me/assets/react_native_flatlist_sec_3.png" alt="react_native_flatlist_sec_3" /><em>react native flatlist section 3</em></p>
<p>我们打开 Show Perf Monitor 看一下性能(in production mode & in iPhone SE(2nd) simulator):</p>
<div class="video_container">
<video controls="true" allowfullscreen="true">
<source src="https://judes.me/assets/react-native-flatList-performance-tuning-2.mp4" type="video/mp4" />
</video>
</div>
<p>可以看到帧率很稳定,很少低于 60 。(肉眼能看到的卡顿是 simulator 的问题,在真机上不会有这个问题)</p>
<p>简单列举一下所做的性能优化:</p>
<ol>
<li>当需要使用 FlatList 渲染列表,但是页面包含除了列表之外的页头、页脚时,就可以用 FlatList 的 ListHeaderComponent/ListFooterComponent 属性渲染这些内容;</li>
<li>就算列表元素的高度并不固定,也可以设置 getItemLayout 属性,替 FlatList 计算出渲染元素的高度,实现方式是用一个 map 记录下各个元素渲染时的高宽,避免重复计算;</li>
<li>当用户点击 展开/折叠 按钮,列表元素会展开、折叠,继而触发 onLayout 事件,在这个事件回调中更新上面的 map ;</li>
<li>可以看到,通常只要两个列表元素就能将首屏填满,因此将 initialNumToRender 属性值设置为 3;</li>
<li>为了避免单个元素 展开/折叠 会触发整个 FlatList 重新渲染,在单个元素中用 hooks 管理自身的 展开/折叠 状态,触发渲染;</li>
<li>虽然 FlatList 有设置置顶元素的属性 stickyHeaderIndices ,但是这个属性固定整个列表元素,而我们的需求只是固定列表的某部分;所以实现方式是设置单独的绝对定位元素、与 FlatList 元素并列位于某个容器下面;</li>
<li>为避免频繁触发 FlatList 重新渲染,除了 data 属性之外,所有传入的 FlatList 的属性值都是简单值或者是方法、对象的引用;</li>
<li>为了实现勾选元素时重新渲染列表(不然复选框不会显示选中),使用 extraData 属性值触发重新渲染;</li>
<li>列表元素以及其子孙元素使用 React.memo 包裹,避免不必要的渲染;</li>
<li>一些状态值,如果不涉及重新渲染,则不保存在 State 中,而是保存在组件的 private 属性中;</li>
<li>一些保存在 State 中的状态值(如是否显示「一键返回顶部」按钮),如果计算得出没有发生改变,就不调用 setState 。</li>
</ol>
<p>参考:</p>
<ul>
<li><a href="https://archive.reactnative.dev/docs/performance">reactnative performance</a></li>
<li><a href="https://archive.reactnative.dev/docs/flatlist">reactnative flatlist</a></li>
<li><a href="https://github.com/filipemerker/flatlist-performance-tips/blob/master/README.md">flatlist-performance-tips</a></li>
<li><a href="https://stackoverflow.com/questions/43709142/scrolling-issues-with-flatlist-when-rows-are-variable-height">scrolling-issues-with-flatlist-when-rows-are-variable-height</a></li>
</ul>chong yun一些优化实践和建议最近沉迷的事2020-10-31T08:20:28+00:002020-10-31T08:20:28+00:00https://judes.me/life/2020/10/31/subtitle-making<p>曾经有一段时间,很喜欢到 B 站上看一档日本广播节目的熟肉。这档节目由一男一女主持,每周都聊些轻松有意思的观众日常来信。男主持是个老广播人,女主持是个女团偶像,他俩之间也经常上演些屌丝与女神的小故事。</p>
<p>长达半个多小时的广播节目,字幕组每周都更新。因为看不到人物动作、表情,也没有生肉字幕辅助,做广播熟肉的难度比视频节目要高。要不是字幕组对女主持爱得深沉,也很难坚持下去吧,我很敬佩他们。</p>
<p>悲剧的是,后来换一个女主持,原来的字幕组就不做了,虽然有新的字幕组接手,但他们没坚持几期就没再继续了。</p>
<p>跟原来的字幕组不一样,自己对原来的女主持并没有多大感觉,听这档节目完全是因为话题有意思。新女主持上任之后,几期下来,我迷上了她的声音,变成既喜欢女主又喜欢内容的双重粉丝了。</p>
<p>熟肉停更对我打击有点大,忍不住跑去油管看看有没有广播源,就算是生肉也听听。</p>
<p>因为很多地方都能听懂,节目还是一如既往的有意思。</p>
<p>后来我想,这么有意思的内容,没能让更多人听到,真可惜,能不能接过字幕组的大旗,做做熟肉呢?</p>
<p>初看,觉得这想法很张狂,因为自己连日语 N5 都没有考(撑死也就 N3 到 N2 之间的水平),对做字幕一无所知,完全不清楚翻译、校对、时轴、特效、压制都要做些什么。</p>
<p>又想,能力低可以放低起点,半个小时的节目做不下来,可以先从几分钟的做起嘛;能力是个问题,但也不是完全逾越不过去的难关,油管上的视频有 AI 自动翻译字幕,虽然不能指望它意义正确,但是捕捉发音很准确,可以利用一下。</p>
<p>于是某个周日,先是研究怎样下载油管视频,再听一句翻译一句,找来一个叫 ArcTime 的字幕工具,学习怎样拉轴,怎样字幕换行,再怎样上传到 B 站,等等。</p>
<p>人生第一个熟肉只有 6 分多钟,但是我花了大约 7 个小时,累得腰痛。期间最费劲的是某几句听不清楚,无论怎样——放慢多少倍语速、把每个音节记下来、上网搜、联系上下文想想——都没有找到能接受的翻译。</p>
<p>日常听直播的广播节目确实很开心、时不时大笑拍掌,但换成做字幕,心态会丰富很多。如果只是娱乐,你完全不在意听漏了一两句,就算完全听不懂,只要主持人大笑,你也会莫名其妙地跟着乐;但是做字幕就会发现原来听漏了很多、还有几句听不清楚或者听清楚了就是不太懂。</p>
<p>几乎 30% 的时间会花在来回听这几句上面,有时候能得到尚算满意的答案,更多的时候只能脑补一个翻译。每脑补一处,就多一分负罪感:要是脑补错了,被带歪的听众朋友,对不住了!</p>
<p>后来,陆陆续续做了十个熟肉,对「脑补」这件事逐渐释怀,原因有两个:因为那是个直播的广播节目,就算日本人说话,可能也会用词造句出错;更重要的是语言交流本身就留不开脑补,我们日常说话也经常让对方听不懂,然后别人脑补出一个自认为的答案。</p>
<p>当然,这也是个掩饰能力不足的借口。做字幕这段时间,学到很多日语,越来越觉得自己有很大进步空间。</p>
<p>回想做熟肉的初衷,原本只是要分享这个有意思的节目、让更多人迷上我的推。如今发现自己收获得更多:欢笑、做字幕的能力(后来 Arctime 坏掉,又学了 aegisub / handbrake)、更多日语表达,还有被关注、点赞的虚荣感。</p>
<p>正如那句话所说:赠人玫瑰、手留余香。</p>chong yun每个周末都花大半天时间去做的事……React 在渲染列表时,列表元素的 Key 重复了会怎样?2020-09-12T10:20:28+00:002020-09-12T10:20:28+00:00https://judes.me/frontend/2020/09/12/react-list-repeated-key<p>如果你在使用 React 渲染列表时,有过不慎产生了重复 key 的经验,你一定会在 console 中看到过以下告警</p>
<blockquote>
<p>Warning: Encountered two children with the same key, …. Non-unique keys may cause children to be duplicated and/or omitted.</p>
</blockquote>
<p>但是 key 重复的 item 也能正常渲染,你或许有疑惑:warning 提到的问题在什么情况下会出现呢?</p>
<p>如果你有疑惑,看看这个<a href="https://codesandbox.io/s/suspicious-wilson-7r3ps">例子</a>。</p>
<p>第一遍渲染时 key 重复的 item ,在第二遍渲染时保留了下来。但这个表现跟 Warning 里说的有点不一样,到底为什么呢?</p>
<p>让我们从源码入手一探究竟吧。</p>
<p>(我得承认对 React 源码不怎么了解,如果你也跟我一样,不妨看看我是怎样找到出问题的源码。)</p>
<p>首先打开开发者工具,点开上面那个 Warning ,查看堆栈信息,可以看到几个有意思的方法名:reconcileChildrenArray / warnOnInvalidKey ,前者似乎是用来决定子数组中要新增加哪些元素、哪些元素要删除;后者当发现不合法的 key 就会报 warning.</p>
<p><img src="https://judes.me/assets/warning_of_repeated_key.jpg" alt="warning_of_repeated_key" /><em>warning of repeated key</em></p>
<p>点进去 react-dom.development.js 看 warnOnInvalidKey 的代码,可以看到它里用一个 knowKeys 变量记录 child 的 key 。如果发现 Key 已经存在,就会报 warning 。</p>
<p><img src="https://judes.me/assets/warnOnInvalidKey.jpg" alt="warnOnInvalidKey" /><em>warnOnInvalidKey function</em></p>
<p>接下来就在 react-dom.development.js 文件内搜索 knownKeys 这个变量,可惜 React 并没有用它来做其他事情。</p>
<p>一时没想到其他办法,先在报 warning 的地方打个断点调试,看看堆栈变量里有没有蛛丝马迹。</p>
<p><img src="https://judes.me/assets/add_first_breakpoint.jpg" alt="add_first_breakpoint" /><em>add_first_breakpoint</em></p>
<p>你会发现程序只会在第一次渲染时运行这个 warning,第二遍渲染时不会触发这个断点。</p>
<p>因为我们要研究第二遍渲染时的问题,因此这个 warning 的价值不高,不得不换个思路了。</p>
<p>在第二遍渲染时,前几个元素被移除了,程序肯定执行了删除操作,我们可以用 ‘remove’ 或者 ‘unmount’ 关键字在 react-dom.development.js 里搜索一下,看看会不会有什么发现。 搜出了 61 个 ‘remove’ ,而 ‘unmount’ 出现在 46 个地方。把它们都快速看一遍,在觉得可疑的地方打个断点,以 remove 为例。</p>
<p>打好断点之后再刷新一遍页面。果然命中了其中一个,可以看到这时页面的前几个元素还没有被移除。只要一步步执行下去,就能定位问题。</p>
<p><img src="https://judes.me/assets/first_remove_breakpoint.jpg" alt="first remove breakpoint" /><em>first remove breakpoint</em></p>
<p>点 step over next function call(或者按 F10),直到看到第一个元素被移除,再继续点 step over next function call 好几遍,不久就会发现程序又回到刚刚上面那个断点,很快第二个元素就会被移除。</p>
<p><img src="https://judes.me/assets/before_second_element_remove_breakpoint.jpg" alt="before second element remove" /><em>before second element remove breakpoint</em></p>
<p>仔细看这个断点的上下文,会发觉它在一个 while(true) 循环内,这个循环用来移除第一到第四个元素。可以猜想而到了第五个元素时,它就跳出了循环,在所有可能跳出循环的地方都打个断点。</p>
<p><img src="https://judes.me/assets/all_possible_return.jpg" alt="all possible return" /><em>all possible return</em></p>
<p>然后点 Resume Script execution (或者按 F8),让程序直接运行到下一个断点。当程序要跳出循环时,再一步步执行,看后面做了些什么事。</p>
<p>原来外层还有一个 while(nextEffect !== null) 循环。因为在循环内部不断地将 nextEffect 重新赋值: nextEffect = nextEffect.nextEffect 。可以想像第四个元素的 nextEffect.nextEffect 并没有指向第五个元素的 nextEffect ,而是指向 null ,所以跳出了循环。</p>
<p><img src="https://judes.me/assets/set_next_effect_1.jpg" alt="set_next_effect_1" /><em>set_next_effect_1</em>
<img src="https://judes.me/assets/set_next_effect_2.jpg" alt="set_next_effect_2" /><em>set_next_effect_2</em></p>
<p>全局搜 nextEffect.nextEffect ,发现有 8 个地方,全都打上断点,然后刷新页面。程序触发一个断点,但是无论从断点的堆栈,还是断点之后的代码上下中都没有发现跟第四个元素相关的信息。</p>
<p><img src="https://judes.me/assets/nextEffect_breakpoint.jpg" alt="nextEffect breakpoint" /><em>nextEffect breakpoint</em></p>
<p>有没有可能在设置第四个元素的 nextEffect.nextEffect 时,用的不是 nextEffect.nextEffect = null ,而是 xxx..nextEffect = null 呢?</p>
<p>试着用 ‘.nextEffect = null’ 去搜索,发现有 9 个地方,在可疑的地方都打上断点,刷新页面。触发了一个断点,而是发现一个很有意义的变量: childToDelete 。React 是怎么决定哪些 child 应该被要 delete 的呢?</p>
<p><img src="https://judes.me/assets/nextEffect_childToDelete.jpg" alt="nextEffect_childToDelete" /><em>nextEffect_childToDelete</em></p>
<p>往上一个堆栈,就能发现一个叫 existingChildren 的变量,而这个变量由 mapRemainingChildren 方法计算得来。如无意外,只要看这个方法的内部实现就能定位问题原因了。</p>
<p><img src="https://judes.me/assets/existingChildren.jpg" alt="existingChildren" /><em>existingChildren</em></p>
<p>在调用 mapRemainingChildren 的代码打断点,刷新页面。点 Step into(或者按 F11) 进去 mapRemainingChildren 内部,</p>
<p><img src="https://judes.me/assets/mapRemainingChildren.jpg" alt="mapRemainingChildren" /><em>mapRemainingChildren</em>
<img src="https://judes.me/assets/mapRemainingChildren_inner.jpg" alt="mapRemainingChildren_inner" /><em>mapRemainingChildren_inner</em></p>
<p>程序会从 currentFirstChild 开始,通过 existingChild.key ,用 Map 实例(existingChildren)记录下 existingChild ,并通过赋值 existingChild = existingChild.sibling ,不断遍历 existingChild 。
<img src="https://judes.me/assets/mapRemainingChildren_1.jpg" alt="mapRemainingChildren_1" /><em>mapRemainingChildren_1</em>
<img src="https://judes.me/assets/mapRemainingChildren_2.jpg" alt="mapRemainingChildren_2" /><em>mapRemainingChildren_2</em></p>
<p>问题就出在这里:
因为第四个元素的 key 跟第五个元素的 key 都是 4 ,所以 existingChildren 中第四个元素会被第五个元素覆盖。被移除的是第五个元素,第四个元素被保留下来。</p>
<p>为了证明这点。可以在列表子元素中新增加一个属性: data-key ,属性值就是当前的元素在数组中的 index 。可以在<a href="https://codesandbox.io/s/wonderful-galileo-jj1zt?file=/src/App.js">这里</a>实际运行。</p>
<p>比较第一遍渲染时的 data-key ,跟第二遍渲染时的 data-key ,可以确定第五个元素被移除,还保留下来的是第四个元素。</p>
<p><img src="https://judes.me/assets/react_duplicated_data_key_1.jpg" alt="react_duplicated_data_key_1" /><em>react_duplicated_data_key_1</em>
<img src="https://judes.me/assets/react_duplicated_data_key_2.jpg" alt="react_duplicated_data_key_2" /><em>react_duplicated_data_key_2</em></p>
<p>用 React 渲染列表时, key 重复看上去只报 warning ,实际真的会引发问题,所以千万不要不当一回事。</p>chong yun除了 warning 还有别的意外惊喜!Excel can help you when reading the source code2020-09-05T10:20:28+00:002020-09-05T10:20:28+00:00https://judes.me/tech/2020/09/05/reading-source-code-with-excel-en<p>A while ago I had to prepare for a sharing within the front-end group, so I went to read the <a href="https://mobx.js.org">mobx</a> source code.</p>
<p>As a full-fledged state management tool, the source code execution process for mobx is so long that I often look back at the stack information and find myself a dozen stacks away from where I started.</p>
<p>While mobx is very well named and the code structure is clear, the code execution flow is a bit long.</p>
<p>Whenever I can’t remember the execution context of a piece of code, I always want to be able to keep track of the flow of code execution while I’m looking at the code.</p>
<p>Previously, I thought I could only use flowcharts to document code execution, but drawing flowcharts was a pain, with more than half the time spent drawing and adjusting styles.</p>
<p>I couldn’t concentrate on reading the source code at all, so I never got around to drawing the flowcharts.</p>
<p>I just saw someone on Twitter saying that “The biggest competitor of many ToB products is actually Excel”. I suddenly had an idea, could I use it to record code execution?</p>
<p>With some practice, I found that Excel really does work.</p>
<p>First, let’s talk about my own needs, when reading the source code I focus on roughly only three.</p>
<ul>
<li>What are the sequential methods of implementation at the same level?</li>
<li>Which Method Called by Which Method</li>
<li>Which method is a bit special and needs a note on it</li>
</ul>
<p>I therefore record the code execution as follows,</p>
<ul>
<li>Records the executing method in the order of “Row”. If there is content above a cell, the program executes the content above the cell before executing the content of that cell.</li>
<li>Records the method being called in the order of “Column”.If there is content to the left of a Cell, that Cell is called from the left side.</li>
<li>Rows have higher priority than columns; if there is content above and to the left of a cell, ignore the content to the left.</li>
<li>Add empty cells to resolve conflicts between rows and columns when necessary.</li>
<li>Comment additional information on the Cells.</li>
</ul>
<p>As an example, look at the following code,</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">func1</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">funcA</span><span class="p">();</span>
<span class="nx">funcB</span><span class="p">();</span>
<span class="nx">funcC</span><span class="p">();</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">funcA</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">funcAA</span><span class="p">();</span>
<span class="nx">funcAB</span><span class="p">();</span>
<span class="nx">funcAC</span><span class="p">();</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">funcB</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">funcBA</span><span class="p">();</span>
<span class="nx">funcBB</span><span class="p">();</span>
<span class="nx">funcBC</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It could be recorded in the manner above as follows.</p>
<p><img src="https://judes.me/assets/excel_record.jpg" alt="Excel record" /><em>Excel record</em></p>
<p>There must be logical judgments in the code, how to handle logical branches? Here’s what I’ve come up with for now.</p>
<ul>
<li>Record different branches with different columns</li>
<li>Every branch having the same background color</li>
<li>Insert one empty column between each branch</li>
</ul>
<p>For example,</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">func1</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">funcA</span><span class="p">();</span>
<span class="nx">funcB</span><span class="p">();</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">funcA</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">funcAA</span><span class="p">();</span>
<span class="nx">funcAB</span><span class="p">();</span>
<span class="nx">funcAC</span><span class="p">();</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">funcB</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">></span> <span class="mf">0.5</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">funcBA</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">funcBB</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Record the above code in Excel like this,</p>
<p><img src="https://judes.me/assets/excel_record_condition.jpeg" alt="Excel record with condition" /><em>Excel record with condition</em></p>
<p>Useing Excel to record the code execution although rudimentary, not as intuitive as a flowchart, but very hassle-free, and easy to read, I am very satisfied.</p>chong yunDidn't think so, did you?