簡體   English   中英

Python 遞歸 function 不返回

[英]Python recursive function not returning

我正在嘗試編寫一個遞歸 function 來返回排序單詞列表中單詞的 position,或者在找不到單詞時返回None 以下是代碼:

def partition(t,kw,upper,lower):
    if ((upper-lower) > 1):
        pivot = lower + (upper-lower)//2
        print("pivot is now {}".format(pivot))
        if (kw >= t[pivot]):
            lower = pivot
            print("searching on the right",upper,lower)
            return(partition(t,kw,upper,lower))
        
        elif (kw <= t[pivot-1]):
            upper = pivot-1
            print("searching on the left",upper,lower)
            return(partition(t,kw,upper,lower))

        else:
            return None 
            #that means the keyword is between t[pivot-1] and t[pivot]
            #which means it doesnt exist in the list
    if (upper - lower) <= 1:
        if (kw == t[upper]):
            print("found element {} at upper bound {}".format(kw,upper))
            return(upper)
        elif (kw == t[lower]):
            print("found element {} at lower bound {}".format(kw,lower))
            return(lower)
        else:
            return None

def search(t, kw):
    u = len(t)
    partition(t,kw,u,0)

如您所見,每當我調用它時,我都會返回 function(不返回是使用遞歸調用的常見錯誤)。 同時,這是我使用的示例數組:

['', '', '150', '150', '1997,with', '36', '49', 'An', 'Annotated', 'Annotation', 'Bibliography', 'China', 'Chinese', 'Chinese', 'Classical', 'During', 'Dynasty', 'Hong', 'Hong', 'Hong', 'Hong', 'Hong', 'In', 'It', 'Kong', 'Kong', 'Kong', 'Kong,', 'Kong.', 'Mainland', 'Poets', 'Qing', 'They', 'Together,', 'Writings', 'a', 'a', 'a', 'a', 'a', 'active', 'activity,', 'addition', 'almost', 'and', 'and', 'and', 'and', 'and', 'and', 'annotations', 'anthologies', 'basic', 'been', 'before.', 'bibliographic', 'bibliographies,', 'by', 'carry', 'ci-poetry', 'ci-poetry', 'classical', 'collected', 'commentaries', 'compilation,', 'compilations', 'compiled', 'covered,', 'development', 'events', 'focused', 'form', 'form', 'formation,', 'from', 'from', 'has', 'help', 'hidden', 'in', 'in', 'in', 'in', 'includes', 'individual', 'information', 'information', 'introduces', 'invaluable', 'is', 'late', 'literary', 'literati', 'literature', 'literature.', 'membership,', 'most', 'never', 'not', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'offer', 'on', 'on', 'on', 'on', 'order', 'over', 'past', 'periods', 'pioneer', 'pity', 'poetic', 'poetry', 'poetry', 'poetry', 'poet’s', 'political', 'previous', 'previously', 'published', 'refuge', 'research', 'sequel', 'shi-', 'shi-', 'societies', 'societies', 'societies', 'societies.', 'societies.', 'splendor,', 'that', 'that', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'these', 'these', 'these', 'this', 'times', 'to', 'to', 'to', 'to', 'to', 'to', 'took', 'tools', 'topic', 'tradition.', 'turmoil', 'two', 'uncover', 'understand', 'understanding', 'unique', 'up', 'various', 'very', 'volume,', 'which', 'works,', 'worthwhile', 'would', 'years,']

我在搜索"Qing"這個詞,它應該在第 31 個 position 中結束。 現在似乎 function 可以找到我需要的單詞,但無法返回:

the result is
pivot is now 92
searching on the left 91 0
pivot is now 45
searching on the left 44 0
pivot is now 22
searching on the right 44 22
pivot is now 33
searching on the left 32 22
pivot is now 27
searching on the right 32 27
pivot is now 29
searching on the right 32 29
pivot is now 30
searching on the right 32 30
pivot is now 31
searching on the right 32 31
found element Qing at lower bound 31
None

我嘗試搜索與遞歸函數相關的問題,但似乎沒有太多相關內容。 SO上的大多數帖子都是由於沒有返回遞歸function引起的,我真的不確定這里出了什么問題。

您的 function 似乎工作正常。 我想你只是忘了從搜索返回,即

def search(t, kw):
    u = len(t)
    lower = partition(t,kw,u,0)
    return lower

輸出

...
searching on the right 32 30
pivot is now 31
searching on the right 32 31
found element Qing at lower bound 31
31

單一用途,簡化

因為它的寫入partition是一個很大的 function,它有很多參數試圖做很多事情。 是否有可能將它的一些關注點分開並使其更有用? 我們能否抽象出一些復雜性以使 function 更易於編寫且不易出錯?

要查看改進partition的方法,我們將首先查看 Python 的range數據結構。 在您的實現中,您使用了以下算術來得出解決方案 -

  • upper - lower > 1
  • lower + (upper - lower) // 2
  • kw <= t[pivot - 1]
  • upper = pivot - 1
  • upper - lower <= 1

