C++STL之模拟实现<unordered_map和unordered_set>

news/2024/5/19 10:54:44/文章来源:https://blog.csdn.net/weixin_59400943/article/details/127016741

目录

      • 🌈前言
  • 🚁1、哈希表的改造
  • 🚂2、模拟实现的完整代码
  • 🚃3、哈希表的应用
    • 🚄3.1、位图的概念
    • 🚅3.2、位图的实现
    • 🚆3.3、位图完整代码
  • 🚇4、位图的变形
  • 🚈5、布隆过滤器

🌈前言

本篇文章学习C++STL< unordered_map和unordered_set >的模拟实现!!!


🚁1、哈希表的改造

  1. 模板参数列表的改造
// 哈希桶节点
template <typename T>
struct HashNode
{
public:HashNode(const T& data): _data(data), _next(nullptr){}
public:T _data;HashNode<T>* _next;
};// Unordered_set -> HashTable<K, Key, KeyOfT, hashF>
// Unordered_map -> HashTable<K, pair<K, V>, KeyOfT, hashF>template <typename K, typename T, typename KeyOfT, typename hashF>
class HashTable
{};

模板参数解析:

  • K:关键码(Key)的类型

  • T:不同的容器,T的类型就不相同。如果是unordered_map,T代表一个键值对,如果是unordered_set,T代表一个关键码(Key)

  • KeyOfT:因为V的类型不同,通过value取key的方式就不同,详细见unordered_map/set的实现

  • hashF: 哈希仿函数,哈希函数使用除留余数法,需要将Key转换为整形数字才能取模


  1. 增加迭代器操作

迭代器:

  • 哈希桶的迭代器是单向的,因为它的底层是一个链表,只有指向下一个节点的指针

  • 迭代器需要访问哈希桶中的私有成员,需要将迭代器设置成友元类

  • 迭代器需要定义在哈希桶前面,但是迭代器需要访问哈希桶的私有成员,因为程序是从当前位置向上开始找哈希桶在不在,所有我们需要前置声明一下哈希桶类

namespace BUCKetHash
{// 前置声明,迭代器需要访问哈希桶类的私有成员template <typename K, typename T, typename KeyOfT, typename hashF>class HashTable;// 迭代器template <typename K, typename T, typename KeyOfT, typename hashF>class HTIterator{private:typedef HashNode<T> Node;					  // 哈希表节点typedef HashTable<K, T, KeyOfT, hashF> HT;	  // 哈希表typedef HTIterator<K, T, KeyOfT, hashF> Self; // 迭代器本身public:HTIterator(Node* _node, HT* _ht): node(_node), ht(_ht){}HTIterator(const HTIterator& hti): node(hti.node), ht(hti.ht){}Self& operator++(){// 如果传过来的节点指针不为空,则直接跳到下一个节点if (node->_next != nullptr){node = node->_next;}// 当前节点为空,找下一个桶else{KeyOfT kot;hashF hf;// 计算这个节点映射到桶的位置size_t hashi = hf(kot(node->_data));hashi %= ht->_tables.size();++hashi; // 当前桶已经遍历完,需要对hashi进行自增// 遍历判断其他哈希桶是否为空,不为空则返回这个桶的指针for (; hashi < ht->_tables.size(); ++hashi){if (ht->_tables[hashi] != nullptr){node = ht->_tables[hashi];break;}}// 映射位置等于有效数据时,说明已经越界(走到尾的情况)if (hashi == ht->_tables.size()){node = nullptr;}}return *this;}// 后置++,先使用后自增Self operator++(int){Self tmp(this);++(*this);return tmp;}T& operator*(){return node->_data;}T* operator->(){return &(node->_data);}bool operator!=(const HTIterator& hti) const{return node != hti.node;}bool operator==(const HTIterator& hti) const{return node == hti.node;}public:Node* node; // 哈希节点指针HT* ht;		// 哈希表指针(this)};
}

哈希桶迭代器自增图解:
在这里插入图片描述


  1. 增加通过key获取value操作
  • 哈希桶节点的模板类型T是不确定的,是unordered_map的时候是键对值(pair<K, V>),而unordered_set只是一个关键码(Key)

  • 所以我们需要建立一个仿函数将它们进行区分(开头有讲到)

