算法 字符串匹配 索引查找表(完整源码)

初级代码游戏的专栏介绍与文章目录-CSDN博客

匹配字符串、匹配字符串开头

典型用途:电话号码的号码头匹配或号码匹配

        当初写这个就是原来做号码头匹配用的是逐个比较,对每个号码要去跟每个号码头匹配一次,本来号码头也没几个嘛,感觉不出什么,谁知道某一天号码头变成了几万个……这一下子程序就比鼻涕还慢了,赶紧连夜写新算法啊。

目录

一、介绍

二、空间换时间(复杂度换时间)

三、完整代码

四、关键点解释

4.1 核心结构

4.2 算法


一、介绍

        一般我们写完整匹配,用二分查找也就差不多了,再快也不会比二分查找快多少。但是遇上号码头匹配就傻眼了,没法二分啊,大部分写业务的程序员只能遍历搜索了,这个性能啊,很难接受了。

        所以呢,得用逐字节匹配的查找表,比较次数最多是查找表的最长字符串的字节数,这个性能,不仅解决了号码头匹配的问题,用在完整匹配上也不比二分差。

        当然了,这是空间换时间的算法,所需内存是相当多的。

        本代码支持完整匹配、最短匹配和最长匹配,每个匹配项可以带一个数据(比如做号码替换的时候数据就是替换号码)。

二、空间换时间(复杂度换时间)

        空间换时间是常见的程序优化方法,把数据展开,不用每次都重新计算。

        典型如大小写转换,一般算法是写几个if判断,毕竟只有字母需要处理,代码只有两行,不额外占用空间,但最快的方法是用一个256长的数组,直接预设结果,只要把字符值作为下标去访问就可以了。

        把数据的关联关系直接做成指针替代搜索也是常见的空间换时间的方法,当然同时增加的还有程序的复杂度,不过程序员不就是靠这个吃饭的吗。

三、完整代码

代码如下:

//ByteMatch.h
//索引查找表

#pragma once

#include <iostream>
#include <string>
#include <vector>

using namespace std;

#define LOG cout<<"["<<__LINE__<<"] "

class CByteMatch
{
private:
	bool m_isUsed;

	bool m_isIncludeFinish;
	string m_exdata;//如果m_isIncludeFinish为true则这里放对应的数据

	int m_base;//子项位置的偏移
	vector<CByteMatch > m_sub;

	//matchlen  匹配的字节数
	//matchlenwithdata 匹配的完整串的长度,与datastr对应
	//datastr 匹配的串对应的数据
	bool _Match(char const* str, bool isFull, long& matchlen, long& matchlenwithdata, string const*& datastr)const
	{
		int c = (unsigned char)str[0];
		if (this->m_isIncludeFinish)
		{
			matchlenwithdata = matchlen;
			datastr = &m_exdata;
		}
		if (0 == c)
		{
			return this->m_isIncludeFinish;
		}
		else
		{
			if (!isFull && this->m_isIncludeFinish)
			{
				return true;
			}
			if (c - m_base >= 0 && c - m_base < static_cast<long>(m_sub.size()) && m_sub[c - m_base].m_isUsed)
			{
				++matchlen;
				return m_sub[c - m_base]._Match(str + 1, isFull, matchlen, matchlenwithdata, datastr);
			}
		}
		return false;
	}
public:
	CByteMatch() { clear(); }
	~CByteMatch() { clear(); }
	void clear()
	{
		m_sub.clear();
		m_isUsed = false;
		m_isIncludeFinish = false;
		m_exdata = "";
		m_base = 0;
	}
    //构造匹配表,逐条加入
	void Insert(char const* str, char const* data = NULL)
	{
		long c = (unsigned char)str[0];
		m_isUsed = true;
		if (0 == c)
		{
			m_isIncludeFinish = true;
			if (NULL != data)m_exdata = data;
		}
		else
		{
			if (m_sub.size() == 0)
			{
				m_sub.reserve(10);
				m_sub.resize(1);
				m_base = (int)c;
			}
			else if (c < m_base)
			{
				CByteMatch tmp;
				m_sub.insert(m_sub.begin(), m_base - c, tmp);
				m_base = (int)c;
			}
			else if (c >= m_base + static_cast<long>(m_sub.size()))
			{
				CByteMatch tmp;
				m_sub.insert(m_sub.end(), c - m_base - m_sub.size() + 1, tmp);
			}
			else
			{
			}
			m_sub[c - m_base].Insert(str + 1, data);
		}
	}
    //打印匹配表,供调试
	void Print(int level = 0)const
	{
		string head;
		head.assign(level, ' ');
		if (!m_isUsed)LOG << head << "no used" << endl;
		if (m_isIncludeFinish)LOG << head << " : " << m_exdata << endl;
		for (int i = 0; i < static_cast<long>(m_sub.size()); ++i)
		{
			if (m_sub[i].m_isUsed)
			{
				LOG << head << (char)(unsigned char)(m_base + i) << endl;
				m_sub[i].Print(level + 1);
			}
		}
	}
	//完整匹配
	bool FullMatch(char const* str, string* pdatastr = NULL)const
	{
		long matchlen = 0;
		long matchlenwithdata = 0;
		string const* pdata = NULL;
		bool ret = _Match(str, true, matchlen, matchlenwithdata, pdata);
		if (NULL != pdatastr && NULL != pdata)
		{
			(*pdatastr) = (*pdata);
		}
		return ret;
	}
	//第一个匹配(最短)
	bool FirstMatch(char const* str, string* pmatchstr = NULL, string* pdatastr = NULL)const
	{
		long matchlen = 0;
		long matchlenwithdata = 0;
		string const* pdata = NULL;
		bool ret = _Match(str, false, matchlen, matchlenwithdata, pdata);
		if (ret)
		{
			if (NULL != pmatchstr)
			{
				(*pmatchstr).assign(str, matchlenwithdata);
			}
			if (NULL != pdatastr && NULL != pdata)
			{
				(*pdatastr) = (*pdata);
			}
		}
		return ret;
	}
	//最大匹配(最长)
	bool MaxMatch(char const* str, string* pmatchstr = NULL, string* pdatastr = NULL)const
	{
		long matchlen = 0;
		long matchlenwithdata = 0;
		string const* pdata = NULL;
		_Match(str, true, matchlen, matchlenwithdata, pdata);
		if (NULL != pmatchstr)
		{
			(*pmatchstr).assign(str, matchlenwithdata);
		}
		if (NULL != pdatastr && NULL != pdata)
		{
			(*pdatastr) = (*pdata);
		}
		return NULL != pdata;
	}
};

        此代码可在vs2022控制台项目下运行,main函数和测试代码(其实测试代码我是和源代码放在一起的,方便使用,这里为了方便说明挪到main函数前面)如下:

#include "ByteMatch.h"

class CByteMatchTest
{
private:
	CByteMatch data;
public:
    //构造匹配表
	void Init()
	{
		data.Insert("010", "0108");
		data.Insert("11", "ab");
		data.Insert("111", "abc");
	}
    //测试,old意思是“旧号码”,因为最初这个代码是用来做号码替换的,wantMaxMatch意思是预期的最大匹配结果,wantFullMatch和wantFirstMatch分别是预期的完整匹配和最短匹配结果,如果三个结果都符合预期,说明测试通过
	bool TestOne(char const* old, bool wantMaxMatch, bool wantFullMatch, bool wantFirstMatch)
	{
		string matchstr, datastr;
		bool ret;

		if (ret = data.MaxMatch(old, &matchstr, &datastr))
		{
			LOG << "MaxMatch匹配到 [" << old << "] [" << matchstr << "] [" << datastr << "]" << endl;
		}
		else
		{
			LOG << "MaxMatch未匹配 [" << old << "]" << endl;
		}
		if (ret != wantMaxMatch)
		{
			LOG << "预期 " << (wantMaxMatch ? "匹配" : "无匹配") << " 测试失败" << endl;
			return false;
		}

		if (ret = data.FullMatch(old, &datastr))
		{
			LOG << "FullMatch匹配到 [" << old << "] [" << datastr << "]" << endl;
		}
		else
		{
			LOG << "FullMatch未匹配 [" << old << "]" << endl;
		}
		if (ret != wantFullMatch)
		{
			LOG << "预期 " << (wantFullMatch ? "匹配" : "无匹配") << " 测试失败" << endl;
			return false;
		}

		if (ret = data.FirstMatch(old, &matchstr, &datastr))
		{
			LOG << "FirstMatch匹配到 [" << old << "] [" << matchstr << "] [" << datastr << "]" << endl;
		}
		else
		{
			LOG << "FirstMatch未匹配 [" << old << "]" << endl;
		}
		if (ret != wantFirstMatch)
		{
			LOG << "预期 " << (wantFirstMatch ? "匹配" : "无匹配") << " 测试失败" << endl;
			return false;
		}

		return true;
	}

    //测试全部通过返回true,否则返回false
	bool test()
	{
		Init();
		string matchstr;
		string datastr;

		if (!TestOne("010123456", true, false, true))return false;
		if (!TestOne("0108", true, false, true))return false;
		if (!TestOne("011123456", false, false, false))return false;
		if (!TestOne("010", true, true, true))return false;
		if (!TestOne("0101", true, false, true))return false;
		if (!TestOne("01", false, false, false))return false;
		if (!TestOne("1111", true, false, true))return false;
		return true;
	}
};

