C++:利用哈希表对unordered系列容器模拟实现

文章目录

  • unordered容器使用
    • [在长度 2N 的数组中找出重复 N 次的元素](https://leetcode.cn/problems/n-repeated-element-in-size-2n-array/description/)
  • 底层结构
    • 初步改造哈希表
    • 基本逻辑的实现
  • 最终实现

本篇主要总结unordered系列容器和其底层结构

unordered容器使用

从使用的角度来讲,不管是unordered_map还是unordered_set,本质上就是把内容不再有序,而是采用无序的方式进行存储,对于其实现细节在后续会进行封装

在长度 2N 的数组中找出重复 N 次的元素

在这里插入图片描述
对于之前的方法,大多使用一种类似于计数排序的方法来解决问题

class Solution 
{
public:
    int repeatedNTimes(vector<int>& nums) 
    {
       int hash[10001] = {0};
        for(auto e : nums)
        {
            hash[e]++;
        }
        for(auto e : nums)
        {
            if(hash[e] != 1)
                return e;
        }
        return -1;
    }
};

而利用unordered_set,可以更方便的解决问题

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) {
        unordered_set<int> found;
        for (int num: nums) {
            if (found.count(num)) {
                return num;
            }
            found.insert(num);
        }
        // 不可能的情况
        return -1;
    }
};

底层结构

unordered系列的关联式容器,在进行查找等的效率比较高,究其原因是因为使用了哈希的结构,那么下面就利用哈希表,来对这两个容器进行封装

首先搭建出主体框架

// set
#pragma once
#include "HashTable.h"
namespace myset
{
	template<class K>
	class unordered_set
	{
		struct SetKeyOfT
		{
			K& operator()(K& key)
			{
				return key;
			}
		};
	public:
		bool insert(const K& key)
		{
			return _table.insert(key);
		}
		void print()
		{
			_table.print();
		}
	private:
		opened_hashing::HashTable<K, K, SetKeyOfT> _table;
	};
}

// map
#pragma once

#include "HashTable.h"

namespace mymap
{
	template<class K, class V>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& key)
			{
				return key.first;
			}
		};
	public:
		bool insert(const pair<K, V>& key)
		{
			return _table.insert(key);
		}
		void print()
		{
			_table.print();
		}
	private:
		opened_hashing::HashTable<K, pair<K, V>, MapKeyOfT> _table;
	};
}

// 测试函数
// 最基础的测试
void test_set1()
{
	myset::unordered_set<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(60);
	st.insert(50);
	st.print();
}

void test_map1()
{
	mymap::unordered_map<int, int> dict;
	dict.insert(make_pair(1, 1));
	dict.insert(make_pair(2, 2));
	dict.insert(make_pair(3, 3));
	dict.insert(make_pair(4, 4));
}

此时运行会报错
在这里插入图片描述
报错的原因是因为,对于map来说Value传递的是键值对,键值对不可以直接进行运算,因此要引入KeyOfT的概念,这里借助这个仿函数找到Key值

在这里插入图片描述
下面实现迭代器的内容:

对于哈希表来说,迭代器内部包含的就是一个一个节点的地址:

实现++

对于哈希表来说,实现++的逻辑可以看成,看当前节点的下一个还有没有值,如果没有就说明走到最后了,要找下一个哈希桶里面的内容,如果后面还有值,那就是下一个内容

那么如何记录所在的桶,可以通过对哈希表的位置进行定位来获取现在属于哪一桶,进而去寻找下一个桶

初步改造哈希表

#pragma once
#include <iostream>
#include <vector>
using namespace std;

namespace opened_hashing
{
	template<class T>
	struct _Convert
	{
		T& operator()(T& key)
		{
			return key;
		}
	};

	template<>
	struct _Convert<string>
	{
		size_t& operator()(const string& key)
		{
			size_t sum = 0;
			for (auto e : key)
			{
				sum += e * 31;
			}
			return sum;
		}
	};

	// 定义节点信息
	template<class T>
	struct Node
	{
		Node(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
		Node* _next;
		T _data;
	};

	template<class K, class T, class KeyOfT>
	class HashTable;

	template<class K, class T, class Ref, class Ptr, class KeyOfT>
	struct _iterator
	{
		typedef _iterator<K, T, T&, T*, KeyOfT> Self;
		typedef Node<T> Node;

		_iterator(Node* node, HashTable<K, T, KeyOfT>* pht, int hashi)
			:_node(node)
			, _pht(pht)
			, _hashi(hashi)
		{}

