コンテンツへスキップ

プログラミング基礎

大学コンピュータ科学 · トピック 1

查看幻灯片 練習する
1.1

このコースとは何か、および使い方は

これは大学コンピュータサイエンス最初のコース:プログラミング基礎 で、Python 3 を用いて教授される。事前のプログラミング知識は不要である。このプリント内の プログラムはすべて完成しており実行可能である—入力して実行し、1行を変更した上で、 再度実行する前に何が変わるか予測せよ。この「予測→実行」ループが実力そのものである。

このコースは階段のような構造である:

  • ユニット1.1から1.3までがメカニズムを構築する:値と型、選択、反復。
  • ユニット1.4が作業を関数にパッケージ化する。
  • ユニット1.5と1.6が標準的なコレクションやファイル、エラー処理を追加する。
  • ユニット1.7が上層の教養として位置づけられる:問題分解、テスト、デバッグ。

各ユニットの読み方:なぜ重要かを示す短い物語、図、または経験則が 文法より先に提示され、次に厳密なルールが続く。その後、難易度が上がる解題例が示され、 それぞれにコードを実行して確認できるトレースがある。さらに、実際に犯されるミステーク、 3行の要約が記載されている。ここではすべての例を別の方法でも検証可能である— 実行すること。コードを実行せずに読むことは、楽譜を読むのに音を聞かないのと同じである。 聞くこと。

1.1

値、型、入出力

购物应用显示“4.20”——这是文本还是数字?如果是文本,加两个 価格を足すと「4.203.50」;もし数値であれば、結果は 8.7 になる。どちらが 型によって決まり、単位 1.1 全体では、何も実行する前に型を見分けることを学ぶ。 型を見分けることを学ぶ。

一个值具有类型,且类型决定对其可执行的操作:int(整数 数), float (小数), str (テキスト), bool (True/False)。 type(...) 関数はあらゆる値の型を指定する — 学習中いつでも自由に問いかけてよい。

age = 21              # int
price = 4.20          # float
name = "Ada"          # str
open_ = True          # bool
print(type(age))      # <class 'int'>

変数は値に紐付けられた名前であり、代入 age = 22 は名前の紐付けを再定義する — 古い値自体は編集されない。学位課程全体を通じて意識しておくと良い心的イメージ:

プログラム状態を示す箱:各名前は独自の値箱を指しており、再バインディングはタグを移動させ、箱自体を編集しない。式 6 * 3.50 は二つの箱を読み取って新しい float 箱 21.0 を生成する
名前は箱につけられたタグであり、式は箱を読み出して新しい箱を作る

式を読むとは「箱を開けて計算し、新しい箱を閉じる」ことである:count * 3.50 count = 6 を用いると int ボックス 6 と float ボックス 3.50 が開き、Pythonの混合 rule promotes the int — the result is the float 21.0, never the int 21.

変換は明示的である: int("21") は 21 だが、"4" + 2 は TypeError であり — Python "42" か 6 のどちらを意図したかを推測することを拒否する。print(...) で出力し、入力では input(...) を使用すると常に文字列が返されるため、数値には変換が必要である:

born = int(input("Birth year? "))
print("About", 2026 - born, "years old")

** worked example 1 (カフェのレジ)。count = int(input("Coffees? "))、そして print("Total:", count * 3.50). 6 のコーヒーで Total: 21.0 を出力します — .0 浮動小数点の印を付けます。なぜなら int * float は浮動小数点に昇格するためです。式から 式の結果は単位 1.1 の全習慣に従う。状態を追跡する方法: input は 文字列 "6" を返す; int(...) は int 6 を保持する新しいボックスを開く; 乗算 新しい float 箱 21.0 を作成し; print がそれを表示します。

** worked example 2 (古典的な事故、修正済み)。バナープリンタがメッセージに カウントを追加します:

count = int(input("Tickets? "))
print("Sold " + count + " today")

実行すると Python は TypeError: can only concatenate str (not "int") to str を raised します — 規則は推測を拒否します。修復は変換を明示的に名指しします: print("Sold " + str(count) + " today")、またはよりクリーンな print("Sold", count, "today"), which inserts the spaces for you. The lesson to carry: + は数値に対して加算、文字列に対して結合であり、決して 暗黙の混合ではありません。

