CS50 ハーバード大学 コンピュータサイエンス講座 2020 Week6 Python

CS50 2020 Week6 Python

pythonって一体どんなん?って思ってたけど、なんか構文はVBAマクロに似てますね。
顔認識や音声認識とかライブラリも充実してるみたいで、すごいワクワクする。

※インデントが大事って言っておきながら、noteに貼り付けたら自動で左揃えになっちゃってました…(--;)すんません
後で直しておきます。

今日もまた、ほんの少しの時間で、新しい言語を学んだと言えるような稀な日です。
今日の言語は、Pythonという言語です。
私たちはまず、Pythonを紹介するために、より親しみやすい友人たちを紹介しようと考えました。
そこで、このコースはWeek0で「Scratch」を紹介するところから始まりました。
そしてあっという間に複雑にエスカレートし、C言語とその構文を紹介しました。
もちろん、まったく同じことをするのですが、「Hello,world」と画面に表示するためだけに、このような様々な構文をすべて理解して組み込む必要があります。
今日、この複雑さ、C言語の構文のすべてが突然氷解し始め、Pythonという新しい言語が残されて、この一行のコードだけでまったく同じ目的を達成することができるようになります。
print("hello,world")
つまり、Pythonはよりアクセスしやすく、少し簡単になる傾向があるということです。
しかしそれは、何年も前に人類がCのような低レベルの言語を作り始め、どのような機能が欠けているか、どのような問題があるかを認識し、それらの古い言語の上に新しいアイデア、新しい機能、そして新しい言語を重ねてきた伝統の上に築かれているのです。
世の中には何十、何百ものプログラミング言語が存在します。
しかし、その中でも特に人気が高く、流行している言語があります。
Pythonはそのような人気の高い言語の一つです。
pythonは今期の3番目の言語として取り上げます。

それでは、これまでの言語と比較しながらpythonの構文を紹介していきましょう。
今日のトピックがどれだけ新しいものに見えたとしても、ループ、条件、変数、関数、戻り値…といった意味では、どれも馴染みのあるものばかりです。
過去の機能を現在の機能に置き換えていくことになるのです。

もちろんscratchの世界では、これはただのパズルのピースや関数で、目的は画面に「hello,world」と表示することでした。
say hello,world

week1では、これをより分かりにくい構文に変換しました。
printf("hello,world");
重要なのはprintfであること、引用符があること、「hello,world」という文字列があること、改行文字を表す\nがあることで、もちろん文の最後に;が無ければならなかった。

今日、pythonで同等のコードを書くとしたら、単純にこうなります。
print("hello,world")
確かに似ていますが、printfがprintになっています。
二重引用符はまだありますが、\nと;は無くなりました。
もしあなたが;のような馬鹿げたことをうっかり忘れて度々自分を責めていたなら、pythonはこれからあなたの友となるでしょう。

では、もう一つの例として、ユーザーからの入力をどのようにして得るかを見てみましょう。

scratch
ask What's your name? and wait
sey join hello answer
ここにはaskというパズルのピースがあって、これは「What's your name?」と聞いて待つものです。
そして、次のパズルは、人間が入力したものが何であれ、その前に「hello」という単語を置く、というものでした。

C言語
string answer = get_string("What's your name?");
printf("hello,%s\n",answer);

pythonでは、この複雑さのいくらかは氷解してしまいそうです。
そしてpythonでは、このようなものが少しずつ見られるようになります。
python
answer = get_string("What's your name?")
print("hello, " + answer)
変数の型についての言及はもうありません。
最後の;も、%sのような表示のための追加の引数も必要ない

では、実際にこれらの機能を見てみましょう。
ちょっとだけCS50 IDEにアクセスしてみましょう。
そして、CS50 IDEの中で、最初のpythonプログラムを書いてみます。
そのために「hello.py」というファイルを作成します。
C言語の世界と同じように、Pythonプログラムは.cの代わりに.pyというファイル拡張子を持ちます。
そして、私が提案した最もシンプルな例の翻訳をしてみます。
print("hello,world")
このファイルを保存します。
そしてターミナルウィンドウに移動します。
以前はもちろん、makeを使って./helloなどを作成していました。
しかし、今日は単純にpythonというコマンドを実行してみます。
そして、先ほど作成したファイルの名前をコマンドライン引数として渡します。
python hello.py
そしてEnterキーを押すと、pythonによる私の最初のプログラムが出来上がります。
※hello,worldと表示される
これはかなりパワフルですよね。

それでは、先ほど提案した2つ目のプログラムを作ってみましょう。
ただ「hello,world」と出力するのではなく、今回は「answer」という変数を用意してみましょう。
answer = get_string("What's your name?")
answerという変数にC言語でお馴染みのget_stringで名前を入れる
print("hello, ")←二重引用符の中にスペースを入れる
そして、%sではなく+演算子を使って、文字通り「answer」という単語を入力します。
answer = get_string("What's your name?")
print("hello, " + answer)
しかし、これだけではうまくいかない。
なぜならget_stringはCに付属していないのと同じく、Pythonにも付属していないからです。
そこで、これまでとは少し違うことをする必要があります。
#includeではなく、文字通りに「from cs50 import get_string」と入力する
C言語の世界では、cs50.hがインクルードされていて、そこにはget_stringやget_intなどの関数の宣言があったことを思い出してください。
pythonの世界では、精神的には似たようなものですが、構文は少しだけ違います。
我々スタッフが書いたpythonライブラリであるcs50から、get_stringという関数をインポート、つまりインクルードしてみましょう。
これで、さっきまで画面に表示されていたエラーが消えました。

from cs50 import get_string

answer = get_string("What's your name?")
print("hello," + answer)

これでファイルを保存してpython answer.pyを実行すると、「What's your name?」と聞かれ、自分の名前を入力すると「hello,名前」と表示されます。

では、このコードのどこが違うのかを分析して、この後にできることを考えてみましょう。

answer = get_string("What's your name?")
繰り返しになりますが、3行目にはもうstringという言葉は出てきません。
変数が欲しければ自分でanswerという変数を作ってしまえば良いのです。
関数の名前はget_stringのままで、C言語版と同じように引数を取りますが、この行はもはや;では終わっていません。

print("hello," + answer)
最後の行のコードでは、プリント文がprintfではなく、printになっています。
これが新しい構文です。しかし、ある意味ではもっと簡単になるでしょう。
どこにプレースホルダを置くかを事前に考えなくてはならない代わりに、この+演算子が何かをしてくれているようです。
この+演算子は何をしているように見えますか?
なぜならそれは算術的な意味ではないからです。数字を足すわけではありません。
しかし、+は明らかに視覚的な結果を得るために何かをしています。
オーディエンス(ピーター)「文字列を連結している」
↑ええ、文字列を連結しています。
つまり、scratchで言うところのjoinブロックのようなものです。
C言語には無かったjoinブロックの直訳ができました。
Cではprintfや%sを使わなければなりませんでした。
pythonではもう少し使いやすくなっていて、hello、コンマ、スペースと、変数の内容といった2つの文字列を結合したい場合、代わりに+演算子を使うことができます。
そして、最後にしなければならないことはもちろん、このライブラリをインポートして、get_string関数自体にアクセスできるようにすることです。
それでは、pythonの他の機能について説明した後、多くの実践的な例を見ていきましょう。先ほどの例では、ユーザから文字列を受け取り、それをanswerという変数に格納する1行目のコードがありました。
2行目のコードは、ピーターが指摘したように、2つの値を連結するものでした。
既存の文字列と別の文字列を、フォーマット文字列などを使わずに結合できるという点では、C言語よりも便利であることは間違いありませんが、pythonのような言語では同じ結果を得るための方法がたくさんあります。
そこで、この行をこのファンキーな構文に変更することを提案します。
print("hello," + answer)

print(f"hello, {answer}")
一見すると不格好ですが、これはpythonの比較的新しい機能であることも理由の一つです。
pythonでは、C言語で使っていた{}中括弧を使って、変数の実際の値を入力できます。
つまり、pythonのprint関数では、%sの代わりに{}括弧を使い、「ここに値を入力してください」と言っているのです。
しかし、ここで一つ奇妙なことがあります。
{}括弧や変数名をいきなり文字列、つまりpythonの引用符付き文字列に入れることはできません。
次に続くものがフォーマット付きの文字列であることを示す必要があります。
これは恐らく、今まで見てきた中で最も奇妙なものです。
しかし、ここにあるような二重引用符""のペアがある場合、それにfを前置きすると、文字通り{}括弧そのものを表示するのではなく、{}括弧の間に値を挿入して、文字列の内容をフォーマットするようにコンピュータに指示します。
それでは、実際のコードに移って試してみましょう。
ピーターが説明したように連結演算子、つまり+演算子を使う代わりに、
print("hello," + answer)

print("hello, answer")
としてみましょう。
これは正しいアプローチではないでしょう。というのも、このプログラム、python answer.pyを再実行すると、私の名前を聞いてくるからです。
それにdavidと答えますが、"hello, answer"とハードコードされているので、完全に無視されてしまいます。
しかし、ただ{}括弧の中に入れれば良いというわけでもありません。
print("hello, {answer}")
なぜなら、もし私が再びこのプログラム、hello.pyを実行し、自分の名前を聞かれ、入力すると、hello, {answer}と表示されてしまうからです。
ここでは、二重引用符""に囲まれたこのタイプの文字列が実際にはフォーマットされた文字列であることをpythonに伝えなければなりません。
"の前にfを入れる↓
print(f"hello, {answer}")
これで自分の名前を入力すると、ちゃんと「hello,名前」と表示される
つまり、C言語よりも少し便利なのです。
なぜなら、ここにプレースホルダ、ここにプレースホルダ、さらに追加の引数をコンマで区切ったリストを用意する必要が無いからです。
言ってみれば、作成する文字列にさらに値を導入するための、より簡潔な方法なのです。
これらはフォーマット文字列、略してf-stringと呼ばれています。
これは、pythonという新しい言語でプログラミングするためのツールキットに含まれる、新しい機能です。

それでは、他の翻訳されたパズルのピースをいくつか見てみましょう。
そして、言語をpythonに切り替えて、自分のプログラムを作り始めましょう。

scratchでは、カウンタを0に初期化する例を以前に扱いました。
set counter to 0

C言語では、week1で次のようなコードに変換しました。
int counter = 0;
これでint型の変数の初期値が0になりました。

pythonでは、コードは同じようなものになります。
似ていますが、もう少しシンプルになります。
counter = 0
注意してほしいのは、pythonでは欲しい変数の種類を言わなくても良い、ということです。
pythonは文脈からそれが何であるかを推測します。
また、;も必要ありません。
pythonでcounter = 0とすると、counterという変数ができます。
そして、0という値を代入しているので、pythonという言語自体が、「ああ、これはintつまり整数であることを意味しているに違いない」と推論します。

scratchでは他に何をしましたか?
counterを1だけ変更します。
change counter by 1
これは変数の値を1だけ増やす方法でした。
Cでは、これをいくつかの方法で実装できました。
counter = counter + 1;
しかし、これでは文が長くなって面倒です。
そこで、代わりにこの方法を可能にする省略記法を用意しました。
counter += 1;
Cではこれで同じ結果を得ることができました。

さて、pythonではいくつかのアプローチがあります。
;を省略して、C言語のように明示的に言うこともできます。
counter = counter + 1
pythonのロジックはC言語と全く同じです。
counter += 1
↑これも同じように可能
今のところ、pythonに存在しないものは、counter++、またはi++という、変数をインクリメントするだけで簡潔になる構文上の工夫ですが、残念ながらそれらはpythonには存在しません。
しかし、counter += 1、または変数が何であれ、それを実行することができます。

さて、scratchとCでは他に何を見ましたか?
if x < y then
say x is less than y
もちろん、かなり早い段階で条件分岐を導入しました。
条件分岐はブール式を使って、これをするか、これ以外のことをするか、あるいは全く別のことをするかを決めるものです。
C言語では、これを似たような形に変換しました。
if(x < y)
{
printf("x is less than y\n");
}
確かに、黄色い条件分岐が紫のsayブロックを包み込むように、{}括弧はprintfの行を包み込むようになっています。
そして、(x < y)のようにブール式を括弧で囲みました。
{}括弧の中で二重引用符、改行を表す\n、;を持つprintfを使いました。

pythonは、精神的には同じようなものですが、構文的にはよりシンプルになります。
pythonがどのように見えるかというと、次のようになります。
if x < y:
print("x is less than y")

x<yを囲む()括弧はなくなります。
{}括弧もなくなります。
改行もなくなります。
そして;もなくなります。
ここに、プログラミング言語の進化のほんの一例を見ることができます。
もしあなたや私が、あちこちにある下らない;や{}にイライラしていたとしたら、それはコードを読むのをある意味で難しくしています。
人間は、新しい言語を発明する際に、単に正しいだけでなく、「意味することを文法的な複雑さ無しに言える」ようにしようとしました。
もっとシンプルに行きましょう。

実際、ここではpythonでの1つの例を紹介しています。
しかし、重要な点があります。
C言語でコードを書いているときに、インデント(文字の位置を揃えること)が少しずさんになっていて、style50が常に「スペースを追加しろ」、「スペースや行を削除しろ」と警告しているような人がいたら、pythonではコードを正しくインデントする必要があります。
Cではもちろん、私たちやCS50、そして世界中の多くの人たちが、コードをインデントする際には通常4つのスペース、または1つのタブでインデントすることを推奨しています。
pythonの文脈では、必ずそうしなければなりません。
if x < y:
print("x is less than y")
↑もし、このprint文の左側にあるこれらのスペースを誤って省略してしまうと、あなたのpythonコードは全く動作しません。
ですから、もういい加減なことはしないでください。
pythonはそれを課してきます。
しかし良い点は、わざわざ{}括弧を入れる必要がない、ということです。

もっと複雑な条件で、ifとelseという2つの分岐がある場合はどうでしょうか?
scratch
if x < y then
say x is less than y
else
say x is not less than y

C言語
if(x < y)
{
printf("x is less than y\n");
}
else
{
printf("x is not less than y\n");
}

恐らくpythonではもう少しコンパクトになると推測できます。
if x < y:
print("x is less than y")
else:
print("x is not less than y")
インデントは必要ですが、{}括弧は必要ありません。
改行も;も必要ない。
このように、今では当たり前のように使われている機能を取り除いているのです。

scratchでif、else、if elseという3つの分岐があった時の例はどうでしょうか。
if x < y then
say x is less than y
if x > y then
say x is greater than y
else
sai x is equal to y

C言語
if (x < y)
{
printf("x is less than y\n");
}
else if(x > y)
{
printf("x is greater than y\n");
}
else
{
printf("x is equal to y\n");
}
この単純なアイデアを実現するために12行というかなりの行数のコードが書かれています。

pythonでは、ここで何がなくなるかというと、再び()、{}、\n、;がなくなります。
if x < y:
print("x is less than y")
elif x > y:
print("x is greater than y")
else:
print("x is equal to y")

ここにはひとつだけ奇妙なものがあります。
あなたには何かがタイプミスのように見えませんか?
私はここで間違ったことはしていないと言えます。
他の場所ではしているかもしれませんが、ここではしていません。
オーディエンス「else ifではなくelifは構文的に異なると言えるでしょう。」
↑その通りです。C言語では文字通りelse ifと言っていましたが、pythonでは何年も前に、「elifの1語で簡潔に言えるなら、なぜelse ifと言って、わざわざ入力する必要があるのだろう」と考えました。
ですから、これは正しい構文なのです。
そして、これらはたくさんあっても構いません。
分岐は4つ、5つ、6つ、それ以上でもいくつでも構いません。
構文は確かに少し違います。
しかし、少しタイトになりましたよね。
このコードを一見すると、構文上の混乱が少なくなっています。
;や{}などをたくさん無視する必要もありません。
pythonは構文的に少しスッキリしている傾向があります。
実際、これは最近の、よりモダンな言語の多くに見られる特徴です。

さて、scratchとC言語の他のブロックも見てみましょう。
scratchでは、ループを何度も何度も、恐らく永遠に実行したい場合、文字通りForeverブロックを使います。
forever say hello,world

C言語では、これをいくつかの異なる方法で実装することができます。
while(true)
{
printf("hello,world\n");
}
ブール式が真の場合にhello,worldの表示を何度も何度も繰り返します。
そして、この場合ブール式は決して変わらないので、実際に永遠に実行されることになります。
pythonも似たようなものですが、微妙な違いがあります。
そのために、C言語の場合がどのようなものか、頭に叩き込んでおいてください。
()の中にtrueがあり、{}、\n、;があります。
これらの多くはこれからなくなりますが、その他にもわずかな違いがあります。
while True:
print("hello,world")
何度も強調しているようにインデントしていることに注目してください。
もはや\nや;、{}はありませんが、TrueやFalseの頭文字は大文字でなければなりません。
つまり、C言語では小文字のfalseとtrueでしたが、pythonでは大文字のFalseとTrueになるのです。
何故かって?そういう仕様だからです。
しかし、ループと条件分岐の両方で、もう一つ重要な点があります。
条件分岐の例を振り返ってみると、
if x < y:
print("x is less than y")
elif x > y:
print("x is greater than y")
else:
print("x is equal to y")

{}や()を取り除いた代わりに:(コロン)が導入されていることに気がつきます。
これはpythonに、「その下にインデントされている後続のコード行が、実際にそのif、elif、またはelseに関連している」ことを明確に伝えるためです。
そして、この同じ機能をループの文脈でも見ることができます。

もちろん他のループの種類も見てきました。
scratchでは、3のような有限回数の処理をしたいときは、次のように3回繰り返していました。
repeat 3 say hello,world

C言語では、これに対していくつかの異なるアプローチがありました。
int i = 0;
while(i < 3)
{
printf("hello,world");
i++;
}
あえて言えば、どれも機械的なものでした。
例えば、何かを3回やりたい場合、C言語では変数を宣言して、何回数えたかを記録し、インクリメントする責任があなたにはあります。パーツが多いのです。
C言語では次のようなアプローチもあります。
i=0という名前の変数を宣言しますが、何とでも呼べます。
次にwhileブロックを置いて、「iは3より小さいか」というブール式を何度も評価します。
そして、ループの中ではhello,worldと出力しています。
そして、C言語の構文記号である++表記を使って、iに1を加え、iに1を加え続け、iが3未満でなくなったら暗黙のうちにループを抜け出します。

