2012年5月26日土曜日

初めてのコンピュータサイエンス第8章解答

第8章の解答。以下ネタバレなので、読みたくない方は立ち去りましょう。問題文は載せませんので悪しからず。必要なら買いましょう。その前にこの章の個人的なメモ。

  • ファイルを読み込む際は関数の外でファイルのオープン・クローズを行うとよい。これは処理を1つの関数だけで済ませることができるためである。Webページやストリームなどを統一的に扱える。
  • ファイルの追加はopenする際の引数に'a'と与える
  • pp.161の注釈は「単一のタプルが返されている」ではなく、「単一のリストが返される」が正しい。おそらく誤植。

1

後ろから前に向かってファイル全体を読み出すという意味をどう捉えるかが問題になりそうですが、行だけ後ろから前へと見るか、行の中身まで反転させるか悩ましいところではあります。今回は前者で考えています。

def read_reverse(file):
    rev = []
    for line in file:
        rev.insert(0, line)
    for line in rev:
        print line,

if __name__ == '__main__':
    f = open('data.txt', 'r')
    read_reverse(f)

2

タプルのタプルの方が扱いやすいのは、意味的なまとまりで区切っているためである。今回のコードでは、日付、緯度、経度、天気情報という意味でまとまっているので、データへのアクセスが単純化できる(経度は何番目だっけ?などと悩むことは減るだろう)。実は問題の解答がオライリーから提供されているが、誤りのように思われる。fieldsがタプルのタプルでないとループが適切に動かないためである。コード中の中で呼び出しているテキストファイルは提供されていないので、関数のコメントにある仕様に沿って作成する必要がある。

def read_weather_data(r):
    '''Read weather data from reader r in fixed-width format.
    The field widths are:
        4,2,2   YYYYMMDD (date)
        2,2,2   DDMMSS   (latitude)
        2,2,2   DDMMSS   (longitude)
        6,6,6   FF.FFF   (temp, deg. C; humidity, %; pressure, kPa)
    The result is a tuples of tuples,
 where each tuple is of the form:
    ((YY, MM, DD), (DD, MM, SS), (DD, MM, SS), (Temp, Hum, Press))'''
    fields = (((4, int), (2, int), (2, int)),       # date
              ((2, int), (2, int), (2, int)),       # latitude
              ((2, int), (2, int), (2, int)),       # longitude
              ((6, float), (6, float), (6, float))) # data
    result = []
    for line in r:
        start = 0
        for grp in fields:
            record = []
            for (width, target_type) in grp:
                text = line[start:start+width]
                field = target_type(text)
                record.append(field)
                start += width
            result.append(tuple(record))
    return result

if __name__ == '__main__':
    f = open('ex08_2_data.txt', 'r')
    ret = read_weather_data(f)
    print ret

3

# -*- encoding: sjis -*-
''' fieldが固定幅の場合、先頭からの位置の場合のどちらにも対応'''

def read_weather_data(r, fields):
    result = []

    for line in r:
        start = 0
        a_line = []
        for (width, target_type) in fields:
            text = line[start:start+width]
            field = target_type(text)
            a_line.append(field)
            start += width
        result.append(tuple(a_line))
    return result

def transform_fields(fields, last_idx):
    res = []

    for i in range(len(fields)):
        if i != len(fields)-1:
            width = fields[i+1][0] - fields[i][0]
        else:
            width = last_idx - fields[i][0]
        res.append((width, fields[i][1]))
    return(tuple(res))

def wrapper_function(data, fields):
    ''' 先頭の幅に関する値で入力のfieldの種類がどちらか判別する。
        0ならば、いったんfieldsを変換してread_weather_dataに入れる。
        これによりread_weather_dataの引数は1つ増えることになる。
    '''
    if fields[0][0] == 0:
        # 38はハードコードだが、幅の値は仕様であり、既知とする。
        fields = transform_fields(fields, 38)
    ret = read_weather_data(data, fields)
    return ret

if __name__ == '__main__':
    f = open('ex08_2_data.txt', 'r')
    '''
    fields = ((4, int), (2, int), (2, int),       # date
              (2, int), (2, int), (2, int),       # latitude
              (2, int), (2, int), (2, int),       # longitude
              (6, float), (6, float), (6, float)) # data
    '''
    fields = ((0, int),    (4, int),    (6, int),    # date
              (8, int),    (10, int),   (12, int),   # latitude
              (14, int),   (16, int),   (18, int),   # longitude
              (20, float), (26, float), (32, float)) # data
    ret = wrapper_function(f, fields)
    print ret

