練習題是為了讓學生解決任意問題而設計的練習,目標是讓他們運用目前為止學到的概念。
想在 track 中加入你的第一個練習題嗎?請參閱新增練習題的文件,或觀看我們的逐步解說影片 👇
你可以從 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
}
]
}
}
practicespractices 鍵應列出這個練習題能讓學生實際練習的概念的 slug。
strings)。這種情況下,我們建議挑選幾個好練習,讓人們以有趣的方式思考這些概念。例如需要處理 UTF-8、字串串接、字元列舉等的練習,都是很好的例子。prerequisitesprerequisites 鍵列出學生必須先完成哪些概念,才能開始這個練習題。
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
用途: 提供提示,幫助學生在練習中突破卡關。
是否必填: 選填
## 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
用途: 描述練習的設計。
是否必填: 選填
這個檔案包含練習設計的相關資訊,例如它的目標、教學目標、不該教的內容等等。
它的存在是為了讓未來的維護者或貢獻者了解某個練習的範圍與限制,避免練習隨時間自然變得更複雜。
# 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:此練習所用檔案的位置,相對於練習目錄(必填)
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 可自行使用與解讀。用途: 包含已實作哪些測試的資訊。
是否必填: 選填
如果該練習在 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 | - |
用途: 提供學生的起始點。
是否必填: 必填
.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.");
}
}
用途: 驗證解法是否正確。
是否必填: 必填
.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"));
}
用途: 提供可通過所有測試的範例實作。
是否必填: 必填
.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。