Rust 学习3, 枚举,集合

Option枚举

Option定义于标准库中,并且会与导入(我们不需要主动引入), 描述了某个值可能存在(某种类型)或不存在的情况

// 4月16日每日一题
//给定一个整数 n ,返回 可表示为两个 n 位整数乘积的 最大回文整数 。因为答案可能非常大,所以返回它对 1337 取余 。
// Debug De到我死,现在每天每日一题加上Rust刷真的,有点东西。
// 但是和下面的Python比性能瞬间就上来了
//执行用时: 88 ms
//内存消耗: 2.1 MB
impl Solution {
    pub fn largest_palindrome(n: i32) -> i32 {
        if (n == 1) {
            return 9;
        }
        
        let mut p1 = n;
        let mut start:u32 = 1;
        // 没找到指数运算符,只能用这种最古老的写法
        while (p1 > 0){
            start = start * 10;
            p1 = p1 - 1;
        }
        let end = start/10 ;
        // 第一个循环回文数枚举
        for huiwen in (end..start).rev(){
            let huiwen_rev = huiwen.to_string();
            let mut huiwen2 = huiwen_rev.clone();
            // 是真的菜啊,学Rust的第三天,一段字符串逆向的代码写了半年
            let huiwen_chars = huiwen_rev.chars();
            for letter in huiwen_chars.rev(){
                let num_wei = letter.to_string();
                huiwen2 = huiwen2 + &num_wei;
            }
            //之所以转u64是因为132我略微看了一眼 n=8 的时侯相乘和回文数都会会越界,,所以没办法,最后在强转一下,,
            let huiwen_num:u64 = huiwen2.trim().parse().expect("Something wrong");
            // 第二重循环, 找因数
            let mut starts = (start - 1) as u64;
            //这里也是,应该用平方个但我比不知道咋用就只能这么写,,脑子已经被驴踢了
            while (starts * starts >= huiwen_num){
                if (huiwen_num % starts == 0){
                    let p:i32 = (huiwen_num/starts).to_string().len() as i32;
                    if (p == n){
                        let h = (huiwen_num % 1337) as i32;
                        println!("{}",h);
                        return h;
                    }   
                }
                starts = starts - 1
            }
        } 
    }
}
# 同样思路的Python代码,我看着这简单的代码只能说世界的参差。
# 哦,但是Python性能太拉了
# 执行用时: 2136 ms
# 内存消耗: 15 MB
class Solution:
    def largestPalindrome(self, n: int) -> int:
        if n == 1:
            return 9
        else:
            for huiwen in range(10**n - 1,10**(n-1) - 1, -1):
                k = str(huiwen)
                k = k[::-1]
                huiwen = eval(str(huiwen) + k)
                m = int(huiwen**(1/2))
                for p in range(10**n - 1,m,-1):
                    if huiwen % p == 0 and (huiwen / p >= 10 **(n-1) and huiwen / p < 10 ** n):
                        return huiwen % 1337

Rust中没有NULL的概念,取而代之的是使用Options的枚举, 主要是放置Null值泛滥

// 会遇到如,源码如下,我们使用的时候直接使用即可
//enum Option{
//  Some(T),
//  None,
//}
// 使用
fn main(){
    // Some无需显示声明
    let p = Some("Hello Google");
	// 这里一定要知名类型是Option, 编译器无法解析到
    let q:  Option<i32> = None;
    println!("{:?},{:?}",p,q);
}
Some("Hello Google"),None
Match控制流

match是Rust中功能最为强大的控制流之一, 包括我们前面l利用match优雅的代替except的过程, 简直就是我的超人 它允许:

  • 一个值和一系列模式进行匹配,并执行匹配的模式对应的代码
  • 模式可以是字面值,变量名, 通配符等等
// 还是用上面Sex的枚举做例子
// 用引用的时候参数变成 & Sex类型
fn judge_boy_or_girl(sex: Sex) -> String{
    match sex{
        // 这里的横线是因为我定义的类型是Vec,集合类型,需要绑定值操作
        Sex::male(_) => String::from("He is a male"),
        Sex::female(_) => String::from ("She is a female"),
    }
}
// 同样的,这里传递玩参数也会时区所有权,我们可以用引用的方式,&boy, &girl
println!("{}",judge_boy_or_girl(boy));
println!("{}",judge_boy_or_girl(girl));
He is a male
She is a female
  • 匹配的分支可以绑定被匹配到的对象的部分值,因此可以从enum变体中提取值, 或用可变引用修改.