** 一般的な間違い。** input() が常に文字列を返すことを忘れるため、 input() * 2 は数値を二倍するのではなくテキストを反復します。 4.20 + 6 を書き、 int の答えを期待する — 式内のいかなる浮動小数点も浮動小数点にする。信頼する 0.1 + 0.2 == 0.3 (それは False である; 二進丸め — 単位 1.2 が修正を示す)。

浮動小数点による昇格で式は型を混合します;変換は明示的であり、 式は浮動小数点との昇格によって型を混合する; 変換は明示的で input() は一つ必要とする。

English 日本語
programming fundamentals/ˈprəʊɡræmɪŋ ˌfʌndəˈmentlz/ プログラミング基礎
type/taɪp/ 型
set/set/ セット
1.2

選択: パス間の選定

テーマパークの改札は、子供、大人、高齢者に対してそれぞれ異なる料金を課す必要があります。一つのプログラムに複数の経路があります。 選択とは、プログラムがif、elif、 elseの条件を満たす場合にのみブロックを実行する方法です。

シネマ料金のフローチャート:年齢チェックが学生チェックの前に配置されている;70 歳の学生は最初の真の分岐を通過し、5 を支払う
年齢70、学生: 最初の真となる分岐が料金を決定します

条件は任意のブール式であり、比較(==、!=、<、>=)や 論理演算(and、or、not)によって構築されます。すべての判断を可読にするための2つのルールがあります:

  1. Branches are tested top to bottom; the first true branch wins and the rest are スキップされます。
  2. Indentation is not decoration — it IS the block. One extra space changes which 線が分岐に属するかどうかが変わります。
temp = 37.5
if temp >= 38:
    print("Fever")
elif temp >= 36 and temp < 38:
    print("Normal")
else:
    print("Low")

実例1(映画館の料金)。 12または65+の場合、料金は5、学生は7、 それ以外の人は10:

age = int(input("Age? "))
student = input("Student? y/n ") == "y"
if age < 12 or age >= 65:
    price = 5
elif student:
    price = 7
else:
    price = 10
print("Pay", price)

顺序很重要:第一个为真的分支胜出,这就是为什么年龄检查先于学生检查, student check — a 70-year-old student should pay 5, and this ordering says so. Run it with 70 and y to see the flowchart's leftmost path taken.

各 2 は一つの比較のみで十分である、なぜならそれ以上のテストですでに where each elif only needs one comparison, because the tests above it have already failed:

score = int(input("Score? "))
if score >= 80:
    band = "A"
elif score >= 70:
    band = "B"
elif score >= 60:
    band = "C"
else:
    band = "F"
print("Band:", band)

79 で最初のテストが失敗し、2番目のテストが成功する: Band: B。エスカレーションは 注意: elif score >= 70 が and score < 80 も必要としない — その ラインに到達した時点で、スコアが 80 より低いことが既に証明されている。このようなフォールスルーを信用することは チェーンを短くする原因であり、すべてを再テストすることは初心者のより長い、誤った バージョンです。

浮動小数点数の比較には注意が必要である: 0.1 + 0.2 == 0.3 は False (二進数 丸め) であるため、許容誤差に対して比較する: abs(x - y) < 1e-9。

Common mistakes. Writing = where == is meant — the classic bug, and Python refuses it in conditions (SyntaxError) precisely because it is so common. Testing the special case after the general one, so the general one always wins. Using elif に、より前の分岐で既に処理されたため到達不可能な条件を指定します。 捕まえた場合です。

まとめ: 条件は経路を選択し、最初の真の値が勝つ。インデントはブロックを表す。順序は テストを特定から一般へ並べる。浮動小数点数は == ではなく許容誤差によって比較する。

English 日本語
testing/ˈtestɪŋ/ テスト
debugging/ˈdiːbʌɡɪŋ/ デバッグ
1.3

反復:再度行う

5,000 の請求書を手作業で処理することはキャリアであり、ループを使えばプログラムになる。反復 は作業を繰り返す: for ループは既知のシーケンスを順にたどる; while ループは条件が成立している間実行される。設計上の問いは常に、移動回数が分かっているか、それとも条件で決まるかである。 条件が成立します。設計上の重要な点は、反復回数が既知か、それとも条件によって決まるかです。 どちらか?

