1        问题引出

前几天在CSDN论坛遇到这样一个问题。

我要通过正则分别取出下面 <font color="#008000"> 与 </font> 之间的字符串

1、在 <font color="#008000"> 与 </font> 之间的字符串是没法固定的,是随机自动生成的

2、其中 <font color="#008000"> 与 </font>的数量也是没法固定的,也是随机自动生成的

<font color="#008000"> ** 这里是不固定的字符串1 ** </font>

<font color="#008000"> ** 这里是不固定的字符串2 ** </font>

<font color="#008000"> ** 这里是不固定的字符串3 ** </font>

有朋友给出这样的正则“(?<=<font[\s\S]*?>)([\s\S]*?)(?=</font>)”,看下匹配结果。

string test = @"<font color=""#008000""> ** 这里是不固定的字符串1 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串2 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串3 ** </font> ";

MatchCollection mc = Regex.Matches(test, @"(?<=<font[\s\S]*?>)([\s\S]*?)(?=</font>)");

foreach (Match m in mc)

{

richTextBox2.Text += m.Value + "\n---------------\n";

}

/*--------输出--------

** 这里是不固定的字符串1 **

---------------

<font color="#008000"> ** 这里是不固定的字符串2 **

---------------

<font color="#008000"> ** 这里是不固定的字符串3 **

---------------

*/

为什么会是这样的结果,而不是我们期望的如下的结果呢?

/*--------输出--------

** 这里是不固定的字符串1 **

---------------

** 这里是不固定的字符串2 **

---------------

** 这里是不固定的字符串3 **

---------------

*/

这涉及到逆序环视的匹配原理,以及贪婪与非贪婪模式应用的一些细节,下面先针对逆序环视的匹配细节展开讨论,然后再回过头来看下这个问题。

2        逆序环视匹配原理

关于环视的一些基础讲解和基本匹配原理,在正则基础之——环视这篇博客里已有所介绍,只不过当时整理得比较匆忙,没有涉及更详细的匹配细节。这里仅针对逆序环视展开讨论。

逆序环视的基础知识在上面博文中已介绍过,这里简单引用一下。

表达式

说明

(?<=Expression)

逆序肯定环视,表示所在位置左侧能够匹配Expression

(?<!Expression)

逆序否定环视,表示所在位置左侧不能匹配Expression

对于逆序肯定环视(?<=Expression)来说,当子表达式Expression匹配成功时,(?<=Expression)匹配成功,并报告(?<=Expression)匹配当前位置成功。

对于逆序否定环视(?<!Expression)来说,当子表达式Expression匹配成功时,(?<!Expression)匹配失败;当子表达式Expression匹配失败时,(?<!Expression)匹配成功,并报告(?<!Expression)匹配当前位置成功。

2.1     逆序环视匹配行为分析

2.1.1  逆序环视支持现状

目前支持逆序环视的语言还比较少,比如当前比较流行的脚本语言JavaScript中就是不支持逆序环视的。个人认为不支持逆序环视已成为目前JavaScript中使用正则的最大限制,一些使用逆序环视很轻松搞定的输入验证,却要通过各种变通的方式来实现。

需求:验证输入由字母、数字和下划线组成,下划线不能出现在开始或结束位置。

对于这样的需求,如果支持逆序环视,直接“^(?!_)[a-zA-Z0-9_]+(?<!_)$”就可以了搞定了,但是在JavaScript中,却需要用类似于“^[a-zA-Z0-9]([a-zA-Z0-9_]*[a-zA-Z0-9])?$”这种变通方式来实现。这只是一个简单的例子,实际的应用中,会比这复杂得多,而为了避免量词的嵌套带来的效率陷阱,正则实现起来很困难,甚至有些情况不得不拆分成多个正则来实现。

而另一些流行的语言,比如Java中,虽然支持逆序环视,但只支持固定长度的子表达式,量词也只支持“?”,其它不定长度的量词如“*”、“+” 、“{m,n}”等是不支持的。

源字符串:<div>a test</div>

需求:取得div标签的内容,不包括div标签本身

Java代码实现:

import java.util.regex.*;

String test = "<div>a test</div>";

String reg = "(?<=<div>)[^<]+(?=</div>)";

Matcher m = Pattern.compile(reg).matcher(test);

while(m.find())

