为什么换行字符计为BTreeMap中的两个键?

时间:2017-10-14 13:32:03

标签: char rust newline

我正在使用Huffman编码在Rust中创建压缩/解压缩库。其中一个步骤是创建一个包含所有唯一字符和出现次数的数据结构。我只是从一个简单的文本文件开始,并且遇到与换行符'字符'相关的问题。

我解决这个问题的第一个尝试是构建一个BTreeMap,基本上是一对键值对的唯一字符及其出现次数。不幸的是,换行符'字符'是\ n,我认为由于是两个字符而没有得到纠正。然后,我将BTreeMap转换为Vec按价值排序,但这并未解决换行问题。

这是我对comp二进制包的初步尝试。使用cargo调用二进制文件,并在此问题的末尾复制我的示例文件:

cargo run <text-file-in> <compressed-output-file>

main.rs

extern crate comp;

use std::env;
use std::process;
use std::io::prelude::*;

use comp::Config;

fn main() {
    // Collect command-line args into a vector of strings
    let mut stderr = std::io::stderr();

    let config = Config::new(env::args()).unwrap_or_else(|err| {
        writeln!(&mut stderr, "Parsing error: {}", err).expect("Could not write to stderr");
        process::exit(1)
    });

    println!("Filename In: {}", config.filename_in);
    println!("Filename Out: {}", config.filename_out);

    if let Err(e) = comp::run(config) {
        writeln!(&mut stderr, "Application error: {}", e).expect("Could not write to stderr");
        process::exit(1);
    }
}

lib.rs

use std::collections::btree_map::BTreeMap;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::iter::FromIterator;

pub struct Config {
    pub filename_in: String,
    pub filename_out: String
}

impl Config {
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        args.next();

        let filename_in = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a filename_in string"),
        };

        let filename_out = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a filename_out string"),
        };

        Ok(Config {
            filename_in: filename_in,
            filename_out: filename_out,
        })
    }
}

pub fn run(config: Config) -> Result<(), Box<Error>> {
    let mut f = File::open(config.filename_in)?;

    let mut contents = String::new();
    f.read_to_string(&mut contents)?;

    for line in contents.lines() {
        println!("{}", line);
    }

    // Put unique occurrences into a BTreeMap
    let mut count = BTreeMap::new();

    for c in contents.chars() {
        *count.entry(c).or_insert(0) += 1;
    }

    // Put contents into a Vec to order by value
    let mut v = Vec::from_iter(count);
    v.sort_by(|&(_, a), &(_, b)| b.cmp(&a));

    // Print key-value pair of input file
    println!("Number of occurrences of each character");
    for &(key, value) in v.iter() {
        println!("{}: {}", key, value);
    }

    Ok(())
}

示例文本文件 poem.txt

I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us — don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

用法:

$ cargo run poem.txt poem
   Compiling comp v0.1.0 (file:///home/chris/Projects/learn_rust/comp-rs)
    Finished dev [unoptimized + debuginfo] target(s) in 1.96 secs
     Running `target/debug/comp poem.txt poem`
Filename In: poem.txt
Filename Out: poem
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us — don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
Number of occurrences of each character
 : 36
o: 24
e: 15
a: 10
n: 10
y: 10
        < What's going on here?
: 9     < What's going on here?
r: 9
d: 8
l: 8
b: 7
i: 7
t: 7
u: 7
h: 6
s: 5
!: 4
': 4
T: 4
g: 4
m: 4
,: 3
w: 3
?: 2
H: 2
f: 2
k: 2
p: 2
.: 1
A: 1
I: 1
W: 1
c: 1
v: 1
—: 1

2 个答案:

答案 0 :(得分:7)

  

不幸的是,换行'字符'是\n,我认为由于是两个字符而未对其进行处理。

不,不是。换行符(UTF-8代码点0x0A)是单个字符。

  

我认为我需要将换行符作为键值对中的键,但它目前只有两个键。

不,不是。这样的事情也不会“偶然”发生。如果我们以某种方式拥有两个密钥,则必须两次调用insert;没有内置的多键映射概念。

这里发生的一切就是将换行字符打印作为...换行符!

y: 10

: 9    

如果你花时间创建一个MCVE,你会很快看到这个:

fn main() {
    let c = '\n';
    println!(">{}<", c);
    println!(">{:?}<", c);
}
>
<
>'\n'<

答案 1 :(得分:2)

换行符实际上是转义序列字符。这意味着如果您在显示为两个字符的代码中将其写为\n,它实际上是单个字符的占位符 - 一个新行 - 并且在运行时应该被视为程序中的“一个字符”

您遇到的核心问题是,您使用println将其打印到命令行,并实际 打印 一个新行, \n被解释为“新行”。这就是为什么当你在这里使用println时,你会得到你所看到的行为。这是典型的 大多数 语言。

虽然这会增加一些额外的代码,但您可能希望这样做而不是专门处理正在打印的新行数据:

// Print key-value pair of input file
println!("Number of occurrences of each character");
for &(key, value) in v.iter() {
    if key == '\n' {
        println!("\\n": {}, value);
    } else {
        println!("{}: {}", key, value);
    }
}

考虑到Shepmaster的解释,虽然要创建一个MCVE来彻底测试事物,但它有助于排除对幕后实际发生的事情的错误解释。

(注意:我不是Rust大师;可能有更好的方法来实现上述目标,但这是我在短时间内提出的最短解决方案)