我正在使用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
答案 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大师;可能有更好的方法来实现上述目标,但这是我在短时间内提出的最短解决方案)