C++编程新手容易犯的 10 种编程错误

来源:https://blog.csdn.net/chenlycly

 

公司每年都会有一定的人员流动,相应地也会招一些应届生补充进来,指导应届生已经成为老员工的必修课了。平日里会我们会经常帮新人排查代码中的问题,在此过程中发现了 C++ 新手容易犯的一些编程错误,在此简单的总结一下,给新人们提供一个参考。

1、有些关键字在 cpp 文件中多写了

对于 C++ 类,一些关键字只要写在 .h 中就好,cpp 中就不用再加上了,比如 virtual、static 等关键字,如果在 cpp 中多写,编译器会报错。比如如下的虚接口与静态成员变量的定义,只要在头文件中声明就可以了。

class shape
{
    virtual Draw();
    //...
    static int nLevel;
}

2、函数参数的默认值写到函数实现中了

带有参数默认值的函数,默认值是加在函数声明处的,函数实现处的参数是不需要带上的。为了方便查看代码,在函数实现处的参数中,将默认值注释起来。正确的做法是,头文件中有默认值:

BOOL CreateConf( const CString& strConfName, const BOOL bAudio = FALSE );
在函数实现处的参数中不用添加默认值:
BOOL CreateConf( const CString& strConfName, const BOOL bAudio/* = FALSE*/ );
{
    // ......
}

3、在编写类的时候,在类的结尾处忘记添加 “;” 分号了

在类的结尾处忘记添加分号,编译会报错,新人们有可能找了半天也没找出引起编译错误的原因。其实很简单,在类的结尾处忘记添加分号了。

class Shape
{
    // ...
};

4、只添加了函数声明,没有函数实现

在添加类的函数时,只在类的头文件中添加了函数声明,但在 cpp 中却没有添加函数的实现。如果其他地方调用到该函数,在编译链接的时候会报unresolved external symbol错误。因为没有实现,所有没有供链接使用的 obj 文件。

5、cpp 文件忘记添加到工程中,导致没有生成供链接使用的 obj 文件

在添加 C++ 类时,我们一般会添加 .h 头文件和一个 .cpp 源文件。结果忘记把 .cpp 文件添加到工程中了,即没有参与编译,没有生成供链接使用的 obj 文件。如果有代码调用到该 C++ 类的接口,则在编译链接的时候会报unresolved external symbol错误,即链接不到该 C++ 类对应的接口。

6、函数中返回了一个局部变量的地址或者引用

在函数中返回了一个局部变量的地址或者引用,而这个局部变量在函数结束时其生命周期就结束了,内存就被释放了。当外部访问到该变量的内存,会触发内存访问违例的异常,因为该变量的内存已经释放了。比如如下的错误代码:

char* GetResult()
{
    char chResult[100] = { 0 };

    // ......

    return chResult;
}

7、忘记将父类中的接口声明 virtual 函数,导致多态没有生效

代码中本来要借助于 C++ 多态的虚函数调用,调用子类实现的接口,结果忘记在父类中将对应的接口声明为 virtual,导致没有调用到子类实现的函数。一定要记住,要实现多态下的函数调用,父类的相关接口必须声明为 virtual。

class Shape()
{
    // ...

    virtual void Draw();

    // ...
}

8、该使用双指针的地方,却使用了单指针

有时我们需要调用一个接口去获取某些数据,接口中将数据拷贝到传入的参数对应的内存中,此时设计参数时会传入指针或引用。我们在调用GetData 之前定义了结构体指针p,并 new 出了对应的结构体对象内存,应该在定义 GetData 接口时应该使用双指针(指针的指针)的,结果错写成了单指针。

有问题的代码如下:

struct CodecInfo     // 编码信息
{
    int nFrameRate;

    // ...
}


CodecInfo* pInfo = new CodecInfo;

GetAudioCodecPtr()->GetCodecInfo(pInfo);   // 调用AudioCodec::GetCodecInfo获取编码信息


AudioCodec::GetCodecInfo( CodecInfo* pInfo)  // 此处的参数不应该使用单指针
{
    memcpy(pInfo, m_codecInfo, sizeof(CodecInfo));
}

