概念練習是為了教授特定(程式設計)概念而設計的練習。 概念練習所教授的這些概念構成一門_教學大綱_。 如要進一步了解如何設計教學大綱,請參閱教學大綱文件。
你可以從軌道的根目錄執行下列指令,快速建立一個新的概念練習的骨架:
bin/fetch-configlet
bin/configlet create --concept-exercise <slug>
如要進一步了解,請參閱configlet create 文件
概念練習的中繼資料定義在 config.json 檔的exercises.concept鍵中。中繼資料會定義練習的 UUID、slug 等資訊。
{
"exercises": {
"concept": [
{
"uuid": "93fbc7cf-3a7e-4450-ad22-e30129c36bb9",
"slug": "cars-assemble",
"name": "Cars, Assemble!",
"concepts": ["if-statements", "numbers"],
"prerequisites": ["basics"]
}
]
}
}
每個概念練習在軌道的exercises/concept目錄下都有各自的目錄。概念練習目錄的名稱必須與 config.json 檔中定義的練習slug屬性相符。
一個概念練習有四種類型的檔案:
這些檔案會呈現給學生,用來協助說明這個練習。
.docs/introduction.md:向學生介紹這個練習所教授的概念(必填).docs/instructions.md:提供這個練習的指示(必填).docs/hints.md:提供提示,幫助學生在練習中突破卡關(必填)這些檔案_不會_呈現給學生,而是用來定義練習的中繼資料。
.meta/config.json:包含這個練習的中繼資訊(必填).meta/design.md:說明這個練習的設計(必填)這些檔案說明這個練習的各種解法。
.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:展示這篇文章的片段(選填)特定語言的檔案,例如實作檔與測試檔。這些檔案的名稱依軌道而定。
exercises
└── concept
└── cars-assemble
├── .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
| └── Exemplar.cs(範例實作)
├── CarsAssemble.cs(骨架實作)
└── CarsAssemblyTests.cs(測試)
對於新的練習,我們偏好採用「樂觀合併」的做法,讓軌道能以「進行中」的狀態開發練習。 能通過 configlet 檢查並讓你合併的最小有效狀態如下:
config.json中的有效項目,且status設為wip。.meta/config.json檔.docs/introduction.md.docs/instructions.md.docs/hints.md.docs/introduction.md
用途: 向學生介紹這個練習所教授的概念。
是否必填: 必填
about.md文件。舉例來說,「strings」練習的簡介可能會把字串描述為「一連串的 Unicode 字元」或「一連串的位元組」,告訴使用者如何建立字串,並說明字串有一些方法可以用來操作它。除非學生需要理解更細微的細節才能解出練習,否則這類簡短的說明(再加上一個語法範例)應該就足以讓學生解出練習。
# Introduction
There are two primary ways to assign objects to names in Ruby - using variables or constants. Variables are always written in snake case. A variable can reference different objects over its lifetime. For example, `my_first_variable` can be defined and redefined many times using the `=` operator:
```ruby
my_first_variable = 1
my_first_variable = "Some string"
my_first_variable = SomeComplexObject.new
```
.docs/introduction.md.tpl
用途: 用來產生introduction.md檔的範本。
是否必填: 選填
introduction.md文件會向學生介紹這個練習的概念。每個概念也有它_自己_的introduction.md文件,但這份文件不會在練習情境之外顯示。
如果概念的簡介需要原文照登在練習的簡介中,可以使用introduction.md.tpl檔。這個檔案讓你能透過佔位符來引用概念的簡介:%{concept:<concept-slug>}。
configlet可以從範本檔產生introduction.md檔。產生的檔案會把概念佔位符替換成該概念的introduction內容。
Exercism 網站只知道introduction.md這份文件。使用範本檔時,產生introduction.md是軌道的責任。
軌道可以針對每個練習決定是否使用範本。在某些情況下,原文照登概念的簡介可能不是最好的做法。請永遠選擇能為學生帶來最佳學習體驗的方式。
# Introduction
%{concept:variables}
.docs/instructions.md
用途: 提供這個練習的指示。
是否必填: 必填
這個檔案分成兩個部分。
每個任務都必須符合下列標準:
## 1. Do X、## 2. Do Y)。## 1. Check if an appointment has already passed)。Implement method X(...) that takes an A and returns a Z),我們非常重視讓 Exercism 的內容對所有人都是安全的,因此在判斷故事是否合適時,經常寧願過度謹慎。雖然我們對合併的內容很謹慎,但我們也知道,要察覺什麼內容可能被視為有問題並不容易,所以我們永遠會假設你是出於善意,並且會在審查時盡量以不對立的方式找出任何問題。如果你想和我們一起檢查某個故事,請標註 @exercism/leadership,我們會一起來看看。以下是一些指引:
# Instructions
In this exercise you're going to write some code to help you cook a brilliant lasagna from your favorite cooking book.
## 1. Calculate the remaining oven time in minutes
Define the `Lasagna#remaining_minutes_in_oven` method that takes the actual minutes the lasagna has been in the oven as a parameter and returns how many minutes the lasagna still has to remain in the oven, based on the expected oven time in minutes from the previous task.
```ruby
lasagna = Lasagna.new
lasagna.remaining_minutes_in_oven(30)
# => 10
```
.docs/hints.md
用途: 提供提示,幫助學生在練習中突破卡關。
是否必填: 必填
## General標題下的 Markdown 清單中。instructions.md中任務標題相符的標題下方,以 Markdown 清單呈現(例如## 2. Do Y)。## 2. Check if a book can be borrowed)。Implement the 'canBorrowBook' function to check if a book can be borrowed. The function takes a book as its parameter and returns `true` if the book has not already been borrowed; otherwise, return `false`)。查看提示不會是「建議」的路徑,除非學生沒有它就無法繼續,否則我們會(委婉地)勸阻使用。因此,值得考慮的是,閱讀提示的學生可能會有點困惑、不知所措,甚至感到挫折。
# Hints
## General
- You need to define a [constant][constant] which should contain the [integer][integers] value specified in the recipe.
## 1. Calculate the remaining oven time in minutes
- You need to define a [method][methods] with a single parameter for the actual time so far.
[constants]: https://www.rubyguides.com/2017/07/ruby-constants/
[integers]: https://ruby-doc.org/core-2.7.0/Integer.html
[methods]: https://launchschool.com/books/ruby/read/methods
用途: 說明這個練習的設計。
是否必填: 必填
這個檔案包含練習設計的相關資訊,例如它的目標、教學目標、不教什麼等等。這些資訊可以從練習對應的 GitHub issue 中擷取。
它的存在是為了讓未來的維護者或貢獻者了解練習的範圍與限制,以避免練習隨著時間自然變得越來越複雜。
# Design
## Goal
The goal of this exercise is to teach the student the basics of programming in Ruby.
## Learning objectives
- Know what a variable is.
- Know how to define a variable.
- Know how to update a variable.
## Out of scope
- Memory and performance characteristics.
- Method overloads.
## Concepts
The Concepts this exercise unlocks are:
- `basics`: know what a variable is; know how to define a variable; know how to update a variable.
## Prerequisites
There are no prerequisites.
.meta/config.json
用途: 包含這個練習的中繼資訊。
是否必填: 必填
這個檔案包含練習的中繼資訊:
authors:練習作者的 GitHub 使用者名稱(必填)
contributors:練習貢獻者的 GitHub 使用者名稱(選填)
forked_from:這個練習是從哪些練習分支出來的(如果練習是分支來的則必填)files:這個練習所用檔案的位置,相對於練習目錄(必填)
language_versions:語言版本需求(選填)blurb:這個練習的簡短描述。長度必須 <= 350。_不_支援 Markdown(必填)source:這個練習所根據的來源(選填)source_url:這個練習所根據來源的 URL(選填)representer:與 representer 如何處理這個檔案相關的中繼資訊(選填)
version:要用於這個練習的 representer 版本整數(若父鍵存在則必填)icon:圖示的 slug(請參閱完整圖示清單)。如果未指定,會使用練習的 slug(選填)custom:任何練習專屬的非標準資料。可用來針對個別練習自訂軌道工具的行為(選填)如果某人同時是作者_和_貢獻者,只需將該人列為作者。
{
"authors": ["FSharpForever"],
"files": {
"solution": ["Lasagna.fs"],
"test": ["LasagnaTests.fs"],
"exemplar": [".meta/Exemplar.fs"]
},
"blurb": "Learn the basics of F# by cooking Lucian's Luscious Lasagna"
}
假設使用者FSharpForever為 F# 軌道寫了一個叫做log-levels的練習。PythonProfessor把這個練習改編給 Python 軌道。後來,使用者GladToHelp改進了這個練習。
{
"authors": ["PythonProfessor"],
"contributors": ["GladToHelp"],
"files": {
"solution": ["log_levels.py"],
"test": ["log_levels_test.py"],
"exemplar": [".meta/exemplar.py"],
"editor": ["test_helper.py"]
},
"forked_from": ["fsharp/log-levels"],
"language_versions": ">=3.7",
"blurb": "Learn how to work with strings by processing log lines.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Log_file",
"representer": {
"version": 2
},
"icon": "logs",
"custom": {
"parallel": true
}
}
請注意:
forked_from正確即可。language_versions是自由格式的字串,軌道可以自行決定如何使用與解讀。.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 在軌道內以及所有軌道之間都必須是唯一的,且永遠不能改變slug:解法的 slug,是一律小寫的 kebab-case 字串。這個 slug 在軌道內的所有解法 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 在軌道內以及所有軌道之間都必須是唯一的,且永遠不能改變slug:文章的 slug,是一律小寫的 kebab-case 字串。這個 slug 在軌道內的所有文章 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"鍵中。class Lasagna
def remaining_minutes_in_oven(actual_minutes_in_oven)
raise NotImplementedError, 'Please implement the Lasagna#remaining_minutes_in_oven method'
end
def preparation_time_in_minutes(layers)
raise NotImplementedError, 'Please implement the Lasagna#preparation_time_in_minutes method'
end
end
用途: 驗證解答是否正確。
是否必填: 必填
instructions.md檔中的範例。.meta/config.json檔的"files.test"鍵中。require 'minitest/autorun'
require_relative 'lasagna'
class LasagnaTest < Minitest::Test
def test_remaining_minutes_in_oven
assert_equal 15, Lasagna.new.remaining_minutes_in_oven(25)
end
def test_preparation_time_in_minutes_with_one_layer
assert_equal 2, Lasagna.new.preparation_time_in_minutes(1)
end
def test_preparation_time_in_minutes_with_multiple_layers
assert_equal 8, Lasagna.new.preparation_time_in_minutes(4)
end
end
用途: 提供學生應該努力的目標實作。
是否必填: 必填
.meta/config.json檔的"files.exemplar"鍵中。class Lasagna
EXPECTED_MINUTES_IN_OVEN = 40
PREPARATION_MINUTES_PER_LAYER = 2
def remaining_minutes_in_oven(actual_minutes_in_oven)
EXPECTED_MINUTES_IN_OVEN - actual_minutes_in_oven
end
def preparation_time_in_minutes(layers)
layers * PREPARATION_MINUTES_PER_LAYER
end
end
用途: 確保測試能夠執行。
是否必填: 當預設檔案不足以執行測試時則必填
有些語言需要其他檔案才能執行測試。例如 C# 的專案檔和 Node 的package.json檔,沒有它們就無法執行測試。
有些檔案並非個別練習專屬,而是適用於_所有_練習。如需更多資訊,請參閱文件。
概念練習應該依照它的故事/主題來命名,而_不是_依照它的概念。
好的名稱範例:
Tim from MarketingLucian's Luscious LasagnaCalculator Conundrum不允許的名稱:
Booleans:使用了概念名稱,而不是故事名稱Exercise #1:練習不是故事/主題分支練習時如果沒有大幅變更,請盡可能使用原本的名稱。
每個練習也有一個_slug_,它是依照下列規則將練習名稱正規化後的版本:
[a-z0-9-]+)two-fer而非2-fer)好的 slug 範例:
tim-from-marketinglucians-luscious-lasagnacalculator-conundrum不允許的 slug:
TIM-FROM-MARKETING:未使用小寫(也就是tim-from-marketing)TimFromMarketing:未使用 kebab-case(也就是tim-from-marketing)floating-point-numbers:使用了概念名稱,而不是故事名稱使用瀏覽器內編輯器與使用 CLI 時,練習文件的呈現方式有所不同。如需更多資訊,請參閱這份文件。
每個練習都有一個搭配的圖示。
根據預設,顯示的圖示是名稱與練習 slug 相符的那一個。
你可以藉由在練習的.meta/config.json檔中指定icon屬性來覆寫。
如果你要分支現有的練習,那個練習可能已經有圖示了。 如果沒有,請在 website-icons 儲存庫開一個 issue。