Как обрезать пространство менее чем в n раз? - PullRequest
1 голос
/ 21 февраля 2020

Как исключить до n пробелов в начале каждой строки?

Например, при усечении 4 пробела:

  • " 5" -> " 5"
  • " 4" -> "4"
  • " 3" -> "3"
const INPUT:&str = "    4\n  2\n0\n\n      6\n";
const OUTPUT:&str = "4\n2\n0\n\n  6\n";
#[test]
fn main(){
    assert_eq!(&trim_deindent(INPUT,4), OUTPUT)
}

Ответы [ 2 ]

1 голос
/ 21 февраля 2020

Я собирался комментировать textwrap::dedent, но потом заметил "2", в котором меньше 4 пробелов. Таким образом, вы хотели, чтобы он продолжал удалять пробелы, если они есть вплоть до 4.

Если просто написать быстрое решение, оно может выглядеть примерно так:

Ваше утверждение пройдет, но учтите, что строки, оканчивающиеся на \r\n, будут преобразованы в \n, поскольку lines не позволяет различать \n и \r\n.

fn trim_deindent(text: &str, max: usize) -> String {
    let mut new_text = text
        .lines()
        .map(|line| {
            let mut max = max;
            line.chars()
                // Skip while `c` is a whitespace and at most `max` spaces
                .skip_while(|c| {
                    if max == 0 {
                        false
                    } else {
                        max -= 1;
                        c.is_whitespace()
                    }
                })
                .collect::<String>()
        })
        .collect::<Vec<_>>()
        .join("\n");

    // Did the original `text` end with a `\n` then add it again
    if text.ends_with('\n') {
        new_text.push('\n');
    }

    new_text
}

Если вы хотите сохранить и \n, и \r\n, тогда вы можете go более сложный маршрут сканирования через строку и, таким образом, избежать использования lines.

fn trim_deindent(text: &str, max: usize) -> String {
    let mut new_text = String::new();

    let mut line_start = 0;
    loop {
        let mut max = max;

        // Skip `max` spaces
        let after_space = text[line_start..].chars().position(|c| {
            // We can't use `is_whitespace` here, as that will skip past `\n` and `\r` as well
            if (max == 0) || !is_horizontal_whitespace(c) {
                true
            } else {
                max -= 1;
                false
            }
        });

        if let Some(after_space) = after_space {
            let after_space = line_start + after_space;

            let line = &text[after_space..];
            // Find `\n` or use the line length (if it's the last line)
            let end = line
                .chars()
                .position(|c| c == '\n')
                .unwrap_or_else(|| line.len());

            // Push the line (including the line ending) onto `new_text`
            new_text.push_str(&line[..=end]);

            line_start = after_space + end + 1;
        } else {
            break;
        }
    }

    new_text
}

#[inline]
fn is_horizontal_whitespace(c: char) -> bool {
    (c != '\r') && (c != '\n') && c.is_whitespace()
}
0 голосов
/ 21 февраля 2020

Более простое и (с немного небезопасным), возможно, более быстрое решение. Один проход ввода и перераспределение строки mem только один раз.

const INPUT:&str = "    4\n  2\n0\n\n      6\n";
const OUTPUT:&str = "4\n2\n0\n\n  6\n";


fn main(){
    assert_eq!(&trim_deindent(INPUT,4), OUTPUT)
}

pub fn trim_deindent(input: &str, indent: usize) -> String {
    let mut out : String = String::from(input);
    let mut j: usize = 0;
    let mut is_counting = true;
    let mut ws_cnt = 0;
    unsafe {
        let outb = out.as_bytes_mut();
        for i in 0..outb.len() {
            if is_counting == true && outb[i] == b' '  {
                ws_cnt+=1;
                if ws_cnt == indent {
                    is_counting = false;
                }
            } else {
                is_counting=false;
                if outb[i]== b'\n' {
                    is_counting=true;
                    ws_cnt=0;
                }
                outb[j]=outb[i];
                j+=1;
            }
        }
    }
    out.truncate(j);
    out
}
...