Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/suggest/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 56 kB image not shown  

Quelle  util.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

use crate::Result;

/// Given a list of keywords for a suggestion, returns a phrase that best
/// completes the user's query. This function uses two heuristics to pick the
/// best match:
///
/// 1. Find the first keyword in the list that has at least one more word than
///    the query, then trim the keyword up to the end of that word.
/// 2. If there isn't a keyword with more words, pick the keyword that forms the
///    longest suffix of the query. This might be the query itself.
pub fn full_keyword(query: &str, keywords: &[impl AsRef<str>]) -> String {
    let query_words_len = query.split_whitespace().count();
    let min_phrase_words_len = if query.ends_with(char::is_whitespace) {
        // If the query ends with a space, find a keyword with at least one more
        // word, so that the completed phrase can show a word after the space.
        query_words_len + 1
    } else {
        query_words_len
    };
    keywords
        .iter()
        .map(AsRef::as_ref)
        .filter(|phrase| phrase.starts_with(query))
        .map(|phrase| phrase.split_whitespace().collect::<Vec<_>>())
        .find(|phrase_words| phrase_words.len() > min_phrase_words_len)
        .map(|phrase_words| phrase_words[..min_phrase_words_len].join(" "))
        .unwrap_or_else(|| {
            keywords
                .iter()
                .map(AsRef::as_ref)
                .filter(|phrase| phrase.starts_with(query) && query.len() < phrase.len())
                .max_by_key(|phrase| phrase.trim().len())
                .unwrap_or(query)
                .to_owned()
        })
}

/// Performs a depth-first traversal over all possible chunk sequences in a
/// slice, applies a filter-map function to each chunk in each sequence, and
/// collects the filter-mapped sequences in a `Vec`.
///
/// "Chunks" are non-overlapping subslices of the parent slice as described in
/// [`slice::chunks()`].
///
/// WARNING: This function potentially does an exponential amount of work! You
/// should always be careful to prune the traversal space by returning `None`
/// from your mappper function, as described further below, when a chunk does
/// not match what you are searching for.
///
/// `max_chunk_size` controls the maximum chunk size (in number of words), which
/// influences the branching factor at each step in the traversal.
///
/// At each traversal step, the filter-map function is called like:
/// `f(chunk, chunk_index, chunk_size, path)`.
///
/// `chunk` is the chunk at that step, `chunk_index` is its index in the parent
/// `words` slice, and `chunk_size` is its size in words. The function can map
/// the chunk to one or more values. Each value expands the branching factor at
/// the current step by `max_chunk_size`. In other words, the branching factor
/// at a given traversal step is `max_chunk_size` multiplied by the number of
/// values returned by the filter-map function at that step. `path` is the path
/// of mapped values that has been travsersed at that step: a sequence of mapped
/// values corresponding to chunks in the parent `words` slice.
///
/// The filter-map function can return `None` to halt traversal at the current
/// step. Returning `None` sets the branching factor at that step to zero,
/// pruning the subtree rooted at that step from the traversal space and
/// discarding the path from the output. This is important for keeping traversal
/// reasonably bounded.
///
/// Traversal ends and the function returns when all paths have been visited.
/// The returned `Vec` will contain all traversal paths that weren't pruned.
///
/// # Examples
///
/// Mapping chunks in `["a", "b", "c"]` to uppercase, up to a max chunk size of
/// `3`:
///
/// ```
/// # use suggest::util::filter_map_chunks;
/// let paths = filter_map_chunks(&["a", "b", "c"], 3, |chunk, _, _| {
///     Ok(Some(vec![chunk.to_uppercase()]))
/// });
/// assert_eq!(paths.unwrap(), vec![
///     vec!["A", "B", "C"],
///     vec!["A", "B C"],
///     vec!["A B", "C"],
///     vec!["A B C"]
/// ]);
/// ```
///
/// Same as previous but using `chunk_index` in the filter-map function to prune
/// paths that don't start with `"a"`:
///
/// ```
/// # use suggest::util::filter_map_chunks;
/// let paths = filter_map_chunks(&["a", "b", "c"], 3, |chunk, chunk_index, _| {
///     if chunk_index > 0 || chunk == "a" {
///         Ok(Some(vec![chunk.to_uppercase()]))
///     } else {
///         Ok(None)
///     }
/// });
/// assert_eq!(paths.unwrap(), vec![
///     vec!["A", "B", "C"],
///     vec!["A", "B C"],
/// ]);
/// ```
///
/// Same as the first example but using `path` in the filter-map function to
/// prune paths that include "A B":
///
/// ```
/// # use suggest::util::filter_map_chunks;
/// let paths = filter_map_chunks(&["a", "b", "c"], 3, |chunk, _, path| {
///     if path.iter().any(|value| value == "A B") {
///         Ok(None)
///     } else {
///         Ok(Some(vec![chunk.to_uppercase()]))
///     }
/// });
/// assert_eq!(paths.unwrap(), vec![
///     vec!["A", "B", "C"],
///     vec!["A", "B C"],
///     vec!["A B C"],
/// ]);
/// ```
///
/// Mapping each chunk to multiple values:
///
/// ```
/// # use suggest::util::filter_map_chunks;
/// let paths = filter_map_chunks(&["a", "b", "c"], 3, |chunk, _, _| {
///     Ok(Some(vec![format!("{chunk}0"), format!("{chunk}1")]))
/// });
/// assert_eq!(paths.unwrap(), vec![
///     vec!["a0", "b0", "c0"],
///     vec!["a0", "b0", "c1"],
///     vec!["a0", "b1", "c0"],
///     vec!["a0", "b1", "c1"],
///     vec!["a0", "b c0"],
///     vec!["a0", "b c1"],
///     vec!["a1", "b0", "c0"],
///     vec!["a1", "b0", "c1"],
///     vec!["a1", "b1", "c0"],
///     vec!["a1", "b1", "c1"],
///     vec!["a1", "b c0"],
///     vec!["a1", "b c1"],
///     vec!["a b0", "c0"],
///     vec!["a b0", "c1"],
///     vec!["a b1", "c0"],
///     vec!["a b1", "c1"],
///     vec!["a b c0"],
///     vec!["a b c1"]
/// ]);
/// ```
pub fn filter_map_chunks<T: Clone>(
    words: &[&str],
    max_chunk_size: usize,
    f: impl Fn(&str, usize, &[T]) -> Result<Option<Vec<T>>>,
) -> Result<Vec<Vec<T>>> {
    let normalized_query = words.join(" ");
    filter_map_chunks_recurse(words, &normalized_query, &mut vec![], 0, max_chunk_size, &f)
}