上面中的AudioCodec::GetCodecInfo接口的参数不应该为单指针,应该用双指针,修改后的代码应该如下:

AudioCodec::GetCodecInfo( CodecInfo** pInfo)  // 此处的参数类型使用双指针
{
    memcpy(*pInfo, m_codecInfo, sizeof(CodecInfo));
}

9、发布 exe 程序时,忘记将 exe 依赖的 C 运行时库和 MFC 库带上

比如新人用 VS-MFC 库编写一个测试用的工具软件,结果在发布 release 版本程序时,没有将程序依赖的 C 运行时库带上,导致该工具软件在某些电脑中启动报错,提示找不到 C 运行时库:

因为程序中依赖了动态版本的运行时库和 MFC 库,在发布程序时要将这些库带上。有些系统中没有这些库,程序启动时就会报找不到库,就会启动失败。

10、应该使用深拷贝,却使用了浅拷贝

本来应该要进行深拷贝的,却使用了浅拷贝(直接赋值),导致另个不同生命周期的 C++ 对象指向了同一块内存,一个对象将内存释放后,另一个对象再用到这块内存,就造成了内存访问违例,产生异常。

有个经典的 C++ 笔试题,让我们实现 String 类的相关函数,其主要目的就是用来考察对深拷贝与浅拷贝的理解的。题目中给出 String类的声明:

class String{
public:
    String();
    String(const String & str);
    String(const char* str);
    String& operator=(String str);
    char* c_str() const;
    ~String();
    int size() const;
private:
    char* data;
};

让写出上述几个函数的内部实现。这些函数的实现代码如下:

//普通构造函数  
String::String(const char *str)
{
  if (str == NULL)
  {
    m_data = new char[1];// 得分点:对空字符串自动申请存放结束标志''的,加分点:对m_data加NULL判断  
    *m_data = '';
  }
  else
  {
    int length = strlen(str);
    m_data = new char[length + 1];// 若能加 NULL 判断则更好
    strcpy(m_data, str);
  }
}
 
 
// String的析构函数  
String::~String(void)
{
  delete[] m_data; // 或delete m_data;  
}
 
 
//拷贝构造函数  
String::String(const String &other)// 得分点:输入参数为const型  
{     
  int length = strlen(other.m_data);
  m_data = new char[length + 1];// 若能加 NULL 判断则更好  
  strcpy(m_data, other.m_data);
}
 
 
//赋值函数  
String & String::operator = (const String &other) // 得分点:输入参数为const型  
{
  if (this == &other)//得分点:检查自赋值  
    return *this; 
  if (m_data)
      delete[] m_data;//得分点:释放原有的内存资源  
  int length = strlen(other.m_data);
  m_data = new char[length + 1];//加分点:对m_data加NULL判断  
  strcpy(m_data, other.m_data);
  return *this;//得分点:返回本对象的引用    
}

简单分享快乐学习,如有错误请多包涵!

PS:欢迎分享,点赞,在看。

 

转自:https://mp.weixin.qq.com/s/tUT9sDR-d1aTzWof3TCjNg

奥密克戎真正的威力藏在超额死亡里

我们聊聊国外数字,最近开始陆续的有2022年的超额死亡数据出来了,我们来看看这波奥密克戎真正的威力。

我找到的这个统计是按照nature的这篇文章的方法来计算的:

https://www.nature.com/articles/s41597-021-01019-1

所以下面默认这篇没问题,如果你对数字有疑问,请去怼这篇文章,最好建议nature撤稿,不要来再问我信源哪来的之类的车轱辘话了,朝这个方向杠的我帮你屏蔽我,省的让您看了心累。

现在开始

美国2020年和2021年两张图打底,两年时间多死了120万人

奥密克戎真正的威力藏在超额死亡里

奥密克戎真正的威力藏在超额死亡里

简单解释一下,,这是机翻的,不要问翻译问题。

“死亡,参考”这个就是选取的过去正常年份的正常值,但是这个每年的是变化的,因上面提到的那篇文章计算方式已经考虑了前面几年的趋势,比如2021年的死亡参考就比2020年的要多,因为根据美国2015年到2019年的趋势,死亡人口是逐年递增的,所以越往后,他的参考值也会递增。所以这个方向杠的,请去杠nature那篇文章。