		Self operator++()
		{
			// 如果后面还有值,++就是到这个桶的下一个元素
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				++_hashi;
				while (_hashi < _pht->_table.size())
				{
					if (_pht->_table[_hashi])
					{
						_node = _pht->_table[_hashi];
						break;
					}

					++_hashi;
				}
				if (_hashi == _pht->_table.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}

		Node* _node;
		int _hashi; // 现在位于哪个桶
		HashTable<K, T, KeyOfT>* _pht; // 迭代器所在的哈希表
	};

	template<class K, class T, class KeyOfT>
	class HashTable
	{
		template<class K, class T, class Ref, class Ptr, class KeyOfT>
		friend struct _iterator;
		typedef Node<T> Node;
	public:
		typedef _iterator<K, T, T&, T*, KeyOfT> iterator;
		// 构造函数
		HashTable()
			:_n(0)
		{
			_table.resize(10);
		}

		// 析构函数
		~HashTable()
		{
			//cout << endl << "*******************" << endl;
			//cout << "destructor" << endl;
			for (int i = 0; i < _table.size(); i++)
			{
				//cout << "[" << i << "]->";
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					//cout << cur->_kv.first << " ";
					delete cur;
					cur = next;
				}
				//cout << endl;
				_table[i] = nullptr;
			}
		}

		// 迭代器
		iterator begin()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return iterator(_table[i], this, i);
				}
			}
			return end();
		}

		iterator end()
		{
			return iterator(nullptr, this, -1);
		}

		// 插入元素
		bool insert(const T& data)
		{
			KeyOfT kot;
			// 如果哈希表中有这个元素,就不插入了
			if (find(data))
			{
				return false;
			}

			// 扩容问题
			if (_n == _table.size())
			{
				HashTable newtable;
				int newsize = (int)_table.size() * 2;
				newtable._table.resize(newsize, nullptr);
				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						// 把哈希桶中的元素插入到新表中
						int newhashi = kot(cur->_data) % newsize;
						// 头插
						cur->_next = newtable._table[newhashi];
						newtable._table[newhashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newtable._table);
			}

			// 先找到在哈希表中的位置
			size_t hashi = kot(data) % _table.size();

			// 把节点插进去
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			_n++;

			return true;
		}

		Node* find(const T& Key)
		{
			KeyOfT kot;
			// 先找到它所在的桶
			int hashi = kot(Key) % _table.size();

			// 在它所在桶里面找数据
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_data == Key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

	private:
		vector<Node*> _table;
		size_t _n;
	};
}

基本逻辑的实现

// set
#pragma once
#include "HashTable.h"
namespace myset
{
	template<class K>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef typename opened_hashing::HashTable<K, K, SetKeyOfT>::iterator iterator;
	public:
		iterator begin()
		{
			return _table.begin();
		}

		iterator end()
		{
			return _table.end();
		}

		bool insert(const K& key)
		{
			return _table.insert(key);
		}
	private:
		opened_hashing::HashTable<K, K, SetKeyOfT> _table;
	};
}

// map
#pragma once

#include "HashTable.h"

namespace mymap
{
	template<class K, class V>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& key)
			{
				return key.first;
			}
		};
		typedef typename opened_hashing::HashTable<K, pair<K, V>, MapKeyOfT>::iterator iterator;
	public:
		iterator begin()
		{
			return _table.begin();
		}

		iterator end()
		{
			return _table.end();
		}
		bool insert(const pair<K, V>& key)
		{
			return _table.insert(key);
		}

	private:
		opened_hashing::HashTable<K, pair<K, V>, MapKeyOfT> _table;
	};
}

现在的主体框架已经搭建出来了,用下面的测试用例已经可以实现迭代器遍历了,证明我们的基本框架逻辑已经完成了

// 最基础的测试
void test_set1()
{
	myset::unordered_set<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(60);
	st.insert(50);
	auto it = st.begin();
	while (it != st.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	cout << endl;
	cout << endl;
}

void test_map1()
{
	mymap::unordered_map<int, int> dict;
	dict.insert(make_pair(1, 1));
	dict.insert(make_pair(10, 1));
	dict.insert(make_pair(220, 1));
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << (*it).first << ":" << (*it).second << endl;
		++it;
	}
	cout << endl;
}

但当遇到string类的内容就不可以继续进行了