pythonの場合も、精神的には似ていますが、やはりごちゃごちゃした部分がなくなります。
i = 0
while i < 3:
print("hello,world")
i += 1

変数を与えるために必要なのはi=0という宣言だけです。
while i < 3とし、:をつけます。
そして、その中で適切にインデントされた状態でhello,worldと出力します。
そして、++は残念ながら使えないので、i += 1でiをインクリメントします。
つまりこれは、3回実行するループと全く同じものをpythonで実装する一つの方法です。
しかし、C言語ではもちろん他のアプローチも見ましたし、pythonでも他のアプローチが可能です。
C言語ではforループというアプローチもありました。
for(int i = 0; i < 3; i++)
{
printf("hello,world");
}
恐らく皆さんはforループを頻繁に使ってきたことでしょう。
なぜなら、見た目は少し難解ですが、;の間にある1行のコードにより多くの機能を詰め込むことができるからです。
つまり、同じロジックでforループを使ってこのhello,worldを3回プリントアウトするだけです。
pythonでは、ここから少しずつエレガントになっていきます。

for i in [0, 1, 2]
print("hello,world")

一見すると奇妙ですが、確かにより簡潔になっています。
何かを3回実行したい場合、pythonではforループにもっと簡潔な構文を使うことができます。
for i in、そして[]括弧の中に値のリストが入っています。
過去に配列や配列のインデックスを意味するために[]括弧をいくつかの異なる場面で使ったように、pythonの世界では、間にカンマが入った値の束を[]括弧で囲み、それらをすべてカプセル化したものをリストと呼ぶことにします。
精神的には配列と似ていますが、pythonの文脈ではリストと呼びます。
このコードでは、for i in[0 , 1, 2]…これはどういう意味でしょうか。
これはpythonのforループでiという変数を要求しています。
このループの最初の繰り返しではiを0に設定し、2回目のループではiを1に設定します。そして、このループの最後の繰り返しで、iを2に設定しています。
このように、すべての処理を行ってくれます。
さて、結局のところ、iの値を印刷していないので、iが何であるか自体は重要ではありません。
それで全然構いません。
iの値を表示していないにも関わらず、hello,worldと表示するなどのように、forループを使って何度も何度も実行したことがあるかもしれません。
技術的には、[]括弧の中に任意の3つのものを入れることができます。
しかし、慣習的には、C言語のように、0、1、2と、コンピュータサイエンティストが0から数えるように、ただ列挙するだけです。
しかし、これはすぐに破綻してしまうかもしれません。
これはすぐにとても醜いものになるかもしれません。
pythonのforループで、[]括弧の間に反復したい値のリストを入れなければならないことに問題を感じる人はいますか?
オーディエンス(ノア)「例えば、繰り返す回数が50回だとすると、0、1、2…49とすべて書き出さなければなりません。」
↑ええ。これではすぐに見苦しくなってしまいますね。あなたが50と言ったのは面白いです。
というのも、今日の講義のためにこのデモを準備していたとき、私はweek0に戻ってみたんです。week0ではhello,worldを50回表示しました。そこで私は「ああ、くそ。これはひどいことになる」と思いました。
なぜならノアが言うように文字通り0、1、2…48、49とすべての回数を[]の中に入れなければならないからです。
何か良い方法があるはずで、実際あります。
この表記は非常に短い回数なら説得力がありますが、pythonで何かを何度も行いたい場合にはもっと簡単な方法があります。
この3つの値のリストを、
for i in [0, 1, 2]

for i in range(3):
返したい値の数を入力とするrangeという関数で置き換えることができます。
基本的に、rangeは3のような入力を受け取ると、0、1、2という3つの値のリストを自動的に生成します。
そして、pythonはその3つの値を反復して返します。
for i in range(3):
print("hello,world")
先ほどのノアの懸念に対して50回繰り返したい場合は、3を50に変更するだけで、0から49までのリストを手入力するというおかしなことをする必要はありません。
そのようなプログラムは、プログラムの長さや失敗の可能性などを考えると、あまり良い設計とは言えないでしょう。
ですから、pythonでは、これは恐らく今、何かを何回か行うための最もPythonicな方法と言えるでしょう。
そして実際、これはpythonコミュニティでは技術用語となっています。
簡潔に言うと、技術者やプログラマは物事を行う「正しい方法」に関しては、ある意味でかなり信心深い傾向があります。
実際、pythonプログラミングの世界では多くのpythonプログラマが意見を持っていますが、pythonコードを「どう書くべきか」を指示する標準化された推奨事項もあります。
そして、このようなトリックはPythonicとみなされるものです。
これは「絶対的に正しい」という意味ではなく、「他の多くの人がこの意味であなたに同意している」という意味で正しいということです。
これから独自の機能を作り始める前に、pythonの最後の機能をいくつか見てみましょう。
C言語ではデータ型のリストがあったことを思い出してください。
もちろん、もっとたくさんのデータ型がありますし、自分で作ることもできます。
しかし、私たちが最初に見たプリミティブは、bool、char、double、float、int、long、stringなどでした。
pythonでは、たとえ私がstringやintのような型を変数に与えられる場合でも、counterやi、answerなどの名前をつけて値を代入すると、pythonは代入された値からその変数のデータ型を推測するので、pythonには確かにデータ型は存在します。
これは、プログラミングの世界では緩やかに型付けされた言語として知られているものです。
Cの世界では、Cは強い型付けの言語で、型が存在するだけでなく、明示的に使用しなければなりません。
pythonの世界では、言語は緩く型付けされています。
型は存在しますが、暗黙のうちに推測することができます。
プログラマであるあなたがデータ型を絶え間なく指定する必要はありません。
コンピュータがそれを理解してくれるのです。
というわけで、
↓これがC言語からの型リストです。
bool、char、double、float、int、long、string

↓これが、pythonの世界での同様のリストになります。
bool、float、int、str
True、Falseを示すbool型はまだあります。
小数位を持つ実数であるfloatがあります。
intはもちろん-1、0、1などの数字です。
そして、stringではなくstrです。
Cの世界では、技術的には「文字列型」は存在しませんでした。
これはCS50ライブラリが提供した機能で、char *のアイデアをより利用しやすくしたものでした。
Cには文字列があり、文字列と呼ばれていますが、stringというデータ型は存在しません。もちろん、C言語で文字列を作成するには、char *として宣言します。
CS50ライブラリでは、そのchar *に「string」という同義語、ニックネーム、エイリアスを与えただけです。
一方pythonには、文字列のための実際のデータ型が存在します。
略してstrと呼ばれています。

では、pythonの他の機能で、ここで使えるものはあるでしょうか?
さて、pythonには他のデータ型もあり、より洗練されたプログラムを開発したり、言語を使ってもっと素敵なことをしたりする際に、実際に非常に役立つことがわかっています。

すでにrangeを見てきました。
厳密に言えば、これはpythonのデータ型の一種で、入力に基づいて、デフォルトでは0から始まる数値列を返します。

listについては、これまでにも言及してきました。
listはpythonの適切なデータ型で、精神的には配列と似ています。
しかしこの数週間、配列は固定サイズであることを強調してきました。
配列の大きさは事前に決めておかなければなりません。
そして先週のようにもし「ああ、もっとメモリが必要だな」と思ったら、動的にスペースを確保し、値をコピーして古いメモリを解放しなければなりません。
このように、C言語で配列を使って大きくしたり小さくしたりするには、いわば多くの手間がかかるのです。
pythonのような高レベルの言語では、そのようなことはすべてやってくれます。
つまりlistとは、自動的に大きさを変えてくれる配列のようなものです。
この機能は、いわば言語の中に無料で備わっています。
自分で実装する必要はありません。

pythonにはtupleと呼ばれるものがあります。
数学やGPSの文脈ではx座標とy座標、または緯度と経度の座標があり、コンマで区切られた値があります。tupleは、pythonでこれらを実装する一つの方法です。

dictは、つまり辞書です。
pythonには、キーと値を格納することができる辞書があります。
文字通り人間の世界では、例えば英語の辞書があったとして、物理的な形の辞書が単語とその定義を格納するのと同じように、pythonのdictはより一般的に、任意のキーと任意の値を格納することができます。
あるものと別のものを関連付けることができます。
そして、これが素晴らしく便利で汎用性の高いデータ構造であることがわかります。

最後に今日の目的のためにsetと呼ばれるものがあります。
数学を思い出すと、setとはa、b、cや1、2、3のような、重複しない値の集まりです。
しかし、pythonはそれを管理してくれます。
setに要素を加えることも、setから要素を取り除くこともできます。
pythonは重複がないようにしてくれますし、メモリもすべて管理してくれます。

一方、私たちが関数として持っているものは、お馴染みの友人です。
Cではcs50ライブラリを使ってget_char、get_double、get_float、get_int、get_long、get_stringでそれぞれの値を取得していたことを思い出してください。
pythonでは、ありがたいことに、もうdoubleやlongを気にする必要はありません。
get_float、get_int、get_string
しかし、数分前に私がインポートしたpythonのcs50ライブラリには上記の3つの関数があり、少なくとも今週の目的のために、あなたの生活を便利にしてくれます。
この2つはcs50独自のライブラリではなく、最終的にネイティブのpythonコードのみを使用するように、すぐに外してしまう補助輪のようなものです。
しかし、今週のC言語からpythonへの移行のためには、これらを外してリラックスする前に、これらが生活を便利にしてくれることがわかるでしょう。
Cでは、ライブラリを使うにはcs50.hをインクルードしなければなりませんでした。
#include<cs50.h>
pythonでは、先に進んでcs50をインポートするか、より明確に、インポートしたい特定の関数を指定してインポートします。
import cs50
or
from cs50 import get_float
from cs50 import get_int
from cs50 import get_string
このように、インポートの方法には様々なものがあることがわかりました。
最終的には同じ目的を達成することができます。
このような行を使って、先ほどのget_stringのように関数を一つずつ明示的にインポートすることもできますし、もっと簡潔に「import cs50」と言ってライブラリ全体を一度にインポートすることもできます。
これは今後使用しなければならない構文に影響を与えますが、今後の例では複数の方法を見ることができます。
これを少し簡略化して、
from cs50 import get_float,get_int,get_string
カンマで区切られた関数のリストを我々のcs50のようなライブラリからインポートすることもできます。
これは頻繁に見られる慣例です。
なぜなら、インターネット上にある他のプログラマによって書かれた人気のあるサードパーティーのライブラリを使い始めると、彼らはごく普通に私たちが使える関数をたくさん提供してくれるからです。

これでpythonの構文の短期集中コースは終わりです。
これから物作りを始めて、pythonの特徴やニュアンスを探っていき、pythonの力を実感していきましょう。

質問のコーナー
オーディエンス(オリビア)「forループで1ではない数でインクリメントさせたいが、リストを明示的に入力したくない場合、どのようにするのでしょうか?」
↑とても良い質問ですね。もし、forループを使ってある範囲の値を反復処理させたいが、その範囲を0、1、2、3でなく、0、2、4、6、8にしたいとしたら?
実際にその場で変更することができます。
実際にできることは、別の値を指定することで、それは次のようになります。
for i in range(3, 1):
print("hello,world")
入力を1つの値ではなく、2つの値に変更すると、合計3つの値をカウントする必要がありますが、デフォルトの1ではなく、一度に2をインクリメントする必要があるということをコンピュータに伝えることができます。
さらに他の機能もあり、例えば0から数え始めるかどうかも調整することができます。
つまり、pythonでは言語に付随する多くの機能を見つけることができます。
さらに強力なのは、pythonで書ける関数や使える関数は、異なる数の引数を取ることができるということです。
0の時もあれば1の時も2の時もありますが、最終的にはあなた次第です。

オーディエンス「rangeはforループ以外の場面でも使いますか?」
↑一般的には使いません。というか、今までrangeを使ったことのある他のケースについて、今、頭を悩ませています。他にももしかしたらあるかもしれませんが、最も一般的なケースはforループのような反復の文脈だと思います。
あるパターンに従った長い値のリストを生成したい場合、それが0であれ1であれ2であれ、あるいはオリビアが指摘したようにギャップのある値の範囲であれ、rangeを使えば手入力を回避することができます。いわば、好きなパターンの値を返す関数です。

さて、ここから先は「hello,world」よりも面白いことをやってみましょう。
結局のところ、プログラミングが本当に楽しくなり、本当に強力になるのはここからです。
ハッシュテーブルのメモリ管理や連結リストのメモリ管理、配列内の値のコピーなど、低レベルの実装の詳細を実装する必要がなくなったとき、あなたや私は本当に強力になるのです。
これまでの数週間は、理解するのには役立ちますが、書いていて楽しいものではない低レベルのプリミティブに焦点を当ててきました。
Problem Setの形で書くのは面白くないかもしれないことは認めます。
確かに、問題解決のためにコードを描きたいだけなのに、今後の人生のために書くのは楽しいものではないでしょう。
しかし、ここでもライブラリの出番となります。
そして、ここからが他の言語の出番です。
ある種の問題を解くには、pythonの方がはるかに優れていて、はるかに簡単な言語であることがわかりました。
では、実際にやってみましょう。

bridge.bmpというファイルを取ってきます。これは過去のPsetで覚えているかもしれません。
これはマサチューセッツ州ケンブリッジのチャールズ川沿いにある、ハーバード大学のウィークスブリッジです。
これはCS50のチームメンバーが撮影した非常に鮮明な写真です。
ここ数週間で、あなたはコードを書き、この画像に様々な変化を与えましたが、その中には画像をぼかすものもありました。
あえて言えば、「ぼかし」は最も簡単な問題ではありませんでした。
上下左右を見て、すべてのピクセルを平均化しなければなりません。
画像が1ピクセルごとにどのように表現されているかを理解しなければなりません。
つまり、最終的にはただ画像をぼかしたいだけなのに、そこにはたくさんの低レベルの微細な技術が必要なのです。
これまでは低いレベルで考えて書かなければなりませんでしたが、pythonではより高い抽象度で考えることができ、自分で書くコードの量も大幅に減らせることがわかりました。
では早速やってみましょう。
この作業にはCS50 IDEではなくMacを使うことにしたので、より早く画像を開くことができます。
このコースではpythonや他の言語のためにCS50 IDEを使いますが、必要なソフトウェアをMacやPC、時には携帯電話にもインストールして、自分のデバイスでpythonや他の言語を使うことが出来るということです。
ただし、授業中はCS50 IDEを使用することが多いので、標準的な環境で作業を行うことができます。
私のコンピュータでblur.pyというプログラムを書いてみましょう。
pyというのは、もちろんpythonプログラムのためのファイル拡張子です。
私のプログラムは少し見え方が変わっています。
この黒と青と白のウィンドウがあります。
しかし、これは私個人のMac上の単なるテキストエディタです。
画像をぼかすには、画像に関する機能が必要ですね。
そこで、PILライブラリ、つまりPillowライブラリから、Imageという特別な機能とImageFilterという特別な機能をインポートすることにします。
from PIL import Image, ImageFilter
つまり、これらは画像操作に関して私よりも賢い誰かが書いた2つの関数で、彼らはそのコードを、インターネット上で自由に利用できるフリー&オープンソースにしました。
つまり、誰でもコードを使うことができ、自分のプログラムにインポートすることができます。実際、私も講義の前にあらかじめダウンロードしてインストールしました。
さて、これから実行してみましょう。

beforeという変数を与えます。
そして、bridge.bmpをImage.openにより呼び出してみます。
before = Image.open("bridge.bmp")
繰り返しになりますが、今まで見たことがなくても、使ったことがなくても、何が起こっているかを構文的に理解することができます。
左側にbeforeという変数があります。
右側にはImage.openという関数があり、bridge.bmpという名前を渡しています。
つまり、これはC言語の世界で言うところのfopenのようなものだと思われます。
ここで注目してほしいのは、このドットが新たな役割を果たしているところです。
これまではC言語の構造体にのみドット演算子を使用し、personオブジェクトやnodeオブジェクトの中に入って、その中の変数にアクセスしたいときに使用していました。
pythonには、精神的にCの構造体と同じようなものがあります。
しかし、pythonでは構造体の中には変数やデータ(名前や番号など)だけではなく、関数も入れることができます。
これにより、利用可能な機能の面であらゆる可能性が広がります。
このImageオブジェクト、Image構造体は、他の誰かからインポートしたのだと思います。その中には、開くファイルの名前を入力として受け取るopen関数があります。
今日の例題の中で、この構文をどんどん見ていきましょう。

二つ目の変数afterを用意します。
このafterという変数に、beforeイメージのfilter関数を呼び出し、ImageFilter.BoxBlurに1を渡した結果を代入してみましょう。
after = before.filter(ImageFilter.BoxBlur(1))
これは少し難解な構文なので、この特別な構文に時間をかけるつもりはありません。
皆さんも、今後の人生で画像をぼかすためのコードを書く機会はあまりないでしょう。
しかし、今日の目的のために、before変数の中身に注意してください。
というのも、ここでは新しい機能の戻り値をbefore変数に割り当てており、それは内部にデータだけでなく、filterのような関数も持っているからです。
そしてこのfilter関数は、他の関数の戻り値を入力としています。
簡単に言うと、「半径1ピクセルのボックスを使って画像をぼかす」というものです。
つまり、あなたのコードと同じように、C言語でblurを実装した場合、このコードは私のコードに、上下左右を見て、ピクセルの周りの平均を取ることで、ピクセルをぼかすように指示することになります。
この後、after.saveをします。これをout.bmpという名前で保存したいと思います。
after.save("out.bmp")
out.bmpという新しいファイルを作りたいだけです。

間違いがなければ、先に進んでPythonでblur.pyを実行し、Enterキーを押してみましょう。
エラーメッセージが出ないことは良いことです。
lsを入力すると、すでに開いているbridge.bmp、先ほど書いたblur.py、そして新たにout.bmpがあることに気づきます。
それではout.bmpを開いて見てみましょう。
さて、インターネット上ではそれほどぼやけて見えないかもしれませんが、数十センチ離れたこのMac上では確かにぼやけて見えます。
しかし、もう少し説得力のある方法でやってみましょう。
上下左右に1ピクセルずつではなく、10ピクセルずつにしてみてはどうでしょうか。
after = before.filter(ImageFilter.BoxBlur(10))
そうすれば、より多くの値を見て、より多くの平均値を取ることで、本当にぼかすことができます。
→もう一度プログラムを実行すると、out.bmpの方がものすごくぼやけた