fn print_sex(sex: & mut Sex) -> String{
    return match sex {
        Sex::male(character) => {
            character.push(String::from("Boy"));
            String::from("He is a male")
        },
        Sex::female(character) => {
            character.push(String::from("Girl"));
            String::from("She is a female")
        },
    }
}
println!("{}",print_sex(& mut boy));
println!("{}",print_sex(& mut girl));
println!("{:?}", boy);
println!("{:?}",girl);
He is a male
She is a female
male(["dicks", "Adam's apple ", "Boy"])
female(["burst", "Menstrual cycle:", "Girl"])

Match匹配的时候必须穷举所有的枚举项,毕竟是枚举类型嘛,可以理解.当然, 我们可以使用通配符"_"来代替没出没的值减少我们的麻烦:

#[derive(Debug)]
enum College_Student{
    benke(String),
    yanjiusheng(String),
    boshisheng(String),
}
impl College_Student{
    fn am_I_a_benke(&self) -> bool{
        match self{
            College_Student::benke(_)=> true,
            _=>false,
        }
    }
}

fn main(){
    let me = College_Student::benke(String::from("非人大学"));
    let brother = College_Student::yanjiusheng(String::from("迪士尼马猴烧酒大学"));
    println!("{}", me.am_I_a_benke());
    println!("{}", brother.am_I_a_benke());
}
true
false

Package, Crete, Module

Rust的代码组织包括:哪些细节可以暴漏,哪些细节私有,作用域内的哪些名称有效,这些功能在Rust中统称为模块系统,包括:

Package (最顶层) : Cargo的特性, 让我们构建,测试,共享crate

Crate(单元包) : 一个模块树, 产生一个Libuary或二进制习惯文件

Module (模块) : 让你控制代码的组织,作用域与私有路径

Path (路径) ** : 为struct fuction module 等项命名的方式**

Crate

类型: binary 二进制可执行文件, library: 类似于dll的库文件

Crate ROOT:

  • 源代码编译文件, RUST编译器从这里开始组成你的Crate的跟Module

作用: 将相关的功能组合到一个作用域内, 便于在项目间进行共享

Package
  • 只包含一个Cargo.toml , 描述了如何构建Crates
  • 只能包含0~1个 Library crate
  • 可以包含任意数量的binary crate
  • 必须至少包含一个crate
# 还是这颗文件树比较清晰
G:.
│  .gitignore
   # Cargo创建项目自动生成的文件,负责追踪项目依赖的精确版本,不需要手动更改
│  Cargo.lock
|  # Cargo配置文件,使用Toml格式, 默认将src/main.re作为crate root, crate名字与package的名字相同
│  Cargo.toml
│      
├─src  # 生成一个带main函数的代码文件
│      main.rs
|	   # package包含一个linrary crate, 这个文件式library crate的 crate root,crate名字与package的名字相同
|      (假如包含lib.rs)
└─target
    │  .rustc_info.json
    │  CACHEDIR.TAG
    └─debug

Cargo 会把我们上面两个有可能出现crate root的部分交给 rustc构建libruary 或binary

Cargo 会把binary crate放在 src/bin目录下面

Module

控制作用域和私有性, 在一个crate内将代码进行分组, 增加可读性易于复用, 控制项目(item)的私有性(public,private)

  • 建立 module:

    mode关键字, 可嵌套, 可以包含其他项(struct,enum,常量,trait, 函数等)

// 在lib.rs中的一个简单的例子
mod classroom{
    // 一教
    mod  Oneclass{
        fn findA_space() -> String{String::from("A113")}
        fn findB_space() -> String{String::from("B305")}
        fn findC_space() -> String{String::from("C209")}
        fn findD_space() -> String{String::from("No Space")}
    }
    // 综合楼
    mod Complex{
        enum Classroom {
            //  综合楼B座空教室列表
            B(Vec<String>),
            //  综合楼C座空教室列表
            C(Vec<String>),
            // 综合楼行政楼空教室列表,当然,绝对是None
            bouleuterion(Option<String>),
        }
    }
}
#所以他的命名空间树形应该长这样, 虽然丑但是我尽力了.
crate
  |____classroom
  			|________one_class
  			|			 |____findA_space
  			|			 |____findB_space
  			|			 |____findC_space
  			|			 |____findD_space
  			|
  			|________complex
  						|_______classroom
  									|______B
                                    |______C
                                    |______bouleuterion
