CodingTour
ARTS #5

Algorithm

本周选择的算法题是:Vowel Spellchecker

规则如下:

Given a wordlist, we want to implement a spellchecker that converts a query word into a correct word.

For a given query word, the spell checker handles two categories of spelling mistakes:

  • Capitalization: If the query matches a word in the wordlist (case-insensitive), then the query word is returned with the same case as the case in the wordlist.
    • Example: wordlist = ["yellow"], query = "YellOw": correct = "yellow"
    • Example: wordlist = ["Yellow"], query = "yellow": correct = "Yellow"
    • Example: wordlist = ["yellow"], query = "yellow": correct = "yellow"
  • Vowel Errors: If after replacing the vowels (‘a’, ‘e’, ‘i’, ‘o’, ‘u’) of the query word with any vowel individually, it matches a word in the wordlist (case-insensitive), then the query word is returned with the same case as the match in the wordlist.
    • Example: wordlist = ["YellOw"], query = "yollow": correct = "YellOw"
    • Example: wordlist = ["YellOw"], query = "yeellow": correct = "" (no match)
    • Example: wordlist = ["YellOw"], query = "yllw": correct = "" (no match)

In addition, the spell checker operates under the following precedence rules:

  • When the query exactly matches a word in the wordlist (case-sensitive), you should return the same word back.
  • When the query matches a word up to capitlization, you should return the first such match in the wordlist.
  • When the query matches a word up to vowel errors, you should return the first such match in the wordlist.
  • If the query has no matches in the wordlist, you should return the empty string.

Given some queries, return a list of words answer, where answer[i] is the correct word for query = queries[i].

Example 1:

Input: wordlist = ["KiTe","kite","hare","Hare"], queries = ["kite","Kite","KiTe","Hare","HARE","Hear","hear","keti","keet","keto"]
Output: ["kite","KiTe","KiTe","Hare","hare","","","KiTe","","KiTe"]

Note:

  • 1 <= wordlist.length <= 5000
  • 1 <= queries.length <= 5000
  • 1 <= wordlist[i].length <= 7
  • 1 <= queries[i].length <= 7
  • All strings in wordlist and queries consist only of english letters.

Solution

我实现的方案:

Runtime:128 ms,快过 96.77%。

Memory:14,8 MB,低于 100%。

class Solution:
    vowel_table = set(['a', 'e', 'i', 'o', 'u'])

    def spellchecker(self, wordlist: List[str], queries: List[str]) -> List[str]:
        if len(queries) == 0:
            return []

        def replace_vowel(word: str) -> str:
            return "".join([char if char not in Solution.vowel_table else "a" for char in word])

        hash_table = set(wordlist)
        word_lower_table = {}
        word_vowel_table = {}
        for word in wordlist:
            word_lower = word.lower()
            word_lower_table.setdefault(word_lower, word)
            word_vowel_table.setdefault(replace_vowel(word_lower), word)

        result = [""] * len(queries)
        i = 0
        for query in queries:
            # exactly matches a word
            if query in hash_table:
                result[i] = query
            else:
                query_lower = query.lower()
                # matches a word up to capitlization
                if query_lower in word_lower_table:
                    result[i] = word_lower_table[query_lower]
                else:
                    query_without_vowel = replace_vowel(query_lower)
                    # matches a word up to vowel errors
                    if query_without_vowel in word_vowel_table:
                        result[i] = word_vowel_table[query_without_vowel]

            i += 1

        return result

规则有点多,特别是元音匹配这里卡了很久,最终还是决定用一张表来处理。这道题也有官方解法,经过对比发现实现思路是一样的,不过具体的实现没有官方优雅,result 的构造过程直接用一个 map 就可以了,可以有效避免多层 if 嵌套。这说明我的函数式编程思维还需要锻炼。

Review

Understanding Asynchronous JavaScript
该文用非常简洁、通畅的语句解释了 JavaScript 是如何实际异步回调的。在此总结下:

The Event Loop

JavaScript 本身是单线程的,不支持异步/多线程,为此需要浏览器或解释环境提供支持。如下图所示: 图中的 event loopweb APIsmessage queue(或叫 task queue)并不属于 JavaScript 引擎,这是浏览器或其他 JavaScript 运行环境提供的能力。call stack 只有一个,因为是单线程;web APIs 提供了一些方法来做异步回调,它会在需要时将回调给到 message queue,然后 event loop 会检查 call stack 是不是空的,如果是空的,且 message queue 中有回调,则会把 message queue 中的回调 pop,然后 push 到 call stack 中来执行。

DOM Events

DOM Events的处理过程也类似,web APIs 环境会在事件触发时 push 给 message queue,之后由 event loop 来处理。整个过程看起来如下:

ES6 Job Queue/Micro-Task Queue

ES6 引入了 Job Queue(又称 Micro-Task Queue),它与 message queue 的概念很像,不同的是 job queue 的优先总是高于 message queue,也就是说,除非 job queue 是空的,不然 message queue 中的回调不会被执行。

例子一

console.log('Script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

new Promise((resolve, reject) => {
    resolve('Promise resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));

console.log('Script End');

输出是:

Script start
Script End
Promise resolved
setTimeout

例子二

console.log('Script start');

setTimeout(() => {
  console.log('setTimeout 1');
}, 0);

setTimeout(() => {
  console.log('setTimeout 2');
}, 0);

new Promise((resolve, reject) => {
    resolve('Promise 1 resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));

new Promise((resolve, reject) => {
    resolve('Promise 2 resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));

console.log('Script End');

输出是:

Script start
Script End
Promise 1 resolved
Promise 2 resolved
setTimeout 1
setTimeout 2

例子三

console.log('Script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

new Promise((resolve, reject) => {
    resolve('Promise 1 resolved');
  }).then(res => console.log(res));
  
new Promise((resolve, reject) => {
  resolve('Promise 2 resolved');
  }).then(res => {
       console.log(res);
       return new Promise((resolve, reject) => {
         resolve('Promise 3 resolved');
       })
     }).then(res => console.log(res));
     
console.log('Script End');

输出是:

Script start
Script End
Promise 1 resolved
Promise 2 resolved
Promise 3 resolved
setTimeout

这些例子展示了 job queue 与 message queue 之间的优先级。

Tip

本周学习到的一些内容:

  • 完成了从 PyCharm 到 VS Code 的过渡
  • Python 爬虫的一些实战经验
  • 在 Python 中可以这样将元组映射到方法参数中: func(*tuple)
  • JavaScript 的异步处理机制

Share

几年后,我打算重新在公共平台记录自己的成长:

  • 公共平台的写作能锻炼自己的沟通能力
  • 公共平台的写作能通过给自己压力,将零散的知识点形成系统性的认识
  • 知识不是死记硬背,持续获取知识的能力更重要
  • 学习需要持续,而成长却看起来不是“持续”的,它不会对学习产生即时反馈,它可能会有某个点突然出现,然后一夜长大,在它长大之前,耐心培育它吧

为此我在7月3号购买了 CodingTour 这个域名,用于记录自己的所看、所思、所想。

Learning to be better。


在分享一篇我选择域名时看到的文章吧

How to Choose the Best Domain Name(11 Tips and Tools)
一个有追求有品位的程序员应该要有自己独立域名的个人主页吧,这会给人一种爱动手做事的感觉。 在此对该文做个记录,方便索引。

选择好域名的14个提示:

  1. 坚持 .com 域名
    • 更可信
    • 更容易记住,大多数人输入网址的时候会下意识输入.com
    • 一些机器(比如智能手机)也只提供了.com的按钮让人快速输入
  2. 在域名中使用关键字
    • 关键字能更容易让搜索引擎索引,也更容易取得排名靠前的位置
  3. 保持域名的简短
    • 太长的域名不好记
    • 太长的域名更容易拼写错误
    • 建议域名不要超过15个字符
  4. 容易拼写和容易读
  5. 唯一性和可品牌化
    • 不要使用别人的商标名或已有的同名服务
    • 唯一性是为了和其他人的服务区分开,不让你的流量流向别的地方
    • 品牌更容易传播和好记,Amazom.com 就比 BuyBooksOnline.com 更好
  6. 不要使用-
    • 怪异的符号通常会和垃圾域名联想在一起
    • 容易写错域名
  7. 不要使用重复字母
    • 重复的域名同样不好记,也更容易出现拼写错误,比如 Processsetup.com
  8. 留有扩充的余地
    • 选择更抽象、不依赖当前业务的名称,这样当你的业务拓展到其他领域的时候,不用切换到新的域名从而导致损失
  9. 调查下你的域名
    • 注册域名前先在索引擎或社交媒体上搜索下你的域名,和商标相似的名称可能会给你带来法律风险,从而导致经济上的损失
  10. 使用域名生成器得到一些灵感
    • 大多数好的域名已经被人占用,域名生成器将会通过组合关键字生成一些好记、又短的域名,帮助你做出选择
  11. 比其他人先得到它
    • 每天都有大量的域名被注册,你如果找到了一个你觉得还不错的域名,应该尽快完成注册
  12. 注册域名的最佳地方
  13. 获取免费的域名和托管服务器
  14. 最受欢迎的域名注册商

12 到 14 这三个 Tips 属于文章标题中的 Tools,借鉴意义没有那么大。国内用户直接使用腾讯或者阿里的服务就好。