ここで何が言いたかったのかというと、これはPset4でやったことですが、ここではたった4行のコードで画像をぼかすことができます。
とてもクールでパワフルですね。
他人の肩の上に立って、彼らのライブラリを使用することによって、我々は非常に迅速に他のことを行うことができます。

ここではもっと最近の問題を解決することもできます。
別のディレクトリに移動してみましょう。
ここにはあらかじめ、授業の前に書いたいくつかのファイルが用意されています。(これらのファイルはコースのウェブサイトからダウンロードすることができます。)
ひとつは、speller.pyというファイルです。
これはProblem Set5の配布コードの一部でしたが、speller.cのコードをCからPythonに翻訳したものです。
辞書とテキストには、Pset5と同じファイルがあり、2つの異なるサイズの辞書と、たくさんの短いテキストと長いテキストが入っています。
まだ作られていないのはdictionary.cに相当するもので、現在はdictionary.pyとなっています。
さて、Pythonでスペルチェッカを実装してみましょう。
慣例に従い、dictionary.pyというファイルを作成しましょう。
4つの関数を実装しなければなりませんよね?
check、load、size、unloadを実装しなければなりません。
しかし、私の辞書を保存するためには、グローバル変数が必要です。
ここで皆さんはポインタを使ってハッシュテーブルを実装し、連結リストや配列など、複雑なものを実装したわけです。
ここでwordsという変数を用意して、それをsetとして宣言してみましょう。
words = set()
setとは、重複した値を処理するための単なる値の集まりであることを思い出してください。
率直に言って、本当に必要なのはそれだけです。
辞書にあるすべての単語を格納し、重複した値がないように、またある単語がsetに含まれているかどうかをチェックできるように、それをsetに投入できる必要があります。
では、先に進んで、単語をsetにloadしてみましょう。
これからloadという関数を定義して、開くファイルの名前を受け取ります。
def load(dictionary):
そして、ここでは認められたいくつかの新しい構文を紹介します。
これまでのところ、私たちはファイルにコードを入力しただけです。
実際、pythonのC言語との最も顕著な違いは、私が一度もmain関数を書いたことがないということです。
これもpythonの特徴です。
プログラムを書きたいと思ったら、わざわざmainという関数にデフォルトのコードを書く必要はありません。
ただ、コードを書き始めれば良いのです。
このようにして、hello,worldをC言語の何行ものコードからpythonの1行に減らすことができたのです。
mainすら必要ありませんでした。
しかし、自分で関数を定義したい場合、pythonでは「def」=defineというキーワードを使い、関数の名前を入れ、C言語のように括弧の中に、関数が受け取る変数やパラメータの名前を入れることがわかりました。
データ型を指定する必要はありませんが。また、ここでも{}括弧は使わず、:を使います。
つまり「pythonよ。dictionaryという引数を取るloadという関数をくれ」ということです。
この関数は何をするのでしょうか?
spellerのload関数の目的は、辞書から各単語を読み込んで、それをなんとかハッシュテーブルに入れることでしたね。
私も同じように、辞書から各単語を読み込んで、それをいわゆるset、wordsという変数に入れようと思います。
そこで、先に進んでファイルを開きますが、これはこの関数でできます。
def load(dictionary):
file = open(dictionary, "r")
pythonではfopenは使いません。openという関数を使うだけです。
そして、openの戻り値をfileという変数に割り当てます。
しかし、私はこれを好きなように呼ぶことができます。
ここからがpythonの醍醐味です。
C言語のファイルから行を読むのは、ちょっと大変だったことを思い出してください。
一度に一行ずつ、一文字ずつ読むために、freadや他の関数を使わなければなりませんでした。
しかし、pythonでは、ファイル内のすべての行を繰り返し読みたい場合は、「for line in file」とします。
def load(dictionary):
file = open(dictionary, "r")
for line in file:
これで自動的にforループが生成され、ファイル内の連続した各行に変数lineを割り当ててくれます。
これで、すべての行がどこにあるのかがわかります。
各行で何がしたいのでしょうか?
その行を私のwordsというsetに追加したいと思います。
def load(dictionary):
file = open(dictionary, "r")
for line in file:
words.add(line)
各行が一つの単語を表している限り、私はグローバル変数のwordsにその行を追加したいのです。
しかし、それは正しくありません。
というのも、私のファイルの各行の終わりには何があるでしょうか?
私のファイルのすべての行には、定義上「\n」がありますよね?
だから、私たちが渡した大きな辞書の中の単語はすべて1行に1つなのです。
では、文字列の最後の改行をなくすにはどうしたらいいのでしょうか?
C言語ではmallocを使ってコピーを作成し、すべての文字を移動させ、\nを取り除いて少し短くしなければなりませんでした。
pythonでは、文字列の最後の改行を削除したい場合は、rstripを実行します。
words.add(line.rstrip())
文字を取り除く(strip)ということは、デフォルトではホワイトスペースを取り除くことを意味します。
ホワイトスペースには、スペースバー、タブ文字、\nが含まれます。
したがって、各行を取得して、その最後の改行を捨てたい場合は、単にline.rstripと言えば良いのです。
ここで、pythonの文字列が威力を発揮します。
文字列は独自のデータ型であり、その中には文字列を構成するすべての文字だけでなく、行末の空白を削除するrstripのような関数も含まれています。
これでもう終わりだと思います。
先に進んでファイルを閉じ、Trueを返すことにします。
def load(dictionary):
file = open(dictionary, "r")
for line in file:
words.add(line.rstrip())
close(file)
return True
というわけで、これで終了です。
これがpythonのload関数です。
辞書ファイルを開き、ファイルの各行をグローバル変数に追加し、ファイルを閉じて、Trueを返します。
私のコードはおそらく数行であり、C言語のコードの実装に比べて何時間も短縮されていると思います。

では、checkについてはどうでしょうか?
もしかしたら複雑さは別のところにあるかもしれません。
では、先にcheckという関数を定義して、引数として特定の単語を入力するようにしてみましょう。
def check(word):
そして、指定された単語が私のwordsのsetに含まれているかどうかをチェックするだけです。
C言語ではforループやwhileループを使って、二分探索や線形探索などを使って、読み込んだ単語のリスト全体を繰り返し処理しなければなりませんでした。
何週間も経った今、私はそれを乗り越えました。
私はただ、「if word in words,return True,else return False」と言うだけです。
def check(word):
if word in words:
return True
else:
return False
これでcheckの実装ができました。
ちょっとバグがありますね。これを修正するつもりです。
pythonを見たことがなくても、自分のバージョンのcheckを何時間もかけて実装してみて、私が論理的に見落としているステップに気付くでしょうか?
ブライアン「大文字と小文字の区別についてコメントしている人が何人かいます。」
↑大文字と小文字の区別ですね。
つまり、C言語では、単語をすべて大文字にするか、すべて小文字にするか、どちらかを選択していたはずです。
まったく問題ありませんが、おそらく一文字ずつ入力しなければなりませんでした。
mallocを使って入力をコピーするか、一文字ずつ配列に入れて、toupperやtolowerを使って一文字ずつ大文字にしたり小文字にしたりしなければならなかったかもしれません。
うわ、そんなことをしていたら、本当に時間がかかったかもしれません。
つまり、与えられた単語を小文字にしたい時は、word.lowerと言えば良いのです。
def check(word):
if word.lower() in words:
return True
else:
return False
すると、pythonはすべての文字を繰り返し処理し、それぞれを小文字に変更し、新しい結果を返してくれます。
実際、これは皆さんが行ったことと一致していると思います。

では、sizeはどうでしょうか?
sizeでは、入力を取らずに単語の集合の中の単語の数を返す関数を定義しなければならなかったことを思い出してください。
ちょっとインデントをずらします。(load関数のインデントがスペース1個分ずれていた←スペースは4個必要)
辞書のサイズ、あるいはset内の単語数を返したい場合は、グローバル変数wordsの長さを返せば良いのです。
def size():
return len(words)
これでsize関数は終了です。

最後に辞書をunloadしたい場合です。
これも引数は必要ありません。
def unload():
正直なところ、mallocに相当するものをやっていないからです。メモリ管理をしていません←なぜ?
なぜかというと、パイソンでは必要ないからです。
文字通り、すべてのケースでTrueを返すことができます。
def unload():
return True
私のコードは間違いなく正しいので、ポインタやアドレス、メモリ管理を気にする必要がなかったからです。
数週間に渡って、メモリ管理の低レベルの詳細を理解するために引き起こされたかもしれないすべてのストレスは、もう解消されました。
覆いの下で起こっていないからではなく、pythonがあなたのためにしてくれているからこそ、解消されるのです。
そして、実はここで1つのバグを発見しました。
ここでC言語のコードに戻ってしまったことに注意してください。
ここで言うべきだったのは、実際にはfile.closeであるということです。
close(file)

file.close()
つまり、loadでファイルを閉じるときには、実際にはfile.close()を呼ばなければなりません。
というのも、今はclose関数が変数に関連付けられているからです。
ここでもメモリ管理が行われています。
mallocやfree、reallocなど、すべてが水面下で行われています。
しかし、そのすべてをpythonがあなたのために管理してくれているのです。
それは、低水準の言語ではなく、いわゆる高水準の言語を使用することで得られるものです。
より多くの機能を手に入れることができます。
そして、この場合、すべての問題を解決することができます。
あなたと私はスペルチェッカを作ることに集中できます。
あなたと私はInstagramのフィルタを作ることに集中できます。
メモリの割り当てや文字列のコピー、大文字への変更などではありません。
正直なところ、初めてそれらを動作させたときには楽しく、非常に満足したかもしれません。
しかし、プログラムを書きたいときにいつでも低レベルで考えてコードを書かなければならないのであれば、プログラミングはすぐに世界で最も退屈なものになってしまうでしょう。
先に進み、ここで失敗していないことを祈って、このコードを実行してみます。
python speller.py
spellerの配布コードでspeller.cを書いたのと同じように、事前にspeller.pyを書いておきました。
しかし、その内部を見ることはしません。
これから、シェイクスピアのような大きな作品でテストしてみます。
python speller.py texts/shakespeare.txt
上手くいくと良いのですが…
(単語がものすごい速さで飛び交っていく)
↑これは正しいと思います。
(結果が表示される)
見慣れた数字が出てきましたね。
トータルで1秒弱の時間がかかっています。
これはかなり速いですね。
私はIDEでなくMacを使っているので、クラウドの場合とは数字が違うかもしれませんが、0.9秒です。
興味があるので、別のタブを開いて、pset5のspeller.cをmakeしてみましょう。
C言語で書かれたspellerのスタッフによる独自実装(dictionary.cとspeller.c)を事前に持ってきて、makeでコンパイルしてみました。
そして、先ほどのshakespeareの同じテキストを使って、./spellerを実行してみます。
先ほどのpython版と比較をしてみます。
↑(すぐ終わる)
すごい。2倍くらいの速さで飛んで行きましたね。
C言語版は0.52秒、つまり半秒でした。
一方、python版は0.9秒、つまり約1秒かかりました。
つまり、私のC言語版は速く、python版は遅いということになります。
なぜそうなるのでしょうか?
なぜなら、pythonの美徳を説くことに時間を費やしたのに、ここで私たちがある意味もっとひどいコードを書いているとしたら、私はちょっとがっかりするからです。
オーディエンス(サンティアゴ)「C言語は低レベルではありますが、コンピュータに何をすべきかを明示的に指示しますので、それによって少し速くなっているのかもしれません。一方、pythonでは、すべてが覆いの下で起こるので、それによって少し遅くなるのかもしれません。」
↑ええ。pythonではメモリ管理や大文字小文字の区別など、C言語では自分で実装しなければならないような機能を、汎用的に実装しています。
しかし、他人のコードを使ってこれらの機能を実装することには代償が伴います。
また、pythonのようなタイプの言語を使うと、さらに大きな代償を払うことになります。
C言語とpythonの間には、もうひとつ顕著な違いがあります。
C言語のコードを書くときは、ソースコードからマシンコードにコンパイルしていました。
マシンコードとは、コンピュータの頭脳、いわゆるCPU(central processing unit)が理解する0と1のことであることを思い出してください。
ソースコードを変更するたびに、必ずコンパイルしなければなりませんでした。
そして、プログラムを実行するために「./hello」のようにしました。
しかし、これまでのpythonのデモでは、makeもclangも使いませんでした。
代わりに、python プログラム名としています。
それはなぜか?
それは、pythonはしばしばインタプリタとして実装されているからなのです。
つまり、pythonは私たちが書いてきたような言語であるだけでなく、それ自体がプログラムでもあるのです。
※インタプリタ とは、人間に分かりやすい高水準プログラミング言語( 高級言語 )で書かれたコンピュータプログラムを、コンピュータが解釈・実行できる形式に変換しながら同時に少しずつ実行していくソフトウェア
私が実行し続けているpythonプログラムは、python言語を理解する同名のプログラムです。
何が起こっているかというと、私のプログラムを実行するために、いわばインタプリタを使うことで、ある程度のオーバーヘッドが発生しているのです。
パフォーマンスの代償を払っているのです。
なぜでしょうか?
week0から思い出してみると、コンピュータは結局のところ0と1しか理解できません。
それが彼らを動かしているのです。
しかし、私は0と1を出力していません。
人間の私はpythonを書いているだけです。
だから、英語のような構文で書かれたpythonのコードを、コンピュータが理解できるように変換する必要があるのです。
もしあなたが、コードを変更するたびにコンパイルする努力をしないで、pythonの世界でそうしているようにコードをインタプリタに通していたら、誰かがあなたのためにトランスレータを実装しなければならないので、あなたはその代償を払うことになります。
実際、これには正式な用語があります。
例えば、pythonの世界には次のようなイメージがあります。
source code → interpreter
C言語の世界では、実際にソースコードを入力して、まずマシンコードを出力し、それを実行しますが、これまでのpythonの世界では、ソースコードを書いて、すぐに実行しています。
あらかじめ0と1にコンパイルしているわけではありません。
偶然にもpythonというプログラムがあって、そのプログラムが私のためにそのコードをコンピュータが理解できるように翻訳してくれることを信じているのです。
これは実際にはどういうことなのでしょうか?
つまり、次のようなアルゴリズムを思い浮かべるとします。
(読めない擬似コード。スペイン語っぽい)
多くの方にとってはおそらく暗号のようなものですが、誰かのために電話帳を検索するスペイン語のアルゴリズムかもしれません。
仮に私がスペイン語をまったく話せないとします。
うまくいけば、スペイン語を英語に翻訳するコンパイラを使って、このプログラムをアルゴリズムを理解できるものにコンパイルすることができるかもしれません。
→コードを英語に翻訳する(電話帳を検索するアルゴリズムだった)
ほら。そうすると、英語版の方が読みやすく、理解しやすいので、このアルゴリズムをかなり速く実行することができます。なぜなら私の母語は英語だからです。
しかし、スペイン語版のソースコードだけを渡されて、それを一行ずつ翻訳したり解釈したりすることを要求されたら、正直言って、それは私の作業を遅らせることになるでしょう。
なぜなら、それは一語ごとにスペイン語の辞書を引くようなものだからです。
辞書を引いて一行ごとに翻訳し続けていたら、間違いなくプロセスは遅くなります。
このように、pythonのプログラムを実行するときには、こうしたことが起こっているのです。
ソースコードを見て、上から下へ、左から右へと読み、基本的に各行をコンピュータが理解できる対応するコードへ翻訳している、いわば翻訳者のような人がいるのです。
その結果、ありがたいことにmakeやclangを実行する必要はなくなりました。
もうコードをコンパイルする必要がないのです。
例えば、C言語で以前のプログラムに変更を加え、ファイルを再コンパイルするのを忘れて再実行したところ、実際には保存だけでなく再コンパイルもしていないので、プログラムは明らかに変わっていなかった…という経験をした人はどれくらいいるでしょうか?
つまり、そのような愚かで厄介な人間によるステップがなくなったのです。
pythonの世界では、ファイルを変更したら、そのまま再実行して、再解釈してください。
そのステップを省くことができます。
しかし、その代償として、少しばかりのオーバーヘッドが発生します。
実際、python版のスペルチェッカでは約1秒かかっているのに対し、C言語版では1/2秒しかかかっていないことからもそれがわかります。
ここでも、過去に約束したトレードオフのテーマが出てきます。
これはコンピュータサイエンスやプログラミングの世界では非常に一般的なことであり、実際には現実の世界でもそうです。
何かを改善したり、利益を得たりするときには、必ず何らかの代償を払うことになります。
それは時間かもしれないし、スペースかもしれないし、お金かもしれないし、複雑さかもしれないし、他の何かかもしれない。
このように、リソースのトレードオフは永遠に続くのです。
優れたプログラマであるということは、最終的にはそのような変曲点を見つけ出し、調整のためにどのようなツールを使うべきかを知ることです。
さて、ここで5分間の休憩を取りましょう。
そして、戻ってきたらpythonの他の機能を見て、最終的には本当に強力な機能を手に入れて、今日を終えることにしましょう。

