Add Record::remove/retain/retain_mut (#10876)

# Description
While we have now a few ways to add items or iterate over the
collection, we don't have a way to cleanly remove items from `Record`.

This PR fixes that:

- Add `Record.remove()` to remove by key
- makes the assumption that keys are unique, so can not be used
universally, yet (see #10875 for an important example)
- Add naive `Record.retain()` for inplace removal
- This follows the two separate `retain`/`retain_mut` in the Rust std
library types, compared to the value-mutating `retain` in `indexmap`
- Add `Record.retain_mut()` for one-pass pruning

Continuation of #10841 

# User-Facing Changes
None yet.

# Tests + Formatting
Doctests for the `retain`ing fun
This commit is contained in:
Stefan Holderbach 2023-10-30 19:51:28 +01:00 committed by GitHub
parent 72f7b9b7cc
commit 005180f269
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -94,6 +94,104 @@ impl Record {
Some((self.cols.get(idx)?, self.vals.get(idx)?))
}
/// Remove single value by key
///
/// Returns `None` if key not found
///
/// Note: makes strong assumption that keys are unique
pub fn remove(&mut self, col: impl AsRef<str>) -> Option<Value> {
let idx = self.index_of(col)?;
self.cols.remove(idx);
Some(self.vals.remove(idx))
}
/// Remove elements in-place that do not satisfy `keep`
///
/// Note: Panics if `vals.len() > cols.len()`
/// ```rust
/// use nu_protocol::{record, Value};
///
/// let mut rec = record!(
/// "a" => Value::test_nothing(),
/// "b" => Value::test_int(42),
/// "c" => Value::test_nothing(),
/// "d" => Value::test_int(42),
/// );
/// rec.retain(|_k, val| !val.is_nothing());
/// let mut iter_rec = rec.columns();
/// assert_eq!(iter_rec.next().map(String::as_str), Some("b"));
/// assert_eq!(iter_rec.next().map(String::as_str), Some("d"));
/// assert_eq!(iter_rec.next(), None);
/// ```
pub fn retain<F>(&mut self, mut keep: F)
where
F: FnMut(&str, &Value) -> bool,
{
self.retain_mut(|k, v| keep(k, v));
}
/// Remove elements in-place that do not satisfy `keep` while allowing mutation of the value.
///
/// This can for example be used to recursively prune nested records.
///
/// Note: Panics if `vals.len() > cols.len()`
/// ```rust
/// use nu_protocol::{record, Record, Value};
///
/// fn remove_foo_recursively(val: &mut Value) {
/// if let Value::Record {val, ..} = val {
/// val.retain_mut(keep_non_foo);
/// }
/// }
///
/// fn keep_non_foo(k: &str, v: &mut Value) -> bool {
/// if k == "foo" {
/// return false;
/// }
/// remove_foo_recursively(v);
/// true
/// }
///
/// let mut test = Value::test_record(record!(
/// "foo" => Value::test_nothing(),
/// "bar" => Value::test_record(record!(
/// "foo" => Value::test_nothing(),
/// "baz" => Value::test_nothing(),
/// ))
/// ));
///
/// remove_foo_recursively(&mut test);
/// let expected = Value::test_record(record!(
/// "bar" => Value::test_record(record!(
/// "baz" => Value::test_nothing(),
/// ))
/// ));
/// assert_eq!(test, expected);
/// ```
pub fn retain_mut<F>(&mut self, mut keep: F)
where
F: FnMut(&str, &mut Value) -> bool,
{
let mut idx = 0;
// `Vec::retain` is able to optimize memcopies internally. For maximum benefit as `Value`
// is a larger struct than `String` use `retain` on `vals`
//
// The calls to `Vec::remove` are suboptimal as they need memcopies to shift each time.
//
// As the operations should remain inplace, we don't allocate a separate index `Vec` which
// could be used to avoid the repeated shifting of `Vec::remove` in cols.
self.vals.retain_mut(|val| {
if keep(self.cols[idx].as_str(), val) {
idx += 1;
true
} else {
self.cols.remove(idx);
false
}
});
}
pub fn columns(&self) -> Columns {
Columns {
iter: self.cols.iter(),