total = 0
for i in range(1, 101):      # 1..100 — range stops BEFORE its end
    total += i
print(total)                  # 5050

反復回数が既知の場合は for を使い、条件で決まる場合は while を使います。すべての while にはコード上で確認できる3つの要素が必要です:変数の初期値、条件式、そして終了に向かう変化 —否则は無限ループになります。 and something inside that moves toward exiting — otherwise it is an infinite ループ。

階乗whileループとトレース表:5と1から1と120へ、条件がFalseになるまでの各ターン後のnとfactorialの値
階乗ループのトレース表
n = 5
factorial = 1
while n > 1:
    factorial *= n
    n -= 1                    # the step toward exiting
print(factorial)              # 120

トレース表は、ループに対する確認の習慣です:紙の上でまず、 変数を各ラウンドごとに追跡します。階乗ループの場合: (n=5, f=1) → (4, 5) → (3, 20) → (2, 60) → (1, 120). 手動で表を作成し、次に ループ内に print(n, factorial) を追加して、Pythonが採点します。

演習例 2 (break を用いた探索、エスカレーション版)。 最初の倍数を見つける 7 が 100 より大きい場合 — 反復回数は不明で、条件が決定する:

n = 100
while True:
    if n % 7 == 0:
        break                 # found it — leave the loop now
    n += 1
print(n)                      # 105

トレースは短いです:100, 101, …, 104 はすべてテストに失敗し、105 が通過して break で 終了します。break はループを早期に離脱し、continue は次のラウンドへスキップします。これらはどちらも 可読性を高めるツールです — 何かを見つけるまでスキャンし、見つけ次第停止するループは、 break で最適。さらに増額し、金を倍にする:年利 5% で、どれほど 1000 は 2000 に達するか?

balance = 1000.0
years = 0
while balance < 2000.0:       # the condition itself is the goal
    balance *= 1.05
    years += 1
print(years, round(balance, 2))   # 15 2078.93

15年。ループは条件が反転した瞬間に停止する — トレース表 は、残高が境界線を越えるのが 2078.93 であり、2000 に正確に到達することはないことを示す。

1.5 の終了ステップが欠落している、またはコメントアウトされている場合:条件式が切り替わらず、 for のループを完了する。100 を意図した range(1, 100) において — 終了は除外される。 while の退出ステップが欠落している、あるいはコメントアウトされているループ:条件は反転せず、 プログラムがフリーズする。

まとめ:反復回数が既知のループには for、条件によるループには while を用いる;すべてのループは開始、 条件とステップ;紙上でトレースし、印刷 grade 级に;break と continue を用いて 早期のクリーンな終了を実現する。

1.4

関数:タスクを一度だけ名付けること

同じ価格計算が5箇所に現れます;税率ルールが変わり;そのうち4つが を定義し、呼び出しが実行されます。 once and trusted everywhere: parameters in, a return value out. def defines; calling runs.

def total_price(count, unit=3.50):
    """Return the total for count items at unit price."""
    return count * unit

print(total_price(6))            # 21.0
print(total_price(6, unit=4.0))  # 24.0

引数(count)を提供します。デフォルト値(unit)により、引数は任意となります。 argument (6). A default value (unit=3.50) makes an argument optional. return hands a value back and ends the call; printing is not returning — a function without return returns None, and the caller that tries arithmetic on it gets a TypeError.

三つの呼び出しフレーム: printが order_total(250, 4) を呼び出し、add_fee(1000) が実行される間一時停止します;1003 が連鎖に沿って戻されます 各呼び出しには独自のローカルスコープがあります:内部の名前は外部から見えず、呼び出し終了時に消滅します。再帰呼び出しのトレースは次のように読み解けます:

呼び出し元の待機 → 被呼び出しの実行 → 返り値が呼び出し元に届く。 呼び出し元が待機 → 呼び出し先が実行 → 返り値が呼び出し元に着地する。 呼び手が一時停止 → 被呼び側が実行 → 返り値が呼び手に着地する。

** worked example 1 (手数料込み注文).**

def add_fee(amount):
    """Return amount plus a fixed 3 pence fee."""
    return amount + 3

def order_total(unit_pence, quantity):
    """Return the order total: subtotal plus the fixed fee."""
    return add_fee(unit_pence * quantity)

print(order_total(250, 4))   # 1003

