程式設計謎題:提升你解決問題的訓練場

華為雲開發者社群發表於2021-12-06
摘要:有趣的程式設計謎題可以練習你解決問題的能力,快來挑戰吧~~

本文分享自華為雲社群《程式碼的出現:用 Python 解決你的難題》,作者: Yuchuan 。

程式碼謎題的出現旨在讓任何對解決問題感興趣的人都能上手。您不需要具有深厚的電腦科學背景即可參與。相反,程式碼的來臨是學習新技能和測試 Python 新功能的絕佳場所。

程式設計中的困惑?

處理謎題似乎是在浪費您可用的程式設計時間。畢竟,您似乎並沒有真正生產出任何有用的東西,也沒有推進您當前的專案。

然而,花一些時間練習程式設計謎題有幾個好處:

  • 與您的常規工作任務相比,程式設計難題通常更明確且包含更多內容。它們讓您有機會針對比您在日常工作中通常需要處理的問題更簡單的問題練習邏輯思維
  • 你經常可以用幾個類似的謎題來挑戰自己。這使您可以建立程式記憶,就像肌肉記憶一樣,並獲得構建某些型別程式碼的經驗。
  • 拼圖的設計通常著眼於解決方案。它們使您可以瞭解和應用經過試驗和測試的演算法,這些演算法是任何程式設計師工具箱的重要組成部分。
  • 對於一些謎題解決方案,如果演算法效率低下,即使是最強大的超級計算機也可能太慢。您可以分析您的解決方案的效能並獲得經驗,以幫助您瞭解什麼時候簡單的方法足夠快,什麼時候需要更優化的程式。
  • 大多數程式語言都非常適合解決程式設計難題。這為您提供了一個很好的機會,可以針對不同的任務比較不同的程式語言。拼圖也是瞭解新程式語言或嘗試您最喜歡的語言的一些最新功能的好方法。

最重要的是,用程式設計難​​題挑戰自己通常非常有趣!當你把所有的東西加起來時,為謎題留出一些時間是非常有益的。

探索線上解決程式設計難題的選項

幸運的是,您可以在許多網站上找到程式設計難題並嘗試解決它們。這些網站存在的問題型別、您提交解決方案的方式以及網站可以提供的反饋和社群型別通常存在差異。因此,您應該花一些時間環顧四周,找到對您最有吸引力的那些。

在本教程中,您將瞭解程式碼的來臨,包括您可以在那裡找到什麼樣的謎題以及您可以使用哪些工具和技巧來解決它們。但是,您也可以在其他地方開始解決程式設計難題:

  • Exercism有許多不同程式語言的學習路徑。每個學習軌道都提供有關不同程式設計概念、編碼挑戰和指導者的小型教程,為您提供有關解決方案的反饋。
  • 尤拉計劃已經存在很長時間了。該網站提供數百個謎題,通常以數學問題的形式表述。您可以使用任何程式語言解決問題,一旦您解決了難題,您就可以訪問社群執行緒,在那裡您可以與其他人討論您的解決方案。
  • Code Wars提供了大量的編碼挑戰,他們稱之為katas。您可以使用許多不同的程式語言通過內建編輯器和自動化測試解決難題。之後,您可以將您的解決方案與其他人的解決方案進行比較,並在論壇中討論策略。
  • 如果您正在尋找工作,HackerRank具有強大的功能。他們提供許多不同技能的認證,包括解決問題和 Python 程式設計,以及一個工作板,可讓您在工作申請中展示自己的解謎技能。

還有許多其他網站可供您練習解謎技巧。在本教程的其餘部分,您將重點關注 Advent of Code 必須提供的內容。

為 Code 的出現做準備:25 個聖誕節的新鮮謎題

現在是程式碼出現的時候了!它由Eric Wastl於 2015 年創立。從那時起,每年 12 月都會發布包含 25 個新程式設計謎題的新出現日曆。這些年來,拼圖變得越來越流行。自 2020 年以來,已有超過 170,000人至少解決了其中一個難題。

注意:傳統上,降臨節日曆是用於在等待聖誕節期間計算降臨節天數的日曆。多年來,降臨節日曆變得更加商業化並且失去了一些與基督教的聯絡。

大多數降臨節日曆從 12 月 1 日開始,到 12 月 24 日(平安夜)或 12 月 25 日(聖誕節)結束。如今,各種降臨節日曆應有盡有,包括樂高日曆、茶日曆和化妝品日曆。

