概念练习是用于教授特定(编程)概念的练习。 概念练习所教授的概念构成一份_教学大纲_。 关于如何设计教学大纲的更多信息,请查阅教学大纲文档。
你可以在轨道的根目录下运行以下命令,快速创建一个新的概念练习:
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目录下拥有自己的目录。概念练习目录的名称必须与概念练习的slug属性一致,该属性定义在 config.json 文件中。
概念练习有四类文件:
这些文件会展示给学生,用于帮助讲解练习。
.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 (exemplar implementation)
├── CarsAssemble.cs (stub implementation)
└── CarsAssemblyTests.cs (tests)
对于新练习,我们倾向于采用“乐观合并”的方式,轨道可以在“进行中”的状态下开发练习。 最小的有效状态(能够通过 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标题下。instructions.md中任务标题相匹配的标题下方(例如## 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:该练习 fork 自哪些练习(如果练习是 fork 来的则必需)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
用途: 展示解题思路的代码片段
是否必需: 可选(对于解题思路必需)
该文件包含一个展示该解题思路的小片段。 该片段会显示在练习的 dig deeper 页面上。
其行数必须 <= 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
用途: 展示解题思路的代码片段
是否必需: 可选(对于文章必需)
该文件包含一个展示该文章的小片段。 该片段会显示在练习的 dig deeper 页面上。
其行数必须 <= 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:练习不是故事/主题在没有重大改动地 fork 练习时,尽可能使用原始名称。
每个练习还有一个 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属性来覆盖这一设置。
如果你在 fork 一个已有练习,那么该练习很可能已经有图标了。 如果没有,请在 website-icons 代码仓库中提交 issue。