Rust自習(連結リスト6)
nth_ref()
メソッド実装。ふつうに書けた。
成果物
nth_ref()
メソッド
指定した位置のノードが持つitem
の不変参照を返す。
std::iter::Iterator.nth()というメソッドがある。これは指定した位置の要素を返す。だが、所有権をムーブしてしまう。リストから削除されてしまう。そこで、所有権をムーブせず参照を返すようにしたのが今回のnth_ref()
メソッドである。
コード
while
版
fn nth_ref(&self, index: usize) -> &T { let mut count = 0; let mut ptr = &self.head; while count < index { if ptr.is_none() { panic!("Out of index. index値が大きすぎます。index: {}", index); } ptr = &ptr.as_ref().unwrap().next; count += 1; } if ptr.is_none() { panic!("Out of index. index値が大きすぎます。index: {}", index); } else { &((*ptr).as_ref().unwrap().item) } }
if
文のところが重複しているのが気になる。でも、while count <= index
にしてしまうと指定位置の要素がNone
のときptr = &ptr.as_ref().unwrap().next;
のところでパニックする。None
はunwrap()
できないから。
かといって以下のようにすると無駄にif文の実行を増やしてしまう。
while count <= index { if ptr.is_none() { panic!("Out of index. index値が大きすぎます。index: {}", index); } if count < index { ptr = &ptr.as_ref().unwrap().next; count += 1; } }
再帰版
再帰を使って書くと以下。
fn nth_ref(&self, index: usize) -> &T { fn get_idx_ptr<T>(ptr: &Option<Box<Node<T>>>, index: usize, count: usize) -> &Option<Box<Node<T>>> { if count < index { if ptr.is_some() { return get_idx_ptr(&ptr.as_ref().unwrap().next, index, count+1); } else { return ptr; } } else { return ptr; } } let ptr = get_idx_ptr(&self.head, index, 0); if ptr.is_none() { panic!("Out of index. index値が大きすぎます。index: {}", index); } else { &((*ptr).as_ref().unwrap().item) } }
get_idx_ptr
は指定したインデックスにある要素への不変参照を返す。ただし、もしインデックスが最後の要素より大きいインデックスなら、最後の要素への不変参照を返す。
インデックスがどこであれ、指し示した値がNone
なら要素が存在しないことを意味する。よってOut of index
としてパニックさせる。
意味付けとしては再帰版コードのほうがいい。
if let
before
let ptr = get_idx_ptr(&self.head, index, 0); if ptr.is_none() { panic!("Out of index. index値が大きすぎます。index: {}", index); } else { &((*ptr).as_ref().unwrap().item) }
after
if let Some(ref _n) = get_idx_ptr(&self.head, index, 0) { return &(_n.item) } else { panic!("Out of index. index値が大きすぎます。index: {}", index); }
完成版
/// 指定位置の参照を得る fn nth_ref(&self, index: usize) -> &T { fn get_ptr_from_idx<T>(ptr: &Option<Box<Node<T>>>, index: usize, count: usize) -> &Option<Box<Node<T>>> { if count < index { if ptr.is_some() { return get_ptr_from_idx(&ptr.as_ref().unwrap().next, index, count+1); } else { return ptr; } } else { return ptr; } } if let Some(ref _n) = get_ptr_from_idx(&self.head, index, 0) { return &(_n.item) } else { panic!("Out of index. index値が大きすぎます。index: {}", index); } }
再帰関数の名前を変えた。あとはそのまま。
- 指定位置もしくは末尾ノードへの不変参照を返す
- 1が存在すればそのノードがもつ
item
への不変参照を返す - 1が存在しなければパニックする
やっていることは簡単。でも、1行あたりの字数が多すぎて読むのが嫌になる。かといって1行あたりの字数を短くすれば、インデントが深化しすぎて嫌。悩ましいが、これまで通り、できるだけ1行にぶちこんだ。
テストコード
異常系
#[test] #[should_panic(expected = "Out of index. index値が大きすぎます。index: 0")] fn LinkedList_nth_ref_out_of_index_0() { let mut list: LinkedList<i32> = LinkedList::new(); list.nth_ref(0); assert_eq!(list.next(), None); let a = list.next(); assert_eq!(list.next(), None); list.push(0); } #[test] #[should_panic(expected = "Out of index. index値が大きすぎます。index: 1")] fn LinkedList_nth_ref_out_of_index_1() { let mut list: LinkedList<i32> = LinkedList::new(); list.push(0); list.nth_ref(1); }
正常系
#[test] fn LinkedList_nth_ref_1() { let mut list: LinkedList<i32> = LinkedList::new(); list.push(10); assert_eq!(*list.nth_ref(0), 10); } #[test] fn LinkedList_nth_ref_3() { let mut list: LinkedList<i32> = LinkedList::new(); list.push(10); list.push(11); list.push(12); let expecteds = vec![10, 11, 12]; for (i, expected) in expecteds.iter().enumerate() { assert_eq!(*(list.nth_ref(i)), *expected); } for (i, expected) in expecteds.iter().enumerate() { assert_eq!(*(list.nth_ref(i)), *expected); } }
ポイントは最後のテスト。2回目のfor
文にて同じ位置の要素を参照している。所有権がムーブされず、参照を返しているため、1度nth_ref
した要素でもリストに保存されたまま。何度でも参照できる。
残念ポイント
- 不変参照であるため、代入はできない
count()
のようにリスト内の要素数を知ることができない- もし最大値を超過したインデックスを指定したら
panic!
する
- もし最大値を超過したインデックスを指定したら
- 低パフォーマンス
- 毎回
index
の値+1回だけnext
を辿る なんという無駄。でもこれが連結リストであり線形リスト。
- 毎回
実用性はない。
疑問
スライス?
これってlist[0]
みたいにして参照するのと同じ処理だと思う。[index]
の演算子をオーバーライドするのって、どうやるんだろう? そもそも、できるのか?
スライスはスライス型という別の型っぽいし、その挙動を真似するにはどうすればいいんだ?
ベクター?
もしや、私が書いている連結リストはVec
の実装を再現しているだけなのか? じつは連結リストのメソッドなど書く必要はなく、最初からVec
を隠蔽して実装すべきものなのだろうか?
linked-list
crate.ioのサイトをみるとすでに連結リストがあった。
このコードをみれば勉強できるかも。
というか、今更ながら気づいたが標準モジュールに存在するのか?
だが、コードが見当たらない。以下のコードは正式なものなのだろうか? それとも個人が書いたもの?
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- rustc 1.34.2 (6c2484dc3 2019-05-13)
- cargo 1.34.0 (6789d8a0a 2019-04-01)
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux
前回まで
- Rust学習まとめ(ドキュメント)
- Rust自習(じゃんけんゲーム1)
- Rust自習(双方向リスト1)
- Rust自習(単方向リスト1)
- Rust自習(単方向リスト2)
- Rust自習(単方向リスト3)
- Rust自習(単方向リスト4)
- Rust自習(単方向リスト5)
- Rust自習(単方向リスト6)
- Rust自習(単方向リスト7)
- Rust自習(リストのインタフェースを考える)
- Rust自習(連結リスト1)
- Rust自習(連結リスト2)
- Rust自習(連結リスト3)
- Rust自習(連結リスト4)
- Rust自習(連結リストの取得系インタフェース考察)
- Rust自習(連結リスト5)
- Rust自習(連結リストの取得系インタフェース考察2)