又到了每日一题386. 字典序排数,虽然方法有瑕疵,不过是一个典型的字典树哦
// 构建一颗字典树
#[derive(Debug)]
struct dict_tree_node{
val:i32,
son:Vec<dict_tree_node>
}
// 定义字典树的前序遍历方法,返回一个Vec
impl dict_tree_node{
fn pre_order(&self) -> Vec<i32>{
let mut return_:Vec<i32> = vec![self.val];
// 没找到切片的方法,只好出现0就pop掉
if self.val == 0{
return_.pop();
}
// 这里我之所以没用&son_node是因为我没有实现copy的trait,所以遍历iter无法复制给son_node.
for son_node in self.son.iter(){
let p = (son_node.pre_order());
// 这里i32本身实现Copy trait就可以直接这样使用
for &m in p.iter(){
if m!=0 {
return_.push(m);
}
}
}
return return_
}
}
// 主函数main
impl Solution {
// 定义根节点,我选择0,不然要有1-9 9个根节点
pub fn lexical_order(n: i32) -> Vec<i32> {
let mut root = dict_tree_node{
val:0,
son:Vec::new(),
};
// 填充,字典树应该考虑0的情况,每棵树都是10个节点
let zero_point = dict_tree_node{
val:0,
son:Vec::new(),
};
root.son.push(zero_point);
let mut start = 1;
// 填充树
while start <= n {
let k = start.to_string();
let mut node_locate = & mut root;
for char_ in k.chars(){
let p:i32 = char_.to_string().trim().parse().expect("Not a number");
if node_locate.son.len() <= p as usize{
let new_node = dict_tree_node{
val:start,
son:Vec::new(),
};
// 解引用
(*node_locate).son.push(new_node);
break;
}else{
let q = p as usize;
node_locate = &mut node_locate.son[q];
}
}
start += 1;
}
// 返回前序遍历的Vec
root.pre_order()
}
}
执行用时:24 ms, 在所有 Rust 提交中击败了100.00% 的用户
内存消耗:4.8 MB, 在所有 Rust 提交中击败了25.00% 的用户
class dict_Tree_Node:
def __init__(self):
self.val = 0
self.som = []
def get_str(self):
return_ = str(self.val)
for node in self.som:
return_ = return_ + '{' + node.get_str() + '}'
return return_
def pre_iter(self):
return_ = [self.val]
for node in self.som:
return_ = return_ + node.pre_iter()
return return_
class Solution:
def lexicalOrder(self, n: int) -> List[int]:
# 字典树尝试一下
dict_Tree = dict_Tree_Node()
dict_Tree.val = 0
dict_Tree.som = [dict_Tree_Node()]
for i in range(1,n+1):
k = str(i)
node_weizhi = dict_Tree
for num in k:
num_ = eval(num)
try:
node_weizhi = node_weizhi.som[num_]
except:
new_val = dict_Tree_Node()
new_val.val = i
node_weizhi.som.append(new_val)
node_weizhi = new_val
return dict_Tree.pre_iter()[2:]
代码简洁度上有差距,性能更有差距,,无语
执行用时:2004 ms, 在所有 Python3 提交中击败了5.25% 的用户
内存消耗:30 MB, 在所有 Python3 提交中击败了5.13% 的用户
每日一题821. 字符的最短距离
//给你一个字符串 s 和一个字符 c ,且 c 是 s 中出现过的字符。
//返回一个整数数组 answer ,其中 answer.length == s.length 且 answer[i] 是 s 中从下标 i 到离它 最近 的字符 c 的 距离 。
//两个下标 i 和 j 之间的 距离 为 abs(i - j) ,其中 abs 是绝对值函数。
// 简单写一下思路,遍历到相同字符时想做延展到比原距离大,同时对其右边字符均用此字符距离赋值。时间复杂度O(2n)(加上我初始化数组应该是3n)
impl Solution {
pub fn shortest_to_char(s: String, c: char) -> Vec<i32> {
let mut return_: Vec<i32> = Vec::new();
loop{
if return_.len() == s.len(){
break;
}
return_.push(10000);
}
let mut distance_left = 0;
let mut distance_right = 10000;
let mut now_location = 0;
for char_ in s.chars() {
if char_ == c {
let mut left_decrease_loca = now_location.clone() ;
distance_left = 0;
while left_decrease_loca >= 0 && distance_left < return_[left_decrease_loca] {
return_[left_decrease_loca] = distance_left;
distance_left = distance_left +1;
// 这里必须加这个判断,usize不能为负会越界
if left_decrease_loca == 0{
break;
}
left_decrease_loca = left_decrease_loca -1;
}
distance_right = 1;
} else {
return_[now_location] = distance_right;
distance_right += 1;
}
now_location += 1;
}
return_
}
}
//执行用时:0 ms, 在所有 Rust 提交中击败了100.00% 的用户
//内存消耗:2 MB, 在所有 Rust 提交中击败了50.00% 的用户
# 同思路Python代码
# 初始化数组时间O(1) 所以是纯纯的O(2n)
class Solution:
def shortestToChar(self, s: str, c: str) -> List[int]:
# 看来我还想简单了
return_ = [10**4] * len(s)
start = 0
distance_right = 10000
for zifu_loca in range(len(s)):
if s[zifu_loca] == c:
loca_reverse = zifu_loca
distance_left = 0
while loca_reverse >= 0 and return_[loca_reverse] > distance_left:
return_[loca_reverse] = distance_left
distance_left += 1
loca_reverse -= 1
distance_right = 1
else:
return_[zifu_loca] = distance_right
distance_right += 1
return return_
# 行用时:28 ms, 在所有 Python3 提交中击败了99.35% 的用户
# 内存消耗:14.9 MB, 在所有 Python3 提交中击败了97.71% 的用户
断言与测试
在Python我的测试就根本没学过,在Rust中补补课
测试: 定义一种函数, 用于验证非测试代码的功能是否和预期一致.通常执行以下三个操作:
- 准备数据/ 状态 arrange
- 运行被测试的代码 act
- **断言(Assert) ** 结果
测试函数再牛逼他也是个函数,不过使用test属性 进行标注,不会修改代码的源逻辑,一段Rust代码的元数据。test标注的方法如下
#[test]
fn test_first_try(){}
测试函数不需要我们在主函数中执行,我们使用cargo_test命令可以运行所有的测试函数,Rust会构建一个Test Runner可执行文件,会逐个调用被标注的test哈数,报告期是否运行成功。使用cargo创建Libruary项目的时候会生成一个test module, 我们直接任意添加即可。测试失败就会引起程序恐慌。测试会新生成一个进程进行测试,引起了那个进程的恐慌,主线程监控会发现测试进程挂掉,将其标记为失败
fn main() {
println!("Hello, world!");
}
fn add(a:i32, b:i32) -> i32{
return a + b;
}
#[test]
fn test_add(){
// 成功测试
// assert_eq!(add(2,7),9);
assert_eq!(add(2,7),9);
}
thread 'test_add' panicked at 'assertion failed: `(left == right)`
left: `9`,
right: `10`', src\main.rs:9:5
error: test failed, to rerun pass '-p Rust_ProjectTest_Assert --bin Rust_ProjectTest_Assert'
断言 Assert
Assert全家桶基本上都是宏,大部分Assert 都来自标准库(应该)
-
assert! 宏,来自于标准库,用来确定某个状态是否为True,
看来是bool用户了。True表示测试通过。False会调用panic宏.源码(现在我还看不懂):#[stable(feature = "rust1", since = "1.0.0")] #[rustc_builtin_macro] #[macro_export] #[rustc_diagnostic_item = "assert_macro"] #[allow_internal_unstable(core_panic, edition_panic)] macro_rules! assert { ($cond:expr $(,)?) => {{ /* compiler built-in */ }}; ($cond:expr, $($arg:tt)+) => {{ /* compiler built-in */ }}; }
所以说,上面的断言等价于 assert!(add(2, 7) == 2 + 7);
-
assert_eq! 和assert_ne! (ne就是not equel) 宏, 来自标注库,传入两个参数比较是否相等。如果断言失败看上面的失败信息,会打印(Debug方法)出两个参数的值。所以我们自定义的时候要用derive(Debug)标记一下。
自定义错误信息
上面的Assert 宏都支持添加自定义的错误信息 , 会将自定义信息于标准失败信息一同打印出来。
assert!(add(2, 7) == 2 + 8 , "add is somethong wrong");
assert_eq!(add(2, 7) ,2 + 8 , "add is somethong wrong");
assert_eq!(add(2, 7) ,2 + 7, "add is somethong wrong");
Should_Panic
测试函数还需要保证代码是否如期的处理了发生错误的情况,用测试代码验证特定情况下是否发生了panic. 这次在test标注的基础上使用should_panic标注了哦,只有发生了恐慌测试才能通过的呦 。
fn str_to_i32(s:String) -> i32{
match s.trim().parse(){
Ok(n) => {return n},
Err(e) => {panic!("Something wrong");},
}
}
#[test]
#[should_panic]
fn test_should_panic() {
let p = str_to_i32("我爱你中国".to_string());
//let p = str_to_i32("321".to_string());
//error: test failed, to rerun pass '-p Rust_ProjectTest_Assert --bin Rust_ProjectTest_Assert'
}
我们可以给shpould_panic属性添加一个可选的 expected参数, 检查失败信息中是否包含指定文字
fn str_to_i32(s:String) -> i32{
match s.trim().parse(){
Ok(n) => {return n},
Err(e) => {panic!("Something wrong");},
}
}
#[test]
#[should_panic(expected = "Something wrong")]
//#[should_panic(expected = "Something wrong")] 中间多加了一个空格
//Something wrong
//thread 'test_should_panic' panicked at 'Something wrong', src\main.rs:11:20
//stack backtrace:
fn test_should_panic() {
let p = str_to_i32("没错".to_string());
}
此外常见的测试修饰属性还包括ignore(#[ignore]), 就不会默认运行此测试
保证控制测试运行
我们刚刚所有示例与跑起来的例子都选择使用默认模式进行测试的:并行运行, 运行所有测试, 捕获所有输出(会注意到main里面的Hello world并没有输出哦)
我们可以使用命令行参数来控制测试的运行
# 并行运行测试, 默认,无需调参
# 控制运行数量
cargo test -- --test-threads = 1 #单线程(线性测试)
# 不捕获输出,所有显示输出
cargo test -- --show-output
# 按名称运行测试,指定使用test_should_panic测试,可以使用匹配
cargo test test_should_panic
cargo test test_ # 匹配所有以test_为名字开头的测试
# 既然指定了某些测试,我门就可以或略某些测试
Rust与命令行,类cat尝试
读取命令行参数
// 毕竟轮子都是别人造好的,我们直接使用就行了。这个库主要是接受命令行参数的
use std::env;
fn main() {
// 接收到的参数,env::args会返回一个iterator, 迭代器上自带collect将迭代结果转化为Vec, 但是是一个泛型的Vec我们要自己指定类型
//fn collect<B: FromIterator<Self::Item>>(self) -> B, 源代码说了这是泛形(自定义的B类型,但是约束实现了From Iterator这个trait)
let args:Vec<String> = env::args().collect();
println!("We receive some args {:?}",args);
}
我们run之后会生成exe, 运行exe并附加参数:
./Ternimate_try.exe -hello -my -name -is -lihua
We receive some args
# 第一个参数是文件路径
["G:\\Rust\\Rust_Project\\Ternimate_try\\src\\Ternimate_try.exe", "-he
llo", "-my", "-name", "-is", "-lihua"]
成功读取到命令行参数。
读取文件
// 这个东西在前面也说过
use std::fs
fn file_read(path: &String) -> Result<String, io::Error> {
let file_ = fs::read_to_string(path);
file_
}
项目重构原则
- 将程序拆分为main.rs 和 lib.rs, 将业务逻辑放入lib.rs
- 命令行逻辑复杂是,将它从main.rs 提取到 lib 或其他文件
- main函数只保留配置, 参数值, 调用lib.rs的run函数。
- Error定义参考https://zhuanlan.zhihu.com/p/109242831
使用TDD进行测试
- 编写一个会失败的测试,运行该测试,确保按照预期失败
- 编写或修改足够的代码,让心测试通过
- 重构刚刚添加或修改的代码,保证测试始终通过
一个简易的cat实现
// main.rs
extern crate core;
include!("command_get.rs");
include!("Readin_With_Method.rs");
include!("file_write.rs");
use std::fmt::Display;
use std::env;
use std::fs;
use std::fmt;
use std::io;
fn main() {
let args:Vec<String> = env::args().collect();
// println!("We receive some args {:?}",args);
let mut command_need:Command;
command_need =Command::new(args);
// println!("{}",command_need);
let suggest = command_need.com_legal();
if suggest!="Ok".to_string(){
// 不Ok直接退了,返回我定义的错误信息。
eprintln!("{}",suggest);
help();
return ();
}
let str = print_string(command_need.filepath1,command_need.parameter);
if str == "".to_string() || str == "False".to_string(){
return ();
}
if command_need.file_convert_fuc == String::from(""){
return ();
}else{
if command_need.file_convert_fuc == String::from("~"){
file_append(command_need.filepath2,str);
}else if command_need.file_convert_fuc == String::from("~~"){
overwrite_(command_need.filepath2,str);
}
return ();
}
}
//command_get.rs
pub struct Command {
parameter: String,
filepath1: String,
file_convert_fuc: String,
filepath2: String,
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Command[ parameter: {}, filepath1: {}, file_convert_fuc: {}, filepath2: {} ]", self.parameter, self.filepath1, self.file_convert_fuc, self.filepath2)
}
}
impl Command{
pub fn new(commands: Vec<String>) -> Command {
let mut command_Stru = Command {
parameter: String::from(""),
filepath1: String::from(""),
file_convert_fuc: String::from(""),
filepath2: String::from(""),
};
if commands.len() < 2 {
return command_Stru;
} else {
let mut par_now = 1;
let mut command1 = commands[par_now].clone();
if command1[0..1] == "*".to_string() {
// 命令切片
command_Stru.parameter = (&command1[1..]).to_string();
} else {
command_Stru.filepath1 = commands[par_now].clone()
}
par_now = 2;
// 如果参数只有一个就直接返回了,省的下面报错
if commands.len() == 2 { return command_Stru; };
if command_Stru.filepath1 == String::from("") {
command_Stru.filepath1 = commands[par_now].clone();
par_now = 3
}
if commands.len() == par_now { return command_Stru; };
// 有一些特殊字符被吞了,麻了
if commands[par_now] == "~".to_string() || commands[par_now] == "~~".to_string() {
command_Stru.file_convert_fuc = commands[par_now].clone();
par_now += 1
}
if commands.len() == par_now { return command_Stru; };
command_Stru.filepath2 = commands[par_now].clone();
}
command_Stru
}
pub fn com_legal(&self) -> String {
if (self).filepath1 == String::from("") && (self).parameter !=String::from("h") {
return "No file path have you been inputed".to_string();
}
if (self).parameter != String::from("") {
for par in (self).parameter.chars() {
if (par != 'b')&&(par != 'e')&&(par!= 'h') {
return "Some parameter wrong with your input".to_string();
}
}
}
if (self).file_convert_fuc==String::from("") && (self).filepath2!= String::from(""){
return "You don't point to the fuc to move file1 to file2".to_string();
}
return "Ok".to_string();
}
}
pub fn help() {
println!(" This is Helping you to get use of cat");
println!(" The Syntax of cat commend can be describe like : cat [*par] filename1 [~/~~] filename2]");
println!(" That means We can use cat to finish copy or print by row");
println!(" There are some parameter for [-par]:");
println!(" *b : println by row with row number");
println!(" *e : show $ at every end of row");
println!(" *h Get Help");
println!(" About copy/ overwrite file command we can use ~ as copy_to and ~~ as overwrite");
}
// filr_write.rs
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
pub fn overwrite_(path:String, text:String){
let mut clear_or_create = fs::File::create(path);
let mut p:fs::File;
match clear_or_create{
Ok(file) =>{p = file;},
Err(e) =>{
eprintln!("Something is wrong with overwriting as following \n {:?}",e);
return ();
},
}
p.write_all(text.as_bytes());
p.flush();
println!("Overwriting is OK");
}
pub fn file_append(path: String,text:String){
if fs::read_to_string(&path).is_err(){
println!("There is no such file or file opening something wrong , Try to use overwriting");
return overwrite_(path.clone(),text.clone());
}
let mut file_write_pre = OpenOptions::new().append(true).open(path);
let mut file:File;
match file_write_pre {
Ok(file_) =>{file = file_;},
Err(e) => {
eprintln!("Something wrong with appending txt as following\n {:?}",e);
return ();
}
}
file.write('\n'.to_string().as_bytes());
file.flush();
file.write_all(text.as_bytes());
println!("Appending OK");
}
// Readin_With_Method.rs
fn file_read(path: String) -> Result<String, io::Error> {
let file_ = fs::read_to_string(path);
file_
}
fn b(text: String) -> String {
let mut return_str = String::from("1: ");
let mut count = 1;
for charss in text.chars() {
return_str = return_str + &charss.to_string();
if charss == '\n' {
count += 1;
return_str = return_str + &count.to_string() + &": ".to_string();
}
}
return return_str;
}
fn e(text:String) -> String{
// e的功能在每一行最后加上$
let mut p:String = String::new();
for char_ in text.chars(){
// 这里是个坑,因为text里面原来回车也会占用一个ascii,ubyte表示为13.我之前一直有Bug就是因为这里。
if char_.to_string().as_bytes()[0] == 13{
p = p + "$";
p = p + &char_.to_string();
}else{
p = p + &char_.to_string();
}
}
p + "$"
}
pub fn print_string(path: String, par: String) -> String {
let read_result = file_read(path);
let mut text: String;
let mut p___ = false;
if par!= String::from("h"){
match read_result {
Ok(t) => { text = t; }
Err(e) => {
eprintln!(" Wrong with open file, specfic wrong is\n {:?}", e);
return "False".to_string();
}
}
for char in par.chars() {
if char == 'b' {
text = b(text);
}
if char == 'e' {
text = e(text);
}
if char == 'h' {
p___ = true;
}
}
println!("{}", text);
if p___{
help();
}
return text;
}else{
help();
return "".to_string();
}
}
每日一题:388. 文件的最长绝对路径
use std::collections::HashMap;
impl Solution {
pub fn length_longest_path(input: String) -> i32 {
let mut dir_hash:HashMap<i32,String> = HashMap::new();
let mut start:usize = 0;
let mut is_file = false;
let mut now_locate:usize = 0;
let mut count_t = 0;
let mut max_len:usize = 0;
for char_ in input.chars(){
if now_locate == input.len() - 1 || char_ == '\n'{
let mut file_path:String = String::new();
if now_locate == input.len() - 1{
file_path = input[start..].to_string();
}else{
file_path = input[start..now_locate].to_string();
}
if is_file{
let mut iter_ = 0;
let mut file_path_:String = String::from("");
while iter_ < count_t{
file_path_ = file_path_+ &dir_hash[&iter_];
file_path_ = file_path_ + "/";
iter_ += 1;
}
file_path_ = file_path_ + &file_path;
let file_path_len = file_path_.len();
if file_path_len > max_len{
max_len = file_path_len;
println!("{}",file_path_)
}
is_file = false;
}else{
dir_hash.insert(count_t,file_path);
}
start = now_locate + 1;
count_t = 0;
}
if char_ == '\t'{
start += 1;
count_t += 1;
}
if char_ == '.'{
is_file = true;
}
now_locate += 1;
}
max_len as i32
}
}
//执行用时:0 ms, 在所有 Rust 提交中击败了100.00% 的用户
//内存消耗:2.1 MB, 在所有 Rust 提交中击败了100.00% 的用户
# Python简洁写法
class Solution:
def count_t_num(self, s):
count_t = 0
for i in s:
if i == '\t':
count_t += 1
return count_t
def lengthLongestPath(self, input: str) -> int:
dir_segment = dict()
input_dir = input.split('\n')
max_len = 0
for file in input_dir:
# 是文件还是文件夹的判断
count_t = self.count_t_num(file)
if '.' in file:
file_dir_len = len(file) - count_t
for i in range(count_t):
file_dir_len += dir_segment[i]
file_dir_len += 1
if file_dir_len > max_len:
max_len = file_dir_len
else:
dir_segment[count_t] = len(file) - count_t
return max_len
# 执行用时:32 ms, 在所有 Python3 提交中击败了84.44% 的用户
# 内存消耗:15.1 MB, 在所有 Python3 提交中击败了27.24% 的用户