实践练习是让学生解决任意题目的练习,目的在于让他们运用迄今为止学到的概念。
想为某个赛道添加第一个实践练习?查看添加实践练习的文档,或观看我们的讲解视频 👇
只需在赛道的根目录下运行以下命令,就能快速搭建一个新实践练习的框架:
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 解决的练习),维护者应考虑学生在该赛道中的学习历程,选择一种他们希望用来解锁该练习的方法。例如,在上面循环/递归的例子中,他们可能认为这个练习是早期练习 loops 的好机会,也可能想把它留到后面用来教递归。他们还可以借助分析器,提示学生尝试另一种方法:“用 loops 解决得很棒。你也可以试试用 Recursion 来解决。”每个实践练习在赛道的 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:展示该文章的代码片段(可选)与具体编程语言相关的文件,例如实现文件和测试文件。这些文件的名称因赛道而异。
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 文件加以扩充,例如当练习实现的测试没有被现有说明覆盖时。
如果某个赛道不希望 Bob 支持非 ASCII 消息,可能会添加以下内容:
# Introduction append
## Note
As part of his teenage rebellion, Bob has decided to only communicate using ASCII.
追加文件应以 H1 标题开头。 该标题不会显示出来,但仍应保留。 H1 标题后面通常跟一个 H2 标题,用来把通用内容与该赛道特有的内容区分开。
.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 标题,用来把通用内容与该赛道特有的内容区分开。
.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:任何练习特有的非标准数据。可用于按练习自定义赛道工具的行为(可选)如果某人既是作者_又_是贡献者,只把该人列为作者。
{
"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是一个自由格式的字符串,赛道可以随意使用和解释。用途: 包含已实现测试的信息。
是否必需: 可选
如果练习在 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 在赛道内以及所有赛道之间都必须唯一,且永远不得更改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" 键中指定。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。