Path

而我们这样画出来了这个树形图可不是白画的,因为我们要想访问这些东西,就要访问它的路径.

路径的使用可以有两种方式:绝对路径与相对路径

  • 绝对路径,从上图路径crate ( crate的root) 开始, 使用crate名或字面值

  • 相对路径: 从当前模块开始,使用 self,super或模块当前的标识符

  • 路径至少 由一个标识符组成, 标识符之间使用::

// 一个例子
fn example(){
	let find_a = crate::classroom::one_class::findA_space();
    println!("{}",find_a);
    let space_bo = crate::classroom::complex::Classroom::bouleuterion(None);
    let space_C = crate::classroom::complex::Classroom::C(vec![String::from("C304"),String::from("C208")]);
    println!("{:?},{:?}",space_bo,space_C);
}
25 |     let find_a = crate::classroom::one_class::findA_space();
   |                                               ^^^^^^^^^^^ private function

上面看到我的演示报了个error, 为什么呢, 看这个错,说这个东西是private fuction, 看着很熟悉.Rust中这种现象叫私有边界,或者我们就可以理解为封装.

私有边界/ 封装

模块不仅可以组织代码,还可以顶以私有边界.Rust中默认所有条目是private的,所以我们定义的时候需要在函数前加上pub代表这是一个piblic的函数.

  • 注意:父级模块无法访问子级模块中的私有条目,但是子级模块可以访问父级模块所有条目

  • 值得注意的是, 对于struct允许细化管理其中的属性的公私有(默认私有) 但是enum一旦共有则其定义全部类型均为共有

Use

引入包的时候我们使用use关键字.

感觉没啥,除了我们熟悉的.全部变成:: 和路径用上面的方法去找之外没什么难的,哦,use也可以使用as, 没了

使用外部包, 修改Cargo.toml, 上官网去复制配置即可. 用Use引入模块我们就可以使用了.

use支持嵌套路径同一行内 将条目引入, 也支持通配符*

//eg.
use std::{cmp::Ordering,io,......};
use std::io::*;

最后,我们想在我们的main.rs中使用我们lib 或其他任意文件中定义的这些模块,只需要用mod + 文件名; 声明使用即可, 这样编译器就会找src文件夹下面的文件.如果层级间的文件创建引用的话需要建更多目录保证层级关系一致

//lib.rs
pub mod classroom{
    // 一教
    pub mod one_class {
        pub fn findA_space() -> String{String::from("A113")}
        fn findB_space() -> String{String::from("B305")}
        fn findC_space() -> String{String::from("C209")}
        fn findD_space() -> String{String::from("No Space")}
    }
    // 综合楼
    pub mod complex {
        #[derive(Debug)]
        pub enum Classroom {
            //  综合楼B座空教室列表
            B(Vec<String>),
            //  综合楼C座空教室列表
            C(Vec<String>),
            // 综合楼行政楼空教室列表,当然,绝对是None
            bouleuterion(Option<String>),
        }
    }
}



//main.rs
mod lib;
use lib::classroom::*;
fn main(){
   let find_a =one_class::findA_space();
   println!("{}",find_a);
   let space_bo =complex::Classroom::bouleuterion(None);
   let space_C = complex::Classroom::C(vec![String::from("C304"),String::from("C208")]);
   println!("{:?},{:?}",space_bo,space_C);
}
A113
bouleuterion(None),C(["C304", "C208"])

常用的集合(Heap上动态大小)

Vector

Vector其实我们之前介绍过,在定义某些类型的时候也用过,是一个长度自由变换的数组.这这里我们做一个较详细的介绍

Vec , vector, 是由标准库提供的, 可存储多个值但只能存储相同类型的数据的一种结构, 其存储值在内存中连续存放

// vector.rs
pub mod vector{
    pub fn create_i32_vector() -> Vec<i32>{
        // 这里的初识话必须知名类型,无法返回Vec这样的类型
        let m:Vec<i32> = Vec::new();
        // 当然,我们前面用的vec!宏的方式创建的,比如
        // let new_vec:Vec = vec![10, 20, 30];
        return m;
    }
}
// main.rs
mod vector;
use vector::vector as vectors;
fn main() {
    let m = vectors::create_i32_vector();
    println!("{:?}",m);
}
[]