“死亡,目标”指的就是实际死亡的人数,

死亡人数这一列就是超额死亡的人数,也就是今天比往年同期多死了多少,

注意:是死了多少!

大概就这几个需要解释到了,现在开始我们看看很多国家是啥情况。

世界抗疫典范,美利坚的前六周数据

奥密克戎真正的威力藏在超额死亡里

仅仅六周,超额死亡了111500人

我们之前统计过一个,那就是美国前三个月因为新冠死亡了154000人

这里有一个细节

美国统计的死亡最高峰是2月8号左右,也就是刚刚六周左右

奥密克戎真正的威力藏在超额死亡里

美国统计的死亡最高峰是2月8号左右,也就是刚刚六周左右

但是按照超额死亡图上看第三周是个死亡高峰

到第六周都开始走低了

这是怎么回事呢?

具体原因其实谁也不知道,但是我们可以通过一些事情来推测

正常情况,死亡高峰应该滞后感染高峰两周左右

那么美国感染高峰是什么时候呢?

相信大家还记得日增百万这件事儿吧,基本就是本轮疫情美国的感染最高峰,随便搜了一下就知道是3号出现的

奥密克戎真正的威力藏在超额死亡里

如果从3号开始往后两周,实际上刚好是三周左右出现死亡高峰,也就是超额死亡的节奏是对的。

而美国统计的新冠死亡高峰明显是太滞后了,差不多滞后了一个月。

所以我们不得不做个猜测,

这几周的超额死亡可能是另一个问题,那就是大规模的感染人数导致医疗资源过载,从而导致大量的人根本没来得及去医院就死了,于是并没有统计为新冠死亡。

总之,不管怎么比,超额死亡这个东西确实的反映了,因为疫情六周时间多死了11万人。

最后别杠超额死亡里有多少是死于新冠了

因为这玩意就是为了衡量大流行的影响。

奥密克戎真正的威力藏在超额死亡里

用我这种喜欢简单粗暴说问题的人的口吻来说,这就是因为新冠死的,管你是什么原因,没有新冠,没有奥密克戎,他们本来就不该死。

好了,简单分析完了,我们来看看世界主要国家在这场“轻微到不如流感的奥密克戎大流行”里的表现吧。

确实有做得比较好的,比如下面几个:

没有整个大英的,只有英格兰加威尔斯的

奥密克戎真正的威力藏在超额死亡里

德国5%

奥密克戎真正的威力藏在超额死亡里

瑞士超额5%

奥密克戎真正的威力藏在超额死亡里

比利时超额5%左右

奥密克戎真正的威力藏在超额死亡里

上面几个地区都非常不错,超额死亡率控制在5%上下,

 

下面是法国,15%超额死亡

奥密克戎真正的威力藏在超额死亡里

意大利12.75%

奥密克戎真正的威力藏在超额死亡里

西班牙14.39%

奥密克戎真正的威力藏在超额死亡里

经常被称为抗疫典范的丹麦9.6%

奥密克戎真正的威力藏在超额死亡里

也经常被称为抗议典范的芬兰 多死了15.4%

奥密克戎真正的威力藏在超额死亡里

大韩民国 15.23%

奥密克戎真正的威力藏在超额死亡里

韩国仅仅1月份就超额死亡15%,那么3月份感染高峰才到来的大韩民国到底未来会是个啥样子,其实现在谁都不知道,报出来的死亡人数真的对得上么?

最近被经常拿出来举例子证明是大号流感的新西兰是什么情况呢?

新西兰17%超额死亡!!!!!!

要知道,新西兰的高峰也是3月份才到来!这才统计到第10周

报出来新冠死亡那么低的新西兰,

到底因为什么神奇的原因让超额死亡到达了17%?

 

奥密克戎真正的威力藏在超额死亡里

这就完事了么?

永远不要低估奥密克戎和医疗挤兑的影响