さて、まず一つ撤回させていただきます。
ブライアンは、オリビアとノアの質問に対する私の答えが、残念ながら的外れだったと指摘してくれました。
なぜなら、私はドキュメントを読まずにその場でやっていたからです。
さて、この例を思い出してみましょう。
for i in range(3):
print("hello,world")
ここではrange関数が3つの値を返していました。
このコードは正しく、0、1、2の値を返します。
しかし、オリビアが尋ねたのは、値をスキップして、例えば1つおきに処理したい場合、どのようにすればいいかということだったと思います。
私は残念ながらそのための構文を間違えてしまい、ここで必要とされる3つの入力ではなく、2つの入力しかrangeに与えていませんでした。
例えば、0から100までの数字を印刷したいとします。
ただし、1つおきに0、2、4、6、8…のように、偶数のみ100までとします。
実際には、次のようにします。
for i in range(0, 101, 2):
print(i)
これはなぜでしょうか?
すぐにドキュメントをお見せしますが、
最初の引数、0はカウントを始めたい場所です。
2番目の引数、101はカウントを止めたい場所です。
しかし、これは定義上、排他的なので、気になる値の1つ先でなければなりません。
そして3番目の引数は、0、2、4、6、8、そして100まで、一度に何個の数字を増やしたいかということです。
今更恥ずかしい思いをするくらいなら、どうやって事前に気づけばよかったのでしょうか?
さて、ここにpythonの公式ドキュメントがあります。
そして、ここには一番上に検索ボックスがあります。
休憩中、私がrangeを検索していたことがバレてしまいましたね。
案の定、rangeのドキュメントを検索してみると、一見して圧倒されるような量の内容になっています。
幸いなことに、ここでの最初の検索結果は、私たちが求めるものです。
これをクリックすると、一見してちょっとわかりにくいドキュメントが出てきます。
しかし、面白いのは、rangeには2つの異なる種類があることです。
また、私はこれを関数と読んでいますが、厳密にはクラスと呼ばれるものです。
これについてはまた別の機会に説明します。
今回の目的では関数として動作します。
ここには2つの行があることに注目してください。
class range(stop)
class range(start,stop[,step])
これらは似ていますが、違います。
最初の行は、このrange関数が1つの入力、つまり停止値を受け付けると指定しています。
では、どの値でカウントを止めたいのでしょうか?
以前、range(3)を実行したとき、デフォルトでは0から数え始めて3で止めると、
i=0、1、2を使用していました。
しかし、私が提案したものとは別の種類のrange関数が存在します。
それは3つの引数を取る可能性があるもので、ここでは2つ取っています。
これは次のように動作します。
pythonのドキュメントでこのような構文を目にしたとき、これはrangeの別な形式がstartという引数を取り、続いてstopという引数を取り、さらにオプションでstepという3番目の引数を取ることを意味しています。
ここでは[]括弧で囲まれているので、読者にはそれが任意であることがわかります。
だから、リストや配列などとは何の関係もありません。
これは単なる人間のためのドキュメントです。
さて、先ほどオリビアとノアの質問に答えたときにはあると私が思っていたstopとstepを指定できるrangeの種類が実際にはないことに注意してください。
代わりに、この3つの引数を取るタイプのものがあります。
つまり、0から始めて、気になる100を過ぎた101で止めて、オプションで2のstepを指定すると、最終的には100までの偶数の数字をすべて出力するプログラムが出来上がります。
では、早速やってみましょう。
まず、ここにあるプログラムを見てみましょう。
for i in range(0,101,2):
print(i)
→0から100までの偶数がすべて表示される(数字ひとつにつき改行されている)
100で止まっていて、最初にスクロールすると0から始まっています。
申し訳ありませんでした。
しかし、pythonの公式ドキュメントを紹介する良い機会となりました。
このドキュメントは一見すると難解に感じるかもしれませんが、すぐにあなたの友人になるでしょう。

それでは、先ほど始めたもう一つのプログラムをもう一度見てみましょう。
そのプログラムとは、先ほどの比較的単純なHelloプログラムです。
pythonのCS50ライブラリのget_string関数を使っていました。

from cs50 import get_string

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

answerという変数があり、get_stringの戻り値を取得していました。
フォーマット文字列やf-stringと呼ばれる、よくわからないけれども便利な新機能を使っていましたが、これは単に{}括弧の中のものを実際の値に置き換えるという意味です。
それでは、たった1時間前につけたばかりの補助輪を今から外してみましょう。
CS50ライブラリを排除するのです。
CS50ライブラリを使わずに、どうやってpythonで入力を得ることができるのでしょうか?
さて、もうget_stringは存在しません。
しかし、ありがたいことに、単純にinputと呼ばれる別の関数を使うことができます。

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

inputはCのget_stringとよく似た関数で、ユーザーに"What's your name?"のようなフレーズを促し、ユーザーが値を入力するのを待ち、Enterを押すとすぐに人間が入力した値を返してくれます。
CS50ライブラリを排除し、get_stringの代わりにinputを使用した後、このプログラムを実行すると、"What's your name?"と聞かれ、名前を入力すると、「hello,名前」と表示されます。
これは生のpythonのコードであり、CS50とは全く関係ありません。
しかし、当面はCS50ライブラリを使い続けましょう。
というのも、多くのエラーチェックをしてくれるので、CS50を使うメリットがすぐにわかるからです。
最終的には補助輪は完全に外します。
しかし、その方法はとても簡単です。
あらかじめ書いておいたプログラムを開いてみます。
そして、先にこれを取りに行きます。
これはいつものようにコースのウェブサイトから入手できるものです。
先に言って、前に見たことのあるaddition.cというファイルを開きます。

▼addition.c
#include <cs50.h>
#include <stdio.h>

int main(void)
{
int x = get_int("x:");
int y = get_int("y:");
printf("%i\n",x + y);
}

ここではちょっとした工夫をして、ウィンドウを分割して2つのファイルを同時に見られるようにします。
新しいファイルを作成し、これをaddition.pyとします。

つまり、今日は一時的にIDEの配置を変えて、左にC言語、右にpython言語を見ることができるようになりました。
また、これらの例題はすべてオンラインでダウンロードできるので、自分でフォローすることも可能です。
左のプログラムを右のプログラムに翻訳する場合、まず左のプログラムが実際に何をしていたかを思い出してみましょう。
これはユーザーにxとyを入力させ、その2つの足し算を行うプログラムでした。
これはweek1のプログラムで、今となっては遠い昔の話ですね。
さて、これを翻訳してみましょう。
今回はCS50ライブラリのget_int関数を使います。これで少しは楽になります。
from cs50 import get_int
そして、get_intを使ってユーザーからintを取得し、xの入力を促します。
x = get_int("x: ")
yの入力も促します。
y = get_int("y: ")
そしてx+yの結果を表示します。
print(x + y)
これでpython addition.pyを実行してみましょう。
→ちゃんとxとyの入力を促され、その足し算の結果が表示される
ほら。簡単ですね。
コードの行数が少ないのは、stdio.hのような不要なインクルードがないからです。
{}括弧もありません。
公平を期すためにコメントを書いてみましょう。
pythonでは、コメントの前には//ではなく#をつけます。

▼addition.py
from cs50 import get_int

#prompt user for x
x = get_int("x: ")

#prompt user for y
y = get_int("y: ")

#perform addition
print(x + y)

コメントをつけてもまだ簡潔ですね。
たった10行のコードに、いくつものコメントがついているのですから・
じゃあ、ちょっと変わったことをしてみましょうか?
補助輪を外して、CS50ライブラリを排除し、入力を得ましょう。

get_int関数をそのままinputに変えてみます。
#prompt user for x
x = input("x: ")

#prompt user for y
y = input("y: ")

#perform addition
print(x + y)

xに1、yに2を入力すると、答えは…12になった。
これは間違っています。
何が起こっているのでしょうか?
こんなに簡単なプログラムでどうして失敗してしまったのでしょうか?
私にとって新しい言語であるpythonではありますが、ここで何をしたのでしょうか?
オーディエンス(ベン)「なぜなら、実際には2つの文字列として受け取っているので、実際に計算するのではなく、隣り合わせにしているだけだからです。実際に計算するのではなくint型として読んでいるのです。」
↑その通りです。
pythonに付属しているこのinputという関数は、CS50のget_stringに相当します。
人間が何を入力しても、キーボード入力文字、つまりASCII文字、もしくはUnicode文字が返ってきます。
たとえそれが数字のように見えても、強制的にそうさせない限り、数字、すなわち整数として扱われることはありません。
さて、C言語では、値をあるものから別のものにキャストする機能がありました。
キャストとは、あるデータ型を別のデータ型に変換することです。
文字から整数、整数から文字に変換することはできましたが、文字列から整数、あるいは整数から文字列に変換することはできませんでした。
そのためには特別な関数が必要でした。
atoi(ASCII to int)を使ったことがある人もいるかもしれませんが、これはASCII文字列のすべての文字を調べて、対応する整数に変換する関数です。
率直に言って、pythonではもう少しシンプルです。
あるものから別のものにキャストすれば良いのです。

整数にキャストしたい部分をint()で囲み、inputの戻り値を渡して、intのように見えるものを実際にintに変換することにしましょう。
#prompt user for x
x = int(input("x: "))

#prompt user for y
y = int(input("y: "))

#perform addition
print(x + y)
→ちゃんとx+yの整数の足し算の結果が表示される
ほら。上手くいきました。これで仕事に戻れます。
しかし、私があまり協調性のない、気のきかないユーザーで、xに「cat」と入力したらどうなるでしょう?
すると、おかしなことが起こり始めます。
~/ $ python addition.py
x: cat
Traceback (most recent call last):
File "/home/ubuntu/addition.py", line 2, in <module>
x = int(input("x: "))
ValueError: invalid literal for int() with base 10: 'cat'
プログラムを実行すると、最初のエラーが発生したことに気づきます。
そもそも私のプログラムは実行されないのです。
そして、ここでやや不可解なメッセージが表示されていることに気がつきました。
File "/home/ubuntu/addition.py", line 2, in <module>
ふむ。なるほど。addition.pyというファイルの2行目で何かおかしなことが起きているんだな。
ValueError: invalid literal for int() with base 10: 'cat'
これは非常にわかりにくい言い方ですが、整数ではないものを整数にキャストしようとした、ということを言っています。
これがCS50ライブラリのようなものを使う理由です。
ユーザーがcatやdogなどの不可解な文字列ではなく、数字だけを入力したかどうかをチェックするコードをすべて書くのは、実際にはちょっと面倒なことです。
もしCS50ライブラリを使いたくないのであれば、私たち自身がこのようなエラーチェックを実装しなければなりません。
そう。トレードオフなのです。
もしかしたら、すべてのコードを自分で書いたほうが安心かもしれません。
「たとえそれがフリーでオープンソースであっても、CS50のものであれ、他の誰かのものであれ、インターネット上の誰かのライブラリを使いたくはありません。
自分で書きたいのです。」
OK、OK。
もし自分で書きたいのであれば、今度はユーザーが10進数の数字を入力したのか、それとも他のASCII文字を入力したのかチェックするために、何行ものコードを追加しなければなりません。
このように、ライブラリを使うか使わないかは、トレードオフの関係にあります。
一般的には、この種の問題を解決するために、共通のライブラリを使用するというのが答えになるでしょう。
では、プログラムを少し変えてみましょう。
division.pyという新しいファイルを開いて、ここでちょっとした割り算をしてみましょう。
先ほどのaddition.pyをコピーペーストしますが、ここでは割り算に変更してみましょう。
▼division.py
#prompt user for x
x = int(input("x: "))

#prompt user for y
y = int(input("y: "))

#perform division
print(x / y)

xを1、yを2と入力します。
ここでEnterを押す前に聞きますが、これがもしC言語だったら、どんな答えが返ってくるでしょうか?
→切り捨てのため、0と表示されてしまう。
1/2は、0.5で浮動小数floatです。
しかし、整数を扱うのであれば、これまでは暗黙的に整数であったとしても、今は明示的にキャストしているので、0.5を捨てて0に戻した方が良いように思います。
先に進んで、python division.pyを実行して、xは1、yは2になるようにしてみましょう。
→0.5と表示される
すごい!C言語の最も厄介な機能の1つ、あるいは機能の欠如が、pythonでは、あなたが望むような割り算によって解決されたように見えます。
今日の言語、pythonのもう一つの特徴は、プログラマが期待することを、浮動小数点数や整数の微妙な違いに入り込むことなく実行してくれることです。
プログラマの代わりに正しいことをしてくれるのです。
さて、ここでもう一つのプログラムを開いてみましょう。これもweek1のものです。
conditions.cと呼ばれていました。

▼conditions.c
#include <cs50.h>
#include <stdio.h>

int main(void)
{
int x = get_int("x:");
int y = get_int("y:");

if(x < y)
{
    printf("x is less than y\n");
}
else if(x > y)
{
    printf("x is greater than y\n");
}
else
{
    printf("x is equal to y\n");
}

}
このプログラムの目的はユーザーからxとyという2つの整数を受け取り、xがyより小さければ、そう表示します。逆にxがyより大きければ、そう表示します。そうでなければxとyは等しい、と表示します。
このプログラムを、これまで見てきた構文を使って、対応するpythonコードに変換してみましょう。
CS50ライブラリを使うことで、間違った入力を別の入力にキャストする際のエラーを心配する必要がなくなると思います。

▼conditions.py
from cs50 import get_int

x = get_int("x:")
y = get_int("y:")

if x < y:
print("x is less than y")
elif x > y:
print("x is greater than y")
else:
print("x is equal to y")
→実行してみると上手くいく
あと、もう一つ指摘しておきたいことがあります。
先ほど、個々の関数名を入力するのが面倒な場合は、CS50ライブラリをインポートするための短縮構文もあると言いました。
import cs50
それは全く問題ありません。
しかし、3行目と4行目でIDEがget_intはもう認識されないと警告していることに注意してください。
これは、他の人のライブラリを使用する際に、あなたのためにそれらを名前空間化するという機能をpythonがサポートしているからです。
つまり、もうget_intを直接参照することはできないのです。
より明確に、CS50ライブラリ内のget_int関数を呼び出すようにしなければなりません。

import cs50
#「cs50にある関数get_intを呼び出しますよ」と明確にしなければならない
x = cs50.get_int("x:")
y = cs50.get_int("y:")

if x < y:
print("x is less than y")
elif x > y:
print("x is greater than y")
else:
print("x is equal to y")
つまり、お馴染みの.演算子を使って、C言語の構造体のように、CS50ライブラリの内部に入り、そこにあるget_intという関数を呼び出すのです。
これでpython conditions.pyを再実行し、1と1を入力すると、ほら、コードが再び動作するようになりました。
では、どちらが良いのでしょうか?それは時と場合によります。
つまり、もしget_intをあちこちに書いた方が読みやすいのであれば、CS50.を省略した方が多くのキーストロークを節約できるでしょう。
しかし、かなり大きなプログラムを書いていて、get_intという関数を実装している2つの異なるライブラリを使っている場合は、一方を他方と区別できるようにしたいものです。
そこで、ライブラリをその名前でインポートし、ここでやったように関数の呼び出しにプレフィックスを付ける方法が考えられますが、これはネームスペーシングとして知られています。
ネームスペーシングとは、2つの異なるネームスペースに、同じ名前の変数や関数を存在させることができるということです。
cs50ライブラリ、または他のライブラリのネームスペースの中にあれば、衝突することはありません。

ここでもう一つ、条件分岐の例を見てみましょう。
week1の別のファイルを開いてみましょう。
agree.cです。

▼agree.c
#include <cs50.h>
#include <stdio.h>

int main(void)
{
char c = get_char("Do you agree? ");
if(c == 'y' || c == 'Y')
{
printf("Agreed.\n");
}
else if(c == 'n' || c == 'N')
{
printf("Not agreed.\n");
}
}

このプログラムでは、ユーザーに同意するかどうかを入力させます。
そして、この時にはYまたはy、Nまたはnを使ってチェックしました。
さて、これをどうやって翻訳しましょうか。
ここで新しいファイルを作ってみましょう。
ここではagree.pyとします。
この問題はいくつかの方法で解くことができます。
まず最初に、利便性を考えてcs50からget_stringをインポートします。

from cs50 import get_string

s = get_string("Do you agree?")

if s == "Y" or s == "y":
print("Agreed.")
elif s == "N" or s == "n":
print("Not agreed.")

これで大丈夫だと思います。
しかし、ここでは何かがおかしい。
いくつかの違いがあります。
C言語との違いは何でしょうか?
このように複数のブール式を組み合わせた条件を使用する場合、今までの書き方とどのように違ってくるでしょうか?
そして、1つ微妙な点があります。
この例だけでもCとpythonには少なくとも2つの顕著な違いがあります。
オーディエンス(ライアン)「この件に関しては、論理演算子の記号を使う代わりに、テキストを直接入力することができるということです。」
↑ええ。論理的な「or」を表現したい場合は、文字通り"or"という英単語を入力すればよいのです。
Cでは思い出してみると||この縦棒のようなものを使っていましたが、これは問題ありません。慣れれば意味がわかります。
しかし、少なくとも英語としてはあまり読みやすいものではありません。
pythonは、実際に左から右に読む英語や英語に似た言葉をより頻繁に使うというアプローチを取りました。
実際、ここには一つのテーマがあります。
pythonのコードを読むと、句読点の多さにつまずくことがないという点で、C言語よりも英語に近くなります。
pythonコードの各行は、英語のフレーズや英文のように少しずつ読める傾向があります。
そして、もう一つ微妙な点があります。
CではYとNをシングルクォート''で囲むように気をつけました。
今週はダブルクォーテーション""で囲んでいます。
しかし、正直なところ、それは重要ではありません。
一貫性があれば、どこでもシングルクォート''を使うことができます。
しかし、pythonでは""と''の間に基本的な違いはなく、一貫性がある限り、""と''を使い分けることができます。
というのも、C言語からpythonまでの間に存在したデータ型を見た際、pythonのデータ型のリストにはcharがありませんでした。
pythonにはcharというデータ型は存在しません。
文字ベースのものはすべて文字列になります。
たとえ一文字であっても、すべては文字列です。
デメリットは細かいコントロールができないことです。
良い点は、文字列構造を使ってより多くの機能を得られることです。
例えば、文字列構造を使って大文字化のようなことができることをすでに見てきました。
さて、先に進みますが、私はこれを単純化できると思います。
Yやyだけでなく、大文字と小文字を含むYesも許容したいと思います。
まあ、s=="Yes"やs=="yes"をコードに追加していくだけでも想像はつくでしょう。
でもちょっと待ってください。ユーザーが少しだらしなくて、もしユーザーが叫んでいたり、あるいはYESだったらどうしますか?
他にもいくつかのバリエーションが考えられます。
このように、すぐに混乱に陥ってしまうのです。
しかし、最終的にYやYesという単語を大文字小文字関係なく検出したいのであれば、pythonを使ってかなり賢くできるはずです。
先に進み、sがYやYesであれば…実は以前のアイデアを借りて、[]括弧表記法を使った可変長のリストを作成することができます。
if s in ["Y","Yes"]:
あらかじめ長さを決めておく必要はありません。
この前置詞inは、pythonの新しいキーワードで、文字通りその疑問に答えてくれます。
そしてこれは以前にも使ったことがあります。
spellerを実装したときに、単語が自分の単語のセットに入っていればTrueを返すこととしました。
つまり、sがこのリストにあるかどうか、その答えに基づいてTrueかFalseを返します。
しかし繰り返しになりますが、これは文字の大小の違いを許容しません。
しかし、大したことではありません。
.lowerバージョンでは、人間が何をしても、この2つの値のリストに大文字版のsが含まれているかどうかを判断できます。
if s.lower in ["Y","Yes"]:
つまりユーザーがすべて大文字を入力しても、交互に大文字を入力しても、1つの大文字を入力しても、他のどんな組み合わせでも良いということです。
以上が私たちの条件分岐です。

