3

トリッキーな問題が発生しました。次のコードは、単語を長さのチャンクに分割することになっていますnumOfChar。関数はそれ自体を呼び出すため、結果のリスト(res)を関数内に含めることはできません。しかし、それをグローバル変数として外部に保持すると、異なる入力値で関数を呼び出すたびに、resクリアされないため、間違った結果になります。

誰かが私を助けることができますか?

コードは次のとおりです(興味がある場合は、これはPySchools.comの問題7-23です)。

res = []

def splitWord(word, numOfChar):        
    if len(word) > 0:
        res.append(word[:numOfChar])
        splitWord(word[numOfChar:], numOfChar)    
    return res

print splitWord('google', 2)
print splitWord('google', 3)
print splitWord('apple', 1)
print splitWord('apple', 4)
4

3 に答える 3

5

純粋な再帰関数はグローバル状態を変更してはなりません。これは副作用としてカウントされます。

追加と再帰の代わりに、これを試してください:

def splitWord(word, numOfChar): 
    if len(word) > 0:
        return [word[:numOfChar]] + splitWord(word[numOfChar:], numOfChar)
    else:
        return []

ここでは、単語を一度に 1 つずつ断片に分割し、呼び出しごとに下に移動し、次に上に移動しながら断片をリストに再構築します。

これは末尾再帰と呼ばれる一般的なパターンです。

PS @e-satisが指摘しているように、再帰はPythonでこれを行う効率的な方法ではありません。ジェネレーターを使用して問題を解決するためのより Pythonic な方法と共に、末尾再帰のより精巧な例については、@e-satis の回答も参照してください。

于 2012-05-29T18:30:00.910 に答える
2

ここでは再帰は完全に不要です。

def splitWord(word, numOfChar):
   return [word[i:i+numOfChar] for i in xrange(0, len(word), numOfChar)]

再帰的な解決策を主張する場合は、グローバル変数を使用しないことをお勧めします (何が起こっているのかを判断するのが非常に難しくなります)。これを行う1つの方法は次のとおりです。

def splitWord(word, numOfChar):
   if len(word) > 0:
      return [word[:numOfChar]] + splitWord(word[numOfChar:], numOfChar)
   else:
      return []
于 2012-05-29T18:29:48.473 に答える
2

@Helgiの回答を詳しく説明するために、よりパフォーマンスの高い再帰的な実装を次に示します。2 つのリストを合計するのではなく、リストを更新します (その結果、毎回新しいオブジェクトが作成されます)。

このパターンでは、リスト オブジェクトを 3 番目のパラメーターとして渡す必要があります。

def split_word(word, num_of_chars, tail):

    if len(word) > 0:
        tail.append(word[:num_of_chars])
        return split_word(word[num_of_chars:], num_of_chars, tail)

    return tail

res = split_word('fdjskqmfjqdsklmfjm', 3, [])

この形式のもう 1 つの利点は、末尾再帰の最適化が可能になることです。Pythonではそういう最適化を行う言語ではないので使い物になりませんが、このコードをErlangやLispに翻訳すればタダで手に入るでしょう。

Python では、再帰スタックによって制限されており、そこから抜け出す方法がないことを思い出してください。これが、再帰が推奨される方法ではない理由です。

yieldand itertools(ジェネレーターを操作するモジュール)を使用して、ジェネレーターを使用する可能性が最も高いでしょう。これは、イテラブルをチャンクに分割できる関数の非常に良い例です。

from itertools import chain, islice

def chunk(seq, chunksize, process=iter):
    it = iter(seq)
    while True:
        yield process(chain([it.next()], islice(it, chunksize - 1)))

Python の学習を開始すると少し複雑になるため、今すぐ完全に理解できるとは思っていませんが、これを見てその存在を知ることができるのは良いことです。後で戻ってきます (Python 反復ツールは最初は圧倒されます)。

このアプローチの利点は次のとおりです。

  • 文字列だけでなく、リスト、辞書、タプル、ストリーム、ファイル、セット、クエリセットなど、あらゆるイテラブルをチャンクできます...
  • 任意の長さの iterable を受け入れ、長さが不明なものも受け入れます (ここではバイト ストリームを考えてください)。
  • ジェネレーターの最も良い点は、その場で値を 1 つずつ生成し、次の計算の前に前の結果を保存しないことであるため、ほとんどメモリを消費しません。
  • 任意の性質のチャンクを返します。つまり、x 個の文字のチャンク、x 個のアイテムのリスト、さらには x 個のアイテムを吐き出すジェネレーター (これがデフォルトです) を持つことができます。
  • ジェネレーターを返すため、他のジェネレーターのフローで使用できます。1 つのジェネレーターから別のジェネレーターへのデータのパイプ処理 (bash スタイル) は、Python の優れた機能です。

関数と同じ結果を得るには、次のようにします。

In [17]: list(chunk('fdjskqmfjqdsklmfjm', 3, ''.join))
Out[17]: ['fdj', 'skq', 'mfj', 'qds', 'klm', 'fjm']
于 2012-05-29T21:10:26.477 に答える