// 带有string类的内容
void test_set2()
{
	myset::unordered_set<string> st;
	st.insert("apple");
	st.insert("banana");
	st.insert("pear");
	st.insert("cake");
	auto it = st.begin();
	while (it != st.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	cout << endl;
	cout << endl;
}

void test_map2()
{
	mymap::unordered_map<string, string> dict;
	dict.insert(make_pair("排序", "sort"));
	dict.insert(make_pair("苹果", "apple"));
	dict.insert(make_pair("香蕉", "banana"));
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << (*it).first << ":" << (*it).second << endl;
		++it;
	}
	cout << endl;
}

在这里插入图片描述
还是前面出现过的原因,因为这里的kot取出来的是第一个元素,但是第一个元素是string,string是不能参与运算的,因此这里要再套一层仿函数来解决string类的问题

最终实现

改造后的哈希表

#pragma once
#include <iostream>
#include <vector>
using namespace std;

template <class T>
struct _HashT
{
	const T& operator()(const T& Key)
	{
		return Key;
	}
};

template<>
struct _HashT<string>
{
	size_t operator()(const string& Key)
	{
		size_t sum = 0;
		for (auto e : Key)
		{
			sum += e * 31;
		}
		return sum;
	}
};

namespace opened_hashing
{
	// 定义节点信息
	template<class T>
	struct Node
	{
		Node(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
		Node* _next;
		T _data;
	};

	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	struct _iterator
	{
		typedef _iterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
		typedef Node<T> Node;

		_iterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht, int hashi)
			:_node(node)
			, _pht(pht)
			, _hashi(hashi)
		{}

		_iterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht, int hashi)
			:_node(node)
			, _pht(pht)
			, _hashi(hashi)
		{}

		Self operator++()
		{
			// 如果后面还有值,++就是到这个桶的下一个元素
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				++_hashi;
				while (_hashi < _pht->_table.size())
				{
					if (_pht->_table[_hashi])
					{
						_node = _pht->_table[_hashi];
						break;
					}

					++_hashi;
				}
				if (_hashi == _pht->_table.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		Node* _node;
		int _hashi; // 现在位于哪个桶
		const HashTable<K, T, KeyOfT, Hash>* _pht; // 迭代器所在的哈希表
	};

	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct _iterator;
		typedef Node<T> Node;
	public:
		typedef _iterator<K, T, T&, T*, KeyOfT, Hash> iterator;
		typedef _iterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;
		// 构造函数
		HashTable()
			:_n(0)
		{
			_table.resize(10);
		}

		// 析构函数
		~HashTable()
		{
			//cout << endl << "*******************" << endl;
			//cout << "destructor" << endl;
			for (int i = 0; i < _table.size(); i++)
			{
				//cout << "[" << i << "]->";
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					//cout << cur->_kv.first << " ";
					delete cur;
					cur = next;
				}
				//cout << endl;
				_table[i] = nullptr;
			}
		}

		// 迭代器
		iterator begin()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return iterator(_table[i], this, i);
				}
			}
			return end();
		}

		iterator end()
		{
			return iterator(nullptr, this, -1);
		}

		const_iterator begin() const
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return const_iterator(_table[i], this, i);
				}
			}
			return end();
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this, -1);
		}

		// 插入元素
		bool insert(const T& data)
		{
			KeyOfT kot;
			Hash ht;
			// 如果哈希表中有这个元素,就不插入了
			if (find(data))
			{
				return false;
			}

			// 扩容问题
			if (_n == _table.size())
			{
				HashTable newtable;
				int newsize = (int)_table.size() * 2;
				newtable._table.resize(newsize, nullptr);
				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						// 把哈希桶中的元素插入到新表中
						int newhashi = ht(kot(cur->_data)) % newsize;
						// 头插
						cur->_next = newtable._table[newhashi];
						newtable._table[newhashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newtable._table);
			}

			// 先找到在哈希表中的位置
			size_t hashi = ht(kot(data)) % _table.size();

			// 把节点插进去
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			_n++;

			return true;
		}

		Node* find(const T& Key)
		{
			KeyOfT kot;
			Hash ht;
			// 先找到它所在的桶
			int hashi = ht(kot(Key)) % _table.size();

			// 在它所在桶里面找数据
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_data == Key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

	private:
		vector<Node*> _table;
		size_t _n;
	};
}

封装后的哈希结构

// set
#pragma once
#include "HashTable.h"
namespace myset
{
	template<class K, class Hash = _HashT<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef typename opened_hashing::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
		typedef typename opened_hashing::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;
	public:
		const_iterator begin() const
		{
			return _table.begin();
		}