{

System.out.println(m.group());

}

/*--------输出--------

a test

*/

但是如果源字符串变一下,加个属性变成“<div id=”test1”>a test</div>”,那么除非标签中属性内容是固定的,否则就无法在Java中用逆序环视来实现了。

为什么在很多流行语言中,要么不支持逆序环视,要么只支持固定长度的子表式呢?先来分析一下逆序环视的匹配原理吧。

2.1.2  Java中逆序环视匹配原理分析

不支持逆序环视的自不必说,只支持固定长度子表达式的逆序环视如何呢。

源字符串:<div>a test</div>

正则表达式:(?<=<div>)[^<]+(?=</div>)

需要明确的一点,无论是什么样的正则表达式,都是要从字符串的位置0处开始尝试匹配的。

首先由“(?<=<div>)”取得控制权,由位置0开始尝匹配,由于“<div>”的长度固定为5,所以会从当前位置向左查找5个字符,但是由于此时位于位置0处,前面没有任何字符,所以尝试匹配失败。

正则引擎传动装置向右传动,由位置1处开始尝试匹配,同样匹配失败,直到位置5处,向左查找5个字符,满足条件,此时把控制权交给“(?<=<div>)”中的子表达式“<div>”。“<div>”取得控制权后,由位置0处开始向右尝试匹配,由于正则都是逐字符进行匹配的,所以这时会把控制权交给“<div>”中的“<”,由“<”尝试字符串中的“<”,匹配成功,接下来由“d”尝试字符串中的“d”,匹配成功,同样的过程,由“<div>”匹配位置0到位置5之间的“<div>”成功,此时“(?<=<div>)”匹配成功,匹配成功的位置是位置5。

后续的匹配过程请参考 正则基础之——环视 和 正则基础之——NFA引擎匹配原理

那么对于量词“?”又是怎么样一种情况呢,看一下下面的例子。

源字符串:cba

正则表达式:(?<=(c?b))a

String test = "cba";

String reg = "(?<=(c?b))a";

Matcher m = Pattern.compile(reg).matcher(test);

while(m.find())

{

System.out.println(m.group());

System.out.println(m.group(1));

}

/*--------输出--------

a

b

*/

可以看到,“c?”并没有参与匹配,在这里,“?”并不具备贪婪模式的作用,“?” 只提供了一个分支的作用,共记录了两个分支,一个分支需要从当前位置向前查找一个字符,另一个分支需要从当前位置向前查找两个字符。正则引擎从当前位置,尝试这两种情况,优先尝试的是需要向前查找较少字符的分支,匹配成功,则不再尝试另一个分支,只有这一分支匹配失败时,才会去尝试另一个分支。

String test = "dcba";

String reg = "(?<=(dc?b))a";

Matcher m = Pattern.compile(reg).matcher(test);

while(m.find())

{

System.out.println(m.group());

System.out.println(m.group(1));

}

/*--------输出--------

a

dcb

*/

虽然有两个分支,但向前查找的字符数可预知的,所以只支持“?”时并不复杂,但如果再支持其它不定长度量词,情况又如何呢?

2.1.3  .NET中逆序环视匹配原理

.NET的逆序环视中,是支持不定长度量词的,在这个时候,匹配过程就变得复杂了。先看一下定长的是如何匹配的。

string test = "<div>a test</div>";

Regex reg = new Regex(@"(?<=<div>)[^<]+(?=</div>)");

Match m = reg.Match(test);

if (m.Success)

{

richTextBox2.Text += m.Value + "\n";

}

/*--------输出--------

a test

*/

从结果可以看到,.NET中的逆序环视在子表达式长度固定时,匹配行为与Java中应该是一样的。那么不定长量词又如何呢?

string test = "cba";

Regex reg = new Regex(@"(?<=(c?b))a");

Match m = reg.Match(test);

if (m.Success)

{

richTextBox2.Text += m.Value + "\n";

richTextBox2.Text += m.Groups[1].Value + "\n";

}

/*--------输出--------

a

cb

*/

可以看到,这里的“?”具备了贪婪模式的特性。那么这个时候是否会有这样的疑问,它的匹配过程仍然是从当前位置向左尝试,还是从字符串开始位置向右尝试匹配呢?

string test = "<ddd<cccba";