上面的認知開銷是巨大的,並且出現一次性錯誤的可能性很高。 使用范圍r ,我們可以找到 pivot p並使用[]有效切片,同時避開我們所有的頭痛 -

r = range(1,100)
p = len(r) // 2
print("input", r)        # input range(1, 100)
print("pivot", p)        # pivot 49
print("guess", r[p])     # guess 50
print("left ", r[0:p])   # left  range(0, 50)
print("right", r[p+1:])  # right range(51, 100)

這是應用於不同范圍的相同算法 -

r = range(44,66)
p = len(r) // 2
print("input", r)        # input range(44, 66)
print("pivot", p)        # pivot 11
print("guess", r[p])     # guess 55
print("left ", r[0:p])   # left  range(44, 55)
print("right", r[p+1:])  # right range(56, 66)

分割

使用這些屬性,我們可以編寫一個關注單個范圍的通用partition -

def partition(r):
  if len(r) == 0: return (yield None)             # base case
  pivot = len(r) // 2                             # pivot
  (a, b) = yield r[pivot]                         # guess
  if a < b: yield from partition(r[0:pivot])      # recur on left
  elif a > b: yield from partition(r[pivot + 1:]) # recur on right
  else: return None                               # equal

上面,我們的 function 不關心單詞列表t 、要搜索的關鍵字kw ,也不關心單獨lowerupper 最重要的算術被最小化,僅使用// 2來找到pivot+1來計算右側范圍。 范圍錯誤的可能性已大大降低。

搜索

現在我們可以將search寫成partition的一種特殊化形式,它接受一個單詞列表t和一個要搜索的關鍵字kw -

def search(t, kw):
  def loop(p, v):                     # loop
    i = p.send(v)                     # get guess
    if i is None: return None         # base case
    elif kw == t[i]: return i         # found it! return i
    else: return loop(p, (kw, t[i]))  # recur with comparables
  r = range(0, len(t))                # init range
  return loop(partition(r), None)     # loop over partition of r 

這是一個簡單的partition loop 在每次迭代中,我們從分區生成器p中獲得一個索引i 如果沒有索引,我們已經到了搜索的結尾。 否則,如果kw等於t[i] ,我們找到了答案並可以返回它。 否則kw等於t[i] ,所以我們在p發送(kw, t[i])重復作為下一個比較。

使用我們的search function 很簡單 -

r = search(words, "Qing")
if r is None:
  print("no result")
else:
  print(f"found at index: {r}")
found at index: 31

如果我們搜索一個不存在的詞——

r = search(words, "Wxyz")
if r is None:
  print("no result")
else:
  print(f"found at index: {r}")
no result

小心輕放

Python 生成器具有獨特的接口,它們是少數例外情況之一,其中使用try..except程序控制流是可以接受的。 上面我明確地在partitionyield None並檢查loop中的None但 Python 提供了一個StopIteration異常,它允許我們消除一些混亂 -

def partition(r):
  if len(r) == 0: return                    # stop
  p = len(r) // 2                           # pivot
  (a, b) = yield r[p]                       # guess
  if a < b: yield from partition(r[0:p])    # recur left
  if a > b: yield from partition(r[p+1:])   # recur right
                                            # (implict) return None

現在,當我們編寫search時,我們可以跳過對None的空檢查,並繼續假設我們有一個有效的猜測。 否則,如果生成器耗盡,我們會捕獲停止異常並返回None -

def search(t, kw):
  def loop(p, v):                           # loop
    i = p.send(v)                           # get guess 
    if kw == t[i]: return i                 # match?
    return loop(p, (kw, t[i]))              # recur
  try:
    r = range(0, len(t))
    return loop(partition(r), None)
  except StopIteration:                     # base case
    return None

隨着時間的推移,我開始更喜歡這個接口,因為它減少了顯式空值和檢查它們的需要。 這些新的partitionsearch實現行為相同,並產生相同的 output。

解耦副作用

這里要注意的另一個重要事項是partition function 中不存在print等副作用。 這使其可用於程序的其他部分,這些部分可能會比較其他數據結構或希望以不同的方式格式化 output -

下面我們可以編寫一個簡單的search修改,它可以調試 output -

def search(t, kw):
  def loop(p, v):
    i = p.send(v)
    print(f"searching index {i}: {kw} == {t[i]}?") # one change
    if kw == t[i]: return i
    return loop(p, (kw, t[i]))
  try:
    r = range(0, len(t))
    return loop(partition(r), None)
  except StopIteration:
    return None

讓我們在調試到位的情況下查看示例的 output -

r = search(words, "Qing")
if r is None:
  print("no result")
else:
  print(f"found at index: {r}")
searching index 92: Qing == literati?
searching index 46: Qing == and?
searching index 23: Qing == It?
searching index 35: Qing == a?
searching index 29: Qing == Mainland?
searching index 32: Qing == They?
searching index 31: Qing == Qing?
found at index: 31