在傳統的降臨節日曆中,您每天開啟一扇門以顯示裡面的內容。Advent of Code 模仿了這一點,讓您從 12 月 1 日到 12 月 25 日每天開啟一個謎題。對於您解決的每個難題,您都將獲得金星,您可以保留這些星星。

在本節中,您將更加熟悉 Advent of Code 並瞥見您的第一個謎題。稍後,您將瞭解如何解決這些難題的詳細資訊,並練習自己解決一些難題。

程式碼謎題的出現

Advent of Code 是一個線上 Advent 日曆,從 12 月 1 日到 12 月 25 日,每天都會釋出一個新的謎題。每個謎題都在美國東部時間午夜可用。Advent of Code 謎題有幾個典型特徵:

  • 每個謎題由兩部分組成,但在您完成第一部分之前不會顯示第二部分。
  • 每完成一個部分,您將獲得一顆金星 (⭐)。這意味著如果您在一年內解決所有難題,您每天可以獲得兩顆星和五十顆星。
  • 每個人的謎題都是一樣的,但您需要根據從 Advent of Code 網站獲得的個性化輸入來解決它。這意味著您對一個謎題的答案將與其他人的不同,即使您使用相同的程式碼來計算它。

您可以參加全球競賽,成為第一個解決每個難題的人。然而,這裡通常擠滿了高技能、有競爭力的程式設計師。如果您將 Code of Code 用作自己的練習,或者如果您向您的朋友和同事發起小型友好競賽,它可能會更有趣。

要了解程式碼出現難題的工作原理,請考慮2020 年的第一天難題:

在你離開之前,會計精靈只需要你修正你的開支報告(你的拼圖輸入);顯然,有些事情並沒有完全加起來。

具體來說,他們需要您找到總和為的兩個條目,2020然後將這兩個數字相乘。

每年,都會有一個非常愚蠢的背景故事將謎題聯絡在一起。2020 年的故事描述了您嘗試離開去度過一個當之無愧的假期,因為您已經連續幾年拯救了聖誕節。這個故事通常對謎題沒有影響,但跟隨它仍然很有趣。

在故事的情節元素之間,你會發現謎題本身。在此示例中,您要在拼圖輸入中查詢總和為 2,020 的兩個條目。在描述問題的解釋之後,您通常會找到一個示例,顯示您需要執行的計算:

例如,假設您的費用報告包含以下內容:

1721
979
366
299
675
1456

在這份名單中,這兩個條目總和2020是1721和299。將它們相乘產生1721 * 299 = 514579,所以正確答案是514579

該示例向您展示了此特定數字列表的答案。如果您要開始解決這個難題,現在您將開始考慮如何在任何有效數字列表中找到這兩個條目。但是,在深入研究這個難題之前,您將探索如何使用 Advent of Code 站點。

如何參與程式碼的出現

您已經看到了程式碼出現難題的示例。接下來,您將瞭解如何提交答案。您永遠不會提交任何程式碼來解決難題。您只需提交答案,通常是數字或文字字串。

通常,您將按照一系列步驟來解決網站上的難題:

  1. 登入Advent of Code網站。您可以使用來自其他服務(如 GitHub、Google、Twitter 或 Reddit)的憑據來執行此操作。
  2. 閱讀拼圖文字並特別注意給定的例子。您應該確保您瞭解示例資料的解決方案。
  3. 下載拼圖的個性化輸入。您將需要此輸入才能找到問題的唯一答案。
  4. 編寫您的解決方案。這是有趣的部分,您將在本教程的其餘部分獲得大量練習。
  5. 在拼圖頁面上輸入您對拼圖的答案。如果您的答案是正確的,那麼您將獲得一顆金星,並開啟謎題的第二部分。
  6. 對拼圖的第二部分重複步驟 2 到 4。第二部分與第一部分類似,但它通常會增加一些需要您調整程式碼的扭曲。
  7. 在拼圖頁面上輸入您的第二個答案,以獲得第二顆星並完成拼圖。

請記住,您不提交任何程式碼——只提交您的謎題答案。這意味著可以用任何程式語言解決程式碼難題的出現。許多人使用 Advent of Code 來練習和學習一種新的程式語言。Advent of Code 的建立者Eric Wastl在 2019 年發表了一次演講,他談到了參與人員的不同背景和動機等。

注意:有一個Advent of Code的排行榜。一般來說,你應該忽略這個排行榜!它僅顯示在拼圖開啟後誰提交了前 100 個解決方案。要想躋身排行榜,您需要大量的準備、奉獻精神和競爭性程式設計的經驗。

