Rust之常用集合(三):哈希映射(Hash Map)

news2024/10/5 14:25:56

开发环境

  • Windows 10
  • Rust 1.66.0

 

  • VS Code 1.74.2

  项目工程

这里继续沿用上次工程rust-demo

 在哈希图中存储带有关联值的键

我们常见的集合中的最后一个是哈希映射。HashMap<K, V>类型使用散列函数存储K类型的键到V类型的值的映射,这决定了它如何将这些键和值放入内存。许多编程语言都支持这种数据结构,但它们通常使用不同的名称,如哈希、Map、对象、哈希表、字典或关联数组,仅举几例。

当你不像使用向量那样使用索引来查找数据,而是使用一个可以是任何类型的键来查找时,哈希映射就很有用。例如,在一个游戏中,你可以在一个哈希映射中记录每个球队的得分,其中每个键是一个球队的名字,值是每个球队的得分。给定一个球队的名字,你可以检索它的分数。

我们将在本节中介绍哈希映射的基本API,但还有很多好东西隐藏在标准库对HashMap<K, V>定义的函数中。一如既往,请查看标准库文档以了解更多信息。

创建新的哈希映射

创建空哈希映射的一种方法是使用new,用insert添加元素。在清下例中,我们要记录两支球队的分数,他们的名字是蓝队和黄队。蓝队以10分开始,而黄队以50分开始。

fn main() {
    use std::collections::HashMap;        // HashMap

    let mut scores = HashMap::new();      // 创建HashMap对象

    scores.insert(String::from("Blue"), 10);      // 存储键,值
    scores.insert(String::from("Yellow"), 50);
}

请注意,我们需要首先use标准库中集合部分的HashMap。在我们的三个常用集合中,这个集合是最不常用的,所以它不包括在前奏中自动带入范围的特性中。哈希映射在标准库中得到的支持也比较少;例如,没有内置的宏来构造它们。

就像向量一样,哈希映射将其数据存储在堆上。这个HashMap的键是String类型,值是i32类型。像向量一样,哈希映射是同质的:所有的键必须具有相同的类型,所有的值必须具有相同的类型。

访问哈希映射中的值

我们可以通过向get方法提供键来从哈希图中获取一个值。如下例所示

fn main() {
    use std::collections::HashMap;                 // 使用HashMap

    let mut scores = HashMap::new();               // 创建HashMap对象

    scores.insert(String::from("Blue"), 10);       // 存储键,值
    scores.insert(String::from("Yellow"), 50);

    let team_name = String::from("Blue");          // 访问HashMap中的键
    println!("team_name  = {}", team_name);
    
    let score = scores.get(&team_name).copied().unwrap_or(0);

    println!("score  = {}", score);
}

编译,运行

cargo run

在这里,score将有与蓝队相关的值,结果将是10get方法返回一个Option<&V>;如果哈希映射中没有该键的值,get将返回None。这个程序通过调用copied得到一个Option<i32>而不是Option<&i32>来处理Option,如果scores中没有该键的条目,则unwrap_orscore设为0。 

我们可以用类似于处理向量的方式来迭代哈希映射中的每个键/值对,使用for循环。

fn main() {
    use std::collections::HashMap;           // 使用HashMap

    let mut scores = HashMap::new();           // 创建HashMap对象

    scores.insert(String::from("Blue"), 10);      // 存储键,值
    scores.insert(String::from("Yellow"), 50);

    for (key, value) in &scores {             // 遍历HashMap,打印键,值
        println!("{key}: {value}");
    }
}

运行

cargo run

 哈希映射和所有权

对于实现了Copy特性的类型,如i32,值被复制到哈希图中。对于像String这样的自有值,这些值将被移动,哈希图将成为这些值的所有者,如下例所示。

fn main() {
    use std::collections::HashMap;

    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);       // insert方法  
}

在调用insert将变量field_namefield_value移入哈希映射后,我们无法使用这些变量。

如果我们在哈希映射中插入对数值的引用,这些数值就不会被移到哈希图映射中。引用所指向的值必须至少在哈希映射有效的时间内是有效的。

更新哈希映射

虽然键和值对的数量是可以增长的,但每个独特的键在同一时间只能有一个与之相关的值(但反之则不然:例如,蓝队和黄队都可以在scores哈希映射中存储值10)。

