從程式設計新手常遇到的挫折談起,逐步探討如何管理程式設計的痛苦與提升程式碼品質。文章以 Python 語言為例,講解如何運用列表推導式、函式設計技巧、物件導向程式設計等方法,撰寫更簡潔、易讀且高效的程式碼。同時,也探討了程式語言的演變、程式設計文化的差異以及程式設計師的思維模式,並以快速排序、指數運算最佳化、缺失整數問題和蒙特卡洛模擬等實際案例,展現如何將演算法思維應用於程式設計實務。

程式設計的過程充滿挑戰,新手容易因過高的期望而感到挫折。學習程式設計的關鍵在於動手實踐,從解決問題的過程中累積經驗。本文分享了許多實用的程式設計技巧,例如如何簡化程式碼、提高可讀性、最佳化效能等,並以 Python 程式碼為例,示範如何應用這些技巧。此外,文章也探討了程式設計師的思維模式、程式設計文化的差異以及程式語言的演變,幫助讀者更全面地理解程式設計的本質。

培養卓越程式設計的良好習慣

撰寫高品質程式碼是每位程式設計師追求的目標。本文將探討如何透過良好的程式設計習慣提升程式設計技巧,並以 Python 為例,提供實用的範例與建議。

寫作動機與目標讀者

本文主要針對有一定程式設計基礎的開發者,旨在分享作者在程式設計領域的經驗與見解。透過閱讀本文,讀者可以學習到如何撰寫更具可讀性、可維護性且高效的程式碼。

第一部分:學校未教的事

第1章:程式設計的幻想

許多程式設計師在初學時總是抱持著過高的期望,認為自己能夠快速寫出完美的程式碼。然而,實際的程式設計過程往往充滿挑戰與挫折。本章將分享如何管理程式設計過程中的痛苦與挫折,並提供實用的建議。

對開發中的程式設計師的建議(痛苦管理)
  1. 接受錯誤是不可避免的:每個程式設計師都會遇到錯誤,重要的是如何從錯誤中學習。

  2. 保持耐心與毅力:程式設計是一個需要長期投入的領域,保持耐心與毅力是成功的關鍵。

第2章:程式設計技巧

本章將介紹一些實用的程式設計技巧,例如如何簡化程式碼、提高程式碼的可讀性等。

# 示例:使用列表推導式簡化程式碼
numbers = [1, 2, 3, 4, 5]
squared_numbers = [n ** 2 for n in numbers]
print(squared_numbers)  # 輸出:[1, 4, 9, 16, 25]

內容解密:

  • 列表推導式是一種簡潔的語法,用於建立新的列表。
  • n ** 2表示對每個元素進行平方運算。
  • 這種寫法比傳統的迴圈更簡潔易讀。

第二部分:程式設計建議

第5章:函式設計

良好的函式設計是撰寫高品質程式碼的基礎。本章將探討如何設計清晰、簡潔且易於維護的函式。

# 示例:良好的函式設計
def calculate_area(radius):
    """計算圓的面積"""
    return 3.14159 * radius ** 2

area = calculate_area(5)
print(area)  # 輸出:78.53975

內容解密:

  • 函式名稱應清晰描述函式的功能。
  • 註解有助於其他開發者理解函式的作用。
  • 單一職責原則:每個函式應只負責一個任務。

第三部分:不同視角

第16章:謹慎使用物件導向程式設計(OOP)

物件導向程式設計(OOP)是一種強大的程式設計正規化,但也可能被濫用。本章將探討如何在適當的情況下使用 OOP,以及如何避免常見的陷阱。

# 示例:簡單的類別定義
class Circle:
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14159 * self.radius ** 2

circle = Circle(5)
print(circle.area())  # 輸出:78.53975

內容解密:

  • 類別定義用於封裝資料與方法。
  • __init__方法用於初始化物件的屬性。
  • area方法計算並傳回圓的面積。

最佳實踐與經驗分享

  1. 持續學習與改進:程式設計領域不斷進步,保持學習新技術、新工具的習慣至關重要。

  2. 撰寫可測試的程式碼:良好的測試習慣有助於確保程式碼的正確性與穩定性。

  3. 重構與最佳化:定期檢視並最佳化現有的程式碼,有助於提升程式碼的品質與效能。

提升程式設計技能的關鍵

對於想要在程式設計領域脫穎而出的開發者,唯一有效的建議是:實際動手實踐。這不是透過閱讀書籍就能達成,而是需要在與具體問題的搏鬥中累積經驗。——Willy Hendriks,《Move First, Think Later》(New in Chess, 2012),第20頁。

本文旨在提升程式設計技能,並學習如何撰寫易讀的程式碼。無論是教師還是正在發展中的程式設計師,都能從中受益。然而,必須立即指出的是,我們只能透過嘗試解決許多具有挑戰性的問題、反思經驗並記住所學到的教訓來學習如何撰寫電腦程式碼。因此,您將在本文中找到二十多個測驗和問題。正如國際象棋教練Willy Hendriks所說:沒有其他方法可以替代實際的實踐經驗

作者的經驗與見解

作者Michael Stueben自1977年起開始在維吉尼亞州費爾法克斯高中教授Fortran程式語言。隨著時間的推移,高中電腦科學課程從Fortran轉變為BASIC、Pascal、C、C++、Java,最終轉變為Python。在過去的五年中,Stueben在亞歷山德里亞的Thomas Jefferson科學與技術高中教授人工智慧。

關鍵技巧的分享

作者在過去38年中,不斷撰寫電腦程式碼並思考如何有效地編寫程式碼。作者表示,很難記得上一次遇到無法解決的嚴重錯誤。那麼,作者與新手之間的區別是什麼?部分原因是作者能夠很好地注意到細節,並且能夠長時間保持專注。然而,這些能力無法直接傳授給他人。不過,作者可以分享一些從閱讀專家、與同行程式設計師交流以及分析自己的錯誤中獲得的技巧。這些技巧保證能夠減少挫折和失敗。

學習程式設計的過程

作者在1974年離開北伊利諾伊大學時,僅修過兩門電腦科學課程(COBOL和Fortran)。這兩門課程都使用了與IBM 360/370電腦配套的光學卡片讀取器。有時候,更改一個逗號也需要在排隊等候打卡機的過程中浪費15分鐘。有時電腦房裡沒有空位。作者對在深夜編寫程式碼感到厭倦。這段經歷讓作者發誓再也不修電腦科學課程。然而,讓作者對程式設計重新產生興趣的是德州儀器(TI)推出的一款可程式設計計算器,該計算器使用磁條儲存記憶體。

程式語言的選擇與體驗

作者在1977年開始在費爾法克斯高中教授Fortran。當時的學校只有三台終端機(遠端連線)供整個班級使用。作者嘗試讓每位學生每週在鍵盤上操作約10分鐘。令人驚訝的是,即使在這種條件下,一些學生仍對程式設計產生了濃厚的興趣。偶爾,作者會發現學生在放學後躲在桌子底下,以便在作者鎖好教室後繼續程式設計數小時。

程式語言的演變

後來,高中的課程從Fortran轉變為BASIC、Pascal、C、C++、Java,最終轉變為Python。作者發現,自己無法同時熟練兩種程式語言。在使用Python六個月後,作者已經忘記了自己五年來學會的Java。Python絕對是最有趣的,其次是C/C++。COBOL是最糟糕的,其次是Java。事實上,作者認為Java語言已經讓許多高中教師對教授電腦科學望而卻步。

結語

正如Bjarne Stroustrup在《The C++ Programming Language》(第二版)中所說:「基本上,每種方法都適用於小型專案。更糟糕的是,似乎每種方法——無論多麼不合理,多麼殘酷地對待參與其中的個人——也適用於大型專案,只要你願意在這個問題上投入大量的時間和金錢。」(第385頁)。

本文旨在幫助讀者提升程式設計技能,並學習如何撰寫易讀、易維護的程式碼。透過實際動手實踐、反思經驗並記住所學到的教訓,讀者將能夠成為一名優秀的程式設計師。

Python語言在演算法開發中的優勢與應用

Python語言因其簡潔易懂的語法和豐富的內建功能,成為開發演算法的理想選擇。以下將探討Python在演算法開發中的特點及其應使用案例項。

Python的內建功能與靈活性

Python的內建函式如minmax等不僅功能強大,而且非常靈活。例如,min函式可以比較數字,也可以比較列表。

print(min(3, 5))  # 輸出:3
paths = [[7, 1, 1], [5, 1, 3]]
print(min(paths))  # 輸出:[5,1,3],因為5 < 7
print(min(paths, key=sum))  # 輸出:[7,1,1],因為7+1+1 < 5+1+3

內容解密:

  • min(3, 5)直接比較兩個數字的大小,輸出較小的數字。
  • min(paths)比較列表時,預設比較第一個元素,因此輸出第一個元素最小的列表。
  • min(paths, key=sum)使用sum函式作為比較的依據,輸出列表元素總和最小的列表。

函式作為引數傳遞

Python允許將函式作為引數傳遞給其他函式,這在測試不同演算法時非常有用。

def fn1():
    print('Hello: ', end='')
    return 1

def fn2():
    print('Goodbye: ', end='')
    return 2

def test(func):
    print('testing', func.__name__, 'Output =', func())

def main():
    print('In program', __file__)
    test(fn1)
    test(fn2)

main()

內容解密:

  • 定義了兩個函式fn1fn2,分別輸出不同的訊息並傳回不同的值。
  • test函式接受一個函式作為引數,輸出該函式的名稱並執行該函式。
  • main函式中,將fn1fn2傳遞給test函式進行測試。

多重指定與交換

Python支援多重指定和一行程式碼內交換變數的值。

a, b, c = 1, 2, 3  # 多重指定
a, b = b, a  # 交換a和b的值

內容解密:

  • 多重指定允許在一行程式碼內為多個變數指定。
  • 交換變數的值不需要使用臨時變數,直接在一行程式碼內完成交換。

字典(Dictionary)資料結構

Python的字典是一種非常有用的資料結構,用於儲存鍵值對。

# 示例:使用字典儲存資料
data = {'name': 'John', 'age': 30}
print(data['name'])  # 輸出:John

內容解密:

  • 字典使用鍵值對儲存資料,可以透過鍵快速存取對應的值。

快速排序(Quick Sort)演算法

快速排序是一種高效的排序演算法,可以使用遞迴實作。

def quickSort(array):
    if len(array) <= 1:
        return array
    return quickSort([x for x in array[1:] if x < array[0]]) + [array[0]] + quickSort([x for x in array[1:] if x >= array[0]])

# 示例:對列表進行快速排序
numbers = [5, 2, 9, 1, 7]
sorted_numbers = quickSort(numbers)
print(sorted_numbers)  

內容解密:

  • 如果列表長度小於或等於1,直接傳回原列表。
  • 否則,將列表分為三部分:小於基準值的元素、基準值、大於或等於基準值的元素。
  • 對小於和大於基準值的部分遞迴進行排序,最終合併結果。

指數運算的最佳化

計算一個數的指數次方時,可以使用更高效的方法減少乘法運算次數。

def power(base, exponent):
    product = 1
    for n in range(exponent):
        product *= base
    return product

def powr(b, exp):
    x = 1
    for n in range(exp):
        x *= b
    return x

# 更高效的指數運算方法
def efficient_power(base, exponent):
    result = 1
    while exponent > 0:
        if exponent % 2 == 1:
            result *= base
        exponent //= 2
        base *= base
    return result

# 示例:計算5的23次方
print(efficient_power(5, 23))  

內容解密:

  • 普通的指數運算方法是連續乘以基數,效率較低。
  • 更高效的方法是利用指數的二進製表示,每次將基數平方,減少乘法次數。
  • 在二進製表示中,如果某位是1,則將對應的基數冪乘入結果。

程式碼最佳化與乘法運算的最小化