相反,您應該檢視私人排行榜。這些在您登入後可用,它們讓您有機會邀請您的朋友和同事加入更輕鬆的社群。您可以選擇基於要麼得分您的私人排行榜時的困惑基礎上得到解決或者乾脆數量困惑的人已經解決了的。

您還可以將您在私人排行榜中的名字連結到您的GitHub帳戶,這樣您就可以與朋友分享您的解決方案。登入後,您可以通過單擊Advent of Code 站點選單中的設定來進行設定。

Advent of Code 完全免費使用,但您仍然可以通過幾種不同的方式支援該專案:

  • 您可以在您的社交媒體上分享有關 Advent of Code 的資訊以宣傳。
  • 您可以通過參與r/adventofcode subreddit 或其他論壇來幫助他人。
  • 您可以邀請您的朋友參加 Advent of Code,在私人排行榜上分享您的結果。
  • 您可以向 Advent of Code捐款。如果您這樣做了,那麼您將在網站上的姓名旁邊看到一個AoC++徽章。

在接下來的部分中,您將看到有關如何準備使用 Python 解決程式碼出現問題的一些建議。還有一個很棒的列表,您可以檢視與 Advent of Code 相關的許多不同資源的連結,包括許多其他人的解決方案。

用 Python 解決程式碼的出現

Code of Code 已成為全球許多編碼人員的年度亮點。2020 年,超過 170,000人提交了他們的解決方案。自 2015 年 Advent of Code 啟動以來,已有超過 380,000 名程式設計師參與其中。他們中的許多人使用 Python 來解決難題。

那麼,現在輪到你了!前往Advent of Code 網站,檢視最新的謎題。然後,返回本教程獲取一些提示並幫助開始使用 Python 解決程式碼出現難題。

拼圖的剖析

在本節中,您將探索程式碼出現難題的典型剖析。此外,您將瞭解一些可用於與之互動的工具。

每個 Advent of Code 謎題都分為兩部分。當您開始處理拼圖時,您只會看到第一部分。一旦您提交了第一部分的正確答案,第二部分就會解鎖。這通常是您在第一部分解決的問題的轉折點。有時,您會發現有必要從第一部分重構您的解決方案,而有時您可以根據您已經完成的工作快速解決第二部分。

兩個部分始終使用相同的拼圖輸入。您可以從當天的拼圖頁面下載拼圖輸入。您會在拼圖說明後找到一個連結。

注意:如前所述,您的拼圖輸入是個性化的。這意味著如果您與其他人討論解決方案,他們的最終答案可能與您的不同。

提交拼圖解決方案所需執行的所有操作(實際解決拼圖除外)都可以在 Advent of Code 網站上完成。您應該使用它來提交您的第一個解決方案,以便您熟悉流程。

稍後,您可以使用多種工具來組織 Advent of Code 設定並提高工作效率。例如,您可以使用該advent-of-code-data包下載資料。這是一個可以使用pip安裝的 Python 包:

$ python -m pip install advent-of-code-data

您可以使用advent-of-code-data其aocd工具在命令列上下載特定的拼圖輸入集。另一個有趣的可能性是在您的 Python 程式碼中自動下載和快取您的個性化拼圖輸入:

>>>
>>> from aocd.models import Puzzle
>>> puzzle = Puzzle(year=2020, day=1)

>>> # Personal input data. Your data will be different.
>>> puzzle.input_data[:20]
'1753\n1858\n1860\n1978\n'

您需要在環境變數或檔案中設定會話 ID,然後才能使用advent-of-code-data. 您將在文件中找到對此的解釋。如果您感興趣,還可以使用advent-of-code-data或aocd提交您的解決方案並檢視您之前的答案。

作為拼圖文字的一部分,您還會發現一個或多個示例,這些示例通常是根據比您的個性化輸入資料更小的資料來計算的。在開始編碼之前,您應該仔細閱讀這些示例,並確保您瞭解要求您執行的操作。

您可以使用這些示例為您的程式碼設定測試。一種方法是在示例資料上手動執行您的解決方案並確認您得到了預期的答案。或者,您可以使用類似工具pytest來自動化該過程。

注意: 測試驅動開發 (TDD)是一個在實現程式碼之前編寫測試的過程。由於 Advent of Code 為您提供了對小示例的預期答案,因此它為您提供了自己嘗試測試驅動開發的絕佳機會。

稍後當您嘗試自己解決一些難題時,您將瞭解有關 TDD 的更多資訊。

