簡體   English   中英

使用 collections.deque 從堆棧中刪除(不彈出)最后一個/最右邊的 object 事件的最佳方法

[英]Best way to remove (not pop) last/rightmost object occurrence from a stack using collections.deque

我正在使用標准的collections.deque來編寫一個 LIFO 堆棧,其中每個 object 可能會出現多次,但現在我陷入了用例,用於刪除給定 object 的最后一次出現(但不是堆棧最右邊的 object .).

雖然這三種方法存在對應的appendleftextendleftpopleft ,但不存在removeright (也不存在indexright )。 所以以下是不可能的。

import collections

stack = collections.deque()

a = object()
b = object()
c = object()

stack.append(a)
stack.append(b)
stack.append(c)
stack.append(a)
stack.append(b)
stack.append(c)

list(stack) # [a, b, c, a, b, c]

stack.removeright(b)  # Fat chance

list(stack)  # Whish: [a, b, c, a, c] and *NOT* [a, c, a, b, c]

我錯過了一些明顯的東西嗎?

現在我要進行雙重反向調用

def removeright(stack, item):
    stack.reverse()
    try:
        stack.remove(item)
    finally:
        stack.reverse()

但這感覺不對。 我擔心這種方法的效率低下和潛在的陷阱。

我總是可以“向后”使用隊列(實際上很傳統),使用appendleftremove ,但我想保留“追加”語義並且仍然不必編寫一個薄包裝器將每個右/左堆棧方法修補為左/右隊列方法。

有人會分享他們對該主題的見解/經驗嗎?

我能找到的唯一其他方法是從最后一個 position 開始遞減,直到找到您要查找的項目。

def for_remove(stack, s):
    # Optionally check if the element is in the stack first
    if s not in stack:
        return

    for i in range(len(stack)-1, -1, -1):
        if s == stack[i]:
            del stack[i]
            break

雖然這避免了兩次反轉操作,但您仍然必須在 Python 中循環遍歷雙端隊列。雖然笨拙,但您的解決方案具有使用速度優化的編譯方法的優勢,並且很可能比上述解決方案更快。

問題是:這種優化水平是否適合您當前的任務? 您是否需要將刪除時間縮短為幾微秒? 在大多數情況下,您的容器足夠小,任何優化對運行時的影響都可以忽略不計。

由於我無法想出一個簡單的解決方案來解決這個問題,我現在相信犧牲一點語義是值得的,只需“向后”使用collections.deque object 即可。

簡單地說:將“左”端視為堆棧的“頂部”。

獎勵 1:當不彈出堆棧時,以 LIFO 順序自然迭代

獎勵 2:對於可能的情況,當刪除的 object 接近最后一個 position(現在是“最左邊”)時,CPython 的 deque.remove 將執行內部數據結構的最小旋轉。 刪除最后一項時完全沒有操作。 所以這應該很快。 螺絲清單!

import collections

stack = collections.deque()

stack.appendleft("a")
stack.appendleft("b")
stack.appendleft("c")
stack.appendleft("a")
stack.appendleft("b")
stack.appendleft("c")

list(stack)  # [c, b, a, c, b, a]

stack.remove("b")

def consume():
    while stack:
        yield stack.popleft()

list(consume())  # yields [c, a, c, b, a]

缺點:問題已恢復,因為沒有簡單的公式可以刪除雙端隊列中給定 object 的第一個(現在是“最右邊”)出現。 因此,使用具有這種“反向語義”的雙端隊列對象應該取決於您的刪除/索引用例是針對堆棧的最后一次出現還是第一次出現。

對於一般解決方案,請繼續閱讀。


如果您真的想刪除雙端隊列 object 中最右邊的匹配項。

事實證明, deque.remove是通過雙重旋轉在內部實現的,直到所需的項目結束,當它被彈出時,然后雙端隊列 object 被旋轉回原始的 state。不過,這是一個 CPython 實現細節。

因此,一個一致且有效的解決方案可能是迭代地找到乘法存在的最后一個索引 object 並且還進行雙重旋轉而不是雙重回歸。

對於我的用例,要刪除的項目通常是整個堆棧中實際的最后一項。 因此,以下實現傾向於旋轉盡可能少的位置,直到不旋轉且僅從右側彈出的極端情況。

此外,最好保留在項目不存在時引發的 ValueError,因此不可避免的deque.index調用。

def index_right(stack, item):
    idx = stack.index(item)
    for _ in range(1, stack.count(item)):
        idx = stack.index(item, idx + 1)
    return idx

def remove_right(stack, item):
    rot = len(stack) - index_right(stack, item) - 1
    d.rotate(rot)
    try:
        d.pop()
    finally:
        d.rotate(-rot)

一個缺點是:對於一個大堆棧,這可能需要很多不必要的值比較,因為deque.index操作,搜索的項目通常在最后。 因此,在某些情況下,原始問題的還原-刪除-還原解決方案可能仍優於此答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM