練習題


練習題是為了讓學生解決任意問題而設計的練習,目標是讓他們運用目前為止學到的概念。

想在 track 中加入你的第一個練習題嗎?請參閱新增練習題的文件,或觀看我們的逐步解說影片 👇

Note

你可以從 track 的根目錄執行以下指令,快速建立新的練習題骨架:

bin/fetch-configlet
bin/configlet create --practice-exercise <slug>

更多資訊請參閱 configlet create 文件

中繼資料

練習題的中繼資料定義在 config.json 檔的 exercises.practice 鍵中。這些中繼資料定義了練習的 UUID、slug 等等。

範例

{
  "exercises": {
    "practice": [
      {
        "uuid": "8ba15933-29a2-49b1-a9ce-70474bad3007",
        "slug": "leap",
        "name": "Leap",
        "practices": ["if-statements", "numbers", "operator-precedence"],
        "prerequisites": ["if-statements", "numbers"],
        "difficulty": 1
      }
    ]
  }
}

practices

practices 鍵應列出這個練習題能讓學生實際練習的概念的 slug。

  • 這些會在介面上顯示為「在這些練習中練習這個概念:TwoFer、Leap 等」
  • 試著為每個概念挑選 3 到 8 個練習。
  • 試著至少挑選兩個能讓人練習某個概念基礎的練習。
  • 有些概念非常常見(例如 strings)。這種情況下,我們建議挑選幾個好練習,讓人們以有趣的方式思考這些概念。例如需要處理 UTF-8、字串串接、字元列舉等的練習,都是很好的例子。

prerequisites

prerequisites 鍵列出學生必須先完成哪些概念,才能開始這個練習題。

  • 這些會在介面上顯示為「學習字串以解鎖 TwoFer」
  • 它應包含學生至少能用一種道地寫法完成該練習所需的全部概念。例如 Ruby 的 TwoFer 練習,先備條件可能包含 strings、optional-params、implicit-return。
  • 對於能用其他概念完成的練習(例如可用 loops 或 recursion 解決的練習),維護者應考量學生在 track 中的學習歷程,選擇他們希望用來解鎖該練習的做法。以迴圈/遞迴的例子來說,他們可能認為這個練習適合早期練習 loops,或者想把它留到後面用來教遞迴。他們也可以利用分析器提示學生嘗試另一種做法:「用迴圈解決很棒。你也可以試試用遞迴來解。」

檔案

每個練習題在 track 的 exercises/practice 目錄下都有各自的目錄。練習題目錄的名稱必須與練習題的 slug 屬性一致,該屬性定義於 config.json 檔。

練習題有四種類型的檔案:

文件檔案

這些檔案會呈現給學生,協助說明該練習。

  • .docs/introduction.md:介紹練習的情境與背景(選填)
  • .docs/introduction.append.md:附加在現有簡介之後的補充簡介文字(選填)
  • .docs/instructions.md:提供練習的指示(必填)
  • .docs/instructions.append.md:附加在現有指示之後的補充文字(選填)
  • .docs/hints.md:提供提示,幫助學生在練習中突破卡關(選填)

中繼資料檔案

這些檔案_不會_呈現給學生,而是用來定義練習的中繼資料。

  • .meta/config.json:包含練習的中繼資訊(必填)
  • .meta/design.md:描述練習的設計(選填)
  • .meta/tests.toml:包含已實作哪些測試的資訊(選填)

做法檔案

這些檔案描述練習的各種做法。

  • .approaches/introduction.md:介紹該練習最常見的做法(選填)
  • .approaches/config.json:做法的中繼資料(選填)
  • .approaches/<approach-slug>/content.md:做法的說明(選填)
  • .approaches/<approach-slug>/snippet.txt:展示該做法的程式碼片段(選填)

文章檔案

這些檔案描述練習的各種文章。

  • .articles/config.json:文章的中繼資料(選填)
  • .articles/<article-slug>/content.md:文章的說明(選填)
  • .articles/<article-slug>/snippet.md:展示該文章的片段(選填)

練習檔案

特定程式語言的檔案,例如實作與測試檔案。這些檔案的名稱依 track 而異。

  • 測試套件:驗證解法是否正確。
  • 骨架實作:提供學生的起始點。
  • 範例實作:提供可通過所有測試的範例實作。
  • 額外檔案:確保測試能執行。

範例

exercises
└── practice
    └── isogram
        ├── .approaches
        |   ├── for-loop
        |   |   ├── content.md
        |   |   └── snippet.txt
        |   ├── config.json
        |   └── introduction.md
        ├── .articles
        |   ├── performance
        |   |   ├── content.md
        |   |   └── snippet.md
        |   └── config.json
        ├── .docs
        |   ├── introduction.md
        |   ├── instructions.md
        |   └── hints.md
        ├── .meta
        |   ├── config.json
        |   ├── design.md
        |   ├── tests.toml
        |   └── Example.cs (example implementation)
        ├── Isogram.cs (stub implementation)
        └── IsogramTests.cs (tests)

檔案:.docs/introduction.md

用途: 向學生介紹練習的情境與背景。

是否必填: 若該練習實作了帶有 introduction.md 檔案的 Problem Specifications 練習,則為必填

如果該練習實作了 Problem Specifications 練習,這個檔案的內容應與該 Problem Specifications 練習的 introduction.md 檔案一致。configlet 可以自動同步此檔案的內容。

如果該練習_不是_以 Problem Specifications 練習為基礎,請考慮以下幾點:

我們非常重視讓 Exercism 的內容對每個人都安全,因此在判斷故事是否合適時,往往寧可過於謹慎。我們在合併內容時會小心把關,但也理解很難察覺什麼可能被視為有問題,所以我們一律假設你是出於善意,並會盡量在審查時以不對立的方式找出任何問題。如果你想和我們討論某個故事,請標註 @exercism/leadership,我們會一起看看。以下是一些指引:

  • 盡量確保故事友善且每個人都能理解。如果故事包含圈內笑話或地區俚語,試著想出替代的說法。
  • 盡量撰寫對每個人都包容的範例。例如,考慮使用來自不同文化的名字與多元性別。
  • 問問自己,你是否認識任何會因這個故事感到被冒犯的人。如果有,請考慮修改以避免這種情況。

範例

# Introduction

Bob is a lackadaisical teenager. In conversation, his responses are very limited.

檔案:.docs/introduction.append.md

用途: 附加在現有簡介之後的補充簡介文字。

是否必填: 選填

在某些(罕見的)情況下,你可能想補充練習的 introduction.md 檔案,例如當練習實作了現有指示未涵蓋的測試時。

如果某個 track 不希望 Bob 支援非 ASCII 訊息,可能會加入以下內容:

# Introduction append

## Note

As part of his teenage rebellion, Bob has decided to only communicate using ASCII.

附加檔案應以 H1 標頭開頭。 這個標頭不會顯示出來,但仍應存在。 H1 標頭後面通常會接著 H2 標頭,這有助於區隔通用內容與 track 專屬內容。

檔案:.docs/instructions.md

用途: 提供練習的指示。

是否必填: 必填

如果該練習實作了 Problem Specifications 練習,這個檔案的內容應與該 Problem Specifications 練習的 instructions.md 檔案一致(如果沒有 instructions.md 檔案,則對應 description.md 檔案)。configlet 可以自動同步此檔案的內容。

如果該練習_不是_以 Problem Specifications 練習為基礎,請考慮以下幾點:

我們非常重視讓 Exercism 的內容對每個人都安全,因此在判斷故事是否合適時,往往寧可過於謹慎。我們在合併內容時會小心把關,但也理解很難察覺什麼可能被視為有問題,所以我們一律假設你是出於善意,並會盡量在審查時以不對立的方式找出任何問題。如果你想和我們討論某個故事,請標註 @exercism/leadership,我們會一起看看。以下是一些指引:

  • 盡量確保故事友善且每個人都能理解。如果故事包含圈內笑話或地區俚語,試著想出替代的說法。
  • 盡量撰寫對每個人都包容的範例。例如,考慮使用來自不同文化的名字與多元性別。
  • 問問自己,你是否認識任何會因這個故事感到被冒犯的人。如果有,請考慮修改以避免這種情況。

範例

# Instructions

Bob answers 'Sure.' if you ask him a question, such as "How are you?".

He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals).

He answers 'Calm down, I know what I'm doing!' if you yell a question at him.

He says 'Fine. Be that way!' if you address him without actually saying anything.

He answers 'Whatever.' to anything else.

檔案:.docs/instructions.append.md

用途: 附加在現有指示之後的補充指示文字。

是否必填: 選填

在某些(罕見的)情況下,你可能想補充練習的 instructions.md 檔案,例如當練習實作了現有指示未涵蓋的測試時。

# Instructions append

## Note

Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English.

附加檔案應以 H1 標頭開頭。 這個標頭不會顯示出來,但仍應存在。 H1 標頭後面通常會接著 H2 標頭,這有助於區隔通用內容與 track 專屬內容。

檔案:.docs/hints.md

用途: 提供提示,幫助學生在練習中突破卡關。

是否必填: 選填

  • 如果學生卡住了,我們會讓他們點擊按鈕索取提示,這會顯示檔案中相關的部分。
  • 提示應以項目符號列在標題下方。
  • 提示應足以讓幾乎所有學生都能繼續下去。
  • 提示不應直接寫出解法,而應指向說明解法的資源(例如連結到要使用的函式的文件)。
  • 提示可以用程式碼範例來解釋概念,但不能用來勾勒解法。例如在陣列練習中,可以展示某個陣列函式如何運作的片段,但不能以可直接複製貼上到解法的方式呈現。
  • 提示必須以 Markdown 清單的形式出現在 ## General 標題下方。
  • 如果沒有提示,應省略該標題。

查看提示不會是「建議」的做法,除非學生沒有它就無法繼續,否則我們會(溫和地)勸退。因此,值得考慮的是,閱讀提示的學生可能會有點困惑、不知所措,甚至感到挫折。

範例

## General

- There are many [built-in methods][integers] to simplify working with integers.

[integers]: https://ruby-doc.org/core-2.7.0/Integer.html

檔案:.meta/design.md

用途: 描述練習的設計。

是否必填: 選填

這個檔案包含練習設計的相關資訊,例如它的目標、教學目標、不該教的內容等等。

它的存在是為了讓未來的維護者或貢獻者了解某個練習的範圍與限制,避免練習隨時間自然變得更複雜。

範例

# Design

## Goal

The goal of this exercise is help students practice how to work with strings.

## Notes

This exercise does not contain any error handling tests.

檔案:.meta/config.json

用途: 包含練習的中繼資訊。

是否必填: 必填

這個檔案包含練習的中繼資訊:

  • authors:練習作者(們)的 GitHub 使用者名稱(選填)
    • 如果審查者的審查大幅改變了練習(大到感覺像是「你們一起完成的」),也應列入
  • contributors:練習貢獻者(們)的 GitHub 使用者名稱(選填)
    • 如果審查者的審查具有意義、可付諸行動或已付諸行動,也應列入。
  • files:此練習所用檔案的位置,相對於練習目錄(必填)
    • solution:骨架實作檔案(必填)
    • test:測試檔案(必填)
    • example:範例實作檔案(必填)
    • editor:在編輯器中以唯讀方式顯示的額外檔案(選填)
    • invalidator:變更後會使解法變成過期的檔案(選填)
  • language_versions:程式語言版本需求(選填)
  • blurb:這個練習的簡短描述。長度必須 <= 350。_不_支援 Markdown(必填)
  • source:這個練習所依據的來源(選填)
  • source_url:這個練習所依據來源的 URL(選填)
  • test_runner:指出此練習的解法是否應在測試執行器中測試。未指定時預設為 true。(選填)
  • representer:與 representer 如何處理此檔案相關的中繼資訊(選填)
    • version:用於此練習的 representer 版本,為一個整數(若上層鍵存在則必填)
  • icon:圖示的 slug(參見完整圖示清單)。若未指定,將使用練習的 slug(選填)
  • custom:任何練習專屬的非標準資料。可用來依練習自訂 track 工具的行為(選填)

如果某人同時是作者_和_貢獻者,只將該人列為作者。

範例

{
  "authors": ["FSharpForever"],
  "files": {
    "solution": ["Bob.fs"],
    "test": ["BobTests.fs"],
    "example": [".meta/Example.fs"]
  },
  "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited"
}

請注意:

  • 作者與貢獻者的順序並不重要,也不具任何意義。
  • language_versions 是自由格式的字串,各 track 可自行使用與解讀。

檔案:.meta/tests.toml

用途: 包含已實作哪些測試的資訊。

是否必填: 選填

如果該練習在 problem-specifications 儲存庫的 canonical-data.json 檔案中定義了任何測試,這個檔案會包含哪些測試正在被實作的資訊。

它的存在是為了協助維護者掌握哪些測試已實作,並(選擇性地)記錄某個測試為何沒有實作。 它也可以用來偵測尚未實作的測試。

configlet 工具會透過 configlet sync 指令,更新/同步此檔案與 problem-specifications 儲存庫中的資料。 同步時,configlet 會針對每個尚未實作的測試,詢問是否要納入該測試。

範例

# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[3e5c30a8-87e2-4845-a815-a49671ade970]
description = "empty strand"

[a0ea42a6-06d9-4ac6-828c-7ccaccf98fec]
description = "can count one nucleotide in single-character input"

[eca0d565-ed8c-43e7-9033-6cefbf5115b5]
description = "strand with repeated nucleotide"

[40a45eac-c83f-4740-901a-20b22d15a39f]
description = "strand with multiple nucleotides"

[b4c47851-ee9e-4b0a-be70-a86e343bd851]
description = "strand with invalid nucleotides"
include = false
comment = "error handling omitted on purpose"

檔案:.approaches/introduction.md

用途: 介紹該練習最常見的做法

是否必填: 選填

這個檔案描述該練習最常見的做法。 關於這個檔案應該包含什麼,請參閱文件。

範例

# Introduction

The key to this exercise is to deal with C# strings being immutable, which means that a `string`'s value cannot be changed.
Therefore, to reverse a string you'll need to create a _new_ `string`.

## Using LINQ

```csharp
public static string Reverse(string input)
{
    return new string(input.Reverse().ToArray());
}
```

For more information, check the [LINQ approach][approach-linq].

## Which approach to use?

If readability is your primary concern (and it usually should be), the LINQ-based approach is hard to beat.

檔案:.approaches/config.json

用途: 做法的中繼資料

是否必填: 選填(當有做法簡介或做法存在時為必填)

這個檔案包含練習各做法的中繼資訊:

  • introduction:練習做法簡介作者(們)的 GitHub 使用者名稱(選填)

    • authors:練習做法簡介作者(們)的 GitHub 使用者名稱(必填)
      • 如果審查者的審查大幅改變了練習做法簡介(大到感覺像是「你們一起完成的」),也應列入
    • contributors:練習做法簡介貢獻者(們)的 GitHub 使用者名稱(選填)
      • 如果審查者的審查具有意義、可付諸行動或已付諸行動,也應列入。
  • approaches:列出詳細做法的陣列(選填)

    • uuid:唯一識別該做法的 V4 UUID。此 UUID 在 track 內以及所有 track 之間都必須唯一,且永遠不得變更
    • slug:做法的 slug,為小寫的 kebab-case 字串。此 slug 在 track 內所有做法 slug 中必須唯一。長度必須 <= 255。
    • title:做法的標題。長度必須 <= 255。
    • blurb:這個做法的簡短描述。長度必須 <= 350。_不_支援 Markdown(必填)
    • authors:練習做法作者(們)的 GitHub 使用者名稱(必填)
      • 如果審查者的審查大幅改變了練習做法(大到感覺像是「你們一起完成的」),也應列入
    • contributors:練習做法貢獻者(們)的 GitHub 使用者名稱(選填)
      • 如果審查者的審查具有意義、可付諸行動或已付諸行動,也應列入。
    • tags:指定提交內容在何種條件下會連結到某個做法。(選填)
      • all:提交內容必須全部具備的標籤陣列(選填;除非 any 沒有任何元素)
      • any:提交內容至少須具備其中一個的標籤陣列(選填;除非 all 沒有任何元素)
      • not:提交內容不得具備其中任何一個的標籤(選填)