您只需使用簡單的 Python 和標準庫即可解決所有 Advent of Code 難題。但是,有一些軟體包可以幫助您整理解決方案:

  • advent-of-code-data 可以下載您的輸入資料並提交您的解決方案。
  • pytest 可以自動檢查示例資料上的解決方案。
  • parse可以用比正規表示式更簡單的語法來解析字串。
  • numpy 可以有效地計算數字陣列。
  • colorama 可以在終端中為您的解決方案設定動畫。

如果您建立了一個虛擬環境並安裝了這些包,那麼您將擁有一個非常可靠的工具箱,用於您的 Advent of Code 冒險。後來,你會看到你如何使用的例子parse,numpy和colorama解決難題。

解決方案的結構

在上一節中,您熟悉瞭如何閱讀和理解 Advent of Code 謎題。在本節中,您將瞭解如何解決這些問題。在解決“程式碼出現”難題之前,您無需進行大量設定。

你有沒有想過如何解決你之前看到的難題?回想一下,您正在查詢列表中總和為 2,020 的兩個數字的乘積。在繼續之前,請考慮——也許可以編寫程式碼——如何找到以下列表中哪兩個條目的總和為 2,020:

numbers = [1721, 979, 366, 299, 675, 1456]

以下指令碼顯示瞭解決2020 年第 1 天難題的第一部分的一種方法:

 1for num1 in numbers:
 2    for num2 in numbers:
 3        if num1 < num2 and num1 + num2 == 2020:
 4            print(num1 * num2)

巢狀for迴圈從列表中查詢兩個數字的所有組合。第 3 行的測試實際上比實際需要的稍微複雜一些:您只需要測試數字總和是否為 2,020。但是,通過新增num1應該小於的條件,num2可以避免兩次找到解。

在這個例子中,一個解決方案的模樣num1 = 1721和num2 = 299,但因為你可以以任何順序新增數字,這意味著還num1 = 299和num2 = 1721形成的解決方案。通過額外檢查,僅報告後一種組合。

一旦你有了這個解決方案,你就可以將你的個性化輸入資料複製到numbers列表中並計算你的謎題答案。

注意:有比嘗試所有可能性更有效的方法來計算這個答案。但是,從基本方法開始通常是個好主意。引用喬·阿姆斯特朗的話說:

讓它工作,然後讓它漂亮,然後如果你真的,真的必須,讓它快。90% 的情況下,如果你讓它漂亮,它已經很快了。所以真的,只是讓它漂亮!(來源)

—喬·阿姆斯特朗

既然你已經看到了這個謎題的有效解決方案,你能把它做得漂亮嗎?

當您解決更多難題時,您可能會開始覺得將資料複製到程式碼中並將其重寫為有效的 Python 變得很煩人。類似地,向程式碼中新增一些函式可為您提供更大的靈活性。例如,您可以使用它們向程式碼中新增測試。

Python 有許多強大的字串解析功能。從長遠來看,最好在下載時保留輸入資料,讓 Python 將它們解析為可用的資料結構。事實上,將程式碼分成兩個函式通常是有益的。一個函式將解析字串輸入,另一個函式將解決這個難題。基於這些原則,你可以重寫你的程式碼:

1# aoc202001.py
 2
 3import pathlib
 4import sys
 5
 6def parse(puzzle_input):
 7    """Parse input"""
 8    return [int(line) for line in puzzle_input.split()]
 9
10def part1(numbers):
11    """Solve part 1"""
12    for num1 in numbers:
13        for num2 in numbers:
14            if num1 < num2 and num1 + num2 == 2020:
15                return num1 * num2
16
17if __name__ == "__main__":
18    for path in sys.argv[1:]:
19        print(f"\n{path}:")
20        puzzle_input = pathlib.Path(path).read_text().strip()
21
22        numbers = parse(puzzle_input)
23        print(part1(numbers))

在第 12 到 15 行,您將識別出您之前的解決方案。首先,您已將其包裝在一個函式中。這使得以後更容易向程式碼中新增自動測試。您還新增了一個parse()函式,可以將字串行轉換為數字列表。

在第 20 行,您用於pathlib將檔案內容作為文字讀取並去除末尾的所有空白行。迴圈sys.argv為您提供在命令列中輸入的所有檔名。

在您處理解決方案時,這些更改為您提供了更大的靈活性。假設您已將示例資料儲存在名為 的檔案中,example.txt並將您的個性化輸入資料儲存在名為input.txt. 然後,您可以通過在命令列上提供它們的名稱,在其中任何一個或什至兩者上執行您的解決方案:

$ python aoc202001.py example.txt input.txt
example.txt:
514579

input.txt:
744475

