この返信が長くなることをお許しください。
私の理解が正しければ、括弧の単純な構文チェックを行って、括弧のバランスが適切に保たれていることを確認してください。あなたの質問では、単純なカウントに基づくテストを指定していますが、他の人が指摘しているように、これは「([)]」のようなものをキャッチしません。
また、あなたのコードの他の側面について建設的な批判をしたいと思います。
まず、コマンド ラインからファイル名を取得し、プロンプトを表示しないようにすることをお勧めします。これは、ファイル名を何度も入力しなくても、開発時に簡単にプログラムを繰り返し実行できるようにするためです。これがあなたのやり方です:
$ python foo.py
ファイル名を与える: data
[何らかの出力]
$ python foo.py
ファイル名を与える: data
[何らかの出力]
$ python foo.py
ファイル名を与える: data
[何らかの出力]
毎回ファイル名を入力する必要があります。プログラムを複数回実行するためにコマンドを入力する必要はありません。初回以降は、矢印キーを使用してシェルのコマンド履歴から取得できます。コマンド ラインからファイル名を取得する場合は、代わりに次のようにします。
$ python foo.py testfile
[一部の出力]
$ python foo.py testfile
[一部の出力]
$ python foo.py testfile
[一部の出力]
これにより、2 回目のテスト時に、上矢印キーと Enter キー以外を入力する必要がなくなります。これはささやかな便利さですが、重要なことです。ソフトウェアを開発しているときは、小さなことでさえ煩わしくなることがあります。それは、長い散歩をしているときに足の下に大きな砂粒のようなものがあります.
Python でコマンド ライン引数にアクセスするには、リストが必要です
sys.argv
。プログラムに関連する変更:
import sys
filename = sys.argv[1]
プロンプトを表示したい場合は、組み込みinput
関数以外のものを使用する必要があります。ユーザーが入力したものは何でも Python 式として解釈し、あらゆる種類の問題を引き起こします。を使用して読み取ることができますsys.stdin.readline
。
とにかく、filename
変数に安全に格納されたファイルの名前を取得しました。それを何とかする時が来ました。あなたのparentheses
関数はほとんどすべてのことを行いますが、経験上、それが最善の方法ではないことがよくあります。代わりに、すべての関数は 1 つのことだけを実行する必要がありますが、それをうまく実行する必要があります。
ファイルを実際に開いたり閉じたりする部分は、実際のカウントとは別にしておくことをお勧めします。これにより、残りのことを心配する必要がないため、カウントのロジックが簡素化されます。コード内:
import sys
def check_parentheses(f):
pass # we'll come to this later
def main():
filename = sys.argv[1]
try:
f = file(filename)
except IOError:
sys.stderr.write('Error: Cannot open file %s' % filename)
sys.exit(1)
check_parentheses(f)
f.close()
main()
再配置に加えて、他にもいくつか変更を加えました。まず、エラー メッセージを標準エラー出力に書き込みます。これは適切な方法であり、シェル ユーザーが出力をリダイレクトすることに驚かされることが少なくなることを意味します。(それがあなたにとって意味をなさない場合でも、心配する必要はありません。今のところ、それを当然のこととして受け入れてください。)
次に、エラーが発生した場合は、プログラムを終了しsys.exit(1)
ます。これは、プログラムを開始した人に、プログラムが失敗したことを示します。Unix シェルでは、これにより次のようなことができます。
if python foo.py inputfile
then
echo "inputfile is OK!"
else
echo "inputfile is BAD!"
fi
もちろん、シェル スクリプトは、単に成功または失敗を報告するだけでなく、もっと興味深いことを行う可能性があります。たとえば、壊れたファイルをすべて削除したり、ファイルを書いた人に電子メールで修正を依頼したりします。美点は、チェッカー プログラムを作成するあなたが気にする必要がないことです。プログラムの終了コードを適切に設定するだけで、あとはシェル スクリプトの作成者に任せることができます。
次のステップは、実際にファイルの内容を読み取ることです。これは、さまざまな方法で行うことができます。最も簡単な方法は、次のように行ごとに行うことです。
for line in f:
# do something with the line
次に、行の各文字を確認する必要があります。
for line in f:
for c in line:
# do something with the character
これで、実際に括弧のチェックを開始する準備が整いました。他の人が示唆しているように、スタックはこれに適したデータ構造です。スタックは基本的にリスト (または配列) であり、一方の端にアイテムを追加し、逆の順序で取り出します。コインの山と考えてください。一番上にコインを追加できます。一番上のコインを削除できますが、真ん中または一番下のコインを削除することはできません。
(まあ、できますし、そうするのも巧妙なトリックですが、コンピューターは単純な獣であり、手品によって動揺してしまいます。)
Python リストをスタックとして使用します。項目を追加するにはリストのappend
メソッドを使用し、削除するにはpop
メソッドを使用します。例:
stack = list()
stack.append('(')
stack.append('[')
stack.pop() # this will return '['
stack.pop() # this will return '('
スタックの一番上のアイテムを見るには、stack[-1]
(つまり、リストの最後のアイテム) を使用します。
スタックを次のように使用します: 開始括弧 ('(')、ブラケット ('[')、またはブレース ('{') を見つけると、それをスタックに置きます。終了括弧を見つけると、チェックしますスタックの一番上にある項目を確認し、それが最後の項目と一致することを確認します. そうでない場合は、エラーを出力します. 次のように:
def check_parentheses(f):
stack = list()
for line in f:
for c in line:
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
if stack[-1] != '(':
print 'Error: unmatched )'
else:
stack.pop()
elif c == ']':
if stack[-1] != '[':
print 'Error: unmatched ]'
else:
stack.pop()
elif c == '}':
if stack[-1] != '{':
print 'Error: unmatched }'
else:
stack.pop()
これにより、さまざまな種類の一致しない括弧が検出されるようになりました。問題が見つかった行と列を報告することで、少し改善できます。行番号と列番号のカウンターが必要です。
def error(c, line_number, column_number):
print 'Error: unmatched', c, 'line', line_number, 'column', column_number
def check_parentheses(f):
stack = list()
line_number = 0
for line in f:
line_number = line_number + 1
column_number = 0
for c in line:
column_number = column_number + 1
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
if stack[-1] != '(':
error(')', line_number, column_number)
else:
stack.pop()
elif c == ']':
if stack[-1] != '[':
error(']', line_number, column_number)
else:
stack.pop()
elif c == '}':
if stack[-1] != '{':
error('}', line_number, column_number)
else:
stack.pop()
error
また、エラー メッセージを実際に出力するためのヘルパー関数 を追加した方法にも注意してください。エラー メッセージを変更する場合は、1 か所で行うだけで済みます。
もう 1 つの注目すべき点は、終了記号を処理するケースがすべて非常に似ていることです。それを関数にすることもできます。
def check(stack, wanted, c, line_number, column_number):
if stack[-1] != wanted:
error(c, line_number, column_number)
else:
stack.pop()
def check_parentheses(f):
stack = list()
line_number = 0
for line in f:
line_number = line_number + 1
column_number = 0
for c in line:
column_number = column_number + 1
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
check(stack, '(', ')', line_number, column_number)
elif c == ']':
check(stack, '[', ']', line_number, column_number)
elif c == '}':
check(stack, '{', '}', line_number, column_number)
プログラムはさらに改良することができますが、今のところはこれで十分です。最後にコード全体を含めます。
このプログラムは、さまざまな種類の括弧のみを考慮することに注意してください。Python プログラム全体の構文の正確性を本当にチェックしたい場合は、Python の構文をすべて解析する必要があります。これはかなり複雑で、1 つのスタック オーバーフローの回答には多すぎます。それが本当に必要な場合は、フォローアップの質問をしてください。
プログラム全体:
import sys
def error(c, line_number, column_number):
print 'Error: unmatched', c, 'line', line_number, 'column', column_number
def check(stack, wanted, c, line_number, column_number):
if stack[-1] != wanted:
error(c, line_number, column_number)
else:
stack.pop()
def check_parentheses(f):
stack = list()
line_number = 0
for line in f:
line_number = line_number + 1
column_number = 0
for c in line:
column_number = column_number + 1
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
check(stack, '(', ')', line_number, column_number)
elif c == ']':
check(stack, '[', ']', line_number, column_number)
elif c == '}':
check(stack, '{', '}', line_number, column_number)
def main():
filename = sys.argv[1]
try:
f = file(filename)
except IOError:
sys.stderr.write('Error: Cannot open file %s' % filename)
sys.exit(1)
check_parentheses(f)
f.close()
main()