		const_iterator end() const
		{
			return _table.end();
		}

		bool insert(const K& key)
		{
			return _table.insert(key);
		}
	private:
		opened_hashing::HashTable<K, K, SetKeyOfT, Hash> _table;
	};
}

// map
#pragma once

#include "HashTable.h"

namespace mymap
{
	template<class K, class V, class Hash = _HashT<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& key)
			{
				return key.first;
			}
		};
		typedef typename opened_hashing::HashTable<K, pair<const K, V>, MapKeyOfT, Hash >::iterator iterator;
		typedef typename opened_hashing::HashTable<K, pair<const K, V>, MapKeyOfT, Hash >::const_iterator const_iterator;
	public:
		iterator begin()
		{
			return _table.begin();
		}

		iterator end()
		{
			return _table.end();
		}

		bool insert(const pair<const K, V>& key)
		{
			return _table.insert(key);
		}

	private:
		opened_hashing::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _table;
	};
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MYSQL索引使用注意事项

索引使用注意事项&#xff1a; 1.索引列运算 不要在索引列上进行运算操作&#xff0c;否则索引将失效&#xff1b; 2.字符串不加引号 字符串类型使用时&#xff0c;不加引号&#xff0c;否则索引将失效&#xff1b; 3.模糊查询 如果仅仅是尾部模糊匹配&#xff0c;索引将不会失…

使用EasyPlayer播放H.265视频流

使用EasyPlayer播放H.265视频流 EasyPlayer流媒体视频播放器 EasyPlayer流媒体视频播放器 EasyPlayer流媒体视频播放器可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持RTSP、RTMP、HLS、FLV、WebRTC等格式的视频流播放&#xff0c;并且已实现网页…

NEJM一篇新文为例,聊聊孟德尔随机化研究mr 连锁不平衡(linkage disequilibrium)

2019年3月14日&#xff0c;新英格兰医学杂志发表了一篇论著&#xff0c;Mendelian Randomization Study of ACLY and Cardiovascular disease, 即《ACLY和心血管疾病的孟德尔随机化研究》。与小咖在2017年1月9日报道的一篇发表在新英格兰医学的孟德尔随机化研究——精读NEJM&am…

【Python】重磅!这本30w人都在看的Python数据分析畅销书更新了!

Python 语言极具吸引力。自从 1991 年诞生以来&#xff0c;Python 如今已经成为最受欢迎的解释型编程语言。 【文末送书】今天推荐一本Python领域优质数据分析书籍&#xff0c;这本30w人都在看的书&#xff0c;值得入手。 目录 作译者简介主要变动导读视频购书链接文末送书 pan…

【GUI】-- 13 贪吃蛇小游戏之食物及成绩判断

GUI编程 04 贪吃蛇小游戏 4.4 第四步&#xff1a;食物及成绩判断 首先&#xff0c;添加食物与分数的数据定义&#xff1a; //食物的坐标int foodX;int foodY;Random random new Random();//积分面板数据结构int score;在初始化方法中&#xff0c;添加(画出)食物与分数&…

编译 CUDA加速的 OpenCV-4.8.0 版本

文章目录 前言一、编译环境二、前期准备三、CMake编译四、VS编译OpenCV.sln五、问题 前言 由于项目需要用上CUDA加速的OpenCV&#xff0c;编译时也踩了不少坑&#xff0c;所以这里记录一下。 一、编译环境 我的编译环境是&#xff1a; Win10 RTX4050 CUDA-12.0 CUDNN 8.9.…

Windows、VMware问题集合

Windows、VMware问题集合 一. Windows11安装VMware17提升虚拟机性能1. 桌面右击图标点击属性——>兼容性&#xff0c;找到“以管理员身份运行此程序”勾选&#xff0c;最后点击确定即可。2. 关闭win11的内核隔离功能。 二. VMware虚拟机报错&#xff08;虚拟化性能计数器需要…

【人工智能入门学习资料福利】

总目录如下&#xff08;部分截取&#xff09;&#xff1a; 百度网盘链接&#xff1a;https://pan.baidu.com/s/1bfDVG-xcPR3f3nfBJXxqQQ?pwdifu6 提取码&#xff1a; ifu6

【电子通识】USB3.0和USB2.0有什么区别?

版本 USB2.0是2000年4月27日由USB-IF组织提出了USB2.0总线协议规范。 USB3.0是2008年11月17日由USB-IF组织提出了超高速USB3.0规范。 图标对比 USB2.0的标志就是和USB1.1的标志基本上没啥区别&#xff0c;还是以前的那个样子&#xff0c;使用黑色颜色用标识 USB3.0它有一个S…

