Matlab:当有时没有匹配时,使用正则表达式解析大型表或单元格数组的最有效方法是什么?



我正在处理一个混乱的手动维护的"数据库";它有一个列,其中包含一个具有名称、值对的字符串。我试图用regexp解析整列,以提取值。该列是巨大的(>100000个条目)。作为我实际数据的代理,让我们使用以下代码:

line1={'''thing1'': ''-583'', ''thing2'': ''245'', ''thing3'': ''246'', ''morestuff'':, '''''};
line2={'''thing1'': ''617'', ''thing2'': ''239'', ''morestuff'':, '''''};
line3={'''thing1'': ''unexpected_string(with)parens5'', ''thing2'': 245, ''thing3'':''246'', ''morestuff'':, '''''};
mycell=vertcat(line1,line2,line3);

这捕获了数据库中遇到的一般问题。我想使用cellfun提取每行中的thing1thing2thing3,以输出标量单元阵列。它们通常应该是3位数字,但有时它们有一种意想不到的形式。有时thing3完全缺失,甚至没有名字出现在行中。有时会出现轻微的格式不一致,比如值周围缺少单引号、缺少空格或三位数前面出现短划线。除了thing3完全丢失的情况外,我已经设法处理了所有这些。

我的一般方法是使用这样的表达式:

expr1='(?<=thing1''):s?''?-?([wd().]*?)''?,';
expr2='(?<=thing2''):s?''?-?([wd().]*?)''?,';
expr3='(?<=thing3''):s?''?-?([wd().]*?)''?,';

这会在后面查找thingX',然后尝试使用惰性匹配来匹配:,然后是零或一个空格、0或1个单引号、0或一个短划线、字母、数字、圆括号或句点的任意组合(这被定义为标记),直到遇到零或一条单引号,然后是逗号。我调用regexp作为regexp(___,'tokens','once')来返回匹配的令牌。

问题是,当不匹配时,regexp会返回一个空数组。这使我无法使用

out=cellfun(@(x) regexp(x,expr3,'tokens','once'),mycell);

除非我用'UniformOutput',false来称呼它。这个问题有两个方面。首先,我需要手动查找不匹配的行。例如,我可以这样做:

emptyout=cellfun(@(x) isempty(x),out);
emptyID=find(emptyout);
backfill=cell(length(emptyID),1);
[backfill{:}]=deal('Unknown');
out(emptyID)=backfill;

在本例中,emptyID的长度为1,因此此代码过于夸张。但我相信,当它更长的时候,这是正确的概括方式。此代码将使用字符串Unknown更改out中的每个空单元格数组。但这导致了第二个问题。我现在得到了一个非标量值的"混乱"单元格数组。例如,我无法检查unique(out)

请原谅我的长篇大论,但我想举一个清楚的例子说明这个问题。现在,我的实际问题分为几个部分:

  1. 有没有一种方法可以在不使用'UniformOutput',false的情况下完成我想要做的?例如,如果不匹配,是否有方法让regexp传递自定义字符串(例如,如果没有匹配,则传递'Unknown')?我可以想到一个"欺骗",那就是在表达式中使用|运算符,如果第一个标记不匹配,则查找总是找到的内容。然后,我仍然需要加倍返回输出,并将该结果的每个实例更改为'Unknown'
  2. 如果我采用'UniformOutput',false方法,我如何在末尾恢复标量单元阵列以轻松操作它(例如,通过unique)?我承认我不是100%清楚标量和非标量单元数组
  3. 如果有一些完全不同的方法我没有想到,我也会接受
  4. 与主要问题无关,我还尝试使用一个表达式运行regexp,使用3个令牌一次性提取thing1thing2thing3的值。即使regexp没有空结果,这似乎也需要'UniformOutput',false。我不知道如何使用这种方法获得标量单元阵列(例如,Nx1单元阵列,其中每个单元是3x1单元)

在一天结束时,我想使用以下结果构建一个表:

mytable=table(out1,out2,out3);

编辑:使用celldisp揭示了一些问题:

celldisp(out)
out{1}{1} =
246

out{2} =
Unknown

out{3}{1} =
246

我假设我需要改变out的结构,使得out{1}{1}out{3}{1}的内容只是out{1}out{3}。但如果我需要'UniformOutput',false,我不确定如何实现这一点。

注意:我没有使用过MATLAB,这不会回答"高效的";方面,但是。。。

强迫总是有一场比赛怎么样?

想想你真的想要一场比赛来跳过这个问题,空比赛怎么样?

在这里的MATLAB帮助页面上,我可以看到一个'emptymatch'选项,也许这是一个值得尝试的东西。

例如

the_thing_i_want_to_find|

匹配";_ hing_i_want_to_;或空匹配,请注意|字符。

在捕获组中,它可能看起来像这样:

(the_thing_i_want_to_find|)

作为一种变通方法,我发现使用regexprep可以用来查找缺少thing3的条目。例如:

replace='$1 ''thing3'': ''Unknown'', ''morestuff''';
missingexpr='(?<=thing2'':s?)(''?-?[wd().]*?''?,) ''morestuff''';
regexprep(mycell{2},missingexpr,replace)
ans =
''thing1': '617', 'thing2': '239', 'thing3': 'Unknown', 'morestuff':, '''

将其应用于整个阵列:

fixedcell=cellfun(@(x) regexprep(x,missingexpr,replace),mycell);
out=cellfun(@(x) regexp(x,expr3,'tokens','once'),fixedcell,'UniformOutput',false);

这感觉有点迂回,但它是有效的。

cellfun可以替换为一个普通的旧for循环。您的代码要么同样快,要么甚至更快。cellfun是用循环实现的,无论如何,使用它除了代码行数较少之外没有任何好处。在显式循环中,您可以检查regexp的输出,并以任何您喜欢的方式构建输出数组。