在程式設計中,最佳化程式碼以減少不必要的運算是非常重要的。考慮以下程式碼片段:

total = 0
for n in range(1, 3000000):
    total += (2*n*n*n + 3*n*n + 4*n)
print('total =', total)

我們的目標是最佳化迴圈體中的一行程式碼,以最小化乘法的次數。

原始程式碼分析

在原始程式碼中,表示式 2*n*n*n + 3*n*n + 4*n 涉及多次乘法運算。具體來說,對於每個 n,乘法運算的次數如下:

  • n*n 發生一次,結果被用於 n*n*n3*n*n
  • n*n*n 需要一次額外的乘法(即 nn*n 相乘)
  • 2*n*n*n3*n*n4*n 各需要一次乘法

因此,總共需要 6 次乘法運算。

最佳化方案

為了減少乘法次數,我們可以對表示式進行簡化:

total += n*(2*n*n + 3*n + 4)

進一步最佳化:

total += n*(n*(2*n + 3) + 4)

內容解密:

  1. 變換表示式順序:我們將原始表示式改寫為 n*(n*(2*n + 3) + 4),這樣可以顯著減少乘法的次數。
  2. 計算過程
    • 首先計算 2*n + 3,需要一次乘法和一次加法。
    • 然後將結果與 n 相乘,得到 n*(2*n + 3),又需要一次乘法。
    • 接著加上 4,並將整個結果與 n 相乘,需要再一次乘法。
  3. 總乘法次數:透過這種方式,我們將乘法次數從 6 次減少到 3 次。

最終最佳化程式碼

total = 0
for n in range(1, 3000000):
    total += n*(n*(2*n + 3) + 4)
print('total =', total)

缺失整數問題的解決方案

給定一個包含前 100 個正整數的未排序列表,其中一個整數被替換為 0,如何找出缺失的整數?

解決方案

print(5050-sum(x))

內容解密:

  1. 前 100 個正整數的總和:首先,我們需要知道前 100 個正整數的總和。利用數學公式 n*(n+1)/2,當 n=100 時,總和為 100*101/2 = 5050
  2. 計算缺失整數:由於列表中的一個整數被替換為 0,因此列表中所有元素的總和比原來的總和小一個整數。利用這個特性,我們可以透過計算 5050 - sum(x) 得出缺失的整數。

程式設計文化的探討

程式設計領域中有三種主要的文化:

  1. 軟體開發者:專注於使用現有的程式函式庫開發新的軟體工具,管理軟體複雜度,並提升程式設計工具的實用性。
  2. 電腦科學家:研究演算法的開發與分析,探討程式語言的語法與語義,並設計高效的儲存與檢索策略。
  3. 計算科學家:利用電腦作為科學工具進行建模與模擬,研究資料中的模式,並解決複雜的方程式。

對程式設計新手的建議

大多數新手只專注於學習程式語言、資料結構和編寫程式碼的技巧,卻忽略了撰寫可讀性高的程式碼的重要性。

蒙特莫特匹配問題

假設有一副撲克牌共 52 張,將其洗牌後,至少有一張牌保持原位的機率是多少?

電腦模擬解決方案

我們可以透過電腦模擬來解決這個問題。具體步驟如下:

  1. 洗牌模擬:對一副排序好的牌進行多次洗牌(例如,1,000,000 次)。
  2. 統計結果:記錄每次洗牌後至少有一張牌保持原位的次數。
  3. 電腦率:將保持原位的次數除以總模擬次數,得出機率。

程式碼範例

import random

def simulate(n):
    count = 0
    for _ in range(n):
        deck = list(range(52))
        random.shuffle(deck)
        if any(i == deck[i] for i in range(52)):
            count += 1
    return count / n

print(simulate(1000000))

內容解密:

  1. 洗牌過程:我們使用 random.shuffle 對一副排序好的牌進行隨機洗牌。
  2. 檢查原位牌:透過 any(i == deck[i] for i in range(52)) 檢查是否至少有一張牌保持原位。
  3. 統計與計算:累計保持原位的次數,並電腦率。