プラクティス演習は、学習者がそれまでに学んだ概念を活かしながら、自由な問題を解けるように設計された演習です。
トラックに初めてのプラクティス演習を追加してみませんか? プラクティス演習を追加するドキュメントを確認するか、以下の解説動画をご覧ください👇
トラックのルートディレクトリで次のコマンドを実行すると、新しいプラクティス演習のひな形をすばやく作成できます:
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で解ける演習のように、別の概念を使って解答できる演習もあります。その場合メンテナーは、学習者のトラックでの歩みを考えながら、その演習をアンロックするのに使いたい1つの方法を選びます。たとえば先ほどのループと再帰の例では、この演習をloopsの早い段階の練習に良いと考えるかもしれませんし、再帰を教えるために後回しにしたいと考えるかもしれません。アナライザーを使って、別の方法を試すよう学習者に促すこともできます。「ループで解けましたね。よかったら、再帰を使った解き方も試してみてください。」各プラクティス演習は、トラックのexercises/practiceディレクトリ内にそれぞれ専用のディレクトリを持ちます。プラクティス演習のディレクトリ名は、config.jsonファイルで定義されている演習のslugプロパティと一致していなければなりません。
プラクティス演習には4種類のファイルがあります:
これらのファイルは、演習の説明を助けるために学習者に提示されます。
.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(実装例)
├── Isogram.cs(スタブ実装)
└── IsogramTests.cs(テスト)
.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.
appendファイルは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.
appendファイルは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: この演習で使われるファイルの場所(演習のディレクトリからの相対パス)(必須)
solution: スタブ実装のファイル(必須)test: テストファイル(必須)example: 実装例のファイル(必須)editor: エディターで読み取り専用として表示される追加ファイル(任意)invalidator: 変更されると解答が古くなるファイル(任意)language_versions 言語のバージョン要件(任意)blurb: この演習の短い説明です。長さは350以下でなければなりません。Markdownはサポートされて_いません_(必須)source: この演習の基になった出典(任意)source_url: この演習の基になった出典のURL(任意)test_runner: この演習の解答をテストランナーでテストするかどうかを示します。指定しない場合のデフォルトはtrueです。(任意)representer: リプレゼンターがこのファイルをどう処理するかに関するメタ情報(任意)
version: 演習で使用するリプレゼンターのバージョンを表す整数(親キーが存在する場合は必須)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で、小文字のケバブケースの文字列です。slugはトラック内のすべてのアプローチslugの中で一意でなければなりません。長さは255以下でなければなりません。title: アプローチのタイトル。長さは255以下でなければなりません。blurb: このアプローチの短い説明です。長さは350以下でなければなりません。Markdownはサポートされて_いません_(必須)authors: 演習のアプローチの作者のGitHubユーザー名(必須)
contributors: 演習のアプローチのコントリビューターのGitHubユーザー名(任意)
tags: 解答がアプローチに紐づけられる条件を指定します。(任意)
all: 解答にすべて含まれていなければならないタグの配列(任意。ただしanyに要素がない場合を除く)any: 解答に少なくとも1つ含まれていなければならないタグの配列(任意。ただし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で、小文字のケバブケースの文字列です。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"キーで指定しなければなりません。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を作成してください。