我们来看看东欧国家,两个比较好的,立陶宛和拉脱维亚,10%超额死亡

奥密克戎真正的威力藏在超额死亡里

奥密克戎真正的威力藏在超额死亡里

第二梯队,爱沙尼亚15%,斯洛文尼亚17%

奥密克戎真正的威力藏在超额死亡里

奥密克戎真正的威力藏在超额死亡里

第三梯队,波兰超额22%,克罗地亚25%

奥密克戎真正的威力藏在超额死亡里

奥密克戎真正的威力藏在超额死亡里

欧洲的顶流,保加利亚 37.39%

奥密克戎真正的威力藏在超额死亡里

大家还记得美利坚多少么?

再帮大家回忆一下

31%

奥密克戎真正的威力藏在超额死亡里

如果把USA放在欧洲那就是个顶流的存在。

不过虽然他不在欧洲,但是有一个国家跟他源远流长,

那就是

以色列!

又一个被吹成抗议典范,同时接种率非常高的以色列

我们之前的视频里说了

接种率高并不是对抗疫情的最关键因素!

 

很多人不服,在这吹mRNA疫苗和辉瑞神药,仿佛mRNA疫苗是解救世界的关键

那么来看看以色列的情况吧!

奥密克戎真正的威力藏在超额死亡里

35%超额死亡!!!!

这就是拿来吹mRNA疫苗牛逼无比,同时接种率也奇高无比的以色列!

很多人提到美国死人多,就说美国接种率不高!

那么以色列呢!?

都在推进第四针了!听说第五针也在准备中了

牛逼无比的mRNA疫苗加接种率高真的能避免死亡么?

那么这35%的超额死亡是怎么刷出来的?

当然,美利坚这么拉胯,幸好还有别的国家给他垫了个底!

一图镇楼!

超额死亡51%的智利,当然,没有人关心智利了!

 

奥密克戎真正的威力藏在超额死亡里

别问为啥没有新加坡?

因为这文章配的网站没有新加坡的数据,我也没法子给你变出来。

但是拿来天天吹新冠死亡率超低的新西兰和以色列的例子不能够说明问题么?

请问他们的超额死亡是怎么刷出来的?

新冠已经不如流感了?

有了mRNA疫苗高接种率就可以躺平了?

有了辉瑞神药就能可以不怕新冠了?

问过以色列和新西兰没有统计在新冠死亡里的,

但多死了的那些人了吗?

转自:https://mp.weixin.qq.com/s/FrOf41mJnub5wWow7M5Yrg

万科2021年报点评:活下去的代价

声明:我从不推荐股票,所有文章都是只针对企业基本面。研究企业是长期持续的过程,谈不上择时,所谈个股的股价波动与本文无关。市场瞬息万变,所有的研究和分析都是有时效性的,请勿刻舟求剑。

一、净利润大幅下滑的原因

万科的年报出台,引起一片哗然。净利润的增速比之前所有悲观的数据还要悲观,原来有券商预测到归母净利润会跌到250亿,结果实际数据竟然是225亿,基本上超出了市场上所有人的预料。

房企的营收大都是两到三年前销售额的转化,2018年万科公开喊出了“活下去”,大幅放慢了发展速度。2019年万科的权益金额比2018年是下降的,今年能做到8%的营收增速已经不容易,相比之下招商蛇口的营收增速是23%,保利发展是17%,可以做到以量补价,净利润下降的幅度相对就小很多,而万科则无货可补。

当初喊“活下去”,市场认为万科是审慎拿地,控制数量提升质量,但从如今的结算毛利率来看,万科并没体现当初“活下去”的收获。房地产开发及相关资产经营业务的毛利率同比下滑了7.63%,为21.74%,比同行业略好,但不足以弥补营业收入下降的幅度。

量缩率减,是净利润下跌了45%的主因。从结果来看,万科的“活下去”更多是体现在净负债率上,2021年的净负债率低到了29.7%,大大少于同行,用利润换来了安全,一得一失吧。

净利润大幅下滑的另外两个因素,一个是投资收益下降69亿元,另一个则是公司对部分项目、个别股权投资等进行减值,合计减少归属上市公司股东的净利润约26亿元。这两项是当前市场不景气状况下的客观反映,无可厚非。

从净利润下滑的结构上来看,报表整体净利润是从592亿下滑到380亿,下跌了35.8%,但少数股东损益只下滑了12.58%,而归母净利润的下滑幅度却高达45.75%。有人会表示怀疑,但因为投资收益和资产减值两项的下降金额较高,这个差额也是可以解释的,从万科过往少数股东损益和归母净利润的变化对比,以及参照保利、招商蛇口的公司的可比数据变化来看,性质也相差不多。

二、回购缺少力度

年报中关于回购的说法,引起了更多争议。公司拟出资20亿-25亿,对A股股票进行回购,回购价格不超过人民币18.27元/股,回购的股份将全部用于出售。公告前万科A的收盘价是18.98元,比预计在4月底可以进行的回购高出了0.71元。这种以后要用于出售的股票,给出这个目标价位,是托市还是砸盘呢?

公告是之前拟好的,18.98元的价格应该是在收盘前确定的,但3月29日的收盘价就有17.48元了,0.8元只是不到5%的空间,一个上市公司,应该不会考虑不到吧?万科目前的净资产是20.3元,现在这个回购说法,更像是为了安慰一直要求回购的小股东:确实是提出回购了啊!

三、现金大幅下降

年报里最出乎我意料的数字,是经营活动产生的现金流量净额同比下降了92.27%,只有41.13亿 。虽然前三个季度的数据也不好看,但房企四季度数据才是最重要的,去年4季度就大增了近200亿,2019年前三个季度只有17.34亿,而年报时就增长到456.87亿。同时,期末现金及现金等价物余额为1407.08亿,这是2016年以来最少的一次。

“活下去”是为了捡到更便宜的战利品,万科也确实捡到了一些,但与期末现金及现金等价物余额继续保持增长态势的招商蛇口、华润置地相比,还存在着国企背景与央企背景的差距。很多投资者对万科过去几个月拿地的速度并不满意,现在看起来,这不是态度问题,而是钱的问题。

四、分红体现诚意

年报中为数不多的亮点,是分红。“2021 年度拟合计派发现金股息人民币 11,276,621,873.75 元(含税),占公司 2021 年归属于母公司股东的净利润的比例为 50.06%,不送红股,不以公积金转增股本。如以 2021 年末公司总股份数 11,625,383,375 股计算,每 10 股派送人民币 9.70 元(含税)现金股息。”

这对小股东们是个安慰,如果从经营角度来看,现在满地战利品的时候,这112亿如果去并购和拿地,对股东的长期回报会更好。但考虑到万科股价持续低迷,大比例分红也是稳定市值的重要手段,可以理解。

整体来说,这份年报真实地体现了房企的现状,尤其是万科这样国有背景混合所有制企业的现状。从各项数据的呈现来看,还是比较客观的。2020年万科的权益金额相比2019年有10%左右的增长,毛利率也基本不会有更大的下降空间,如果今年后三个季度,房地产市场整体稳定回暖,万科2022年的年报数据应该会恢复增长态势。

就目前的情况来看,关键点还是在能否尽快恢复融资速度,多购置一些当前质量好、毛利润又高的好地,现在拿到一块地,收益会是去年同期的倍数。去年年底万科提过“春天里”,现在春天到了,拭目以待吧。

转自:https://mp.weixin.qq.com/s/scBNgA_ZHiOIMl-9xoq2Fw

PONG – 100行代码写一个弹球游戏

 

今天跟大家讲一讲:如何做游戏

游戏的主题是弹球游戏《PONG》,它是史上第一款街机游戏。因此选它作为我这个游戏开发系列的第一期主题。

游戏引擎用的是 Python 的一个游戏库:pgzero。它是对 pygame 的一个封装,让你不需要写多余的套路代码,只要配置游戏的内容逻辑即可。

我们这个游戏用它来写,一共只需要100行代码。

首先需要安装 python 环境。这一步没搞定的同学,可以参考我们 python 入门教程:python666.cn,上面有详细图文介绍。