我們的第二個例子——

r = search(words, "Wxyz")
if r is None:
  print("no result")
else:
  print(f"found at index: {r}")
searching index 92: Wxyz == literati?
searching index 46: Wxyz == and?
searching index 23: Wxyz == It?
searching index 35: Wxyz == a?
searching index 29: Wxyz == Mainland?
searching index 32: Wxyz == They?
searching index 34: Wxyz == Writings?
no result

沒有遞歸

你會經常聽到人們在 Python 中談論遞歸。 因為二進制搜索非常有效,所以上面的遞歸解決方案需要一個巨大的輸入來溢出堆棧。 也就是說,如果我們願意,沒有什么能阻止我們使用迭代代替遞歸來編寫partitionsearch -

def partition(r):
  while len(r):
    p = len(r) // 2
    (a, b) = yield r[p]
    if a < b: r = r[0:p]
    if a > b: r = r[p+1:]
    if a == b: break
def search(t, kw):
  try:
    r = range(0, len(t))
    p = partition(r)
    v = None
    while True:
      i = p.send(v)
      if kw == t[i]: return i
      v = (kw, t[i])
  except StopIteration:
    return None

正如這個很好的答案所指出的,您忽略了在search function 中添加return語句。 但是,您的代碼中有一些可以改進的地方:

def partition(t, kw, upper, lower):
    if upper - lower > 1:
        pivot = lower + (upper - lower) // 2
        print("pivot is now {}".format(pivot))
        if kw >= t[pivot]:
            lower = pivot
            print("searching on the right", upper, lower)
            return partition(t, kw, upper, lower) 
        if kw <= t[pivot - 1]:
            upper = pivot - 1
            print("searching on the left", upper, lower)
            return partition(t, kw, upper, lower)
    if upper - lower <= 1:
        if kw == t[upper]:
            print("found element {} at upper bound {}".format(kw, upper))
            return upper 
        if kw == t[lower]:
            print("found element {} at lower bound {}".format(kw, lower))
            return lower

def search(t, kw):
    u = len(t)
    return partition(t, kw, u, 0)

lst = ['', '', '150', '150', '1997,with', '36', '49', 'An', 'Annotated', 'Annotation', 'Bibliography', 'China', 'Chinese', 'Chinese', 'Classical', 'During', 'Dynasty', 'Hong', 'Hong', 'Hong', 'Hong', 'Hong', 'In', 'It', 'Kong', 'Kong', 'Kong', 'Kong,', 'Kong.', 'Mainland', 'Poets', 'Qing', 'They', 'Together,', 'Writings', 'a', 'a', 'a', 'a', 'a', 'active', 'activity,', 'addition', 'almost', 'and', 'and', 'and', 'and', 'and', 'and', 'annotations', 'anthologies', 'basic', 'been', 'before.', 'bibliographic', 'bibliographies,', 'by', 'carry', 'ci-poetry', 'ci-poetry', 'classical', 'collected', 'commentaries', 'compilation,', 'compilations', 'compiled', 'covered,', 'development', 'events', 'focused', 'form', 'form', 'formation,', 'from', 'from', 'has', 'help', 'hidden', 'in', 'in', 'in', 'in', 'includes', 'individual', 'information', 'information', 'introduces', 'invaluable', 'is', 'late', 'literary', 'literati', 'literature', 'literature.', 'membership,', 'most', 'never', 'not', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'of', 'offer', 'on', 'on', 'on', 'on', 'order', 'over', 'past', 'periods', 'pioneer', 'pity', 'poetic', 'poetry', 'poetry', 'poetry', 'poet’s', 'political', 'previous', 'previously', 'published', 'refuge', 'research', 'sequel', 'shi-', 'shi-', 'societies', 'societies', 'societies', 'societies.', 'societies.', 'splendor,', 'that', 'that', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'the', 'these', 'these', 'these', 'this', 'times', 'to', 'to', 'to', 'to', 'to', 'to', 'took', 'tools', 'topic', 'tradition.', 'turmoil', 'two', 'uncover', 'understand', 'understanding', 'unique', 'up', 'various', 'very', 'volume,', 'which', 'works,', 'worthwhile', 'would', 'years,']
print(search(lst, "Qing"))

Output:

pivot is now 92
searching on the left 91 0
pivot is now 45
searching on the left 44 0
pivot is now 22
searching on the right 44 22
pivot is now 33
searching on the left 32 22
pivot is now 27
searching on the right 32 27
pivot is now 29
searching on the right 32 29
pivot is now 30
searching on the right 32 30
pivot is now 31
searching on the right 32 31
found element Qing at lower bound 31
31

實際上,也可以消除每秒的if語句(例如,刪除if行並取消縮進其內容) ,但為了便於閱讀,保留它們也可以。

此外,如果您使用的是 python 3,則可以將"Example number {}".format(5)元素替換為f"Example number {5}"

暫無
暫無

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

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