4,5(2問同時解答)

continueを利用することにより、インデントの数が減るので目を動かす量が減り、読みやすさは向上するものと思われる。

# -*- encoding: sjis -*-
import sys
import tsdl

def smallest_value(r):
    line = tsdl.skip_reader(r).strip()
    if not line:
        return -1 # something error occurred.
    smallest = int(line)

    for line in r:
        line = line.strip()
        if line == '-':
            continue
        value = int(line)
        if value < smallest:
            smallest = value

    return smallest

if __name__ == '__main__':
    input_file = open(sys.argv[1], 'r')
    print smallest_value(input_file)
    input_file.close()

6

以下のコードに登場するmultimol3.pdbは、通常のpdbに対し、CMNTで始まるコメント行やタブやスペースのみからなる空白の行が含まれたファイルになります。訳者のコードでは動かし方が分かりにくいので、メソッド名は採用しておりますが、1から書き直しています。

# -*- encoding: sjis -*-

def read_all_molecules(r):
    result = []
    reading = True
    while reading:
        molecule = read_molecule(r) # COMPNDという行からENDという行まで読む
        if molecule:
            result.append(molecule)
        else:
            reading = False
    return result

def get_line(r):
    ''' 空白やコメントでない行を返す。rは返す行の次の行の先頭に向かう '''

    while True:
        line = r.readline() # とりあえず1行読んで
        if not line: # 最後に来たら終了
            break
        line = line.strip() # 前後の空白を取り除いて
        if not line: # 空白ならばタブやスペースのみからなる行なので処理を続行
            continue
        elif line.startswith('CMNT'): # コメントであっても続行
            continue
        else: # それ以外はPDBファイルに必要な行である
            return line
    return ''

def read_molecule(r):
    line = get_line(r)
    if not line:
        return None

    key, name = line.split()
    molecule = [name]
    reading = True

    while reading:
        line = get_line(r)
        if line.startswith('END'):
            reading = False
        else:
            key, num, typ, x, y, z = line.split()
            molecule.append((typ, x, y, z))
    return molecule

if __name__ == '__main__':
    f = open('multimol3.pdb', 'r')
    ret = read_all_molecules(f)
    f.close()
    print ret

7

本来であれば、原子のシリアルナンバーが1ずつ大きくなっていない場合、プログラムの実行者に対し、何も結果を返さず、エラーを明示的に出してやるのがよい。途中まで出してそれを表示するということも考えられるが、正常終了に見えてしまうため、問題があるということを隠してしまうことになるので、そのような処理にしないほうがよいものと思われる。ただし、以下のコードではエラーの出し方が本テキスト読書中時点でははっきりしない(例外を投げてしまうのが一番よさげなのだが、例外の利用方法を知らない)ため、print文でエラーメッセージを出しつつ、途中で処理を打ち切るようにしている。この場合、途中までの結果が返されることになる。改めて解答しなおすことになりそうですが、ひとまず載せることにします。

# -*- encoding: sjis -*-

def read_all_molecules(r):
    result = []
    reading = True
    while reading:
        molecule = read_molecule(r) # COMPNDという行からENDという行まで読む
        if molecule:
            result.append(molecule)
        else:
            reading = False
    return result

def get_line(r):
    while True:
        line = r.readline()
        if not line:
            break
        line = line.strip()
        if not line: # 空白ならば続行
            continue
        elif line.startswith('CMNT'):
            continue
        else:
            return line
    return ''

def read_molecule(r):
    line = get_line(r)
    if not line:
        return None

    print line
    key, name = line.split()
    molecule = [name]
    reading = True
    check_num = 1
    ret_mol = True

    print "line = ", line
    while reading:
        line = get_line(r)
        if line.startswith('END'):
            reading = False
        else:
            print "!", line
            key, num, typ, x, y, z = line.split()
            if int(num) != check_num:
                print "serial number error in %s!" % molecule[0]
                return None # Noneとすることで続きの処理が行われなくなる
            check_num += 1
            molecule.append((typ, x, y, z))
    return molecule

if __name__ == '__main__':
    f = open('multimol4.pdb', 'r') # ファイルは適当にいじっておくように。
    ret = read_all_molecules(f)
    f.close()
    print ret

0 件のコメント:

コメントを投稿

フォロワー

ブログ アーカイブ

ページビューの合計