514579確實是使用示例輸入資料時問題的答案。請記住,您的個性化輸入資料的解決方案將與上面顯示的解決方案不同。

現在是時候讓 Advent of Code 網站一展身手了!轉到2020 Advent of Code 日曆並找到第 1 天的謎題。如果您還沒有,請下載輸入資料並計算謎題的解決方案。然後,在網站上輸入您的解決方案並點選提交。

起始模板

正如您在上面看到的,程式碼謎題的出現遵循一套結構。因此,為自己建立一個模板是有意義的,您可以在開始編寫解決方案時將其用作起點。您在這樣的模板中到底想要多少結構是個人品味的問題。首先,您將探索一個基於您在上一節中看到的原則的模板示例:

 1# aoc_template.py
 2
 3import pathlib
 4import sys
 5
 6def parse(puzzle_input):
 7    """Parse input"""
 8
 9def part1(data):
10    """Solve part 1"""
11
12def part2(data):
13    """Solve part 2"""
14
15def solve(puzzle_input):
16    """Solve the puzzle for the given input"""
17    data = parse(puzzle_input)
18    solution1 = part1(data)
19    solution2 = part2(data)
20
21    return solution1, solution2
22
23if __name__ == "__main__":
24    for path in sys.argv[1:]:
25        print(f"{path}:")
26        puzzle_input = pathlib.Path(path).read_text().strip()
27        solutions = solve(puzzle_input)
28        print("\n".join(str(solution) for solution in solutions))

該模板具有用於解析輸入以及解決謎題的兩個部分的單獨函式。您根本不需要觸及第 15 到 27 行。他們照顧的閱讀文字從輸入檔案,要求parse(),part1()和part2(),然後再報告解決方案控制檯。

您可以建立一個類似的模板來測試您的解決方案。

注意:如前所述,示例資料對於建立測試很有用,因為它們代表具有相應解決方案的已知資料。

以下模板pytest用作測試執行程式。它的三個不同的測試,每一個功能的準備parse(),part1()以及part2():

 1# test_aoc_template.py
 2
 3import pathlib
 4import pytest
 5import aoc_template as aoc
 6
 7PUZZLE_DIR = pathlib.Path(__file__).parent
 8
 9@pytest.fixture
10def example1():
11    puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
12    return aoc.parse(puzzle_input)
13
14@pytest.fixture
15def example2():
16    puzzle_input = (PUZZLE_DIR / "example2.txt").read_text().strip()
17    return aoc.parse(puzzle_input)
18
19@pytest.mark.skip(reason="Not implemented")
20def test_parse_example1(example1):
21    """Test that input is parsed properly"""
22    assert example1 == ...
23
24@pytest.mark.skip(reason="Not implemented")
25def test_part1_example1(example1):
26    """Test part 1 on example input"""
27    assert aoc.part1(example1) == ...
28
29@pytest.mark.skip(reason="Not implemented")
30def test_part2_example2(example2):
31    """Test part 2 on example input"""
32    assert aoc.part2(example2) == ...

你會看到,你如何使用這個模板的例子以後。在此之前,您應該注意以下幾點:

  • 如第 1 行所示,您應該pytest使用test_字首命名您的檔案。
  • 類似地,每個測試都在一個以test_字首命名的函式中實現。您可以在第 20、25 和 30 行看到這些示例。
  • 您應該更改第 5 行的匯入以匯入您的解決方案程式碼。
  • 該模板假定示例資料儲存在名為example1.txt和 的檔案中example2.txt。
  • 當您準備好開始測試時,您應該刪除第 19、24 和 29 行的跳過標記。
  • 您需要...根據示例資料和相應的解決方案填寫第 22、27 和 32 行的省略號 ( )。

例如,如果您要將此模板改編為上一節中 2020 年第 1 天謎題第一部分的重寫解決方案,則您需要建立一個example1.txt包含以下內容的檔案:

1721
979
366
299
675
1456

接下來,您將刪除前兩個測試的跳過標記並按如下方式實現它們:

def test_parse_example1(example1):
    """Test that input is parsed properly"""
    assert example1 == [1721, 979, 366, 299, 675, 1456]

def test_part1_example1(example1):
    """Test part 1 on example input"""
    assert aoc.part1(example1) == 514579

最後,您需要確保您正在匯入您的解決方案。如果您使用了 filename aoc202001.py,那麼您應該將第 5 行更改為 import aoc202001:

 5import aoc202001 as aoc

然後,您將執行pytest以檢查您的解決方案。如果您正確地實施了您的解決方案,那麼您會看到如下內容:

$ pytest
====================== test session starts =====================
collected 3 items