質問コーナー
質問「pythonでは2つの文字列を比較するために==の構文を使っても良いのですね?」
↑はい。良いところに気がつきましたね。
pythonにはポインタがありません。
覆いの下にはまだアドレスがあります。
つまり、メモリはどこにも行っていないのです。
しかし、覆いの下では、そのすべてが言語によって管理されています。
ですから、ある文字列と別の文字列を概念的に比較したい場合、今7行目でやったように、==等式を使うことができ、pythonは「正しいこと」をしてくれます。
あなたが代わりにstrcmpを使う必要は無いのです。

さて、先に進んで別の例を見てみましょう。
良い例、より良い例、最良の例、と例を増やしていったのを覚えているかもしれませんが、これは猫が鳴いている例です。
それではweek1のmeow0と呼ばれる例を見てみましょう。

▼meow0.c
#include <stdio.h>

void meow(void);

int main(void)
{
printf("meow\n");
printf("meow\n");
printf("meow\n");
}

単純に3回鳴きました。
つまり、pythonでは、このように3回何かをするのはとても簡単なことなのです。
これをmeow.pyと呼ぶことにします。
もちろんprint("meow")として、それをコピーペーストすれば良いのです。

▼meow.py
print("meow")
print("meow")
print("meow")

しかし、week1のこの例では、ただのコピーペーストにならないようにと考えていました。
もっと良い方法があるはずです。
今回は、その方法を見てみましょう。
もしこれをC言語のforループに変更したいのであれば、for(int i = 0;i < 3;i++)のようにすることができました。
そして{}括弧の中でprintf("meow\n");とすることができました。
#include <stdio.h>

void meow(void);

int main(void)
{
for(int i = 0;i < 3;i++)
{
printf("meow\n");
}

}
これがC言語で書かれたmeow.cの次のバージョンです。
しかし、pythonではもちろん、もう少し簡潔です。
for i in range(3):
print("meow")
とすることができます。
精神的には以前のhello,worldの時と似ています。
しかし、繰り返しになりますが、このためにライブラリを用意する必要はありません。
また、main関数も、{}括弧や;も要りません。
ただひたすらコードに集中すれば良いのです。
しかし、前回はmeowプログラムを進化させて、独自のヘルパー関数、つまりmeowの上に抽象化した機能を作ることができるようにしました。
それが第3のバージョン、meow2です。

▼meow2.c
#include <stdio.h>

void meow(void);

int main(void)
{
for(int i = 0;i < 3;i++)
{
meow();
}
}

void meow(void)
{
printf("meow\n");
}

このバージョンでは少し複雑になってきていることに注意してください。
というのも、一番下にmeow関数があり、その目的は単に"meow"と表示することでしたが、新しいヘルパー関数としてそれを抽象化しています。
そしてこのコードにはforループが含まれています。
さて、pythonでは、このバージョンでも少しシンプルに動作するようになっています。

▼meow2.py
for i in range(3):
meow()
もちろん、この時点でmeow関数は存在していません。
そこでこの問題を解決したいと思います。
spellerで、meowのような独自の関数を定義できることを、簡単にではありますが、見てきました。
def meow():
関数の中に引数を入れたくなければ、入れなければ良いだけなので、voidはもう必要ありません。pythonには戻り値の指定がありません。
def meow():
print("meow")
これで"meow"と表示されるヘルパー関数を作ることができました。

実行してみると…
~/ $ python meow2.py
Traceback (most recent call last):
File "/home/ubuntu/meow2.py", line 2, in <module>
meow()
NameError: name 'meow' is not defined

↑ふむ。meow2.pyの2行目でNameError: name 'meow' is not defined
というエラーが発生しています。
さて、ここでpythonが使用している言語はC言語と少し異なります。
率直に言って、もう少し人間に優しい言葉です。
しかし、何が起こったのでしょうか?
今までつまずくことのなかった問題が発生したのでしょうか?
オーディエンス(ジニ)「関数を呼ぼうとした時に、その関数が見つからないということです。なぜなら、関数はそれを呼んでいる場所より下に記述されているからです。プロトタイプがありません。」
↑ええ。プロトタイプがありません。
そして、pythonにはプロトタイプという概念はありません。
だから残念ながら、week1で見た解決策のように、1行目を上にコピーペーストして;で終わらせるということはできません。
その代わり、こうすることができます。
meow関数をファイルの先頭に移動させれば、関数を最初に定義し、最後に使うことができます。
def meow():
print("meow")

for i in range(3):
meow()

もちろん、これは長期的には役立ちません。
なぜなら、この関数はこの関数を呼び出したいが、この関数はこの関数を呼び出したい…という状況を想像することができるからです。
そのような場合には、安全な方法で並べることはできません。
また、これではメンテナンス性が悪くなってしまいます。
Cプログラムの先頭にmainを置くことの価値の一つは、コードを理解しようとする人は、おそらく上から下に向かって読み始めるだろうということでした。
実際のmainコードを探すために、コードをすべてスクロールすることは望まないでしょう。
そこで、pythonではmain関数が必要ないにも関わらず、実際にはmain関数を定義するのが一般的です。
それは次のような形で実装されます。
def main():
そして、その下でコードをインデントしていきます。
def main():
for i in range(3):
meow()

def meow():
print("meow")

さて、mainを定義しました。
しかし、まだ何のコードも実行していません。
6行目ではmeowを定義しましたが、まだ何もコードを実行していません。
これは文字通りの意味です。
python meow2.pyを実行してEnterキーを押すと"meow meow meow"と表示されることを期待していましたが、何も表示されませんでした。
これは少し奇妙なことです。
しかし、pythonは私が指示したことをそのまま実行しています。
mainという関数を定義するように言い、meowという関数を定義するように言いました。
しかし、これらの関数のいずれかを呼び出すことは指示していません。
C言語とは少し違っていて、少し変なのですが、ここでの最も簡単な修正方法は、mainをファイルの最後に呼び出すことです。
つまり、mainを一番上に定義して、ほとんどのプログラマが期待するところに置き、一番下で呼び出すのです。

def main():
for i in range(3):
meow()

def meow():
print("meow")

main()

ほら、"meow meow meow"が戻ってきました。
mainを定義し、meowを定義し、そしてmainを呼び出しているからです。
さて、余談ですが、オンラインの様々なドキュメントやチュートリアルではこれよりもはるかに不可解な、次のような呪文を唱えているのをよく見かけます。
if name == "main":
main()
これは同じ目的のを達成するものですが、今回の目的には厳密には必要ありません。
このコードは、オンラインのリファレンス、サンプル、書籍、セクションなどで見かけたとしても、基本的には独自のライブラリ(独自のcs50ライブラリや独自の画像ぼかしライブラリなど)を実装する場合にのみ必要です。
個々のプログラムを書くときには必要ありません。
ですから、私の場合はシンプルに、文字通りmainを呼び出すだけにしておきます。
ただし、他の場合にこの文脈でなぜこのような構文が必要なのか、説明しておきました。
しかし、最後にもう一度、これを修正してみましょう。
なぜなら、C言語では私のプログラムの最後のバージョンではmeowを実行して入力を渡していたことを思い出してください。
私はmeowをnのような入力を受け取り、for(int i = 0;i < n;i++)のようにして、{}括弧の中でmeowを表示するよう定義しました。
そして、nという整数を入力とするヘルパー関数を作成しました。
そしてそれはn回ループし、n回meowを出力します。
▼meow3.c
#include <stdio.h>

void meow(void);

int main(void)
{
meow(3);
}

void meow(int n)
{
for(int i=0; i < n; i++)
{
printf("meow\n");
}
}
そして今、私は本当に素晴らしい抽象化を手に入れ、私のプログラムを抽出しました。
それはmeowと3回鳴きます。
私がどのようにmeowを実装したかはここでは重要ではありません。
これと同じことがpythonでもできます。
わざわざ型を指定する必要もなく、meowはnという引数を取ると言えば良いのです。
これでfor i in range(n)と言えば、meowをその回数だけ表示することができます。
そして、mainのループをなくして、meowと3回だけ言うことができます。
▼meow3.py
def main():
meow(3)

def meow(n):
for i in range(n):
print("meow")

main()

先ほどのコードと結果は同じですが、今回はヘルパー関数をいくつか与えることで、より洗練された方法でコードを設計しています。

今、私たちはpythonの新しい構文を見ているわけではありません。
実際の過去のCプログラムをpythonに翻訳して、その同等性を示しているに過ぎません。

では先に進んで、week1から別な例positive.cを見てみましょう。
▼positive.c
#include <cs50.h>
#include <stdio.h>

int get_positive_int(void);

int main(void)
{
int i = get_positive_int();
printf("%i\n",i);
}

int get_positive_int(void)
{
int n;
do
{
n = get_int("Positive Integer:");
}
while(n < 1)
return n;
}
これはget_positive_intという独自のヘルパー関数を定義するだけでなく、お馴染みのdo whileループを導入する機会でもありました。
そして残念ながら、今回はそれはできません。
pythonにはdo whileループはありません。
しかし、「ある条件が満たされている間に何かをすることができる」というのは、もちろんとても便利なことです。
結局のところ、このクラスでユーザーから入力を得るときには、大抵do whileを使って、少なくとも一回はユーザーに促し、オプションでユーザーが協力してくれるまで何度も繰り返します。
それではpythonでこれを実装してみましょう。
positive.pyというファイルを作成し、positive.pyの中で次のように翻訳します。
まずfrom cs50 import get_intとします。
そしてdef main():でmainを定義します。今後はこの方法を習慣づけます。
iという変数を用意して、get_positive_intを呼び出してみます。
そして先に進み、iをプリントアウトします。
from cs50 import get_int

def main():
i = get_positive_int()
print(i)
シンプルですね。
次はget_positive_intを実装しなければなりません。
これは入力を受け取る必要がないので、引数を与えるつもりはありません。
そして、今度はdo whileの処理をしなければなりません。
pythonでこのようなことをするためのPythonicな方法は、ほとんどの場合、意図的に無限ループを引き起こすことです。
考え方としては、もし何かを繰り返し行いたいのであれば、永遠にやり続けて、準備ができたときにループから抜け出せば良いのです。
では、この関数では何をしたいのでしょうか?
まず、intを取得して、ユーザーに正の整数を入力するように促します。
def get_positive_int():
while True:
n = get_int("Positive Integer: ")
そして次の行に進み、質問をします。
nが0より大きければ、つまり正の値であれば、breakします。
そして、ここでの最後のコードは、nを返すことになります。
if n > 0:
break
return n
C言語ではdo whileループを行っています。
{}括弧の外側でないとスコープに入らないので、do whileループの外側でnを宣言しなければなりませんでした。
しかしpythonでは、ここでやっていることは実際には少し違うことに気づきます。
実際にpythonでは何か違うことをやっているのでしょうか?
10行目でわざと無限ループを起こしていますが、これは次のことを永遠に行うということです。
そして、get_intでユーザーに変数nを聞いて、nが0より大きいかどうかをチェックします。
もしそうなら、ループから抜け出します。
どうすればループから抜け出せるのか?
さて、ここでのインデントは非常に一貫しています。
ループから抜け出すと元のインデントに戻り、現在は14行目(return n)になります。
returnがwhileと並んでいることに注目してください。

def get_positive_int():
while True:
n = get_int("Positive Integer: ")
if n > 0:
break
return n
つまり、ループの外に出た最初のコードです。
以前であれば、非常に明確な{}括弧を使用していました。
今ではインデントだけを頼りに、nを返しています。
では、具体的にどのような違いがあるのでしょうか?
一つ目は、do whileループが完全になくなったこと。
二つ目は、スコープが問題でなくなったことです。
pythonでは、変数は宣言した瞬間から、その関数が終わるまで存在することがわかっています。
C言語のように、最初に変数を宣言してから下で返すといったニュアンスを気にする必要はありません。
この11行目のコード(n = get_int("Positive Integer: "))を実行した瞬間、nは突然その関数の残りの部分全体に渡って存在することになります。
つまり、インデントの通り、いわばループの内側で宣言したにも関わらず、プログラムの最後にあるreturn文からアクセスできるのです。

ここで一旦止めて、ユーザーの入力を得ること、論理的にはdo whileに相当することをよりPythonicな方法で行うことについて、質問や不明点がないか確認しましょう。
オーディエンス(ピーター)「pythonでは関数をまたいで変数にアクセスできますか?」
↑良い質問ですね。答えはNoです。
関数の中で変数を宣言した場合、その変数は、いわばその関数にスコープされています。
他の場所では使用できません。
その変数を関数から返したり、関数の出力として入力に渡したりしなければなりません。
あるいはその代わりに、例えばグローバル変数として定義しなければなりません。
※プログラミングでの「スコープ」は「見ることができる範囲」のこと。定義したスコープから外れると、その変数・定数・関数はプログラミングに認識されなくなり、機能しません。

では、他にどんな翻訳ができるでしょうか?
week1でマリオの例で遊んだのを思い出してください。
例えばpythonやC言語で、画面上のピラミッドやコイン、小さなレンガなどの概念を模倣したものを出力したいとします。
さて、ここでmario.pyという新しいファイルを開いてみましょう。
ここからはビフォーアフターを見せるのではなく、pythonのコードに焦点を当てていきます。
しかし、対応するC言語版が欲しければ、いつでも振り返ることができます。
このように3つのレンガを縦にプリントアウトするにはどうしたらいいでしょうか?




さて、これまで何度か行ってきたように、pythonではfor i in range(3)のようにして、単純に#記号を出力することができます。
改行を気にする必用はありません。いわば無料で手に入るからです。

▼mario.py
for i in range(3):
print("#")

実行してみると、ほら。このマリオの構造の非常に単純なASCIIバージョンが出来上がりました。
しかし、私が代わりにコインをやりたいと思ったらどうでしょう?
この4つのレンガに現れる水平方向のコインをプリントアウトしちとしたら?
????
先に行って、これを変更してみましょう。
私のコードでi in range(4)とすると、これを4つプリントアウトすることができます。
for i in range(4):
print("?")
これを実行してみましょう。
?
?
?
?
おっと、残念。望んだ結果にはなりませんでした。
これがトレードオフの関係です。
あなたはもう改行文字をいちいち入力する必要はないと知って、ちょっとした興奮を覚えたかもしれません。
しかし、もし改行文字が不要な場合があったとしたら?
私たちは、文末に自動的に改行文字を追加することの欠点をここで見つけました。
pythonのprint関数のドキュメントを読むと、print関数も複数の引数を取ることができることがわかります。
pythonの強力なところは、単に位置指定引数をサポートしているだけではなく、複数の引数をカンマで区切って関数に渡すことができることです。
pythonは名前付き引数と呼ばれるものをサポートしており、関数、特にprintのような非常に強力な関数が複数の入力を取る場合、それらの入力はそれぞれ名前を持つことができます。
そして、関数のユーザーはその名前を指定することができます。
pythonのprintは"end"という引数をサポートしています。
そして、そのパラメータの名前を言うことで、そのパラメータに与えたい値を明示的に言うことができます。
そして、私は文字通りそれを実行しようとしています。
print("?", end="")
print関数の引数であるendの値を""にして欲しい、とprint関数に伝えます。
その理由は、ドキュメントを読むと、デフォルトは実際にこれだからです。
「end="\n"」
printのend引数のデフォルト値は「\n」なのです。
これも、Cにはなかった機能です。
Cにはオプションの引数がありませんでした。
存在するかしないかです。
というより、存在しなければならないか、存在してはならないかのどちらかです。
pythonはデフォルト値を持つオプションの引数をサポートしています。
この場合、ドキュメントによると、endのデフォルト値は引用符で囲まれた\nであり、すべての行がこの値で終わることになります。
それを何もない、いわゆる空の文字列にしたい場合は""に変更します。
では、先に行ってこれを実行してみましょう。
for i in range(4):
print("?",end="")
→????
横に4つ並べることができましたが、プロンプトが横に来ていて、ちょっと間抜けな感じがしますね。
そこで、この行の後に何も表示しないプリント文、つまり改行を加えてみましょう。
for i in range(4):
print("?",end="")
print()
実行すると、ほら、思った通りの効果が得られました。
ここで何が起こっているのかを知りたければ、""の代わりに"HELLO"のような馬鹿げたものを代わりに入れてみれば良いのです。
これで、すべてのプリントを"HELLO"で終わらせることができます。
そんなことはしないでしょうが、これが意味することの全てです。
すべてのプリント文の呼び出しをその表現で終わらせる、ということです。
ここにはちょっとクールなものがあります。
これは、あなたがちょっとしたオタクであれば、ここからが本当に面白くなってくるところです。
4つのクエスチョンマークを表示するpythonのコードを、単純にprint("?"4)にへんこうすることができます。
print("?" * 4)
→????
一行で同じことができる。
ここでもまた、ループや多くの構文を考える必要のない、この言語の多くの機能を手に入れることができました。
クエスチョンマークを4回繰り返してプリントアウトしたい(4つ横に並べたい)場合、文字通り演算子を使うことができます。
*演算子はオーバーロードされており、数値の乗算だけでなく、文字列の自動連結もサポートしています。

それでは、marioの最終バージョンを作ってみたいと思います。
marioで最後に作ったものは、以下のようなものだったことを思い出してください。
3×3のグリッドなので、marioのコードをfor i in range(3)としてみましょう。




