# Python 基礎介紹
## Python is Object-Oriented
Q: 什麼是 object?
A:
<font color = "snake">Object</font> 基本上就是 ++a collection of data,和一些可以對這些 data 作用的 functions++。
幾乎所有會在 python 裡看到的東西都是 object,像是 data structures 如 strings, lists, arrays, dictionaries...,和這些各式各樣的 objects 一起的是可以對它們作用的各種不同的 methods。
> <font color = "snake">method</font>:可以作用在 object 上的 ++functions++。
舉例來說,假設我們有一個 string:
```python
c = "My dog's name is Bingo"
```
其中一個可以對 string object 作用的 method 叫做 <font color = "green">`split()`</font>:
```python
c.split()
>>> ['My', "dog's", 'name', 'is', 'Bingo']
```
> `split()` 會對和它用 `.` 相連的 string `c` 作用,從空格處將這個 string 分割成(在這個例子中)五個分開的 strings,然後 return 一個包含這五個 strings 的 ++list++。
我們也可以在 `split()` 裡加上 argument,例如:
```python
c.split('g')
>>> ['My do', "'s name is Bin", 'o']
```
> 把 `c` 這個 string 從有出現 g 的地方切開
所以從這個例子我們知道 `split()` 是一種 string method,而除了 `split()` 以外,也有很多其他的 string methods。
Object 除了有 methods 以外,還有另個東西叫做 <font color = "snake">instance variables</font>,也就是++和這個 object 存在一起的 data++;如果我們把 ++methods 和 instance variables++ 一起看,它們兩個合稱 <font color = "snake">attributes</font>。
$\rightarrow$ 每一種 object(像是 strings, lists, arrays, dictionaries)都會有自己一個 set 的 attributes,而且這個 set 是 uniquely associated with 這個 object class。
舉例來說: `split()` 是 string 的 attribute,但不是 array 的 attribute。
# list / dictionary / tuple / set
## list
<font color = "snake">list</font>: 一個 python objects 的 collection,用 ++int++ 作為 index,從 0 開始。
- multidimensional lists and tuples
```python
a = [[3, 9], [8, 5], [11, 1]]
print(a[1])
>>> [8, 5]
print(a[1][0])
>>> 8
```
> tuple 的概念也相同,只是 tuple 裡的東西不能改變。
### 建立 list
- 建立空 list
```python
mylist = []
```
- 建立一個具 n elements 的 list,其中 init value 皆為 0
```python
mylist = [0]*5
print(mylist)
>>> [0, 0, 0, 0, 0]
```
- 建立一個具 n elements 的 list,其中除了第一個 init value 為 -1,其餘皆為 0
```python
mylist = [-1] + [0]*4
print(mylist)
>>> [-1, 0, 0, 0, 0]
```
### 刪除 list 某些 index 的 element
如果沒有要 return 需要刪除的 element,我們可以用 `del`,如下面的例子,我們希望刪除 index 分別在 1 和 3 的 `'b'` 和 `'d'`:
```python
my_list = ['a', 'b', 'c', 'd', 'e']
indices_to_remove = [1, 3]
for index in sorted(indices_to_remove, reverse=True):
del my_list[index]
print(my_list)
>>> ['a', 'c', 'e']
```
> 首先我們先將要刪除的 index 利用 `sorted()` 排序,接著再一個一個刪除
>
> $\rightarrow$ 須注意的是 <font color = "red">`reverse` 設成 `True`</font>,因為如果按照 index 小到大的順序刪除,在刪除 index 1 時,後面的 elements 會遞補過來,接下來刪 index 3 時就不是 `'d'` 而是刪到 `'e'` 了。
## dictionary
### 基本性質
<font color = "snake">dictonary</font>: 一個 python objects 的 collection,但是 index(也就是 keys) 可以是 strings / numbers。
$\rightarrow$ ++key++ 不見得要 int,甚至可以用 tuples,++幾乎任何 python object 都可以,只要這個 object 是 immutable (不會改變的),所以例如 list 就不能作為 key++。
例子:
```python
room = {"Emma":309, "Jake":582, "Olivia":764}
```
> 每個 dictionary 的 entry 用 `,` 分隔,每個 entry 包含一個 <font color = "snake">key</font> 和一個 <font color = "snake">value</font>,兩者用 `:` 分開。
>
> $\rightarrow$ access dictionary entry 的方式和去 access list 類似,只是 list 是用 index,dictionary 則是用 key。
混合不同形式的 key 的例子:
```python=
weird = {"tank":52, 846:"horse", "bones":[23, 'fox', 'grass'], "phase":'I am here'}
print(weird[846])
>>> "horse"
print(weird["bones"])
>>> [23, 'fox', 'grass']
```
> 在 access dictionary 的 element 時,如果 key 是 int 就不需要 quotation,例如第三行我們用 `weird[846]` 而非 `weird["846"]`
>
> 另一方面,如果 key 是 string 則需要 quotation,不管用 single quote `'` 或是 double quote `"` 都可以,這兩個是通用的(雖然習慣上會用 `"`)
>
> $\rightarrow$ 即使我們在定義 dictionary entries 時,key 是用 double quote 包住,在 access 時打 single quote 也行,例如第六行的 `weird["bones"]` 改 `weird['bones']` 也可以得到一樣的結果。
### 建立一個 dictionary
建立一個空的 dictionary:
```python
d = {}
```
有了空的 dictionary `d` 以後,我們可以加入一些 entries:
```python=
d["last name"] = "Alberts"
d["first name"] = "Marie"
d["birthday"] = "January 27"
print(d)
>>> {'last name': 'Alberts', 'first name': 'Marie', 'birthday': 'January 27'}
```
> 每一個 `d[]` 裡的東西,就好像我們去 access list element 時裡面是 index,只是對 dictionary 來說,我們的 index 就是 key,如第一行的 `"last name"` 是 key,後面的 `"Alberts"` 是這個 key 對應的 value。
### 利用 dictionary 計數
假設我們需要計算一個 list 裡的每個單字出現的次數,我們可以透過建立一個 dictionary,來存放每個單字(key)以及對應的出現次數(value)。
例子:
```python=
word_list = ['apple', 'banana', 'orange', 'apple', 'banana', 'apple']
word_counts = {}
for word in word_list: # 對於每個在 word_list 裡的 element
if word in word_counts: # 我們都去檢查它是否已經存在於 word_counts 這個 dictionary 中
word_counts[word] += 1 # 如果有,則增加它的 value(即出現次數)
else: # 如果 dictionary 中還未加入這個 word
word_counts[word] = 1 # 把這個 word 加入 dictionary 中,並令它的 value(即出現次數)為 1
print(word_counts)
>>> {'apple': 3, 'banana': 2, 'orange': 1}
```
> `word_counts[word]` 代表的是在 `word_counts` 這個 dictionary 中, `word` 這個 key 對應到的 value。
> $\rightarrow$ 當使用 `word_counts[word]` 時,python 就會在 `word_counts` 這個 dictionary 中尋找 `word` 這個 key,然後再 return 它的 value。
### 利用 max 找出 dictionary 中最大的 value 及對應的 key
```python
my_dict = {'apple': 10, 'banana': 20, 'orange': 15}
max_key = max(my_dict, key=my_dict.get)
print("Key with the greatest value:", max_key)
print("Value of the key:", my_dict[max_key])
>>> Key with the greatest value: banana
>>> Value of the key: 20
```
> `max()` 的 parameter `key` 被設成一個 function `my_dict.get`,`my_dict.get` 會去取得 `my_dict` 這個 dictionary 中每個 key 對應的 value,藉由這個值,`max()` 會去比較每個 key 的 value,然後 return value 最大的 ++key++。
>
> $\rightarrow$ 雖然說 `max()` 會 return 一個 iterable 中最大的「值」,但不代表我們在此 return 並 assign 給 `max_key` 的,會是某個 key 的 value(也就是 `max_key` 為 `banana` 而非它的 value `20`)
>> 因為在此使用 `max()` 時,它針對的 iterable 是一個 dictionary `my_dict`,而在 iterate through 一個 dictionary 時,預設是 iterate over keys。
>> 因此,當我們在將 `key` 設成 `key=my_dict.get` 時,也就代表了每個 dictionary 的 key 都已經被賦予了它的 value 作為比較的基準,所以最後 `max()` return 的「最大值」仍是一個 key。
## tuple
<font color = "snake">tuples</font>: 不能改變的 list,用 ``()`` 表示
## set
set 的長相:
```python
this_is_a_set = {1, 'apple', (2, 3), 4.5}
```
$\rightarrow$ set 由大括號 `{}` 表示
$\rightarrow$ 裡面的 element 可以是不同的 type,如此例中四個 element 的 type 依序為:int, str, tuple, float
- Note: 雖然 set 是由大括號 `{}` 表示,但是在建立空 set 時不能如建立空 list 一樣 `new_empty_list = []` 令 `new_empty_set = {}`
> $\Rightarrow$ 因為會被認為是一個空的 dictionary
因此建立一個空 set 需用: `new_empty_set = set()`
### set 特性:
- <font color = "green">unordered</font>
> $\rightarrow$ 因此無法用 index 去 access set 裡面的 element
>> 例如對 list 可以: `target = mylist[0]`
>>
> $\rightarrow$ 也無法用 slicing
>> 例如對 list 可以: `newlist = oldlist[3:6]`
- set 中的 <font color = "green">elements unique</font>
> $\rightarrow$ 所以重複將相同的 element 加入 set 的動作會被 ignore
>> 例如重複將 `"apple"` 加入 set 兩次,最後 set 中只會有一個 `"apple"`:
```python
myset = set()
myset.add("apple")
print(myset)
myset.add("apple")
print(myset)
>>> {'apple'}
>>> {'apple'}
```
### set 用途
- set 經常用來檢驗++是否存在 duplicate++,例子如下:
```python
def has_duplicate(nums: list[int]):
seen_numbers_set = set()
for num in numbers_list:
print("\ncurrent number examined is:", num)
if num in seen_numbers_set:
print("Exists duplicate")
return True
seen_numbers_set.add(num)
print("Current seen numbers set is:", seen_numbers_set)
return False
numbers_list = [1, 4, 5, 6, 2, 3, 1]
ans = has_duplicate(numbers_list)
print("Duplicate in this list?", ans)
```
output:
<img src= "https://hackmd.io/_uploads/B1oL1j_W0.jpg" alt=output width="70%">
# 一些常用的 functions
## 獲得 input
獲得 input 的兩種方式,一個是用 `input` 讓 user 輸入,另一種是用 `loadtxt` 讀檔案(像是 txt 檔、csv 檔)進來。
### input
Python 有一個 function 叫作 `input`,可以從 user 那邊拿到一個 input,然後 assign 這個 input 一個變數名稱。例如:

當我們執行到這行 `input` function 時,螢幕上就會顯示 input 括弧內的 string argument:

然後等待 user 輸入 input,而我們得到的 input 會被 assign 到 `distance` 這個變數。假設 user 輸入 `450`,那麼:

$\rightarrow$ 如果我們想把得到的 input 變成小數,只要加上 `float()` 就行:
```python
distance = float(input("Input trip distance(miles): "))
```
### loadtxt
#### 從 txt 檔讀 input
假設我們有一個檔案叫做 `mydata.txt`,裡面可能有一些實驗資料和相關資訊長這樣:
```=
Data for experiment
Date: 16-Aug-2016
Data taken by L and J
data point time(sec) height(mm) uncertainty(mm)
0 0.0 180 3.5
1 0.5 182 4.5
2 1.0 178 4.0
3 1.5 165 5.5
```
我們想把這些資料裡的每一行都讀進一個對應名字的 array 裡面,其中一個作法就是用 NumPy 的 `loadtxt` function:

例子:
```python
dataPt, time, height, error = np.loadtxt("mydata.txt", skiprows = 5, unpack = True)
```
此處的 `loadtxt` 用了三個 arguments:
1. 檔案名稱的 string
> 也就是例子中的 `"mydata.txt"`
2. 檔案的最前面(也叫做 <font color = "snake">header</font>)要跳過幾行
> 此例中,因為前五行非 data 本身的內容,所以跳過
3. 如果 `unpack = True` (預設為 `False`),每一行會被拆成分開的 array,如果設 `unpack = False`,所有讀到的 data 會在同個 array 中。
> 此例中,因為 `unpack = True`,所以我們可以按照這四行資料拆成四個 array。
>
> $\rightarrow$ 如果用預設的 `unpack = False`
>
> 
>
> 結果會是:
>
> 
- Note:這裡的 `dataPt`, `time`, `height`, `error` 的 type 都是 numpy array 而非 lists,我們可以看下方的 code:

印出來長這樣:

一樣可以用 access list 的 index 方式,例如:

結果會是:

## 刪除
### del
用於刪除 list 中特定 index 的 element,且不需要 return 被刪的東西時。
> 如果刪除的同時要 return 被刪的 element,可以用 `pop()`
直接看例子:
```python
my_list = [1, 2, 3, 4, 5]
n = 2
del my_list[n]
print(my_list)
>>> [1, 2, 4, 5]
```
- Note: 因為 `del` modifies list in place,所以如果有一個情況是我們有一個 list A,然後我們++把 list A 的內容 assign 給 list B++,那麼<font color = "red">當我們對 list A 做 `del` 時,list B 的值也會跟著更改</font>,例子如下:
```python
list_A = [1, 2, 3]
list_B = list_A
del list_A[0]
print("List A: ", list_A) # Output: [2, 3]
print("List B: ", list_B) # Output: [2, 3]
>>> List A: [2, 3]
>>> List B: [2, 3]
```
> $\rightarrow$ 因為 `list_A` 和 `list_B` refer 到 memory 裡的同個 list object,所以對 `list_A` 做的更動對 `list_B` 來說也是 visible 的。
如果我們不希望在 `del list_A[0]` 時也一起改到 `list_B` 的內容,我們可以使用 <font color = "green">`copy()`</font> 將 `list_B = list_A` 改成 `list_B = list_A.copy()`,如下:
```python
list_A = [1, 2, 3]
list_B = list_A.copy()
del list_A[0]
print("List A: ", list_A)
print("List B: ", list_B)
>>> List A: [2, 3]
>>> List B: [1, 2, 3]
```
### 刪除特定類型字元: join
例子:
用 `join()` 刪除所有不是英文的 character
```python
original_string = "Hello, 123world!"
result_string = ''.join(char for char in original_string if char.isalpha())
print(result_string)
>>> Helloworld
```
> `join()` 會 iterate 過所有 `original_string` 裡的 character,對每個 char,我們會 call `isalpha()` 來檢查是否為英文字母,如果是的話, `join()` 會 concatenate 這些字母,再 return 單一的一個 string。
- `join()` 的 syntax:
```python
string.join(iterable)
```
> `iterable` 如 list, tuple...是你要拿來 concatenate 的東西;而 `string` 則是一個 separator,也就是你在 concatenate 各個 elements 時,每兩個中間的要插入的東西(例如空格)舉例來說:
```python
words = ['Hello', 'world', 'how', 'are', 'you?']
result_string = ' '.join(words)
print(result_string)
>>> Hello world how are you?
```
> 在這個例子裡,`string` 是一個空格 `' '`,所以在把 `words` 這個 iterable 裡的每個 string concatenate 時,中間都會插入一個空格。
Note:
- `join()` 是 call on `string`
- `join()` 通常用在要 concatenate 大量的 strings,來避免用 `+` 來 concatenate 的 performance overhead。
#### 反向 concatenate
一種常見的題目是把一個句子裡所有的單字反向再 output,這種時候一個作法是可以先將句子切割成單字(用 `split()`,用法可參考下方 `split()` 小節。)接著再用 slicing 反向,如下方例子:
```python
my_list = ['apple', 'banana', 'orange']
result = ' '.join(my_list[::-1])
print(result)
>>> 'orange banana apple'
```
> `my_list[::-1]` 是一種 slicing 的 operation,會++產生++原本 list `my_list` 的反向 ++copy++,其中方括弧中的內容為<font color = "green">`[start:stop:step]`</font>:
>> 第一個 `:` 前的數字(start)代表從哪裡開始
>> 第二個 `:` 和第一個 `:` 之間的數字(stop)代表結束的位置
>> 第二個 `:` 後的最後一個數字(step)如果是正的,代表多少一個間隔,如果是負的則是反向
>>
> 因此,此處即反向的去 `join()` `my_list`,每個 element 在 concatenate 時中間插入一個間隔 `' '`,並且因為沒有設定 start 和 stop,所以會從最後一個 element traverse 到第一個。
## 排序
### sort
`sort()` 會 sort in place,也就是會 modify 原本的 list
$\rightarrow$ 如果不想要更動到原本 list 的內容,可改用 `sorted()`(見下一小節)
```python
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5]
my_list.sort()
print(my_list)
>>> [1, 1, 2, 3, 4, 5, 5, 6, 9]
```
### sorted
```python
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5]
sorted_list = sorted(my_list)
print("The original list is: ", my_list)
print("The sorted list is: ", sorted_list)
>>> The original list is: [3, 1, 4, 1, 5, 9, 2, 6, 5]
>>> The sorted list is: [1, 1, 2, 3, 4, 5, 5, 6, 9]
```
> $\rightarrow$ 原本的 `my_list` 內容不會隨著 `sorted()` 的使用而改變。
- `sorted()` 的 syntax:
```python
sorted(iterable, *, key=None, reverse=False)
```
> - iterable: 可以是如 list, tuple, string...
> - key (optional): 即排序的依據,是一個會對每個 iterable 的 element 作用的 function,預設為 `None`,即用 natural order 排序。
>> 其餘常見的 key 使用方式可參考下方 lambda 小節
>>
> - reverse (optional):預設為 `False`(由小到大),如果設為 `True` 則為由大到小排序。
### 更改排序依據: lambda
`lambda` 是一種 function 的 keyword,通常在當我們想要快速使用一個簡單的 function,卻又不想再多去定義一大個 code block 時用。舉一個很常見的例子:
```python
my_list = [(1, 'b'), (2, 'a'), (3, 'c')]
sorted_list = sorted(my_list, key=lambda x: x[1])
print(sorted_list)
>>> [(2, 'a'), (1, 'b'), (3, 'c')]
```
在這個例子裡面,`lambda x: x[1]` 就是一個 lambda function,它的 argument `x` 代表了 `my_list` 裡的每個 tuple,而這個 lambda function 會 return 的就是每個 tuple 的 index 1 項 `x[1]`。
這裡我們用到這個 lambda function 是因為我們想要根據 `my_list` 裡每個 tuple 的第一項去 sort,所以我們將 lambda function 的 return value `x[1]` 設成排序依據的 key。
- lambda function 的 syntax:
```python
lambda arguments: expression
```
- Note:
- lambda function 是 ++user-defined++ function,並非一些 predefined 的 functions
- lambda function ++只能有一行++,如果需要 define 的 function 太複雜,需要多行,就還是該用 `def` 去完整定義另外一個 function。
- lambda function ++可以有多個 arguments++,每個用 `,` 分隔,舉例來說:
```python
lambda x, y: x + y
```
> $\rightarrow$ 這個例子中的 lambda function 有兩個 arguments `x` 和 `y`,然後會 return 他們的和 `x + y`
## 數值大小、界線
### min
syntax:
`min(iterable, *iterables, key, default)`
> $\rightarrow$ 後面三者為 optional
> - <font color = "green">iterable</font> 如 list, tuple, set, dictionary...
> - <font color = "green">\*iterables</font> 即可以有多個 iterables
> - <font color = "green">key</font> 為某個 function,我們去比較求 min 即基於這個 function 回傳的值
> - <font color = "green">default</font> 即如果我們的 iterable 為空,預設的回傳值
舉例來說,如果有一個 list 裡面每個 element 都是 string,我們希望求這個 list 裡最短的 string:
```python
my_list = ["apple", "banana", "pear"]
shortest_string = min(my_list, key = len)
print("Shortest string:", shortest_string)
>>> Shortest string: pear
```
$\rightarrow$ 如果沒有特別 specify 我們的 string 的比較基準是長度,那麼比較基準就是首個字母的 alphabetical order,同上面例子的 list:
```python
my_list = ["apple", "banana", "pear"]
smallest_string = min(my_list)
print("Smallest string:", smallest_string)
>>> Smallest string: apple
```
也可以用在常見的求一個 list 裡最小的數:
```python
numbers = [0,22,101,8,44]
print(min(numbers))
>>> 0
```
### range
`range(n)` 產生一個從 0 到 n (但不包含 n)的數列。
```python
list(range(0,10,2))
>>> [0, 2, 4, 6, 8]
```
> - 只有在要把 `range(n)` 的結果 print 出來時需要先用 `list()` 把產生的數列轉換成 list
>
> $\rightarrow$ `range()` 的三個 parameters 依序為 <font color = "green">start, stop, step</font>,也就是從哪個數字開始、到哪個數字(但不包含此數)、每次跳的值為多少
如果我們把 step 設成負數,就會產生一個由大到小的數列,例如:
```python
list(range(10,0,-2))
>>> [10, 8, 6, 4, 2]
```
如果要創造一個遞減的數列,另個方法是用 `reversed()`,例子如下:
```python
list(reversed(range(0,10,2)))
>>> [8, 6, 4, 2, 0]
```
- `list(range(0))` 會 output `[]`(empty list)
- `list(range(-10))` (任何負數)會 output `[]`(empty list)
## split
用來將一個 string(常用如對一個句子或一個段落),切割成一個一個單字,最後會 ++return++ 的是切好的一個個單字的 ++list++。
> 其實正確的來說也不一定是單字,而是(預設)會根據遇到空格或分行的地方去切。
舉例來說:
```python
sentence = "This is a sentence."
words = sentence.split()
print(words)
>>> ['This', 'is', 'a', 'sentence.']
```
- `split()` 的 syntax:
```python
str.split(sep=None, maxsplit=-1)
```
> - `str`:要拿來 split 的 string 名稱
> - `sep` (optional):要拿來切割的分界依據,如果沒有特別更改,預設為遇到 whitespace characters(空格、tab `\t`、換行 `\n`),可以特別指定某個字母、符號⋯⋯去作為分界依據,例如:
```python
date = "2022-04-19"
parts = date.split('-')
print(parts)
>>> ['2022', '04', '19']
```
> - `maxsplit` (optional): 最多可以++切幾次++,所以假設 `maxsplit=2`,那麼會最多只能切出<font color = "red">三</font>個 element。預設為 `-1`,也就是對切的次數沒有限制。
> 例子:
```python
sentence = "This is a sentence."
words = sentence.split(maxsplit=2)
print(words)
>>>['This', 'is', 'a sentence.']
```
Note: ++`split()` 的 `sep` 只能設定一個++,因此,如果有多種符號需要作為劃分依據,一個方法是可以先換成同一種。例子:
```python
date = "2024,05-06 12:51"
date_number_only = date.replace(',',' ').replace('-', ' ').replace(':', ' ')
numbers = date_number_only.split()
print(numbers)
>>> ['2024', '05', '06', '12', '51']
```
> $\rightarrow$ 可以一次對同個 string 連結多個 `replace()`
## lower
`lower()` 可以把所有大寫英文字母轉換成小寫,如果本身即是小寫的 char 則不變,非英文字母的 char(如數字)也不變。
例子:
把一個 list 裡的每個 string 轉換成小寫
```python
words = ['Hello', 'WORLD', 'How', 'Are', 'You?']
lowercase_words = [word.lower() for word in words]
print(lowercase_words)
>>>['hello', 'world', 'how', 'are', 'you?']
```
例子:
如果遇到數字、逗號驚嘆號等符號不變
```python
string = "Hello, 123world!"
lowercase_string = string.lower()
print(lowercase_string)
>>> hello, 123world!
```