見出し画像

CS50 2023 - Week6 Python


概要

Week6では、Pythonについて学びます。
講義の主な内容は、関数、引数、戻り値、変数、ブール式、条件分岐、ループ、モジュール、そしてパッケージです。

5週にわたり学び続けたC言語を離れ、Pythonの学習が始まります。
しかし、それに伴いWeek6はProblem Setの数が増えているため、本ページではこれまで以上にコード例に焦点を絞って記していきます。


Lab 6

World Cup

ワールドカップでどの国が勝利するかを予測するシミュレーションプログラムを作成します。

以下は実際に渡しが提出したコードです。

tournament.py

# Simulate a sports tournament

import csv
import sys
import random

# Number of simluations to run
N = 1000


def main():
    # Ensure correct usage
    if len(sys.argv) != 2:
        sys.exit("Usage: python tournament.py FILENAME")

    teams = []
    # TODO: Read teams into memory from file
    with open(sys.argv[1], "r") as file:
        reader = csv.DictReader(file)
        for row in reader:
            teams.append({"team": row["team"], "rating": int(row["rating"])})

    counts = {}
    # TODO: Simulate N tournaments and keep track of win counts
    for i in range(N):
        winner = simulate_tournament(teams)
        if winner in counts:
            counts[winner] += 1
        else:
            counts[winner] = 1

    # Print each team's chances of winning, according to simulation
    for team in sorted(counts, key=lambda team: counts[team], reverse=True):
        print(f"{team}: {counts[team] * 100 / N:.1f}% chance of winning")


def simulate_game(team1, team2):
    """Simulate a game. Return True if team1 wins, False otherwise."""
    rating1 = team1["rating"]
    rating2 = team2["rating"]
    probability = 1 / (1 + 10 ** ((rating2 - rating1) / 600))
    return random.random() < probability


def simulate_round(teams):
    """Simulate a round. Return a list of winning teams."""
    winners = []

    # Simulate games for all pairs of teams
    for i in range(0, len(teams), 2):
        if simulate_game(teams[i], teams[i + 1]):
            winners.append(teams[i])
        else:
            winners.append(teams[i + 1])

    return winners


def simulate_tournament(teams):
    """Simulate a tournament. Return name of winning team."""
    # TODO
    while len(teams) > 1:
        teams = simulate_round(teams)
    return teams[0]["team"]


if __name__ == "__main__":
    main()

answers.txt

Times:

10 simulations: 0m0.028s
100 simulations: 0m0.030s
1000 simulations: 0m0.050s
10000 simulations: 0m0.104s
100000 simulations: 0m0.731s
1000000 simulations: 0m7.958s

Questions:

Which predictions, if any, proved incorrect as you increased the number of simulations?: TODO

With a small number of simulations, randomness cannot be eliminated,
and three teams are selected at random from the highly rated teams.

Suppose you're charged a fee for each second of compute time your program uses.
After how many simulations would you call the predictions "good enough"?: TODO

It seems like the predictions stabilized after about 1000 simulations

Problem Set 6

これまでC言語で提出してきた課題のいくつかをPythonで再び作成します。これらの課題名には、Sentimentalという接頭語が付きます。

Week1のHelloMarioCashまたはCreditが選出されています。そして、Week2からはReadabilityが加わります。

Week1の時と同じく、Marioless comfortablemore comfortableから選べます。CashCreditはどちらか一方だけの提出となります。

ここでは、Mario (less comfortable)Cashのコード例を示します。

Sentimental / Hello

# TODO
answer = input("What's your name? ")
print(f"hello, {answer}")

Sentimental / Mario (Less)


# TODO
from cs50 import get_int

# Prompt the user for the height until a valid input is entered
while True:
    h = get_int("Height: ")
    if h >= 1 and h <= 8:
        break

# For each row in the height of the pyramid
for i in range(h):
    print(" " * (h - 1 - i), end="")
    print("#" * (i + 1))

Sentimental / Cash

