RustTips
イテレータから値を返さない場合map()
の代わりにfor_each()
を使うと高速
2つの変数の中身を交換したい場合、普通は
let tmp=a;
a=b;
b=tmp;
とするが、rustの場合、tupleを使えば
(a,b)=(b,a);
と、1行で済ませられる。
@2023-01-13 nightlyでしか使えない
src/
内にlib.rs
とmain.rs
が両方ある場合、二つは別々のcrateとして扱われる
zip()
を使うと二つの配列(等)をタプルのiteratorとして扱える
let x=vec![0,1,2,3,4];
let y=vec![4,3,2,1,0];
for (i,j) in x.zip(y){
println!("{i},{j}");
}
/* this prints
0,4
1,3
2,2
3,1
4,0
*/
char
からu8
に変換したい場合 as
以外に
let letters=b'z'-b'a';
とする方法がある
iterationをしているときに所有権が必要な場合iter()
methodではなくinto_iter()
methodを使う
iteratorのfold()
methodを使うことで楽に新しい値を作ることができる
例:leet code 49
const ALPHABETS: usize = (b'z' - b'a' + 1) as _;
impl Solution {
pub fn group_anagrams(mut strs: Vec<String,>,) -> Vec<Vec<String,>,> {
strs.into_iter()
.fold(HashMap::<[u8; ALPHABETS], Vec<String,>,>::new(), |mut map, s| {
let freqs = s.bytes().map(|b| (b - b'a') as usize,).fold(
[0; ALPHABETS],
|mut freqs, bin| {
freqs[bin] += 1;
freqs
},
);
map.entry(freqs,).or_default().push(s,);
map
},)
.into_values()
.collect()
}
}
if boolean {
1
} else {
0
}
というコードは::from(T)
methodを使って短縮できる
例:
vec![6,7][usize::from(true)]==7
vec![6,7][usize::from(false)]==6
HashMap
でよくやる操作の一つ、もしkey
が存在したら+1 (or other operation)
、しなかったら特定の値を代入。これは
t_chars.entry(key,).and_modify(|i| *i += 1,).or_insert(1);
のように、and_modify
とor_insert
を使った方法がよく取られるが、
t_chars.entry(key,).or_insert(0,) += 1;
とした方が高速。
format modifier
format!
マクロではformat modifierが使える。
let printout = "printout";
assert_eq!(format!("{printout:?}"), "\"printout\"");
rustでは関数やクロージャの型はお互いに固有なものになっている。
fn closure_ornot<GenT: 'static,>(_which: GenT,) -> &'static str {
let gen_id = TypeId::of::<GenT,>();
if TypeId::of::<i32,>() == gen_id {
"GenT is i32"
} else if TypeId::of::<dyn Fn() -> i32,>() == gen_id {
"GenT is Fn()->i32"
} else {
"unexpected!!"
}
}
assert_eq!(closure_ornot(return_closure()(),), "GenT is i32");
assert_eq!(closure_ornot(return_closure(),), "unexpected!!");
fn fst_citizen() -> i32 { 0 }
assert_eq!(closure_ornot(fst_citizen),"unexpected!!");
こっちの方がわかりやすいかも
// call's "anonymous type"
let cl1 = || 1;
let cl2 = || 2;
assert!(cl1.type_id() != cl2.type_id());
//fn's type is as well
fn ret1() -> i32 { 1 }
fn ret2() -> i32 { 2 }
fn retn() -> i32 { 1 }
assert!(ret1.type_id() != ret2.type_id());
assert!(ret1.type_id() != retn.type_id());
trait
は標準ライブラリが提供している型はもちろん組み込み型にも実装できる。
trait MyName {
fn is(&self,) -> &str;
}
impl<T,> MyName for Vec<T,> {
fn is(&self,) -> &str { "!Vec<T>!" }
}
impl MyName for i32 {
fn is(&self,) -> &str { "!int!" }
}
impl<T,> MyName for (i32, Vec<T,>,) {
fn is(&self,) -> &str { "!(i32, Vec<T>)!" }
}
let v = vec![0, 1, 1, 2, 23];
let ai = 0;
assert_eq!(v.is(), "!Vec<T>!");
assert_eq!(ai.is(), "!int!");
assert_eq!((ai, v,).is(), "!(i32, Vec<T>)!");
そりゃそうか
Option<T>
Option<T>
型はiteratorにできる(なぜ???)
let a = Some("a",);
for &i in a.iter() {
assert_eq!(i, "a");
}
Option<T>::map()
に渡されるクロージャのとる引数はOption<T>
型ではなくT
型。
何を言っているのかわからねーと思うが自分も何を言っているのかわからなかったので例:
None.map(|_one: i32| panic!("This painc won't be executed"),);
Some(1,).map(|one| assert!(one == 1),);
cargo test
で出力を見たい時はcargo test -- --nocapture
とするのが一般的だが、-- --nocapture
と指定するのが面倒な場合
#[cfg(test)]
mod tests {
#[test]
fn box_test() {
let b = Box::new(1,);
assert!(false, "{b:?}");
}
}
のようにassert!(false, "some output")
とすると便利。
if
文の代わりにbool::then()
を使うとrubyのようにかける
let mut a = 0;
true.then(|| { a += 1; });
false.then(|| { a += 1; });
assert_eq!(a, 1);
let Some(msg) = return_boolean().then(|| "returned true") else {
panic("returned false");
};
assert_eq!(msg, "returned true");
Iterator::flat_map()
はnestした型を均したい時に便利
let vector = vec![0, 1, 2];
let from_map: Vec<u8,> = vector.iter().map(|n| n * 2,).collect();
let vecvec = vec![vector.clone(); 3];
let from_flat_map: Vec<u8,> = vecvec.iter().flat_map(|i| i.clone(),).collect();
assert_eq!(from_map, [0, 2, 4]);
assert_eq!(from_flat_map, [0, 1, 2, 0, 1, 2, 0, 1, 2]);
labelから値を返すことができるようになった(from rust 1.65.0
)
statement用のearly returnとでも言いましょうか
let rslt = 'b: {
if false {
break 'b 1;
}
if true {
break 'b 2;
}
3
};
assert_eq!(rslt, 2);
rustでもscientific notationは使えまぁす
let a = 1e5 as i32;
assert_eq!(a, 100000);
rustにはサイズが0の型がある(zst stands for Zero Sized Type)
()
なんかはzstの代表的な例
これが何の役に立つかというと
Set<key>
をMap<key,()>
のラッパーとして実装するときに()
がzstなので余分なメモリを確保しなくて済むのだそうだ
以下公式ドキュメントからの引用
#![allow(unused)]
fn main() {
struct Foo; // フィールドがない = サイズ 0
// すべてのフィールドのサイズがない = サイズ 0
struct Baz {
foo: Foo,
qux: (), // 空のタプルにはサイズがありません
baz: [u8; 0], // 空の配列にはサイズがありません
}
}
where for
という構文がある
デバッグ情報として、現在実行されている関数へのパスも表示したい時がある
そんな時はmodule_path!()
とstd::any::type_name::<fn()>()
で解決できる
例↓
println!("current path is: {}::{}", module_path!(), std::any::type_name::<fn()>());
rustでバイナリ表示の際の桁数を求めたい時、leading_zeros()
メソッドが便利
fn binary_digit_count_bitwise(num: u32) -> u32 {
32 - num.leading_zeros()
}
fn main() {
let number = 42;
let count = binary_digit_count_bitwise(number);
println!("The binary digit count of {} is {}", number, count);
}
ベクトルの要素を取り除く関数たちのパフォーマンス比較 by gpt君
How drain() Works
Definition:
pub fn drain<R>(&mut self, range: R) -> Drain<'_, T>
where
R: RangeBounds<usize>;
Purpose:
Extracts elements from the vector, either in a specific range or for the entire vector (default if no range is provided).
It operates by creating an iterator over the specified range and removes the elements from the vector once the iterator is dropped.
Key Features:
Avoids cloning: It moves the elements instead of cloning them.
Destructive: The vector shrinks, as the drained range is removed.
Efficient for partial removals: It doesn’t reallocate memory unnecessarily, especially if the drained range is at the beginning or end of the vector.
Performance Comparison
- drain() vs retain()
retain():
Keeps elements in the vector based on a predicate.
Requires evaluating every element, which can be slower for simple removal tasks.
Retains elements in place without creating an iterator.
Performance:
drain() is faster if you know exactly which range you want to remove.
retain() may be better when removing elements based on a condition.
2. drain() vs split_off()
split_off():
Splits the vector into two at a given index, returning the second half while leaving the first half intact.
Useful for separating at a specific index, but less versatile than drain() for arbitrary ranges.
Performance:
split_off() is faster for separating into halves because it avoids shifting elements unnecessarily.
drain() is more flexible but can involve additional overhead for ranges not aligned with the vector's layout.
3. drain() vs Direct Indexing
Direct Indexing:
Removing elements using methods like vec.remove() in a loop.
Works for small, specific removals but inefficient for large ranges, as each remove() call shifts all subsequent elements.
Performance:
drain() is significantly faster for ranges, as it minimizes shifting operations and manages memory efficiently.
4. drain() vs Iteration with extend()
Iteration with extend():
Using iter() or into_iter() to collect and create a new vector without specific elements.
This requires allocating a new vector, making it slower and more memory-intensive.
Performance:
drain() is more memory-efficient since it modifies the collection in place.
尻尾から取り除く場合はVec
頭から取り除く場合はVecDeque
drain()関数を使う場合はVecDeque