docstring内の契約が重要である:全体を投入すれば、全体が出力される。追跡 order_total(250, 4): 小計 1000、add_fee は 1003 を返す — そしてフレーム 上の図は、一時停止と返却を示している。

計算例 2(小さな関数内の実アルゴリズムの拡張版)。 is_prime(n) は 2 から $\sqrt{n}$ までで n が割り切れない場合に True を返す:

def is_prime(n):
    if n < 2:
        return False
    d = 2
    while d * d <= n:        # only test up to the square root
        if n % d == 0:
            return False     # found a divisor — answer known, stop
        d += 1
    return True

print([p for p in range(2, 30) if is_prime(p)])
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

ここで2つの関数の習慣が出会います:早期の return は答えが出た瞬間に呼び出しを終了します。 既知である(フラグ変数は不要)。ループの上限 d * d <= n は、 $a \le b$で$n = a \times b$ならば$a \le \sqrt{n}$であるため、$\sqrt{n}$を超える範囲でのテストは既に確認済みの約数を再テストすることになる。 実行すると:リストはちょうど30未満の素数となる。

よくある間違い。 結果をリターンする代わりに関数内で印刷してしまうため、結果を再利用できない。 cannot be reused. Forgetting the return on one branch — that path quietly yields None. Reaching into a function's locals from outside; they are gone when the call 呼び出しが終了するとそれらは消滅する。

まとめ:defは一度だけ、どこでも呼び出せる;パラメータが入り、returnを通じて一つの値が出力される; 早期リターンで呼び出しを終了する;ローカル変数は呼び出しと共に生滅する;docstringsは契約を記述する。 呼び出しと共に生滅する;docstringsは契約を記述する。

English 日本語
value/ˈvæljuː/ 値
variables/ˈveərɪəblz/ 変数
string/strɪŋ/ 文字列
selection/sɪˈlekʃn/ 選択
iteration/ˌɪtəˈreɪʃn/ 反復
infinite loop/ˈɪnfɪnət luːp/ 無限ループ
trace table/treɪs ˈteɪbl/ トレース表
function/ˈfʌŋkʃn/ 関数
return value/rɪˈtɜːn ˈvæljuː/ 返り値
parameter/pəˈræmɪtə/ 引数
argument/ˈɑːɡjuːmənt/ 実引数
local scope/ˈləʊkl skəʊp/ ローカルスコープ
1.5

コレクション:リスト、辞書、タプル、セット

A café's till needs the day's orders in order, the menu by name, a location that must not drift, and today's unique drinks. Four jobs, four containers — choosing それらの中から選ぶことが、モジュール1.5における設計判断である。

A list is an ordered, changeable sequence: marks = [72, 85, 61], indexed from 0, sliced with marks[1:3]. A dictionary maps keys to values: price = {"flat white": 3.5, "tea": 2.8}、keyによって読み書きされます。タプル は固定された配列 (53.1, -1.5)であり、変更してはならない座標などに適しています。セット は順序を持たない一意の要素を保持します: {"a", "b"}。

marks = [72, 85, 61]
marks.append(90)               # [72, 85, 61, 90]
best = max(marks)              # 90
average = sum(marks) / len(marks)   # 77.0
aとbという名前はどちらも同じリストオブジェクトを指しており、b.append(2)の実行後には1と2が含まれています
エイリアス: 2つの名前で1つのリスト

代入ではコピーが行われません: a = [1]; b = a; b.append(2)が a を [1, 2] にします — リストは共有され、名前は別物です。この エイリアス は、 共有されているのはデータですが、名前ではありません。この别名化は、ユニット 1.1 のボックスが再び二重ラベル付きになったことで説明されます。 with two tags on one box, and it explains the next rule too: mutating a list while looping over it skips elements — loop over a copy (for m in marks[:]) or build a new list.

演習例 1(カテゴリ別集計)。

orders = ["tea", "flat white", "tea", "tea", "mocha"]
tally = {}
for item in orders:
    tally[item] = tally.get(item, 0) + 1
print(tally)   # {'tea': 3, 'flat white': 1, 'mocha': 1}

The dictionary's .get(key, 0) supplies 0 for a first sighting — the idiom that ** worked example orders (その日のベストセラー、拡張版) 。** カウントに基づき、 上位アイテムとそのカウントを報告します:

演習例 2(当日のベストセラー、段階的処理)。 集計に基づき、 最上位の項目とその件数を報告する:

best = max(tally, key=tally.get)
print(best, tally[best])       # tea 3

key=tally.get引数はmaxに対して、アルファベット順ではなく件数に基づいて項目を比較させる ——同じkey=の手法は、任意のカスタム指標で集合を並べ替えることも可能である。 escalation matters: real programs chain these tools (count, then rank) rather than re-scanning orders twice.

コレクションの選択は設計上の判断です: 順序+重複 → リスト; 名前による参照 →辞書; 固定されたグループ化 → タプル; 一意性 → セット。 名前 → 辞書;固定されたグループ → タプル;一意性 → セット。

tally[item] += 1がリストをコピーすると勘違いすること; 実際には参照( aliases )しています。 (KeyError) —— .getが正解である。セットや辞書に順序保持機能を期待してはいけない。 b = a がリストをコピーすると仮定しますが、実際には aliasing(别名化)しています。

要約:4つのコンテナで4つのタスクに対応;割り当ては共有され、コピーされない。.get key=ブロックは安全装置です: 内部で何が起こっても、ファイルは必ず閉じられます。

English 日本語
list/lɪst/ リスト
dictionary/ˈdɪkʃənəri/ 辞書
tuple/ˈtuːpl/ タプル
aliasing/ˈeɪlɪəsɪŋ/ 别名化
1.6

ファイルとエラー

カフェが閉まり、停電が発生しても、朝の集計結果が残っているのは、 プログラムがそれをディスクに書き込んでいた場合のみである。ファイルはプログラムの実行期間を超える。with open(path) as f: opens, closes on exit, and reads text; open(path, "w") で書き込みます (かつ最初に空にする——これが古典的なミスである)。

with open("log.txt", "w") as f:
    f.write("opened\n")

with open("log.txt") as f:
    for line in f:
        print(line.rstrip())     # rstrip removes the trailing newline

withブロックは安全装置である:内部で何が起こっても、ファイルは必ず閉じる。 読み取りでは行データに改行文字を含むため、.rstrip()はすべてのループ内に含めるべきである。

エラーはPythonが例外としてraiseする値である:FileNotFoundError, ValueError, KeyError, TypeError. try/exceptは名前付きの型をキャッチする — 決して 実装例except:(存在しない設定ファイルの場合)。設定ファイルからのテキストサイズを読み取る際

** worked example 1(欠落した設定ファイル).** 設定ファイルからテキストサイズを読み込む 存在しない場合や、整数値を保持していない可能性があるファイル:

try:
    with open("settings.txt") as f:
        size = int(f.read().strip())
except FileNotFoundError:
    size = 12                    # no file yet: a sensible default
except ValueError:
    size = 12                    # the file holds "big", not a number
print("Text size", size)

ファイルを16で、ファイルなしで、そしてbigを含むファイルで、3回実行する。 経路を指定し、残りは激しく crash させよ。 期待するエラーを処理し、対応できる箇所で対処してください;残りは大いに crash させてください。 残りは大らかに crash する。

設定リーダー: try ブロックが開いて値を読み込み変換します;成功するか、FileNotFoundError または ValueError の分岐でデフォルトサイズ12を設定します;3つの道は合流し、プログラムは続行します

** worked example 2(頑健な入力ループ、エスカレート版).** ユーザーが整数を入力するまで尋ねる: 全体数:

def read_count():
    while True:
        try:
            return int(input("How many? "))
        except ValueError:
            print("Whole numbers only.")

print(read_count())

x を渡して次に4 :最初の試行は例外を発生させ、キャッチされ、メッセージが印刷され、 while True が再度尋ねます;2回目の試行のint(...) は成功し、return が終了する。 1.3 を忘れるため、出力に二重改行が表示されます。 まとめ:1.4 は読み込みにも書き込みにも使用します;1.6 はまず空にします; 例外は値です— 期待する名称付きタイプをキャッチし、合理的にフォールバックし、残りは crash させてください。

** 一般的なミス.** 異なるパスにopen(path, "w") :ファイルが作成されるか、 状態で継続します。Exceptionを忘れるため、出力に改行が重複して表示されます。 state. Forgetting .rstrip(), so output shows double-spaced lines.