unordered_map:

namespace Myself
{template <typename K, typename V, typename hashF = BUCKetHash::HashDF<K>>class unordered_map{private:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}private:BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF> ht;}; 
}

unordered_set:

namespace Myself
{template <typename K, typename hashF = BUCKetHash::HashDF<K>>class unordered_set{private:struct SetKeyOfT{const K& operator()(const K& key){return key;}private:BUCKetHash::HashTable<K, K, SetKeyOfT, hashF> ht;};
}

  1. 增加通过哈希函数转换无符号整形操作

对关键码进行转换:

  • 因为哈希桶需要对该关键码映射对应的位置,所有需要把该关键码统一修改
namespace BUCKetHash
{// 仿函数:处理特殊的Key值问题 -- 后面需要对数据进行无符号整形转换template <typename K>struct HashDF{size_t operator()(const K& key){return static_cast<size_t>(key);}};template <>struct HashDF<string>{size_t operator()(const string& str){size_t HDS = 0;for (auto& e : str){HDS = HDS * 131 + static_cast<size_t>(e);}return HDS;}};
}

🚂2、模拟实现的完整代码

HashTable.h

#pragma once#include <iostream>
#include <vector>
using namespace std;// 哈希桶(指针数组):哈希表中存储指针,这个指针存储映射的值,映射相同位置时,创建新的节点直接链到这个指针中
namespace BUCKetHash
{// 仿函数:处理特殊的Key值问题 -- 后面需要对数据进行无符号整形转换template <typename K>struct HashDF{size_t operator()(const K& key){return static_cast<size_t>(key);}};template <>struct HashDF<string>{size_t operator()(const string& str){size_t HDS = 0;for (auto& e : str){HDS = HDS * 131 + static_cast<size_t>(e);}return HDS;}};// 哈希表节点template <typename T>struct HashNode{public:HashNode(const T& data): _data(data), _next(nullptr){}public:T _data;HashNode<T>* _next;};// 前置声明,迭代器需要访问哈希桶类的私有成员template <typename K, typename T, typename KeyOfT, typename hashF>class HashTable;// 迭代器template <typename K, typename T, typename KeyOfT, typename hashF>class HTIterator{private:typedef HashNode<T> Node;					  // 哈希表节点typedef HashTable<K, T, KeyOfT, hashF> HT;	  // 哈希表typedef HTIterator<K, T, KeyOfT, hashF> Self; // 迭代器本身public:HTIterator(Node* _node, HT* _ht): node(_node), ht(_ht){}HTIterator(const HTIterator& hti): node(hti.node), ht(hti.ht){}Self& operator++(){// 如果传过来的节点指针不为空,则直接跳到下一个节点if (node->_next != nullptr){node = node->_next;}// 当前节点为空,找下一个桶else{KeyOfT kot;hashF hf;// 计算这个节点映射到桶的位置size_t hashi = hf(kot(node->_data));hashi %= ht->_tables.size();++hashi; // 当前桶已经遍历完,需要对hashi进行自增for (; hashi < ht->_tables.size(); ++hashi){if (ht->_tables[hashi] != nullptr){node = ht->_tables[hashi];break;}}// 映射位置等于有效数据时,说明已经越界if (hashi == ht->_tables.size()){node = nullptr;}}return *this;}Self operator++(int){Self tmp(this);++(*this);return tmp;}T& operator*(){return node->_data;}T* operator->(){return &(node->_data);}bool operator!=(const HTIterator& hti) const{return node != hti.node;}bool operator==(const HTIterator& hti) const{return node == hti.node;}public:Node* node; // 哈希节点指针HT* ht;		// 哈希表指针(this)};// Unordered_set -> HashTable<K, Key, KeyOfT, hashF>// Unordered_map -> HashTable<K, pair<K, V>, KeyOfT, hashF>template <typename K, typename T, typename KeyOfT, typename hashF>class HashTable{template <typename K, typename T, typename KeyOfT, typename hashF>friend class HTIterator; // 迭代器内部需要访问哈希表的私有成员private:typedef HashNode<T> Node;public:typedef HTIterator<K, T, KeyOfT, hashF> iterator;typedef HTIterator<const K, const T, KeyOfT, hashF> const_iterator;public:iterator begin(){for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];if (cur != nullptr){return iterator(cur, this);}}return end();}iterator end(){return iterator(nullptr, this);}const_iterator cbegin() const{for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];if (cur != nullptr){return const_iterator(cur, this);}}return cend();}const_iterator cend() const{return const_iterator(nullptr, this);}public:HashTable() = default;HashTable(const HashTable& ht){_tables.resize(ht._tables.size(), nullptr);for (size_t i = 0; i < ht._tables.size(); ++i){Node* cur = ht._tables[i];while (cur != nullptr){this->Insert(kot(cur->_data));cur = cur->_next;}}}HashTable& operator=(const HashTable ht){HashTable tmp(ht);this->_n = tmp._n;tmp._tables.swap(_tables);return *this;}~HashTable(){for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur != nullptr){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}// 返回值为pair是为了支持unordered_map的[]重载pair<iterator, bool> Insert(const T& data){Node* IfExist = Find(kot(data));if (IfExist != nullptr)return make_pair(iterator(IfExist, this), false); // 插入识别,数据冗余if (_tables.size() == 0 || _tables.size() == _n){size_t NewSize = _tables.size() == 0 ? 10 : _tables.size() * 2;// 把旧哈希桶的节点重新映射到新开辟的哈希桶HashTable newHT;newHT._tables.resize(NewSize, nullptr);for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur != nullptr){// 将旧哈希桶的值挪到新哈希桶中 -- 头插Node* next = cur->_next; // 保存下一个节点,挪动后防止找不到size_t hashi = HashDF(kot(cur->_data)) % NewSize; // 需要重新映射新扩容后的哈希桶,模新长度的哈希桶cur->_next = newHT._tables[hashi];newHT._tables[hashi] = cur;cur = next;}_tables[i] = nullptr;}newHT._tables.swap(_tables);}// 跟闭散列的方法一样需要继续模表大小size_t hashi = HashDF(kot(data));hashi %= _tables.size();// 构造需要插入的键对值,然后插入到映射的位置 -- 画图Node* newNode = new Node(data);newNode->_next = _tables[hashi];_tables[hashi] = newNode;++_n;// 插入成功,返回指向新节点的迭代器return make_pair(iterator(newNode, this), true); }bool erase(const K& key){size_t hashi = HashDF(key);hashi %= _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur != nullptr){if (kot(cur->_data) == key){// 删除位置为"头"if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}Node* Find(const K& key){if (_tables.size() == 0)return nullptr;// 查找跟闭散列差不多size_t hashi = HashDF(key);hashi %= _tables.size();// 对映射的位置进行循环遍历查找Node* cur = _tables[hashi];while (cur != nullptr){if (kot(cur->_data) == key)return cur;cur = cur->_next;}return nullptr;}private:vector<Node*> _tables;size_t _n = 0;hashF HashDF;KeyOfT kot;};
}

Unordered_map.h

#pragma once
#include "HashTable.h"namespace Myself
{template <typename K, typename V, typename hashF = BUCKetHash::HashDF<K>>class unordered_map{private:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef BUCKetHash::HashNode<pair<K, V>> Node;public:// 迭代器和常量迭代器typedef typename BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF>::iterator iterator;typedef typename BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF>::const_iterator const_iterator;public:iterator begin(){return ht.begin();}iterator end(){return ht.end();}const_iterator cbegin() const{return ht.cbegin();}const_iterator cend() const{return ht.cend();}pair<iterator, bool> insert(const pair<K, V>& kv){return ht.Insert(kv);}bool erase(const K& key){return ht.erase(key);}Node* find(const K& key){return ht.Find(key);}V& operator[](const K& key){pair<iterator, bool> ret = ht.Insert(make_pair(key, V()));return ret.first->second;}private:BUCKetHash::HashTable<K, pair<K, V>, MapKeyOfT, hashF> ht;}; 
}

Unordered_set

#pragma once
#include "HashTable.h"
namespace Myself
{template <typename K, typename hashF = BUCKetHash::HashDF<K>>class unordered_set{private:struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef BUCKetHash::HashNode<K> Node;public:typedef typename BUCKetHash::HashTable<K, K, SetKeyOfT, hashF>::iterator iterator;typedef typename BUCKetHash::HashTable<K, K, SetKeyOfT, hashF>::const_iterator const_iterator;public:// 迭代器iterator begin(){return ht.begin();}iterator end(){return ht.end();}const_iterator cbegin() const{return ht.cbegin();}const_iterator cend() const{return ht.cend();}// 其他接口pair<iterator, bool> insert(const K& key){return ht.Insert(key);}bool erase(const K& key){return ht.erase(key);}Node* find(const K& key){return ht.Find(key);}private:BUCKetHash::HashTable<K, K, SetKeyOfT, hashF> ht;};
}

🚃3、哈希表的应用

🚄3.1、位图的概念

概念:

  • 所谓位图,就是用每一位(比特位)来存放某种状态

  • 适用海量数据,数据无重复的场景。通常用来判断某个数据是否存在

在这里插入图片描述


🚅3.2、位图的实现

🌈前言:位图常用的接口只有三个,插入、删除和查找

位图的结构:

  • 位图的结构是一个vector,底层是char/int类型,char可以标记8个状态(1字节),而int可以标识32个状态(4字节),模板类型是一个无符号整形

  • 位图的基本操作就是存储不重复数据,即可以使用0/1来标识存不存在

在这里插入图片描述


  1. 构造函数:
  • 每个char类型可以标识8个状态,如果是80个数据的话,只需要开辟10个空间足以

  • 如果是81 - 87的话,那么只需要多开辟一个char类型空间就足够了,也不会浪费很大内存

template <size_t N>
class bitset
{
public:bitset(){// N是数据个数,我们使用比特位来映射,char类型一个字节占八个比特位bit.resize((N / 8) + 1, 0);}
}

  1. 插入:
  • 使用哈希表的直接定址法,用一个比特位标识映射的位置在不在

  • 标识状态要注意大小端问题,否则会出错

namespace myself
{// 注意大小端问题template <size_t N>class bitset{public:void set(size_t x){// x映射的位置在第几个char中size_t i = x / 8;// x标识的状态在char中的第几个比特位size_t j = x % 8;// 将映射位置的值(bit[i]) "按位或" 上一个向左移动j位的值bit[i] |= (1 << j);}private:vector<char> bit;};
}

在这里插入图片描述


  1. 删除:
  • 删除就是把这个数据映射在char中的位置的状态更改尾0即可
namespace myself
{template <size_t N>class bitset{public:void reset(size_t x){size_t i = x / 8;size_t j = x % 8;bit[i] &= ~(1 << j);}private:vector<char> bit;};
}

在这里插入图片描述


  1. 查找:
namespace myself
{template <size_t N>class bitset{public:bool test(size_t x){size_t i = x / 8;size_t j = x % 8;return  bit[i] & (1 << j);}private:vector<char> bit;};
}

在这里插入图片描述


🚆3.3、位图完整代码

// 位图:哈希表的变式,用于处理海量数据
namespace myself
{// 注意大小端问题template <size_t N>class bitset{public:bitset(){// N是数据个数,我们使用比特位来映射,char类型一个字节占八个比特位bit.resize((N / 8) + 1, 0);}void set(size_t x){size_t i = x / 8;size_t j = x % 8;// 将映射位置的值(bit[i]) "按位或" 上一个向左移动j位的值bit[i] |= (1 << j);}void reset(size_t x){size_t i = x / 8;size_t j = x % 8;bit[i] &= ~(1 << j);}bool test(size_t x){size_t i = x / 8;size_t j = x % 8;return  bit[i] & (1 << j);}private:vector<char> bit;};
}

🚇4、位图的变形

首先抛出一个问题:给定100亿个整数,设计算法找出只出现一次和二次的整数!!!

  • 首先,使用位图是解决不了这个问题的,因为位图只能标识两种状态(0/1)

  • 这道题需要标识四种状态才能解决问题


思路:

在这里插入图片描述

完整代码:

namespace myself
{
template <size_t N>class two_bitset{public:void set(size_t x){bool in1 = bs1.test(x);bool in2 = bs2.test(x);// 如果二个位图中都不存在这个数据,则状态改为出现一次(01)if (in1 == 0 && in2 == 0){bs2.set(x);}// 如果这个数据已经存在,则状态改为出现二次(10)else if (in1 == 0 && in2 == 1){bs1.set(x);bs2.reset(x);}// 如果这个数据已经存在二次以上,则状态改为(11)else{bs1.set(x);}}// 只存在一次的数据bool is_once(size_t x){return bs1.test(x) == 0 && bs2.test(x) == 1;}private:bitset<N> bs1;bitset<N> bs2;};
}

🚈5、布隆过滤器

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

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

相关文章

Python数据分析——数据基础

数据的分类 结构化数据 结构化数据遵从一定的数据规范和格式&#xff0c;一般存储在结构化的文件中&#xff0c;或者结构化数据库中。 例如这就是一条结构化的数据&#xff0c;按照Company、Date、Stock这样的数据格式进行存储&#xff0c;数据类型是确定的&#xff0c;数据的…

福特汽车是美股电动汽车行业值得投资的股票吗?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 福特是一个值得投资的电动汽车股票吗&#xff1f; 猛兽财经认为福特&#xff08;F &#xff09;是美股电动汽车行业中一支不错的电动汽车股票&#xff0c;该公司在7 月下旬对其电动汽车计划的更新就证明了这一点。 2022 年…

【Js】JS的历史:认识JS的来龙去脉

文章目录1 JavaScript 历史2 JavaScript是什么&#xff1f;3 JavaScript的作用4 Html/Css/Js的关系4.1Html/CSS 标记语言---描述类语言4.2 Js脚本语言 -- 编程类语言4.3 执行的区别5 JS的组成5.1 ECMAScript5.2 Dom5.3 Bom6 JS书写位置6.1 行内式6.2 内嵌式&#xff08;★&…

Unity Shader: multi_compile一个文件变幻多个shader

multi_compile 我们在写shader时&#xff0c;经常会出现同一个shader在面对不同的一些需求时&#xff0c;可能需要出现一些局部的变化&#xff0c;比如有些地方需要描边&#xff0c;有些地方不需要描边&#xff0c;由于在shader中不适合使用if&#xff0c;所以最好就是再写一份…

sklearn包使用Extra-Trees和GridSearchCV完成成人死亡率预测

成年人死亡率指的是每一千人中 15 岁至 60 岁死亡的概率&#xff08;数学期望&#xff09;。这里我们给出了世界卫生组织&#xff08;WHO&#xff09;下属的全球卫生观察站&#xff08;GHO&#xff09;数据存储库跟踪的所有国家健康状况以及许多其他相关因素。要求利用训练数据…

Fiddler使用

最近老是使用fiddler工具&#xff0c;用了忘所以特此来记录。先说说fiddler吧&#xff0c;这是一款功能强大的抓包工具&#xff0c;平时可以拿来抓抓小程序的包&#xff0c;直接通关羊了个羊&#xff0c;安装就不说了&#xff0c;都是无脑下一步&#xff0c;提供下地址&#xf…

阿里巴巴面试题- - -多线程并发篇(三十八)

前言:七月末八月初的时候,秋招正式打响,公司会放出大量的全职和实习岗位。为了帮助秋招的小伙伴们,学长这里整理了一系列的秋招面试题给大家,所以小伙伴们不用太过焦虑,相信你们一定能超常发挥,收到心仪公司的Offer~~ 内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elast…

商汤AI象棋机器人到底谁在买?北大象棋大师已签收

金磊 衡宇 发自 凹非寺量子位 | 公众号 QbitAI郭晶晶家的象棋家教——没错&#xff0c;就是商汤AI象棋机器人“元萝卜”&#xff08;SenseRobot&#xff09;&#xff0c;近日正式现货发售。从近2个月前开始预售的那刻起&#xff0c;各种讨论就萦绕在它周围&#xff1a;AI象棋机…

非零基础自学Java (老师:韩顺平) 第21章 网络编程 21.3 Socket 21.4 TCP网络通信编程

非零基础自学Java (老师&#xff1a;韩顺平) ✈【【零基础 快速学Java】韩顺平 零基础30天学会Java】 第21章 网络编程 文章目录非零基础自学Java (老师&#xff1a;韩顺平)第21章 网络编程21.3 Socket21.3.1 基本介绍21.4 TCP网络通信编程21.4.1 基本介绍21.4.2 应用案例1 -…

网络法规——知识产权及侵权鉴别学习笔记

一、知识产权概念 《中华人民共和国民法通则》规定&#xff0c;知识产权是指民事权利主体&#xff08;公民、法人&#xff09;基于创造性的智力成果。 1、知识产权分类 知识产权可分为工业产权和著作权。 工业产权 工业产权包括专利、实用新型、工业品外观设计、商标、服务…

随机生成植物生长及舞动算法

几年前写过一套随机树木的生成算法&#xff0c;其中使用了分形和放样建模。那时候还不知道有speedtree这款软件&#xff0c;写的比较粗糙&#xff0c;最近看了speedtree的演示把原算法改进了一下&#xff0c;算是一个speedtree的简化版本。 重构主要是把原先使用递归函…

使用Consul做注册中心简示

基地版同一个地点 不同的风景

JAMA子刊:孕妈妈每天喝半杯咖啡,可能让胎儿发育迟缓

爱心提示本文不仅适用于喜欢咖啡的孕妇&#xff0c;也适用于喜欢茶&#xff08;包括奶茶&#xff09;、巧克力、能量饮料、可乐和其他含有咖啡因的食物或饮料的孕妇。核心要点题目及新发现的相关背景&#xff1b;已知各权威机构对孕期咖啡因摄入量的建议&#xff1b;业主的个人…

Java数据结构之二叉树的基本操作

二叉树的基本操作1 二叉树的基本概念2 二叉树的遍历3 代码实现二叉树的遍历4 代码实现前序、中序、后序查找5 代码实现二叉树指定节点的删除1 二叉树的基本概念 &#xff08;1&#xff09;树有很多种&#xff0c;每个节点最多只能有两个子节点的树就是二叉树。 &#xff08;2&…

[CISCN 2019 初赛]Love Math

<?php error_reporting(0); //听说你很喜欢数学&#xff0c;不知道你是否爱它胜过爱flag if(!isset($_GET[c])){show_source(__FILE__); }else{//例子 c20-1$content $_GET[c];if (strlen($content) > 80) {die("太长了不会算");}$blacklist [ , \t, \r, \n…

【C++11新特性】类的新功能,可变模板参数,包装器

文章目录一、类的新功能1.default2.delete二、可变参数模板1.参数包2.参数包的插入与解析(1)参数包的个数(2)添加参数解析(3)逗号表达式展开(4)emplace_back三、包装器1.function(封装)2.bind(绑定)一、类的新功能 1.default 在继承与多态中&#xff0c;我们介绍了final与ove…

Feign的简单介绍及配置参数

contextId用于区分实例,类似beanName

mysql存储过程的写法

示例表 area_code_2022 &#xff1a; DROP TABLE IF EXISTS area_code_2022; CREATE TABLE area_code_2022 ( code bigint(12) unsigned NOT NULL COMMENT 区划代码, name varchar(128) NOT NULL DEFAULT COMMENT 名称, level tinyint(1) NOT NULL COMMENT 级别1-5,省市…

python识别选中文本

目标&#xff1a;识别鼠标选中区域的文本 be like : 这是我在模拟键鼠操作时遇到的情况&#xff0c;我需要根据某个位置返回的值进行判断&#xff0c;但是只是依赖pyautogui是做不到的。 方法一 经过上网冲浪寻找答案&#xff0c;被告知了此方法&#xff0c;经测试可行 impor…

Django项目想要在 Python Console里面进行操作 报错找不到对应模块

Django项目想要在 Python Console里面进行操作 报错找不到对应模块 问题描述 ModuleNotFoundError: No module named django ’ 问题原因 在进行对 Python console操作 进行管理查询要导入对应的模块&#xff0c;但是和项目中的models.py文件中的 导包引入 冲突了 导致在Py…