文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、uthash是什么?
在使用python时我们常常会使用字典来进行数据检索,这个字典实际就是哈希表,通过key值来进行唯一检索。
那么,在C语言中,我们应该如何实现哈希表呢?当哈希表中元素比较简单时,我们可以直接构造一个数组,把下标看作key值,value值就是该下标对应得元素值。但是当我们需要使用很多复杂操作时,还是希望能找到一个类似于内置函数的东西去实现对于哈希表的各种操作。
于是,uthash就出现了!
uthash 是C的比较优秀的开源代码,它实现了常见的hash操作函数,例如查找、插入、删除等。该套开源代码采用宏的方式实现hash函数的相关功能,支持C语言的任意数据结构作为key值(可以是自定义的struct或基本数据类型),甚至可以采用多个值作为key,需要注意的是对于不同类型的key值,hash函数声明略有不同。(参考博文)
uthash相当于为我们提供了一个专门用于处理哈希表的函数库,在学会它里面基本函数的用法后,就可以应用在自己的程序中了。
二、基本hash用法
uthash用法详情可以参看英文使用手册。
1.添加头文件
由于uthash是以宏的方式定义了对哈希表的操作函数,因此想在代码中使用hash函数时,一定要在自己程序的初始添加uthash头文件。当然还得先下载源代码。
#include "uthash.h"
2.创建键-值对结构
uthash中定义的哈希表中每个键值对都是一个实例化的结构体,结构体定义如下:
struct my_struct {
int id; /* key */
char name[10]; /* value */
UT_hash_handle hh; /* makes this structure hashable */
};
在结构体定义中,key的数据类型可以是整型、字符、指针等,value的类型也是自定义的,并且value是不一定存在的。
我们可以在结构体中只定义key值,这样哈希表就只关注表中有没有某个key,而不关心它对应的value值;这样构造出的哈希表可以看作是python中的set,可以保证内部无重复的元素(因为哈希表的key不能有重复):
struct my_struct {
int id; /* key */
UT_hash_handle hh; /* makes this structure hashable */
};
结构体中key的定义一定要有,UT_hash_handle hh也不能去掉。
hh是内部使用的hash处理句柄,在使用过程中,只需要在结构体中定义一个UT_hash_handle类型的变量即可,不需要为该句柄变量赋值,但必须在该结构体中定义该变量。
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
如何定义哈希表呢?
可以想到哈希表就是上面这种键-值对的数组,因此我们定义一个指向struct my_struct类型的指针就可以表示哈希表 (数组和指针的关系),可以对它进行增删改查操作。
初始化一个哈希表:
struct my_struct *users = NULL; /* important! initialize to NULL */
这里注意一定要初始化为NULL,不然会报错!
接下来介绍几个常用hash函数,全部是以key是int类型的情况来讲。
3.查找元素 HASH_FIND_INT
函数使用:
HASH_FIND_INT( users, &user_id, s ); /* s: output pointer */
参数含义:
- users:待查询的hash表;
- &user_id:指向想查询的key的地址;user_id表示要查的key值,前面加& 取址;
- s:表示该函数的输出值,即我们根据user_id查到的键值对;它是一个指向哈希表users中一个键值对的指针。
因此在调用该函数前,要先定义s, 完整用法如下:
struct my_struct *find_user(int user_id) { /* 获得key=user_id的键值对 */
struct my_struct *s; /* 定义s */
HASH_FIND_INT( users, &user_id, s ); /* s: output pointer */
return s;
}
4.插入元素 HASH_ADD_INT
由于要保持哈希表中的唯一性,在插入键值对之前,一定要先判断表中是否已经存在要插入的键,如果已存在,就直接修改键对应的value;如果没有存在,插入键值对。
函数使用:
HASH_ADD_INT( users, id, s ); /* id: name of key field */
参数含义:
- users:待插入的hash表;
- id:自定义的键值对结构体struct my_struct中,key域的变量名;即下面struct中的“id”;注意这里只把变量名输入即可,不需要带入值。
struct my_struct {
int id; /* key */
char name[10]; /* value */
UT_hash_handle hh; /* makes this structure hashable */
};
- s:待插入的键值对结构体,是指针形式。它的key和value都要给定了。
完整用法:
void add_user(int user_id, char *name) {
struct my_struct *s;
HASH_FIND_INT(users, &user_id, s); /* id already in the hash? */
if (s==NULL) { /* 如果s的key不存在 */
s = (struct my_struct *)mallo c(sizeof *s);
s->id = user_id;
HASH_ADD_INT( users, id, s ); /* id: name of key field */
}
strcpy(s->name, name); /* s的key存在,直接更新value值 */
}
5.统计元素个数 HASH_COUNT
函数使用:
num_users = HASH_COUNT(users);
参数含义:
- users:待统计元素个数的hash表
函数输出即为哈希表中存在的键值对个数。
6.循环表中元素 HASH_ITER
循环哈希表的元素有两种方法
方法一:自己写for循环
在uthash中,哈希表中每个键值对之间有指针相连,并且可以通过句柄hh来实现指针调用。 每个键值对都会有一个前向指针hh.prev与后向指针hh.next,因此哈希表也可以当作双向链表使用。
我们就可以用 s = s->hh.next 实现从前向后循环的功能。
下面这个代码实现循环输出哈希表中每个键值对的功能。
void print_users() {
struct my_struct *s;
for(s=users; s != NULL; s=s->hh.next) {
printf("user id %d: name %s\n", s->id, s->name);
}
}
这种方法中,在循环时s不能被随便释放或删除,因为还要获取它的next值;不过可以通过加入一个临时变量tmp,把s->hh.next 保存下来,再删除s。于是就出现了函数HASH_ITER,它里面即包含了这样一个tmp变量,使我们在循环过程中可以安全地删除s。
方法二:使用定义好的函数
循环函数HASH_ITER
struct my_struct *s, *tmp;
HASH_ITER(hh, users, s, tmp)
参数含义:
- hh:表示hash句柄,不是个变量;
- users:待循环的hash表;
- s:表示每次循环时获得的那个键值对,在函数前直接定义,不用赋初值。在循环中,我们就对s进行操作。
- tmp:就是前面提到的临时变量。这个变量从表面上来看没有什么意义,但是会在这个函数内部被使用,所以一定要声明一个tmp结构体指针(不用赋值),并送入函数。
可以用下面代码实现循环输出哈希表中每个键值对:
struct my_struct *s, *tmp;
HASH_ITER(hh, users, s, tmp) {
printf("user id %d: name %s\n", s->id, s->name);
/* ... it is safe to delete and free s here */
}
除了以上函数,uthash还提供了许多其他函数和高级功能,下面图中列出了全部(应该是的吧==)hash函数和它们的参数。详细用法需要参照英文使用手册。
做leetcode时有需要再去查吧hhhhh
总结
大家可以在leetcode1207上练习使用uthash。
————————————————
原文链接:https://blog.csdn.net/lijianyi0219/article/details/109343993