然后需要安装 pgzero 库,可以命令行下通过 pip 命令安装:

pip install pgzero

安装完,运行一句

pgzrun.go()

我们的游戏世界之门就已经打开了。

PONG - 100行代码写一个弹球游戏

现在上面还是混沌初开,一片漆黑。

设定一个矩形的左上角坐标和长宽,在游戏的绘制函数 draw 中用指定颜色填充,我们就得到了一个矩形。

pad_1 = Rect((20, 20), (10, 100))
def draw():    screen.clear()    screen.draw.filled_rect(pad_1, 'white')

适当调整一下,就得到了一块游戏中用来挡球的板。

PONG - 100行代码写一个弹球游戏

在游戏的更新函数中增加判断,当键盘上的“上”、“下”按键被按下时,修改挡板的y坐标,就可以在游戏中控制挡板的移动了。

PAD_SPEED = 10
def update(dt):    if keyboard.up:        pad_1.y -= PAD_SPEED    elif keyboard.down:        pad_1.y += PAD_SPEED

这样就已经完成 PONG 游戏中的玩家操控角色:一块可上下移动的挡板。而现在我们用到的代码仅仅10行。

 

有的小伙伴可能注意到了,这里有两个函数,一个叫 draw,它是负责游戏中的画面绘制,另一个叫 update,它负责游戏中的逻辑更新。

 

我们经常听到说游戏运行时速度是每秒30帧、60帧之类,或者叫做 FPS(Frames Per Second)。draw 和 update 就是在游戏的“一帧”画面中所要做的事情。你的计算机或者游戏主机的性能越高,每一帧所花费的计算时间就越少,游戏帧数就可以更高,游戏体验也就更流畅。

创建一个叫做 Ball 的类型,属性值包括位置和速度。然后,在绘图函数中以小球的位置为圆心画一个圆,在更新函数中按照匀速直线运动位移公式,也就是 位移=速度x时间,计算出小球下一帧的位置。如此就实现了一个会运动的小球。

class Ball():    def __init__(self):        self.pos = [300200]        self.speed = [1, 1]            def update(self, dt):        for i in range(2):            self.pos[i] += self.speed[i] * dt
ball = Ball()
def draw():    screen.clear()    screen.draw.filled_rect(pad_1, 'white')    screen.draw.filled_circle(ball.pos, BALL_RADIUS, 'white')  

再设置一下边界条件,让小球到达屏幕边缘时可以改变对应的速度方向,碰到上下边缘就将y速度分量乘以-1,超出左右边缘则位置重新设置回屏幕中心。

class Ball():    ...      
    def update(self, dt):        for i in range(2):            self.pos[i] += self.speed[i]
        if self.pos[1] < 0 or self.pos[1] > HEIGHT:            self.speed[1] *= -1        if self.pos[0] < 0 or self.pos[0] > WIDTH:            self.reset()

有了板,有了球,接下来就是让他们之间产生关联。

在更新函数中做一个碰撞检测:如果板子的矩形与球的圆心产生了交集,就让球反弹回去。

def update(dt):    ...        ball.update(dt)
    if pad_1.collidepoint(ball.pos) and ball.speed[0] < 0:        ball.speed[0] *= -1

到这一步,游戏的核心物理规则就已经定义完毕。

按照同样的方法,在屏幕的右侧创建第二块板,通过另外的按键进行控制。然后,当小球超出左右边界时,分别给对面一方得分。

class Ball():    ...      
    def dead(self, side):        scores[side] += 1        self.reset()

这样,一个最最简单的,双人版弹球游戏就完成了。

当然,如果你找不到另一个人陪你一起玩,也可以让自己的左手跟右手玩。

或者,给一侧板增加一点自动追踪的代码:让板的位置随着球的位置移动。这也算是一个游戏AI了。

def auto_move_pad(dt):    if ball.pos[0] > WIDTH / 2 and ball.speed[0] > 0:        if pad_2.y + pad_2.height * 0.25 > ball.pos[1]:            pad_2.y -= PAD_SPEED * dt            if pad_2.top < 0:                pad_2.top = 0        elif pad_2.y + pad_2.height * 0.75 < ball.pos[1]:            pad_2.y += PAD_SPEED * dt            if pad_2.bottom > HEIGHT:                pad_2.bottom = HEIGHT