test_aoc202001.py ..s                                     [100%]
================= 2 passed, 1 skipped in 0.02s =================

注意... 前面的兩個點 ( ) s。它們代表兩個通過的測試。如果測試失敗,您會看到F而不是每個點,以及出現問題的詳細說明。

Cookiecutter和Copier等工具可以更輕鬆地處理此類别範本。如果您安裝了 Copier,那麼您可以通過執行以下命令來使用類似於您在此處看到的模板:

$ copier gh:gahjelle/template-aoc-python advent_of_code

這將為advent_of_code您計算機上目錄的子目錄中的一個特定拼圖設定模板。

解決策略

程式碼謎題的出現非常多樣化。隨著日曆的推進,您將解決許多不同的問題,並發現許多解決這些問題的不同策略。

其中一些策略非常通用,可以應用於任何謎題。如果您發現自己被一個謎題卡住了,您可以嘗試解決以下問題:

  • 重新閱讀說明。程式碼謎題的出現通常非常明確,但其中一些可能包含大量資訊。確保您沒有遺漏拼圖的重要部分。
  • 積極使用示例資料。確保您瞭解這些結果是如何實現的,並檢查您的程式碼是否能夠重現這些示例。
  • 一些謎題可能會涉及一些。將問題分解為更小的步驟,並單獨實施和測試每個步驟。
  • 如果您的程式碼適用於示例資料但不適用於您的個性化輸入資料,那麼您可以根據您能夠手動計算的數字構建其他測試用例,以檢視您的程式碼是否涵蓋所有極端情況。
  • 如果您仍然被卡住,那麼請在一些致力於程式碼出現的論壇上與您的朋友和其他解謎者聯絡,並詢問他們如何解決謎題的提示。

隨著您做越來越多的謎題,您將開始認識到一些反覆出現的一般謎題。

一些謎題涉及文字和密碼。Python 有幾個強大的工具來處理文字字串,包括許多字串方法。要讀取和解析字串,瞭解正規表示式的基礎知識會很有幫助。但是,您通常也可以使用第三方parse庫。

例如,假設您有字串"shiny gold bags contain 2 dark red bags."並希望從中解析相關資訊。您可以使用parse及其模式語法:

>>>
>>> import parse
>>> string = "shiny gold bags contain 2 dark red bags."
>>> pattern = "{outer_color} bags contain {num:d} {inner_color} bags."
>>> match = parse.search(pattern, string)
>>> match.named
{'outer_color': 'shiny gold', 'num': 2, 'inner_color': 'dark red'}

在後臺,parse構建一個正規表示式,但您使用類似於f-strings使用的語法的更簡單的語法。

在其中一些文字問題中,明確要求您使用程式碼和解析器,通常構建小型自定義組合語言。解析完程式碼後,通常需要執行給定的程式。實際上,這意味著您構建了一個小型狀態機,可以跟蹤其當前狀態,包括其記憶體的內容。

您可以使用類將狀態與行為保持在一起。在 Python 中,資料類非常適合快速設定狀態機。以下示例顯示了一個可以處理兩種不同指令的小型狀態機的實現:

 1from dataclasses import dataclass
 2
 3@dataclass
 4class StateMachine:
 5    memory: dict[str, int]
 6    program: list[str]
 7
 8    def run(self):
 9        """Run the program"""
10        current_line = 0
11        while current_line < len(self.program):
12            instruction = self.program[current_line]
13
14            # Set a register to a value
15            if instruction.startswith("set "):
16                register, value = instruction[4], int(instruction[6:])
17                self.memory[register] = value
18
19            # Increase the value in a register by 1
20            elif instruction.startswith("inc "):
21                register = instruction[4]
22                self.memory[register] += 1
23
24            # Move the line pointer
25            current_line += 1

這兩個指令set並inc進行分析和內處理.run()。請注意,第5 行和第 6 行的型別提示使用更新的語法,該語法僅適用於Python 3.9及更高版本。如果您使用的是舊版本的 Python,那麼您可以使用 importDict和Listfromtyping代替。

要執行您的狀態機,您首先使用初始記憶體對其進行初始化並將程式載入到機器中。接下來,您呼叫.run(). 程式完成後,您可以檢查.memory以檢視機器的新狀態:

>>>
>>> state_machine = StateMachine(
...     memory={"g": 0}, program=["set g 44", "inc g"]
... )
>>> state_machine.run()
>>> state_machine.memory
{'g': 45}

該程式首先設定g為 的值44,然後增加它,使其最終值為45。