向Vector做增删改 (对Vec索引用[]): push 增, pop 删/ remove删索引, 使用[]索引修改, 使用索引或get 进行查(get 遇到越界不会像[] panic, 而是返回None)

fn main() {
    let mut  m = vectors::create_i32_vector();
    m.push(10);
    m.push(30);
    m.push(20);
    m[0] = 5;
    m.remove(2);
    let n = m[m.len() - 1];
    m.pop();
    println!("{},{:?}",n,m);
}
30,[5]

Vector可以用for轻松遍历:

// 两种都可
pub fn iter_vector(vecs:& Vec<i32>){
    for i in vecs.iter(){
        println!("{}",i);
    }
    for &k in vecs{
        println!("{}",k);
    }
}

虽然Vector只能存放相同的数据,但是我们可以利用枚举类型可以不加不同类型数据这一点定义同一枚举下的附加不同数据来达到存储多种数据的目的 (毕竟是指针嘛,也算是同一类型,问题不大,这个方法真的是妙诀

#[derive(Debug)]
enum Student{
    name(String),
    age(u8),
    hobby(Vec<String>),
    study_good(bool),
}
fn main() {
    let my_name = Student::name(String::from("Hello"));
    let my_age = Student::age(69);
    let my_hobby = Student::hobby(vec![String::from("Rust"), "Python".to_string()]);
    let my_study = Student::study_good(false);
    let mut m = Vec::new();
    m.push(my_name);
    m.push(my_age);
    m.push(my_hobby);
    m.push(my_study);
    println!("{:?}",m);
    for iters in m.iter(){
        match iters{
            Student::name(a) =>{print!("{} ",a);},
            Student::age(a) =>{print!("{} ",a);},
            Student::hobby(a) => {print!("{:?} ",a)}
            Student::study_good(a) => {print!("{} ",a);},
        };
    }
}
[name("Hello"), age(69), hobby(["Rust", "Python"]), study_good(false)]
Hello 69 ["Rust", "Python"] false 
String

学一个东西肯定要从定义学起,字符串就是Byte的集合, 并提供了一些方法将字节解析为文本

在Rust的核心语言层面 只有一个字符串类型: 字符串切片str/&str . 字符串切片,就是对存储在其它地方、UTF8编码的字符串引用. 而我们所说所用的String类型,来自标准库而不是核心语言,可增长,可修改,有所有权的采用UTF8编码的类型.

我们所说的Rust的字符串并不是单指一种,而是String 与&str两种都是. 这两种在便准苦衷使用的最多,且都是用UTF8编码.(Rust还提供很多其他的字符串类型)

其实我再前面所有权的规则和切片都有放过Dtring类型的底层数据结构的图片区方便我们理解.String最大的好处就是所有的基础数据类型/标量类型 ,都可以无条件转String,就这么强大和牛逼给你你不用?反正我是一定会用的.

fn main(){
    let a = 1;
    let b:u128 = 2;
    let c = 10.99;
    let d:char = 'a';
    let e:&str = "hello";
    let f = false;
    a.to_string();
    b.to_string();
    c.to_string();
    d.to_string();
    e.to_string();
    f.to_string();
    println!("{} {} {} {} {} {}",a,b,c,d,e,f);
}
1 2 10.99 a hello false

几个实用的方法:

// 增加
fn main(){
    let mut  a = String::from("Hello");
    a.push_str(" Google");
    a.push('');
    //  这里如果不赋值给a a就会失去所有权
    a = a + "But I think Baidu is better ";
    a = a.add("So I use Baidu");
    println!("{}",a);
    //遍历
    for letter in a.chars(){
        print!(" {}",letter);
    }
    // 
}

注意:

  • String是由Vec衍生的一种类型,所以有Len方法获取长度.但是获取到的长度是Byte长度而不是字数,遇到稍微复杂点的Unicode可能出现12个我们直观意义上的字符(名字叫做Unicode标量值) 输出24甚至48的情况都有可能. 这同时意味着我们索引切片也可能会发生意想不到的乱码.建议直接用chars获取长度也行啊

    fn main(){
        let a = "我是kamisama哈哈哈".to_string();
        println!("{}",a.len());
        // 字符串切片
        let n = &a[1..20];
        println!("{}",n);
    }
    27   // 这个数值是怎么来的:中文len = 3(*5), 字母len = 1(*8), 表情 len = 4(*1), 加起来27
    thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside '我' (byte 0..3) of `我是kamisama哈哈哈`', src\main.rs:44:14		// 错误字符无法解析
    
    //用chars()的方法获取Unicode标量值
    fn(){
        let n = a.chars();
        let mut count = 0;
        for letter in n{
            count += 1
        }
        println!("{:?}",count);
    }
    

上面设计到了Rust看待字符串的两种方式:字节, 变量值.实际上Rust还会以字形簇(Grapheme Clusters)来看待字符串.当然,我只能说只要你不是使用什么七扭八歪的的语言(比如梵语),那么问题就不大.最关键的是字形簇解析方式在标准库中没有自带,要用得去官网学

HashMap

不容易啊终于到了Hashmap, 在Python中字典(/Hashmap)可是我超人一般的存在! 有了hashMpa就可以开启两数之和,梦开始地方

什么是HashMap Hashmap就是key, Values的键值对 , 在Rust中表现为HashMap, 其原理是Hash函数决定了内存中如何存放Key 与 Value. 当然, 由于Rust的严格性,我们定义hashMap的时候要显示指定类型.基本操作如下

n main(){
    let mut try_hash:HashMap<i32, i32> = HashMap::new();
    try_hash.insert(10,6);
    try_hash.insert(20,3);
    // 遍历
    for key in try_hash.keys(){
        print!("{}:{:?},",key,try_hash.get(key));
    }
    println!();
    try_hash.remove(&20);
    println!("{:?}",try_hash);
    try_hash.clear();
    try_hash.insert(10,8);
    println!("{}",try_hash[&10]);
    let b = try_hash.len();
    println!("{}",b);
    // 没有见会返回Option的None
    let c = try_hash.get(&15);
    let d = try_hash.keys();
    println!("{:?}",c);
    println!("{:?}",d);
}
20:Some(3),10:Some(6),
{10: 6}
8
1
None
[10]

下面压力来到了hashMap所有权这一边.众所周知,所有权是Rust的核心, 对于HashMap而言:

  • 对于实现了Copy trait类型的值会被复制到 Hashmap中

  • 对于持有所有权的值(如String), 值会被移动, 所有权会转移给hashMap.这个时候我们考虑使用引用.

    fn main(){
        let a = 10;
        let b = String::from("Hello");
        let mut try_hash:HashMap<i32, String> = HashMap::new();
        try_hash.insert(a,b);
        println!("{}",a);
        // 这里的B已经没有所有全了,所以打印B会报错 value borrowed here after move
        // println!("{}",b);
    }
    
    // 使用引用
    fn main(){
        let a = 10;
        let b = String::from("Hello");
        let mut try_hash:HashMap<i32, &String> = HashMap::new();
        try_hash.insert(a,&b);
        println!("{}",a);
        println!("{}",b);
        println!("{:?}",try_hash);
    }
    10
    Hello
    {10: "Hello"}
    

HashMap的更新:

  • 替换现有key 的value: 再插入相同的Key即可

HashMap 判断键是否在字典中: Entry

最后来一个梦开始的地方: 两数之和:

use std::collections::HashMap;
impl Solution {
    pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
        let mut hashmap:HashMap<i32,[i32;2]> = HashMap::new();
        let mut count = 0;
        for num in nums.iter(){
            match hashmap.get(num){
                Some(_index_) => {
                    let mut ind:[i32;2] = (*_index_);
                    ind[1] = count;
                    hashmap.insert((*num),ind);},
                _ => {
                    hashmap.insert((*num),[count,-1]);
                },
            }
            count = count + 1;
        }
        println!("{:?}",hashmap);
        for key in hashmap.keys(){
            let num1 = *(key);
            let loca:i32 = match hashmap.get(key){
                Some(_index_) => {_index_[0]},
                _ => {-1},
            };
            if target - num1 == num1{
                match hashmap.get(key){
                    Some(_index_) =>{
                        if _index_[1] == -1{
                            continue;
                        }else{
                            return vec![_index_[0],_index_[1]];
                        }
                    },
                    _ => {continue;},
                }
            }else{
                match hashmap.get(&(target - num1)){
                    Some(_index_) =>{
                        
                        return vec![loca,_index_[0]]
                    },
                    _ => {continue;},
                }
            }
        }
        // 得指定一个不可能返回的数,不然会出问题.
        return vec![3,4];
    }     
}

你可能感兴趣的:(rust)