






















While @jdahlstrom’s solution is a really elegant one, I would like to point out a more general perspective.
This is the first half of the problem, and the simpler one.
Basically, this is a nested sequence: a sequence of sequences, an array of arrays, two nested loops, or a generator function returning generators. There are many manifestations of this concept.
They all share the idea of flattening: somewhere in the program, you only see the inner item. In your example, that is the body of the nested for loop. That is one of many possible solutions, and it is perfectly fine.
This is the second half of the problem.
“When it changes” means that you are effectively processing the sequence in pairs. There is some function or logic that takes two elements from the (abstract) sequence as parameters.
And at the end, your implicit requirement is that this function or logic should still be called with the last element of the sequence as the first parameter, even though there is obviously no element available for the second parameter. That missing thing is the root cause of the awkward repeated if statement in your example.
A very old technique, with its origins, I think, in list processing, is the introduction of sentinel values at the end (and sometimes the beginning) of a sequence. These values can be concrete instances of the original element type, or special values.
An extremely widely adopted sentinel value, for example, is the terminating NULL byte in strings in many programming languages. It is so common that virtually nobody realizes that the NULL is a sentinel. ![]()
In a language like Rust, you can define better sentinel values by using enums, or simply use a pre-made enum like Option. So, given a (flattened) sequence, you add sentinels and then process it.
A more general example:
fn main() {
let sequence = [1, 2, 3];
let iter = sequence.iter().map(Some).chain([None]);
let mut last = None;
for item in iter {
match (last, item) {
(None, Some(x)) => println!("first: {x}"),
(Some(a), Some(b)) => println!("pair: ({a}, {b})"),
(Some(x), None) => println!("last: {x}"),
_ => unreachable!(),
}
last = item;
}
}
And more concretely for your example (note: your original code processed an empty buffer at the start; this one does not):
fn process_buffer(buf: &mut Vec<char>) {
println!("process_buffer: {buf:?}");
buf.clear();
}
fn char_language(c: char) -> u32 {
c as u32
}
fn main() {
let strs = ["aab", "bbc", "dda"];
let chars = strs.iter().flat_map(|s| s.chars());
let mut buf = Vec::new();
let mut prev_lang = None;
for c in chars.map(Some).chain([None]) {
let lang = c.map(char_language);
if lang != prev_lang && prev_lang.is_some() {
process_buffer(&mut buf);
}
if let Some(c) = c {
buf.push(c);
}
prev_lang = lang;
}
assert!(buf.is_empty());
}
The concept of sentinels can be generalized even further. Different sentinel-like values can be inserted into a sequence at different points to carry information forward to subsequent operations on that sequence.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。