当你想改变哈希映射中的数据时,你必须决定如何处理一个键已经分配了一个值的情况。你可以用新值替换旧值,完全不考虑旧值。你可以保留旧值而忽略新值,只在键还没有值的情况下添加新值。或者你可以把旧值和新值结合起来。让我们来看看如何做这些事情吧!

覆盖值

如果我们在哈希映射中插入一个键和一个值,然后用不同的值插入同一个键,那么与该键相关的值将被替换。尽管下例中的代码调用了两次insert,但哈希映射将只包含一个键/值对,因为我们两次都是插入蓝队的键的值。

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Blue"), 25);   // 覆盖了键为Blue的值

    println!("{:?}", scores);
}

运行

cargo run

 这段代码将打印{"Blue":25}。原来的数值10已经被覆盖了。

仅当键不存在时才添加键和值

常见的做法是检查一个特定的键是否已经存在于哈希映射中的一个值,然后采取以下行动:如果该键确实存在于哈希映射中,现有的值应该保持原状。如果该键不存在,则插入该键和它的一个值。

哈希映射为此有一个特殊的API,叫做entry,它把你想检查的键作为一个参数。entry方法的返回值是一个叫做Entry的枚举,代表一个可能存在或不存在的值。比方说,我们想检查黄队的钥匙是否有一个与之相关的值。如果没有,我们要插入值50,对蓝队也是如此。使用entryAPI,代码看起来如下例。

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    scores.entry(String::from("Yellow")).or_insert(50);      // entry接口
    scores.entry(String::from("Blue")).or_insert(50);

    println!("{:?}", scores);
}

运行

cargo run

 

 

Entry上的or_insert方法被定义为:如果该键存在,则返回对应的Entry键的值的可变引用;如果不存在,则插入参数作为该键的新值并返回新值的可变引用。这种技术比我们自己写逻辑要干净得多,此外,与借贷检查器的配合也更加默契。

运行上例中的代码将打印{"黄": 50, "Blue": 10}。对entry的第一次调用将插入黄队的键,值为50,因为黄队还没有一个值。第二次调用entry不会改变哈希图,因为蓝队已经有了值10。

基于旧值更新一个数值

哈希映射的另一个常见用例是查询一个键的值,然后根据旧值进行更新。例如,下例中显示了计算每个词在一些文本中出现多少次的代码。我们使用一个以单词为键的哈希映射,并递增其值以记录我们看到该单词的次数。如果这是我们第一次看到一个词,我们将首先插入值0。

fn main() {
    use std::collections::HashMap;

    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);       // entry接口
        *count += 1;
    }

    println!("{:?}", map);
}

运行

cargo run

 

 这段代码将打印{"world"。2, "hello": 1, "wonderful": 1}. 你可能会看到相同的键/值对以不同的顺序打印出来:回顾一下 "访问哈希映射中的值 "部分,在哈希映射上的迭代是以任意的顺序进行的。

split_whitespace方法返回text中值的子片的迭代器,子片之间用空格分隔。or_insert方法返回一个对指定键的值的可变引用(&mut V)。在这里,我们把这个易变的引用存储在count变量中,所以为了赋值给这个值,我们必须先用星号(*)解除对count的引用。可变引用在for循环结束时退出作用域,所以所有这些变化都是安全的,也是借用规则所允许的。

哈希函数

默认情况下,HashMap使用一个名为SipHash的散列函数,可以抵抗涉及散列表的拒绝服务(DoS)攻击1。这不是现有的最快的散列算法,但是随着性能的下降而带来的更好的安全性的折衷是值得的。如果你对你的代码进行剖析,发现默认的哈希函数对你的目的来说太慢了,你可以通过指定一个不同的哈希函数来切换到另一个函数。哈希函数是一个实现了BuildHasher特性的类型。我们将在后续讨论特质和如何实现它们的问题。你不一定要从头开始实现你自己的哈希器;crates.io有其他Rust用户共享的库,提供了实现许多常见哈希算法的哈希器。

总结