Regex reg = new Regex(@"(?<=(<.*?b))a");

Match m = reg.Match(test);

if (m.Success)

{

richTextBox2.Text += m.Value + "\n";

richTextBox2.Text += m.Groups[1].Value + "\n";

}

/*--------输出--------

a

<cccb

*/

从结果可看出,在逆序环视中有不定量词的时候,仍然是从当前位置,向左尝试匹配的,否则Groups[1]的内容就是“<ddd<cccb”,而不是“<cccb”了。

这是非贪婪模式的匹配情况,再看一下贪婪模式匹配的情况。

string test = "e<ddd<cccba";

Regex reg = new Regex(@"(?<=(<.*b))a");

Match m = reg.Match(test);

if (m.Success)

{

richTextBox2.Text += m.Value + "\n";

richTextBox2.Text += m.Groups[1].Value + "\n";

}

/*--------输出--------

a

<ddd<cccb

*/

可以看到,采用贪婪模式以后,虽然尝试到“c”前面的“<”时已经可以匹配成功,但由于是贪婪模式,还是要继续尝试匹配的。直到尝试到开始位置,取最长的成功匹配作为匹配结果。

2.2     匹配过程

再来理一下逆序环视的匹配过程吧。

源字符串:<div id=“test1”>a test</div>

正则表达式:(?<=<div[^>]*>)[^<]+(?=</div>) 

首先由“(?<=<div[^>]*>)”取得控制权,由位置0开始尝匹配,由于“<div[^>]*>”的长度不固定,所以会从当前位置向左逐字符查找,当然,也有可能正则引擎做了优化,先计算一下最小长度后向前查找,在这里“<div[^>]*>”至少需要5个字符,所以由当前位置向左查找5个字符,才开始尝试匹配,这要看各语言的正则引擎如何实现了,我推测是先计算最小长度。但是由于此时位于位置0处,前面没有任何字符,所以尝试匹配失败。

正则引擎传动装置向右传动,由位置1处开始尝试匹配,同样匹配失败,直到位置5处,向左查找5个字符,满足条件,此时把控制权交给“(?<=<div[^>]*>)”中的子表达式“<div[^>]*>”。“<div[^>]*>”取得控制权后,由位置0处开始向右尝试匹配,由于正则都是逐字符进行匹配的,所以这时会把控制权交给“<div[^>]*>”中的“<”,由“<”尝试字符串中的“<”,匹配成功,接下来由“d”尝试字符串中的“d”,匹配成功,同样的过程,由“<div[^>]*”匹配位置0到位置5之间的“<div ”成功,其中“[^>]*”在匹配“<div ”中的空格时是要记录可供回溯的状态的,此时控制权交给“>”,由于已没有任何字符可供匹配,所以“>”匹配失败,此时进行回溯,由“[^>]*”让出已匹配的空格给“>”进行匹配,同样匹配失败,此时已没有可供回溯的状态,所以这一轮匹配尝试失败。

正则引擎传动装置向右传动,由位置6处开始尝试匹配,同样匹配失败,直到位置16处,此时的当前位置指的就是位置16,把控制权交给“(?<=<div[^>]*>)”,向左查找5个字符,满足条件,记录回溯状态,控制权交给“(?<=<div[^>]*>)”中的子表达式“<div[^>]*>”。“<div[^>]*>”取得控制权后,由位置11处开始向右尝试匹配, “<div[^>]*>”中的“<”尝试字符串中的“s”,匹配失败。继续向左尝试,在位置10处由“<”尝试字符串中的“e”,匹配失败。同样的过程,直到尝试到位置0处,由“<div[^>]*”在位置0向右尝试匹配,成功匹配到“<div id=“test1”>”,此时“(?<=<div[^>]*>)”匹配成功,控制权交给“[^>]+”,继续进行下面的匹配,直到整个表达式匹配成功。

总结正则表达式“(?<=SubExp1) SubExp2”的匹配过程:

1、  由位置0处向右尝试匹配,直到找到一个满足“(?<=SubExp1) ”最小长度要求的位置x;

2、  从位置x处向左查找满足“SubExp1”最小长度要求的位置y;

3、  由“SubExp1”从位置y开始向右尝试匹配;

4、  如果“SubExp1”为固定长度或非贪婪模式,则找到一个成功匹配项即停止尝试匹配;