至此,一个具备完整核心玩法的弹球游戏 PONG 已经完成了。加上空格也不到100行代码。特别适合编程新手刚刚接触游戏开发的小伙伴进行练习。

不过,我还给游戏增加了一点点细节,感兴趣的小伙伴可点击文章开头的视频进行观看。喜欢的话欢迎点赞和转发!

PONG - 100行代码写一个弹球游戏

之后我还会来尝试更多的游戏类型,更多的玩法。争取完成最初立下的FLAG:实现100个游戏如果你想看某类游戏或者某个游戏的实现,或者对某个实现细节有疑问,也可以留言中告诉我,我会优先考虑。

代码已经开源,获取请在公众号“Crossin的编程教室”后台回复关键词:pong



转自:https://mp.weixin.qq.com/s/64iupQIhUf0n45p0qlJ9xw

比正则快 M 倍以上!Python 替换字符串的新姿势

比正则快 M 倍以上!Python 替换字符串的新姿势

FlashText 算法是由 Vikash Singh 于2017年发表的大规模关键词替换算法,这个算法的时间复杂度仅由文本长度(N)决定,算法时间复杂度为O(N)。

 

而对于正则表达式的替换,算法时间复杂度还需要考虑被替换的关键词数量(M),因此时间复杂度为O(MxN)。

简而言之,基于FlashText算法的字符串替换比正则表达式替换快M倍以上,这个M是需要替换的关键词数量,关键词越多,FlashText算法的优势就越明显

下面就给大家介绍如何在 Python 中基于 flashtext 模块使用 FlashText 算法进行字符串查找和替换,如果觉得对你的项目团队很有帮助,请记得转发一下哦。

1.准备

pip install flashtext
2.基本使用

提取关键词

一个最基本的提取关键词的例子如下:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
keyword_processor = KeywordProcessor()
# 2. 添加关键词
keyword_processor.add_keyword('Big Apple', 'New York')
keyword_processor.add_keyword('Bay Area')
# 3. 处理目标句子并提取相应关键词
keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.')
# 4. 结果
print(keywords_found)
# ['New York', 'Bay Area']

其中 add_keyword 的第一个参数代表需要被查找的关键词,第二个参数是给这个关键词一个别名,如果找到了则以别名显示。

替换关键词

如果你想要替换关键词,只需要调用处理器的 replace_keywords 函数:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
keyword_processor = KeywordProcessor()
# 2. 添加关键词
keyword_processor.add_keyword('New Delhi', 'NCR region')
# 3. 替换关键词
new_sentence = keyword_processor.replace_keywords('I love Big Apple and new delhi.')
# 4. 结果
print(new_sentence)
# 'I love New York and NCR region.'

关键词大小写敏感

如果你需要精确提取,识别大小写字母,那么你可以在处理器初始化的时候设定 sensitive 参数:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器, 注意设置大小写敏感(case_sensitive)为TRUE
keyword_processor = KeywordProcessor(case_sensitive=True)
# 2. 添加关键词
keyword_processor.add_keyword('Big Apple', 'New York')
keyword_processor.add_keyword('Bay Area')
# 3. 处理目标句子并提取相应关键词
keywords_found = keyword_processor.extract_keywords('I love big Apple and Bay Area.')
# 4. 结果
print(keywords_found)
# ['Bay Area']

标记关键词位置

如果你需要获取关键词在句子中的位置,在 extract_keywords 的时候添加 span_info=True 参数即可:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
keyword_processor = KeywordProcessor()
# 2. 添加关键词
keyword_processor.add_keyword('Big Apple', 'New York')
keyword_processor.add_keyword('Bay Area')
# 3. 处理目标句子并提取相应关键词, 并标记关键词的起始、终止位置
keywords_found = keyword_processor.extract_keywords('I love big Apple and Bay Area.', span_info=True)
# 4. 结果
print(keywords_found)
# [('New York', 7, 16), ('Bay Area', 21, 29)]

