最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 实现一个did you mean效果

    正文概述 掘金(shadow_)   2021-02-23   531

    前言

    这次来尝试实现一下did you mean效果。

    具体来说就是比如我们使用一些cli工具的时候,如果打错了几个字,则会提示你要输入的是不是别的什么东西。

    比如使用vue-cli打一个命令vue create的时候故意打错vue creat,就会有类似的提示。

    Unknown command creat.
    
    Did you mean create?
    

    一般这种效果会使用编辑距离去计算,当编辑距离小于一定数量时,则认为相差不大。

    最常见的定义是每次可以插入,删除,或替换一个字符,计算整个操作的次数。

    那么就需要一个,计算两个字符串相差字符个数的算法。

    变成算法题了有没有,刚好LeetCode上也有一道类似的题目。

    编辑距离

    给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

    你可以对一个单词进行如下三种操作:

    插入一个字符 删除一个字符 替换一个字符  

    示例 1:

    输入:word1 = "horse", word2 = "ros"
    输出:3
    解释:
    horse -> rorse (将 'h' 替换为 'r')
    rorse -> rose (删除 'r')
    rose -> ros (删除 'e')
    

    示例 2:

    输入:word1 = "intention", word2 = "execution"
    输出:5
    解释:
    intention -> inention (删除 't')
    inention -> enention (将 'i' 替换为 'e')
    enention -> exention (将 'n' 替换为 'x')
    exention -> exection (将 'n' 替换为 'c')
    exection -> execution (插入 'u')
    

    思路

    先来整理一下,一共有3种操作情况:

    • 情况1:word1添加一个字符
    • 情况2:word1减少一个字符
    • 情况3:word1替换一个字符

    然后稍微思考一下就可以发现,第二条的操作可以转换成另一个字符串的加法运算。

    • 情况2:word1减少一个字符串 -> word2添加一个字符

    这两个操作的次数一定是一样的。

    然后这个问题看上去无从下手,这个时候就可以考虑能不能找到每次的状态间的关联,然后我们可以通过分别计算3种可能性得出以下3点规律。

    为了方便理解这里拿出具体的例子好了,就如同题目的例子

    1. 假设horse转换到ro需要a步,然后只考虑情况1,则horse,转换到ros最多需要a+1步。原因是可以通过a步先把horse转换成ro,再通过一步加上一个s
    2. 反之亦然,只考虑情况2,假设ros转换到hors需要b步,则ros转换到horse最多需要b+1步。
    3. 接下来,假设horse转换到ro需要c步,只考虑情况3,则horse,转换到ros,最多需要c+1步。当然因为只能转换字符就先假设两个字符长度相等,通过最多一步把最后一位转换成正确的字符,然后再通过c步转换其他的就可以了。

    然后上述3种情况就包含了所有可能的变化了,一次只能执行一种变化,所以总共需要的最少次数,一定是3者中最少的那一个,所以是min(a, b, c)

    而刚刚思考的是变化最多的情况,那最少又是如何,稍加思考就可以得出,对于各种情况,变化最少的情况是最后一位相同,假设abc变化到ab需要n步,那abc变化到ab+c,也就是最后一位相同,就是变化最少的情况了。

    那么对于上面的3种假设最少的变化步数又是多少,整理一下

    1. 只能用加法的情况下,原本a步,最后一位一样最少还是要a+1步,因为无论最后一位是否一样都需要加一个字符。
    2. 反之亦然,原本b步,最少b+1步。
    3. 只能用转换的情况下,原本c步,可以发现,最少还是c步,因为长度一样,最后多了一位但不需要变化,操作数量不变。

    所以整理以上逻辑,可以得出类似的状态转移方程式:

    假设word1转换到word2需要的3种情况的次数分别是a, b, c次,也就是

    • 情况1:word1添加一个字符 - a
    • 情况2:word2添加一个字符 - b
    • 情况3:word1替换一个字符 - c

    则word1转换到word2+任意字符的次数是:

    当 最后一位相同时
        次数 = min(a + 1, b + 1, c)
    当 最后一位不同时
        次数 = min(a, b, c) + 1
    

    但是但是,这个里面怎么看都有3个未知量,小学就学过是这种是解不出来的。

    所以我们至少需要找出一种实际值,于是我们可以思考到,假设一个字符串为空,那么对于任意一个字符,他一定是需要1次变化,这也就是情况1和情况2,而对于情况3,空字符串转换到空字符串毫无疑问是0次。

    所以可以写出如下的二维矩阵:

        ''  r   o   s
    
    ''  0   1   2   3
    
    h   1
    
    o   2
    
    r   3
    
    s   4
    
    e   5
    
    

    第一行代表''分别转换到r, ro, ros的次数,第一列则表示''分别转换到horse中到每个字符为止的字符串的次数。

    而对角线就表示了到当前位置为止的字符串相互转换的次数,比如左上角的空转换到空为0。

    这样我们就相当于有了初始的a, b, c的值,接下来只要按照我们上面的规则填充好就可以了。

        ''  r   o   s
    
    ''  0   1   2   3
    
    h   1   1   2   3
    
    o   2   2   1   2
    
    r   3   2   2   2
    
    s   4   3   3   2
    
    e   5   4   4   3
    
    

    对于每一个点x, y,相当于到x为止的字符串,转换到到y为止字符串需要的最少次数,就是我们需要的编辑距离,那么右下角那个点就是最终结果了,需要3次。

    接下来我们只要按照上面这个矩阵的计算逻辑,把代码写出来即可。

    代码

    /**
     * @param {string} word1
     * @param {string} word2
     * @return {number}
     */
    var minDistance = function(word1, word2) {
        let m = word1.length
        let n = word2.length
        // 初始化第一行的数据
        let dp = Array(n + 1)
        for (let i = 0; i <= n; i++) {
            dp[i] = i
        }
    
        // 用prev记录上一行的数据
        let prev
        // 从第二行开始遍历
        for (let i = 1; i <= m; i++) {
            prev = dp
            // 置空当前行,并设置当前行的第一位
            dp = [i]
    
            // 从第二行开始遍历
            for (let j = 1; j <= n; j++) {
                // 此处的判断参考上面的状态转移方程式
                if (word1[i - 1] === word2[j - 1]) {
                    dp[j] = Math.min(
                        dp[j - 1] + 1,
                        prev[j] + 1,
                        prev[j - 1]
                    )
                } else {
                    dp[j] = Math.min(
                        dp[j - 1],
                        prev[j],
                        prev[j - 1],
                    ) + 1
                }
            }
        }
    
        return dp[n]
    };
    

    大概的复杂度如下:

    • 时间复杂度是O(mn),因为对所有元素都遍历了一遍。
    • 空间复杂度则为O(n),因为一共创建了两个长度为n的数组。

    总结

    上述的距离又叫做莱文斯坦距离,是常用的编辑距离表示方法。

    而对于vue-cli来说,内部使用的是leven这个库去计算的,内部对字符串进行了其他细节优化,有兴趣的可以去看一下源代码。

    前端虽然对算法直接使用比较少但是算法还是无处不在。

    参考

    • 编辑距离
    • leven
    • 原文地址

    起源地下载网 » 实现一个did you mean效果

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元