# メモ

python を学ぶ。

アイデアは、サクサクっと python を学ぶための colab ノートブックを作る。

とりあえず kaggle と chainer と functional とかを材料に書き始めてみる。

数学と違って、定義や証明ではなく、問題・解答ペアで進む予定。

# 最初の例

```python
# 最初の例
x = 0
print(x) # 括弧は必須

x = x + 4 # functional ではないのでこのように書けるがこれでよいのか

if x > 0: # コロンが必要、インデントが必要 IndentationError <= camelCase
    print("x is larger than 0") # 行末の改行は自動で入る

text = "hello " * x
print(text)
```

上の例からわかること
* 変数は突然使える。 型を指定しないでよい。
* 変数は変えられる。
* if が使える。 コロンが必要。インデントが必要。
* 文字列に数字を掛けることができる。
* コメントは `#` で始まる。

実験してみてわかること
* 文字列に例えば 4.5 とかかけようとするとエラーになる。
* 4.5 とかは float らしい。 4 は int か。

補足
* `=`は代入演算子と呼ばれる
* 変数を宣言する必要はない。
* 変数に代入される値のタイプをあとから変えられる。数値を代入したあとに文字列やブール型の値を代入するができる。

```python
x = 4
print(type(x))
x = 0.0
print(type(x))
x = True
print(type(x))
x = "this is x."
print(type(x))
```

    <class 'int'>
    <class 'float'>
    <class 'bool'>
    <class 'str'>

メモ
* 型の名前: int, float, bool, str とか。
* print は改行が自動で入る。
* bool は True, False で小文字にするとエラーになる。
* True, False 以外はなにが True になりなにが False になるのか。 => 課題 真偽の値。=> python 真偽値 で検索

# いまここ

関数は関数名のあとにカッコをつけることによって、関数を呼び出す。 カッコの中には引数 ひきすう を入れる。

`type`も  python のビルトイン関数、組み込み関数、built-in 関数。

メモ
* python の組み込み関数はオブジェクトのメソッドとしての関数ではないので、使い方が決まっている。 [1,2,3].len() ではなくて、len([1,2,3])。

言葉
* 再代入 `reassignment`
* 演算子オーバーロード 文字列 * 整数 => 繰返し、のように演算子の意味を変えて使うこと。

* `int` は integer (整数) の省略形。
* `float` は浮動小数点型。

| Operator	| Name	| Description |
| ---- | ---- | ----|
|a + b  | Addition	     |   Sum of a and b |
|a - b	| Subtraction	   |   Difference of a and b |
|a * b	| Multiplication |	 Product of a and b |
|a / b	| True division	 |   Quotient of a and b |
|a // b	| Floor division |	 Quotient of a and b, removing fractional parts |
|a % b	| Modulus	       |   Integer remainder after division of a by b |
|a ** b	| Exponentiation |	 a raised to the power of b |
|-a	    | Negation	     |   The negative of a |

割り算の演算子が2種類あることに注意。

電卓の割り算は "True division" 。

演算子 `/` の結果は常に `float` 。

演算子 `//` は割り算の結果以下の最大の整数を返す。

切り下げ flooor、 切り上げ ceiling は次のようにする。

```python
print (5 // 2)
print (-(-5 // 2))
```

    2
    3

# 演算の優先順位
小学校で習う算数で、計算には順序があることを習う。PEMDAS というニーモニックを覚えている人もいるだろう。
* Parentheses カッコ
* Exponents 冪乗
* Multiplication/Division 乗算/除算
* Addtion/Subtraction 加算/減算

python  においても同じルールが適用される。

# 数字に関する組み込み関数
`min` と `max` は引数の最小値と最大値を返す。

```python
print(min(1, 2, 3))
print(max(1, 2, 3))
```

    1
    3

`abs` は引数の絶対値を返す。

```python
print(abs(32))
print(abs(-32))
```

    32
    32

`float` と `int` は数字の型の名前であるだけでなく引数の型を変換する関数として呼び出すことができる。

```python
print(float(10))
print(int(3.33))
print (int (-3.33))
print(-3.33 // 1)
print(-(-3.33 // 1))
```

    10.0
    3
    -3
    -4.0
    4.0

文字列を数字に直すこともできる。

```python
# 文字列を数字に直すこともできる
print(int('807') + 1)
10.0
print(float('33.3') + 0.2 )
```

    808
    33.5

---
質問

文字列で与えられた 2 進数や 16 進数を int に変換できるか。

逆に int を 2 進数や 16 進数に変換できるか。

基数変換と言う。base conversion, radix conversion

いまはわからないので、わかったら追記する。

* `int()` で文字列の 10 進数を数値に変換できる。
* `bin()` `hex()` `oct()` もあるみたい。
* `bin("100")` はエラーになった。
* 型はどうなっているのか。
* 少数、分数は変換できるか。このやり方ではできなかった。
* 負の数は頭にマイナスがついたがそれでいいのか。
* 汎用の $n$ 進数変換のツールはあるか。 math など。
* python 以外のプログラミング言語ではどうか。
* 数学で特殊記号とかないのか。

```python
print(int("101"))
print(bin(101))
print(0b1100100)
print(oct(101))
print(0o144)
print(hex(101))
print(0x64)
print()
print(type(int("101")))
print(type(bin(101)))
print(type(0b1100100))
print(type(oct(101)))
print(type(0o144))
print(type(hex(101)))
print(type(0x64))
print()
print(0xFF)
print(bin(0xFF))
print(hex(0b100000000))
print(hex(-101))
```

    101
    0b1100101
    100
    0o145
    100
    0x65
    100

    <class 'int'>
    <class 'str'>
    <class 'int'>
    <class 'str'>
    <class 'int'>
    <class 'str'>
    <class 'int'>

    255
    0b11111111
    0x100
    -0x65

```python
# https://runestone.academy/runestone/books/published/pythonds/index.html より

def toStr(n,base):
    convertString = "0123456789ABCDEF"
    if n < base:
        return convertString[n]
    else:
        return toStr(n//base,base) + convertString[n%base]

print(toStr(1453,16))
print(toStr(101,16))
print(toStr(101,2))
print(toStr(101,8))

```

    5AD
    65
    1100101
    145

---
# 四則演算

| 演算子	| 名前	| 説明 |
| ---- | ---- | ----|
|a + b  | 加算	     |   a と b の合計 |
|a - b	| 減算	   |   a と b の差分 |
|a * b	| 乗法 |	 a と b の積 |
|a / b	| 除算	 |   a と b の商 |
|a // b	| 床除算 |	 a と b の商から少数部分を除いたもの floor |
|a % b	| 余り       |   整数割り算の余り modulo|
|a ** b	| 冪乗 |	 a の b 乗 power |
|-a	    | マイナス	     |   マイナスa |

```python
# 円の面積を計算する
pi = 3.14159 # 少数5桁までの円周率π
diameter = 3 # 直径

# 直径の半分の長さの半径(radius)という名前の変数を作る
radius = diameter / 2

# 公式 円の面積 = 円周率かける半径の二乗 を使って円の面積を(area)計算する
area = pi * radius ** 2
print ("円の面積: " , area)
```

    円の面積:  7.0685775

---
問題 $\quad$ 変数 a と b の中身を入れ替える。

```python
# 変数 a と b の中身を入れ替える。
a = [1, 2, 3]
b = [3, 2, 1]
print(a, b)
temp = a; a = b; b = temp
print(a, b)
```

    [1, 2, 3] [3, 2, 1]
    [3, 2, 1] [1, 2, 3]

python では a,b = b,a ができる。

```python
# 別解
a = [1, 2, 3]
b = [3, 2, 1]
print(a, b)
a, b = b, a
print(a, b)
```

    [1, 2, 3] [3, 2, 1]
    [3, 2, 1] [1, 2, 3]

```python
bin(100)
```

    '0b1100100'

# 関数
* help(関数名)
* dir(クラス/オブジェクト)
* 関数名?
* 関数名??

とか。便利。

例 $\quad$ `help(round)`

`help(round)` の出力を読んでから、次のコードを実行してみる。

* round(-2.01)
* round(3.12345455, -2)
* round(1234567, 3)
* round(1234567, -3)

他の関数についてもhelp()を実行してみる。

* help(abs)

```python
# 実験
print(round(3.14))
print(type(round(3.14)))
print(round(3.14,0))
print(type(round(3.14,0)))
print (round(1234567, -3))
print(type (round(1234567, -3)))
print(round(1234567.3, -3))
print(type (round(1234567.3, -3)))
```

    3
    <class 'int'>
    3.1
    <class 'float'>
    1235000
    <class 'int'>
    1235000.0
    <class 'float'>

`round()` は単純で説明の短い関数だった。

`print()`の場合 `help(print)`の結果も長くなる。

# 関数を定義する

```python
def least_difference(a, b, c):
    """Return the smallest difference between any two numbers among a, b and c.
    >>> least_difference(1, 5, -5)
    4
    """
    diff1 = abs(a - b)
    diff2 = abs(b - c)
    diff3 = abs(a - c)
    return min(diff1, diff2, diff3)

least_difference(10, 3, 5)
```

    2

* ここでは least_difference() という関数が作られている。
* 3 つの引数 a, b, c を取る。
*  関数の定義は `def` というキーワードで始まる。`:` のあとのインデントブロックが関数定義の本体。
* キーワード `return` で関数を抜け、`return` のあとにある値が返り値となる。

```python
print(
    least_difference(1, 10, 100),
    least_difference(1, 10, 10),
    least_difference(5, 6, 7), #  python は引数の最後にカンマがついていても大丈夫。
)
```

    9 0 1

```python
help(least_difference)
```

    Help on function least_difference in module __main__:

    least_difference(a, b, c)
        Return the smallest difference between any two numbers among a, b and c.
        >>> least_difference(1, 5, -5)
        4

# ドックストリング
ドックストリング `docstring` は関数を定義する時にヘッダーの直後に書かれる 3 クォートで囲まれた数行にわたる部分のこと。 help()でこのdocstringが出力される。

numpyの関数 np.eye の docstring が参考になる。

```python
# import numpy as np
# help (np.eye)
```

---
返り値のない関数

キーワード `return` を含まない関数の返り値は `None` になる。

```python
print(type(None))
```

    <class 'NoneType'>

```python
mystery = print()
print(mystery)
```

    None

---
引数のデフォルト値

関数 `print` のオプションとしていくつかの引数をがある。
たとえば出力の項目の間に入れる文字列を指定するには `sep` という変数に値を入る。

```python
print(1, 2, 3, sep=' < ')
```

    1 < 2 < 3

しかし、値を指定しない場合、`sep`にはデフォルトの値であるスペース 1 個が入る。

```python
print(1, 2, 3)
```

    1 2 3

関数の定義の時にデフォルト値を指定するには、次のようにする。

```python
def greet(who="Colin"):
    print("Hello,", who)

greet()
greet(who="Kaggle")

# 明確で誤解が生じる余地のない場合、引数の名前を省略できる。
greet("world")
```

    Hello, Colin
    Hello, Kaggle
    Hello, world

---
関数から関数を呼び出す

関数から関数を呼び出す、というと関数型プログラムの Haskell や Scheme のようだが、それほど抽象的なことはできないが、つぎのようなやり方で関数を呼び出すことができる。

```python
def mult_by_five(x):
    """x is multiplied by five"""
    return 5 * x

def call(fn, arg):
    """Call fn on arg"""
    return fn(arg)

def squared_call(fn, arg):
    """Call fn on the result of calling fn on arg"""
    return fn(fn(arg))

def cubed_call(fn, arg):
    """Call fn on the result of fn calling fn on arg"""
    return fn(fn(fn(arg)))

print(
    call(mult_by_five, 1),
    squared_call(mult_by_five, 1),
    cubed_call(mult_by_five,1),
    sep='\n', # '\n' is the newline character - it starts a new line
)
```

    5
    25
    125

関数に作用する関数は高階関数 "Higher order functions" と呼ばれる。

python の `max` は複数の引数の中で最大のものを返すが、オプションとして `key` に関数を指定すると `key(x)` が最大なxを返す。

```python
def mod_5(x):
    """Return the remainder of x after dividing by 5"""
    return x % 5

print(
    'Which number is biggest?',
    max(100, 51, 14),
    'Which number is the biggest modulo 5?',
    max(100, 51, 14, key=mod_5),
    sep='\n',
)
```

    Which number is biggest?
    100
    Which number is the biggest modulo 5?
    14

---
問題 `docstring` に基づいて関数を完成させる。

ヒント: `round` は python の組み込み関数。

```python
def round_to_three_places(num):
    """小数点 3 桁までを求める。4 桁目を四捨五入
    >>> round_to_three_places(3.14159)
    3.141
    """
    return round(num, 3)

round_to_three_places(3.14159)
```

    3.141

---
問題  `round` の `ndigits`

`help()` によると `round` の 2 番目の引数 `ndigits` は負の数でもかまわない。

次のコードセルで実験してみる。

```python
round(12345678, -6)
```

    12000000

---
エラーメッセージを読む

エラーメッセージを読むことは、 python の勉強になる。

バギーなコードを、コメントアウト、アンコメントし、実行することを繰り返しながらコードを完成させる。

ちなみに、`ctrl`+`/` で選択した行を交互にコメントアウト、アンコメントできる。

---
問題 $\quad$絶対値の小さい方を返す関数を作る

```python
# 絶対値の小さい方を返す
def smallest_abs(x,y):
    """絶対値の小さい方を返す
    >>> smallest_abs(-10, 5)
    5
    """
    return min(abs(x), abs(y))

smallest_abs(-10, 5)
```

    5

# ブール型
ブール型の値は True と False だけ。

```python
x = True
print(x)
print(type(x))
```

    True
    <class 'bool'>

---
比較演算

| 演算	| 説明	|	|演算	|説明|
|---|---|---|---|---|
| a == b	| a は b に等しい            |	| a != b	| a は b に等しくない |
| a  b	  | a 大なり b |
| a <= b	| a 小なりイコール b	|	| a >= b	| a 大なりイコール b |

```python
# 実験
print(True)
if 1 :
    print ("1 is True")
if None :
    print ("None is True")
else: print ("None is False")
print(None)
if -1 :
    print ("-1 is True")
else: print ("-1 is False")
if 0 :
    print ("0 is True")
else: print ("0 is False")
print([])
if [] :
    print ("[] is True")
else: print ("[] is False")
if [1] :
    print ("[1] is True")
else: print ("[1] is False")
if "" :
    print ("\"\" is True")
else: print ("\"\" is False")

```

    True
    1 is True
    None is False
    None
    -1 is True
    0 is False
    []
    [] is False
    [1] is True
    "" is False

python で False なのは

