Leetcode中遇到的一些問(wèn)題(1)

題號(hào):500, 鍵盤(pán)行

給定一個(gè)單詞列表,只返回可以使用在鍵盤(pán)同一行的字母打印出來(lái)的單詞。鍵盤(pán)如下圖所示。鍵盤(pán)分布圖如下:


image.png

這是一個(gè)比較容易但稍微有點(diǎn)麻煩的問(wèn)題。

class Solution:
    def findWords(self, words: List[str]) -> List[str]:
        keyboardtab = ["qwertyuiop", "asdfghjkl", "zxcvbnm"]
        kbdict = {}
        res = words
        for k, v in enumerate(keyboardtab):
            for i in v:
                kbdict[i] = k
        for word in words:
            for s in word[1:]:
                if kbdict[s.lower()] != kbdict[word[0].lower()]:
                    res.remove(word)
                    break
        return res

測(cè)試用例中,運(yùn)行得還可以,但提交后遇到一個(gè)案例輸出有誤。

>>> findWords(["abdfs", "ccdd","a", "qwwewm"])
['ccdd', 'a']

很顯然,正確答案不應(yīng)該包括"ccdd"上。代碼的整體邏輯問(wèn)題不大,可能是循環(huán)過(guò)程中出現(xiàn)的一些問(wèn)題。添加print的語(yǔ)句,查看words和word的變化。

    def findWords(self, words: List[str]) -> List[str]:
        keyboardtab = ["qwertyuiop", "asdfghjkl", "zxcvbnm"]
        kbdict = {}
        res = words
        for k, v in enumerate(keyboardtab):
            for i in v:
                kbdict[i] = k
        for word in words:
            print(words, word)
            for s in word[1:]:
                if kbdict[s.lower()] != kbdict[word[0].lower()]:
                    res.remove(word)
                    break
        return res

運(yùn)行樣例后結(jié)果如下:

>>> findWords(["abdfs", "ccdd","a", "qwwewm"])
['abdfs', 'ccdd', 'a', 'qwwewm'] abdfs
['ccdd', 'a', 'qwwewm'] a
['ccdd', 'a', 'qwwewm'] qwwewm
['ccdd', 'a']

問(wèn)題很明顯了,我們的res、words引用了同一個(gè)列表,當(dāng)調(diào)用res.remove(word)時(shí),words也被修改掉了。因此造成外層循環(huán)的錯(cuò)誤,最終導(dǎo)致結(jié)果出錯(cuò)。解決問(wèn)題的方法也很簡(jiǎn)單,res=words修改成res=words.copy()即可,把for語(yǔ)句中的words改成words[:]更Pythonic一些。在循環(huán)過(guò)程中不要對(duì)正在循環(huán)的對(duì)象做修改,在很多入門(mén)教程中都提到過(guò)很多次,但不經(jīng)意間總還是會(huì)犯這種錯(cuò)誤,引以為鑒吧!


那么,當(dāng)在for循環(huán)中修改正在迭代的對(duì)象,實(shí)際上又發(fā)生了些什么呢?
從上面的錯(cuò)誤案例中,看到的似乎循環(huán)中記住了某個(gè)固定的索引,然后根據(jù)這個(gè)索引返回變化中的列表中對(duì)應(yīng)的元素。但實(shí)際上是這樣么?借助Python官方文檔,嘗試尋找一下答案。

for語(yǔ)句做了些什么?

引用自官方文檔
for 語(yǔ)句用于對(duì)序列(例如字符串、元組或列表)或其他可迭代對(duì)象中的元素進(jìn)行迭代:

for-stmt :: = for 目標(biāo)列表 in 表達(dá)式列表: 執(zhí)行子句 else:執(zhí)行子句

  1. 表達(dá)式列表調(diào)用__iter__()函數(shù),生成一個(gè)迭代器對(duì)象(iterator);
  2. 為迭代器的每一項(xiàng)執(zhí)行一次子句,具體次序與迭代器的次序相同(迭代器通過(guò)調(diào)用__next__()來(lái)獲取下一項(xiàng)),每一項(xiàng)按照標(biāo)準(zhǔn)賦值語(yǔ)句給目標(biāo)列表。
  3. 當(dāng)所有項(xiàng)耗盡時(shí)(表達(dá)式列表為空或者迭代器引發(fā) StopIteration),執(zhí)行else 的子句,并退出循環(huán)。
    因此當(dāng)在循環(huán)中修改表達(dá)式列表時(shí),每次循環(huán)都會(huì)根據(jù)新列表生成新的迭代器對(duì)象,即便不看官方文檔,這和我們的印象也是符合的。但不斷變化的迭代器是如何保留一個(gè)類(lèi)似索引的變量來(lái)從新列表中進(jìn)行賦值操作的又是一個(gè)問(wèn)題。
    好在文檔中連這個(gè)問(wèn)題也考慮到了

序列在循環(huán)過(guò)程中會(huì)有一個(gè)內(nèi)部計(jì)數(shù)器來(lái)追蹤下一個(gè)要使用的項(xiàng),每一次迭代都會(huì)使這個(gè)項(xiàng)增遞增,當(dāng)計(jì)數(shù)器的值達(dá)到列表的長(zhǎng)度時(shí)循環(huán)即停止。這意味著如果在子句中刪除了序列當(dāng)前或之前的一項(xiàng),其后一項(xiàng)再下次循環(huán)中將會(huì)被跳過(guò)。

這將會(huì)帶來(lái)相當(dāng)多的困擾,文檔也貼心的介紹了使用索引 [:] 創(chuàng)建副本作為表達(dá)式列表的方法。
之前關(guān)注的只是循環(huán)中序列取值時(shí)受到的影像,實(shí)際上在循環(huán)中修改序列對(duì)迭代次數(shù)的影像也挺值得關(guān)注,以下就是一個(gè)例子。

>>> l = list(range(10))
>>> for i in l:
        print(l, i)
        l.pop(i)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 0
[1, 2, 3, 4, 5, 6, 7, 8, 9] 2
[2, 3, 4, 5, 6, 7, 8, 9] 4
[3, 4, 5, 6, 7, 8, 9] 6
[4, 5, 6, 7, 8, 9] 8

每次循環(huán)len(l) - 1,內(nèi)部計(jì)數(shù)器 + 1,因此當(dāng)進(jìn)行到第5次的時(shí)候,len(l) == 內(nèi)部計(jì)數(shù)器,循環(huán)結(jié)束。剛好輸出了所有偶數(shù)項(xiàng)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容