/// `remaining_words` is the slice of remaining words in the query string at
/// this step. `remaining_query` is the remaining slice of the normalized query
/// string at this step.
///
/// `path` is the sequence of values returned by the filter-map function so far
/// at this step.
///
/// `chunk_index` is the word-based index in the query string at this step.
fn filter_map_chunks_recurse<T: Clone>(
    remaining_words: &[&str],
    remaining_query: &str,
    path: &mut Vec<T>,
    chunk_index: usize,
    max_chunk_size: usize,
    f: &impl Fn(&str, usize, &[T]) -> Result<Option<Vec<T>>>,
) -> Result<Vec<Vec<T>>> {
    // Filtered-in (non-pruned) paths that will be returned from this step of
    // the traversal.
    let mut this_step_paths: Vec<Vec<T>> = vec![];

    for chunk_size in 1..=max_chunk_size {
        if remaining_words.len() < chunk_size {
            // `chunk_size` and the later chunk sizes in this for-loop are too
            // big to visit the remaining words. We already visited them earlier
            // in the loop when the chunk size was small enough.
            break;
        }

        // Get the current chunk within the remaining query. Its char length is
        // the sum of the lengths of the words in the chunk + `chunk_size - 1`
        // spaces between the words. There will only be one space between each
        // word in `remaining_query` because `remaining_query` is normalized.
        let chunk_char_len = remaining_words[..chunk_size]
            .iter()
            .fold(chunk_size - 1, |memo, w| memo + w.len());
        let chunk = &remaining_query[..chunk_char_len];

        // Call the mapper function.
        if let Some(mapped_values) = f(chunk, chunk_index, &path[..])? {
            for value in mapped_values {
                if chunk_size == remaining_words.len() {
                    // This is the final chunk in the path. Stop recursing.
                    this_step_paths.push(vec![value.clone()]);
                } else {
                    // Recurse. Note that the new `remaining_words` slice won't
                    // be empty because if it were, `chunk_size` would equal the
                    // remaining word count, which is if-branch condition.
                    path.push(value.clone());
                    let subtree_paths = filter_map_chunks_recurse(
                        &remaining_words[chunk_size..],
                        &remaining_query[(chunk_char_len + 1)..],
                        path,
                        chunk_index + chunk_size,
                        max_chunk_size,
                        f,
                    )?;
                    path.pop();
                    for mut p in subtree_paths {
                        p.insert(0, value.clone());
                        this_step_paths.push(p);
                    }
                }
            }
        }
    }

    Ok(this_step_paths)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn keywords_with_more_words() {
        assert_eq!(
            full_keyword(
                "moz",
                &[
                    "moz",
                    "mozi",
                    "mozil",
                    "mozill",
                    "mozilla",
                    "mozilla firefox"
                ]
            ),
            "mozilla".to_owned(),
        );
        assert_eq!(
            full_keyword(
                "mozilla",
                &[
                    "moz",
                    "mozi",
                    "mozil",
                    "mozill",
                    "mozilla",
                    "mozilla firefox"
                ]
            ),
            "mozilla".to_owned(),
        );
    }

    #[test]
    fn keywords_with_longer_phrase() {
        assert_eq!(
            full_keyword("moz", &["moz", "mozi", "mozil", "mozill", "mozilla"]),
            "mozilla".to_owned()
        );
        assert_eq!(
            full_keyword(
                "mozilla f",
                &["moz", "mozi", "mozil", "mozill", "mozilla firefox"]
            ),
            "mozilla firefox".to_owned()
        );
    }

    #[test]
    fn query_ends_with_space() {
        assert_eq!(
            full_keyword(
                "mozilla ",
                &["moz", "mozi", "mozil", "mozill", "mozilla firefox"]
            ),
            "mozilla firefox".to_owned()
        );
    }

    fn fmc<T: Clone>(
        query: &str,
        max_chunk_size: usize,
        f: impl Fn(&str, usize, &[T]) -> Result<Option<Vec<T>>>,
    ) -> Result<Vec<Vec<T>>> {
        let words: Vec<_> = query.split_whitespace().collect();
        filter_map_chunks(&words, max_chunk_size, f)
    }

    fn check_paths(actual: Vec<Vec<(String, usize)>>, expected: Vec<Vec<(&str, usize)>>) {
        assert_eq!(
            actual,
            expected
                .into_iter()
                .map(|p| p
                    .into_iter()
                    .map(|(w, i)| (w.to_string(), i))
                    .collect::<Vec<_>>())
                .collect::<Vec<Vec<_>>>()
        );
    }

    #[test]
    fn filter_map_chunks_1() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 1, |chunk, chunk_index, _| {
            Ok(Some(vec![(chunk.to_string(), chunk_index)]))
        })?;
        check_paths(
            paths,
            vec![vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)]],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| {
            Ok(Some(vec![(chunk.to_string(), chunk_index)]))
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| {
            Ok(Some(vec![(chunk.to_string(), chunk_index)]))
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| {
            Ok(Some(vec![(chunk.to_string(), chunk_index)]))
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_5() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 5, |chunk, chunk_index, _| {
            Ok(Some(vec![(chunk.to_string(), chunk_index)]))
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
                vec![("a b c d e", 0)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_1_map_many() -> anyhow::Result<()> {
        let paths = fmc("a b c", 1, |chunk, _, _| {
            Ok(Some((0..3).map(|i| format!("{chunk}{i}")).collect()))
        })?;
        assert_eq!(
            paths,
            vec![
                vec!["a0", "b0", "c0"],
                vec!["a0", "b0", "c1"],
                vec!["a0", "b0", "c2"],
                vec!["a0", "b1", "c0"],
                vec!["a0", "b1", "c1"],
                vec!["a0", "b1", "c2"],
                vec!["a0", "b2", "c0"],
                vec!["a0", "b2", "c1"],
                vec!["a0", "b2", "c2"],
                vec!["a1", "b0", "c0"],
                vec!["a1", "b0", "c1"],
                vec!["a1", "b0", "c2"],
                vec!["a1", "b1", "c0"],
                vec!["a1", "b1", "c1"],
                vec!["a1", "b1", "c2"],
                vec!["a1", "b2", "c0"],
                vec!["a1", "b2", "c1"],
                vec!["a1", "b2", "c2"],
                vec!["a2", "b0", "c0"],
                vec!["a2", "b0", "c1"],
                vec!["a2", "b0", "c2"],
                vec!["a2", "b1", "c0"],
                vec!["a2", "b1", "c1"],
                vec!["a2", "b1", "c2"],
                vec!["a2", "b2", "c0"],
                vec!["a2", "b2", "c1"],
                vec!["a2", "b2", "c2"]
            ]
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_map_many() -> anyhow::Result<()> {
        let paths = fmc("a b c", 2, |chunk, _, _| {
            Ok(Some((0..3).map(|i| format!("{chunk}{i}")).collect()))
        })?;
        assert_eq!(
            paths,
            vec![
                vec!["a0", "b0", "c0"],
                vec!["a0", "b0", "c1"],
                vec!["a0", "b0", "c2"],
                vec!["a0", "b1", "c0"],
                vec!["a0", "b1", "c1"],
                vec!["a0", "b1", "c2"],
                vec!["a0", "b2", "c0"],
                vec!["a0", "b2", "c1"],
                vec!["a0", "b2", "c2"],
                vec!["a0", "b c0"],
                vec!["a0", "b c1"],
                vec!["a0", "b c2"],
                vec!["a1", "b0", "c0"],
                vec!["a1", "b0", "c1"],
                vec!["a1", "b0", "c2"],
                vec!["a1", "b1", "c0"],
                vec!["a1", "b1", "c1"],
                vec!["a1", "b1", "c2"],
                vec!["a1", "b2", "c0"],
                vec!["a1", "b2", "c1"],
                vec!["a1", "b2", "c2"],
                vec!["a1", "b c0"],
                vec!["a1", "b c1"],
                vec!["a1", "b c2"],
                vec!["a2", "b0", "c0"],
                vec!["a2", "b0", "c1"],
                vec!["a2", "b0", "c2"],
                vec!["a2", "b1", "c0"],
                vec!["a2", "b1", "c1"],
                vec!["a2", "b1", "c2"],
                vec!["a2", "b2", "c0"],
                vec!["a2", "b2", "c1"],
                vec!["a2", "b2", "c2"],
                vec!["a2", "b c0"],
                vec!["a2", "b c1"],
                vec!["a2", "b c2"],
                vec!["a b0", "c0"],
                vec!["a b0", "c1"],
                vec!["a b0", "c2"],
                vec!["a b1", "c0"],
                vec!["a b1", "c1"],
                vec!["a b1", "c2"],
                vec!["a b2", "c0"],
                vec!["a b2", "c1"],
                vec!["a b2", "c2"]
            ]
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_1_prune_a() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 1, |chunk, chunk_index, _| match chunk {
            "a" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(paths, vec![]);
        Ok(())
    }

    #[test]
    fn filter_map_chunks_1_prune_b() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 1, |chunk, chunk_index, _| match chunk {
            "b" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(paths, vec![]);
        Ok(())
    }

    #[test]
    fn filter_map_chunks_1_prune_c() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 1, |chunk, chunk_index, _| match chunk {
            "c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(paths, vec![]);
        Ok(())
    }

    #[test]
    fn filter_map_chunks_1_prune_d() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 1, |chunk, chunk_index, _| match chunk {
            "d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(paths, vec![]);
        Ok(())
    }

    #[test]
    fn filter_map_chunks_1_prune_e() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 1, |chunk, chunk_index, _| match chunk {
            "e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(paths, vec![]);
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_a() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "a" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_b() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "b" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_c() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_d() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_e() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_ab() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "a b" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_bc() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "b c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_cd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_de() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_a_bc() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "a" | "b c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_a_cd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "a" | "c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_bc_cd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "b c" | "c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_2_prune_bc_de() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 2, |chunk, chunk_index, _| match chunk {
            "b c" | "d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_a() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "a" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_b() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "b" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_c() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_d() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_e() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_ab() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "a b" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_bc() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "b c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_cd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_de() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_abc() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "a b c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_bcd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "b c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_cde() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "c d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_3_prune_a_bc_cde() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 3, |chunk, chunk_index, _| match chunk {
            "a" | "b c" | "c d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_a() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "a" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_b() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "b" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_c() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_d() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_e() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_ab() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "a b" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_bc() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "b c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_cd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_de() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_abc() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "a b c" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_bcd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "b c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_cde() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "c d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_abcd() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "a b c d" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a", 0), ("b c d e", 1)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_bcde() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "b c d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c d e", 2)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a", 0), ("b c d", 1), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_a_bc_de() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "a" | "b c" | "d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b", 0), ("c d e", 2)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_4_prune_a_bc_cde() -> anyhow::Result<()> {
        let paths = fmc("a b c d e", 4, |chunk, chunk_index, _| match chunk {
            "a" | "b c" | "c d e" => Ok(None),
            _ => Ok(Some(vec![(chunk.to_string(), chunk_index)])),
        })?;
        check_paths(
            paths,
            vec![
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
                vec![("a b c", 0), ("d", 3), ("e", 4)],
                vec![("a b c", 0), ("d e", 3)],
                vec![("a b c d", 0), ("e", 4)],
            ],
        );
        Ok(())
    }

    #[test]
    fn filter_map_chunks_spaces() -> anyhow::Result<()> {
        let paths = fmc("   a   b  c        d  e ", 2, |chunk, chunk_index, _| {
            Ok(Some(vec![(chunk.to_string(), chunk_index)]))
        })?;
        check_paths(
            paths,
            vec![
                vec![("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b", 1), ("c", 2), ("d e", 3)],
                vec![("a", 0), ("b", 1), ("c d", 2), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d", 3), ("e", 4)],
                vec![("a", 0), ("b c", 1), ("d e", 3)],
                vec![("a b", 0), ("c", 2), ("d", 3), ("e", 4)],
                vec![("a b", 0), ("c", 2), ("d e", 3)],
                vec![("a b", 0), ("c d", 2), ("e", 4)],
            ],
        );
        Ok(())
    }
}

[ Dauer der Verarbeitung: 0.56 Sekunden  (vorverarbeitet)  ]