5、  如果“SubExp1”为贪婪模式,则要尝试所有的可能,取最长的成功匹配项作为匹配结果。

6、  “(?<=SubExp1) ”成功匹配后,控制权交给后面的子表达式,继续尝试匹配。

需要说明的一点,逆序环视中的子表达式“SubExp1”,匹配成功时,匹配开始的位置是不可预知的,但匹配结束的位置一定是位置x。

3       问题分析与总结

3.1     问题分析

那么再回过头来看下最初的问题。

string test = @"<font color=""#008000""> ** 这里是不固定的字符串1 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串2 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串3 ** </font> ";

MatchCollection mc = Regex.Matches(test, @"(?<=<font[\s\S]*?>)([\s\S]*?)(?=</font>)");

foreach (Match m in mc)

{

richTextBox2.Text += m.Value + "\n---------------\n";

}

/*--------输出--------

** 这里是不固定的字符串1 **

---------------

<font color="#008000"> ** 这里是不固定的字符串2 **

---------------

<font color="#008000"> ** 这里是不固定的字符串3 **

---------------

*/

其实真正让人费解的是这里的逆序环视的匹配结果,为了更好的说明问题,改下正则。

string test = @"<font color=""#008000""> ** 这里是不固定的字符串1 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串2 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串3 ** </font> ";

MatchCollection mc = Regex.Matches(test, @"(?<=(<font[\s\S]*?>))([\s\S]*?)(?=</font>)");

for(int i=0;i<mc.Count;i++)

{

richTextBox2.Text += "第" + (i+1) + "轮成功匹配结果:\n";

richTextBox2.Text += "Group[0]:" + m.Value + "\n";

richTextBox2.Text += "Group[1]:" + m.Groups[1].Value + "\n---------------\n";

}

/*--------输出--------

第1轮成功匹配结果:

Group[0]: ** 这里是不固定的字符串1 **

Group[1]:<font color="#008000">

---------------

第2轮成功匹配结果:

Group[0]:

<font color="#008000"> ** 这里是不固定的字符串2 **

Group[1]:<font color="#008000"> ** 这里是不固定的字符串1 ** </font>

---------------

第3轮成功匹配结果:

Group[0]:

<font color="#008000"> ** 这里是不固定的字符串3 **

Group[1]:<font color="#008000"> ** 这里是不固定的字符串2 ** </font>

---------------

*/

对于第一轮成功匹配结果应该不存在什么疑问,这里不做解释。

第一轮成功匹配结束的位置是第一个“</font>”前的位置,第二轮成功匹配尝试就是从这一位置开始。

首先由“(?<=<font[\s\S]*?>)”取得控制权,向左查找6个字符后开始尝试匹配,由于“<”会匹配失败,所以会一直尝试到位置0处,这时“<font”是可以匹配成功的,但是由于“<font[\s\S]*?>”要匹配成功,匹配的结束位置必须是第一个“</font>”前的位置,所以“>”是匹配失败的,这一位置整个表达式匹配失败。

正则引擎传动装置向右传动,直到第一个“</font>”后的位置,“<font[\s\S]*?>”匹配成功,匹配开始位置是位置0,匹配结束位置是第一个“</font>”后的位置,“<font[\s\S]*?>”匹配到的内容是“<font color="#008000"> ** 这里是不固定的字符串1 ** </font>”,其中“[\s\S]*?”匹配到的内容是“color="#008000"> ** 这里是不固定的字符串1 ** </font”,后面的子表达式继续匹配,直到第二轮匹配成功。

接下来的第三轮成功匹配,匹配过程与第二轮基本相同,只不过由于使用的是非贪婪模式,所以“<font[\s\S]*?>”在匹配到“<font color="#008000"> ** 这里是不固定的字符串2 ** </font>”时匹配成功,就结束匹配,不再向左尝试匹配了。

接下来看下贪婪模式的匹配结果。

string test = @"<font color=""#008000""> ** 这里是不固定的字符串1 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串2 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串3 ** </font> ";

MatchCollection mc = Regex.Matches(test, @"(?<=(<font[\s\S]*>))([\s\S]*?)(?=</font>)");

for(int i=0;i<mc.Count;i++)