一些有趣的謎題涉及網格和迷宮。如果您的網格具有固定大小,那麼您可以使用NumPy來獲得它的有效表示。迷宮通常有助於視覺化。您可以使用Colorama直接在您的終端中繪製:

import numpy as np
from colorama import Cursor

grid = np.array(
    [
        [1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1],
        [1, 1, 1, 0, 1],
        [1, 0, 0, 2, 1],
        [1, 1, 1, 1, 1],
    ]
)

num_rows, num_cols = grid.shape
for row in range(num_rows):
    for col in range(num_cols):
        symbol = " #o"[grid[row, col]]
        print(f"{Cursor.POS(col + 1, row + 2)}{symbol}")

此指令碼顯示了使用 NumPy 陣列儲存網格,然後使用Cursor.POSfrom Colorama 在終端中定位游標以列印出網格的示例。執行此指令碼時,您將看到如下輸出:

#####
#   #
### #
#  o#
#####

在執行時視覺化您的程式碼可能很有趣,並且還可以為您提供一些很好的見解。當您在除錯並且不太瞭解正在發生的事情時,它也可以是非常寶貴的幫助。

到目前為止,在本教程中,您已經獲得了一些關於如何使用 Advent of Code 謎題的一般提示。在接下來的部分中,您將獲得更明確的資訊並解決早年的兩個難題。

程式碼實踐:2019 年第 1 天

您將嘗試自己解決的第一個難題是2019 年第 1 天,稱為火箭方程式的暴政。這是一個典型的第 1 天難題,因為該解決方案並不是很複雜。這是一個很好的練習,可以習慣 Advent of Code 的工作方式並檢查您的環境是否已正確設定。

第 1 部分:拼圖說明

在 2019 年的故事情節中,你將拯救被困在太陽系邊緣的聖誕老人。在第一個謎題中,您正在準備發射火箭:

精靈們迅速將您裝入宇宙飛船並準備發射。

在第一次 Go / No Go 民意調查中,每個 Elf 都是 Go,直到 Fuel Counter-Upper。他們還沒有確定所需的燃料量。

發射給定模組所需的燃料基於其質量。具體來說,要找到模組所需的燃料,取其質量,除以 3,向下取整,然後減去 2。

示例資料如下所示:

  • 對於質量12,除以 3 並向下取整得到4,然後減去 2 得到2。
  • 對於質量14,除以 3 並四捨五入仍然產生4,因此所需的燃料也是2。
  • 對於質量為1969,所需的燃料為654。
  • 對於質量為100756,所需的燃料為33583。

您需要計算航天器的總燃料需求:

Fuel Counter-Upper 需要知道總燃料需求。要找到它,請單獨計算每個模組(您的拼圖輸入)的質量所需的燃料,然後將所有燃料值加在一起。

您的航天器上所有模組的燃料需求總和是多少

現在是時候嘗試自己解決難題了!下載您的個性化輸入資料並在 Advent of Code 上檢查您的解決方案可能是最有趣的,這樣您就可以獲得星星。但是,如果您還沒有準備好登入 Advent of Code,請根據上面提供的示例資料隨意解決難題。

第 1 部分:解決方案

完成拼圖並獲得星星後,您可以展開摺疊塊以檢視拼圖解決方案的討論:

2019 年第 1 天的解決方案,第 1 部分顯示隱藏

您現在已經解決了謎題的第一部分。但是,在進入謎題的第二部分之前,下一部分將展示如何使用之前在解決此問題時看到的模板。

第 1 部分:使用模板的解決方案

展開下面的摺疊塊,檢視 2019 年第 1 天程式碼出現難題第一部分的另一個解決方案——這次使用您之前看到的模板來組織程式碼並簡化測試:

2019 年第 1 天第 1 部分的模板化解決方案顯示隱藏

您現在可以繼續進行拼圖的第二部分。你準備好扭轉了嗎?

第 2 部分:拼圖說明

每個 Advent of Code 謎題都由兩部分組成,只有在您解決第一部分後才會顯示第二部分。第二部分始終與第一部分相關,並將使用相同的輸入資料。但是,您可能經常需要重新考慮解決謎題前半部分的方法,以便考慮後半部分。

展開下面摺疊的塊以檢視 2019 年第 1 天程式碼出現難題的第二部分:

2019 年第 1 天,第 2 部分顯示隱藏

您將在下一部分看到第二部分的可能解決方案。但是,請先嚐試自己解決難題。如果您需要開始的提示,請展開下面的框:

2019 年第 1 天的提示,第 2 部分顯示隱藏