当你需要存储、访问和修改数据时,Vectors,Strings和HashMap将在程序中提供大量的必要功能。下面是一些你现在应该有能力解决的练习。

  • 给定一个整数列表,使用一个向量并返回列表的中位数(当排序时,位于中间位置的值)和模式(出现频率最高的值;哈希图在这里会有帮助)。
  • 将字符串转换为猪拉丁语。每个词的第一个辅音被移到词尾,并加上 "ay",所以 "first "变成 "irst-fay"。以元音开头的单词则在词尾加上 "hay"("苹果 "变成 "apple-hay")。牢记关于UTF-8编码的细节!
  • 使用哈希映射和向量,创建一个文本界面,让用户将员工姓名添加到公司的某个部门。例如,"将莎莉添加到工程部 "或 "将阿米尔添加到销售部"。然后让用户检索一个部门所有人员的列表,或按部门检索公司所有人员的列表,按字母排序。

标准库的API文档描述了向量、字符串和哈希映射的方法,这些方法对这些练习很有帮助!

本章重点

  • 哈希映射的概念
  • 创建哈希映射,存储键和值
  • 访问哈希映射的键和值
  • 哈希映射的所有权
  • 更新哈希映射:覆盖,当键不存在添加键和值,基于旧值更新数值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124495.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue2.0

JavaScript&#xff08;JS 教程&#xff09; - JavaScript | MDN Vue是构建用户界面的 JavaScript 框架 安装 — 2.0 Vue.jsGitHub - vuejs/devtools: ⚙️ Browser devtools extension for debugging Vue.js applications.Installation | Vue DevtoolsVSCode插件&#xff1a…

【SpringMVC】SpringMVC整合Mybatis

1.整合思路 第一步&#xff1a;整合dao层 mybatis和spring整合&#xff0c;通过spring管理mapper接口使用mapper的扫描自动扫描mapper接口在spring中进行注册 第二步&#xff1a;整合service层 通过spring管理service接口使用配置方式将service接口配置在spring配置文件中实现…

如何能有兴趣的编代码,而不是畏难?

如果我告诉你&#xff0c;成功做出一道代码题拿下5万美元&#xff0c;做出四道代码题20万美金的年薪到手&#xff0c;你是不是会立刻发愤图强呢&#xff1f;这不是个段子&#xff0c;是北美程序员的面试情况&#xff1a;有小伙伴刷题的过程中觉得刷不下去了&#xff0c;就以此来…

【Javassist】快速入门系列10 当检测到instanceof表达式时用代码块替换

系列文章目录 01 在方法体的开头或结尾插入代码 02 使用Javassist实现方法执行时间统计 03 使用Javassist实现方法异常处理 04 使用Javassist更改整个方法体 05 当有指定方法调用时替换方法调用的内容 06 当有构造方法调用时替换方法调用的内容 07 当检测到字段被访问时使用语…

Android核心技术——Jetpack Hilt依赖注入

依赖注入是什么 个人理解&#xff1a;把有依赖关系的类放在容器中&#xff0c;解析这些类的实例&#xff0c;并在运行时注入到对应的字段中&#xff0c;就是依赖注入&#xff0c;目的是为了类的解耦 例子&#xff1a;A 类 中用到了 B 类&#xff0c;一般情况下需要在 A 类中 …

Promise:工作流程、常见API、使用方法、手撕Promise、async/await

Promise和axios一、Promise的常见骚操作0.初体验1.使用Promise封装原生AJAX2.Promise实例对象的两个属性&#xff08;1&#xff09;状态属性PromiseState&#xff08;2&#xff09;结果值属性PromiseResult3.Promise的工作流程4.Promise的API&#xff08;1&#xff09;.then和.…

ceph--理论

分布式存储--------Ceph 前言&#xff1a;随着OpenStack的快速发展&#xff0c;给Ceph的发展注入了强心剂&#xff0c;越来越多的人使用Ceph作为OpenStack的底层共享存储&#xff0c;Ceph在中国的社区也蓬勃发展起来。近两年OpenStack火爆度不及当年&#xff0c;借助于云原生尤…

SoringBoot+VUE前后端分离项目学习笔记 - 【01 环境配置以及VUE2集成ElementUI】

技术栈一览 SpringBoot2 Vue2 ElementUI Axios Hutool Mysql Echarts 所需软件环境 版本一览 JDK 1.8Mysql5.7Node 14.16.0navicatIdea 2021 Vue-cli 安装 npm install -g vue/cli 查看版本 创建VUE工程 初始化工程 vue create vue 选择Manually select feature…

【MySQL】数据库索引 - 浅谈索引类型

索引类型可以分为哈希表、有序数组和 N 叉树 不管是哈希还是有序数组&#xff0c;或者 N 叉树&#xff0c;它们都是基于其自身数据结构的特性来提高读写速度。在 NoSQL 里面还运用到了 LSM 树&#xff0c;来提高写的速度&#xff0c;还有跳表等数据结构来进行优化。 不过需要…

