diff --git a/Cargo.lock b/Cargo.lock index 9a49b34..af39369 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,61 @@ version = 4 [[package]] name = "leetcode-practice" version = "0.1.0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml index 55c2bea..73359fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,7 @@ [package] name= "leetcode-practice" version = "0.1.0" -edition = "2021" \ No newline at end of file +edition = "2021" + +[dependencies] +thiserror = "2.0.18" diff --git a/src/bin/step1.rs b/src/bin/step1.rs new file mode 100644 index 0000000..9664a48 --- /dev/null +++ b/src/bin/step1.rs @@ -0,0 +1,150 @@ +// Step1 +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 会議の開始時間、終了時間を表すInterval構造体からなる配列intervalsが入力として与えられるので、全ての会議が重複しないために必要な最小の会議室の数を返す。 + [(start1,end1), (start2,end2))] のとして、[(0,3), (0,2)] のとき必要な会議室の数は2になる。 + + 何を考えて解いていたか + - 全問と同じ考え方で、会議の開始時間でソートして重複を確認する。会議時間の重複を見つけるたびに必要な会議室の数をインクリメントしていけば良さそう。 + - intervalsが空であれば0を返すエッジケースの処理が必要そう。intervalsが空でなければ会議室の数は1からスタートするので。 + n = intervals.len() + 時間計算量: O(n log n) + 空間計算量: O(1) + 重複した時に、数え上げる方向で本当に良いのか不安を感じるので、コードを書く前にできる範囲でテストケースを考えて問題なさそうか確認する。 + [(0,5)] out=1 + [(0,5), (0,2)] out=2 + [(0,5), (5,7)] out=1 + [(0,5), (0,2), (5,7)] out=2 + [(0,5), (0,2), (1,5), (5,7)] out=3 + この考え方で大丈夫そうに見える。 + + 提出したところ、次のテストケースでWrong Answerとなった。 + intervals=[(1,5), (2,6), (3,7), (4,8), (5,9)] actual=5 expect=4 + (1,5), (5,9)をうまく処理できていないように見える。(1,5)の会議終わりで、(5,9)が使えるのにも関わらずインクリメントしてしまっている。 + つまり、会議の開始時間でソートして隣接する会議時間を見るだけでは正しい答えが得られないようにみえる。 + 他の解法が思いつかないので、step1a.rsで他の人のコードを見て解法を理解する。 + + 何がわからなかったか + - 単にソートして数え上げるだけでは対応できないケースがあることを見抜けなかった。 +*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidIntervalRange(i32, i32), + #[error("invalid interval. start and end must be greater than 0. start: {0}, end: {1}")] + InvalidIntervalValue(i32, i32), +} + +#[derive(Debug)] +pub struct Interval { + start: i32, + end: i32, +} +impl Interval { + pub fn new(start: i32, end: i32) -> Result { + if start < 0 || end < 0 { + return Err(IntervalError::InvalidIntervalValue(start, end)); + } + if end <= start { + return Err(IntervalError::InvalidIntervalRange(start, end)); + } + + Ok(Self { start, end }) + } +} + +pub struct Solution {} +impl Solution { + pub fn min_meeting_rooms(mut intervals: Vec) -> i32 { + /* + このコードはWrong Answerとなるコードです。 + intervals=[(1,5), (2,6), (3,7), (4,8), (5,9)] actual=5 expect=4 + */ + if intervals.is_empty() { + return 0; + } + + intervals.sort_by_key(|v| v.start); + + let mut min_required_meeting_rooms_count = 1; + for w in intervals.windows(2) { + let (interval, next_interval) = (&w[0], &w[1]); + if next_interval.start < interval.end { + min_required_meeting_rooms_count += 1; + } + } + + min_required_meeting_rooms_count + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (i32, i32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step1_conflict_intervals_test() { + let intervals = vec![(0, 40), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 2); + } + + #[test] + fn step1_no_conflict_intervals_test() { + let intervals = vec![(0, 40)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 1); + + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 0); + } + + #[test] + fn step1_invalid_interval_range_test() { + let (start, end) = (1, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + + let (start, end) = (10, 10); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + } + + #[test] + fn step1_invalid_interval_value_test() { + let (start, end) = (-1, 1); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + + let (start, end) = (-2, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + } +} diff --git a/src/bin/step1a.rs b/src/bin/step1a.rs new file mode 100644 index 0000000..72bcced --- /dev/null +++ b/src/bin/step1a.rs @@ -0,0 +1,173 @@ +// Step1a +// 目的: 答えを見て解法を理解する + +/* + https://www.youtube.com/watch?v=FdzJmTCVyJU + - NeetCodeの解法 + - 動画を見ていたら、heapを使う解法で解けそうなことに気付いた。 + - 利用中の会議室の数を会議時間の開始で+1,終了で-1する。 + - 会議が新たに開始する時に、必要な会議室の最大値を更新する。 + + 所感 + - この考え方で実装してAcceptedになったが、[(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)]をmin_heapにpush(),pop()したときの挙動を理解しきれていないことに気付いた。 + - 会議の開始、終了時間をそれぞれtupleの1つ目の要素としてmin_heapにpushしていくと、 + - [(1,1), (2,1), (3,1), (4,1), (5,1), (5,-1) ...]になると思っていた。tupleの1つ目の要素が同一の場合はpush順で並ぶと思い込んでいたため。実際には(5,-1)の方が先に来る。 + - [(1,1), (2,1), (3,1), (4,1), (5,-1), (5,1) ...]になっている。 + - この挙動のおかげで自身の書いたロジックが動いているようにみえる。 + - 公式ドキュメントにtupleのOrd,PartialOrdでの比較時には最初に等しくない値を見つけるまでタプルの値を順番に見ていくという記述があった。 + https://doc.rust-lang.org/std/primitive.tuple.html + > The sequential nature of the tuple applies to its implementations of various traits. For example, in PartialOrd and Ord, the elements are compared sequentially until the first non-equal set is found. + - この点を知らない状態で意識せずに実装していたので、たまたま正解した結果になってしまった。 + - この視点で見ると実装がパズルに見える。tuple比較時の挙動を正確に把握していないとロジックが動いている理由が正確に把握できないため。 +*/ + +use std::{cmp::Reverse, collections::BinaryHeap}; + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidIntervalRange(i32, i32), + #[error("invalid interval. start and end must be greater than 0. start: {0}, end: {1}")] + InvalidIntervalValue(i32, i32), +} + +#[derive(Debug)] +pub struct Interval { + start: i32, + end: i32, +} +impl Interval { + pub fn new(start: i32, end: i32) -> Result { + if start < 0 || end < 0 { + return Err(IntervalError::InvalidIntervalValue(start, end)); + } + if end <= start { + return Err(IntervalError::InvalidIntervalRange(start, end)); + } + + Ok(Self { start, end }) + } +} + +pub struct Solution {} +impl Solution { + pub fn min_meeting_rooms(intervals: Vec) -> i32 { + if intervals.is_empty() { + return 0; + } + + let mut time_to_use_room_count = BinaryHeap::new(); + for interval in intervals { + time_to_use_room_count.push(Reverse((interval.start, 1))); + time_to_use_room_count.push(Reverse((interval.end, -1))); + } + + let mut min_meeting_rooms_count = 0; + let mut using_meeting_rooms_count = 0; + while let Some(Reverse((_, use_room_count))) = time_to_use_room_count.pop() { + using_meeting_rooms_count += use_room_count; + + let use_meeting_room = use_room_count == 1; + if use_meeting_room { + min_meeting_rooms_count = min_meeting_rooms_count.max(using_meeting_rooms_count); + } + } + + min_meeting_rooms_count + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (i32, i32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn play_ground() { + let intervals = vec![(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + let mut min_heap = BinaryHeap::new(); + for interval in intervals { + min_heap.push(Reverse((interval.start, 1))); + min_heap.push(Reverse((interval.end, -1))); + } + + assert_eq!(Reverse((1, 1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((2, 1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((3, 1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((4, 1)), min_heap.pop().unwrap()); + // tupleは1つ目の要素T1が等しい時、次の要素T2で比較する。 + // なので、5が重複すると次の要素である-1,1の比較によって順序が決まる。 + // https://doc.rust-lang.org/std/primitive.tuple.html + assert_eq!(Reverse((5, -1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((5, 1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((6, -1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((7, -1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((8, -1)), min_heap.pop().unwrap()); + assert_eq!(Reverse((9, -1)), min_heap.pop().unwrap()); + } + + #[test] + fn step1a_conflict_intervals_test() { + let intervals = vec![(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 4); + + let intervals = vec![(0, 40), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 2); + } + + #[test] + fn step1a_no_conflict_intervals_test() { + let intervals = vec![(0, 40)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 1); + + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 0); + } + + #[test] + fn step1a_invalid_interval_range_test() { + let (start, end) = (1, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + + let (start, end) = (10, 10); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + } + + #[test] + fn step1a_invalid_interval_value_test() { + let (start, end) = (-1, 1); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + + let (start, end) = (-2, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs new file mode 100644 index 0000000..a29018d --- /dev/null +++ b/src/bin/step2.rs @@ -0,0 +1,180 @@ +// Step2 +// 目的: 自然な書き方を考えて整理する + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + 他の人のコードを読んで考えたこと + https://github.com/olsen-blue/Arai60/pull/57#discussion_r2030075157 + - 座標圧縮という知らない用語が出てきた。 + https://drken1215.hatenablog.com/entry/2021/08/09/235400 + 数列のそれぞれの要素が数列の中で何番目に小さいかということを求める作業のことを「座標圧縮」という。 + step1a.rsの解法では、(会議の開始時間,1), (会議の終了時間,-1) とすることで二次元座標圧縮しているように見える。 + こうやって見ると、Rustのtupleが1つ目の要素が等しい時に次の要素で比較するのは自然に見える。(5,1), (5,-1)のとき、5は等しいので次の値である1, -1で比較する。 + + https://github.com/olsen-blue/Arai60/pull/57/changes#diff-a0ae933995d3a32d66b233c1e96d7f1bbe7ff33e80eb0997d04a4806ba5d2be5R122 + - step1a.rsで自分が感じたtupleの暗黙的な並び替えの動きについては、heapを使わずにsort系のメソッドでpredicateを記述すれば明示的になるので分かりやすくなるなと思った。 + + https://github.com/nittoco/leetcode/pull/45#discussion_r1996403980 + > https://github.com/nittoco/leetcode/pull/45/changes/BASE..b5bdf0b884b8a18e951161ff3a59fe94d73b6c6a#diff-f58a8d93c7990227607003cbdcf9ac9bc8c009c853befff8a9f204036e1ce249R3 + - step1a.rsの自分と同じようなことを考えている人がいた。 + + https://github.com/Yoshiki-Iwasa/Arai60/pull/61/changes#diff-dc5acf3ef0f2cf3ed60df746eb0a49f46bd5e823ef000d992cd0c1ba7d9f34c0R8 + - (start,1), (end,-1)の1と-1はマジックナンバーなので、Enumで定義するとより分かりやすくなっていて良いと思った。 + + https://github.com/ryosuketc/leetcode_arai60/pull/56/changes#diff-ef32d0d747c317926cc64728bede34c3076b19b0207cb57561ab021fed93cc3fR45 + - このコードを読んでいて気付いたが、step1a.rsの自分のコードでif use_meeting_room は無くても動く。endの方が先に来ることを理解していなかったためだと思った。 + + https://github.com/Satorien/LeetCode/pull/55#discussion_r2658899716 + - RustのBinaryHeapのドキュメントを確認したところ、push()は最悪ケース(capacityの伸長)で時間計算量O(n)となるので、今回の問題のようにsort()で事足りるのであれば、闇雲にheapを使うべきではないと思った。 + sort()を利用しておけば時間計算量O(n log n)になることが分かりやすくシンプルなので。 + + step1a.rsから改善する時に考えたこと + - min_meeting_rooms_count -> min_required_rooms_countの方が良いと思った。 + - requiredを含めたほうが「必要な最小の」という意味が分かりやすいと思った。 + - meetingはなくても良いかなと思って削った。部屋の中で何をするのかは関係なくて必要な部屋の数がわかればよいので。 + - BinaryHeapを使うのを止めて、sort()とfor-loopで書く。 + - 会議の開始、終了に対応する1,-1がマジックナンバーなのでEnumで表す。 + - 不要な条件分岐をなくす。 + + 所感 + - Enumを活用することで利用中の会議室の増減とイベントがひと目見て分かるようになり読みやすくなったと思う。 + - MeetingEventでEndの方が小さくなることをEnumの定義順で決めているところが分かりづらい気がするものの代替案は特に思い浮かばない。 + - MeetingEvent::Startを先頭に書いてPartialOrdトレイトを手動で実装してもボイラープレートなコードが増えるだけでかえって読みづらくなりそう。 +*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidIntervalRange(i32, i32), + #[error("invalid interval. start and end must be greater than 0. start: {0}, end: {1}")] + InvalidIntervalValue(i32, i32), +} + +#[derive(Debug)] +pub struct Interval { + start: i32, + end: i32, +} +impl Interval { + pub fn new(start: i32, end: i32) -> Result { + if start < 0 || end < 0 { + return Err(IntervalError::InvalidIntervalValue(start, end)); + } + if end <= start { + return Err(IntervalError::InvalidIntervalRange(start, end)); + } + + Ok(Self { start, end }) + } +} + +#[derive(PartialEq, PartialOrd, Eq, Ord)] +enum MeetingEvent { + End, + Start, +} + +pub struct Solution {} +impl Solution { + pub fn min_meeting_rooms(intervals: Vec) -> i32 { + let mut meeting_events = intervals + .iter() + .flat_map(|interval| { + vec![ + (interval.end, MeetingEvent::End), + (interval.start, MeetingEvent::Start), + ] + }) + .collect::>(); + meeting_events.sort(); + + let mut min_required_rooms_count = 0; + let mut using_rooms_count = 0; + for (_, meeting_event) in meeting_events { + match meeting_event { + MeetingEvent::End => using_rooms_count -= 1, + MeetingEvent::Start => using_rooms_count += 1, + }; + min_required_rooms_count = min_required_rooms_count.max(using_rooms_count); + } + + min_required_rooms_count + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (i32, i32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step2_conflict_intervals_test() { + let intervals = vec![(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 4); + + let intervals = vec![(0, 40), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 2); + } + + #[test] + fn step2_no_conflict_intervals_test() { + let intervals = vec![(0, 40)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 1); + + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 0); + } + + #[test] + fn step2_invalid_interval_range_test() { + let (start, end) = (1, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + + let (start, end) = (10, 10); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + } + + #[test] + fn step2_invalid_interval_value_test() { + let (start, end) = (-1, 1); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + + let (start, end) = (-2, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs new file mode 100644 index 0000000..9e7866a --- /dev/null +++ b/src/bin/step3.rs @@ -0,0 +1,150 @@ +// Step3 +// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する + +// 方法 +// 時間を測りながらもう一度解く +// 10分以内に一度もエラーを吐かず正解 +// これを3回連続でできたら終わり +// レビューを受ける +// 作れないデータ構造があった場合は別途自作すること + +/* + n = intervals.len() + 時間計算量: O(n log n) + 空間計算量: O(n) +*/ + +/* + enum MeetingEvent,min_meeting_roomsの実装のみ + 1回目: 3分11秒 + 2回目: 2分49秒 + 3回目: 3分10秒 +*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidIntervalRange(i32, i32), + #[error("invalid interval. start and end must be greater than 0. start: {0}, end: {1}")] + InvalidIntervalValue(i32, i32), +} + +#[derive(Debug)] +pub struct Interval { + start: i32, + end: i32, +} +impl Interval { + pub fn new(start: i32, end: i32) -> Result { + if start < 0 || end < 0 { + return Err(IntervalError::InvalidIntervalValue(start, end)); + } + if end <= start { + return Err(IntervalError::InvalidIntervalRange(start, end)); + } + + Ok(Self { start, end }) + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +enum MeetingEvent { + End, + Start, +} + +pub struct Solution {} +impl Solution { + pub fn min_meeting_rooms(intervals: Vec) -> i32 { + let mut meeting_events = intervals + .iter() + .flat_map(|interval| { + vec![ + (interval.end, MeetingEvent::End), + (interval.start, MeetingEvent::Start), + ] + }) + .collect::>(); + meeting_events.sort(); + + let mut min_required_rooms_count = 0; + let mut using_rooms_count = 0; + for (_, meeting_event) in meeting_events { + match meeting_event { + MeetingEvent::End => using_rooms_count -= 1, + MeetingEvent::Start => using_rooms_count += 1, + } + min_required_rooms_count = min_required_rooms_count.max(using_rooms_count); + } + + min_required_rooms_count + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (i32, i32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step3_conflict_intervals_test() { + let intervals = vec![(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 4); + + let intervals = vec![(0, 40), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 2); + } + + #[test] + fn step3_no_conflict_intervals_test() { + let intervals = vec![(0, 40)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 1); + + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::min_meeting_rooms(intervals), 0); + } + + #[test] + fn step3_invalid_interval_range_test() { + let (start, end) = (1, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + + let (start, end) = (10, 10); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalRange(start, end) + ); + } + + #[test] + fn step3_invalid_interval_value_test() { + let (start, end) = (-1, 1); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + + let (start, end) = (-2, 0); + assert_eq!( + Interval::new(start, end).unwrap_err(), + IntervalError::InvalidIntervalValue(start, end) + ); + } +}