int main()
{
    CByteMatchTest zu;
    if(!zu.test())return __LINE__;

    cout << "执行成功" << endl;
    return 0;
}

        多说几句:国内大部分公司不讲究测试,更不用提模块测试了,作为程序员的自我修养,我们应该自己写测试代码,并且和源代码一起提供,未必要用什么测试框架,只要写个测试函数放进去就可以了。如果我不是随手运行了一下,我也不敢说这代码一定还能运行、还是正确的。

四、关键点解释

4.1 核心结构

        数据结构组织为一棵树,核心就是树节点:

class CByteMatch
{
private:
	bool m_isUsed;

	bool m_isIncludeFinish;
	string m_exdata;//如果m_isIncludeFinish为true则这里放对应的数据

	int m_base;//子项位置的偏移
	vector<CByteMatch > m_sub;
。。。。。。

        CByteMatch是树节点也是整个树。 

        m_sub是所有子节点,也就是下一级。由于大部分情形并不会用到0-255全部,所以用了m_base来指出第一个,这样,m_sub所需的空间只是第一个用到的数据到最后一个用到的数据,浪费的空间就比较少了。

        m_isIncludeFinish指示存在一个在这里结束的值。比如两个数据,“12”和“123”,那么数据结构如下:

1(不包括结束)
----2(包括结束)
--------3(包括结束)

        如果匹配上之后存在关联数据(一般是替换),可以存放在 m_exdata。

4.2 算法

        算法很容易理解,逐个字节匹配,比较次数和字节数有关,与数据量无关。

class CByteMatch
{
。。。。。。
    //构造匹配表,逐条加入
	void Insert(char const* str, char const* data = NULL);
    //打印匹配表,供调试
	void Print(int level = 0)const;
	//完整匹配
	bool FullMatch(char const* str, string* pdatastr = NULL)const;
	//第一个匹配(最短)
	bool FirstMatch(char const* str, string* pmatchstr = NULL, string* pdatastr = NULL)const;
	//最大匹配(最长)
	bool MaxMatch(char const* str, string* pmatchstr = NULL, string* pdatastr = NULL)const;
};

        Insert()用于构造数据

        Print()用于打印数据

        FullMatch()完整匹配,str结束同时遇到m_isIncludeFinish才算匹配,比如数据有“12”和“123”,那么“12345”返回匹配失败

        FirstMatch()遇到第一个m_isIncludeFinish就结束,比如数据有“12”和“123”,那么“12345”返回匹配结果是“12”

        MaxMatch()最长匹配,比如数据有“12”和“123”,那么“12345”返回匹配结果是“123”

        三种匹配方式都会返回匹配到的值对应的附加数据,后两种还会返回匹配到的值。

(这里是文档结束)

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
程序接收用户键入的一个关键字以及一个句子。如果句子中不包含关键字则显示’no match’;如果句子中包含关键字则显示‘match’,且把该字在句子中的位置用十六进制数显示出来,要求程序的执行过程如下: enter keyword :abc enter sentence :we are studying abc match at location :11H of the sentence enter sentence: xyz ,ok? no match enter sentence :^c 四. 方法说明: 程序可由三部分组成: (1 ) 输入关键字和一个句子,分别存入相应的缓冲区中,可用功能调用0AH。 (2) 在句子中查找关键字。 1. 关键字和一个句子中相应字段的比较可使用串比较指令,为此必须定义附加段,但附加段和数据段可以定义为同一段,以便于串指令的使用,这样,相应的寄存器内容也有了确定的含义,如下: SI 寄存器为关键字的指针 DI 寄存器为句子中正相比较的字段的指针 CX寄存器存放关键字的字母个数(长度) 2. 整个句子和关键字的比较过程可以用一个循环结构来完成。循环次数为: (句子长度--关键字长度)+1在计算循环次数时,如遇到句子长度小于关键字长度的情况则应转向显示“no match”,循环中还需要用到BX寄存器,它用来保存句子中当前正在比较字段的首地址。 (3) 输出信息: 用功能调用09h分“找到”或“找不到”两种情况分别显示不同的信息。在“找到”时,还要求显示出匹配字符串在句子中的位置,在“找到”时BX寄存器的内容为匹配字符串的首地址,将此值减到句子的首地址,再将差值加1 即是所要的匹配字符串在句子中的位置,可将位置转换为十六进制数从屏幕上显示出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值