数据结构与算法-java

什么是数组&#xff1f; &#xff08;1&#xff09;数组是计算机中最基本的数据结构之一&#xff0c;我们会用一些名为索引的数字来标识每项数据在数组中的位置。 &#xff08;2&#xff09;大多数编程语言中索引是从0开始的。 &#xff08;3&#xff09;数组在内存中是存在连续…

如何打造一个流式数据湖

Flink将数据写入到 hudi 准备阶段 启动hadoop集群&#xff08;单机模式&#xff09; ./sbin/start-all.shhdfs离开安全模式 hdfs dfsadmin -safemode leave启动hive 后台启动元数据 ./hive --service metastore &启动hiveserver2 ./hiveserver2 &执行sql语句之前…

fpga实操训练(ip rom)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 altera的fpga本身自带了rom的ip&#xff0c;使用起来也十分方便。实际开发中&#xff0c;使用rom的场景也很多&#xff0c;比如一些默认的配置文件…

TensorFlow之回归模型-2

1 基本概念 回归模型 线性 线性模型 非线性模型 线性回归 逻辑回归 Log Loss&#xff08;损失函数&#xff09; 分类临界值 2 效率预测 回归问题是预测一个持续的值&#xff0c;主要是用于解决不确定性的问题&#xff0c;例如&#xff0c;一个商品在未来可能的价格或…

CMAKE_INSTALL_PREFIX

一、定义 CMAKE_INSTALL_PREFIX为cmake的内置变量&#xff0c;用于指定cmake执行install命令时&#xff0c;安装的路径前缀。Linux下的默认路径是/usr/local &#xff0c;Windows下默认路径是 C:/Program Files/${PROJECT_NAME} 二、用…

dcloud如何苹果ios系统真机测试-HBuilderX真机运行ios测试

dcloud如何运行到IOS真机测试 1&#xff0c;下载安装iTunes 安装完毕后重新打开HBuilderX 2&#xff0c;点击运行真机 将iPhone 与电脑进行链接&#xff0c;点信任&#xff0c; 运行-运行到手机或模拟器-运行到IOS APP 基座 安装过itunes就会有显示&#xff0c;但是这里还有…

进程的学习 —— Linux下的进程

目录前言1 认识进程1.1 进程的概念1.2 进程的管理1.3 查看进程的两种方法1.4 getpid、getppid和fork函数2 进程状态2.1 普遍概念下的进程状态2.2 Linux下的进程状态2.2.1 测试Linux的各种进程状态2.2.2 僵尸进程2.3 孤儿进程3 进程切换与进程优先级3.1 并行、并发3.2 进程切换3…

kafka和sparkStreaming

1、Kafka 1、kafka集群架构 producer 消息生产者&#xff0c;发布消息到Kafka集群的终端或服务 broker Kafka集群中包含的服务器&#xff0c;一个borker就表示kafka集群中的一个节点 topic 每条发布到Kafka集群的消息属于的类别&#xff0c;即Kafka是面向 topic 的。 更通俗…

HDFS 常用命令

一、HDFS常用命令 1、查看版本 hdfs version 2、创建 HDFS 文件系统目录。 格式&#xff1a; hdfs dfs -mkdir /user/dir1 3、列出目录下的所有文件 类似 Linux Shell 的 ls 命令。用它可以列出指定目录下的所有文件 hdfs dfs -ls /user/ 4、把本地文件系统文件和目录拷贝…

整合Tkinter GUI界面的古诗词词云生成

Python语言提供的wordcloud词云功能&#xff0c;使文本数据的可视化&#xff0c;简单而美丽。但网上的大多数词云生成功能&#xff0c;多半没有可交互的GUI界面&#xff0c;使用起来稍觉不便。笔者结合网上的中文词云功能&#xff0c;以唐诗三百首&#xff0c;宋词三百首&#…

拟合算法(模型+代码)

拟合的结果是得到一个确定的曲线 最小二乘法的几何解释&#xff1a; argmin 存在参数k&#xff0c;b使括号里的值最小 第一种有绝对值&#xff0c;不易求导&#xff08;求导在求最小值&#xff09;&#xff0c;计算较为复杂&#xff1b;所以我们往往使用第二种定义&#xff0…