```python
# python で False 以外は True
print(bool(False))
print(bool(None))
print(bool())
print(bool(EOFError)) # エラーは True
print(bool(0)) # 0 以外の数値は True だが
print(bool(0.0))
print(bool(0j))
print(bool(0+0j))
print(bool("")) # カラの str
print(bool(())) # カラのタプル
print(bool([])) # カラのリスト
print(bool({})) # カラの dict
print(int(True))
print(int(False))

```

    False
    False
    False
    True
    False
    False
    False
    False
    False
    False
    False
    False
    1
    0

```python
name = ""
if name:
    print("名前は" + name + "です")
else:
    print("名前は未登録です")
```

    名前は未登録です

---
問題 $\quad$ 引数に年齢を入れて呼び出すと、アメリカの大統領に立候補できる年齢かどうかを判定する

```python
# 引数に年齢を入れて呼び出すと、アメリカの大統領に立候補できる年齢かどうかを判定する
def can_run_for_president(age):
    """引数に年齢を入れて呼び出すと、アメリカの大統領に立候補できる年齢かどうかを判定する"""
    # The US Constitution says you must be at least 35 years old
    return age >= 35

print("Can a 19-year-old run for president?", can_run_for_president(19))
print("Can a 45-year-old run for president?", can_run_for_president(45))
```

    Can a 19-year-old run for president? False
    Can a 45-year-old run for president? True

---
ブール値の組み合わせ

`and`、`or`、`not`でブール値を組み合わせることができる。

```python
# 引数に年齢とアメリカ生まれかどうかを入れて呼び出すと、
# アメリカの大統領に立候補することが法律上できるかどうかを判定してTrueまたはFalseを返す

def can_run_for_president(age, is_natural_born_citizen):
    """Can someone of the given age and citizenship status run for president in the US?"""
    # The US Constitution says you must be a natural born citizen *and* at least 35 years old
    return is_natural_born_citizen and (age >= 35)

print(can_run_for_president(19, True))
print(can_run_for_president(55, False))
print(can_run_for_president(55, True))
```

    False
    False
    True

---
ブール演算の優先順位

`and`は`or`より先に評価される。

なるべくカッコを使ってわかりやすくする。

```python
print(True or True and False)
print(True or (True and False))
print((True or True) and False)
```

    True
    True
    False

```python
# True は False より大きい
if True > False:
    print ("True is larger than False")
```

    True is larger than False

---
条件文

ブール型は `if`、`elif`、`else` キーワードの条件文と組み合わせて使う。

条件文は `if` 文とも言われる。

```python
# 引数がゼロか、正か、負かを判定して文字列を返す
# 関数の中で print を使わない
def inspect(x):
    if x == 0:
        return "{} is zero".format(x)
    elif x > 0:
        return "{} is positive".format(x)
    elif x < 0:
        return "{} is negative".format(x)
    else:
        return "{} is unlike anything I've ever seen...".format(x)

print(inspect(0))
print(inspect(-15))
```

    0 is zero
    -15 is negative

---
質問

* なぜ else if でなく elif なのか。
* if は後置で使えるか。
* if, elif, else を改行なしで書けるか。

---
問題

数値の引数を取り、負の場合は-1、正の場合は1、0の場合は0を返す、signと呼ばれる関数を定義する。多くのプログラミング言語にはあって、 python の組み込み関数にはないもの。

```python
import numpy as np
np.sign(-1)
```

    -1

```python
from sympy import sign
sign(-1)
```

```python
# 数値の引数を取り、負の場合は-1、正の場合は1、0の場合は0を返す
def sign (x) :
    if x < 0 :
        return -1
    elif x > 0 :
        return 1
    else :
        return 0

print (sign(-1))
print (sign(1))
print (sign(0))

```

    -1
    1
    0

---
問題 $\quad$ 引数を args を set で受ける

* hotdog (ketchup, mustard, onion) => "wants all toppings with ketchup and mustard and onion"
* hotdog () => "wants plain hotdog without toppings"
* hotdog (ketchup) => "wants hotdog with once sauce ketchup"
* hotdog (ketchup, onion) => "wants two sauce ketchup and onion"

```python
def hotdog (*args):
    args = set(args)
    if args == set():
        return "wants plain hotdog without toppings"
    elif args == {"ketchup"}:
        return "wants hotdog with once sauce ketchup"
    elif args == {"mustard"}:
        return "wants hotdog with once sauce mustard"
    elif args == {"onion"}:
        return "wants hotdog with once sauce onion"
    elif args ==  {"ketchup", "mustard"}:
        return "wants two sauce ketchup and mustard"
    elif args ==  {"ketchup", "onion"}:
        return "wants two sauce ketchup and onion"
    elif args ==  {"mustard", "onion"}:
        return "wants two sauce mustard and onion"
    elif args ==  {"ketchup", "mustard", "onion"}:
        return "wants all toppings with ketchup and mustard and onion"

print(hotdog("onion", "mustard"))
print(hotdog())
print(hotdog("onion", "mustard", "ketchup"))
print(hotdog("onion"))
```

    wants two sauce mustard and onion
    wants plain hotdog without toppings
    wants all toppings with ketchup and mustard and onion
    wants hotdog with once sauce onion

---
問題

`int()` の引数にブール型を入れて、合計をとることにより、1 つだけ選ばれたことを判定することができる。

```python
def exactly_one_topping(ketchup, mustard, onion):
    return (int(ketchup) + int(mustard) + int(onion)) == 1

ketchup = True
mustard = True
onion = True

print (exactly_one_topping(ketchup, mustard, onion))
```

    False

# リスト

```python
# リストの例
primes = [2, 3, 5, 7]
print(primes)
```

    [2, 3, 5, 7]

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
print(planets)
```

    ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

リストのリストが作れる。

```python
hands = [
    ['J', 'Q', 'K'],
    ['2', '2', '2'],
    ['6', 'A', 'K'], # 最後の要素のあとにカンマがあってもかまわない
]

print(hands)
```

    [['J', 'Q', 'K'], ['2', '2', '2'], ['6', 'A', 'K']]

python のリストは型の混在が可能。

```python
my_favourite_things = [32, 'raindrops on roses', help]
print(my_favourite_things)
```

    [32, 'raindrops on roses', Type help() for interactive help, or help(object) for help about object.]

---
インデクス

リストの要素には角括弧 `square brackets` でアクセスできる。

太陽に一番近い惑星はどこか?  python のインデクスは0スタートなので、最初の要素はインデクス0となる。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
planets[0]
```

    'Mercury'

冥王星 Pluto がないのは惑星ではなくなったから。

太陽から一番遠い惑星はどこか?

リストの最後の要素はマイナスの数でアクセスできる。

```python
print(planets[-1])
print(planets[-2])
```

    Neptune
    Uranus

---
スライス、スライシング、切り取り、コロン