{

richTextBox2.Text += "第" + (i+1) + "轮成功匹配结果:\n";

richTextBox2.Text += "Group[0]:" + m.Value + "\n";

richTextBox2.Text += "Group[1]:" + m.Groups[1].Value + "\n---------------\n";

}

/*--------输出--------

第1轮匹配结果:

Group[0]: ** 这里是不固定的字符串1 **

Group[1]:<font color="#008000">

---------------

第2轮匹配结果:

Group[0]:

<font color="#008000"> ** 这里是不固定的字符串2 **

Group[1]:<font color="#008000"> ** 这里是不固定的字符串1 ** </font>

---------------

第3轮匹配结果:

Group[0]:

<font color="#008000"> ** 这里是不固定的字符串3 **

Group[1]:<font color="#008000"> ** 这里是不固定的字符串1 ** </font>

<font color="#008000"> ** 这里是不固定的字符串2 ** </font>

---------------

*/

仅仅是一个字符的差别,整个表达式的匹配结果没有变化,但匹配过程差别却是很大的。

那么如果想得到下面这种结果要如何做呢?

/*--------输出--------

** 这里是不固定的字符串1 **

---------------

** 这里是不固定的字符串2 **

---------------

** 这里是不固定的字符串3 **

---------------

*/

把量词修饰的子表达式的匹配范围缩小就可以了。

string test = @"<font color=""#008000""> ** 这里是不固定的字符串1 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串2 ** </font>

<font color=""#008000""> ** 这里是不固定的字符串3 ** </font> ";

MatchCollection mc = Regex.Matches(test, @"(?is)(?<=(<font[^>]*>))(?:(?!</?font\b).)*(?=</font>)");

for(int i=0;i<mc.Count;i++)

{

richTextBox2.Text += "第" + (i+1) + "轮匹配结果:\n";

richTextBox2.Text += "Group[0]:" + mc[i].Value + "\n";

richTextBox2.Text += "Group[1]:" + mc[i].Groups[1].Value + "\n---------------\n";

}

/*--------输出--------

第1轮匹配结果:

Group[0]: ** 这里是不固定的字符串1 **

Group[1]:<font color="#008000">

---------------

第2轮匹配结果:

Group[0]: ** 这里是不固定的字符串2 **

Group[1]:<font color="#008000">

---------------

第3轮匹配结果:

Group[0]: ** 这里是不固定的字符串3 **

Group[1]:<font color="#008000">

---------------

*/

3.2     逆序环视应用总结

通过对逆序环视的分析,可以看出,逆序环视中使用不定长度的量词,匹配过程很复杂,代价也是很大的,这也许也是目前绝大多数语言不支持逆序环视,或是不支持在逆序环视中使用不定长度量词的原因吧。

在正则应用中需要注意的几点:

1、  不要轻易在逆序环视中使用不定长度的量词,除非确实需要;

2、  在任何场景下,不只是逆序环视中,不要轻易使用量词修饰匹配范围非常大的子表达式,小数点“.”和“[\s\S]”之类的,使用时尤其要注意。

注:本文分析过程有部分为自己的猜测,无从考证,如果错漏,还请批评指正。

转载于:https://www.cnblogs.com/Eleanore/archive/2012/06/13/2548176.html