そして、このループの中で、もう一つ入れ子になったループを作って、3列にしてみましょう。
for i in range(3):
for j in range(3):
print("#",end="")
print()
つまり、pythonは\nを自動的に与えるので、過去に書いたロジックを基本的には逆にする(\nを消す)必要があるということがわかりました。
今まで改行していた人は、今は何もする必要がありません。そして、今まで改行をしなかったのであれば、逆に何かをする必要があります。
つまり、pythonではC言語のようにループを入れ子のようにすることができるということです。
{}括弧も;も必要ありません。
しかし、繰り返しになりますが、ロジックとアイデアは同じです。
ただ、例えばいくつかの新しい構文のようなものに慣れるのに少し時間がかかります。

C言語では、かなり早い段階で整数に関する問題に遭遇したことを思い出してください。
ここでint.pyというプログラムを作ってみましょう。
そして、iという変数を1に初期化します。
i = 1
そして、これを永遠に続けてみましょう。
while True:
whileブロックの中で、iが何であれ、プリントアウトしてみましょう。
print(i)
そして、繰り返しのたびにiに1を加えていきます。
i += 1
そして、とりあえずウィンドウのサイズを大きくして、このプログラムを実行してみましょう。
→無限大までカウントし続けているのがわかります。
正直なところ、これには時間がかかりそうです。
1つずつ数えるよりも速いのは、2をかけることかもしれません。
i *= 2
プログラムを終了させるには、C言語と同じくCtrl + Cを使いました。
再実行してみて、本当に大きな数字まで数えてみましょう。
→ものすごい数まで数え続ける
インターネットの速度が遅いため、表示がなめらかではないですが、2をかけ続けた場合には本当に大きな数になります。
もし私がC言語を使ってこのプログラムを実装していたら、この時点ですでに何かが起こっていたでしょうか?
C言語でiという変数を宣言し、それをint型とし、文字通り永遠に何度も何度も倍にし続けていたら?
オーディエンス(ジョイ)「クラッシュしていたと思います。多くのメモリを使用するからです。」
↑良い考えですね。
それ自体はクラッシュするわけではありません。
何かが上手くいかないのです。
なぜならint型であることに変わりはなく、少なくともC言語では、一般的なコンピュータでは32ビット、または4バイトを使用することになります。
しかし、正直なところ、このプログラムは今頃0や負の数を表示し始めているかもしれません。
というのも、C言語の限界の1つとして、整数は32ビットまたは4バイトという有限の大きさであることを思い出してください。
つまり1、2、4、8…800万…と続けていくと、最終的には数十億になってしまうということです。
符号付きや符号なしの数字を使う場合、20億あるいは40億の閾値を超えるとすぐに大きくなりすぎてしまいます。
整数のオーバーフローが発生してしまいます。
しかし、pythonの世界では整数のオーバーフローはもう存在しません。
pythonの世界では、あなたが必要とするだけ、あなたの数字は大きくなり続けます。
この問題には自動的に対処してくれるのです。
残念ながら、浮動小数点数の不正確さはまだ残っています。
先程は1を2で割っただけでした。
しかし、他の値の割り算を続けて、十分な数の小数点を見たとしても、残念ながら、浮動小数点数の不正確さに悩まされることになります。
しかし、pythonの世界では、Javaや他の言語と同様に、必要なだけの精度、少なくともコンピュータが持っているメモリの量を使用できるライブラリ、サイエンティフィック・ライブラリがあります。
このような問題も、よりモダンな言語ではC言語より優れた形で解決されています。
しかし、この数字を何度も掛け合わせるだけで、過去に見たこともないような大きな数字を示すことができたのです。

さて、ここでもう一つのプログラム、scores.pyを見てみましょう。
これはweek2の授業の初期に扱った、スコアを記録する例です。
pythonでは72、73、33のようなスコアのリストを作成しますが、これもASCIIを使った遊びです。
scores = [72,73,33]
この文脈ではクイズのスコアです。
つまり、2つの良いスコアと1つの低いスコアですが、これらは100点満点だとします。
しかし、私の使っている構文に注目してください。
pythonの[]括弧はリストを与えてくれます。
リストの大きさを事前に決める必要はありません。
それ自体は配列ではありませんが、精神的には似ています。
しかし、自動的に大きくなったり、小さくなったりします。
そして、構文はさらにシンプルです。
これらのスコアをpythonで平均化したいと思います。
例えば「これらのスコアの平均は…」と出力して、次のようにすることができます。
print("Average: " + (sum(scores) / len(scores)))
スコアの合計をスコアの長さで割ることができます。
そして、これらのいくつかは実際にはすでに新しいものです。
pythonには、リストを入力してそれらの項目の合計を返すsum関数があることがわかりました。
また、リストの長さを示すlen関数があることはすでに見たとおりです。
すべてのスコアを合計して、スコアの総数で割ると、定義上、平均値が得られます。
それで、これを実行すると…おや、どこかで間違ったでしょうか?
~/ $ python scores.py
Traceback (most recent call last):
File "/home/ubuntu/scores.py", line 3, in <module>
print("Average: " + (sum(scores) / len(scores)))
TypeError: can only concatenate str (not "float") to str
~/ $
ああ、失敗しました。
確かに意図的ではありませんでしたが、ここでは自分を守るために頑張ります。
さて、何が起こったか?
このエラーメッセージは少し意味不明です。
TypeError: can only concatenate str (not "float") to str
要するに、この場合のpythonは、左の文字列Averageを右のfloatに連結しようとしていることが気に入らないのです。
この問題を解決するにはいくつかの方法があります。
先ほど基本的な解決方法を見ました。
ここで強調した式は、数学的には浮動小数点数ですが、文字列にしたい場合、pythonに「浮動小数点数に文字列を変換しろ」と言えば良いのです。
皆さんの中にはatoi関数の反対の意味を持つitoa関数を発見した人もいるかもしれませんが、pythonで浮動小数点数を文字列に変換することができるのです。
print("Average: " + str(sum(scores) / len(scores)))
→実行すると「59.333333333333336」
すでに少し不正確さが見られます。
完全な3分の1ではない丸め誤差が最後にあります。
しかし、別の方法でこれを行うこともできます。
これは少し醜いものです。
f-stringを使う方法です。
例えば、ここに値を入力して、平均値を出力することもできます。
print("Average: {}")
↑{}括弧の中には、変数だけを表示する必要はないことがわかりました。
実際にはコード化された式全体を表示することができます。
print("Average: {str(sum(scores) / len(scores))}")
ただし、すぐに読みにくくなるので、おかしな長さのコードを貼り付けないようにしましょう。そのような場合は変数を使うべきでしょう。
ここでは先に進み、python scores.pyを実行します。
ほら…また失敗してしまいました。(笑)
これも意図的ではありませんが、修正することができます。
そう、これをフォーマットするために先頭にfを入力するのです。
print(f"Average: {str(sum(scores) / len(scores))}")
再実行すると→59.333333333333336
ほら、同じく正確な答えが返ってきます。
つまり、私には複数の方法があります。
私はこの場合、strを必要としません。
print(f"Average: {sum(scores) / len(scores)}")
というのも、フォーマット文字列の中にある場合、pythonはそれを自動的に文字列に変換するからです。
これは素晴らしいことです。
また、これを分解して、次のようにすることもできます。
average = sum(scores) / len(scores)
print("Average: {average}")
averageという変数を与えて、それを計算し、平均値を出力するのです。
このようにC言語と同じく、問題を解決するための様々な方法があります。
どれが一番良いかは、どれが一番読みやすいか?、どれが一番メンテナンスしやすいか?、どれが一番やりやすいか?、ということにかかっています。
先に進んで、いくつかのスコアを動的に追加してみましょう。
3つのスコアをハードコーディングする代わりに、学期中のスコアを自分に聞いてみましょう。
cs50からget_intをインポートして、簡単に数字を得られるようにします。
from cs50 import get_int
スコアの空のリストを自分に与えてみましょう。
scores = []
構文はただの開いたブラケットと閉じたブラケットで、最初は何も入っていません。
次に、自分で3つのスコアを取得してみましょう。
3の範囲内のiに対して、get_intの戻り値が何であれ、scores配列に追加してみましょう。
for i in range(3):
scores.append(get_int("Score: "))
average = sum(scores) / len(scores)
print(f"Average: {average}")
ここで私がしていることに注目してください。
get_intの戻り値をappendという新しい関数に渡しているのです。
リスト、つまり[]括弧は、scoresのような変数で定義すると、それらにも関数が組み込まれていることがわかりました。
ですから、リストに数字を追加するためにscores.appendを実行することができます。
それでは先に進み、python scores.pyを実行してみましょう。
~/ $ python scores2.py
Score: 72
Score: 73
Score: 33
Average: 59.333333333333336
↑ほら、同じ答えになりました。
しかし、C言語では、配列のサイズを事前に決めるか、事前に決めずにmallocやreallocを使って配列を大きくしたり小さくしたりしなければならないために、これがどれほど苦痛だったかを考えてみてください。
pythonではリスト変数の中に入っているappend関数を使うことで、これらすべてを自動的に処理してくれます。
はい。このように機能が満載ですね。

質問のコーナー
オーディエンス(サンティアゴ)「たとえappendが書かなければならないコードの量を減らしたとしても、覆いの下ではCでやっていたmallocとreallocのようなことをそのままやっているのでしょうか?それは、python内部で起こっていることなのでしょうか?」
↑そうですね。この言語では、それはいわば無料で提供されているものです。
mallocやreallocといったものは、実際のコンピュータのメモリのように、覆いの下では配列に実装されているかもしれません。
先週見たような連結リストかもしれません。
でも、それらはすべてあなたのために行われています。
しかし、これもまた、コードが最終的に少し遅くなる理由の一つです。
あなたとあなたのコンピュータのCPUの間に誰か他の人のコードが入っていて、あなたの代わりにその仕事をしているからです。

オーディエンス(ソフィア)「fフォーマットを利用したprintと、これまで使ってきた他のフォーマットとでは、効率的な違いはありますか?」
↑心配する必要はありません。私の理解が正しければ、これにはいくつかの素晴らしい機能があります。
例えば、浮動小数点数の後に表示する小数点数を指定するための構文があります。
しかし、それはもはや%iや%s、%fなどではありません。
それらは若干異なる構文ですが、幸いなことに、それらの規約をそれほど気にする必要がないので、その数は少なくて済みます。

それではもう一つの例を見てみましょう。
過去何週間かで見たことがあるかもしれません。
以前の例のうち、より自然な形で見られた小文字の例と結びつけるために、大文字の例を簡単に作ってみましょう。
今回の例ではuppercase.pyというファイルを使います。
cs50ライブラリからget_stringをインポートしましょう。
from cs50 import get_string
これができたら、今度はユーザーから文字列をもらって、例えばBeforeと聞いてみましょう。
s = get_string("Before: ")
そして、次のようにしてみましょう。
ユーザーのために文字列全体を大文字にすることが目的です。
そして、それをすべて同じ行に表示したいです。
print("After: ",end = "")
繰り返しになりますが、beforeを出力してユーザーに入力を求め、afterで文字列全体を大文字にして表示するプログラムを書きたいのです。
では、どうすればいいか?
一つの方法はすでに見たとおりです。
例えば、s.upperのように、文字通りの方法です。
print(s.upper())

▼uppercase.py
from cs50 import get_string

s = get_string("Before: ")
print("After: ",end = "")
print(s.upper())

hiと入力してみると、狙い通りHIと返ってきます。
しかし、必要であれば、実際に個々の文字を操作することもできます。
それでは、もう少し丁寧にやってみましょう。
print(s.upper())

for c in s:
print(c)
さて、これはまだ私が望むものではありませんが、足がかりにはなるでしょう。
注意してほしいのは、「hi」を小文字で入力するとh、iが表示されますが、すべて小文字のままです。つまり、私は何も面白いことをしていないのです。
しかし、全部が同じ行に収まるように、新しい行を削除してみましょう。
print(c,end="")
OK、少しは良くなりました。
プログラムの最後に新しい行を追加して、カーソルを新しい行に移動させてみましょう。
for c in s:
print(c,end = "")
print()
さて、この時点で私は何も大文字に変えていません。
でもcをc.upper()に変えれば、期待通りにできますね。
for c in s:
print(c.upper(),end = "")
print()
そして、もう一度実行してみましょう。
~/ $ python uppercase.py
Before: hi
After: HI
これでもう一つ働くプログラムを作ることができました。
しかし、今回の新機能では、5行目のこのカッコ良さに注目してください。
for c in s:
文字列の文字を反復処理したい場合、C言語のようにiを0に初期化して[]括弧表記にする必要はありません。
「for c in s」とすれば良いのです。
forは、暗号化などを行う際に必要となる、文字列中の個々の文字の反復処理にも使用できます。
つまり、文字列全体を一度に大文字にする必要はないのです。
個々の値にアクセスすることは可能です。