まとめ:with open(...) で読み込みと書き込みの両方;"w" は initially 空にする; 残りは crash させよ。 残りは crash する。

English 日本語
file/faɪl/ ファイル
exceptions 例外
decomposition/ˌdiːkɒmpəˈzɪʃn/ 分解
1.7

分解、テスト、デバッグ

違いはタイピング速度ではありません — 速いチームのコードは、それぞれが正しさを証明できる小さな 部品から構成されていますが、遅いチームは一度に全体アプリをデバッグしています。 個別にテストするだけで十分です。テスト は、関数の実際の出力と モジュール 1.7 はその分野です。

分解とは、問題を一つのタスクごとの機能に分割することです。各小さな機能は 単体でテストするのに十分である。** テスト** は関数の実際の出力を 期待される結果と比較されます。

テスト駆動ループ:契約を記述する失敗するテストを書きます;正しい理由で失敗することを確認して実行します;通る最も単純な本体を書きます;緑になるまで再実行します;テストが構造を保つようにリファクタリングします
テスト駆動ループ:赤 → 緑 → リファクタリング
def add_fee(amount):
    return amount + 3

def order_total(unit_pence, quantity):
    return add_fee(unit_pence * quantity)

assert order_total(250, 4) == 1003
assert order_total(250, 0) == 3      # the zero-quantity boundary
assert order_total(0, 4) == 3

一度も発火しない assert は、成功したテストです。可能であれば、本体を書く前にテストを書いてください。それらは実行可能な契約であり、上記のループ (赤 → 緑 → リファクタリング)は、その習慣が継続的に実行されていることを意味します。 (赤 → 緑 → 再構築) は、その習慣が継続的に実行されていることを指します。

演習例 1(関数に合わせてテストを拡張する)。 order_total を以下のように拡張します。 £10 に対して 10% の割引 — テストはまず新しい境界値で記述されます:

def order_total(unit_pence, quantity):
    subtotal = add_fee(unit_pence * quantity)
    if subtotal >= 1000:             # pence: over £10
        subtotal = int(subtotal * 0.9)
    return subtotal

assert order_total(200, 4) == 803    # under £10 — unchanged
assert order_total(250, 4) == 902    # 1003 pence → 10% off → 902.7 → 902
assert order_total(250, 5) == 1127   # 1253 pence → 1127.7 → 1127

中央のテストを手動で確認します:小計 1003、10%引きで 902.7、そして int(...) rounds down to 902. Notice the first assert too: at 200 pence and four cup で合計は803 のまま — 境界テストは割引が発生することと、過早に発生しないことを証明する。 リストではなく連鎖として学ぶ:電荷が移動し、その速度が電流であり、電位差を通じたエネルギー移動の速度が電力である。

演習例 2(print文でバグを二分法で特定し、段階を上げる)。 1.5 からの集計 print文が {'tea': 3} を出力しますが、ベストセラー行は誤った飲み物を出力しています。この手法: (1)最も小さい失敗する入力を使って再現する;(2)信頼できる部分と疑われる部分の境界における値を出力する — ここでは、max 前の集計です; (3)一つ修正する;(4)テストを再実行する。コードの途中半分にprint文を移動させることで 各実行で探索範囲が半減するため、16行のプログラムにバグがある場合、4回の出力で特定できる。 実際には隠蔽されていることがほとんどだ。

デバッグとは、コードが何を行うと信じていることと、実際に何を行っていることの違いを見つけることである。 信頼性・疑わしさの境界における出力は、その検証のための手段である。

一般的なミス。 ハッピーパスのみをテストする:境界条件(ゼロ、一つ、空、負)でプログラムは破綻しやすい。 最初のテストを書く前に一週間分のコードを書き、後に山のようなデバッグに直面する。 一度に二つを修正すると、どちらの結果も意味を持たなくなる。 何物も物理的に上にあることはない。

要約:すべての部分が単体でテストされるまで分解し、関数が存在した瞬間に境界条件をアサーションする。 可能であればテストを先に書き、信頼性境界における出力を用いて二分法でデバッグする。 学位課程全体が基づく習慣である。

このトピックのインタラクティブ授業

一歩ずつ進め、即時チェック付きの問題で学習します。

大学コンピュータ科学 の他のトピック

ログインまたはアカウント作成

IGCSE, A-Level & AP