Two Bucket
1. Readme
两个桶
给定两个不同尺寸的桶,演示如何通过两个桶,策略性地传输液体来测量精确的升数.
由于这个数学问题,很容易受到解释/个体方法的影响,因此这些测试代码专门针对期望一个总体解决方案而编写.
为了提供帮助,测试代码首先为桶(1/2)装满。这意味着,若开始时,选择较大的桶装满,那就不会出现,较小的桶装满而较大的桶为空的情况。(也就是说,起点应该用较小桶);不然这会破坏比较两种方法的目的!
您的程序将作为输入:
- 桶 1 的大小
- 桶 2 的大小
- 要达到的理想升数
- 首先要装满哪个桶,要么是桶 1 还是桶 2
您的计划应确定:
- 达到所需的升数,所需的”移动”总数,包括第一次装满
- 哪个桶应该以所需的升数结束(假设这是桶 A) - 要么桶 1 ,要么桶 2
- 另一个桶中,剩下多少升(桶 B)
注意:任何时候对其中一个或两个桶进行更改,都计为一(1)次移动。
示例:桶 1 最多可容纳 7 升,桶 2 最多可容纳 11 升。让我们说桶 1,第一步,持有 7 升,桶 2 持有 8 升(7,8)。如果您清空桶 1,并且不对桶 2 进行任何更改,则分别为 0 升和 8 升(0,8),这将作为一个”移动”。相反,如果你已经把桶 1 倒入桶 2,直到桶 2 充满,那留下的,桶 1 中的 4 升和桶 2 中的 11 升(4,11),这也算作只有一个”移动”。
总而言之,唯一有效的行动是:
- 从一个桶,倒到另一个桶
- 清空一个桶,对另一个什么都不做
- 装满一个桶,对另一个什么也不做
写<3 于全栈教程 Academy作 者:Lindsay Levine.
资源
水浇注问题http://demonstrations.wolfram.com/WaterPouringProblem/
2. 开始你的表演
#[derive(PartialEq, Eq, Debug)] pub enum Bucket { One, Two, } /// A struct to hold your results in. #[derive(PartialEq, Eq, Debug)] pub struct BucketStats { /// The total number of "moves" it should take to reach the desired number of liters, including /// the first fill. pub moves: u8, /// Which bucket should end up with the desired number of liters? (Either "one" or "two") pub goal_bucket: Bucket, /// How many liters are left in the other bucket? pub other_bucket: u8, } /// Solve the bucket problem pub fn solve(capacity_1: u8, capacity_2: u8, goal: u8, start_bucket: &Bucket) -> BucketStats { unimplemented!( "Given one bucket of capacity {}, another of capacity {}, starting with {:?}, find pours to reach {}", capacity_1, capacity_2, start_bucket, goal, ); }
3. 测试代码查看
# #![allow(unused_variables)] #fn main() { #[test] fn test_case_1() { assert_eq!( solve(3, 5, 1, &Bucket::One), BucketStats { moves: 4, goal_bucket: Bucket::One, other_bucket: 5, } ); } #[test] //#[ignore] fn test_case_2() { assert_eq!( solve(3, 5, 1, &Bucket::Two), BucketStats { moves: 8, goal_bucket: Bucket::Two, other_bucket: 3, } ); } #[test] //#[ignore] fn test_case_3() { assert_eq!( solve(7, 11, 2, &Bucket::One), BucketStats { moves: 14, goal_bucket: Bucket::One, other_bucket: 11, } ); } #[test] //#[ignore] fn test_case_4() { assert_eq!( solve(7, 11, 2, &Bucket::Two), BucketStats { moves: 18, goal_bucket: Bucket::Two, other_bucket: 7, } ); } #[test] //#[ignore] fn test_case_5() { assert_eq!( solve(1, 3, 3, &Bucket::Two), BucketStats { moves: 1, goal_bucket: Bucket::Two, other_bucket: 0, } ); } #[test] //#[ignore] fn test_case_6() { assert_eq!( solve(2, 3, 3, &Bucket::One), BucketStats { moves: 2, goal_bucket: Bucket::Two, other_bucket: 2, } ); } #}
4. 答案
# #![allow(unused_variables)] #fn main() { use std::collections::{HashSet, VecDeque}; // We can turn this problem into a simple graph searching problem. Each move represents an // edge in our graph; the buckets' fill states form the graph's vertices. For example, say bucket // one holds up to 7 liters, and bucket two holds up to 11 liters, and at the current step, bucket // one has 7 liters and bucket two has 8 liters: (7, 8). By emptying the first bucket, we form an // edge (7, 8) -> (0, 8). Similarly, by pouring the first bucket into the second, we form an edge // (7, 8) -> (4, 11). // // Since we want to minimize the number of moves, we search the graph breadth-first, starting with // the configuration provided as the problem input. Note that, to avoid cheating, we mark both // possible starting configurations as visited; otherwise, our search might just empty the initial // bucket and fill the other one. #[derive(PartialEq, Eq, Debug)] pub enum Bucket { One, Two, } /// A struct to hold your results in. #[derive(PartialEq, Eq, Debug)] pub struct BucketStats { /// The total number of "moves" it should take to reach the desired number of liters, including /// the first fill. pub moves: u8, /// Which bucket should end up with the desired number of liters? (Either "one" or "two") pub goal_bucket: Bucket, /// How many liters are left in the other bucket? pub other_bucket: u8, } /// Solve the bucket problem pub fn solve(capacity_1: u8, capacity_2: u8, goal: u8, start_bucket: &Bucket) -> BucketStats { let state = match *start_bucket { Bucket::One => (capacity_1, 0), Bucket::Two => (0, capacity_2), }; let mut next_search = VecDeque::new(); let mut visited = HashSet::new(); let mut moves = 1; next_search.push_front(state); // "Visit" both starting states. This will ensure that we don't cheat, i.e. // empty our starting bucket completely and fill the other bucket. visited.insert((capacity_1, 0)); visited.insert((0, capacity_2)); loop { let mut current_search = next_search; next_search = VecDeque::new(); for state in current_search.drain(0..) { let (bucket_1, bucket_2) = state; if bucket_1 == goal { return BucketStats { moves: moves, goal_bucket: Bucket::One, other_bucket: bucket_2, }; } else if bucket_2 == goal { return BucketStats { moves: moves, goal_bucket: Bucket::Two, other_bucket: bucket_1, }; } // Empty the first bucket let empty_1 = (0, bucket_2); if !visited.contains(&empty_1) { next_search.push_front(empty_1); visited.insert(empty_1); } // Empty the second bucket let empty_2 = (bucket_1, 0); if !visited.contains(&empty_2) { next_search.push_front(empty_2); visited.insert(empty_2); } // Fill the first bucket let fill_1 = (capacity_1, bucket_2); if !visited.contains(&fill_1) { next_search.push_front(fill_1); visited.insert(fill_1); } // Fill the second bucket let fill_2 = (bucket_1, capacity_2); if !visited.contains(&fill_2) { next_search.push_front(fill_2); visited.insert(fill_2); } // Pour the first bucket into the second bucket let pour_1_into_2 = if bucket_1 + bucket_2 <= capacity_1 { (bucket_1 + bucket_2, 0) } else { (capacity_1, bucket_1 + bucket_2 - capacity_1) }; if !visited.contains(&pour_1_into_2) { next_search.push_front(pour_1_into_2); visited.insert(pour_1_into_2); } // Pour the second bucket into the first bucket let pour_2_into_1 = if bucket_1 + bucket_2 <= capacity_2 { (0, bucket_1 + bucket_2) } else { (bucket_1 + bucket_2 - capacity_2, capacity_2) }; if !visited.contains(&pour_2_into_1) { next_search.push_front(pour_2_into_1); visited.insert(pour_2_into_1); } } moves += 1; } } #}