太陽から近い順に3つの惑星をリストにする。このようなときにスライシングが使える。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
planets[0:3]
```

    ['Mercury', 'Venus', 'Earth']

`planets[0:3]` の意味はインデクス 0 から 3 までで、3 を含まない、ということ。

スタートとエンドの要素は省略可能。最初のインデクスを省略すると、0とみなされるので、前の例は次のように書ける。

```python
planets[:3]
```

    ['Mercury', 'Venus', 'Earth']

エンドのインデクスを省略すると、リストの長さを指定したことになる。

```python
planets[3:]
```

    ['Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

`planets[3:]`の意味はインデクス3から最後まで、という意味になる。

スライシングにマイナスのインデクスを使うこともできる。

```python
# All the planets except the first and last
planets[1:-1]
```

    ['Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus']

```python
# The last 3 planets
planets[-3:]
```

    ['Saturn', 'Uranus', 'Neptune']

---
リストの更新

リストは可変 `mutable` である。コピーを作らずその場でそのものを更新できる。

インデクスやスライスで値を入れることでリストを更新できる。

例えば火星 `Mars` の名前を変えたいとしよう。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
planets[3] = 'Malacandra'
print(planets)
```

    ['Mercury', 'Venus', 'Earth', 'Malacandra', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

最初の3つの惑星の名前を短くしてみよう。

```python
planets[:3] = ['Mur', 'Vee', 'Ur']
print(planets)
```

    ['Mur', 'Vee', 'Ur', 'Malacandra', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

```python
# 元に戻す
planets[:4] = ['Mercury', 'Venus', 'Earth', 'Mars',]
planets
```

    ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

---
リスト関数

リストを扱う便利な関数がいろいろある。

`len` はリストの長さを返す。

```python
# How many planets are there?
len(planets)
```

    8

`sorted` はリストのソートしたものを返す。

```python
# The planets sorted in alphabetical order
sorted(planets)
```

    ['Earth', 'Jupiter', 'Mars', 'Mercury', 'Neptune', 'Saturn', 'Uranus', 'Venus']

`sum` は数値の合計を返す。

```python
primes = [2, 3, 5, 7]
sum(primes)
```

`min` と `max` はリストを引数として渡すこともできる。

```python
max(primes)
```

    7

```python
min(planets)
```

    'Earth'

---
オブジェクトとドットシンタクス

オブジェクトはなんらかの性質をもっている物であり、その性質にアクセするためにドットシンタクスがある。

例えば、 python の数値は `imag` と呼ばれる虚数部分を表す変数を持っていて、次のようにアクセスする。

```python
x = 12
# x is a real number, so its imaginary part is 0.
print(x.imag)
```

    0

```python
# Here's how to make a complex number, in case you've ever been curious:
c = 12 + 3j
print(c.imag)
```

    3.0

オブジェクトは関数を持っていていて、オブジェクトに付属する関数のことはメソッドと呼ばれる。

`imag`のようなオブジェクトの性質は属性 `attributes` と呼ぶ。

例えば、整数は`bit_length`というメソッドを持っていて、次のように使う。

```python
x = -3
x.bit_length()
```

    2

普通の関数の `help()` は 'help(max)` のように使うが、メソッドについての `help()` は次のようにオブジェクトのドットシンタクスとして呼び出す。 属性も同じ。 オブジェクトがないとエラーになる。

```python
x = 12
help(x.bit_length)
```

    Help on built-in function bit_length:

    bit_length() method of builtins.int instance
        Number of bits necessary to represent self in binary.

        >>> bin(37)
        '0b100101'
        >>> (37).bit_length()
        6

`list.append` は要素をリストの最後に付け加える

```python
# Pluto is a planet darn it!
planets.append('Pluto')
print(planets)
print(planets.append("Nothing")) # append に返り値はない
```

    ['Mercury', 'Venus', 'Earth', 'Malacandra', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Pluto']
    None

メソッド `append` はリスト型のメソッドなので、`help()` で呼び出す際にもなんらかのリストをつける必要がある。

`help(append)`とするとエラーになる。そこが組み込み関数の `max` などと違うところで、`max` の場合 `help(max)` で呼び出せる。

`help([].append)` のドキュメントにある、`-> None` の部分が `list.append` が返り値がないことを表している。

---
`list.pop` はリストの最後の要素を削除する。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
print(planets)
planets.pop()
print(planets)
planets.append('Neptune')
print(planets)
```

    ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
    ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus']
    ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

質問 $\quad$ ということは、push, pop ペアではなくて、append, pop ペアなのか。これでスタック操作ができるかも。

---
リストの検索

`planets`の中で`Earth`は何番目にあるか?

list.indexメソッドを使う。

```python
planets.index('Earth')
```

    2

`planets`の中で`Earth`は3番目にあることがわかる。インデクスは 0 スタートなので、2 が 3 番目という意味になる。

存在しない要素を検索するとエラーになる。

'Pluto' is not in list

```python
# 次のコードはエラーになる
# planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
# planets.index('Pluto')
```

エラーにしないために事前に調べる方法がある。

```python
# Is Earth a planet?
"Earth" in planets
```

    True

---
リストについてのすべてのメソッドを知るには、`help(planets)`もしくは`help([])とすればいい。

```python
# help([])
```

`__eq__`とか` __iadd__ `とか変な名前のメソッドがたくさん出てくるが、とりあえず気にしないでよい。たぶ直接使うことはなく、インデクスや比較演算子の背後で使われているメソッドである．

最後の方にある、`append`、`clear`、`copy` 等を学ぶのがよい。

```python
help([].clear)
```

    Help on built-in function clear:

    clear() method of builtins.list instance
        Remove all items from list.

```python
help([].copy)
```

    Help on built-in function copy:

    copy() method of builtins.list instance
        Return a shallow copy of the list.

# タプル

タプルとリストの違いは次の2点.

1. タプルを作るときには丸括弧 `parenthesis`を使う。リストは角括弧 `square bracket`。 集合は波括弧 'brace' 。

2. タプルは可変ではない。イミュータブル `immutable` である。

```python
t = (1, 2, 3)
t
```

    (1, 2, 3)

```python
t = 1, 2, 3 # t = (1, 2, 3) と同じ。カンマがタプルを生成する
t
```

    (1, 2, 3)

```python
# タプルは変更できない。エラーになる
# t = (1,2,3)
# t[0] = 100
```

タプルは複数の返り値を持つ関数に使われる。

例えば、`float` オブジェクトのメソッドである `as_integer_ration()` は分子と分母をタプルの形で返す。

```python
x = 0.125
x.as_integer_ratio()
```

    (1, 8)

結果を変数に入れるには次のようにする。

```python
numerator, denominator = x.as_integer_ratio()
print(numerator / denominator)
```

    0.125

メモ

次の例を含め、有名な ` stupid  python tricks` を考えよう。 課題。

```python
a = 1
b = 0
a, b = b, a
print(a, b)
```

    0 1

```python
a = 1
b = 0
tuple = a, b = b, a
print (tuple)
print (a, b)
```

    (0, 1)
    0 1

---
問題

リストを引数として、2 番目の要素を返す関数を作る。 もし 2 番目の要素がない場合は None を返す。

```python
def select_second(L):
    """Return the second element of the given list. If the list has no second element, return None."""
    if len(L) < 2 :
        return None
    else :
        return L[1]

L = [0, 1, 2, 3]
print(select_second(L))
print(select_second([]))
print(select_second([1]))
print(select_second([1,2]))

```

    1
    None
    None
    2

---
問題

スポーツチームの問題。それぞれのチームのリストでコーチの名前がリストの最初にあり、キャプテンが2番目、それ以降はチームのメンバー。このチームのリストがリストになっていて、最初がベストチーム、で最後がワーストチームの順に並んでいる。

ワーストチームのキャプテンを選ぶプログラムを作る。

```python
def losing_team_captain(teams):
    """Given a list of teams, where each team is a list of names, return the 2nd player (captain)
    from the last listed team
    """
    return teams[-1][1]

teams = [["bob", "mark", "john"], ["peter", "matthew", "willy"]]

print(losing_team_captain(teams))
```

    matthew

---
問題

マリオ・カートゲームの次回作では、*Purple Shell*というアイテムが装備され、それを使うとビリのレーサーとトップのレーサーが入れ替わるというとんでもないことが起きる。

次のプログラムを完成させ、Purple Shellの効果を実装する。

```python
def purple_shell(racers):
    """Given a list of racers, set the first place racer (at the front of the list) to last
    place and vice versa.

    >>> r = ["Mario", "Bowser", "Luigi"]
    >>> purple_shell(r)
    >>> r
    ["Luigi", "Bowser", "Mario"]
    """
    racers[0], racers[-1] = racers[-1], racers[0]
    return racers

r = ["Mario", "Bowser", "Luigi"]
purple_shell(r)
r
```

    ['Luigi', 'Bowser', 'Mario']

---
問題

リストの長さの確認

```python
a = [1, 2, 3]
b = [1, [2, 3]]
c = []
d = [1, 2, 3][1:]

print(len(a))
print(len(b))
print(len(c))
print(len(d))

```

    3
    2
    0
    2

---
問題

パーティ出席者のリストがある。次の 7 人のゲストのリストで Adela が最初に到着し、Fordが 最後に来た。

```python
party_attendees = ['Adela', 'Fleda', 'Owen', 'May', 'Mona', 'Gilbert', 'Ford']
```

かっこよく遅刻`fashionably late`は、パーティのゲストの半分より後に到着し、最後ではないゲストたちを指す。今回の例では Mona と Gilbet が `fashionably late` である。

リストと名前を引数に取り、`fashionably late`かどうかを返すプログラムを作る。

```python

```

```python
import math

def fashionably_late(arrivals, name):
    """Given an ordered list of arrivals to the party and a name, return whether the guest with that
    name was fashionably late.
    """
    fashionable_attendees = party_attendees[math.ceil(len(party_attendees)/2):-1]
    return name in fashionable_attendees

party_attendees = ['Adela', 'Fleda', 'Owen', 'May', 'Mona', 'Gilbert', 'Ford']

print(fashionably_late(party_attendees, "Adela"))
print(fashionably_late(party_attendees, "May" ))
print(fashionably_late(party_attendees, "Gilbert"))
print(fashionably_late(party_attendees, "Ford"))
```

    False
    False
    True
    False

# ループ

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
for planet in planets:
    print(planet, end=' ') # print all on same line
```

    Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune

`for` ループでは次のようなことが行われる。

* 使われる変数が指定される。今回の場合 `planet`
* ループする値のセット。今回の場合は `planets`
* `in` という単語を使っての変数と値のセットとのリンク

`for` ループで集合 `set` が使えるか。

使えた。 下記参照。

```python
multiplicands = (2, 2, 2, 3, 3, 5)
product = 1
for mult in multiplicands:
    product = product * mult
print(multiplicands)
print(product)

multiplicands = set(multiplicands)
print(multiplicands)
product = 1
for mult in multiplicands:
    product = product * mult
print(product)
```

    (2, 2, 2, 3, 3, 5)
    360
    {2, 3, 5}
    30

文字列の文字についてもループできる。

```python
s = 'steganograpHy is the practicE of conceaLing a file, message, image, or video within another fiLe, message, image, Or video.'
msg = ''
# print all the uppercase letters in s, one at a time
for char in s:
    if char.isupper():
        print(char, end='')
```

    HELLO

---
range()

`range()`は数列をつくる関数で、ループを書くときに役に立つ。

たとえば同じ作業を5回やりたいとする:

```python
for i in range(5):
    print("Doing important work. i =", i)
```

    Doing important work. i = 0
    Doing important work. i = 1
    Doing important work. i = 2
    Doing important work. i = 3
    Doing important work. i = 4

---
while ループ

`while` ループは条件を満たしている間、繰り返す。

```python
i = 0
while i < 10:
    print(i, end=' ')
    i += 1
```

    0 1 2 3 4 5 6 7 8 9

# リスト内包表記 list comprehension

python のリスト内包表記はたいへん人気がある。

```python
squares = [n**2 for n in range(10)]
squares
```

    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

これをリスト内包表記でなくあらわすとしたら次のようになる。

```python
squares = []
for n in range(10):
    squares.append(n**2)
squares
```

    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

次のは `if` を使ったリスト内包表記である。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
short_planets = [planet for planet in planets if len(planet) < 6]
short_planets
```

    ['Venus', 'Earth', 'Mars']

`SQL`の `WHERE` 節に似ている。

次の例ではループ変数に `if` 条件文でフィルターをかけて、それを変形している。

```python
# str.upper() returns an all-caps version of a string
loud_short_planets = [planet.upper() + '!!!!' for planet in planets if len(planet) < 6]
loud_short_planets
```

    ['VENUS!!!!', 'EARTH!!!!', 'MARS!!!!']

リスト内包表記の左部分はループ変数を含む必要はないが、たいていは含んでいる。

次のプログラムはリスト内包表記の左部分にループ変数を含まない例である。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
[32 for planet in planets]
```

    [32, 32, 32, 32, 32, 32, 32, 32]

リスト内包表記と `min`、`max`、`sum` などの関数を組み合わせるとリスト内包表記を使わなければ何行にもなるプログラムを一行で書くことができる場合がある。

次の2つのコードセルは同じことをやっている。

```python
def count_negatives(nums):
    """Return the number of negative numbers in the given list.
    >>> count_negatives([5, -1, -2, 0, 3])
    2
    """
    n_negative = 0
    for num in nums:
        if num < 0:
            n_negative = n_negative + 1
    return n_negative

count_negatives([5, -1, -2, 0, 3])
```

    2

```python
def count_negatives(nums):
    return len([num for num in nums if num < 0])

count_negatives([5, -1, -2, 0, 3])
```

    2

コードを短くすることだけが目的ならば次のようなのはどうか。

```python
def count_negatives(nums):
    # Reminder: in the "booleans and conditionals" exercises, we learned about a quirk of
    # python where it calculates something like True + True + False + True to be equal to 3.
    return sum([num < 0 for num in nums])

count_negatives([5, -1, -2, 0, 3])
```

どの解決方法が最善か、は主観的なものだが、短いコードはそれだけで価値がある。

しかし、`the zen of python` のつぎのくだりを心に留めておくことも大切だ。

```
読みやすさは大切だ。
明示的であることは暗黙的了解よりよいことだ。
```

リスト内包表記のようなツールを使ってプログラムを短く読みやすくしよう。多くの場合、短いプログラムは理解もしやすい。

---
問題

数のリストに、1つでも 7 で割り切れる数があった場合、 True を返すプログラムを書く。

```python
def has_lucky_number(nums):
    """Return whether the given list of numbers is lucky. A lucky list contains
    at least one number divisible by 7.
    """
    answer = False
    for num in nums:
        if num % 7 == 0:
            answer = True
            break
    return answer

print (has_lucky_number([1, 2, 3]))
print (has_lucky_number([1, 2, 3, 7]))
```

    False
    True

```python
# 別解
def has_lucky_number(nums):
    if sum([num % 7 == 0 for num in nums]) > 0:
        return True
    else:
        return False

print (has_lucky_number([1, 2, 3]))
print (has_lucky_number([1, 2, 3, 7]))
```

    False
    True

---
問題 2

数のリストから 2 より大きい数のリストにする。

```python
def greater_than_two (nums) :
    answer = []
    for num in nums :
        if num > 2 :
            answer.append(num)
    return answer

print (greater_than_two([1, 2, 3, 4]))
```

    [3, 4]

```python
# 別解
def greater_than_two (nums) :
    return [num for num in nums if num > 2]

print (greater_than_two([1, 2, 3, 4]))

```

    [3, 4]

python は numpy と pandas というライブラリーがあり、リストと 2 を比べて `[False, False, True, True]` のようなブール型のリストを返す機能がある。

対応する要素が `n` より大きいかどうかをブール型の要素のリストで返す関数を作る。

```python
def elementwise_greater_than(L, thresh):
    """Return a list with the same length as L, where the value at index i is
    True if L[i] is greater than thresh, and False otherwise.

    >>> elementwise_greater_than([1, 2, 3, 4], 2)
    [False, False, True, True]
    """
    answer = []
    for num in L :
        answer.append(num > thresh)
    return answer

elementwise_greater_than([1, 2, 3, 4], 2)

```

    [False, False, True, True]

```python
# 別解
[num > 2 for num in [1,2,3,4]]
```

    [False, False, True, True]

---
問題

食事のリストについて、同じ食事が2日続けて出されていれば `True`、そうでなければ `False` を返すプログラムを書く。

```python
def menu_is_boring(meals):
    """Given a list of meals served over some period of time, return True if the
    same meal has ever been served two days in a row, and False otherwise.
    """
    answer = False
    for i in range(len(meals) - 1) :
        if meals[i] == meals[i + 1] :
            answer = True
    return answer

meals = ["curry", "cutlet", "soup", "sushi"]
print (menu_is_boring(meals))
meals = ["curry", "cutlet",  "soup", "sushi", "sushi"]
print (menu_is_boring(meals))

```

    False
    True

```python
# 別解
meals = ["curry", "cutlet", "soup", "sushi"]
meals2 = ["dummy"] + meals
print(sum([a == b for (a,b) in list(zip(meals, meals2))])>0)

meals = ["curry", "cutlet",  "soup", "sushi", "sushi"]
meals2 = ["dummy"] + meals
print(sum([a == b for (a,b) in list(zip(meals, meals2))])>0)

```

    False
    True

---
問題

python  カジノにはブラックジャックのテーブルの横にスロットマシンがある。

`play_slot_machine()` を実行すると、賞金がでる。普通は 0 ドルを返してくるが、運がよければ大金が手に入る。

```python
import random

def play_slot_machine():
    r = random.random()
    if r < .005:
        return 100
    elif r < .05:
        return 50
    elif r < .25:
        return 1.5
    else:
        return 0

play_slot_machine()
```

    0

1 回 1 ドルかかる。

スロットマシンでプレイするたびの期待される儲け(ロス)はいくらか。

モンテカルロ法によっ期待値を計算する。

次の関数を完成させる。

```python
def estimate_average_slot_payout(n_runs):
    """Run the slot machine n_runs times and return the average net profit per run.
    Example calls (note that return value is nondeterministic!):
    >>> estimate_average_slot_payout(1)
    -1
    >>> estimate_average_slot_payout(1)
    0.5
    """
    result = []
    for x in range(1, n_runs):
        result.append(play_slot_machine())
    return (sum(result) - n_runs)/n_runs

estimate_average_slot_payout(1000)
```

    2.391

# 文字列処理

python の文字列はシングルクォートでもダブルクォートでも作ることができて、機能的に全く同じである。

```python
x = 'Pluto is a planet'
y = "Pluto is a planet"
x == y
```

    True

文字列がアポストロフィなどシングルクォートを含んでいる場合はダブルクォートが便利だし、逆にダブルクォートを含んでいる場合はシングルクォートが便利。

```python
print("Pluto's a planet!")
print('My dog is named "Pluto"')
```

    Pluto's a planet!
    My dog is named "Pluto"

シングルクォートで囲んだ文字列の中にシングルクォートがあればエラーになる。

```python
# 'Pluto's a planet!'
```

このような場合、バックスラッシュでエスケープすることができる。

```python
'Pluto\'s a planet!'
```

    "Pluto's a planet!"

次の表はバックスラッシュの用法についてまとめたもの。

| What you type... |	What you get |	example	| print(example) |
| --- | --- | --- | --- |
|\\'  |	'	| 'What\\'s up?'	| What's up? |
| \\"  |	"	| "That's \\"cool\\"\" |	That's "cool" |
| \\\  |	\	| "Look, a mountain: /\\"	| Look, a mountain: /\ |
| \\n  |	 | "1\n2 3" |	1  |
|  |  | | 2 3 |

表の最後の行の`\n`は、改行文字を表し、 python では改行して新しい行になる。

```python
hello = "hello\nworld"
print(hello)
```

    hello
    world

さらに python の 3 重引用符は、｀Enter` による改行をうけつける。

見たままの形で文字列を変数に入れることができる。

すでに `docstring` で見てきている。

```python
triplequoted_hello = """hello
world"""
print(triplequoted_hello)
triplequoted_hello == 'hello\nworld'
```

    hello
    world

    True

`printf()` 関数は自動的に改行コードを最後に入れる。デフォルトの `¥n` を入れさせないためには次のようにする。

```python
print("hello")
print("world")
print("hello", end='')
print("pluto", end='')
```

    hello
    world
    hellopluto

文字列とリスト

文字列 `string` は文字 `character` のシーケンス `sequence`、リスト、とみなすことができて、リストについてのほとんどの関数やメソッドが使える。

```python
# Indexing
planet = 'Pluto'
planet[0]
```

    'P'

```python
# Slicing
planet[-3:]

```

    'uto'

```python
# How long is this string?
len(planet)
```

    5

```python
# Yes, we can even loop over them
[char+'! ' for char in planet]
```

    ['P! ', 'l! ', 'u! ', 't! ', 'o! ']

しかし!!!!

リストと文字列の最大の違いは、文字列はイミュータブル `immutable` だと言うことだ。文字列は変更できない。

```python
# planet[0] = 'B'
# planet.append doesn't work either

# TypeError: 'str' object does not support item assignment
```

文字列メソッド

文字列型 `str` はたくさんの便利なメソッドがある。ここで紹介するのはほんの一部である。

```python
# ALL CAPS
claim = "Pluto is a planet!"
claim.upper()

```

    'PLUTO IS A PLANET!'

```python
# all lowercase
claim.lower()
```

    'pluto is a planet!'

```python
# Searching for the first index of a substring
claim.index('plan')
```

    11

```python
claim.startswith(planet)
```

    True

```python
claim.endswith('dwarf planet')
```

    False

---
文字列をリスト、リストを文字列に変換 `split()` と `join()`

`str.split()` はデフォルトはスペースで文字列を短い文字列のリストに変換する。長い文字列から単語を切り出すのに使える。

```python
split = "Pluto is a planet!"
words = claim.split()

words
```

    ['Pluto', 'is', 'a', 'planet!']

ホワイトスペース以外の切れ目で切り分けたい時。

```python
datestr = '1956-01-31'

year, month, day = datestr.split('-')

(year, month, day)
```

    ('1956', '01', '31')

`str.join()` は逆に文字列のリストをひとつの文字列にする。関数を呼び出す際にセパレーターを指定する。

```python
'/'.join([month, day, year])
```

    '01/31/1956'

```python
# Yes, we can put unicode characters right in our string literals :)
' 👏 '.join([word.upper() for word in words])
'PLUTO 👏 IS 👏 A 👏 PLANET!'

```

    'PLUTO 👏 IS 👏 A 👏 PLANET!'

---
文字列の整形 `.format()`

python  は`+`で文字列を結合できる。

```python
planet + ', we miss you.'

```

    'Pluto, we miss you.'

文字列でないものを文字列と結合するときは、`str()` で文字列変換してから結合する。

```python
position = 9
planet = 'Pluto'
planet + ", you'll always be the " + str(position) + "th planet to me."
```

    "Pluto, you'll always be the 9th planet to me."

`str.format()`は、引数に挿入したい値を入れるので、扱いやすい。

プレースフォルダーとして `{}` を使い、引数は文字列以外も自動的に変換されるので `str{}` で文字列変換する必要がない。

```python
# use str.format()
"{}, you'll always be the {}th planet to me.".format(planet, position)
```

    "Pluto, you'll always be the 9th planet to me."

`str.format()` はもっとすごいことができる。

以下はその一例。

```python
pluto_mass = 1.303 * 10**22
earth_mass = 5.9722 * 10**24
population = 52910390
# 2 decimal points   3 decimal points, format as percent     separate with commas
"{} weighs about {:.2} kilograms ({:.3%} of Earth's mass). It is home to {:,} Plutonians.".format(
    planet, pluto_mass, pluto_mass / earth_mass, population,
)

```

    "Pluto weighs about 1.3e+22 kilograms (0.218% of Earth's mass). It is home to 52,910,390 Plutonians."

```python
# Referring to format() arguments by index, starting from 0
s = """Pluto's a {0}.
No, it's a {1}.
{0}!
{1}!""".format('planet', 'dwarf planet')
print(s)
```

    Pluto's a planet.
    No, it's a dwarf planet.
    planet!
    dwarf planet!

`str.form` だけで1冊の本が書ける。

* pyformat.info (https://pyformat.info/)
* 公式ドキュメント (https://docs.python.org/3/library/string.html#string-formatting)

を参照のこと。

# 辞書

辞書 `dictionary` は python に組み込まれたデータ型で、キーと値をマップしたもの。

他の言語では、連想配列、associative array、ハッシュテーブル、マップとも呼ばれる。

```python
numbers = {'one':1, 'two':2, 'three':3}
numbers
```

    {'one': 1, 'two': 2, 'three': 3}

この例の場合、'one', 'two', 'three'がキーで、1, 2, 3 が対応する値。

値はリストや文字列のときのインデクスと似た文法で角括弧 `square bracket` でアクセスできる。

```python
numbers = {'one':1, 'two':2, 'three':3}
numbers['three']
```

    3

同じ文法で別のキー、値ペアを加える。

```python
numbers = {'one':1, 'two':2, 'three':3}
numbers['eleven'] = 11
numbers
```

    {'eleven': 11, 'one': 1, 'three': 3, 'two': 2}

すでにあるキーに結び付けられている値を変えるのも同じ。

```python
numbers = {'one':1, 'two':2, 'three':3}
numbers['one'] = 'Pluto'
numbers
```

    {'one': 'Pluto', 'three': 3, 'two': 2}

リスト内包表記と似たやりかたで内包表記ができる。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
planet_to_initial = { planet: planet[0] for planet in planets }
planet_to_initial
```

    {'Earth': 'E',
     'Jupiter': 'J',
     'Mars': 'M',
     'Mercury': 'M',
     'Neptune': 'N',
     'Saturn': 'S',
     'Uranus': 'U',
     'Venus': 'V'}

`in` 演算子でキーの有無を知ることができる。

```python
print ('Saturn' in planet_to_initial)
print ('Betelgeuse' in planet_to_initial)
```

    True
    False

辞書で `for` ループを行うとキーについてループする。

```python
numbers = {'one':1, 'two':2, 'three':3}
for k in numbers:
    print("{} = {}".format(k, numbers[k]))
```

    one = 1
    two = 2
    three = 3

すべてのキーが欲しいときは `dict.keys()`、すべての値が欲しいときは `dict.values()` を使う。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
planet_to_initial = { planet: planet[0] for planet in planets }

# Get all the initials, sort them alphabetically, and put them in a space-separated string.
' '.join(sorted(planet_to_initial.values()))

```

    'E J M M N S U V'

キーと値のペアを同時に取得するには次のようにする。

```python
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
planet_to_initial = { planet: planet[0] for planet in planets }

for planet, initial in planet_to_initial.items():
    print("{} begins with \"{}\"".format(planet.rjust(10), initial))
```

       Mercury begins with "M"
         Venus begins with "V"
         Earth begins with "E"
          Mars begins with "M"
       Jupiter begins with "J"
        Saturn begins with "S"
        Uranus begins with "U"
       Neptune begins with "N"

`dict.items()` という専用のメソッドがある。

```python
for pair in planet_to_initial.items() :
     print("{} begins with \"{}\"".format(pair[0], pair[1]))
```

    Mercury begins with "M"
    Venus begins with "V"
    Earth begins with "E"
    Mars begins with "M"
    Jupiter begins with "J"
    Saturn begins with "S"
    Uranus begins with "U"
    Neptune begins with "N"

`help(dict)` で dict を調べてみよう。

---
問題

文字列の長さ

```python
print (len("")) #=> 0
print (len("it's ok")) #=> 7
print (len('it\'s ok')) #=> 7
print (len("""hey""")) #=> 3
print (len('\n')) #=> 1
```

    0
    7
    7
    3
    1

---
問題

データサイエンス

データサイエンス学者は80%の時間をデータクリーニングに使い、残りの20%はデータクリーニングへの不平を言っている、という言い回しがある。

アメリカの郵便番号をチェックするプログラムをつくる。今回は文字列がちょうど 5 桁であればオーケーとする。

```python
def is_valid_zip(zip_code):
    """Returns whether the input string is a valid (5 digit) zip code
    """
    return len(zip_code) == 5

print (is_valid_zip("12"))
print (is_valid_zip("12345"))
print (is_valid_zip("123456"))
print (is_valid_zip(""))

```

    False
    True
    False
    False

---
問題

フィルタリング

とある研究者が数千のニュース記事を集めた。特定の単語で記事をフィルターにかけたいと考えている。そのための関数を作る。

関数は次の基準を満たすものとする。

* キーワードは単独で現れるものだけをとる。つまり "closed" だったら、"enclosed" は含まれない。
* 大文字小文字は区別しない。つまりキーワードが "closed" ならば"Closed the case." は含まれる。
* ピリオドはマッチに影響しない。"It is closed." は含まれる。他の句読点はないものとしてよい。

```python
def word_search(doc_list, keyword):
    """
    Takes a list of documents (each document is a string) and a keyword.
    Returns list of the index values into the original list for all documents
    containing the keyword.

    Example:
    doc_list = ["The Learn  python  Challenge Casino.", "They bought a car", "Casinoville"]
    >>> word_search(doc_list, 'casino')
    >>> [0]
    """
    match = [doc.lower().strip(".?").split().count(keyword.lower()) for doc in doc_list]
    dictOfMatch = { i : match[i] for i in range(0, len(match))}
    return  [key for key in dictOfMatch if dictOfMatch[key] > 0]

doc_list = ["The Learn  python  Challenge Casino.", "They bought a car", "Casinoville", "What are they?"]
print(word_search(doc_list, 'casino'))
print(word_search(doc_list, 'casinoville'))
print(word_search(doc_list, 'they'))

```

    [0]
    [2]
    [1, 3]

---
問題

複数キーワード検索

次に、研究者は複数のキーワードで検索したい、と思った。

いま作った `word_search` を利用して次の関数を作る。

```python
def multi_word_search(doc_list, keywords):
    """
    Takes list of documents (each document is a string) and a list of keywords.
    Returns a dictionary where each key is a keyword, and the value is a list of indices
    (from doc_list) of the documents containing that keyword

    >>> doc_list = ["The Learn  python  Challenge Casino.", "They bought a car and a casino", "Casinoville"]
    >>> keywords = ['casino', 'they']
    >>> multi_word_search(doc_list, keywords)
    {'casino': [0, 1], 'they': [1]}
    """
    dictOfMatch = { keyword : word_search(doc_list, keyword) for keyword in keywords }

    return dictOfMatch

doc_list = ["The Learn  python  Challenge Casino.", "They bought a car", "Casinoville", "What are they?"]
keywords = ['casino', 'they']

multi_word_search(doc_list, keywords)
```

    {'casino': [0], 'they': [1, 3]}

* インポート import
* ライブラリー library とそのオブジェクト object
* 演算子オーバーロード operator overload

について学ぶ。

# Import

python  の利点は  python  のために書かれ、データサイエンスなどで利用される高品質のライブラリーがたくさんあることである。

そのいくつかは標準ライブラリーで、 python の動く環境ならば必ずあるが、それ以外はかならずしも  python  と一緒ではないが、簡単に加えることができる。

import によってライブラリーにアクセスすることになる。

```python
import math

print("It's math! It has type {}".format(type(math)))

```

    It's math! It has type <class 'module'>

 math はモジュールで、モジュールとは変数の集まり(ネームスペースと呼ばれる)のことである。関数 dir() を使うと math で使われるすべての name を見ることができる。

メモ $\quad$ matplotlib も numpy も type() は <class 'module'> だった。

これらの変数にはドット・シンタクスでアクセスすることができる。いくつかは単純な値の変数でたとえば math.pi がある。

```python
print("pi to 4 significant digits = {:.4}".format(math.pi))
```

    pi to 4 significant digits = 3.142

しかし、モジュールのほとんどは関数であり、たとえば math.log がある。

```python
math.log(32, 2)
```

    5.0

もし、 math.log がなにをするかわからなければ help() を呼び出す。

```python
help(math.log)
```

    Help on built-in function log in module math:

    log(...)
        log(x[, base])

        Return the logarithm of x to the given base.
        If the base not specified, returns the natural logarithm (base e) of x.

モジュールそのもの help() を呼び出すこともできる。モジュール自体の高度な説明と、すべての関数と変数のドキュメントが得られる。

```python
# help(math)
```

モジュールの関数を頻繁に呼び出すなら、モジュール名の alias を import 時に決めるとタイプの数を少し減らすことができる。

```python
import math as mt
mt.pi
```

    3.141592653589793

`Pandas`、`Numpy`、`Tensorflow`、`Matplotlib`などポピュラーなライブラリーについては通常使われる `alias` が決まっていたりする。たとえば

```
import numpy as np
import pandas as pd
```

`as`ただ`rename`しているだけなので、次のように書いても同じである。

```python
import math
mt = math
```

`math` のすべての変数を `math` をつけずに使えるようにすることも可能だ。

たとえば `math.pi` と書かずに `pi` と書けば済むようになる。

```python
from math import *
print(pi, log(32, 2))
```

    3.141592653589793 5.0

`import *` と書くとモジュールのすべての変数がドット付きの前置きなしで直接使えるようになる。

デメリット: 純粋主義者に文句を言われる。

理由はあって、考えずにこれをやるとたいへんなことになる。

```python
from math import *
from numpy import *
# this will lead to error!!!!
# print(pi, log(32, 2))
```

このような`スターインポート`はしばしば奇妙な、デバッグのむずかしい状況を作り出す。

今回の場合、問題は `math` と `numpy` の両方が `log` という関数を持っていいて、違った意味合いを持っている、ということ。`numpy` をあとに読み込んだので`math` で読み込んだ `log` を上書き `overwrite (or shadow)` してしまった。

丁度いい妥協点はそれぞれのモジュールから個々に必要なものをインポートすることだろう。

```python
from math import log, pi
from numpy import asarray
```

---
サブモジュール

モジュールは関数や変数を指し示す名前の集まりであるが、名前はさらにモジュールの名前でもあり得る。モジュールの中のモジュールをサブモジュール `submodule` と言う。

```python
import numpy

from IPython.display import HTML, display

print("numpy.random is a", type(numpy.random))

print("it contains names such as...",
      dir(numpy.random)[-15:] )
```

    numpy.random is a <class 'module'>
    it contains names such as... ['seed', 'set_state', 'shuffle', 'standard_cauchy', 'standard_exponential', 'standard_gamma', 'standard_normal', 'standard_t', 'test', 'triangular', 'uniform', 'vonmises', 'wald', 'weibull', 'zipf']

したがって、`numpy` の場合、`random` サブモジュールの中の関数を呼び出すためには、2 回ドットが必要になる。

```python
# Roll 10 dice
import numpy
rolls = numpy.random.randint(low=1, high=6, size=10)
rolls
```

    array([3, 4, 5, 4, 1, 4, 2, 3, 1, 3])

いままでのレッスンで`int`、`float`、`bool`、`list`、`string`、`doct` などの型を学んだ。

話はそこで終わらない。これから個々の仕事でさまざまなライブラリーを使っていくと、学ばなければならない型 `type` がいろいろある。

作図ライブラリーの `matplotlib` ならば `Subplot`、`Figure`、`TickMark`、`Annotation` などを表現するオブジェクトと出会う。`pandas` 関数ならば`DataFrame`と`Series`だ。

新しい型に出会ったときの短いサバイバル・ガイドを提供する。

# 新しい型を理解するための 3 つのツール

`numpy` で関数を呼ぶと `array` が返ってくる。いままで `array` は出てこなかった。

* こんなときまず `type()` を使う。 いったいこれは何か、がわかる。
* 次に `dir()` によりなにができるか、がわかる。
* `help()` でドキュメントを読む。

```python
import numpy
rolls = numpy.random.randint(low=1, high=6, size=10)
type(rolls)
```

    numpy.ndarray

```python
# print(dir(rolls))
```

```python
# 平均をとるにはどれが使えるかな? `mean` を使ってみる
rolls.mean()
```

```python
# 扱うにはなれた型がいいので、`tolist`を使ってみみる
rolls.tolist()
```

    [4, 3, 3, 4, 1, 5, 1, 1, 1, 5]

```python
# "ravel" 属性`attribute`がおもしろそうだ。
help(rolls.ravel)

```

```python
# Okay, just tell me everything there is to know about numpy.ndarray
# (Click the "output" button to see the novel-length output)
# help(rolls)
```

もちろん、オンライン・ドキュメントもあるので、そちらを参照してもよい。

# 演算子オーバーロード Operator overloading

次の式の値は何か?

```python
# [3, 4, 1, 2, 2, 1] + 10
```

もちろんエラーになる。

しかし、次のはどうか。

```python
rolls + 10
```

    array([14, 13, 13, 14, 11, 15, 11, 11, 11, 15])

python  はコアとなる文法は定義されている。
`+`、`<`、`in`、`==`、インデクスやスライスのための `square bracket` など。
しかし、新しい型を定義する際に、足す、や同等である、の意味を定義することができる。

`list` のデザイナーはリストに数値を足すのは駄目、と決めたが、`numpy`のデザイナーは `array` に数値を足すのは、あり、と考えた。

`numpy` の `array` でいくつかの python の演算子が意外な (少なくとも `list` と違った) ふるまいをするかを示す。

```python
# At which indices are the dice less than or equal to 3?
import numpy
rolls = numpy.random.randint(low=1, high=6, size=10)
rolls <= 3
```

    array([ True,  True,  True,  True,  True,  True,  True, False,  True,
           False])

---
2 次元アレイを作る

```python
import numpy
xlist = [[1,2,3],[2,4,6],]
# Create a 2-dimensional array
x = numpy.asarray(xlist)
print("xlist = {}\nx =\n{}".format(xlist, x))
print(type(xlist))
print(type(x))
```

    xlist = [[1, 2, 3], [2, 4, 6]]
    x =
    [[1 2 3]
     [2 4 6]]
    <class 'list'>
    <class 'numpy.ndarray'>

```python
# Get the last element of the second row of our numpy array
x[1,-1]
```

    6

```python
x[1][-1]
```

    6

```python
# Get the last element of the second sublist of our nested list?
# xlist[1,-1]
```

```
TypeError: list indices must be integers or slices, not tuple
```
numpy の `ndarray` は n-dimensional array ということで $n$ 次元のデータ処理のためのデータ型で、タプルによる indexing ができるようになっている。

```python
# 実験
import numpy as np
xlist = [[1,2,3],[2,4,6],]
print(type(np.asarray(xlist)))
print(type(np.array(xlist)))
```

    <class 'numpy.ndarray'>
    <class 'numpy.ndarray'>

上記の実験では同じ結果になっているが、help() を取ってみると

`np.asarray()` は convert input to an array で

`np.array` は create an array らしい。 使い方も違う。

---
`tesorflow` での演算子オーバーロード

ディープラーニングで広く使われる `tensorflow` というライブラリーがある。

`tensorflow` では演算子オーバーロードを広範囲に使う。

```python
import tensorflow as tf
# Create two constants, each with value 1
a = tf.constant(1)
b = tf.constant(1)
# Add them together to get...
a + b
```

    <tf.Tensor: shape=(), dtype=int32, numpy=2>

a + b は 2,ではない。

`tensorflow`のドキュメントによると

> a symbolic handle to one of the outputs of an Operation.
> It does not hold the values of that operation's output,
> but instead provides a means of computing those values
> in a TensorFlow tf.Session.

ということでなに言っているかわからなくても、大事なのはかならずしも明確でなく、魔法のようなやりかたで演算子オーバーロードが行われることがある、ということを理解しておくことだ。

python  の演算子が `int`、`string`、`list` でどう働くかを理解していてもそれが `tensorflow Tensor`、`numpy ndarray`、`pandas Dataframe` でどういう意味かをそのままでわかったことにはならないが、すこし `DataFrame` をかじるとたとえば次のような式がどのような意味かが、本能的にわかり始める。

```python
# Get the rows with population over 1m in South America
# df[(df['population'] > 10**6) & (df['continent'] == 'South America')]
```

しかし、どうやってこれができているのか。上の例は 5 つほどの違った演算子オーバーロードが使われているが、それぞれなにをやっているのか。なにか間違ったときにこの辺を理解しておくと役に立つ。

---
**演算子オーバーロードの仕組み**

`help()`や`dir()`を呼び出すと前後に2つずつアンダースコアがついた名前がたくさんでてくる。

```python
# print(dir(list))
```

```
 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
 '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__',
 '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__',
 '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__',
 '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__',
 '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append',
 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove',
 'reverse', 'sort']
```

これらが、演算子オーバーロードに直接関係している。

python  のプログラマーが自分の作った型に演算子を定義する際、まず `__lt__`、`__setattr__`、`__contains__`のような前後に2つずつアンダースコアがついた名前のメソッドを作る。

このダブル・アンダースコア書式は python にとって一般に特殊な意味を持つ。

例えば、`[1, 2, 3]` が裏で `__contains__` を呼び出すとすると、それは `[1, 2, 3].__contains__(x)` としていることになる。

この辺を興味があって学びたいならば python の公式ドキュメントにたくさんのアンダースコア・メソッドについて書いてある。

----
**浮動小数点数がもつメソッド**

`int` 型の変数や、`float` 型の変数にも、その型の特徴に合わせた機能が、メソッドとして提供されている。

例えば、`float` 型の変数には、`as_integer_ratio()` と言うメソッドがあり、比がその浮動小数点数の値となるような整数の組を返す。

例えば、0.5 と言う値は、分数で表すと $\frac{1}{2}$ である。
これは、以下のようにして調べることができる。

```python
0.5.as_integer_ratio()
```

    (1, 2)

0.25 であれば、$\frac{1}{4}$ となる。

```python
0.25.as_integer_ratio()
```

    (1, 4)

```python
type(0.25.as_integer_ratio())
```

    tuple

---
`ndarray` のスライシング

list では、`numbers[:]` と `numbers` の結果が同じである。

しかし numpy と言うライブラリを用いてリストの中にリストが入ったような**多次元配列 (multidimensional array, ndarray)** では、`0 列目のすべての値`を抽出するために `[:, 0]` のような記法を用いるケースが登場する。

これは python 標準の機能ではないが、python 標準のスライス表記を拡張したものになっている。

```python
import numpy
x = numpy.asarray([[1,2],[3,4]])
x[:,0]
```

    array([1, 3])

```python
# リストをイテラブルオブジェクトに指定
for name in names:
    print('{}さん'.format(name))
```

    佐藤さん
    鈴木さん
    高橋さん

---
`enumerate()`

リストをイテラブルオブジェクトとして指定した場合、要素番号を取得できないが、状況によっては要素番号を使用したいことがある。

そのような場合は、`enumerate()` と言う組み込み関数を使う。
これにイテラブルオブジェクトを渡すと、`(要素番号, 要素)` と言うタプルを 1 つずつ返すイテラブルオブジェクトになる。

```python
# enumerate の使い方
names = ['佐藤','鈴木','高橋']
for i, name in enumerate(names):
    message = '{}番目: {}さん'.format(i, name)
    print(message)
```

    0番目: 佐藤さん
    1番目: 鈴木さん
    2番目: 高橋さん

```python
# 実験
print(list(enumerate(names)))
```

    [(0, '佐藤'), (1, '鈴木'), (2, '高橋')]

`enumerate()` と同様、`for` 文と合わせてよく使う組み込み関数に `zip()` がある。

`zip()` は、複数のイテラブルオブジェクトを受け取り、その要素のペアを順番に返すイテラブルオブジェクトを作る。
このイテラブルオブジェクトは、渡されたイテラブルオブジェクトそれぞれの先頭の要素から順番に、タプルに束ねて返す。
このイテラブルオブジェクトの長さは、渡されたイテラブルオブジェクトのうち最も短い長さと一致する。

```python
# zip の使い方
names = ['python', 'Chainer']
versions = ['3.7', '5.3.0']
suffixes = ['!!', '!!', '?']

for name, version, suffix in zip(names, versions, suffixes):
    print('{} {} {}'.format(name, version, suffix))

#実験
print(list(zip(names, versions, suffixes)))
```

    Python 3.7 !!
    Chainer 5.3.0 !!
    [('Python', '3.7', '!!'), ('Chainer', '5.3.0', '!!')]

`suffixes` の要素数は 3 だが、より短いイテラブルオブジェクトと共に `zip` に渡されたため、先頭から 2 つ目までしか値が取り出されていない。

**条件分岐（if 文）**

`if` は、指定した条件が `True` か `False` かによって、処理を変えるための制御構文である。

**python の if は文であって、式ではない。**

`elif` と `else` は任意であり、`elif` は 1 つだけでなく複数連ねることができる。

**関数を定義する**

```
def 関数名 (引数1,2,...): ヘッダー
    処理ブロック
```

例えば、**受け取った値を 2 倍して表示する関数**を作ってみる。

関数を定義するには、まず名前を決める必要がある。
今回は `double()` と言う名前の関数を定義してみる。

関数も制御構文と同じく**ヘッダー**と**ブロック**を持っている。

---
無名関数の書き方

```python
# 関数 double() の定義
def double(x):
    print(2 * x)
```

```python
# 実験 無名関数 lambda => 定義と同じ構文
(lambda x: print (2*x))(3)
```

    6

`double(x)` における `x` のように、関数に渡される変数や値のことを**引数 (argument, ひきすう)** と言う。
上の例は、名前が `double` で、1つの引数 `x` をとり、`2 * x` と言う計算を行い、その結果を表示している。

---
**引数のデフォルト値**

引数には、あらかじめ値を与えておくことができる。

これは、引数をとる関数を定義する際に、何も引数に値が渡されなかったときにどのような値がその引数に渡されたことにするかをあらかじめ決めておける機能で、その値のことを**デフォルト値**と呼ぶ。

例えば、上の `hello()` と言う関数に、`message` と言う引数をもたせ、そこにデフォルト値を設定しておく。

```python
def hello(message='Chainerチュートリアルにようこそ'):
    print(message)
```

この関数は引数に何も与えずに呼び出すと、「Chainerチュートリアルにようこそ」と言うメッセージを表示し、引数に別な値が渡されると、その値を表示する。

```python
hello()
```

    Chainerチュートリアルにようこそ

```python
hello('Welcome to Chainer tutorial')
```

    Welcome to Chainer tutorial

デフォルト値が与えられていない引数は、関数呼び出しの際に必ず何らかの値が渡される必要があるが、デフォルト値を持つ場合は、何も指定しなくても関数を呼び出すことができるようになる。

---
# 変数のスコープ

関数の中で定義した変数は基本的には関数の外では利用できない。

例えば、以下の例を見てみる。

```python
a = 1

# 関数の内部で a に 2 を代入
def change():
    a = 2

change()

a
```

    1

関数の外で `a = 1` と初期化した変数と同じ名前の変数に対して、`change()` 関数の内部で `a = 2` と言う代入を行っているにもかかわらず、`change()` 関数の実行後にも関数の外側では `a` の値は 1 のままになっている。
**関数の外側で定義された変数** `a` **に、関数内部での処理が影響していないことがわかる。**

なぜこうなるかと言うと、関数の中で変数に値が代入されるとき、その変数はその関数の**スコープ (scope)** でだけ有効な**ローカル変数**になり、関数の外にある同じ名前の変数とは別のものを指すようになるためである。
スコープとは、その変数が参照可能な範囲のことである。
上の例では、`a = 2` の代入を行った時点で`change()` 関数のスコープに `a` と言う変数が作られ、`change()` 関数の中からは `a` といえばこれを指すようになる。関数から抜けると、`a` は 1 を値に持つ外側の変数を指すようになる。

ただし、代入を行わずに、参照するだけであれば、関数の内側から外側で定義された変数を利用することができる。

```python
a = 1

def change():
    print('From inside:', a)

change()

print('From outside:', a)
```

    From inside: 1
    From outside: 1

この場合は、`change()` 関数のスコープには `a` と言う変数は作られないので、関数の中で `a` といえば外側で定義された変数を指す。

関数の外で定義された変数は**グローバル変数**と呼ばれる。
グローバル変数は、特に特別な記述を要せず参照することはできるが、関数の中で**代入**を行う場合は、`global` 文を使って、代入先をグローバル変数とする宣言を行う必要がある。

```python
a = 1

def change():
    global a  # a をグローバル変数である宣言
    a = 2       # グローバル変数への代入

# 関数の実行
change()

# 結果の確認 <- a の値が上書きされている
a
```

    2

`global a` と言う行を `change()` 関数内で `a` と言う変数を使用する前に追加すると、その行以降は `a` と言う変数への代入も関数の外側で定義されたグローバル変数の `a` に対して行われる。

---
# クラス

**オブジェクト指向プログラミング (object-oriented programming)** の特徴の一つである**クラス (class)** は、**オブジェクト (object)** を生成するための設計図にあたるものである。

クラスから作成された実体のことを**インスタンス (instance)** または**オブジェクト (object)** とも呼び、**クラスから実体を作成する**と言う操作のことを**インスタンス化 (instantiation)** と呼ぶ。

**クラスの定義**

それでは、家の設計図を表す `House` と言うクラスを定義してみる。

`House` クラスには、インスタンス化されたあとに、各インスタンス、すなわち誰か特定の人の家ごとに異なる値を持つ、`name_plate` と言う変数を持たせてみる。

`name_plate` と言う変数には、個別の家の表札に表示するための文字列が与えられるが、クラスを定義する際には「`name_plate` と言う変数を持つことができる」ようにしておくだけでよく、**実際にその変数に何か具体的な値を与える必要はない。**

クラスは、**設計図**であればよく、具体的な値を持たせなくてもよいためである。

具体的な値は、個別の家を作成するとき、すなわちインスタンス化の際に与え、各インスタンスが `name_plate` と言う値に自分の家の表札の名前を保持するようにする。

このような、インスタンスに属している変数を**属性 (attribute)** と呼ぶ。同様に、インスタンスから呼び出すことができる関数のことを**メソッド (method)** と呼ぶ。

クラスは、以下のような構文を使って定義する。

具体的には、以下のようになる。
```
class クラス名: ヘッダー
    def メソッド名(引数1, ...): ヘッダー
        ブロック処理
```

```python
# クラスの定義
class House:
    # __init__() メソッドの定義
    def __init__(self, name):
        self.name_plate = name
```

ここで、`__init__()` と言う名前のメソッドが `House` クラスの中に定義されている。
メソッドの名前は自由に名付けることができるが、いくつか特別な意味を持つメソッド名が予め決められている。
`__init__()` はそういったメソッドの一つで、**インスタンス化する際に自動的に呼ばれるメソッド**である。

`House` クラスの `__init__()` は、`name` と言う引数をとり、これを `self.name_plate` と言う変数に代入している。
この `self` と言うのは、クラスがインスタンス化されたあと、作成されたインスタンス自身を参照するのに用いられる。
これを使って、`self.name_plate = name` とすることで、作成された個別のインスタンスに属する変数 `self.name_plate` へ、引数に渡された `name` が持つ値を代入することができる。
`self` が指すものは、各インスタンスから見た「自分自身」なので、各インスタンスごとに異なる。
これによって、`self.name_plate` は各インスタンスに紐付いた別々の値を持つものとなる。

メソッドは、インスタンスから呼び出されるとき自動的に第一引数にそのインスタンスへの参照を渡す。
そのため、メソッドの第一引数は `self` とし、渡されてくる自分自身への参照を受け取るようにしている。
ただし、呼び出す際には**そのインスタンスを引数に指定する必要はない。**
以下に具体例を示し、再度このことを確認する。

それでは、上で定義した `House` クラスのインスタンスを作成してみる。
クラスのインスタンス化には、クラス名のあとに `()` を追加して、クラスを呼び出すような記法を使う。
この際、関数を呼び出すときと同様にして、`()` に引数を渡すことができる。
その引数は、`__init__()` メソッドに渡される。

```python
my_house = House('Chainer')
```

`House` と言うクラスの `__init__()` メソッドに、`'Chainer'` と言う文字列を渡している。
`my_house` が、`House` クラスから作成されたインスタンスである。
ここで、クラス定義では `__init__()` メソッドは `self` と `name` と言う 2 つの引数をとっていたが、呼び出しの際には `'Chainer'` と言う一つの引数しか与えていない。
この `'Chainer'` と言う文字列は、1 つ目の引数であるにも関わらず、`__init__()` メソッドの定義では 2 つ目の引数であった `name` に渡される。
前述のように、**メソッドは、インスタンスから呼び出されるとき自動的に第一引数にそのインスタンスへの参照を渡す**ためである。
この自動的に渡される自身への参照は、呼び出しの際には明示的に指定しない。
また、かならず 1 つ目の引数に自動的に渡されるため、呼び出し時に明示的に与えられた引数は 2 つ目以降の引数に渡されたものとして取り扱われる。

それでは次に、このクラスに `hello()` と言うメソッドを追加し、呼び出すと誰の家であるかを表示すると言う機能を実装してみる。

```python
# クラスの定義
class House:

    # __init__() の定義
    def __init__(self, name):
        self.name_plate = name

    # メソッドの定義
    def hello(self):
        print('{}の家である。'.format(self.name_plate))
```

それでは、2 つのインスタンスを作成して、それぞれから `hello()` メソッドを呼び出してみる。

```python
sato = House('佐藤')
suzuki = House('スズキ')

sato.hello()   # 実行の際には hello() の引数にある self は無視
suzuki.hello() # 実行の際には hello() の引数にある self は無視
```

    佐藤の家である。
    スズキの家である。

`sato` と言うインスタンスの `name_plate` 属性には、`'佐藤'` と言う文字列が格納されている。
`suzuki` と言うインスタンスの `name_plate` 属性には、`'スズキ'` と言う文字列が格納されている。
それぞれのインスタンスから呼び出された `hello()` メソッドは、`self.name_plate` に格納された別々の値を `print()` を用いて表示している。

このように、同じ機能を持つが、インスタンスによって保持するデータが異なったり、一部の動作が異なったりするようなケースを扱うのにクラスを利用する。
python の `int` 型、`float` 型、`str` 型…などは、実際には `int` クラス、`float` クラス、`str` クラスであり、それらの中では個別の変数（インスタンス）がどのような値になるかには関係なく、同じ型であれば共通して持っている機能が定義されている。
`5` や `0.3` や `'Chainer'` などは、それぞれ `int` クラスのインスタンス、`float` クラスのインスタンス、`str` クラスのインスタンスである。

以上から、クラスを定義すると言うのは、**新しい型を作る**と言うことでもあると分かる。

---
**継承**

あるクラスを定義したら、その一部の機能を変更したり、新しい機能を付け足したりしたくなることがある。
これを実現する機能が**継承 (inheritance)** である。
例えば、`Link` と言うクラスを定義し、そのクラスを継承した `Chain` と言う新しいクラスを作ってみる。
まず、`Link` クラスを定義する。

```python
class Link:

    def __init__(self):
        self.a = 1
        self.b = 2
```

この `Link` と言うクラスは、インスタンス化を行う際には 1 つも引数をとらないが、属性として `a` と `b` の 2 つの変数を保持し、それぞれには `__init__()` メソッドで 1 と 2 と言う値が代入される。
このクラスのインスタンスを作成してみる。

```python
l = Link()

l.a
```

    1

```python
l.b
```

    2

`l` と言う `Link` クラスのインスタンスが持つ 2 つの属性を表示している。
インスタンス化を行った際に `__init__()` メソッドの中で代入していた値が、表示されている。

次に、このクラスを**継承**する、`Chain` と言うクラスを定義してみる。
継承を行う場合は、クラス定義の際にクラス名に続けて `()` を書き、その中にベースにしたいクラスの名前を書く。
`()` の中に書かれたクラスのことを、定義されるクラスの**親クラス**と言う。
それに対し、`()` の中に書かれたクラスからみると、定義されるクラスは**子クラス**と呼ばれる。
親から子へ機能が受け継がれるためである。

```python
class Chain(Link):

    def sum(self):
        return self.a + self.b
```

`Chain` クラスは `__init__()` メソッドの定義を持たない。

`__init__()` メソッドが定義されていない場合、親クラスの `__init__()`  メソッドが自動的に呼び出される。
そのため、`Chain` クラスでは一見何も属性を定義していないように見えるが、インスタンス化を行うと親クラスである `Link` の `__init__()`  メソッドが自動的に実行され、`a`、`b` と言う属性が定義される。
以下のコードで確認してみる。

```python
# Chain クラスをインスタンス化
c = Chain()

c.a
```

    1

```python
c.b
```

    2

`Chain` クラスの `sum()` メソッドでは、この親クラスの `__init__()`  メソッドで定義されている 2 つの属性を足し合わせて返している。
今作成したインスタンスから、この `sum()` メソッドを呼び出してみる。

```python
# sum メソッドを実行
c.sum()
```

    3

このように、**親クラスを継承し、親クラスに無かった新しい機能が追加された、新しいクラスを定義することができる。**

それでは、この `Chain` と言うクラスにも `__init__()`  メソッドを定義して、新しい属性 `c` を定義し、`sum()` メソッドでは親クラスの `a`、`b` と言う属性とこの新たな `c` と言う属性の 3 つの和を返すように変更してみる。

```python
class Chain(Link):

    def __init__(self):
        self.c = 5  # self.c を新たに追加

    def sum(self):
        return self.a + self.b + self.c

# インスタンス化
C = Chain()
```

```python
# error
# C.sum()
```

エラーが出た。

**エラーメッセージを読む。**

> AttributeError: 'Chain' object has no attribute 'a'

`'Chain'` と言うオブジェクトは、`'a'` と言う名前の属性を持っていない、と言われている。
`a` と言う属性は、`Chain` の親クラスである `Link` の `__init__()`  メソッドで定義されている。
そのため、`Chain` クラスをインスタンス化する際に、親クラスである `Link` の `__init__()`  メソッドが呼ばれているのであれば、このエラーは起こらないはずである。
なぜエラーとなってしまったのだろうか。

それは、`Chain` クラスにも `__init__()` メソッドを定義したため、親クラスである `Link` の `__init__()`  メソッドが上書きされてしまい、実行されなかったためである。
しかし、親クラスの `__init__()`  メソッドを明示的に呼ぶことで、これは解決できる。

それには、`super()` と言う組み込み関数を用いる。
これを用いると、子クラスから親クラスを参照することができる。

```python
class Chain(Link):

    def __init__(self):
        # 親クラスの `__init__()` メソッドを呼び出す
        super().__init__()

        # self.c を新たに追加
        self.c = 5

    def sum(self):
        return self.a + self.b + self.c

# インスタンス化
c = Chain()
```

```python
c.sum()
```

    8

今回はエラーが起きない。
`Link` クラスの `__init__()`  メソッドの冒頭で、まず親クラスの `__init__()`  メソッドを実行し、`a`、`b` と言う属性を定義しているためである。

あるクラスを継承して作られたクラスを、さらに継承して別のクラスを定義することもできる。

```python
class MyNetwork(Chain):

    def mul(self):
        return self.a * self.b * self.c
```

`MyNetwork` クラスは、`Link` クラスを継承した `Chain` クラスをさらに継承したクラスで、`a`、`b`、`c` と言う 3 つの属性を掛け合わせた結果を返す `mul()` と言うメソッドを持つ。

このクラスのインスタンスを作成し、`mul()` を実行してみる。

```python
net = MyNetwork()

net.mul()
```

    10

$1 \times 2 \times 5 = 10$ が返ってきた。

---

# 関数型 python の考え方

python は関数型言語ではないが、関数型で書くための道具は揃っている。

* reduce, map, filter を使う。
* iterator, generator を使う。
https://docs.python.org/ja/3/howto/functional.html

これらを活用することで、わかりやすくバグの少ないプログラミングを目指す。

質問 $\quad$ accumulator に当たるものはあるか。

python accumulator で検索

# reduce

```python
# reduce は functools にある
from functools import reduce
array = [20, 1, 2, 3, 4, 5]

print(reduce(lambda x, y: x+y, array))

from operator import add
from operator import sub
from operator import mul

print(reduce(add, array)) # 35
print(reduce(sub, array)) # 5
print(reduce(mul, array)) # 2400
```

    35
    35
    5
    2400

```python
# map とかは built-in 関数としてある
numbers = [-8, 5, -1, 4, -9, 2]

print(list(map(abs, numbers)))
```

    [8, 5, 1, 4, 9, 2]

```python
# map の結果は map object
numbers = [-8, 5, -1, 4, -9, 2]

print(map(abs, numbers))
```

    <map object at 0x7f76d47a62b0>

```python
# filter
numbers = [-8, 5, -1, 4, -9, 2]

print(list(filter(lambda x: abs(x) > 3, numbers))) #[-8, 5, 4, -9]
```

    [-8, 5, 4, -9]

```python
# 実験 ndarrayにreduceが使えるか
from functools import reduce
import numpy as np
print(reduce(lambda x,y: x+y, np.arange(10)))
```

    45

```python
# np.arange は ndarray で range は range クラス
import numpy as np
print(type(np.arange(10)))
print(type(range(10)))
```

    <class 'numpy.ndarray'>
    <class 'range'>

```python
# ndarray について実験
import numpy as np
a = np.array([1,2,3])
a02 = np.arange(1,4)
print(type(a))
print(a == a02) # True ではなく [True True True] になる
b = np.array([[1,2,3], [4,5,6]])
print(a)
print(b)
print(a.T)
print(b.T)
print(a.data)
print(a.dtype)
```

    <class 'numpy.ndarray'>
    [ True  True  True]
    [1 2 3]
    [[1 2 3]
     [4 5 6]]
    [1 2 3]
    [[1 4]
     [2 5]
     [3 6]]
    <memory at 0x7f1bb69bef48>
    int64

# flatten

```python
# flatten
import numpy as np
b = np.array([[1,2,3], [4,5,6]])
print(b.flatten())
print(b.flat[3])

from functools import reduce
print(reduce(lambda a, b: a + b, [[1,2,3],[4,5,6],[7,8,9]]))
print(reduce(lambda a, b: a + b, np.array([[1,2,3], [4,5,6]]))) # flatten にならない
print(reduce(lambda a, b: a + b, np.array([[1,2,3],[4,5,6],[7,8,9]]))) # flatten にならない

```

    [1 2 3 4 5 6]
    4
    [1, 2, 3, 4, 5, 6, 7, 8, 9]
    [5 7 9]
    [12 15 18]

```python
# flatten
import numpy as np

print(np.concatenate([np.array([[1,2,3], [4,5,6]])[0],  np.array([[1,2,3], [4,5,6]])[1]] ))
print(reduce(lambda a, b: np.concatenate([a,b]), np.array([[1,2,3], [4,5,6]])))
print(reduce(lambda a, b: np.concatenate([a,b]), np.array([[1,2,3], [4,5,6],[7,8,9]])))

```

    [1 2 3 4 5 6]
    [1 2 3 4 5 6]
    [1 2 3 4 5 6 7 8 9]
    [1 2 3 4 5 6 7 8 9]

```python
# real, imag
c = np.array([1.-2.6j, 2.1+3.J, 4.-3.2j])
print(c.real)
print(c.imag)
```

    [1.  2.1 4. ]
    [-2.6  3.  -3.2]

# iterator

```python
# iter はなんでも iterator にしてしまう
# iterator はちょっと扱いがむずかしい
it = iter([1,2,3])
print(it)
print(it.__next__())
print(next(it))
# print(it.next()) これはエラーになる
print(next(it))
# print(next(it)) StopIteration エラー
```

    <list_iterator object at 0x7f1d70999650>
    1
    2
    3

# genertor と list comprehension

```python
line_list = ['  line 1\n', 'line 2  \n']

# Generator expression -- returns iterator
stripped_iter = (line.strip() for line in line_list)
print(stripped_iter)
# List comprehension -- returns list
stripped_list = [line.strip() for line in line_list]
print(stripped_list)
```

    <generator object <genexpr> at 0x7f655cb0a0a0>
    ['line 1', 'line 2']

```python
# map を使ってみる

line_list = ['  line 1\n', 'line 2  \n']

print(list(map(lambda line: line.strip(),  line_list)))
```

    ['line 1', 'line 2']

```python
def generate_ints(N):
   for i in range(N):
       yield i

gen = generate_ints(3)
print(gen)
print(next(gen))
print(next(gen))
print(next(gen))
# print(next(gen)) # StopIteration

```

    <generator object generate_ints at 0x7f6554ceea40>
    0
    1
    2

```python
# A recursive generator that generates Tree leaves in in-order.
def inorder(t):
    if t:
        for x in inorder(t.left):
            yield x

        yield t.label

        for x in inorder(t.right):
            yield x
```

```python
# val = (yield i)

def counter(maximum):
    i = 0
    while i < maximum:
        val = (yield i)
        # If value provided, change counter
        if val is not None:
            i = val
        else:
            i += 1

it = counter(10)
print(next(it))
print(next(it))
print(it.send(8))
print(next(it))
# print(next(it))
```

    0
    1
    8
    9

```python
def upper(s):
    return s.upper()

print(list(map(upper, ['sentence', 'fragment'])))
print([upper(s) for s in ['sentence', 'fragment']])

# 関数を作らない場合、map では lambda を使う
# 内包表記ではメソッドのまま使える => 内包表記の方が便利
print(list(map(lambda x: x.upper(),  ['sentence', 'fragment'])))
print([s.upper() for s in ['sentence', 'fragment']])
```

    ['SENTENCE', 'FRAGMENT']
    ['SENTENCE', 'FRAGMENT']
    ['SENTENCE', 'FRAGMENT']
    ['SENTENCE', 'FRAGMENT']

# filter

```python
# filter
def is_even(x):
    return (x % 2) == 0

print(list(filter(is_even, range(10))))

print(list(filter(lambda x: (x % 2) == 0, range(10))))

print(list(x for x in range(10) if is_even(x)))
```

    [0, 2, 4, 6, 8]
    [0, 2, 4, 6, 8]
    [0, 2, 4, 6, 8]

# enumerate

```python
# enumerate
for item in enumerate(['subject', 'verb', 'object']):
    print(item)

print(list(enumerate(['subject', 'verb', 'object'])))
print(list(map(lambda x: (x[0], x[1]), enumerate(['subject', 'verb', 'object']))))
```

    (0, 'subject')
    (1, 'verb')
    (2, 'object')
    [(0, 'subject'), (1, 'verb'), (2, 'object')]
    [(0, 'subject'), (1, 'verb'), (2, 'object')]

```python
# enumerate の使い方
f = ['This is an apple.', '', 'That is a pen.','', 'END']
for i, line in enumerate(f):
    if line.strip() == '':
        print('Blank line at line #%i' % i)

print(list(map(lambda x: "Blank line at line #"+ str(x[0]), (filter(lambda x: x[1] == '', enumerate(f))))))
```

    Blank line at line #1
    Blank line at line #3
    ['Blank line at line #1', 'Blank line at line #3']

# sorted

```python
import random
# Generate 8 random numbers between [0, 10000)
rand_list = random.sample(range(10000), 8)
print(rand_list)
print(sorted(rand_list))
print(sorted(rand_list, reverse=True))

```

    [8472, 1787, 5965, 2983, 4798, 9739, 6306, 7455]
    [1787, 2983, 4798, 5965, 6306, 7455, 8472, 9739]
    [9739, 8472, 7455, 6306, 5965, 4798, 2983, 1787]

```python
# any , all
print(any([0, 1, 0]))
print(any([0, 0, 0]))
print(any([1, 1, 1]))
print(all([0, 1, 0]))
print(all([0, 0, 0]))
print(all([1, 1, 1]))

```

    True
    False
    True
    False
    False
    True

```python
list(zip(['a', 'b', 'c'], (1, 2, 3)))
```

    [('a', 1), ('b', 2), ('c', 3)]

# itertools

# いまここ

```python
# itertools.islice(iterable, stop)  --> islice object
# itertools.count(start, step=1) --> count object

import itertools

print(list(itertools.islice(itertools.count(), 10)))
print(list(itertools.islice(itertools.count(10), 10)))
print(list(itertools.islice(itertools.count(10, 5), 10)))

```

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
    [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]

```python
import itertools
print(list(itertools.islice(itertools.cycle([1, 2, 3]), 10)))

print(list(itertools.repeat('abc', 5)))

print(list(itertools.repeat([1,2,3,], 5)))

```

    [1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
    ['abc', 'abc', 'abc', 'abc', 'abc']
    [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]

```python
import itertools
print(list(itertools.islice(range(10), 8)))
print(list(itertools.islice(range(10), 2, 8)))
print(list(itertools.islice(range(10), 2, 8, 2)))
```

    [0, 1, 2, 3, 4, 5, 6, 7]
    [2, 3, 4, 5, 6, 7]
    [2, 4, 6]

```python
itertools.tee?
```

```python
# itertools.tee(iterable, n=2) --> tuple of n iindependent iterators

itertools.tee( itertools.count() )

```

    (<itertools._tee at 0x7fc85db05870>, <itertools._tee at 0x7fc85db05820>)

```python
import os
list(itertools.starmap(os.path.join,
                  [('/bin', 'python'), ('/usr', 'bin', 'java'),
                   ('/usr', 'bin', 'perl'), ('/usr', 'bin', 'ruby')]))
```

    ['/bin/python', '/usr/bin/java', '/usr/bin/perl', '/usr/bin/ruby']

```python
print(list(itertools.islice(itertools.filterfalse(lambda x: x%2 != 0, itertools.count()),10)))
```

    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

```python
print(list(itertools.takewhile(lambda x: x < 10, itertools.count())))
```

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

```python
print(list(itertools.takewhile(lambda x: x%2==0, itertools.count())))
```

    [0]

```python
print(list(itertools.islice(itertools.dropwhile(lambda x: x < 10, itertools.count()),10)))
```

    [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

```python
print(list(itertools.islice(itertools.dropwhile(lambda x: x%2==0, itertools.count()), 10)))
```

    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

```python
list(itertools.compress([1, 2, 3, 4, 5], [True, True, False, False, True]))
```

    [1, 2, 5]

```python
print(list(itertools.combinations([1, 2, 3, 4, 5], 2)))
print(list(itertools.combinations([1, 2, 3, 4, 5], 3)))
```

    [(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
    [(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2, 4, 5), (3, 4, 5)]

```python
print(list(itertools.permutations('aba', 3)))
```

    [('a', 'b', 'a'), ('a', 'a', 'b'), ('b', 'a', 'a'), ('b', 'a', 'a'), ('a', 'a', 'b'), ('a', 'b', 'a')]

```python
print(list(itertools.combinations_with_replacement([1, 2, 3, 4, 5], 2)))
```

    [(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 3), (3, 4), (3, 5), (4, 4), (4, 5), (5, 5)]

```python
# groupby
import itertools

city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'),
             ('Anchorage', 'AK'), ('Nome', 'AK'), ('Flagstaff', 'AZ'),
             ('Phoenix', 'AZ'), ('Tucson', 'AZ')]

def get_state(city_state):
    return city_state[1]

list(map (lambda x: (x[0], list(x[1])), itertools.groupby(city_list, get_state)))
```

    [('AL', [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL')]),
     ('AK', [('Anchorage', 'AK'), ('Nome', 'AK')]),
     ('AZ', [('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')])]

# functools.partial

```python
# functoolsfunctools.partial
import functools

def sayhello(message="hello", to="ryo"):
    print("{1}さん、{0}".format(message, to))

sayhello()

konnnichiwa = functools.partial(sayhello, "こんにちわ")

konnnichiwa("nishizawa")
konnnichiwa("takahashi")

```

    ryoさん、hello
    nishizawaさん、こんにちわ
    takahashiさん、こんにちわ

```python
# reduce は haskell の foldr と考えてよいか
import operator, functools
functools.reduce(operator.concat, ['A', 'BB', 'C'])

functools.reduce(operator.concat, ['A', 'BB', 'C'], 'END')
```

    'ENDABBC'

```python
import operator, itertools
print(list(itertools.accumulate([1, 2, 3, 4, 5])))
print(list(itertools.accumulate([1, 2, 3, 4, 5], operator.mul)))
```

    [1, 3, 6, 10, 15]
    [1, 2, 6, 24, 120]


# Setup

## Instructions
1. Work on a copy of this notebook: _File_ > _Save a copy in Drive_ (you will need a Google account).
2. (Optional) If you would like to do the deep learning component of this tutorial, turn on the GPU with Edit->Notebook settings->Hardware accelerator->GPU
3. Execute the following cell (click on it and press Ctrl+Enter) to install Julia, IJulia and other packages (if needed, update `JULIA_VERSION` and the other parameters). This takes a couple of minutes.
4. Continue to the next section.

_Notes_:
* If your Colab Runtime gets reset (e.g., due to inactivity), repeat steps 3, 4.
* After installation, if you want to change the Julia version or activate/deactivate the GPU, you will need to reset the Runtime: _Runtime_ > _Delete and disconnect runtime_ and repeat steps 2-4.

**Run the following code to install Julia**

```python
%%shell
set -e

#---------------------------------------------------#
JULIA_VERSION="1.7.2"
JULIA_PACKAGES="PyCall SymbolicRegression"
JULIA_NUM_THREADS=4
#---------------------------------------------------#

if [ -z `which julia` ]; then
  # Install Julia
  JULIA_VER=`cut -d '.' -f -2 <<< "$JULIA_VERSION"`
  echo "Installing Julia $JULIA_VERSION on the current Colab Runtime..."
  BASE_URL="https://julialang-s3.julialang.org/bin/linux/x64"
  URL="$BASE_URL/$JULIA_VER/julia-$JULIA_VERSION-linux-x86_64.tar.gz"
  wget -nv $URL -O /tmp/julia.tar.gz # -nv means "not verbose"
  tar -x -f /tmp/julia.tar.gz -C /usr/local --strip-components 1
  rm /tmp/julia.tar.gz

  for PKG in `echo $JULIA_PACKAGES`; do
    echo "Installing Julia package $PKG..."
    julia -e 'using Pkg; pkg"add '$PKG'; precompile;"'
  done

  julia -e 'println("Success")'
fi
```

Install PySR and PyTorch-Lightning:

```python
%pip install -Uq pysr pytorch_lightning
```

The following step is not normally required, but colab's printing is non-standard and we need to manually set it up PyJulia:

```python
from julia import Julia

julia = Julia(compiled_modules=False)
from julia import Main
from julia.tools import redirect_output_streams

redirect_output_streams()

```

Let's install the backend of PySR, and all required libraries. We will also precompile them so they are faster at startup.

**(This may take some time)**

```python
import pysr

pysr.install()

```

```python
import sympy
import numpy as np
from matplotlib import pyplot as plt
from pysr import PySRRegressor
import torch
from torch import nn, optim
from torch.nn import functional as F
from torch.utils.data import DataLoader, TensorDataset
import pytorch_lightning as pl
from sklearn.model_selection import train_test_split

```

# Simple PySR example:

First, let's learn a simple function

$$2.5382 \cos(x3) + x0^2 - 2$$

```python
# Dataset
np.random.seed(0)
X = 2 * np.random.randn(100, 5)
y = 2.5382 * np.cos(X[:, 3]) + X[:, 0] ** 2 - 2

```

By default, we will set up 30 populations of expressions (which evolve independently except for migrations), use 4 threads, and use `"best"` for our model selection strategy:

```python
default_pysr_params = dict(
    populations=30,
    procs=4,
    model_selection="best",
)

```

PySR can run for arbitrarily long, and continue to find more and more accurate expressions. You can set the total number of cycles of evolution with `niterations`, although there are also a [few more ways](https://github.com/MilesCranmer/PySR/pull/134) to stop execution.

**This first execution will take a bit longer to startup, as the library is JIT-compiled. The next execution will be much faster.**

```python
# Learn equations
model = PySRRegressor(
    niterations=30,
    binary_operators=["plus", "mult"],
    unary_operators=["cos", "exp", "sin"],
    **default_pysr_params
)

model.fit(X, y)

```

We can print the model, which will print out all the discovered expressions:

```python
model

```

We can also view the SymPy format of the best expression:

```python
model.sympy()

```

We can also view the SymPy of any other expression in the list, using the index of it in `model.equations_`.

```python
model.sympy(2)

```

## Output

`model.equations_` is a Pandas DataFrame. We can export the results in various ways:

```python
model.latex()

```

These is also `model.sympy(), model.jax(), model.pytorch()`. All of these can take an index as input, to get the result for an arbitrary equation in the list.

We can also use `model.predict` for arbitrary equations, with the default equation being the one chosen by `model_selection`:

```python
ypredict = model.predict(X)
ypredict_simpler = model.predict(X, 2)

print("Default selection MSE:", np.power(ypredict - y, 2).mean())
print("Manual selection MSE for index 2:", np.power(ypredict_simpler - y, 2).mean())

```

# Custom operators

A full list of operators is given here: https://astroautomata.com/PySR/#/operators,
but we can also use any binary or unary operator in `julia`, or define our own as arbitrary functions.

Say that we want a command to do quartic powers:

$$ y = x_0^4 - 2 $$

```python
y = X[:, 0] ** 4 - 2

```

We can do this by passing a string in Julia syntax.

We also define the operator in sympy, with `extra_sympy_mappings`, to enable its use in `predict`, and other export functions.

```python
model = PySRRegressor(
    niterations=5,
    populations=40,
    binary_operators=["plus", "mult"],
    unary_operators=["cos", "exp", "sin", "quart(x) = x^4"],
    extra_sympy_mappings={"quart": lambda x: x**4},
)
model.fit(X, y)

```

```python
model.sympy()

```

Since `quart` is arguably more complex than the other operators, you can also give it a different complexity, using, e.g., `complexity_of_operators={"quart": 2}` to give it a complexity of 2 (instead of the default 2). You can also define custom complexities for variables and constants (`complexity_of_variables` and `complexity_of_constants`, respectively - both take a single number).

One can also add a binary operator, with, e.g., `"myoperator(x, y) = x^2 * y"`. All Julia operators that work on scalar 32-bit floating point values are available.

Make sure that any operator you add is valid over the real line. So, e.g., you will need to define `"mysqrt(x) = sqrt(abs(x))"` to enable it for negative numbers,
or, simply have it return a very large number for bad inputs (to prevent negative input in a soft way):
`"mysqrt(x::T) where {T} = (x >= 0) ? x : T(-1e9)"` (Julia syntax for a template function of input type `T`), which will make `mysqrt(x)` return -10^9 for negative x–hurting the loss of the equation.

## Scoring

Using `model_selection="best"`selects the equation with the max score and prints it. But in practice it is best to look through all the equations manually, select an equation above some MSE threshold, and then use the score to select among that loss threshold.

Here, "score" is defined by:
$$ \text{score} = - \log(\text{loss}_i/\text{loss}_{i-1})/
(\text{complexity}_i - \text{complexity}_{i-1})$$

This scoring is motivated by the common strategy of looking for drops in the loss-complexity curve.

From Schmidt & Lipson (2009) -

# Noise example

Here is an example with noise. Known Gaussian noise with $\sigma$ between 0.1 and 5.0. We record samples of $y$:

$$ \sigma \sim U(0.1, 5.0) $$
$$ \epsilon \sim \mathcal{N}(0, \sigma^2)$$
$$ y = 5\;\cos(3.5 x_0) - 1.3 + \epsilon.$$
We have 5 features, say. The weights change the loss function to be:
$$MSE = \sum [(y - f(x))^2*w],$$

so in this example, we can set:
$$w = 1/\sigma^2.$$

```python
np.random.seed(0)
N = 3000
upper_sigma = 5
X = 2 * np.random.rand(N, 5)
sigma = np.random.rand(N) * (5 - 0.1) + 0.1
eps = sigma * np.random.randn(N)
y = 5 * np.cos(3.5 * X[:, 0]) - 1.3 + eps

```

Let's look at this dataset:

```python
plt.scatter(X[:, 0], y, alpha=0.2)
plt.xlabel("$x_0$")
plt.ylabel("$y$")

```

Define some weights to use:

```python
weights = 1 / sigma[:, None] ** 2

```

```python
weights[:5, 0]

```

Let's run PySR again:

```python
model = PySRRegressor(
    loss="myloss(x, y, w) = w * abs(x - y)",  # Custom loss function with weights.
    niterations=20,
    populations=20,  # Use more populations
    binary_operators=["plus", "mult"],
    unary_operators=["cos"],
)
model.fit(X, y, weights=weights)

```

Let's see if we get similar results to the true equation

```python
model

```

We can also filter all equations up to 2x the most accurate equation, then select the best score from that list:

```python
best_idx = model.equations_.query(
    f"loss < {2 * model.equations_.loss.min()}"
).score.idxmax()
model.sympy(best_idx)

```

We can also use `denoise=True`, which will run the input through a Gaussian process to denoise the dataset, before fitting on it.

Let's look at the fit:

```python
plt.scatter(X[:, 0], y, alpha=0.1)
y_prediction = model.predict(X, index=best_idx)
plt.scatter(X[:, 0], y_prediction)

```

# High-dimensional input: Neural Nets + Symbolic Regression

In this example, let's learn a high-dimensional problem. **This will use the method proposed in our NeurIPS paper: https://arxiv.org/abs/2006.11287.**

Let's consider a time series problem:

$$ z = y^2,\quad y = \frac{1}{100} \sum(y_i),\quad y_i = x_{i0}^2 + 6 \cos(2*x_{i2})$$

Imagine our time series is 100 timesteps. That is very hard for symbolic regression, even if we impose the inductive bias of $$z=f(\sum g(x_i))$$ - it is the square of the number of possible equations!

But, as in our paper, **we can break this problem down into parts with a neural network. Then approximate the neural network with the symbolic regression!**

Then, instead of, say, $(10^9)^2=10^{18}$ equations, we only have to consider $2\times 10^9$ equations.

```python
###### np.random.seed(0)
N = 100000
Nt = 100
X = 6 * np.random.rand(N, Nt, 5) - 3
y_i = X[..., 0] ** 2 + 6 * np.cos(2 * X[..., 2])
y = np.sum(y_i, axis=1) / y_i.shape[1]
z = y**2
X.shape, y.shape

```

## Neural Network definition

So, as described above, let's first use a neural network with the sum inductive bias to solve this problem.

Essentially, we will learn two neural networks:
- `f`
- `g`

each defined as a multi-layer perceptron. We will sum over `g` the same way as in our equation, but we won't define the summed part beforehand.

Then, we will fit `g` and `f` **separately** using symbolic regression.

```python
hidden = 128
total_steps = 50000

def mlp(size_in, size_out, act=nn.ReLU):
    return nn.Sequential(
        nn.Linear(size_in, hidden),
        act(),
        nn.Linear(hidden, hidden),
        act(),
        nn.Linear(hidden, hidden),
        act(),
        nn.Linear(hidden, size_out),
    )

class SumNet(pl.LightningModule):
    def __init__(self):
        super().__init__()

    ########################################################
    # The same inductive bias as above!
        self.g = mlp(5, 1)
        self.f = mlp(1, 1)

    def forward(self, x):
        y_i = self.g(x)[:, :, 0]
        y = torch.sum(y_i, dim=1, keepdim=True) / y_i.shape[1]
        z = self.f(y)
        return z[:, 0]

    ########################################################

    # PyTorch Lightning bookkeeping:
    def training_step(self, batch, batch_idx):
        x, z = batch
        predicted_z = self(x)
        loss = F.mse_loss(predicted_z, z)
        return loss

    def validation_step(self, batch, batch_idx):
        return self.training_step(batch, batch_idx)

    def configure_optimizers(self):
        self.trainer.reset_train_dataloader()

        optimizer = torch.optim.Adam(self.parameters(), lr=self.max_lr)
        scheduler = {
            "scheduler": torch.optim.lr_scheduler.OneCycleLR(
                optimizer,
                max_lr=self.max_lr,
                total_steps=self.total_steps,
                final_div_factor=1e4,
            ),
            "interval": "step",
        }
        return [optimizer], [scheduler]

```

## Data bookkeeping

Put everything into PyTorch and do a train/test split:

```python
Xt = torch.tensor(X).float()
zt = torch.tensor(z).float()
X_train, X_test, z_train, z_test = train_test_split(Xt, zt, random_state=0)
train_set = TensorDataset(X_train, z_train)
train = DataLoader(train_set, batch_size=128, num_workers=2)
test_set = TensorDataset(X_test, z_test)
test = DataLoader(test_set, batch_size=256, num_workers=2)

```

## Train the model with PyTorch Lightning on GPUs:

Start the model:

```python
pl.seed_everything(0)
model = SumNet()
model.total_steps = total_steps
model.max_lr = 1e-2

```

PyTorch Lightning trainer object:

```python
trainer = pl.Trainer(max_steps=total_steps, gpus=1, benchmark=True)

```

Here, we fit the neural network:

```python
trainer.fit(model, train_dataloaders=train, val_dataloaders=test)

```

## Latent vectors of network

Let's get the input and output of the learned `g` function from the network over some random data:

```python
np.random.seed(0)
idx = np.random.randint(0, 10000, size=1000)

X_for_pysr = Xt[idx]
y_i_for_pysr = model.g(X_for_pysr)[:, :, 0]
y_for_pysr = torch.sum(y_i_for_pysr, dim=1) / y_i_for_pysr.shape[1]
z_for_pysr = zt[idx]  # Use true values.

X_for_pysr.shape, y_i_for_pysr.shape

```

## Learning over the network:

Now, let's fit `g` using PySR:

```python
np.random.seed(1)
tmpX = X_for_pysr.detach().numpy().reshape(-1, 5)
tmpy = y_i_for_pysr.detach().numpy().reshape(-1)
idx2 = np.random.randint(0, tmpy.shape[0], size=3000)

model = PySRRegressor(
    niterations=20,
    binary_operators=["plus", "sub", "mult"],
    unary_operators=["cos", "square", "neg"],
)
model.fit(X=tmpX[idx2], y=tmpy[idx2])

```

## Validation

Recall we are searching for $y_i$ above:

$$ z = y^2,\quad y = \frac{1}{100} \sum(y_i),\quad y_i = x_{i0}^2 + 6 \cos(2 x_{i2})$$

```python
model

```

A neural network can easily undo a linear transform, so this is fine: the network for $f$ will learn to undo the linear transform.

Then, we can learn another analytic equation for $z$.

**Now, we can compose these together to get the time series model!**

Think about what we just did: we found an analytical equation for $z$ in terms of $500$ datapoints, under the assumption that $z$ is a function of a sum of another function over an axis:

$$ z = f(\sum_i g(x_i)) $$

And we pulled out analytical copies for $g$ using symbolic regression.

# Other PySR Options

The full list of PySR parameters can be found here: https://astroautomata.com/PySR/#/api


# Basic Image Standards

In this notebooks we are going to explore some of the different standards that are used to store photographs and similar images. Specifically, we will explore the following standards

* [TGA](https://en.wikipedia.org/wiki/Truevision_TGA)
* [PNG](https://www.w3.org/TR/2003/REC-PNG-20031110/)
* [TIFF](https://www.adobe.io/open/standards/TIFF.html)
* [JPEG](https://jpeg.org/jpeg/)

```python
!python -m pip install -U git+https://github.com/chapmanbe/dminteract#egg=dminteract
```

```python
from dminteract.modules.m4c import *
import warnings
warnings.filterwarnings('ignore')
```

We are going to start with a sample photograph of my daughter (used with her permission!).

```python
display(question_banks["qbank1"]["photo1, qbank1"])
```

### Once you have typed your thoughts about the metadata...

Let's look at the metadata that is stored for our simplest ima

```python
print(view_img_metadata("./data/daughter.tga"))

```

```python
display(question_banks["qbank1"]["photo2, qbank1"])
```

### Now let's look at a the TIFF representation

```python
print(view_img_metadata("./data/daughter.tiff"))
```

```python
display(question_banks["qbank1"]["photo3, qbank1"])
```

## Now let's look at PNG

```python
print(view_img_metadata("./data/daughter.png"))

```

### That is a lot more information!

* Now we have lots of information about how the photograph was created
    * Camera make and model
    * Camera settings (e.g. PhotographicSensitivity=200)
    * Note that there are values that obviously need interpretation
        * What in the world does `MeteringMode=3` mean? or `LightSource=9`?
* Notice that there is some sense of file history:
    * DateTimeOriginal=2005:07:08 19:51:47
    * DateTime=2008:12:23 12:50:21
    * modify=2017-02-06T03:47:30+00:00
    * create=2020-03-25T23:34:30+00:00
* Endianness has changed!
* There is now explicit information about how color is represented: `Pixel format: RGB`.
    * Did TGA and TIFF only have one choice

### Now let's look at the original JPEG image

```python
print(view_img_metadata("./data/daughter.jpg"))
```

```python
display(question_banks["qbank1"]["photo4, qbank1"])
```

### What are the respective file sizes?

```python
!ls -ltra data/daughter.*
```

The TIFF and TGA images are uncompressed, so they only differ by the size of the header (metadata). PNG uses a **lossless** compression, so is substantially smaller than the TIF/TGA but larger than the JPEG image which uses **lossy** compression.

### Here is a newer image

```python
print(view_img_metadata("./data/skiing.jpg"))
```

```python
display(question_banks["qbank1"]["photo5, qbank1"])
```

## Exercise

Now let's try to reverse engineer an image standard.

You have been given 3 "images" in a proprietary format and software to render the images. You are are trying to reverse engineer the standard being used to represent the images.

You can "peek" at the raw data in the file on the computer system and see that the files are the following list of numbers for each image respectively:

\begin{eqnarray}
1, 5, 2, 10 , 5 , 3 , 7 , -6 , 4 , 2 , 0 , 21 , 11 , -2 , 17\\
2 , 3 , 2 , 1 , 0 , 5 , -1 , 17 , 11 , -5 , 6 \\
2, 2, 3, 2, 3, 2, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
\end{eqnarray}.

When the "images" are "rendered" to the screen, this is what you see:

\begin{equation}
\begin{array}{ccccc}
(-7,2) & (-16, -1)  & (-8,-5) & (11,1) & (-12,12)
\end{array}
\end{equation}

---------------------

\begin{equation}
\begin{array}{cc}
5 & -1 \\
17 & 11\\
-5 & 6
\end{array}
\end{equation}

------------------
\begin{equation}
\begin{array}{ccc}
(-2,-1) & (-1, 1)  & (2, 6) \\
(10, 19) & (31, 53) & (86,142)
\end{array}
\end{equation}.

------------------

The you know the image data consists of a **header**  followed by **values,** but you don't know where the **header** ends and the **values** begin. The values make up the elements (e.g. pixel--"picture element") of the picture. The header describes the structure and nature of the values. Based on your analysis, answer the following questions about the header and values

* The standard has a fixed header size (that is, the header size is the same regardless of the nature of the values) (T/F) (F)

* The values are stored by rows. T
* Image values can be multidimensional. T
* The rendered values are modified by multiplying baseline value(s) defined in the header. F. They are subtracted.

```python
for q in question_banks["qbank2"].values():
    display(q)
```

### [Move onto the next notebook](./dicom_intro.ipynb)


# Basic API usage
# `torchcontrol`

```python
import sys; sys.path.append(1*'../') # go n dirs back

import torch
from torchdyn.numerics.odeint import odeint
from torchcontrol.systems.classic_control import Pendulum
from torchcontrol.controllers import *

%load_ext autoreload
%autoreload 2
```

```python
# Change device according to your configuration
device = torch.device('cuda:0') if torch.cuda.is_available() else torch.device('cpu') # feel free to change :)
```

## Loading a system module

Here we load a system module and plot some random trajectories. The first example is the pendulum, described by the following equation:

$$\begin{equation}
    \begin{bmatrix} \dot{q}\\ \dot{p}\\ \end{bmatrix} =
    \begin{bmatrix}
    0& 1/m \\
    -k& -\beta/m\\
    \end{bmatrix}
    \begin{bmatrix} q\\ p\\ \end{bmatrix} -
    \begin{bmatrix}
    0\\
    mgl \sin{q}\\
    \end{bmatrix}+
    \begin{bmatrix}
    0\\
    1\\
    \end{bmatrix} u
\end{equation}$$

```python
?? Pendulum
```

    Init signature:  Pendulum(*args, **kwargs)
    Source:
    class Pendulum(ControlledSystemTemplate):
        """
        Inverted pendulum with torsional spring
        """
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.m  = 1.
            self.k  = 0.5
            self.l  = 1
            self.qr = 0
            self.beta  = 0.01
            self.g  = 9.81

        def dynamics(self, t, x):
            self.nfe += 1 # increment number of function evaluations
            u = self._evaluate_controller(t, x)

            # States
            q, p = x[..., :1], x[..., 1:]

            # Differential equations
            dq = p/self.m
            dp = -self.k*(q - self.qr) - self.m*self.g*self.l*sin(q)- self.beta*p/self.m + u
            self.cur_f = torch.cat([dq, dp], -1)
            return self.cur_f
    File:           ~/Desktop/torchcontrol/torchcontrol/systems/classic_control.py
    Type:           type
    Subclasses:

## Initial conditions
Now we can see how the system behaves with no control input in time. Let's declare some initial variables:

```python
from math import pi as π

# Time span
dt = 0.05 # step size
t0, tf = 0, 10 # initial and final time
steps = int((tf - t0)/dt) + 1
t_span = torch.linspace(t0, tf, steps).to(device)

# Initial distribution
x_0 = π/2 # limit of the state distribution (in rads and rads/second)
init_dist = torch.distributions.Uniform(torch.Tensor([-x_0, -x_0]), torch.Tensor([x_0, x_0]))
x0 = init_dist.sample((50,)) # sample 50 trajectories

# Controller: we set it to 0
u = RandConstController()
u.u0 = 0.

# Instantiate the pendulum class
pendulum = Pendulum(u, solver='dopri5')
```

## Propagate the system forward
We have two ways to propagate the system forward:
1. Use `odeint` from `torchdyn` and forward propagate through the dynamics
2. Use the `forward` method of the base `ControlledSystemTemplate` class

```python
# 1. odeint
traj_odeint = odeint(pendulum.dynamics, x0, t_span, solver='dopri5')[1]

# 2. forward
traj = pendulum(x0, t_span)

print(torch.equal(traj_odeint, traj))
```

    True

## Plot trajectories

```python
# Plotting
import matplotlib.pyplot as plt

def plot_pendulum_trajs():
    fig, axs = plt.subplots(1, 2, figsize=(12,4))
    for i in range(len(x0)):
        axs[0].plot(t_span.cpu(), traj[:,i,0].detach().cpu(), 'tab:red', alpha=.3)
        axs[1].plot(t_span.cpu(), traj[:,i,1].detach().cpu(), 'tab:blue', alpha=.3)
    axs[0].set_xlabel(r'Time [s]'); axs[1].set_xlabel(r'Time [s]')
    axs[0].set_ylabel(r'p'); axs[1].set_ylabel(r'q')
    axs[0].set_title(r'Positions'); axs[1].set_title(r'Momenta')

plot_pendulum_trajs()

```

## Accessing internal variables
We can also change the physical properties of the controlled system. For example, let's change the damping coefficient $\beta$:

```python
pendulum.beta = 0.5
traj = pendulum(x0, t_span)
plot_pendulum_trajs()
```

Here we change the pendulum's mass, which will modify its period:

```python
pendulum.beta = 0.01 # restore beta
pendulum.m = 5
traj = pendulum(x0, t_span)
plot_pendulum_trajs()
```