範例

{
  "introduction": {
    "authors": ["erikschierboom"]
  },
  "approaches": [
    {
      "uuid": "448fb2b4-18ab-4e55-aa54-ad4ed6d5f7f6",
      "slug": "span",
      "title": "Use Span<T>",
      "blurb": "Use Span<T> to efficiently reverse a string.",
      "authors": ["erikschierboom"]
    }
  ]
}

檔案:.approaches/<approach-slug>/content.md

用途: 做法的詳細說明

是否必填: 選填(做法為必填)

這個檔案包含該做法的詳細說明。 關於這個檔案應該包含什麼,請參閱文件。

範例

# Span

```csharp
Span<char> chars = stackalloc char[input.Length];
for (var i = 0; i < input.Length; i++)
{
    chars[input.Length - 1 - i] = input[i];
}
return new string(chars);
```

This `Span<T>` approach uses a `for` loop.

檔案:.approaches/<approach-slug>/snippet.txt

用途: 展示該做法的程式碼片段

是否必填: 選填(做法為必填)

這個檔案包含一段展示該做法的小片段。 此片段會顯示在練習的深入探索頁面上。

其行數必須 <= 8。

關於這個檔案應該包含什麼,請參閱文件。

範例

Span<char> chars = stackalloc char[input.Length];
for (var i = 0; i < input.Length; i++)
{
    chars[input.Length - 1 - i] = input[i];
}
return new string(chars);

檔案:.article/config.json

用途: 文章的中繼資料

是否必填: 選填(當有文章存在時為必填)

這個檔案包含練習各文章的中繼資訊:

  • articles:列出詳細文章的陣列(選填)
    • uuid:唯一識別該文章的 V4 UUID。此 UUID 在 track 內以及所有 track 之間都必須唯一,且永遠不得變更
    • slug:文章的 slug,為小寫的 kebab-case 字串。此 slug 在 track 內所有文章 slug 中必須唯一。長度必須 <= 255。
    • title:文章的標題。長度必須 <= 255。
    • blurb:這篇文章的簡短描述。長度必須 <= 350。_不_支援 Markdown(必填)
    • authors:練習文章作者(們)的 GitHub 使用者名稱(必填)
      • 如果審查者的審查大幅改變了練習文章(大到感覺像是「你們一起完成的」),也應列入
    • contributors:練習文章貢獻者(們)的 GitHub 使用者名稱(選填)
      • 如果審查者的審查具有意義、可付諸行動或已付諸行動,也應列入。

範例

{
  "articles": [
    {
      "uuid": "6db71962-62d5-448b-a980-c20ae41013ed",
      "slug": "performance",
      "title": "Optimizing performance",
      "blurb": "Explore how to most efficiently reverse a string and what the trade-offs are.",
      "authors": ["erikschierboom"]
    }
  ]
}

檔案:.articles/<article-slug>/content.md

用途: 做法的詳細說明

是否必填: 選填(做法為必填)

這個檔案包含該做法的詳細說明。 關於這個檔案應該包含什麼,請參閱文件。

範例

# Performance

In this document, we'll find out which approach is the most performant one.

## Benchmark results

| Method |      Mean |     Error |    StdDev |    Median | Allocated |
| -----: | --------: | --------: | --------: | --------: | --------: |
|   Linq | 29.133 ns | 0.5865 ns | 0.5486 ns | 28.984 ns |      80 B |
|  Array |  4.806 ns | 0.4999 ns | 1.4739 ns |  3.967 ns |         - |

檔案:.articles/<article-slug>/snippet.txt

用途: 展示該做法的程式碼片段

是否必填: 選填(文章為必填)

這個檔案包含一段展示該文章的小片段。 此片段會顯示在練習的深入探索頁面上。

其行數必須 <= 8。

關於這個檔案應該包含什麼,請參閱文件。

範例