你怎麼做的?你的火箭準備好發射了嗎?

第 2 部分:解決方案

本節展示瞭如何解決第二部分,繼續使用上面看到的模板:

2019 年第 1 天的解決方案,第 2 部分顯示隱藏

恭喜!您現在已經解決了整個 Advent of Code 難題。您準備好迎接更具挑戰性的挑戰了嗎?

程式碼實踐:2020 年第 5 天

您將嘗試解決的第二個難題是2020 年第 5 天的難題,稱為Binary Boarding。這個謎題比前一個更具挑戰性,但最終的解決方案不需要很多程式碼。首先檢視第一部分的拼圖說明。

第 1 部分:拼圖說明

2020 年,您正在努力前往您當之無愧的度假勝地。在第 5 天,當麻煩接踵而至時,您將要登機:

你登上飛機卻發現一個新問題:你丟了登機牌!你不確定哪個座位是你的,所有的空乘人員都忙著處理突然通過護照檢查的人潮。

您編寫了一個快速程式,使用手機的攝像頭掃描附近的所有登機牌(您的拼圖輸入);也許你可以通過淘汰的過程找到你的座位。

這家航空公司使用二元空間分割槽來安排座位,而不是區域或組。一個座位可能被指定為FBFBBFFRLR,其中的F意思是“前”,B意思是“後”,L意思是“左”,R意思是“右”。

前 7 個字元將是F或B; 這些精確指定的一個128行上的平面(編號0通過127)。每個字母都會告訴您給定的座位位於區域的哪一半。

從整個行列表開始;第一個字母表示座位是在前面(0通過63)還是在後面(64通過127)。下一個字母表示座位位於該區域的哪一半,依此類推,直到您只剩下一排。

例如,僅考慮 的前七個字元FBFBBFFRLR:

  • 首先考慮整個範圍,行0到127.
  • F意味著採取下半部分,保持行0通過63。
  • B意味著採取上半部分,保持行32通過63。
  • F意味著採取下半部分,保持行32通過47。
  • B意味著採取上半部分,保持行40通過47。
  • B保持行44通過47。
  • F保持行44通過45。
  • finalF保留兩者中較低的,row44。

最後三個字元將是L或R; 這些精確指定的一個8列的在飛機上的座位(編號0通過7)。再次進行與上述相同的過程,這次只需要三個步驟。L表示保留下半部分,同時R表示保留上半部分。

例如,僅考慮 的最後 3 個字元FBFBBFFRLR:

  • 首先考慮整個範圍,列0到7.
  • R意味著採取上半部分,保持列4通過7。
  • L意味著採取下半部分,保持列4通過5。
  • finalR保留兩者中的較高者,column5。

因此,解碼FBFBBFFRLR顯示它是row 44, column5的座位。

每個座位也有一個唯一的座位 ID:將行乘以 8,然後新增列。在此示例中,座位具有 ID 44 * 8 + 5 = 357。

以下是其他一些登機牌:

  • BFFFBBFRRR:行70,列7,座位ID 567。
  • FFFBBBFRRR:行14,列7,座位ID 119。
  • BBFFBBFRLL:行102,列4,座位ID 820。

作為健全性檢查,請檢視您的登機牌清單。登機牌上的最高座位 ID 是多少?

這個謎題說明中有很多資訊!但是,大部分內容都涉及二進位制空間分割槽如何為該特定航空公司工作。

現在,嘗試自己解決難題!請記住,如果您從正確的角度考慮,從登機牌規範到座位 ID 的轉換並不像最初看起來那麼複雜。如果您發現自己在該部分遇到困難,請展開下面的框以檢視有關如何開始的提示。

2020 年第 5 天的提示,第 1 部分顯示隱藏

完成解決方案後,請檢視下一部分以瞭解有關該難題的討論。

第 1 部分:解決方案

既然您已經自己試過了,您可以繼續展開以下塊以檢視解決難題的一種方法:

2020 年第 5 天的解決方案,第 1 部分顯示隱藏

是時候進入拼圖的第二部分了。你能登機嗎?

第 2 部分:拼圖說明

準備好拼圖的第二部分後,展開以下部分:

2020 年第 5 天,第 2 部分顯示隱藏

花點時間研究第二部分的解決方案。

第 2 部分:解決方案

當您準備好將您的解決方案與另一個解決方案進行比較時,請開啟下面的框:

2020 年第 5 天的解決方案,第 2 部分顯示隱藏

恭喜!到目前為止,您已經解決了至少兩個 Advent of Code 難題。幸運的是,還有數百個等著你!

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章