正则应用之——逆序环视探索相关推荐

  1. 数据结构_Java_基于 线性表-单链表的初始化、逆序、去重、非递减序列的合并(开辟新链表先整体插入一个链表全部元素,再遍历另外一个链表寻找合适位置插入 、开辟新链表实现舍弃原链表)等操作实现

    写在前面 不久前学习了数据结构线性表-数组-链表的相关知识,用C/C++语言实现了 单链表的系列相关操作 .见往期博客: 数据结构实验2_C语言_基于顺序表的非递减有序表的合并.线性表元素的增.删.改 ...

  2. python string类型的英文句子逆序输出

    输入的是一个string类型的英文句子,输出也是一个string类型的英文句子:是把输入的句子里面的单词按逆序输出,但是每个单词按内部的字符按顺序输出 示例: 输入:i like you, but j ...

  3. 华为机考HJ13句子逆序C语言弱智解法

    华为机考HJ13句子逆序C语言弱智解法 句子逆序C语言低端解法,易于理解. 1.收数据老生常谈. 2.将句子全部逆序. 3.将单词正序. (这里有一个bug,最后一个词(原句子第一个词)总是收不到,所 ...

  4. 字符串面试题(一)字符串逆序

    字符串逆序可以说是最经常考的题目.这是一道入门级的题目,相信80%的程序员经历过这道题.给定一个字符串s,将s中的字符顺序颠倒过来,比如s="abcd",逆序后变成s=" ...

  5. C语言的单链表逆序和指定范围逆序

    文章目录 前言 逆序 指定范围逆序 源码实现 前言 关于链表的逆置,是考察对链表指针的理解.知道了如何不实用额外空间,同时使用O(n)复杂度对链表进行逆序之后将会对链表有好理解. 同时关于如何在指定范 ...

  6. 单链表逆序生成及逆置的完整实现

    单链表逆序生成及逆置的完整实现 本例中单链表数据类型定义成int型,可更改 头文件1(1.h) 宏定义及Status类型定义 头文件2(2.h) 单链表基本操作函数与逆置函数 include" ...

  7. 题目 1093:【蓝桥杯】【入门题】字符逆序

    题目 1093:字符逆序 蓝桥杯刷题群已成立,微信后台回复[蓝桥杯],即可进入. 如果加入了之前的社群不需要重复加入. 时间限制: 1Sec 内存限制: 64MB 1. 题目描述 将一个字符串str的 ...

  8. 【数据结构】单链表的逆序输出(两种方法)

    第一种方法:转换指针方向 即:将一个已经创建好的单链表进行指针域的改变 今天突然被问到单链表逆序的问题,弄了好久才看出别人的程序有啥问题,就重新写了一遍. 今天才在CSDN客户端上看到美团的面试题是冒 ...

  9. ACMNO.27 Python的两行代码解决 C语言-字符逆序 写一函数。使输入的一个字符串按反序存放,在主函数中输入输出反序后的字符串。 输入 一行字符 输出 逆序后的字符串

    题目描述 写一函数,使输入的一个字符串按反序存放,在主函数中输入输出反序后的字符串. 输入 一行字符 输出 逆序后的字符串 样例输入 123456abcdef 样例输出 fedcba654321 来源 ...

最新文章

  1. 给算法工程师和研究员的「霸王餐」| 附招聘信息
  2. Android Studio使用OpenCV后,使APP不安装OpenCV Manager即可运行
  3. java当中有关循环的代码_有关Java循环的内容,编程中还是比较常用的,下面分享给大家几个循环的示例代码,练习一下。1、循环输出1到100之间所有能被3或能被4整除的数。pack...
  4. Hibernate hql getHibernateTemplate()常用方法汇总
  5. 音视频开发(39)---语音增强
  6. 使用Adobe Acrobat DC时PDF“文档无法保存。文件可能是只读文件”解决方法
  7. C# 创建Excel并写入内容
  8. python没基础能自学吗-没有基础先要自学python,有什么比较好的书推荐?
  9. Java完全参考手册笔记1
  10. 实证研究的步骤_本科生毕业论文设计可以用到的研究方法有哪些
  11. 图论专题1(网络流)
  12. RIDE中testcase的edit界面显示异常/空白
  13. 综合型集团该如何利用数字化转型支撑磅礴多元的服务?
  14. linux系统如何运行ansys,ANSYS在linux下使用
  15. 单片机dac c语言,基于单片机Stm32f103 DAC电流输出解决方案
  16. 用于长延迟多径衰落环境下的强化UF-OFDM
  17. 社交类语音聊天系统开发
  18. 微信公众号 接口测试平台 获取自定义菜单教程
  19. 关于电荷放大器的问题
  20. matlab傅里叶变换 绘图

热门文章

  1. PL3330 同步整流
  2. 【进制转换】十进制到二、八、十六进制
  3. 遥想当年,机缘巧合入了 ACM 的坑,周边巨擘林立,从此过上了天天被虐似死狗的生活…
  4. 关于竞争上岗机制的操作流程
  5. Hadoop那些事儿(四)---MapReduce编程实例(基础)
  6. 编码器c语言斌编程,旋转编码器的编程思路
  7. 无人机遥感技术在各个行业中的应用
  8. 时间序列预测 预测时间段_什么是时间序列预测
  9. element 可编辑表格方向键操控
  10. [Luogu P4338] [BZOJ 5212] [ZJOI 2018] 历史