Concept Exercises are exercises designed to teach specific (programming) concepts. The concepts taught by the concept exercises form a syllabus. For more information on how to design a syllabus, check the syllabus documentation.
You can quickly scaffold a new Concept Exercise by running the following commands from the track's root directory:
bin/fetch-configlet
bin/configlet create --concept-exercise <slug>
For more information, check the configlet create
docs
Concept Exercise metadata is defined in the exercises.concept
key in the config.json file. The metadata defines the exercise's UUID, slug and more.
{
"exercises": {
"concept": [
{
"uuid": "93fbc7cf-3a7e-4450-ad22-e30129c36bb9",
"slug": "cars-assemble",
"name": "Cars, Assemble!",
"concepts": ["if-statements", "numbers"],
"prerequisites": ["basics"]
}
]
}
}
Each Concept Exercise has its own directory within the track's exercises/concept
directory. The name of the Concept Exercise directory must match the slug
property of the Concept Exercise, as defined in the config.json file.
A Concept Exercise has four types of files:
These files are presented to the student to help explain the exercise.
.docs/introduction.md
: introduces the concept(s) that the exercise teaches to the student (required).docs/instructions.md
: provides instructions for the exercise (required).docs/hints.md
: provides hints to a student to help them get themselves unstuck in an exercise (required)These files are not presented to the student, but used to define metadata of the exercise.
.meta/config.json
: contains meta information on the exercise (required).meta/design.md
: describes the design of the exercise (required)These files describe approaches for the exercise.
.approaches/introduction.md
: introduction to the most common approaches for the exercise (optional).approaches/config.json
: metadata for the approaches (optional).approaches/<approach-slug>/content.md
: description of the approach (optional).approaches/<approach-slug>/snippet.txt
: snippet showcasing the approach (optional)These files describe articles for the exercise.
.articles/config.json
: metadata for the articles (optional).articles/<article-slug>/content.md
: description of the article (optional).articles/<article-slug>/snippet.md
: snippet showcasing the article (optional)The language-specific files, like the implementation and test files. The names of these files are track-specific.
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)
We favor an "optimistic merging" approach to new exercises, where tracks can develop exercises in a "work in progress" state. The minimal valid state, which will pass configlet and allow you to merge is:
config.json
, with the status
set to wip
..meta/config.json
file.docs/introduction.md
.docs/instructions.md
.docs/hints.md
.docs/introduction.md
Purpose: Introduce the concept(s) that the exercise teaches to the student.
Presence: Required
about.md
document.As an example, the introduction to a "strings" exercise might describe a string as just a "Sequence of Unicode characters" or a "series of bytes", tell the users how to create a string, and explain that a string has methods that can be used to manipulate it. Unless the student needs to understand more nuanced details in order to solve the exercise, this type of brief explanation (along with an example of its syntax) should be sufficient information for the student to solve the exercise.
# 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
Purpose: Template to generate an introduction.md
file from.
Presence: Optional
The introduction.md
document introduces the exercise's concept(s) to the student. Each concept also has its own introduction.md
document, which is not shown outside the context of an exercise.
If the concept's introduction should be included verbatim in the exercise's introduction, an introduction.md.tpl
file can be used. This file allows referring to concept introductions through placeholders: %{concept:<concept-slug>}
.
configlet can generate an introduction.md
file from a template file. The generated file will have the concept placeholders replaced by the concept's introduction
contents.
The Exercism website only knows about the introduction.md
document. It is the track's responsibility to generate the introduction.md
when a template file is used.
Tracks can decide per exercise whether to use a template or not. In some cases, using the concept's introduction verbatim might not be optimal. Always go with what provides the best learning experience to the student.
# Introduction
%{concept:variables}
.docs/instructions.md
Purpose: Provide instructions for the exercise.
Presence: Required
This file is split into two parts.
Each task must conform to the following standard:
## 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
),We place high value on making Exercism's content safe for everyone and so often err on the side of caution in deciding whether stories are appropriate or not. While we are careful about what we merge, we appreciate that it's hard to be aware of what may be seen as problematic, so we'll always assume you're acting in good faith and do our best to catch any issues in review in a non-confrontational way. If you'd like to check a story with us, please mention @exercism/leadership and we'll look at it together. Here are some guiding points:
# 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
Purpose: Provide hints to a student to help them get themselves unstuck in an exercise.
Presence: Required
## General
heading.instructions.md
(e.g. ## 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`
).Viewing hints will not be a "recommended" path and we will (softly) discourage using it unless the student can't progress without it. As such, it's worth considering that the student reading it will be a little confused/overwhelmed and maybe frustrated.
# 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
Purpose: Describe the design of the exercise.
Presence: Required
This file contains information on the exercise's design, which includes things like its goal, its teaching goals, what not to teach, and more. This information can be extracted from the exercise's corresponding GitHub issue.
It exists in order to inform future maintainers or contributors about the scope and limitations of an exercise, to avoid the natural trend towards making exercises more complex over time.
# 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
Purpose: Contains meta information on the exercise.
Presence: Required
This file contains meta information on the exercise:
authors
: The GitHub username(s) of the exercise's author(s) (required)
contributors
: The GitHub username(s) of the exercise's contributor(s) (optional)
forked_from
: Which exercise(s) it was forked from (required if the exercise is forked)files
: The locations of the files used in this exercise, relative to the exercise's directory (required)
solution
: the stub implementation file(s) (required)test
: the test file(s) (required)exemplar
: the exemplar implementation file(s) (required)editor
: other files shown as read-only in the editor (optional)invalidator
: files that when changed, cause a solution to become out-of-date (optional)language_versions
: Language version requirements (optional)blurb
: A short description of this exercise. Its length must be <= 350. Markdown is not supported (required)source
: The source this exercise is based on (optional)source_url
: The URL of the source this exercise is based on (optional)representer
: Meta information related to how the representer processes this file (optional)
version
: An integer for the version of the representer to use for the exercise (required if parent key is present)icon
: The slug of the icon (see the full list of icons). If not specified, the exercise's slug will be used (optional)custom
: Any exercise-specific, non-standard data. Can be used to customize behavior of the track's tooling per exercise (optional)If someone is both an author and a contributor, only list that person as an author.
{
"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"
}
Assume that the user FSharpForever
has written an exercise called log-levels
for the F# track. PythonProfessor
adapts the exercise for the Python track. Later on, the user GladToHelp
improves the exercise.
{
"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
}
}
Note that:
forked_from
is correct.language_versions
is a free-form string that tracks are free to use and interpret as they like..approaches/introduction.md
Purpose: Introduction to the most common approaches for the exercise
Presence: Optional
This file describes the most common approaches for the exercise. Check the documentation for more information on what should go in this file.
# 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
Purpose: Metadata for the approaches
Presence: Optional (required when an approach introduction or approach exists)
This file contains meta information on the exercise's approaches:
introduction
: The GitHub username(s) of the exercise approach introduction's author(s) (optional)
authors
: The GitHub username(s) of the exercise approach introduction's author(s) (required)
contributors
: The GitHub username(s) of the exercise approach introduction's contributor(s) (optional)
approaches
: An array listing the detailed approaches (optional)
uuid
: a V4 UUID that uniquely identifies the approach. The UUID must be unique both within the track as well as across all tracks, and must never changeslug
: the approach's slug, which is a lowercased, kebab-case string. The slug must be unique across all approach slugs within the track. Its length must be <= 255.title
: the approach's title. Its length must be <= 255.blurb
: A short description of this approach. Its length must be <= 350. Markdown is not supported (required)authors
: The GitHub username(s) of the exercise approach's author(s) (required)
contributors
: The GitHub username(s) of the exercise approach's contributor(s) (optional)
tags
: Specify the conditions for when a submission is linked to an approach. (optional)
all
: An array of tags that must all be present on a submission (optional, unless any
has no elements)any
: An array of tags of which at least one must be present on a submission (optional, unless all
has no elements)not
: none of the tags must be present on a submission (optional){
"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
Purpose: Detailed description of the approach
Presence: Optional (required for approaches)
This file contains a detailed description of the approach. Check the documentation for more information on what should go in this file.
# 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
Purpose: Snippet showcasing the approach
Presence: Optional (required for approaches)
This file contains a small snippet that showcases the approach. The snippet is shown on an exercise's dig deeper page.
Its number of lines must be <= 8.
Check the documentation for more information on what should go in this file.
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
Purpose: Metadata for the articles
Presence: Optional (required when an article exists)
This file contains meta information on the exercise's articles:
articles
: An array listing the detailed articles (optional)
uuid
: a V4 UUID that uniquely identifies the article. The UUID must be unique both within the track as well as across all tracks, and must never changeslug
: the article's slug, which is a lowercased, kebab-case string. The slug must be unique across all article slugs within the track. Its length must be <= 255.title
: the article's title. Its length must be <= 255.blurb
: A short description of this article. Its length must be <= 350. Markdown is not supported (required)authors
: The GitHub username(s) of the exercise article's author(s) (required)
contributors
: The GitHub username(s) of the exercise article's contributor(s) (optional)
{
"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
Purpose: Detailed description of the approach
Presence: Optional (required for approaches)
This file contains a detailed description of the approach. Check the documentation for more information on what should go in this file.
# 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
Purpose: Snippet showcasing the approach
Presence: Optional (required for articles)
This file contains a small snippet that showcases the article. The snippet is shown on an exercise's dig deeper page.
Its number of lines must be <= 8.
Check the documentation for more information on what should go in this file.
| Method | Mean | Allocated |
| -----: | --------: | --------: |
| Linq | 29.133 ns | 80 B |
| Array | 4.806 ns | - |
Purpose: Provide a starting point for students.
Presence: Required
.meta/config.json
file's "files.solution"
key.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
Purpose: Verify a solution's correctness.
Presence: Required
instructions.md
file..meta/config.json
file's "files.test"
key.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
Purpose: Provide the target implementation that a student should aim for.
Presence: Required
.meta/config.json
file's "files.exemplar"
key.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
Purpose: Ensure that the tests can run.
Presence: Required if default files are not enough to run the tests
Some languages require additional files for the tests to run. Example of these are C#'s project files and Node's package.json
files, without which it will not be possible to run the tests.
Some files are not specific to individual exercises, but are instead applicable to all exercises. Check the documentation for more information.
Concept Exercises should be named after their story/theme, not after their concept(s).
Good examples of names:
Tim from Marketing
Lucian's Luscious Lasagna
Calculator Conundrum
Disallowed names:
Booleans
: uses a concept name, not a story nameExercise #1
: an exercise is not a story/themeWhen forking an exercise without major changes, use the original name when possible.
Each exercise also has a slug, which is normalized version of the exercise name using the following rules:
[a-z0-9-]+
)two-fer
over 2-fer
)Good examples of slugs:
tim-from-marketing
lucians-luscious-lasagna
calculator-conundrum
Disallowed slugs:
TIM-FROM-MARKETING
: does not use lowercase (i.e. tim-from-marketing
)TimFromMarketing
: does not use kebab-case (i.e. tim-from-marketing
)floating-point-numbers
: uses a concept name, not a story nameThere is a difference in how exercise documentation is presented to the student when using the in-browser editor versus using the CLI. See this document for more information.
Each exercise has an accompanying icon.
By default, the displayed icon is the one which name matches the exercise's slug.
One can override this by specifying the icon
property in the exercise's .meta/config.json
file.
If you're forking an existing exercise, there probably already is an icon for that exercise. If not, please open an issue in the website-icons repository.