有条件地修改 Vec 的可选元素的最惯用的 Rust 方法是什么?
What's the most idiomatic Rust way to modify an optional element of a Vec conditionally?
我对编写以下非常常见的代码的最佳方式感到困惑:
let old_best = best_by_pos[y][x].as_ref();
if old_best.is_none() || &new_cost < old_best.unwrap() {
best_by_pos[y][x] = Some(new_cost.clone());
}
这只是一个代码示例,但它说明了问题。
best_by_pos
是一个Vec<Vec<Option<BigInt>>>
;当我们找到最佳成本的新可能性时,我们想要 (a) 检查新成本是否优于旧成本,以及 (b) 如果是,则更新向量。
问题是 old_best
不可变地借用 best_by_pos
,并且借用一直持续到范围结束。这可以防止 if 块内的突变。理想情况下,我想在测试后立即释放 old_best
,但不清楚该怎么做。
有一种非常丑陋的方法可以做到这一点 -- 创建一个更深的范围来进行测试并公开一个布尔值,然后对其进行条件处理。这很实用但令人不快。
或者,我可以制作一个辅助方法来进行比较(并在它终止时释放它的借用),它看起来更干净,但仍然感觉臃肿。
有没有更简洁的方法来实现这一点?
Option<T>
为所有类型 T
实现 Ord
,实现 Ord
,这样 None
小于 Some(v)
对于任何 v
。您可以这样编写代码:
if best_by_pos[y][x].is_none() || Some(&new_cost) < best_by_pos[y][x].as_ref() {
best_by_pos[y][x] = Some(new_cost.clone());
}
考虑到interjay的答案,也可以写成
let cost = &mut best_by_pos[y][x];
if cost.is_none() || Some(&new_cost) < cost.as_ref() {
*cost = Some(new_cost.clone());
}
您可以使 old_best
成为矢量的可变引用,并在赋值中写入它。这也允许您避免再次索引向量:
let old_best = &mut best_by_pos[y][x];
if old_best.is_none() || &new_cost < old_best.as_ref().unwrap() {
*old_best = Some(new_cost.clone());
}
如果您必须使用 Vec
,这样的事情可以避免任何明确的 unwrap
s:
let slot = &mut best_by_pos[y][x];
let is_better = slot.as_ref().map_or(true, |old_cost| &new_cost < old_cost);
if is_better {
*slot = Some(new_cost.clone());
}
这仍然持有对向量的可变借用,因此您需要将其包装在一个范围内。
另一种可能性是一些不太常见的模式语法:
match best_by_pos[y][x] {
ref mut entry @ None => *entry = Some(new_cost.clone()),
Some(ref mut entry) => {
if &new_cost < entry {
*entry = new_cost.clone();
}
}
}
猜测,根据向量中 Option
的用法,我建议您 不要使用 Vec
。相反,一个 HashMap
可以更好地表示稀疏数组的概念。此外,您还可以使用条目 API:
use std::collections::HashMap;
use std::collections::hash_map::Entry;
let mut best_by_pos: HashMap<(usize, usize), BigInt> = Default::default();
match best_by_pos.entry((x, y)) {
Entry::Vacant(e) => {
e.insert(new_cost.clone());
}
Entry::Occupied(mut e) => {
if &new_cost < e.get() {
e.insert(new_cost.clone());
}
}
}
HashMap
可以相当简洁,如果你使用其他Entry
方法,and_modify
和or_insert
:
use std::collections::HashMap;
fn main() {
let mut best_pos: HashMap<(usize, usize), f64> = Default::default();
let newprice = 60.0;
best_pos
.entry((1, 2))
.and_modify(|e| *e = e.min(newprice))
.or_insert(newprice);
}
我对编写以下非常常见的代码的最佳方式感到困惑:
let old_best = best_by_pos[y][x].as_ref();
if old_best.is_none() || &new_cost < old_best.unwrap() {
best_by_pos[y][x] = Some(new_cost.clone());
}
这只是一个代码示例,但它说明了问题。
best_by_pos
是一个Vec<Vec<Option<BigInt>>>
;当我们找到最佳成本的新可能性时,我们想要 (a) 检查新成本是否优于旧成本,以及 (b) 如果是,则更新向量。
问题是 old_best
不可变地借用 best_by_pos
,并且借用一直持续到范围结束。这可以防止 if 块内的突变。理想情况下,我想在测试后立即释放 old_best
,但不清楚该怎么做。
有一种非常丑陋的方法可以做到这一点 -- 创建一个更深的范围来进行测试并公开一个布尔值,然后对其进行条件处理。这很实用但令人不快。
或者,我可以制作一个辅助方法来进行比较(并在它终止时释放它的借用),它看起来更干净,但仍然感觉臃肿。
有没有更简洁的方法来实现这一点?
Option<T>
为所有类型 T
实现 Ord
,实现 Ord
,这样 None
小于 Some(v)
对于任何 v
。您可以这样编写代码:
if best_by_pos[y][x].is_none() || Some(&new_cost) < best_by_pos[y][x].as_ref() {
best_by_pos[y][x] = Some(new_cost.clone());
}
考虑到interjay的答案,也可以写成
let cost = &mut best_by_pos[y][x];
if cost.is_none() || Some(&new_cost) < cost.as_ref() {
*cost = Some(new_cost.clone());
}
您可以使 old_best
成为矢量的可变引用,并在赋值中写入它。这也允许您避免再次索引向量:
let old_best = &mut best_by_pos[y][x];
if old_best.is_none() || &new_cost < old_best.as_ref().unwrap() {
*old_best = Some(new_cost.clone());
}
如果您必须使用 Vec
,这样的事情可以避免任何明确的 unwrap
s:
let slot = &mut best_by_pos[y][x];
let is_better = slot.as_ref().map_or(true, |old_cost| &new_cost < old_cost);
if is_better {
*slot = Some(new_cost.clone());
}
这仍然持有对向量的可变借用,因此您需要将其包装在一个范围内。
另一种可能性是一些不太常见的模式语法:
match best_by_pos[y][x] {
ref mut entry @ None => *entry = Some(new_cost.clone()),
Some(ref mut entry) => {
if &new_cost < entry {
*entry = new_cost.clone();
}
}
}
猜测,根据向量中 Option
的用法,我建议您 不要使用 Vec
。相反,一个 HashMap
可以更好地表示稀疏数组的概念。此外,您还可以使用条目 API:
use std::collections::HashMap;
use std::collections::hash_map::Entry;
let mut best_by_pos: HashMap<(usize, usize), BigInt> = Default::default();
match best_by_pos.entry((x, y)) {
Entry::Vacant(e) => {
e.insert(new_cost.clone());
}
Entry::Occupied(mut e) => {
if &new_cost < e.get() {
e.insert(new_cost.clone());
}
}
}
HashMap
可以相当简洁,如果你使用其他Entry
方法,and_modify
和or_insert
:
use std::collections::HashMap;
fn main() {
let mut best_pos: HashMap<(usize, usize), f64> = Default::default();
let newprice = 60.0;
best_pos
.entry((1, 2))
.and_modify(|e| *e = e.min(newprice))
.or_insert(newprice);
}