Rust学习5

又到了每日一题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% 的用户
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值