-
Notifications
You must be signed in to change notification settings - Fork 3
2020 Update
AtCoder の2020年言語アップデート以降で利用できるようになる Rust のバージョンとクレートの一覧です。
- versionは1.42.0
-
editionは
2018
-
targetは
x86_64-unknown-linux-gnu
- 直のrustcではなくCargoによるビルド
- コマンドは
cargo build --release --offline --quiet
(Language Test 202001) - ワークスペースは
/imojudge/rust
-
Cargo.toml
にはprofile
の設定はされていない -
Cargo.toml
のdependencies
: 1 2 -
Cargo.lock
: 1 2
- コマンドは
情報を割り出すのには各コンテストの「コードテスト」("Custom Test")を使うといいです。
ただ2KiB (一時期は1KiBだった)の出力の制限があります。
atcoder-rust-baseを用意するにあたってはこのようなツールを作ってファイルをcat
しました。
bash-on-atcoder (Crates.ioには最近上げた)
See also:
https://github.com/rust-lang-ja/atcoder-rust-resources/wiki/Since-1.15.1#以上の情報を割り出す方法
最終的には次のクレートが導入される予定です。
-
num
- num-bigint
- num-complex
- num-integer
- num-iter
- num-rational
- num-traits
- num-derive
- ndarray
-
nalgebra
- alga
- libm
-
rand
- getrandom
- rand_chacha
- rand_core
- rand_hc
- rand_pcg
- rand_distr
- petgraph
- indexmap
- regex
- lazy_static
- ordered-float
- ascii
- permutohedron
- superslice
- itertools
- itertools-num
- maplit
- either
- im-rc
- fixedbitset
- bitset-fixed
- proconio
- text_io
- whiteread
- rustc-hash
- smallvec
数値型に対するサポートを充実させるクレートです。
v0.2.1
- num-bigint v0.2.6
- num-complex v0.2.4
- num-integer v0.1.42
- num-iter v0.1.40
- num-rational v0.2.4
- num-traits v0.2.11
num
それ自体は上にあげた6つのサブクレートをまとめて再公開しているクレートです。今現在 num
上で実装されている関数や構造体はありません。 例えば多倍長整数は実際には num_bigint
クレート内で実装されていますが、直接このクレートを依存関係に追加しなくても num::bigint
としてアクセスすることができるようになっています。さらによく使われる一部のアイテム、例えば num_bigint::BigInt
などは num
直下でも再公開されており、 num::BigInt
として使うことができます。
各サブクレートを簡単に紹介します。
-
num-traits
組み込み数値型を統一的に扱えるトレイトを提供します。
順番(アルファベット順)を前後してこちらから紹介します。 Rustの整数型や浮動小数点数型には多くの同名のメソッド(例えば
{min, max}_value
,{leading, trailing}_zeros
,pow
など)が用意されていますが、それらをトレイトにまとめる抽象化はなされていません。 また、数値型の間をキャストするのに使う演算子as
をオーバーライドすることもできません。 これらの抽象化を提供することで、多くの数値型に対してジェネリックに使える関数などを定義しやすくするのがnum-traits
です。num-traits
はPrimInt
,Float
をはじめとした多数のトレイトを提供します。 また、個別に指定すると面倒になりがちな複数のトレイトをまとめたトレイト(例えば+-*/%
をすべて実装していることを表すNumOps
など)を提供します。また数値型の抽象化の他にも
One
とMul
でべき乗を計算するpow
やFloat::integer_decode
といった関数が新たに定義, 実装されています。 これらも地味ながら有用です。num-traits
による抽象化は良くできていて、競技プログラミングのライブラリ整備においても有用です。 現状AtCoderの他においては使えませんが、必要なものに絞れば各トレイトの模倣は簡単です。 例えば上記のNumOps
の定義はこれなのでこれを真似ればT: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + ..
とする必要はなくなります。 ボイラープレートが嵩むなら、Rustのプログラミング全般に言えますがmacro_rules!
が有用です。 AtCoder以外のコンテストに参加する方もライブラリを整備するときに参考にしてみてはいかかでしょうか。あるいはまずはお試しで
num-traits
を活用してライブラリを組むという手もあります。 クレート依存を絶ちたくなった場合、例としてPrimInt
はこのようにすれば簡易的なものが手に入ります。use std::ops::{Add, Div, Mul, Rem, Sub}; // 必要な分だけ pub trait PrimInt: Copy + Ord + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self> + Div<Output = Self> + Rem<Output = Self> { fn zero(self) -> Self; fn one(self) -> Self; fn trailing_zeros(self) -> u32; fn pow(self, exp: u32) -> Self; } macro_rules! impl_prim_int(($($ty:ty),*) => { $( impl PrimInt for $ty { fn zero(self) -> Self { 0 } fn one(self) -> Self { 1 } fn trailing_zeros(self) -> u32 { self.trailing_zeros() } fn pow(self, exp: u32) -> Self { self.pow(exp) } } )* }); impl_prim_int!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
-
num-iter
ジェネリックな整数型やカスタム整数型へのイテレータのサポートを追加します。
プリミティブの整数型には
std::iter::Step
が実装されています。 これはインクリメント等の操作を定義したトレイトです。 これにより整数型の範囲(a..b
やa..=b
)はイテレータになり、for文で回せるようになります。 しかしリンク先を見ていただけるとわかりますがstd::iter::Step
はまだunstableです。 つまりカスタム整数型やジェネリック整数型に対してa..b
等をイテレータにすることはできません。このクレートは代替として
std::cmp::PartialOrd
(大小比較)とstd::ops::Add
(加法)とnum_traits::One
(乗法単位元 =1
)を使って範囲を示すイテレータを生成する関数を提供します。また、
range_step
,range_step_inclusive
という1
以外のステップを設定できるものもあります。 ただRust 1.28でIterator::step_by
が安定化されたので具体的な整数型に対して使うことは無いでしょう。TODO: パフォーマンスは?
-
num-bigint
多倍長整数、すなわち巨大な整数のサポートです。
通常のRustで利用できる型のうち最大の整数を扱える型は
u128
ですが、場合によってはそれよりも大きな数を扱いたいことがあります。 そういったニーズに答えるBigInt
,BigUint
と、それらの型の乱数を生成するのに利用するRandomBits
を提供しています。 -
num-complex
複素数型のサポートです。
Complex<_>
,ComplexDistribution<_, _>
を提供します。 -
num-integer
整数関連のサポートを強化します。
u16
,i32
などの組み込み整数型とBigInt
などのカスタム整数型を拡張するInteger
,Roots
と、これらを使った関数 (gcd
,sqrt
,binomial
など) を提供します。 -
num-rational
有理数型、すなわち分数のサポートです。
有理数を扱うときには通常浮動小数点数が用いられますが、大抵の場合誤差と戦う必要が出てます。
このクレートは
Ratio<_>
を提供します。 これは以下の場合に有用です。- 64-bit/64-bit、または128-bit/128-bitの分数で表現できる(一応
BigInt
やBigUint
も使えますが..) - 浮動小数点数だと苦しい(苦しくないのなら
f64
を使うべきです) - 大小比較が必要(等値比較のみで良いのならいくつかのpに対してℤ/pℤ上で計算すれば良いです)
See also: 誤差と戦う小手先のテク - みさわめも
- 64-bit/64-bit、または128-bit/128-bitの分数で表現できる(一応
-
examples/abc073-d.rs
(num_traits::One
) -
examples/abc129-f.rs
(num_traits::{PrimInt, Unsigned}
) -
examples/abc150-d.rs
(num_integer::lcm
) -
examples/abc154-e.rs
(num_integer::binomial
) -
examples/abc162-c.rs
(num_integer::gcd
) -
examples/abc168-c.rs
(num_complex::Complex
) -
examples/abc168-e.rs
(num_rational::Ratio
,num_traits::pow
) -
examples/agc026-c.rs
(num_traits::pow
) -
examples/atc002-b.rs
(num_bigint::BigUint::modpow
) -
examples/judge-update-202004-a.rs
(num_traits::clamp
) -
examples/judge-update-202004-d.rs
(num_integer::gcd
)
自分で新しく作った newtype に、 num
で定義されるトレイトを自動実装する機能を追加するクレートです。
v0.3.0
先に挙げられた Float
や Num
といったトレイトを、新しく定義した newtype に #[derive(Num)]
などを利用して自動実装できるようになります。
Rust では、実質的に同じ型の値を持つ型であっても型システム上で区別するために新しい構造体を作る、いわゆる newtype パターンが広く利用されています。例えば、グラフ構造の頂点番号と重みに専用の Index
型や Weight
型を用意するなどが考えられます。こうすることで、たとえどちらも実質的には i32
であったとしても、頂点番号と重みを間違えるとコンパイルエラーとなるようにでき、バグを埋め込む確率を下げることができます。しかし、区別する弊害として Weight
型同士の四則演算を個別に実装する必要が生まれます。この型への四則演算の実装をただひたすら中身の整数同士の演算として定義するのは面倒な作業なのですが、このクレートを使うと次のように自動実装させることができます。
use num_derive::{FromPrimitive, Num, NumCast, NumOps, One, ToPrimitive, Zero};
#[derive(
Debug,
Eq,
PartialEq,
PartialOrd,
Ord,
FromPrimitive,
ToPrimitive,
One,
Zero,
Num,
NumCast,
NumOps,
)]
struct Weight(i32);
fn main() {
let w1 = Weight(3);
let w2 = Weight(4);
println!("{:?}", w1 + w2); // => "Weight(7)"
}
すべての四則演算を定義するのは都合が悪い場合でも、例えば Zero
だけ実装したり FromPrimitive
だけ実装したりということも可能です。
また、値を取らない普通の enum に対して FromPrimitive
とToPrimitive
を実装することもできます。数値を enum に変換することは Rust では少し難しいのですが、 FromPrimitive
を実装すれば簡単になります。
use num::{FromPrimitive as _, ToPrimitive as _};
use num_derive::{FromPrimitive, ToPrimitive};
#[derive(Debug, PartialEq, FromPrimitive, ToPrimitive)]
enum Color {
Red,
Blue,
Green,
}
assert_eq!(Color::from_u32(0), Some(Color::Red));
assert_eq!(Color::from_u32(1), Some(Color::Blue));
assert_eq!(Color::from_u32(2), Some(Color::Green));
assert_eq!(Color::from_u32(3), None);
assert_eq!(Color::Red.to_u32(), Some(0));
assert_eq!(Color::Blue.to_u32(), Some(1));
assert_eq!(Color::Green.to_u32(), Some(2));
-
examples/abc129-f.rs
(One
,Zero
)
多次元配列のサポートです。
v0.13.0
Python の numpy.ndarray
のような多次元配列を提供します。
通常 Rust で二次元以上の配列を扱うときは Vec<Vec<...>>
の形で扱いますが、これはその型の通り Vec<...>
の Vec
でしかないので、全要素に対する変更などのユーティリティは用意されていません。
ndarray
では NumPy のものと同じように形状変更やスライス、多次元配列同士の演算などの様々な機能を提供します。また行列の演算にも用いることができます。
使い方について、 NumPy ユーザー向けのドキュメントが ndarray::doc::ndarray_for_numpy_users
に用意されています。ここでは ndarray
の使い方が NumPy で同等のことをするコードと対比する形で解説されています。
-
examples/abc129-f.rs
(行列の積) -
examples/abc151-d.rs
(迷路をArray2<bool>
で表現)
線形代数に特化したクレートです。
v0.20.0
線形代数、特に行列に対する演算 (例えば、通常の和・積、アフィン変換、あるいはLU分解など) を扱うクレートです。一部では ndarray
と似た部分もありますが、より行列に特化しています。また、少し使い勝手の異なる部分もあります。(例えば *
と dot()
が行なう演算が ndarray
(や NumPy)とは逆になっています。)
ndarray
ほどの手軽さはないかもしれませんが、 NumPy が提供しているもので ndarray
には無いような関数もいくつかあります。
alga
は抽象化のためのトレイトを提供します。
代数構造を扱うalga::general
と線形代数を扱うalga::linear
からなります。
See also: データ構造と代数構造 - koba-e964の日記
-
examples/abc145-c.rs
(Point2
,distance
) -
examples/abc157-e-naive.rs
(alga::general
) -
examples/abc157-e-proconio.rs
(alga::general
) -
examples/abc157-e-text-io.rs
(alga::general
) -
examples/abc157-e-whiteread.rs
(alga::general
)
C言語で馴染み深い数学ライブラリ、 libm の pure Rust による実装です。
v0.2.1
core
(⊂ std
)の浮動小数点数の関数はLLVMの実装によるものが多いですが、このクレートは基本的にplatform independentな結果を返すので特に浮動小数点数を扱うライブラリによく使われています。
しかしAtCoderにおいては他に気にするべき点が無数にありますし、実装の違いはさほど重要ではないのでstd
の関数をそのまま使うのが良いでしょう。
ただstd
でカバーされていない関数もいくつか存在します。
また関数名や呼び出し方が math.h
(⊊ cmath
) とほぼ一致するので、特に Rust に慣れていないだけの経験者には使いやすいかもしれません。
-let val = 10f64.powf(-9.0); // 実はプラットフォーム毎に微妙に挙動が違う
+let val = libm::pow(10.0, -9.0);
assert_eq!(error.to_string(), "0.000000001");
-
examples/abc144-d.rs
(atan2
)
擬似乱数ライブラリです。
v0.7.3
Rust には標準で疑似乱数を生成する機能がないため、乱数を利用するにはこのクレートを使う必要があります。このクレートは、例えば次のような擬似乱数生成器を提供します。
-
OsRng
: プラットフォームごとに提供される方法で乱数を得る生成器。 -
SmallRng
: より高速性を重視した乱数生成器。 -
StdRng
: より安全性を重視した乱数生成器。 ThreadRng
それぞれに特徴がありますが、暗号理論的に安全である必要はなくより高速なほうが望ましい競技プログラミングには SmallRng
が向いています。
より多くの種類の乱数の分布を提供します。
v0.2.2
rand::distribution
を拡張するものです。さらにいくつかの分布が追加されます。
グラフアルゴリズムを提供します。
v0.5.0
-
Graph
: 隣接リスト形式のグラフです。 -
StableGraph
:Graph
と同様ですが、頂点削除時の挙動が異なります。 -
GraphMap
:HashMap
を使った隣接リスト形式のグラフです。 -
MatrixGraph
: 隣接行列形式のグラフです。 -
Csr
: 隣接行列が疎行列であるようなグラフです。 -
UnionFind
: 素集合データ構造です。集合の「グループ分け」が効率的にできます。
またグラフをgraphviz形式で表示する機能もあり、グラフの形を図示することも簡単にできます。日頃の勉強やデバッグ時に役立つかもしれません。
TODO: Boost や NetworkX との比較
-
examples/abc054-c.rs
(Graph::node_indices
) -
examples/abc073-d.rs
(dijkstra
) -
examples/abc157-d.rs
(GraphMap
,UnionFind
) -
examples/atc001-b.rs
(UnionFind
)
挿入順を保つ HashMap
を提供します。
v1.3.2
IndexMap
, IndexSet
と、それらの maplit 風のコンストラクタ用マクロ indexmap!
, indexset!
を提供します。基本的には機能・インターフェース共に HashMap
, HashSet
と同様ですが、 iter()
や keys()
などで得られるイテレータが要素を挿入した順に返すことを保証しています。
正規表現を提供します。
v1.3.6
Rust には標準で正規表現を扱う機能がないため、このクレートを利用する必要があります。 str
用の regex::Regex
と [u8]
用の regex::bytes::Regex
が提供されます。
正規表現を使う前にはコンパイル処理が行われるため、繰り返し同じ正規表現を利用する場合は初回に一度だけコンパイルしたものを使い回すほうが高速です。したがって、正規表現を繰り返し呼ばれる関数で使ったり複数の関数で使い回したりするときは、コンパイル済みの正規表現をグローバル変数においておくほうが効率的です。これは遅延初期化グローバル変数を定義する lazy_static
と組み合わせることで実現できます。
lazy_static! {
static ref REGEX: Regex = Regex::new(r#"[Rr]egex"#).unwrap();
}
なお、正規表現にはバックスラッシュ (\
) など通常の文字列では特別な意味を持つ文字が出現する機会も多いので、こういった場合は生文字列リテラルと言われる r#"..."#
を使うことも手です。 r#"
と "#
で囲まれた部分は、ダブルクオート ("
) やバックスラッシュ (\
) であっても通常の文字とみなされます。
-
examples/arc065-c.rs
(regex::bytes::Regex
)
遅延初期化されるグローバル変数を提供します。
v1.4.0
グローバル変数が存在するとスレッド安全性と可変参照の排他性を保つことが非常に困難となるため、 Rust ではグローバル変数、特にミュータブルなものはできるだけ使わせないよう設計されています。 (言語からの削除すら検討されています。) 例えば、ミュータブルなグローバル変数へのすべてのアクセスは unsafe です。また、必ずコンパイル時に値が定まるものしか置くことができません。Rust のグローバル変数 (static 変数) については Rustのstaticいろいろ - 簡潔なQ といったページも参考になります。
なお、競技プログラミングでミュータブルなグローバル変数を利用することについては、 競技プログラミングにてミュータブルなグローバル変数を使ってみたら書くスピードが上がりそう - Qiita という話題もありました。これについては static mutの危険性 - Qiita が参考になります。是非ではなく、簡単に速く書けるメリットもあり、そのトレードオフもあるというご紹介です。
一方、こうした制約はあまりにも不便でもあります。特に必ずコンパイル時に値が定まっていなければいけないため、要素をもつ Vec
や HashMap
、あるいはコンパイル済み正規表現を持たせることもできません。そこで、最初のアクセスまで値の初期化を遅延するグローバル変数が考えられました。 lazy_static
は、それを簡単に定義するためのマクロ lazy_static!
を提供しています。また、ミュータブルなグローバル変数が必要な場合も、これと Mutex
などの適切なロックを組み合わせることで安全に定義することができます。
Before:
static mut VALUES: Vec<i32> = Vec::new();
unsafe {
VALUES.extend_from_slice(&[1, 2, 3, 4]);
assert_eq!(&*VALUES, &[1, 2, 3, 4]);
}
After:
use lazy_static::lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref VALUES: Mutex<Vec<i32>> = Mutex::default();
}
let mut values = VALUES.lock().unwrap();
values.extend_from_slice(&[1, 2, 3, 4]);
assert_eq!(&*values, &[1, 2, 3, 4]);
浮動小数点数に Ord
/Eq
を実装するためのラッパー NotNan<f64>
, OrderedFloat<f64>
を提供します。
v1.0.2
Rust では、等値比較のために Eq
と PartialEq
が、順序比較のために Ord
と PartialOrd
が用意されています。それぞれ2つずつあるのは前者と後者で満たすべき制約が異なるからです。詳しくはリンク先を参照していただけれると分かりますが、 Eq
のほうが PartialEq
よりも制約が強く、 Ord
のほうが PartialOrd
よりも制約が強くなっています。比較演算子は PartialEq
と PartialOrd
が実装されている型で利用できます。
Eq
と PartialEq
の区別がある理由は、 a == a
(反射律と呼ばれます) が常には成り立たない型があるからです。例えば浮動小数点数においては NaN != NaN
となります。しかし、それをもって浮動小数点数には ==
や !=
演算子を定義できないとするのはいささか極端です。したがって、比較演算子は反射律を要求しない PartialEq
を利用します。
Ord
と PartialOrd
の区別がある理由も同様です。 a < b
, a == b
, a > b
のいずれも成り立たないような a
, b
が存在する型があるからです。例えば浮動小数点数においては NaN < NaN
, NaN == NaN
, NaN > NaN
はいずれも false
となります。しかし、それをもって浮動小数点数には <
や >
演算子を定義できないとするのはいささか極端です。したがって、比較演算子は反射律を要求しない PartialOrd
を利用します。
一方で、厳密に順序が定まってほしい状況もあります。例えばジェネリックなソート関数においてNaN
のような値が混じっていると非常に困ります。他の言語だと対象の型には全順序であることを期待し、そうではない場合結果は保証されません。C++に至っては鼻から悪魔が出てきます。 Rustでは対象が全順序でないと困る関数やデータ構造でEq
やOrd
を要求することで意図しない変な結果になるのを防止しています。(ただしその実装で嘘をつくと当然変な結果になります)
非常に Rust らしく厳格な仕組みですが、実際にEq
またはOrd
が要求される場所では浮動小数点数をそのままで利用することができないという問題があります。
このうち関数については.foo()
に対して大抵.foo_by(impl FnMut(..) -> std::cmp::Ordering)
のようなものが共に用意されているのでこれを使えばどうにかすることができます。例えばソートについては以下のように雑にできます。この場合NaN
が混じっていると.unwrap()
の部分でpanicします。
let mut xs = vec![2.0, 1.0, 0.0];
xs.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
max
やmin
も同様にIterator::max_by
を使うかあるいはf64::max
でfold
すれば良いです。
use std::f64;
static XS: &[f64] = &[0.0, 1.0, 2.0];
let max = XS.iter().copied().fold(f64::NEG_INFINITY, f64::max);
しかしBTreeMap<_>
(B木のマップ)やBinaryHeap<_>
(優先度付きキュー)等のデータ構造になるとこうはいきません。
Ord
を実装するf64
/f32
のラッパーが必要になります。
use std::collections::BinaryHeap;
let mut queue = BinaryHeap::<f64>::new();
// error[E0599]: no function or associated item named `new` found for type `std::collections::BinaryHeap<f64>` in the current scope
// --> src/lib.rs:6:36
// |
// 5 | let mut queue = BinaryHeap::<f64>::new();
// | ^^^ function or associated item not found in `std::collections::BinaryHeap<f64>`
// |
// = note: the method `new` exists but the following trait bounds were not satisfied:
// `f64 : std::cmp::Ord`
ordered-float
はそのようなラッパーを2種類提供します。
-
中身として
NaN
が含まれないことを保証します。NotNan::new
の返り値はResult
でNaN
を渡すとErr
になります。 また計算過程でNaN
となってしまった場合も整数型のように panic します。 ただしこちらはrelease build時でもチェックされます。 浮動小数点数はNaN
を除けばOrd
とEq
の要件を満たすため、NotNan
はOrd
とEq
を実装することができます。実際に使う際ですが、
f64
からNotNan<f64>
に変換するにはNotNan::new(x).unwrap()
とする必要があります。 これは少々冗長です。 ただNotNan<f64>
はFromStr
を実装しているので標準入力からそのまま得ることができ、また四則演算の右辺には中身の型が許されているので1.0
等のリテラルをどうするかについても困ることはそう無いはずです。use ordered_float::NotNan; let x = "42.0".parse::<NotNan<_>>().unwrap(); // `proconio`等で読む。 let half: NotNan<_> = x / 2.0; let plus_one: NotNan<_> = x + 1.0;
それでも
NotNan
のリテラルが必要になる場合にはマクロを一行用意すると良いでしょう。macro_rules! notnan(($lit:literal) => (NotNan::new($lit).unwrap())); let y = if p(x) { x } else { notnan!(1.0) };
-
NaN
を「その浮動小数点数型の中で最大の値でNaN == NaN
となる値」と定義する方法です。こちらは
f64
からの変換は容易ですがNotNan
と違い四則演算自体ができません。std::cmp::Reverse
のようなものだと思いましょう。use ordered_float::OrderedFloat; let x = "42.0".parse::<OrderedFloat<f64>>().unwrap(); // Error //let half: OrderedFloat<f64> = x / 2.0; //let half: OrderedFloat<f64> = x / OrderedFloat(2.0); let half: f64 = *x / 2.0; let half: OrderedFloat<f64> = OrderedFloat(half); // Error //let plus_one: OrderedFloat<f64> = x + 1.0; //let plus_one: OrderedFloat<f64> = x + OrderedFloat(1.0); let plus_one: f64 = *x + 1.0; let plus_one: OrderedFloat<f64> = OrderedFloat(plus_one);
use itertools::Itertools as _;
+use ordered_float::OrderedFloat;
-use std::cmp;
use std::collections::BTreeSet;
-#[derive(PartialEq, PartialOrd, Debug)]
-struct DirtyOrd(f64);
-
-// The inner value cannot be `NaN`... probably!
-impl Eq for DirtyOrd {}
-
-impl Ord for DirtyOrd {
- fn cmp(&self, rhs: &Self) -> cmp::Ordering {
- self.partial_cmp(rhs).unwrap()
- }
-}
-
let mut input = "10.0 30.0 20.0".split_whitespace();
macro_rules! read(($ty:ty) => (input.next().unwrap().parse::<$ty>().unwrap()));
let mut set = BTreeSet::new();
-set.insert(DirtyOrd(read!(f64) + 1.1));
-set.insert(DirtyOrd(read!(f64) + 3.3));
-set.insert(DirtyOrd(read!(f64) + 2.2));
+set.insert(OrderedFloat(read!(f64) + 1.1));
+set.insert(OrderedFloat(read!(f64) + 3.3));
+set.insert(OrderedFloat(read!(f64) + 2.2));
-assert_eq!(set.iter().map(|DirtyOrd(x)| x).join(" "), "11.1 22.2 33.3");
+assert_eq!(set.iter().join(" "), "11.1 22.2 33.3");
use itertools::Itertools as _;
+use ordered_float::NotNan;
-use std::cmp;
use std::collections::BTreeSet;
-#[derive(PartialEq, PartialOrd, Debug)]
-struct DirtyOrd(f64);
-
-// The inner value cannot be `NaN`... probably!
-impl Eq for DirtyOrd {}
-
-impl Ord for DirtyOrd {
- fn cmp(&self, rhs: &Self) -> cmp::Ordering {
- self.partial_cmp(rhs).unwrap()
- }
-}
-
let mut input = "10.0 30.0 20.0".split_whitespace();
macro_rules! read(($ty:ty) => (input.next().unwrap().parse::<$ty>().unwrap()));
let mut set = BTreeSet::new();
-set.insert(DirtyOrd(read!(f64) + 1.1));
-set.insert(DirtyOrd(read!(f64) + 3.3));
-set.insert(DirtyOrd(read!(f64) + 2.2));
+set.insert(read!(NotNan<_>) + 1.1);
+set.insert(read!(NotNan<_>) + 3.3);
+set.insert(read!(NotNan<_>) + 2.2);
-assert_eq!(set.iter().map(|DirtyOrd(x)| x).join(" "), "11.1 22.2 33.3");
+assert_eq!(set.iter().join(" "), "11.1 22.2 33.3");
TODO: 浮動小数点数をpriority queueとかに突っ込まなければならない問題がAtCoder上にないか
ASCII 文字列をより扱いやすくするクレートです。
v1.0.0
Rust の組み込みの文字列型 &str
は UTF-8 文字列であり、1 文字が 1 バイトで表されるとは限らないため、文字列に対するインデックスアクセスができないようになっています。したがって、文字単位のランダムアクセスをするためには毎回 .as_bytes()[i]
とするか、一度 Vec<u8>
や Vec<char>
に変換してから扱うこととなります。ランダムアクセスをできるようにする代わりに str
にある (replace
などの) 便利なメソッド類を諦めることになります。
しかし、競技プログラミングの世界で扱う文字列は基本的にはアルファベットのみなのでインデックスアクセスに何の問題もありません。 ascii
はこういった需要に答え、 ASCII 文字のみを含むような文字列をもう少し扱いやすくするクレートです。 AsciiChar
, AsciiStr
, AsciiString
を提供します。それぞれの内部表現はu8
, [u8]
, Vec<u8>
であり、インデックスアクセスにオーバーヘッドは生じません。
use ascii::{AsciiChar, AsciiStr};
let mut s = AsciiStr::from_ascii("abcde\n").unwrap().to_owned();
s[2] = AsciiChar::C;
assert_eq!(s, "abCde\n");
assert_eq!(s.trim_end(), "abCde");
-
examples/abc168-b.rs
(AsciiString::truncate
)
TODO: 実際に嬉しい例があるはずだが見つからない!
順列を生成します。
v0.2.4
Ord
を実装するような型 T
のスライス [T]
の拡張メソッドとして prev_permutation
, next_permutation
を提供します。また、順に順列を返していくイテレータ Heap
や、少しだけ高速に順列を列挙できる関数 heap_recursive
を提供します。
heap_recursive
は Heap のアルゴリズムを使うもので、順に数列を生成していく場合は next_permutation
を繰り返し利用するよりも少しだけパフォーマンスがよいようです。また、値の順序を要求しないため浮動小数点数でもそのまま使えます (順序について詳しくは ordered-float
の項も参照ください) 。
use permutohedron::LexicalPermutation as _;
let mut values = [1, 2, 3];
loop {
eprintln!("{:?}", values);
if !values.next_permutation() {
break;
}
}
なお、 Rust には do-while 文はありませんが、 block expression { ... }
を利用すれば次のように書くこともできてしまいます。ただし、誤解を招きやすい書き方になるため十分な注意が必要です。
use permutohedron::LexicalPermutation as _;
let mut values = [1, 2, 3];
while {
eprintln!("{:?}", values);
values.next_permutation()
} {}
heap_recursive()
関数を利用する場合は次のようになります。
let mut values = [1, 2, 3];
permutohedron::heap_recursive(&mut values, |values| eprintln!("{:?}", values));
スライスの扱いを強化します。
v1.0.0
スライスに対して Ext
トレイトとExt2
トレイトにより以下の拡張メソッドを提供します。
-
lower_bound
/upper_bound
昇順にソートされた列に対し、指定された値以上の値になる (lower_bound
) / 指定された値を超える (upper_bound
) のような最初のインデックスを二分探索により求めます。 Rust 1.17 からはBTreeSet::range
を使うこともできます。 -
lower_bound_by_key
/upper_bound_by_key
lower_bound
/upper_bound
と同様ですが、要素そのものではなくその各要素から定まる値を使います。 -
lower_bound_by
/upper_bound_by
lower_bound
/upper_bound
と同様ですが、比較関数を指定します。 -
equal_range
/equal_range_by
/equal_range_by_key
昇順にソートされた列に対し、値が指定された値と等しいようなインデックスの範囲を返します。 -
next_permutation
/prev_permutation
permutohedron
と同様に、列を前後の順列に変更する関数です。 -
apply_permutation
もともとの列を、指定された順列に従って並び替える関数です。つまり、元の列をbefore
、適用後の列をafter
、指定された順列をperm
とすれば、after[i] == before[perm[i]]
となります。数学で言えば置換に当たります。 -
apply_inverse_permutation
apply_permutation
と同様ですが、与えられた配列による置換の逆を適用します。つまり、元の列をbefore
、適用後の列をafter
、指定された順列をperm
とすれば、after[perm[i]] == before[i]
となります。
-
examples/arc084-c.rs
({lower, upper}_bound
) -
examples/abc142-c.rs
(invert_permutation
) -
examples/abc143-d.rs
(upper_bound
) -
examples/judge-update-202004-d.rs
(lower_bound_by
)
イテレータをさらに強力にするクレートです。
v0.9.0
Iterator
を拡張する Itertools
とその他の便利な関数やマクロを提供します。 Python の more-itertools のような位置付けですが、単純に対応するものではなく、互いに存在しないものや対応していても名前が違う場合があります。
提供するものの例を挙げると、
-if xs.iter().all(|x| x % 2 == 0) || xs.iter().all(|x| x % 2 == 1) {
+if xs.iter().map(|x| x % 2).all_equal() {
todo!();
}
-for i in 0..xs.len() {
- for j in 1 + 1..xs.len() {
- todo!();
- }
+for (i, j) in (0..xs.len()).tuple_combinations() {
+ todo!();
}
let ans = (0..xs.len())
- .flat_map(|i| (i + 1..xs.len()).map(move |j| (i, j)))
+ .tuple_combinations()
.map(|(i, j)| f(&xs[i..=j]))
.sum::<i32>();
-for i in 0..xs.len() {
- for j in 0..ys.len() {
- todo!();
- }
+for (i, j) in iproduct!(0..xs.len(), 0..ys.len()) {
+ todo!();
}
-let ans = (0..xs.len())
- .flat_map(|i| (0..ys.len()).map(move |j| (i, j)))
+let ans = iproduct!(0..xs.len(), 0..ys.len())
.map(|(i, j)| f(i, j))
.sum::<i32>();
-
chunks
,join
,tuple_windows
, ..
let ans = xs
.into_iter()
.map(f)
- .collect::<Vec<_>>()
- .windows(2)
- .map(|w| g(w[0], w[1]))
+ .tuple_windows()
+ .map(|(a, b)| g(a, b))
.max()
.unwrap();
let ans = [1u32, 2, 3, 4, 5]
.iter()
.filter(|&x| x % 2 == 0)
- .map(|x| (10 * x).to_string())
- .collect::<Vec<_>>()
+ .map(|x| 10 * x)
.join(" "); // `<[_]>::join` → `Itertools::join`
その他イテレータをソート済みにするsorted
, 2つのイテレータをマージする merge
, イテレータの要素から k
個取り出す順列をすべて生成する permutations
の他、 combinations
, zip_longest
, intersperse
, pad_using
, interleave
,
coalesce
, set_from
など、挙げればキリがないような様々なメソッドが使えるようになります。
かなり多種多彩で便利なので、一度ドキュメントを読んでどのようなメソッドがあるのかを知っておくと役立つこともあると思います。 メソッドチェーン一本で解決できる問題が増えます。
See also:
-
examples/abc054-c.rs
(Itertools::permutations
) -
examples/abc073-d.rs
(Itertools::permutations
) -
examples/abc142-c.rs
(Itertools::format
) -
examples/abc143-d.rs
(Itertools::tuple_combinations
) -
examples/abc145-c.rs
(Itertools::tuple_combinations
) -
examples/abc150-d.rs
(Itertools::all_equal
) -
examples/abc151-d.rs
(Itertools::concat
) -
examples/abc157-d.rs
(Itertools::format
) -
examples/abc160-e.rs
(Itertools::kmerge_by
) -
examples/abc162-c.rs
(iproduct!
) -
examples/abc165-b.rs
(iterate
) -
examples/abc165-c.rs
(Itertools::combinations_with_replacement
) -
examples/abc166-b.rs
(Itertools::unique
) -
examples/agc026-c.rs
(Itertools::{collect_vec, partition_map}
) -
examples/apg4b-ex25.rs
(Itertools::format
) -
examples/judge-update-202004-b.rs
(Itertools::sorted_by_key
) -
examples/judge-update-202004-c.rs
(iproduct!
,Itertools::permutations
) -
examples/practice-b-naive.rs
(Itertools::{concat, permutations}
) -
examples/practice-b-proconio.rs
(Itertools::{concat, permutations}
) -
examples/practice-b-text-io.rs
(Itertools::{concat, permutations}
) -
examples/practice-b-whiteread.rs
(Itertools::{concat, permutations}
)
cumsumとlinspace
v0.1.3
一次元累積和のイテレータを生成するItertoolsNum::cumsum
と等間隔な浮動小数点数のイテレータを生成する linspace
の2つを提供します。
提供するのはこの2つのみです。
そして累積和やlinspaceは用意に手で書くことができます。
+use itertools_num::ItertoolsNum as _;
+
static XS: &[u32] = &[1, 2, 3, 4, 5];
-let cumsum = XS
- .iter()
- .scan(0, |sum, x| {
- *sum += x;
- Some(*sum)
- })
- .collect::<Vec<_>>();
+let cumsum = XS.iter().cumsum().collect::<Vec<u32>>();
assert_eq!(cumsum, &[1, 3, 6, 10, 15]);
const START: f64 = 2.0;
const STOP: f64 = 3.0;
const NUM: usize = 5;
-let linspace = (0..NUM)
- .map(|i| START + (STOP - START) * (i as f64) / ((NUM - 1) as f64))
- .collect::<Vec<_>>();
+let linspace = itertools_num::linspace(START, STOP, NUM).collect::<Vec<_>>();
assert_eq!(linspace, &[2.0, 2.25, 2.5, 2.75, 3.0]);
ただしcumsum
の使い道は結構多いです。
オーソドックスに先頭に0
を入れた上でVec
にして部分和を取るのに使っても良し、
use itertools_num::ItertoolsNum as _;
use std::iter;
static XS: &[u64] = &[0, 0, 1, 1, 2, 2, 2, 2, 4, 4, 5, 5, 6, 7, 8, 9];
let cumsum = iter::once(&0).chain(XS).cumsum().collect::<Vec<u64>>();
assert_eq!((5..=10).map(|i| XS[i]).sum::<u64>(), cumsum[11] - cumsum[5]);
そのままfor文に突っ込んでも良し、
-let mut sum = 0;
-for x in &xs {
- sum += x;
+for sum in xs.iter().cumsum() {
todo!();
}
2次元累積和の構築や2次元いもす法の展開で使っても良しです。
// TODO: 何かの問題で使って`examples`に入れる
use itertools::Itertools as _;
use itertools_num::ItertoolsNum as _;
// https://imoz.jp/algorithms/imos_method.html
let mut imos = [[0; 7]; 7];
for &(i1, j1, i2, j2) in &[(0, 0, 3, 3), (2, 2, 5, 5), (3, 1, 4, 2)] {
imos[i1][j1] += 1;
imos[i1][j2 + 1] -= 1;
imos[i2 + 1][j1] -= 1;
imos[i2 + 1][j2 + 1] += 1;
}
let cumsum = imos
.iter()
.scan(vec![0; imos[0].len()], |prev, cur| {
// ここで使う
for (prev, sum) in prev.iter_mut().zip_eq(cur.iter().cumsum::<i32>()) {
*prev += sum;
}
Some(prev.clone())
})
.collect::<Vec<_>>();
assert_eq!(
cumsum,
[
[1, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0],
[1, 1, 2, 2, 1, 1, 0],
[1, 2, 3, 2, 1, 1, 0],
[0, 1, 2, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0],
],
);
BTreeMap
, BTreeSet
, HashMap
, HashSet
を生成するためのマクロを提供します。
v1.0.2
Vec
には vec![a, b, c]
のようなマクロが提供されていますが、標準ライブラリでは Vec
以外のコレクションに対してそのようなマクロを提供していません。このクレートは vec!
と同様に、各コレクションを簡単に生成するマクロとして btreemap!
, btreeset!
, hashmap!
, hashset!
を提供します。また、マクロに渡したキーや値を追加する前に指定の関数に通すようにするマクロ convert_args!
も提供されます。このマクロを使うと、例えば String
を要素にもつコレクションを生成するときに &str
で初期値を与えるといったことができます。
// キーの部分で一々 `"a".to_string()` などとするのは面倒だが、
// 次のようにして一括で `String::from` を適用するようにできる。
let map2 = convert_args!(keys=String::from, hashmap!(
"a" => 1,
"c" => 2,
));
-use std::collections::HashMap;
+use maplit::hashmap;
-let mut map = HashMap::new();
-map.insert("foo", 10);
-map.insert("bar", 20);
-map.insert("baz", 30);
+let map = hashmap!(
+ "foo" => 10,
+ "bar" => 20,
+ "baz" => 30,
+);
-
examples/abc168-e.rs
(hashmap!
) -
examples/agc023-a.rs
(hashmap!
) -
examples/practice-b-naive.rs
(hashset!
) -
examples/practice-b-proconio.rs
(hashset!
) -
examples/practice-b-text-io.rs
(hashset!
) -
examples/practice-b-whiteread.rs
(hashset!
)
汎用的に使える2バリアントの enum を提供します。
v1.5.3
Rust でさっと使える2バリアントの enum としては Result<T, E>
がありますが、これはもともとエラー処理のための型なので、バリアントに Ok
と Err
という強い意味を持つ名前がつけられています。もちろん汎用的な enum として使えないことはありませんが、取りうる値に特に成功/失敗の関係がない場合、ミスリーディングにもなりえます。そういった事情もあり、特に意味のないバリアントとして Left
, Right
を持つような Either<L, R>
を提供します。
-enum Either<L, R> {
- Left(L),
- Right(R),
-}
+use either::Either;
永続データ構造を提供します。
v14.3.0
永続データ構造とは、端的に言えば更新したときに更新前の状態を保存するデータ構造のことです。
標準ライブラリのHashMap
, HashSet
, BTreeMap
, BTreeSet
, Vec
に対応する永続データ構造として、それぞれ HashMap
, HashSet
, OrdMap
, OrdSet
, Vector
を提供します。
ただし(&self, ..) -> Self
のシグネチャを持つメソッドは多くありません。
mut
で宣言して(&mut self, ..) -> _
の形で操作することになります。
これはREADMEにあるように(TODO)という方針だからです。
-let mut map = maplit::hashmap!();
-map.insert("foo", 42);
+let map = im::hashmap!();
+let map = map.update("foo", 42);
(TODO: Vector
、すごい遅い気がする..)
可変長の bitset を提供します。 (その1)
v0.2.0
FixedBitSet
を提供します。基本的な使い方などは HashSet<usize>
と同様ですが、要素があるかないかをビットで管理する点が異なります。より省スペースで、高速な集合の演算が可能です。
TODO: bit-vec
, bitvec
との違い
Before:
// 😧
let mut ps = vec![false; 10000];
After:
use fixedbitset::FixedBitSet;
// 😃
let mut ps = FixedBitSet::with_capacity(10000);
-
examples/apg4b-ex25.rs
(FixedBitSet::{ones, set}
,<FixedBitSet as FromIterator>
&
,|
,^
) -
examples/sumitrust2019-c.rs
(FixedBitSet::insert_range
)
可変長の bitset を提供します。 (その2)
v0.1.0
BitSet
を提供します。
機能はfixedbitset
のものと比べて控え目ですがこちらはビットシフトが可能です。
use bitset_fixed::BitSet;
fn main() {
let mut bitset = BitSet::new(64);
bitset.set(0, true);
bitset.set(1, true);
bitset.set(2, true);
assert_eq!(bitset.buffer(), &[0b111]);
bitset <<= 1;
assert_eq!(bitset.buffer(), &[0b1110]);
bitset >>= 2;
assert_eq!(bitset.buffer(), &[0b11]);
bitset <<= 64;
assert_eq!(bitset.buffer(), &[0b0]);
}
-
examples/agc020-c.rs
(ビットシフト)
標準入力ヘルパーその1です。また高速な標準出力を提供します。
v0.3.6
practice contestのA問題 (ログイン不要版)を解きたいとしましょう。
この問題においては(a, b, c, s): (u32, u32, u32, String)
さえ得られればprintln!("{} {}", a + b + c, s);
として正答できます。
さて、Rustの標準入力についてGoogle検索等で適当に調べると
-
Stdin::read_line
で一行読むことができる。 -
str
はstr::split
,str::split_whitespace
,str::split_ascii_whtiespace
を使うことでスペースで分割できる。
ということがわかると思います。 これらを使って書いてみましょう。
use std::io;
fn main() {
let line = getline();
let mut split = line.split_ascii_whitespace();
let a = split.next().unwrap().parse::<u32>().unwrap();
let line = getline();
let mut split = line.split_ascii_whitespace();
let b = split.next().unwrap().parse::<u32>().unwrap();
let c = split.next().unwrap().parse::<u32>().unwrap();
let line = getline();
let mut split = line.split_ascii_whitespace();
let s = split.next().unwrap().parse::<String>().unwrap();
println!("{} {}", a + b + c, s);
}
fn getline() -> String {
let mut buf = "".to_owned();
io::stdin().read_line(&mut buf).unwrap();
buf
}
何だか冗長ですね。
ところでインタラクティブ問題でもない限り、一行一行ではなく一度に全体を読み込んでしまってもよいはずです。
Read::read_to_string
で書き直してみましょう。
use std::io::{self, Read as _};
fn main() {
let mut input = "".to_owned();
io::stdin().read_to_string(&mut input).unwrap();
let mut input = input.split_ascii_whitespace();
let a = input.next().unwrap().parse::<u32>().unwrap();
let b = input.next().unwrap().parse::<u32>().unwrap();
let c = input.next().unwrap().parse::<u32>().unwrap();
let s = input.next().unwrap().parse::<String>().unwrap();
println!("{} {}", a + b + c, s);
}
まだ_.next().unwrap().parse::<_>().unwrap()
という表現が何度も出て来てます。
これを切り出してみましょう。
input
をキャプチャしたクロージャを使ってみます。
// error[E0282]: type annotations needed for the closure `fn() -> _`
let mut read = || input.next().unwrap().parse().unwrap();
はい。残念ながら現在のRustでも多相なクロージャは書けません。
しかしRustにはマクロがあります。 マクロでは定義する場所から見える場合のみ外部のローカル変数を使うことができます。
macro_rules! read(($ty:ty) => (input.next().unwrap().parse::<$ty>().unwrap()));
このread!
を使うとこのように最初のコードよりもずっとシンプルになりました。
use std::io::{self, Read as _};
fn main() {
let mut input = "".to_owned();
io::stdin().read_to_string(&mut input).unwrap();
let mut input = input.split_ascii_whitespace();
macro_rules! read(($ty:ty) => (input.next().unwrap().parse::<$ty>().unwrap()));
let a = read!(u32);
let b = read!(u32);
let c = read!(u32);
let s = read!(String);
println!("{} {}", a + b + c, s);
}
なお、インタラクティブ問題で一行ずつ読まなければならない場合でもRust 1.42ならこのようにすれば良いです。
use std::io;
fn main() {
let mut input = "".split_ascii_whitespace();
let mut read = || loop {
if let Some(word) = input.next() {
break word;
}
input = {
let mut input = "".to_owned();
io::stdin().read_line(&mut input).unwrap();
if input.is_empty() {
panic!("reached EOF");
}
Box::leak(input.into_boxed_str()).split_ascii_whitespace()
};
};
macro_rules! read(($ty:ty) => (read().parse::<$ty>().unwrap()));
let a = read!(u32);
let b = read!(u32);
let c = read!(u32);
let s = read!(String);
println!("{} {}", a + b + c, s);
}
しかしシンプルになったと言ってもC++やJava, Pythonと比べればどうでしょうか?
C++にはstd::cin
、Javaにはjava.util.Scanner
、Pythonには__builtin__.input
があります。
もっとコード量を減らしたくはならないでしょうか?
そこでrust-lang-jaとしては今回のアップデートにおいてproconio
, text_io
, whiteread
の3つのクレートを提案しました。
これらを使った例を載せておきます。 (-naive
は比較の為のクレートを使わない例です)
- ABC057: B - Checkpoints
- ABC118: B - Foods Loved by Everyone
- ABC121: B - Can you solve this?
- ABC157: E - Simple String Queries
- APC001: C - Vacant Seat
- practice contest: A - Welcome to AtCoder (ログイン不要版)
- practice contest: B - Interactive Sorting (ログイン不要版)
前置きが長くなりましたがproconio
の解説に入ります。
proconio
はtanakh さんのマクロを参考にして改良されたマクロ input!
と、 println!
を自動的に高速化するマクロ #[fastout]
を提供します。
なお、元となった tanakh さんのマクロは現在Mit Licenseで公開されています。
AtCoderバイナリ提出専用になったようでinput!
が消え、pub use proconio::input
になってました...
消える直前のライブラリ版はマーカーとしてbytes
が追加されているだけなので以下Qiita版の方を「オリジナル」と呼びます。
proconio::input!
はオリジナルの input!
に以下の機能を追加したものです。
-
入力を行ごとに読むのか EOF まで一度に読むのかを選べます。
デフォルトでは、デバッグビルド時はデバッグに便利な行ごとのモード、リリースビルド時にはより高速な一気読みのモードを利用します。 また行ごとのモードでも記事にあるような重大なオーバーヘッドはありません。
-
同じプログラムで2回以上使えます。
proconio::input!
のデフォルトのsourceはlazy_static
で宣言されていて、読み込んだ標準入力の内容をしっかりと保存しています。一回の
input!
で読むことが難しい入力にも対処できます。use proconio::input; input!(q: usize); for _ in 0..q { input!(kind: u32); match kind { 1 => { input!(xs: [i64]); todo!(); } 2 => { input!(i: usize); todo!(); } _ => unreachable!(), } }
-
読む入力を
str
やFile
に替えることができます。テストを言語に組み込みの
#[test]
を使って行う場合、標準入力ではなくファイルや文字列リテラルから読み込みたい場合があるかもしれません。そのような場合に備え、読み込み元を変更することができます。use proconio::{input, source::once::OnceSource}; input! { from OnceSource::from("42"), k: u32, } assert_eq!(k, 42);
-
変数に
mut
を付けられます。また使わない値を_
でスキップできます。$(mut)? $tt
に該当するパターンで変数を取れます。use proconio::{input, source::once::OnceSource}; input! { from OnceSource::from("57 42"), _: u32, mut k: u32, } assert_eq!(k, 42); k -= 1; assert_eq!(k, 41);
-
n: usize, xs: [T; n]
をxs: [T]
と書けます。入れ子にしたときに役立ちます。 例えばABC166-Bの入力はこのように取れます。
use proconio::{input, marker::Usize1}; input! { n: usize, ass: [[Usize1]], }
-
Readable
というトレイトで読み込み方をカスタマイズできます。proconio
では値の読み方をReadable
というトレイトを通して制御します。これを用いると読み方を特別に定義するマーカーを作ることができます。たとえば文字列をVec<u8>
として受けとるBytes
,Vec<char>
として受けとるChars
, 整数値を-1
して読み込むUsize1
,Isize1
が用意されています。use proconio::{input, marker::Usize1, source::once::OnceSource}; input! { from OnceSource::from("3\n2 1 2"), g: [Usize1], } assert_eq!(g, &[1, 0, 1]);
また #[fastout]
は、標準出力を Rustで高速な標準出力 - κeenのHappy Hacκing Blog で紹介されている方法で自動的に高速化します。
Rustではstd::println!
で10^5個程度の要素を出力しようとすると150 msくらいかかることがあります。
これは致命的ではないものの気分が良いものではないのでこのようなときはprintln!
を使わず、BufWriter
とwriteln!
を使ってまとめて書き込むということがなされます。
use std::io::{self, BufWriter, Write as _};
fn main() {
let ans: Vec<i64> = unimplemented!();
let stdout = io::stdout();
let mut stdout = BufWriter::new(stdout.lock());
for ans in ans {
// 間違えて`println!`を使わないように注意
writeln!(stdout, "{}", ans).unwrap();
}
stdout.flush().unwrap();
}
// あるいは
use std::io::{self, BufWriter, StdoutLock, Write as _};
fn main() {
let ans: Vec<i64> = unimplemented!();
buf_print(|stdout| {
macro_rules! println(($($tt:tt)*) => (writeln!(stdout, $($tt)*).unwrap()));
for ans in ans {
println!("{}", ans);
}
});
}
fn buf_print(f: impl FnOnce(&mut BufWriter<StdoutLock>)) {
let stdout = io::stdout();
let mut stdout = BufWriter::new(stdout.lock());
f(&mut stdout);
stdout.flush().unwrap();
}
このような変更を自動的に行うのが #[fastout]
です。
関数内のprint!
とprintln!
を発見して置き換えます。
#[proconio::fastout]
fn main() {
let ans: Vec<i64> = unimplemented!();
for ans in ans {
println!("{}", ans);
}
}
現行のAtCoderでの#[proconio::fastout]
はmatch armのprintln!
を置き換えません。
これを踏んだ場合、標準出力の順番が変わります。
気づいたあとすぐに修正したのですが、アップデートにギリギリ間に合いませんでした。(2020-04-01)
最新のproconio
では、新たにprint!
とprintln!
をシャドーイングする形で定義して元のブロックをそのまま包み込みます。(fastout
実装当時のstableのRustでは不可能でした)
-
examples
の大半
標準入力ヘルパーその2です。
v0.1.8
read!
マクロを提供します。
read!()
とするだけで空白区切りの値を1つずつ読むことができます。 FromStr
を実装している型を読むことができます。競技プログラミングではあまり使う機会はないかもしれませんが、C言語の scanf
のように、もう少し柔軟に使うこともできます。
1つずつしか読めないのでproconio
やwhiteread
のように複数の値をVec<_>
で取るといったことは苦手です。
また、read!()
(引数無し)はread!("{}")
のエイリアスであり特殊化は行なわれておらずその仕組み上あまり高速ではありません。
Rustの競プロ向け入力関数やマクロを高速化できるかやってみたという記事と同様の方法で性能評価すると以下のようになります。
プログラム | 内容 | 所要時間(秒) |
---|---|---|
main1a | (記事本編) read() 関数 |
7.751 |
main1b | (記事本編) 改良版 reads() , readl() メソッド |
2.308 |
main1b 改 | (記事本編) main1b で str::from_utf8_unchecked() を使用 |
1.953 |
main2a | (記事本編) get!() マクロ |
2.550 |
main2b | (記事本編) 改良版 get!() マクロ |
2.127 |
main3a | (コメント欄) InputScanner<R: BufRead>
|
1.895 |
main3b | (コメント欄) InputScanOnce
|
1.658 |
tanakh-input-once | @tanakh氏のオリジナルの input! (EOFまで一度に) |
2.021 |
tanakh-input-line | @tanakh氏のオリジナルの input! (行ごと) |
5.996 |
proconio |
proconio クレート |
2.039 |
text-io |
text_io クレート |
14.544 |
whiteread |
whiteread クレート |
2.523 |
iostream | C++ (g++ 9.2.0 with -std=c++17 -O2 ) の iostream
|
3.791 |
cstdio | C++ (g++ 9.2.0 with -std=c++17 -O2 ) の cstdio
|
3.223 |
examples/abc057-b-text-io.rs
examples/abc118-b-text-io.rs
examples/abc121-b-text-io.rs
examples/practice-a-text-io.rs
examples/practice-b-text-io.rs
標準入力ヘルパーその3です。
v0.5.0
std::cin
の使い勝手を再現したようです。
3つの標準入力ヘルパーのうち唯一' '
と'\n'
を区別することができます。
examples/abc057-b-whiteread.rs
examples/abc118-b-whiteread.rs
examples/abc121-b-whiteread.rs
examples/practice-a-whiteread.rs
examples/practice-b-whiteread.rs
高速なハッシュ関数を提供します。
v1.1.0
Rust コンパイラの rustc
や Firefox でも使われているハッシュ関数です。かなり高速なようです。(hatooさんのツイート)
nightly-2020-02-17
でのベンチマーク結果は以下のようになりました。
$ cargo +nightly bench 2>/dev/null
running 12 tests
test bench_default_hasher ... bench: 1,208,041 ns/iter (+/- 47,247)
test bench_default_hasher_80bytes ... bench: 5,107,028 ns/iter (+/- 68,891)
test bench_default_hasher_hashset ... bench: 5,037,694 ns/iter (+/- 62,134)
test bench_fnv ... bench: 916,753 ns/iter (+/- 2,426)
test bench_fnv_80bytes ... bench: 9,170,034 ns/iter (+/- 37,260)
test bench_fnv_hashset ... bench: 3,954,849 ns/iter (+/- 103,217)
test bench_metrohash ... bench: 785,097 ns/iter (+/- 79,548)
test bench_metrohash_80bytes ... bench: 4,094,272 ns/iter (+/- 84,689)
test bench_metrohash_hashset ... bench: 3,898,019 ns/iter (+/- 141,702)
test bench_rustc_hash ... bench: 8,556 ns/iter (+/- 35)
test bench_rustc_hash_80bytes ... bench: 1,375,398 ns/iter (+/- 10,474)
test bench_rustc_hash_hashset ... bench: 2,558,677 ns/iter (+/- 56,769)
test result: ok. 0 passed; 0 failed; 0 ignored; 12 measured; 0 filtered out
-use std::collections::HashMap;
+use rustc_hash::FxHashMap;
-let mut map = HashMap::new();
+let mut map = FxHashMap::default();
ある長さになるまで要素を「直に」持ち、超えた場合ヒープアロケーションに切り替える可変長配列です。
v1.2.0
一般にヒープアロケーションは低速なので、可変長が必要でも大半は要素数 1 か 2 、という場合に Vec
を使うよりも高速になることが期待できます。C++ の boost::container::small_vector
に相当します。
+use smallvec::SmallVec;
+
use std::iter;
let probably_single_values = (0..100)
.map(|i| generate_values(i).collect())
- .collect::<Vec<Vec<_>>>();
+ .collect::<Vec<SmallVec<[_; 1]>>>();
-
examples/abc151-d.rs
(迷路の各マスの遷移先は高々4つ) -
examples/panasonic2020-d.rs
(大きさが高々10のASCII文字列)