Ranges represent an interval between two values.
The most common types that support ranges are Int
, Char
, and String
.
They can be used for many things, such as quickly creating a collection, slicing strings, checking if a value is in a range, and iteration.
They are created using the range operator ..
or ...
(inclusive and exclusive, respectively).
1..5 Â # A range containing 1..5
1...5 # A range containing 1...5
The reason for having two range operators is to create ranges that are inclusive or exclusive of the end value, which can be useful when, for example, working with zero-based indexes.
Ranges can also be created using the Range
initializer.
Range.new(1, 5) # A range containing 1, 2, 3, 4, 5
When creating a range in Crystal using the range operators ..
or ...
, and wanting to call a method on the range, you need to wrap the range in parentheses.
This is because the otherwise will the method be called on the 2nd argument of the range operator.
(1..5).size # => 5
1..5.size # => Error: undefined method 'size' for Int32
When wanting to slice a string, you can use the range operator to get a sub-string. That is, by creating a range with the start and end index of the sub-string.
"Hello World"[0..4] # => "Hello"
"Hello World"[6..10] # => "World"
You can also use negative indexes to get the substring from the end of the string.
"Hello World"[-5..-1] # => "World"
"Hello World"[6..-4] # => "Wo"
Ranges do have a set of methods that can be used to work with them. For example, these methods can be used to get the sum of all the values in the range or check if the range includes a value.
Method          | Description                               | Example             |
---|---|---|
sum       |
Returns the sum of all the values in the range              | (1..5).sum # => 15 |
size      |
Returns the size of the range                      | (1..5).size # => 5 |
includes? |
Returns true if the range includes the given value, otherwise false
|
(1..5).includes?(3) # => true |
A range can be endless and beginless.
The endless or beginless range has its start or end value as nil
, but nil
can be omitted when defining the range.
Using beginless and endless ranges is useful when you want to, for example, slice a string from the beginning or to the end.
"Hello World"[0..] # => "Hello World"
"Hello World"[4..] # => "o World"
"Hello World"[..5] # => "Hello"
If not used on a collection, the endless range can cause an endless sequence, if not used with caution.
Chars can be used in ranges and allow you to get an interval of chars between two chars. For example, this can be handy when you want to get the alphabet.
'a'..'z' # A range containing ['a', 'b', 'c', ..., 'z']
Strings can also be used in ranges, allowing one to get an interval of strings between two strings.
But its behavior is slightly different than that of Char
when multiple characters are used in a string range.
Its behavior can become confusing when doing more complex string ranges, so use caution.
("aa".."az") # A range containing ["aa", "ab", "ac", ..., "az"]
As a chess enthusiast, you want to write your version of the game. Yes, there may be plenty of implementations of chess available online already, but yours will be unique!
You start with implementing a basic movement system for the pieces.
The chess game will be played on an eight-square wide and eight-square long board. The squares are identified by a letter and a number.
The game will have to store the ranks of the board. The ranks are the rows of the board, and are numbered from 1 to 8.
The game will also have to store the files on the board. The files are the board's columns and are identified by the letters A to H.
Define the Chess::RANKS
and Chess::FILES
constants that store the range of ranks and files respectively.
Chess::RANKS
# => 1..8
Chess::FILES
# => 'A'..'H'
The game will have to check if a square is valid. A square is valid if the rank and file are within the ranges of the ranks and files.
Define the Chess.valid_square?
method that takes the arguments rank
that holds an int of the rank and file
that holds a char of the file.
The method should return true
if the rank and file are within the range of ranks and files, and return false
otherwise.
Chess.valid_square?(1, 'A')
# => true
The game will have to get the player's nickname. The nickname is the first two characters of the player's first name and the last two characters of the player's last name. The nickname should be capitalized.
Define the Chess.nickname
method that takes the arguments first_name
that holds a string of the player's first name and last_name
that holds a string of the player's last name.
The method should return the nickname of the player as an uppercased string.
Chess.nickname("John", "Doe")
# => "JOOE"
The game will have to create a message for a move to say which player moved to which square. The message should use the player's nickname and the square they moved to. The game must also determine if the move is valid by checking if the file and rank of the square are within the ranges of the files and ranks.
If the move is valid, the message should be: "{nickname} moved to {square}"
If the move is invalid, the message should be: "{nickname} attempted to move to {square}, but that is not a valid square"
Define the Chess.move_message
method that takes the arguments first_name
that holds a string of the player's first_name, last_name
that holds a string of the player's last_name, and square
that holds a string of the square the player moved to.
The method should return the message for the move as a string.
Chess.move_message("John", "Doe", "A1")
# => "JOOE moved to A1"
Sign up to Exercism to learn and master Crystal with 26 concepts, 133 exercises, and real human mentoring, all for free.