C言語でできることがpythonでもできるもう一つの例があります。
ここではargv.pyというプログラムを作ってみましょう。
argvとはargument vectorの略で、コマンドラインの引数にアクセスするためのmainへの入力の名前でした。
今日、私たちはmain関数を持つことができることを見てきましたが、その必要はありませんでした。それはもう必須ではありません。
そのため、argcやargvはまだ見ていませんが、それはpythonの別の場所にあるからです。
pythonでコマンドライン引数にアクセスしたい場合、argvというモジュールをインポートできます。
そして、これは少し新しいものですが、CS50ライブラリと同じパターンを踏襲しています。
今回はsysライブラリからargvという機能をインポートしてみます。
from sys import argv
つまり、これは単にpythonに付属しているということですが、使用するには明示的にインポートする必要があります。
そして、次にこうします。
argvの長さが2になったら、数週間前にやったように「hello」と表示して、argv[1]を表示してみます。
if len(argv) == 2:
print(f"hello, {argv[1]})
ちょっとわかりにくいですが、この話はまた後でします。
先に進んで、"hello,world"のデフォルトをプリントアウトしてみましょう。
else:
print("hello,world")
何週間か前のweek2にやったことですが、ユーザーが自分の名前をプロンプトに入力すると、"hello,名前"と表示するプログラムを実行しました。
名前を入力しなければ"hello,world"と表示されます。
つまり、はっきりさせておきたいのは、コマンドライン引数なしでこのプログラムを実行すると、"hello,world"としか表示されないということです。
python argv.py Davidと入力すると、"hello,David"と表示されます。
では、これはどのように動作するのでしょうか?
さて、この最初のコードでargvにアクセスできるようになりましたが、このargvは今ではsysライブラリ、いわばsysパッケージの中に隠されています。
しかし、動作は同じです。
argcはありませんが、問題ありません。
argvがコマンドライン引数のリストであれば、lenはそのリストの長さを教えてくれるので、len(argv)は、つまりargcと同じです。
つまり、Cのバージョンから同じアイデアを再構築することができます。
ここでは、"hello,{}括弧の中にあるものすべてを出力するフォーマット文字列を置いています。そして、argvはリストです。
C言語の配列と同じように、リストは動的に成長したり縮小したりする配列に過ぎません。
[]括弧表記を使って、この場合、ユーザーが2番目に入力したものを取り出すことができます。
確認のためにargv[1]をargv[0]に変えてみると、コマンドライン引数にDavidと入力しても、奇妙なことに"hello,argv.py"と表示されます。
つまり、見えないのは"python"という言葉です。
pythonはインタプリタですが、それ自体はプログラムの実行の一部ではありません。
argv[0]には実行中のpythonプログラムの名前が入り、argv[1]にはそれ以降の最初の単語が入る、といった具合です。
つまり、この機能にはまだアクセスできますが、今度はpythonに変換することができます。
そして実際に、すべてのコマンドライン引数をプリントしたい場合は、もっと単純にこうします。
from sys import argv

for arg in argv:
print(arg)
一見しただけではわからないかもしれませんが、とても簡潔ですね。
では次に、"David Malan"のように、2つの単語を入力してみましょう。
Enterを押すと、プログラムの名前の後にプリントされたり入力されたりしたものがすべて表示されます。
~/ $ python argv2.py David Malan
argv2.py
David
Malan

ここでも、pythonでリストの反復処理がいかに綺麗にできるかに注目してください。
iや[]括弧は必要ありません。
先ほどfor c in sと言ったように、for arg in argvと言うだけで良いのです。
pythonのforループは、文字列でもリストでも、繰り返し処理したいものが何であるかを理解するのに十分賢いのです。
インクリメントや++、セミコロンなどの構文上の混乱を気にしなくて済むので、プログラムを書くのがとても楽しくなりました。

これらの例はすぐに終わってしまいますが、本当にただの翻訳に過ぎません。
今後の問題やProblem Setでは、より几帳面に前後の比較ができるようになるでしょう。

それでは、最後の例を見てみましょう。
そして、今日の最後にはpythonのような言語があるからこそできる、さらに強力なものを見ていくための時間を確保します。
今回はexit.pyという名前のプログラムを作ってみましょう。
このプログラムの目的は、単に終了ステータスを示すことです。
C言語で、mainから0や1などの値を返すという概念を導入したことを思い出してください。
pythonにもその機能があり、より大きなプログラムで見ることができるようになります。
ここでも、別の方法を示すために、sysライブラリを全部インポートしてみましょう。
import sys
ここでは、sys.argvの長さが2にならなかった場合、"missing command-line argument"とユーザーに警告することにします。
if len(sys.argv) != 2:
print("Missing command-line argument")
そしてこの後、私は先に進み、sys.exit(1)を実行します。
sys.exit(1)
そうでなければ、print(f"hello,{sys.argv[1]}")というフォーマットされた文字列を出力します。
そして最後に、デフォルトではsys.exit(0)を表示するようにしています。
ここでは何が起こっているのでしょうか?
1つは、今sysを2つの異なることに使っているので、argvを特別にインポートするのではなく、ライブラリ全体をインポートすることにしました。
しかし、そうしたために、argvという単語をどこにでも書く、ということができなくなりました。
argvという言葉の前に、それが入っているパッケージやライブラリの名前を付けなければならないのです。
だからこそ私はsys.argv、sys.argvと書き続けたのです。
しかし、私はsysライブラリの別の機能も使っており、exit関数にアクセスすることができます。
つまり、これはちょっとした二項対立なのです。
C言語ではmainから0か1、またはその他の整数を返さなければなりませんでした。
pythonではその代わりにsys.exitを同じ種類の数値で呼び出します。
つまり、構文的には少し違いますが、基本的な考え方は同じなのです。
このプログラムの目的はなんでしょうか?
このプログラムを実行すると、その目的は私にプログラムの名前の後に1つの単語だけを入力させることです。
つまり、pythonでexit.pyをただ実行すると"Missing command-line argument"と警告します。
代わりに私の名前を入力して実行すると、"hello,David"と表示されます。
これは、異なる値を返したり、プログラムから早々に戻ったりすることができることを示すためだけのプログラムです。
もはやmainにはいないので、それ自体を返すことはできませんが、pythonでは必要に応じて終了することができます。
これが比較可能なラインです。
繰り返しになりますが、私たちはC言語で見た機能のリストを整理しているだけで、たとえそれがごく自然に思いつくものでなくても、pythonの世界には類似のものがあるということです。

さて、その後の授業ではアルゴリズムに重点を置くようになったことを思い出してください。
データセットのサイズやコードの効率性が重要になってきたのはこの頃です。
例えばnumbers.pyというプログラムを書いてみましょう。
このプログラムでは最初にsysをインポートします。
import sys
そして、4,6,8,2,7,5,0のような数字の配列を渡しておきます。
numbers = [4,6,8,2,7,5,0]
これらはweek3でドアの後ろにあった数字です。
そして、0という数字を探したいとします。
C言語では線形探索を実装するために、forループとiのような変数を使って、すべての場所をチェックしました。
pythonはもっとシンプルです。
数字の中に0があれば、先に言って"Found"と出力します。
そして、そうでなければ"Not found"と出力します。
if 0 in numbers:
print("Found")
else:
print("Not found")
これで終わりです。
実際、本当にC言語版と同じにしたければ、ここでは0で終了し、ここでは1で終了します。
if 0 in numbers:
print("Found")
sys.exit(0)
else:
print("Not found")
sys.exit(1)
↑でも、これは厳密には必要ありません。

線形探索はただの前置詞的なフレーズで、数字の中に0があれば、欲しい答えの真偽が得られます。
これが線形探索です。

これを名前に対して行いたい場合はどうすれば良いでしょうか?
さて、先に進んで、同じような精神の2つ目のファイル、names.pyを作ってみましょう。
names = ["Bill","Charlie","Fred","George","Ginny","Percy","Ron"]
そして線形探索でRonが含まれているかどうかを探します。
if "Ron" in names:
print("Found")
else:
print("Not found")
今回はわざわざ0や1を出力して終了したりしません。
実行してみると、ほら、Ronを見つけることができました。
例えばリストにRonが無かったり、Ronaldだったりした場合はNot foundとなります。
完全に一致しているものだけを探し出すのです。
このようなことが簡単にできるのはとても素晴らしいことです。
さて、少し前にpythonには他のデータ型もあると述べましたが、その中には辞書に似た精神を持つキーと値のペアのコレクションを表すDICT(ディクショナリー)と呼ばれるものがあります。
例えばスペイン語の辞書はスペイン語のキーと英語の値を相互に変換しますが、この英語の辞書は英語の単語と英語の定義を持っています。
しかし、キーと値の集合体という点では同じです。
一方を使えばもう一方を見つけることができます。
さて、これをpythonに翻訳して、phonebook.pyというプログラムにしましょう。
少し前にC言語で電話帳のようなものを実装しています。
C言語では、最初はいくつかの配列を使っていましたが、それをやめて、代わりに構造体の配列を使いました。
では、より汎用的なデータ構造であるDICTを使ってみましょう。
ここで、cs50からget_stringをインポートしてみましょう。
from cs50 import get_string
続いて、人の辞書を作ってみましょう。
ここでは構文が少し違いますが、先に{}括弧を使うことにします。
辞書の目的のためにもう一度使うことにします。
そして、キーと値のペアを定義する方法を紹介します。
キーはBrianとします。そして彼の値は「+1-617-495-1000」となります。
people = {
"Brian": "+1-617-495-1000"
}
私は他のキーの一つとなります。
これをとても小さな電話帳、つまり辞書に格納します。
people = {
"Brian": "+1-617-495-1000",
"David": "+1-949-468-2750"
}
以上です。
{}括弧は技術的には別の行にあっても構いません。
キーと同じ行に移動させることもできます。
しかし、pythonには特定のスタイルに関する慣習があります。
しかし、ポイントは、「辞書は{}括弧で囲まれる」ということです。
キーと値は:コロンで区切られ、キーと値のペアは,カンマで区切られています。
ですから、上記のような書き方が一般的なのです。
これが2つのキーを持つ辞書で、それぞれが値を持っていることが少しだけわかりやすくなっています。いわば左から右への連想ですね。
さて、これはどういうことでしょうか。
例えば、誰かの名前を検索したいとします。
ここではget_stringという名前の変数を用意して、ユーザーに名前を尋ねてみましょう。
name = get_string("Name: ")
そして、携帯電話の連絡先アプリのように、自分の仮想電話帳を実装してみましょう。
名前がわかったら、もしその名前がpeopleの中にあったら、それは素晴らしいことだと思います。
peopleに名前が載っていたら、その人の番号であるpeople[name]を出力してみましょう。
if name in people:
print(f"Number: {people[name]}")
そして、ここからが辞書の威力を発揮するところです。
まずは実行してみてから説明します。
Brianの名前を検索してみましょう→Brianの番号が表示される
Davidの名前を入れるとDavidの番号が表示される
さて、ここでは何をしているのでしょうか?
まず最初にpeopleという新しい変数を宣言しています。
これは辞書で、左右のキーと値のペアのセットになっています。
そして、先ほどと同じようにget_stringを使ってユーザーから文字列を取得しています。
そしてこれも強力です。
if name in people:
これは基本的に9行目で辞書全体を検索して、指定された名前を探しています。
そして、その人の名前に関連した番号をここに返してくれています。
people[name]
そして、これを分解することで、より明確にしてみましょう。
自分にnumberという変数を与えて、更にその変数名を明示的にプリントアウトしてみましょう。
number = people[name]
if name in people:
print(f"Number: {number}")
今日は何が違うかというと、ここです。
nameがpeopleの中にある場合、pythonがその名前のすべてのキーを検索しているのです。
値は検索しません。
nameが辞書にある場合、それはキーだけを検索します。
もしあなたがそのキーを見つけたなら、私には「David」や「Brian」が辞書に載っていることが分かるのです。
注意してください。これはC言語の配列構文と同じです。
[]括弧表記を使ってDavidやBrianのような単語を使って辞書に索引を付け、電話番号のような値を返すことができるのです。
C言語やpythonでは[]括弧表記は通常、数字のためだけに使われます。
なぜなら配列やリストにはインデックスがあり、最初の場所、真ん中、最後など、その間のすべてを表す数字があるからです。
しかし、辞書が優れているのは、別名「連想配列」と呼ばれる点にあります。
辞書はキーと値のペアのコレクションです。
キーを検索するには、数字に[]括弧表記を使うのと同じように、[]括弧表記を使うだけです。
pythonは非常に洗練された言語なので、検索を代行してくれます。
更に良い事に、線形探索を使用しません。
辞書を検索する際には、先週「ハッシュテーブル」と呼んだものを使うことで、一定時間を実現しています。
辞書は通常、ハッシュテーブルのようなものを使って実装されています。
実際には一定時間を達成することが目標でしたが、非常に優れたハッシュ関数と、ハッシュ化するのに適したサイズの配列を選択すれば、一定時間に近づけることができることを思い出してください。
繰り返しになりますが、pythonにおける辞書の特徴は、非常に高いパフォーマンスが得られることです。
線形探索ではありません。
実際、以前pythonで遊び始めた時、私がspellerを再実装したときのことを思い出してください。
Pset5のために書いた多くのコードの代わりに、spellerは最大で10~20行のコードで済んでいました。spellerはsetを使っています。
setとは単なる値の集まりです。
簡単に言うと辞書と似たようなもので、覆いの中ではハッシュテーブルを使って素早く答えを出すことができます。
先ほどのスペルチェッカの例を思い出してみてください。
「words = set()」とだけ書かれたコードがありましたが、この1行のコードがスペルチェッカのほぼすべてを実装していたのです。
ポインタ、ハッシュテーブル、チェーン、連結リストのすべてが、たった1行のコードに集約されているのです。
これは言語そのものにも言えることです。
辞書は最も優れたデータ構造の一つです。
なぜなら、何かと何かを関連付ける能力は、データを整理するための素晴らしい方法だとわかったからです。

質問のコーナー
オーディエンス(ソフィア)「pythonがこれらの辞書のために定義したハッシュ関数しかないのでしょうか?それともハッシュ関数をどのようにでも変更できるのでしょうか?」
↑良い質問ですね。ハッシュ関数が付属していて、pythonがそのすべてを計算してくれます。
なぜなら、必要に応じてデータ構造を動的に変更する方法を誰かがずっと考えてくれているので、自分でspellerを実装するときのようなストレスを感じる必要がないからです。

そして、他のことも簡単になることがわかりました。
これは必ずしも一般的に必要とされる機能ではありませんが、私たちができることです。
swap.pyという簡単なプログラムを書いてみましょう。
2週間前のswap.cでは、xに1の値を与え、yに2の値を与えて、xとyの値をプリントアウトしたことを思い出してください。
x = 1
y = 2
print(f"x is {x} , y is {y}")

その後、swap(x,y)のようなことをして、ただ最善の結果を期待し、その値を再びプリントアウトしました。
さて、pythonではポインタがなく、アクセスできるアドレスもないので、先週のような解決作に頼ることはできず、変数を参照して渡すことはできません。
なぜか?機能を奪っているようにも思えますが、正直なところ、先週も含めてこの1週間が何かの兆候だったかもしれません。ポインタは難しいです。
そして、セグメンテーションフォールトが多発しています。
そして、その全てを正しく理解することは難しい。
そして、最悪の場合、誰かがアクセスしてはいけないメモリにアクセスしてしまい、プログラムが危険にさらされる可能性があります。
だからpythonはその機能を奪ったのです。
また、Javaは、あなたが先週何度もやってしまったような、あるいはやりそうだった失敗からあなた自身を守るために、その機能を奪っています。
しかし、pythonにはこれらの問題の解決策があります。
もしあなたがxとyを入れ替えたいのであれば、それは構いません。xとyを入れ替えましょう。

▼swap.py
x = 1
y = 2
print(f"x is {x} , y is {y}")
x,y = y,x
print(f"x is {x} , y is {y}")

これで、このプログラムを実行すると、ほら、一行にまとまりました。
このように、大きなダメージを与えたり、多くのミスを犯す可能性のあるものが奪われたとしても、swapの1行で、より強力な機能を取り戻すことができます。
そして、左ではx,yですが、右ではy,xであることに注目してください。
これはBrianが液体のグラスでやったように、一時的な変数を用意しなくても、覆いの下で何か魔法が起きているかのように、交換を行う効果があります。

さて、week4の最後のプログラムをいくつか実行し、week6の独自のプログラムをいくつか導入してみましょう。
今回はもう少し本格的な電話帳を実装してみましょう。
ここでphonebook.csvというファイルを作ります。

▼phonebook.csv
name,number
csvファイルとは簡単な表計算ソフトのようなものです。
このファイルを近くに置いておきましょう。
そして、最初は空っぽのphonebook.pyという新しいファイルを作ります。
先ほどと同じようにcs50からget_string関数をインポートしてきます。
一方で、csvライブラリと呼ばれるライブラリもインポートすることにします。
import csv
pythonにはcsvファイルに関連した機能がたくさん用意されていて、csvを使った処理を簡単に行えるようになっています。
その中でも特にやりたいことがあります。
先に進み、2週間前のfopenと同じように、appendモードでphonebook.csvというファイルを開いてみましょう。
そして、それをfileという変数に代入します。
file = open("phonebook.csv","a")
続いて、ユーザーの名前を取得してみましょう。
get_stringを使って誰かの名前nameを取得します。
name = get_string("Name: ")
次に、もう一度get_stringを使って、誰かの番号を取得します。
number = get_string("Number: ")
そして最後に、これが新しいコードですが、その名前と番号をファイルに保存してみましょう。
Pset4から思い出して欲しいのですが、ファイルの保存やファイルへのデータの書き出しはかなり複雑です。
例えばrecoverやblurなど、新しいファイルを作成するフィルタを実装するのには時間がかかったと思います。
しかし、csvライブラリはこれをとても簡単にしてくれます。
先に進み、writerと呼ばれるものを与えてみます。
そして、csv.writer(file)を呼び出したときの戻り値を与えることにします。
writer = csv.writer(file)
これは何をしようとしているのでしょうか?
fileは私が開こうとしているファイルです。
csv.writerはcsvライブラリに付属する関数です。
この関数は、すでに開いているファイルを入力としています。
そして、そのファイルをちょっとした機能で包み込み、プログラマである私がそのファイルに何かを書き込むのを簡単にしてくれるのです。
何をしようとしているのでしょうか?
writer変数を使って、名前と数字を含んだ行を書きます。
writer.writerow([name,number])
リストを使っているのは、列と行で構成されたスプレッドシートを思い浮かべれば、リストというのは正しい考え方だからです。
左から右へのそれぞれのセルは、リストのようなものです。
行はリストのようなものです。
ですから、ここでは意図的にリストを使うことにします。。
そして最後に、これまでと同じように、ファイルを閉じておきます。
file.close()
これは少しわかりづらいですよね。
しかし、繰り返しになりますが、get_stringはもうお馴染みです。
ですから、新しいことはcsvをインポートすることだけです。
C言語でやったのと同じように、このファイルをappendモードで開いています。
そして、この行ではcsv機能でファイルをラッピングし、writerowでこのファイルに行を書き込み、そして閉じています。
それでは実行してみましょう。
phonebook.csvを開いてみると、今のところ、先ほど手動で作成したヘッダだけが含まれています。
実行して、phonebook.csvを開いてみると…
name,numberBrian,+1-617-495-1000
↑失敗しました。ファイルを手動で作成した時にEnterキーを押すべきだったのですが、作成時に押し忘れていました。
Davidの名前と番号も追加できた。
ほら。うまくいきました。
writerrowが私のために行を入れてくれたのですから。
また、このファイルをダウンロードすると…先週行ったようにphonebook.csvをダウンロードしてみましょう。
Apple Numbersがダウンロードされていようが、Microsoft Excelがインストールされていようが、このようなものが開きます。(表になったphonebookが表示される)
ほら、pythonのコードを使って、自分だけのcsvファイルを動的に作成することができました。
そして、これを少しだけ厳密にする方法があります。
私が扱った例では大したことはありませんが、ファイルの開閉方法を少し変えることもできます。
file = open("phonebook.csv","a")

with open("phonebook.csv","a") as file:
以下の行をすべてインデントし、ファイルを閉じる処理をなくすことができます。
開いたり閉じたりする方法は大したことではありませんが、ここでやった方法はもう少しPythonicなものです。

▼phonebook.py
import csv

with open("phonebook.csv","a") as file:
name ="David"
number ="+1-949-468-2750"
writer = csv.writer(file)
writer.writerow([name,number])

このwithというキーワードは、C言語では見たことがありませんが、ファイルを開くと、最終的には自動的にファイルを閉じてくれます。
オンラインの参考書などでは、このようなことが書かれていることがあります。
でも、これも自動的にやってくれるだけです。

では、早速やってみましょう。
csvを操作できるようになったのが良いですね。
そして、もしあなたがGoogleフォームを使ったことがあれば、それはユーザーからデータを収集するための非常に一般的な方法であることがわかります。
実際に、このようなフォームが表示されるURLにアクセスしてみましょう。
ブライアン、もしチャットに入力してもよければ、cs50.ly.hogwartsというURLにアクセスしてみてください。
そして、みんなで一緒に遊んでくれるなら、ホグワーツの世界で組分け帽子によって割り当てられたいと思っている家を教えてください。
あなたならどの家に入りたいですか?
Googleフォームを使ったことのある人なら、この結果をGoogleフォーム自体で見ることができることを覚えていると思いますが、すでに122人が回答してくれています。
そして、分布やグラフなどを見ることができます。
しかし、私が欲しいのは、そこに描かれている分布ではありません。
先にスプレッドシートを開いてみます。
Googleフォームを使ったことがない人はボタンをクリックしてください。すると、今ライブで届いている回答のリストが表示されます。
デフォルトではGoogleはタイムスタンプ、つまりフォームが送信された時間と、そして実際に使用された家を記録しています。
Timestamp House
10/12/2020 15:52:09 Slytherin

だから、今からこれをやってみようと思います。
先に行って、別のタブでダウンロードしてみましょう。
ファイル→ダウンロード→csvで、csvファイルをMacにダウンロードします。
これでダウンロードフォルダに入ります。
そして、これをIDEにドラッグ&ドロップでアップロードしてみます。
→cs50のファイルのところにドラッグ&ドロップ
このファイルは「Sorting Hat Responses--Form Responses 1」と呼ばれています。
さて、このデータを操作するプログラムを書いてみましょう。
学生グループがGoogleフォームでデータを収集したり、一般的な情報を収集してcsv形式にしたりするときのように、特にGoogleが結果を図示してくれない場合、どのように集計すれば良いでしょうか?
さて、hogwarts.pyというプログラムを書いてみましょう。
これはC言語では見たことがありませんでしたが、csvライブラリをインポートしてみましょう。
import csv
最初にhousesという辞書を用意して、「Gryffindor」(初期値0)、「Hufflepuff」(初期値0)、「Ravenclaw」(初期値0)、「Slytherin」(初期値0)のようなたくさんのキーを入れておきます。
houses = {
"Gryffindor":0,
"Hufflepuff":0,
"Ravenclaw":0,
"Slytherin":0
}
pythonのdictでは、キーと値が両方とも文字列である必要はありません。
文字列と数値でも構いません。
というのも、私は最終的にこの辞書を使って、ある家や別の家へのすべての投票数を数えようと思っているからです。
では、やってみましょう。
先に進み、Sorting Hatファイルを開いてみましょう。
with open("Sorting Hat - From Responses 1.csv","r") as file:
開いて閉じるのではなく、withを使って一行で済ませることにします。
今回は今までになかったreaderを自分で用意してみます。
csvライブラリにはreader機能があって、csvファイルを自動的に読むことができます。
reader = csv.reader(file)
nextは1行目をスキップする関数です。
next(reader)
なぜなら1行目は項目名(タイムスタンプ、ハウス)なので、無視したいからです。
これは無視して、皆さんから本当のデータを頂きたいと思います。
pythonでのcsvのクールな点はここにあります。
スプレッドシートのすべての行を反復処理したい場合は、for row in reader:を実行できます。
for row in reader:
そして、先に進んで、例えば、問題の家を見てみましょう。
つまり、ある行の家は、その行の最初のエントリで、インデックスは0になります。
house = row[1]
ここでは何が起こっているのでしょうか?
さて、先ほどのGoogleスプレッドシートの話に戻ります。
Googleのスプレッドシートには2つの列があります。
Timestamp/house
csv_readerの仕組みは、1行ずつ返してくるというもので、概念的にはとてもわかりやすいもので、スプレッドシートの概念と一致しています。
しかし、各行はリストとして返されます。この場合、リストのサイズは2です。
row[0]はTimestampを、row[1]は指定されたhouseの名前を表しています。
だからこと、IDEではhouseという変数を宣言して、それをrow[1]に等しく割り当てているのです。
Timestampは気にしません。みんなほとんど同時に入力しているので。
C言語で数字を使って配列にインデックスを付けるのと同じように、houseを取得したことで、辞書にインデックスを付けることができます。
dectではインデックスに数値だけでなく文字列を使うことができます。
そこで、上で定義したhouses辞書に入り、houseキーに行き、1ずつ増やしていくことにします。
houses[house] += 1
以上です。
この時点で私はcsvファイルを開き、ライブラリを使ってそれを読みました。
このループでは、皆さんがフォームに何度も入力して作成したスプレッドシートの各行を繰り返し処理しています。
2列目、つまりrow[1]にあるものを、変数を使って取得しています。
そして、先ほど定義したhousesという辞書に入ります。
配列のようにインデックスを作成していますが、この場合はリストなので、家の名前を使って適切なキーを探します。
そして、そのキーの値をインクリメントします。
このように、辞書の中に入って、その値を変化させることができます。
そして、最後に辞書の値をプリントアウトしてみましょう。
for house in housesは、辞書のすべてのキーを反復処理して、次のようにフォーマットされた文字列を出力する格好の方法です。
for house in houses:
print(f"{house}: {houses[house]}")
これも暗号のようなものです。これについては後で説明します。
実行してみると…→各家の投票数が表示される
しかし、ここにcsvを分析するプログラムがあります。
今回はたまたまハリーポッターのデータを使って分析しました。
しかし、ユーザーから好きなデータを集めて、それをcsvとしてMacやPC、IDEにダウンロードして、そのデータを好きなように分析するコードを書けることw想像してみてください。
今回は非常にシンプルな集計を行いましたが、集計や平均、標準偏差の計算など、より高度なことができるのは間違いありません。
そのような機能もすべて手に入れることができます。

さて、プログラミングの中でも最も強力な機能を紹介する前に、辞書について何か質問はありますか?
特に質問は無し。

これから私のMacに移行して、あらかじめpythonをインストールしておき、ローカルで作業ができるようにします。
そうすれば少しは速くなるでしょう。
インターネットの速度を気にする必要はありません。
実際、自分のMacやPCにpythonインタプリタをダウンロードしてインストールし、自分のMacやPCで実行することができるのです。
しかし、私はこのIDEを使い続けることをおすすめします。
Problem Setのためには学期末まで使い続け、最終的なプロジェクトのときだけMacやPCに移行するといいでしょう。
なぜなら私はこの週末、自分のMacで馬鹿げたライブラリを動作するために膨大な時間を費やしてしまったからです。
プログラマがどこでも動くコードを書こうとしたとしても、実際には微妙なバージョンの違いやインストール方法の違い、非互換性などが存在します。
そのため、ローカルで作業する場合には、このような問題がすぐに発生します。
ですから、IDEから離れて、私が今からやるようなことをするのは、最終的なプロジェクトが終了するまで待ってみてはいかがでしょうか。
その方がこれらのデモをより明確に理解することができるでしょう。
これから、自分のMacでspeech.pyというプログラムを作ります。
あらかじめ、音声合成をサポートするライブラリをインストールしておきました。
そして、その機能にアクセスしたい場合、事前にダウンロードしてMacにインストールしたオープンソースのフリーライブラリpyttsx3をインポートすれば十分です。
import pyttsx3
ドキュメントを読んでみたのですが、先週までは文字通り一度も使ったことがありませんでした。
そして、例えばengineという変数を宣言できることがわかりました。
そして、pyttsx3.initを呼び出して、ライブラリを初期化します。
engine = pyttsx3.init()
なぜでしょうか?それはプログラマがどう設計したかによります。
まず初期化しなければなりません。
そのengineを使って、例えば"hello,world"と言ってみます。
engine.say("hello,world")
engineを実行し、自分のプログラムが終了する前にengineが終了するのを待ちます。
engine.runAndWait()
さて、自分のMacでpython speech.pyを実行してみます。
→"hello,world"とコンピュータがしゃべる
良いですね。私が入力した内容が反映されました。
そして実際、これをもっと面白くすることができるかもしれません。
こんなことを言ってみましょう。
speech.pyをもう一度開いて、いくつかの機能を追加してみましょう。
cs50ライブラリは使わず、代わりにinput関数を使います。
name = input("What's your name?")
そして、"hello,world"ではなく、f文字列を使ってみましょう。
engine.sey(f"hello,{name}")
f文字列は、常にprint文だけで使う必要はなく、文字列を取る関数の中ではいつでも使えます。
それではもう一度、python speech.pyを実行してみます。
→Davidと入力すると、"hello,David"としゃべる
変なアクセントですが、確かに合成されています。
設定をいじって、声をもう少し自然に聞こえるようにすることもできるでしょう。
しかし、これはかなりクールですね。

さて、前もって書いておいたコードを見てみましょう。
今回は別のライブラリを使っていますが、これは顔検出に関するものです。
確かに、最近のソーシャルメディアではFacebookやその他のウェブサイトが自動的にタグ付けすることが流行していますし、州政府や連邦政府、法執行機関が群衆の中から人を見つけるために顔検出を利用することも増えてきていますね。
例えば、オフィスにいる大勢の人々のような、もう少し穏やかな感じのファイルを開いてみましょう。
ここには、オフィスにいる何人かの人々の写真があります。
たくさんの顔が映っていますね。
しかし、顔の他にも紙の箱やその他の気になるものがたくさんあります。
それでは早速、detect.pyというプログラムを見てみましょう。
このファイルのほとんどはコメントになっていて、何をしているのか理解できるようになっています。
しかし、いくつかの重要な行を強調しておきます。
ここにはPillowライブラリがあり、pythonのプリインストールされた関数から画像関連の機能にアクセスしています。
from PIL import Image
そして、これはちょっとした驚きです。
import face_recognition
顔認識技術を使いたければ、単にface_recognitionをインポートすれば良いのです。
このライブラリをインポートすることで、そのような機能にアクセスできるようになります。
ここから先は、ドキュメントを読んだだけですが、face_recognition.load_image_fileというライブラリにアクセスして、その意味するところを実行する関数になっています。
image = face_recognition.load_image_file("office.jpg")
先ほど見たoffice.jpgを開いています。
青色の部分はすべてコメントですが、この行はpythonで顔認識ライブラリを使用し、与えられた画像内のすべての顔の位置を見つけ、face_locationsと呼ばれるリストに保存するために必要なコードとなっています。
face_locations = face_recognition.face_locations(image)
このコードはpythonのループで、検出された顔の中のすべての顔に対して反復処理しています。
for face_location in face_locations:
そして、以下の数行のコードは、簡単に言えば、個々の顔を切り取り、検出された顔で新しい画像を作成するだけです。
それでは、ライブラリの煩雑な詳細にはあまり触れずに、とりあえず機能は面白いので、detect.pyを実行してみましょう。
→写真が生成される。
写真を拡大すると、フィリスやジム、ロイなど、この写真で検出されたほとんどすべての顔が、実際に個々の顔として切り取られています。
フェイスブックで写真をアップロードするときに自分の上に小さな四角が表示されたことがあるなら、これはまさにフェイスブックや他の企業がそのためにサーバで使用しているコードによるものです。
こうしたらどうでしょうか?
同じThe officeの写真で、いつも目立っている人がいるじゃないですか。
誰にも好かれていない。トビーです。
トビーの顔写真を別のファイルに入れたらどうでしょう?
人ごみの中でトビーを見つけられるでしょうか?
まあ、できますね。
ではrecognize.pyというプログラムを実行してみましょう。これはオンラインで見ることができます。
似たようなコードで、それほど量は多くはありませんが、いくつかの処理を行います。
OfficeのJPEGとこのJPEGの両方を開いています。
拡大してみると、素晴らしいことに、トビーだけが顔の周りに大きな緑のボックスをつけています。これは本当に認識されたということです。
そこで今回も、コードをちらっと見てみます。
今回、recognize.pyを開いてみると、コードが数行増えています。
ここでも顔認識機能やその他の機能をインポートしています。
toby.jpgを読み込んでいます。
そして、office.jpgを読み込んでいます。
そして、ここには更にいくつかのコードがあり、トビーを探し、最終的に見つかった顔の周りに大きな緑のボックスを描いています。
繰り返しになりますが、結局のところ、これは単なるループとただの関数や変数の集まりです。
しかし、関数はかなり派手で強力なものになっています。
なぜなら、私たち自身がC言語のような言語で実装してきた、あるいはpythonの世界で垣間見てきたほかのすべての機能を利用しているからです。

ではもう一つやってみましょう。
2次元バーコード、いわゆるQRコードを作成するプログラムを開いてみましょう。
qr.pyというファイルを作成して、このファイルの中で次のことを行います。
オペレーティングシステムのライブラリをインポートし、QRコードのライブラリをインポートします。
import os
import qrcode
それではimg変数を作成して、qrcode.makeの値を割り当てます。
そして、このコースの講義ビデオのURLを貼り付けてみましょう。
img = qrcode.make("https://youtu.be/oHg5SJYRHA0")
この画像をqr.png(Portable Network Graphic。写真などのためのファイルフォーマット)という名前で保存してみましょう。
img.save("qr.png","PNG")
そして、実際にこれを開いてみましょう。
os.system("open qr.png")
つまり、3行のコードで、あるURLでQRコードを作り、qr.pngとして保存して、そのファイルを開く。
3行のコードです。
実行してみます。→QRコードが表示される。
ほら。結構速かったですよ。
もし良かったら、自分のスマホのカメラでこのQRコードをスキャンしてみてください。
YouTubeが開くはずです。

▼qr.py
import os
import qrcode

img = qrcode.make("https://youtu.be/oHg5SJYRHA0")
img.save("qr.png","PNG")
os.system("open qr.png")

しかし、私たちがやったことは、詳細は授業では説明しませんが、2次元のフォーマットにURLを埋め込んだだけです。
この2次元バーコードの中には何でも保存でき、カメラのようなもので解読すれば、最近の携帯電話に搭載されているソフトウェアが解読してくれるということです。

さて、今度は別の感覚、聴覚を使ってみましょう。
listen.pyというファイルを開いてみましょう。
そして、とても簡単なことをやってみましょう。
input関数を使って、ユーザーの入力をwordsという変数に入れてみます。
そして、シンプルにするために、すべて小文字に変換してみます。
words = input("Saying something: ").lower()
そして、次のようにします。
ユーザーの言葉を受け取り、"hello"という言葉が含まれていたら、"Hello to you too!"とプリントアウトしてみましょう。
if "hello" in words:
print("Hello to you too!")
つまり、相手が「こんにちは」と言えば、こちらも「こんにちは」と返したいのです。
同様に、「お元気ですか」と言っていたら、「元気です。ありがとう。」のように、コンピュータが返すようにします。
elif "How are you" in words:
print("I am well. Thanks.")
「さようなら」と言っていたら、「さようなら」と言わせてみましょう。
elif "goodbye" in words:
print("Goodbye to you too!")
そして最後に、それ以外の場合は「はい?」のようなものだけを出力しましょう。
認識されなかった、ということです。
else:
print("Huh?")
これは人工知能の始まりであり、AIであり、人間によるフレーズの入力と相互に作用するプログラムです。
先に行って、実行してみましょう。
→helloと入力すると「Hello to you too!」と返してくれる
なんてフレンドリーなプログラムなんでしょう!
他の言葉にもちゃんと反応します。
かなりクールですね。
前置詞inを使った非常にシンプルな文字列比較で、物事を検出することができます。
しかし、適切なライブラリを使えば、この機能をもっと強力にできるはずです。
先に進み、顔認識機能をインポートしたように、pythonの音声認識機能をインポートしてみましょう。
import speech_recognition
これも私のMacにプリインストールしたライブラリです。
recognizer = speech_recognition.Recognizer()
とします。
このライブラリを使用するためのドキュメントに従って、recognizerという変数を作成しました。
次に、これもドキュメントにあるように、
with speech_recognition.Microphone() as source:
とします。
これである意味、マイクを開放したことになりますが、これもドキュメントに従っただけです。
先に進んで、ユーザーに「何か言って」と表示します。
print("Saying something: ")
そして、その後、audioという変数を宣言して、recognizer.listen(source)に設定し、ソースとして私のマイクを渡してみましょう。
audio = recognizer.listen(source)
そしてこの下で、「You said」をプリントアウトし、その下で、ここが今日一番の難しいところですが、recognizer.recognize_google(audio)をプリントアウトしてみましょう。
print("You said: ")
print(recognizer.recognize_google(audio))
良いでしょう。
何が起こっているのでしょうか?

▼listen2.py
import speech_recognition

recognizer = speech_recognition.Recognizer()
with speech_recognition.Microphone() as source:
print("Saying something: ")
audio = recognizer.listen(source)
print("You said: ")
print(recognizer.recognize_google(audio))

このコードの最初の4行はMacのマイクへの接続を開始しています。
そして、音声認識ライブラリを使って私のマイクの音を聞き、マイクからの音声をaudioという変数に格納しています。
そして、マイクで録音した音声ファイルをgoogle.comに渡して、googleから返ってきた音声をプリントアウトしています。
さて、何が出てくるでしょうか。
先生「hello,world」→You said:hello world
先生「How are you?」→You said:how are you
Hoo!
これはかなり優れた音声認識ですね。
いわばクラウドを利用しています。Googleを通しているのです。
しかし、今度はもう少し高度にして、実際に人間に反応してみましょう。
そこで、先ほどのロジックを追加して、次のように言ってみましょう。

※先ほどのlisten.pyのwordsをaudioに変更している
words = recognizer.recognize_google(audio)
if "hello" in words:
print("Hello to you too!")
elif "how are you" in words:
print("I am well. Thanks.")
elif "goodbye" in words:
print("Goodbye to you too!")
else:
print("Huh?")
↑できるまで3回くらい失敗した。最初の1行目が大事。
音声をGoogleに渡して得られた結果のテキストをwordsに格納する必要があります。
→音声で会話できる、更に説得力のあるAIを作ることができました。
もちろん、それほど知的ではなく、あらかじめ決められた文字列を探しているだけです。
でも、それ以上のことができるに違いありません。
実際、私も中に入ってみて、私の同僚がリアルタイムで何かできないか見てみましょう。
劇場にある大きなPCでは、リアルタイムで実行できるほど高速なCPUを使って、pythonプログラムを実行しています。
このPCにはカメラが接続されていて、今見ているのは、カメラからの入力をPC上のpythonソフトウェアに入力して得られた結果です。
そして、このpythonソフトウェアを使って、過去の画像を認識するようにPCを訓練しました。
これもできるかどうか見てみましょう。
ブライアン、私をスクリーン1に映してくれませんか?
そしてロンシン、最初のゲストをロードしてくれませんか?
私たちはライブだと思います。
→画面左に先生と同じ動きをするアインシュタインの顔が映し出される
ここでも、私の口がアインシュタインに合わせて動いているのがわかりますね。
アインシュタインの頭や口の動きが私の動きと一致しています。
眉毛を上げて好奇心を表情で示すことだってできます。
そして、リアルタイムのpythonプログラムが、私の顔の動きを他人の顔にマッピングしているのがわかります。これはもちろん、ディープフェイクとして知られているものです。
ロンシン、代わりにブライアンの写真を映してもらえませんか?
→アインシュタインからブライアンに変わる
ここではブライアンが同じように満面の笑みを浮かべています。
ちょっと嘘くさいかもしれませんが、これをライブでなくプリレンダリングしたら、PCの方がもっと良い仕事ができるかもしれません。
ハーバード大学のラリー・バコウ学長をお招きして、ご登場いただくのはどうでしょう?ロンシン?
→ブライアンから学長に変わる
これはCS50、ハーバード大学がコンピュータサイエンスの知的事業とプログラミング技法を紹介するものです。
イェール大学のピーター・サロベイ学長はいかがでしょうか、ロンシン?
→ハーバード大学学長からイェール大学学長に変わる
さて、この時点で、このことの現実的な意味合いが次第に明らかになってくるはずです。
InstagramやTikTokなどで、最近の様々なモバイルアプリケーションを使って、本質的に同じことをするのは楽しいし、ゲームもあります。
しかし、今、私が速く動き始めると、画像が私に追いついてこないのがわかったと思いますが、これは政治、政府、ビジネスの世界、そして実際にはもっと一般的な現実世界において、非常に現実的な意味を持っています。
ここまでの例がそれほど説得力のあるものではないことは明らかですし、私があまり速く動き始めると、動作が同期しなくなるのはお分かりになると思いますが、想像してみてください。1年経てば、私たちのコンピュータは2倍速くなり、メモリなどもさらに増えます。
ソフトウェアはますます高性能になり、ライブラリや人工知能もより鍛えられていきます。
これからの授業のテーマは、単にテクノロジーを使って何かをする方法やコードの書き方だけでなく、「テクノロジーを使って何をすべきか」「実際にそのようなコードを書くべきか」という、より大きくて重要な問題を率直に問うことです。
私たちは事前にサロベイ学長とバコウ学長に、このような形で彼らになりすますことを許可してもらいました。
しかし、最後にもっと遊び心を持って、InstagramやTikTokなどで見かける他の例を紹介しようと思います。
ロンシン、まずはパムをお願いします。
→The officeのキャスト?に変わる

今日のCS50とpythonはここまでです。
次回にお会いしましょう。

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