# TODO
from cs50 import get_float


def get_change():
    while True:
        x = get_float("Change owed: ")
        if x >= 0:
            return int(x * 100)


def calculate_coins(cents):
    coins = 0

    # Calculate the number of quarters to give the customer
    coins += cents // 25
    cents %= 25

    # Calculate the number of dimes to give the customer
    coins += cents // 10
    cents %= 10

    # Calculate the number of nickels to give the customer
    coins += cents // 5
    cents %= 5

    # Calculate the number of pennies to give the customer
    coins += cents

    return coins


def main():
    # Ask how much change the customer is wed
    cents = get_change()
    coins = calculate_coins(cents)
    print(coins)


main()

Sentimental / Readability

# TODO
from cs50 import get_string
import re
import math

# Prompt for user input
text = get_string("Text: ")

# String length
i = len(text)
letters = 0
# Initialize words to 1
words = 1
sentences = 0

# Loop for counting
for x in range(i):
    # Letter counting
    c = text[x]
    if c.isalpha():
        letters += 1

    # Word counting
    if c == " ":
        words += 1

    # Sentence counting
    if c in [".", "!", "?"]:
        sentences += 1

# Calculation
L = (letters / words) * 100
s = (sentences / words) * 100
subindex = 0.0588 * L - 0.296 * s - 15.8
index = round(subindex)
if index > 16:
    print("Grade 16+")
elif index < 1:
    print("Before Grade 1")
else:
    print(f"Grade {index}")

DNA

これはWeek6で唯一Sentimentalではない課題となります。
サンプルデータとデータベースを用いて、DNAの持ち主を特定するプログラムを作成します。Lab 5のInheritanceと少し似ていますね。

以下は実際に私が提出したコードです。

import csv
import sys


def main():
    # TODO: Check for command-line usage
    if len(sys.argv) != 3:
        print("Usage: python dna.py data.csv sequence.txt")
        sys.exit(1)

    # TODO: Read database file into a variable
    with open(sys.argv[1], "r") as csvfile:
        reader = csv.DictReader(csvfile)
        dna_data = list(reader)
        str_keys = reader.fieldnames[1:]

    # TODO: Read DNA sequence file into a variable
    with open(sys.argv[2], "r") as seqfile:
        sequence = seqfile.read()

    # TODO: Find longest match of each STR in DNA sequence
    str_counts = {key: longest_match(sequence, key) for key in str_keys}

    # TODO: Check database for matching profiles
    for dna in dna_data:
        if all(int(dna[key]) == str_counts[key] for key in str_keys):
            print(dna["name"])
            break

    else:
        print("No match")

    return


def longest_match(sequence, subsequence):
    """Returns length of longest run of subsequence in sequence."""

    # Initialize variables
    longest_run = 0
    subsequence_length = len(subsequence)
    sequence_length = len(sequence)

    # Check each character in sequence for most consecutive runs of subsequence
    for i in range(sequence_length):
        # Initialize count of consecutive runs
        count = 0

        # Check for a subsequence match in a "substring" (a subset of characters) within sequence
        # If a match, move substring to next potential match in sequence
        # Continue moving substring and checking for matches until out of consecutive matches
        while True:
            # Adjust substring start and end
            start = i + count * subsequence_length
            end = start + subsequence_length

            # If there is a match in the substring
            if sequence[start:end] == subsequence:
                count += 1

            # If there is no match in the substring
            else:
                break

        # Update most consecutive matches found
        longest_run = max(longest_run, count)

    # After checking for runs at each character in seqeuence, return longest run found
    return longest_run


main()

さいごに

C言語からPythonへの移行を経て、コードの構造は大幅にシンプルになったように感じます。特にSentimentalのProblem Setを通じて、その変化を実感することができました。非常に良く練られたコース構成です。
余談にはなりますが、Sentimentalという単語の選択もおしゃれで、個人的にはとても好きです。

この記事が気に入ったらサポートをしてみませんか?