範囲は、2つの値の間の区間を表します。
範囲をサポートする最も一般的な型は、Int、Char、Stringです。
コレクションを手早く作ったり、文字列を切り出したり、ある値が範囲に含まれているか調べたり、繰り返し処理をしたりと、さまざまなことに使えます。
範囲は、範囲演算子..または...を使って作ります(それぞれ終端を含むものと含まないものです)。
1..5 # A range containing 1, 2, 3, 4, 5
1...5 # A range containing 1, 2, 3, 4
範囲演算子が2つあるのは、終端の値を含む範囲と含まない範囲を作り分けるためです。これは、たとえば0から始まるインデックスを扱うときに便利です。
範囲は、Rangeの初期化でも作ることができます。
Range.new(1, 5) # A range containing 1, 2, 3, 4, 5
Crystalで範囲演算子..や...を使って範囲を作り、その範囲に対してメソッドを呼び出したいときは、範囲を括弧(())で囲む必要があります。
そうしないと、メソッドが範囲演算子の2番目の引数に対して呼び出されてしまうからです。
(1..5).size # => 5
1..5.size # => Error: undefined method 'size' for Int32
文字列を切り出したいときは、範囲演算子を使って部分文字列を取得できます。 つまり、部分文字列の開始インデックスと終了インデックスで範囲を作ります。
"Hello World"[0..4] # => "Hello"
"Hello World"[6..10] # => "World"
負のインデックスを使うと、文字列の末尾から部分文字列を取得することもできます。
"Hello World"[-5..-1] # => "World"
"Hello World"[6..-4] # => "Wo"
範囲には、操作に使えるメソッドがいくつか用意されています。 たとえば、範囲内のすべての値の合計を求めたり、ある値が範囲に含まれているか調べたりするのに使えます。
| メソッド | 説明 | 例 |
|---|---|---|
sum |
範囲内のすべての値の合計を返します | (1..5).sum # => 15 |
size |
範囲のサイズを返します | (1..5).size # => 5 |
includes? |
指定した値が範囲に含まれていればtrue、そうでなければfalseを返します |
(1..5).includes?(3) # => true |
範囲は、終端のないものや始端のないものにできます。
終端や始端のない範囲では、開始値または終了値がnilになりますが、範囲を定義するときにnilは省略できます。
始端や終端のない範囲は、たとえば文字列を先頭から、または末尾まで切り出したいときに便利です。
"Hello World"[0..] # => "Hello World"
"Hello World"[4..] # => "o World"
"Hello World"[..5] # => "Hello"
コレクションに対して使うのでなければ、注意しないと、終端のない範囲が終わりのないシーケンスを生むことがあります。
Charの範囲Charも範囲に使えて、2つの文字の間の文字の区間を取得できます。
たとえば、アルファベットを得たいときに便利です。
'a'..'z' # A range containing ['a', 'b', 'c', ..., 'z']
Stringの範囲Stringも範囲に使えて、2つの文字列の間の文字列の区間を取得できます。
ただし、文字列の範囲で複数の文字を使うと、その動作はCharの場合とは少し異なります。
より複雑な文字列の範囲を扱うと動作が分かりにくくなることがあるので、注意してください。
("aa".."az") # A range containing ["aa", "ab", "ac", ..., "az"]
チェスのファンなら、自分だけのチェスゲームを書いてみたいですよね。 すでにネット上にはたくさんのチェスの実装が公開されていますが、自分で作るものは世界に一つだけのものです!
まずは、駒を動かすための基本的な仕組みから実装していきます。
チェスは、8マス×8マスの盤の上で行います。 マスは、アルファベット1文字と数字1つで表します。
このゲームでは、盤面のランクを保持する必要があります。 ランクとは盤面の行のことで、1から8までの番号が付いています。
また、盤面のファイルも保持する必要があります。 ファイルとは盤面の列のことで、AからHまでのアルファベットで表します。
それぞれランクとファイルの範囲を保持するChess::RANKSとChess::FILESという定数を定義します。
Chess::RANKS
# => 1..8
Chess::FILES
# => 'A'..'H'
このゲームでは、マスが有効かどうかを確認する必要があります。 マスは、そのランクとファイルが、それぞれランクとファイルの範囲内にあれば有効です。
rankというランクを表す整数と、fileというファイルを表す文字を入力として受け取るChess.valid_square?メソッドを定義します。
このメソッドは、ランクとファイルがそれぞれの範囲内にあればtrueを、そうでなければfalseを返します。
Chess.valid_square?(1, 'A')
# => true
このゲームでは、プレイヤーのニックネームを取得する必要があります。 ニックネームは、プレイヤーの名の最初の2文字と、姓の最後の2文字を組み合わせたものです。 ニックネームは大文字にします。
プレイヤーの名を表す文字列first_nameと、姓を表す文字列last_nameを入力として受け取るChess.nicknameメソッドを定義します。
このメソッドは、プレイヤーのニックネームを大文字の文字列として返します。
Chess.nickname("John", "Doe")
# => "JOOE"
このゲームでは、どのプレイヤーがどのマスに移動したかを伝えるメッセージを作成する必要があります。 メッセージには、プレイヤーのニックネームと、移動先のマスを使います。 また、マスのファイルとランクがファイルとランクの範囲内にあるかどうかを確認して、その移動が有効かどうかも判断する必要があります。
移動が有効な場合、メッセージは"{nickname} moved to {square}"とします。
移動が無効な場合、メッセージは"{nickname} attempted to move to {square}, but that is not a valid square"とします。
プレイヤーの名を表す文字列first_name、姓を表す文字列last_name、そして移動先のマスを表す文字列squareを入力として受け取るChess.move_messageメソッドを定義します。
このメソッドは、その移動のメッセージを文字列として返します。
Chess.move_message("John", "Doe", "A1")
# => "JOOE moved to A1"