变态跳台阶,剑指offer

目录 题目&#xff1a; 我们直接看题解吧&#xff1a; 相似题目&#xff1a; 解题方法&#xff1a; 审题目事例提示&#xff1a; 解题思路&#xff1a; 代码实现&#xff1a; 题目地址&#xff1a; 【剑指Offer】9、变态跳台阶 难度&#xff1a;简单 今天刷变态跳台阶&#xf…

不停的挖掘硬盘的最大潜能

从 NAS 上退休的硬盘被用在了监控的存储上了。 随着硬盘使用寿命的接近尾声&#xff0c;感觉就是从高附加值数据到低附加值数据上。监控数据只会保留那么几个月的时间&#xff0c;很多时候都会被覆盖重新写入。 有人问为什么监控数据不保留几年的&#xff0c;那是因为监控数据…

Golang 中的良好代码与糟糕代码

最近&#xff0c;有人要求我详细解释在 Golang 中什么是好的代码和坏的代码。我觉得这个练习非常有趣。实际上&#xff0c;足够有趣以至于我写了一篇关于这个话题的文章。为了说明我的回答&#xff0c;我选择了我在空中交通管理&#xff08;ATM&#xff09;领域遇到的一个具体用…

Apache POI简介

三十二、Apache POI 32.1 介绍 Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用POI在Java程序中对Miscrosoft Office各种文件进行读写操作。 一般情况下&#xff0c;POI都是用于操作Excel文件。 Apache POI 的应用场…

【开源】基于JAVA的开放实验室管理系统

项目编号&#xff1a; S 013 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S013&#xff0c;文末获取源码。} 项目编号&#xff1a;S013&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实…

LeetCode 2304. 网格中的最小路径代价:DP

【LetMeFly】2304.网格中的最小路径代价&#xff1a;DP 力扣题目链接&#xff1a;https://leetcode.cn/problems/minimum-path-cost-in-a-grid/ 给你一个下标从 0 开始的整数矩阵 grid &#xff0c;矩阵大小为 m x n &#xff0c;由从 0 到 m * n - 1 的不同整数组成。你可以…

#gStore-weekly | ​gAnswer源码分析:基于通用数据集的NE、RE服务开发

PART1 简 介 目前基于知识图谱的问答模式有两种&#xff0c;一种是基于信息检索的方式&#xff0c;一种是基于语义分析的方式。前者较之于后者&#xff0c;没有真正关心语义&#xff0c;主要是ranker算法&#xff0c;擅于处理简单问题&#xff0c;后者则是从语义的角度将用户…

数环通入选中国信通院《高质量数字化转型技术方案集(2023)》,积极推动企业数字化转型

近日&#xff0c;中国信息通信研究院“铸基计划”发布《高质量数字化转型技术方案集&#xff08;2023&#xff09;》&#xff0c;数环通《数字化协同管理解决方案》成功入选。 随着科技的快速发展和市场竞争的日益激烈&#xff0c;数字化转型已成为企业持续发展和提升竞争力的关…

JetLinks设备接入的认识与理解【woodwhales.cn】

为了更好的阅读体验&#xff0c;建议移步至笔者的博客阅读&#xff1a;JetLinks设备接入的认识与理解 1、认识 JetLinks 1.1、官网文档 官网&#xff1a;https://www.jetlinks.cn/ JetLinks 有两个产品&#xff1a;JetLinks-lot和JetLinks-view 官方文档&#xff1a; JetLi…

WPF树形控件TreeView使用介绍

WPF 中的 TreeView 控件用于显示层次结构数据。它是由可展开和可折叠的 TreeViewItem 节点组成的&#xff0c;这些节点可以无限嵌套以表示数据的层次。 TreeView 基本用法 例如实现下图的效果&#xff1a; xaml代码如下&#xff1a; <Window x:Class"TreeView01.Mai…

优秀智慧园区案例 - 上海世博文化公园智慧园区,先进智慧园区建设方案经验

一、项目背景 世博文化公园是上海的绿色新地标&#xff0c;是生态自然永续、文化融合创新、市民欢聚共享的大公园。作为世博地区的城市更新项目&#xff0c;世博文化公园的建设关乎上海城市风貌、上海文化展示、城市生态环境、市民游客体验、上海服务品牌等&#xff0c;被赋予…
最新文章