メインコンテンツへスキップ
  1. 第08講 クラス(1/2)/

第08講 週次課題

週次課題 クラス定義 メソッド インスタンス オブジェクト ドット記法
目次

課題08-1 Stack
#

難易度 ⭐

LIFO(Last in, First out; 後入れ先出し)のStackを表すクラスを実装してください.

  • pushメソッドに item を渡すと,リストの一番最後にitemを追加します.
  • popメソッドを呼び出すと,リストの一番最後の要素を取り出して返します(リストからは最後の要素を削除します). 要素が存在しない場合(リストの長さが0の場合)は None を返します.
  • peek メソッドは,リストの一番最後の要素を返しますが,リストからは要素を削除しません.
  • len メソッドは,スタックにある要素数を返します.

なお,コンストラクタの引数でリストを初期化できるようにしてください(デフォルト引数で None を受け取るようにしてください.理由はこちら).

Stack クラスの定義の下に以下の Python コードを書いて実行してテストが通るように stack.py を作成してください.

class Stack:
    pass # pass を削除して,以下のテストコードが通るようプログラムを書いてください

s1 = Stack()
s1.push("This")
s1.push("is")
s1.push("stack")
s1.push("test")

assert s1.len() == 4, "スタックの長さは4になっているはずです"
assert s1.peek() == "test", "peek() は最後の要素である test を返すべきです"
assert s1.len() == 4, "peek() は要素の長さを変更しません"

assert s1.pop() == "test",  "pop() は最後の要素である test を返すべきです"
assert s1.pop() == "stack", "pop() は最後の要素である stack を返すべきです"
assert s1.pop() == "is",    "pop() は最後の要素である is を返すべきです"
assert s1.pop() == "This",  "pop() は最後の要素である This を返すべきです"
assert s1.len() == 0, "スタックの長さは0になっているはずです"

s2 = Stack([1, 2, 3, 4, 5]) # コンストラクタで要素を指定できる.
assert s2.len() == 5, "スタックの長さは5になっているはずです"
s2.pop()
s2.pop()
s2.pop()
s2.pop()
s2.pop()
assert s2.len() == 0, "スタックの長さは0になっているはずです"

s3 = Stack()
assert s3.pop() == None, "空のスタックから pop() すると None が返るべきです"

課題08-2 環状バッファ
#

難易度 ⭐⭐

n個の要素を持つ環状バッファを表すRingBufferクラスを実装してください. 環状バッファとは,n 個の要素を持つリング状のバッファです. 要素を追加したときnを超えると,最初に追加した要素が上書きされる構造です.

コンストラクタで n を受け取ってください.ただし,nが指定されない場合は 12 が指定されたものとしてください. RingBuffer クラスの定義の下に以下の Python コードを書き,実行するとエラーなく実行できるようにしてください.

ring_buffer.py というファイル名で保存してください. RingBuffer クラスが持つべきメソッドは以下のテストコードを参照の上,考えてみてください.

class RingBuffer:
    pass # pass を削除して,以下のテストコードが通るようプログラムを書いてください

rb1 = RingBuffer(3)
assert rb1.get(0) == None, "何も要素が入っていないため,None であるはずです"
rb1.add(1)
assert rb1.get(0) == 1, "0番目の要素は 1 であるはずです"
rb1.add(2)
rb1.add(3)
rb1.add(4) # 最初の要素である 1 が上書きされる.
assert rb1.get(0) == 4, "0番目の要素は 4 であるはずです"
assert rb1.get(1) == 2, "0番目の要素は 2 であるはずです"
assert rb1.get(2) == 3, "0番目の要素は 3 であるはずです"
assert rb1.len() == 3, "3つの要素が入っているため,長さは3であるはずです"

rb2 = RingBuffer()
assert rb2.get(0) == None, "何も要素が入っていないため,None であるはずです"
rb2.add("item1")
rb2.add("item2")
assert rb2.len() == 2, "2つの要素が入っているため,長さは2であるはずです"
assert rb2.get(5) == None, "5番目の要素は存在しないため,None であるはずです"

以下の図は,上記の rb1rb2 の動作イメージです.クリックすると更新されます.

RingBuffer

課題08-3 拡張子ごとのワードカウント
#

難易度 ⭐⭐⭐

指定されたディレクトリ以下のファイルを再帰的に検索し,拡張子ごとに文字数,行数,ファイル数を数えるプログラムを作成してください. そして,拡張子ごとに文字数,行数,ファイル数を表示してください. word_count.py というファイル名で保存してください.

  • WordCountsself.types というディクショナリをもち,拡張子をキーとして,Counts オブジェクトを値として持ちます.
  • Countsオブジェクトは,拡張子ごとの文字数,行数,ファイル数を保持します.
  • Countsオブジェクトは,最初に拡張子が見つかったときに,初期化されます.
  • また,Countsオブジェクトの parseメソッドは,当該拡張子を持つファイルが見つかった時に呼び出されます.
    • そのため,parseメソッドが呼び出されると,ファイル数を+1し,ファイルの内容を読み込んで文字数と行数を数えて,self.linesself.chars に加算します.
import os
import sys

class Counts:
    def __init__(self):
        # 文字数(chars),行数(lines),ファイル数(files)を初期化する.
    def parse(self, file):
        with open(file, 'r') as f:
            # ファイルの内容を読み込んで,文字数と行数を数える.
        # ファイル数を1増やす.
    def str(self):
        return f"files: {self.files:5}, chars: {self.chars:5}, lines: {self.lines:5}"

class WordCounts:
    def __init__(self):
        self.types = {}

    def traverse(self, path):
        if os.path.isdir(path):             # ディレクトリかどうかを判定する.
            for p in os.listdir(path):      # ディレクトリ内のファイル,ディレクトリを列挙する.
                new_path = os.path.join(path, p) # ディレクトリ名とファイル名を結合する.
                self.traverse(new_path)     # ディレクトリを再帰的に探索する.
        else:                               # ディレクトリではない場合(ファイルの場合)
            ext = # 拡張子を取得する(_find_ext を呼び出す).
            if ext not in self.types:       # ディクショナリに拡張子に対応する Counts が存在するか.
                pass                        # 存在しなければ,新たに作成して,types に追加する.
            self.types[ext].parse(path)     # ファイルを解析する.
    
    def _find_ext(self, path):              # ファイル名を受け取り,拡張子を返す.
        ext = os.path.splitext(path)[-1]    # 拡張子を取得する(一番最後の要素を取得する).
        if ext == '':                       # 拡張子が存在しない場合
            return os.path.basename(path)   # ファイル名を返す.
        return ext                          # 拡張子を返す.

    def print(self):
        for ext, counts in self.types.items(): # 拡張子と Counts のペアを取り出す.
            print(f"{ext:12}: {counts.str()}")    # 結果を出力する.

wc = WordCounts()
for arg in sys.argv[1:]:
    wc.traverse(arg)
wc.print()

実行例
#

以下の helloworld.zip をダウンロードして展開してできたディレクトリ,helloworld を指定すると以下のような結果になります.

📥 helloworld.zipのダウンロード

$ python3 word_count.py helloworld
.mod            files:     1, chars:    45, lines:     3
LICENSE         files:     1, chars:  7048, lines:   121
Dockerfile      files:     1, chars:   236, lines:    11
.md             files:     1, chars:   565, lines:    14
.go             files:     1, chars:   262, lines:    20