| Method |      Mean | Allocated |
| -----: | --------: | --------: |
|   Linq | 29.133 ns |      80 B |
|  Array |  4.806 ns |         - |

檔案:骨架實作

用途: 提供學生的起始點。

是否必填: 必填

  • 設計骨架時,要讓學生知道該在哪裡加入程式碼。
  • 對於編譯式語言,請考慮讓程式碼可以編譯,因為對初學該語言的學生來說,編譯器訊息有時難以理解。
  • 程式碼應盡可能簡單。
  • 只使用先備條件(以及先備條件的先備條件,依此類推)所涵蓋的語言特性。
  • 學生在瀏覽器中撰寫程式時會看到骨架檔案,使用 CLI 時則會下載到學生的檔案系統。
  • 骨架實作檔案的相對路徑必須在 .meta/config.json 檔案的 "files.solution" 鍵中指定。

範例

using System;

public static class Isogram
{
    public static bool IsIsogram(string word)
    {
        throw new NotImplementedException("You need to implement this function.");
    }
}

檔案:測試

用途: 驗證解法是否正確。

是否必填: 必填

  • 程式碼應盡可能簡單。
  • 只使用該練習先備條件(以及先備條件的先備條件,依此類推)所涵蓋的語言特性。
  • 學生在瀏覽器中撰寫程式時會看到測試檔案,使用 CLI 時則會下載到學生的檔案系統。
  • Exercism 偏好以測試驅動開發來完成練習題。為此有兩種做法:
    • 測試執行器必須依檔案中定義的順序執行測試,且測試套件必須在第一個失敗時中止;或
    • 除了第一個測試之外,其餘測試預設應略過。
  • 測試檔案的相對路徑必須在 .meta/config.json 檔案的 "files.test" 鍵中指定。

範例

using Xunit;

public class IsogramTest
{
    [Fact]
    public void Empty_string() =>
        Assert.True(Isogram.IsIsogram(""));

    [Fact(Skip = "Remove this Skip property to run this test")]
    public void Isogram_with_only_lower_case_characters() =>
        Assert.True(Isogram.IsIsogram("isogram"));

    [Fact(Skip = "Remove this Skip property to run this test")]
    public void Word_with_one_duplicated_character() =>
        Assert.False(Isogram.IsIsogram("eleven"));
}

檔案:範例實作

用途: 提供可通過所有測試的範例實作。

是否必填: 必填

  • 這個實作用來驗證存在一個可通過測試的實作。它刻意_不是_我們希望學生追求的目標程式碼。
  • 每個 track 都應在其持續整合設定中驗證範例實作能通過測試。
  • 導師不會看到這段程式碼。
  • 學生在瀏覽器中撰寫程式時_不會_看到範例檔案,使用 CLI 時也_不會_下載到學生的檔案系統。
  • 範例實作檔案的相對路徑必須在 .meta/config.json 檔案的 "files.example" 鍵中指定。

範例

using System.Linq;

public static class Isogram
{
    public static bool IsIsogram(string word)
    {
        var lowerCaseLetters = word.ToLower().Where(char.IsLetter).ToList();
        return lowerCaseLetters.Distinct().Count() == lowerCaseLetters.Count;
    }
}

檔案:額外檔案

用途: 為了讓測試能執行所需的額外專案、建置或支援檔案。

是否必填: 必填,若預設檔案不足以執行測試

有些語言需要額外檔案才能執行測試。例如 C# 的專案檔與 Node 的 package.json 檔案,少了它們就無法執行測試。

共用檔案

有些檔案並非特定於個別練習,而是適用於_所有_練習。詳情請參閱文件。

呈現方式

使用瀏覽器編輯器與使用 CLI 時,練習文件呈現給學生的方式有所不同。詳情請參閱這份文件。

圖示

每個練習都有隨附的圖示。 預設顯示的圖示是其名稱符合該練習 slug 的圖示。 你可以透過在練習的 .meta/config.json 檔案中指定 icon 屬性來覆寫。

如果你正在實作來自 problem-specifications 中繼資料的練習,該練習可能已經有圖示了。 如果沒有,請在 website-icons 儲存庫中開一個 issue。