获取目前所有的关键词

如果你需要获取当前已经添加的所有关键词,只需要调用处理器的 get_all_keywords 函数:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
keyword_processor = KeywordProcessor()
# 2. 添加关键词
keyword_processor.add_keyword('j2ee', 'Java')
keyword_processor.add_keyword('colour', 'color')
# 3. 获取所有关键词
keyword_processor.get_all_keywords()
# output: {'colour': 'color', 'j2ee': 'Java'}

批量添加关键词

批量添加关键词有两种方法,一种是通过词典,一种是通过数组:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
keyword_processor = KeywordProcessor()
# 2. (第一种)通过字典批量添加关键词
keyword_dict = {
    "java": ["java_2e", "java programing"],
    "product management": ["PM", "product manager"]
}
keyword_processor.add_keywords_from_dict(keyword_dict)
# 2. (第二种)通过数组批量添加关键词
keyword_processor.add_keywords_from_list(["java", "python"])
# 3. 第一种的提取效果如下
keyword_processor.extract_keywords('I am a product manager for a java_2e platform')
# output ['product management', 'java']

单一或批量删除关键词

删除关键词也非常简单,和添加类似:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
keyword_processor = KeywordProcessor()
# 2. 通过字典批量添加关键词
keyword_dict = {
    "java": ["java_2e", "java programing"],
    "product management": ["PM", "product manager"]
}
keyword_processor.add_keywords_from_dict(keyword_dict)
# 3. 提取效果如下
print(keyword_processor.extract_keywords('I am a product manager for a java_2e platform'))
# ['product management', 'java']
# 4. 单个删除关键词
keyword_processor.remove_keyword('java_2e')
# 5. 批量删除关键词,也是可以通过词典或者数组的形式
keyword_processor.remove_keywords_from_dict({"product management": ["PM"]})
keyword_processor.remove_keywords_from_list(["java programing"])
# 6. 删除了java programing关键词后的效果如下
keyword_processor.extract_keywords('I am a product manager for a java_2e platform')
# ['product management']
3.高级使用

支持额外信息

前面提到在添加关键词的时候第二个参数为其别名,其实你不仅可以指示别名,还可以将额外信息放到第二个参数中:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
kp = KeywordProcessor()
# 2. 添加关键词并附带额外信息
kp.add_keyword('Taj Mahal', ('Monument', 'Taj Mahal'))
kp.add_keyword('Delhi', ('Location', 'Delhi'))
# 3. 效果如下
kp.extract_keywords('Taj Mahal is in Delhi.')
# [('Monument', 'Taj Mahal'), ('Location', 'Delhi')]

这样,在提取关键词的时候,你还能拿到其他一些你想要在得到此关键词时输出的信息。

支持特殊单词边界

Flashtext 检测的单词边界一般局限于 w [A-Za-z0-9_] 外的任意字符,但是如果你想添加某些特殊字符作为单词的一部分也是可以实现的:

from flashtext import KeywordProcessor
# 1. 初始化关键字处理器
keyword_processor = KeywordProcessor()
# 2. 添加关键词
keyword_processor.add_keyword('Big Apple')
# 3. 正常效果
print(keyword_processor.extract_keywords('I love Big Apple/Bay Area.'))
# ['Big Apple']
# 4. 将 '/' 作为单词一部分
keyword_processor.add_non_word_boundary('/')
# 5. 优化后的效果
print(keyword_processor.extract_keywords('I love Big Apple/Bay Area.'))
# []
4.结尾

个人认为这个模块已经满足我们的基本使用了,如果你有一些该模块提供的功能之外的使用需求,可以给 flashtext 贡献代码:
https://github.com/vi3k6i5/flashtext

附 FlashText 与正则相比 查询关键词 所花费的时间之比:

比正则快 M 倍以上!Python 替换字符串的新姿势

附 FlashText 与正则相比 替换关键词 所花费的时间之比:

比正则快 M 倍以上!Python 替换字符串的新姿势

– EOF –

转自:https://mp.weixin.qq.com/s/dawYdtbetE5IQ-Wq3cqakA