rs.nkmk.me

Rustで範囲を表すRange(..)の種類と使い方

Posted: | Tags: Rust

Rustでは、..で範囲を表す構造体Rangeを生成できる。スライスの範囲を指定したり、イテレータとしてforループで使ったり、連番のベクタVecに変換したりできる。

範囲を表す構造体には、他にもRangeInclusive..=)などがある。

Rangeなどの範囲を表す構造体まとめ(スライスの例)

範囲を表す構造体は以下の6種類。

スライスの範囲として指定した場合の結果を以下に示す。

=がつくとendが含まれ、startを省略すると先頭から、endを省略すると末尾までが範囲になる。

let a = [0, 10, 20, 30, 40];

assert_eq!(&a[1..3],  [   10, 20        ]); // Range
assert_eq!(&a[1..=3], [   10, 20, 30    ]); // RangeInclusive
assert_eq!(&a[..3],   [0, 10, 20        ]); // RangeTo
assert_eq!(&a[..=3],  [0, 10, 20, 30    ]); // RangeToInclusive
assert_eq!(&a[1..],   [   10, 20, 30, 40]); // RangeFrom
assert_eq!(&a[..],    [0, 10, 20, 30, 40]); // RangeFull
source: range.rs

なお、範囲外を指定すると実行時にパニックになる。

// let x = &a[1..100];
// thread 'main' panicked at 'range end index 100 out of range for slice of length 5'
source: range.rs

getメソッドを使うと結果をOptionで取得できる。範囲外だとNoneが返される。

assert_eq!(a.get(1..3).unwrap(), &a[1..3]);
assert_eq!(a.get(1..100), None);
source: range.rs

Rangeなどをイテレータとして使う(forループ、Vecに変換)

RangeなどにはIteratorトレイトやDoubleEndedIteratorトレイトが実装され、イテレータとして使えるものがある。

  • Range, RangeInclusive
    • Iterator, DoubleEndedIteratorが実装されている
  • RangeFrom
    • Iteratorのみが実装されている
  • RangeTo, RangeToInclusive, RangeFull
    • どちらも実装されていない

Range, RangeInclusive

RangeRangeInclusiveにはIteratorトレイトが実装されている。forループで使ったり、collectでベクタVecに変換したりできる。

for i in 1..4 {
    println!("{i}");
}
// 1
// 2
// 3

let v: Vec<i32> = (1..4).collect();
assert_eq!(v, [1, 2, 3]);
source: range.rs

mapと組み合わせる例。

let v: Vec<i32> = (1..4).map(|x| x * 10).collect();
assert_eq!(v, [10, 20, 30]);
source: range.rs

ステップを指定したり降順(逆順)にする例は次の節でまとめて示す。

RangeFrom

RangeFromにもIteratorトレイトが実装されているが、終点を持たないので、breakなしのforループで使うと無限ループになってしまう。要注意。

また、そのままcollectVecに変換すると実行時にパニックになる。Clippyを使っていれば拒絶(deny)される。

// let v: Vec<i32> = (1..).collect();
// thread 'main' panicked at 'capacity overflow'
source: range.rs

zipで終点(個数)が決まっている相手と組み合わせると変換可能。enumerateの開始値を変更するような処理ができる。

let a = ['a', 'b', 'c'];
let v: Vec<(i32, char)> = (10..).zip(a).collect();
assert_eq!(v, [(10, 'a'), (11, 'b'), (12, 'c')]);
source: range.rs

RangeTo, RangeToInclusive, RangeFull

RangeToRangeToInclusive, RangeFullは始点が定められないので、Iteratorトレイトは実装されていない(できない)。イテレータとしては使えない。

// for i in ..4 {}
// error[E0277]: `std::ops::RangeTo<{integer}>` is not an iterator

// let v: Vec<i32> = (..4).collect();
// error[E0599]: `std::ops::RangeTo<{integer}>` is not an iterator
source: range.rs

Range, RangeInclusiveから連番のベクタを生成

ステップ: step_by

step_byでステップを指定できる。

let v: Vec<i32> = (1..10).step_by(2).collect();
assert_eq!(v, [1, 3, 5, 7, 9]);
source: range.rs

降順(逆順): rev

RangeRangeInclusiveにはDoubleEndedIteratorトレイトも実装されているので、revで反転できる。

let v: Vec<i32> = (1..4).rev().collect();
assert_eq!(v, [3, 2, 1]);
source: range.rs

step_byとrevの組み合わせ

step_byrevを組み合わせる例は以下の通り。当然ながら、順番によって結果が変わる。

let v: Vec<i32> = (1..10).step_by(3).rev().collect();
assert_eq!(v, [7, 4, 1]);

let v: Vec<i32> = (1..10).rev().step_by(3).collect();
assert_eq!(v, [9, 6, 3]);

// let v: Vec<i32> = (1..=10).step_by(3).rev().collect();
// error[E0277]: the trait bound `std::ops::RangeInclusive<i32>: std::iter::ExactSizeIterator` is not satisfied

let v: Vec<i32> = (1..=10).rev().step_by(3).collect();
assert_eq!(v, [10, 7, 4, 1]);
source: range.rs

RangeInclusivestart..=end)はstep_by().rev()の順番だとエラーになるので注意(バージョン1.66時点)。start..=endではなくstart..=(end+1)を使えばよい。

エラーメッセージにあるように、RangeInclusiveExactSizeIteratorトレイトを実装していないのが原因。

空のRange, RangeInclusive

Rangeにおいてstart >= endRangeInclusiveにおいてstart > endだと、空になる。collectでベクタVecに変換しても空。

let start = 5;
let end = 1;
let r = start..end;

assert!(r.is_empty());

let v: Vec<i32> = r.collect();
assert!(v.is_empty());
source: range.rs

降順にしたい場合は上述のrevを使う。

なお、空のRange, RangeInclusiveをスライスの範囲として指定すると実行時にパニックになる。getメソッドだとNoneが返される。

let start: usize = 5;
let end: usize = 1;
let r = start..end;

let a = [0, 10, 20, 30, 40];

// let x = &a[r];
// thread 'main' panicked at 'slice index starts at 5 but ends at 1'

assert_eq!(a.get(r), None);
source: range.rs

関連カテゴリー