rust_1
kuteliyafukarust编写csv排序工具知识点
周末两天的时间,学了以下io操作,文件的读写,代码的组织、测试和安全性的操作。
然后简单地写了一个用于将pipeline输出的qc_metrics.tsv转变成qc_metrics.csv并且按照样本顺序排序的工具
这是对这个工具的回顾。回头看一看如何发到github上。
琐碎的知识点:
rust analyzer自动格式化代码shift+alt+f。如果没能自动化,很可能因为代码有语法错误。
Path和PathBuf的区别是前者没有所有权,后者有。
.clone()和.cloned()的区别,.cloned()相当于对迭代器所有元素clone,以下两个用法等价。
1 2
| let a: Vec<String> = v.iter().cloned().collect(); let b: Vec<String> = v.iter().map(|x| x.clone()).collect();
|
传递参数
使用clap工具,基本用法如下:
1
| cargo add clap --features derive
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #[derive(Parser, Debug)] #[command(name="read_and_write",version, about, long_about = None)] pub struct Args { #[arg(short, long, help = "read file path")] pub read_filename: String, #[arg(short, long, help = "output file path",default_value_t=String::from("output.csv"))] pub output_filename: String,
#[arg(short = 'm',long, help = "input the order file",default_value_t=String::from("order.txt"))] pub order_filename:String, }
fn main(){ let args = Args::parse(); }
|
使用
安全性
rust返回的内容多是Result<T,Box>或者std::io::Result<()>,这两者我暂时还没搞清楚区别;
等学了智能指针之后再回来看。
这里的PathBuf是一种常见的处理路径的格式。
File::open()和read_to_string()没什么好说的,是必须记住的内容,值得注意的是”?”,用来快速返回结果或传递错误,只能在返回Result<>或者Option<>的函数中使用,因为main函数返回的是(),所以不能够用问号“?”。
1 2 3 4 5 6 7 8 9 10 11
| use std::error::Error; use std::fs::File; use std::io::{Read,Write}; use std::path::PathBuf; pub fn read(filename: PathBuf) -> Result<String, Box<dyn Error>> { let mut file = File::open(filename)?; let mut content = String::new(); file.read_to_string(&mut content)?; Ok(content)
}
|
在main函数中,使用返回Result<>的函数时,需要使用match进行模式匹配,示例如下:
1 2 3 4 5 6 7 8 9
| use std::process; fn main() { let csv_content = match tsv2csv(tsv_file) { Ok(content) => content, Err(_) => { println!("something wrong with tsv file"); process::exit(1); }, };}
|
错误时需要使用process::exit(1);来退出程序并返回错误码。
读和写
读写用std::fs::File或者std::io::{Read,Write},我暂时不清楚这两者的区别。
注意这里file都必须是可变的,因为需要移动光标(好像是)?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| use std::fs::File; use std::io::{Read,Write};
pub fn read(filename: PathBuf) -> Result<String, Box<dyn Error>> { let mut file = File::open(filename)?; let mut content = String::new(); file.read_to_string(&mut content)?; Ok(content)
}
pub fn write(filename:PathBuf,content:&str) -> Result<(), Box<dyn Error>> { let mut file = File::create(filename)?; file.write_all(content.as_bytes())?; Ok(()) }
|
文件内容操作
这里写了两个函数,tsv2csv,
列一下主要用到的函数:
.lines()返回Lines迭代器
.next()对迭代器用,返回第一个元素的Option()对象,消耗掉lines第一个元素。通常单独处理header。
push_str(),在string的末尾增加内容,输入&str。
push(),输入char。(和vector的不一样)
尽量在处理各种文本的时候,能用String就用String,能够省去很多麻烦!!!
先把csv文件转成Vec<Vec>的二维矩阵,方便处理。
.collect(),用来将迭代器转变成集合类型,需要显示声明集合类型。
.split(),用来将String分割成Split迭代器,
.join(“,”),用来将Vec[‘a’,’b’]变成”a,b”,集合变字符串。
.replace(from,to)
.map(),迭代器最常用的method!输入闭包,依次修改每个元素;另外.filter()是另外一个迭代器最常用的method。
String::with_capacity(data.capacity()),高级用法,使String有固定空间,优化内存分配。
HashMap的常见用法,.enumerate(), .insert(), .keys(), .values(),.get(k)返回Option()
ok_or()可以用来将Option()转化为result
.iter()和.into_iter(),前者是引用,后者会消耗所有权
contains()很好用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| pub fn tsv2csv(tsv_file:PathBuf) -> Result<String, Box<dyn Error>> { let mut result = String::new(); let content = read(tsv_file)?; let mut lines = content.lines(); let mut header = lines.next().expect("csv is empty").to_string(); header = header.replace("\t",","); result.push_str(&header); result.push('\n');
let rows:Vec<Vec<String>> = lines.map(|row| row.split("\t").map(|s| s.to_string()).collect()).collect(); for mut row in rows { row[0] = row[0].replace("_R","-"); let row_str = row.join(","); result.push_str(&row_str); result.push('\n'); } Ok(result) }
pub fn sort_by_rowname(data: String, order_file: PathBuf) -> Result<String, Box<dyn Error>> { let order_content = read(order_file)?; let order:Vec<&str> = order_content.lines().collect(); let mut result = String::with_capacity(data.capacity()); let mut rank = HashMap::new();
for (i, k) in order.into_iter().enumerate() { rank.insert(k, i); }
let mut lines = data.lines(); let header = lines.next().ok_or("csv is empty")?;
let mut rows:Vec<Vec<&str>> = lines.map(|line| line.split(",").collect::<Vec<&str>>()).collect(); let rownames:Vec<String> = rows.iter().map(|row| row[0].to_string()).collect(); for &key in rank.keys() { if !rownames.contains(&key.to_string()) { rows.push(vec![key]) }
}
rows.sort_by_key(|row| { let k = row[0]; rank.get(k).cloned().unwrap_or(usize::MAX) }); result.push_str(header); result.push('\n');
for row in rows { result.push_str(&row.join(",")); result.push('\n') }
Ok(result) }
|
测试与代码组织
1
| catgo test -- --nocapture #可以展示println!()打印的内容
|
基本结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #[cfg(test)] mod test { use super::*;
#[test] fn order_input() { let order = PathBuf::from("order.txt"); let content = match read(order) { Ok(content) => content, Err(_) => "something wrong with order.txt".to_string(), }; let content:Vec<&str> = content.lines().collect(); }
#[test] fn tsv2csv() { let mut result = String::new(); let tsv_file = PathBuf::from("qc_metrics.tsv"); let content = match read(tsv_file) { Ok(content) => content, Err(_) => "something wrong with tsv file".to_string(), }; let mut lines = content.lines(); let mut header = lines.next().expect("csv is empty").to_string(); header = header.replace("\t",","); result.push_str(&header); result.push('\n');
let rows:Vec<Vec<String>> = lines.map(|row| row.split("\t").map(|s| s.to_string()).collect()).collect(); for mut row in rows { row[0] = row[0].replace("_R","-"); let row_str = row.join(","); result.push_str(&row_str); result.push('\n'); } println!("{}",result); }
}
|