对不起我之前的回答;我误解了数据的格式。
和以前一样,让我们先将文本文件读入字符串元胞数组:
fid = fopen('deaths.txt');
scanned_fields = textscan(fid, '%s', 'Delimiter','\n');
text_array = scanned_fields{1};
fclose(fid);
虽然textscan 能够进行一些基本的解析,但它对于我们正在做的事情来说还不够复杂。所以我们只是用它来读取每一行作为一个字符串:格式%s意味着我们期待一个字符串,将Delimiter设置为\n意味着字符串由换行符分隔。
在您发布的示例数据中,每个条目是 4 行(名称、原因、位置、日期),后跟一个空行。只要我们可以依赖这种格式,这就提供了一种拆分数据的简单方法(而不是我在之前的回答中提出的 regexp 解析)。
name_str_array = text_array(1:5:end);
cause_str_array = text_array(2:5:end);
loc_str_array = text_array(3:5:end);
date_str_array = text_arary(4:5:end);
例如,name_strs 将从第 1 行开始每隔 5 行出现一次。同样,cause_strs 是每 5 行,从第 2 行开始。请注意数据中没有任何多余或缺失的行。
接下来,我们将解析其中的每一个以获取我们想要的信息。在我之前的回答中,我建议一次解析所有字符串,但我认为如果我们一次处理一个条目会更容易理解。例如,让我们考虑第一个条目。
name_str = name_str_array{1};
loc_str = loc_str_array{1};
date_str = date_str_array{1};
让我们从最简单的开始:解析日期。
date_format = 'Date of death:\s*(?<date>.*)';
parsed_fields = regexp(date_str, date_format, 'names');
DOD = parsed_fields.date;
我们正在寻找的格式是字符串Date of death:,后跟任意数量的空白字符(\s*),然后是我们希望捕获的文本块(又名“令牌”):@987654334 @
括号表示这是我们希望捕获的标记,?<date> 表示我们希望将此标记称为“日期”,.* 指定要查找的字符。 . 是通用通配符,即它匹配所有可能的字符。 * 表示我们对任意数量的重复感兴趣。所以本质上,这个.* 的意思是“匹配字符串中所有剩余的字符”。
使用names 选项调用regexp 会导致它返回一个结构体,其中包含命名标记作为其字段。
接下来,让我们做国家。这个有点棘手,因为城市/地区说明符的数量是可变的。但是这个国家永远是最后一个,所以我们要抓住那个。
country_format = '(?<country>\w[ \w]*)$';
parsed_fields = regexp(loc_str, country_format, 'names');
Country = parsed_fields.country;
此格式规范是标记(?<country>\w[ \w]*) 后跟字符串结尾(由特殊字符$ 表示)。在令牌规范中,我们匹配一个字母数字字符 (\w),后跟任意数量的空格和/或字母数字字符 ([ \w]*)。指定这个前导\w 的原因是我们不匹配前面的逗号和国家名称开头之间的空格。
最后,让我们做年龄。这个很棘手,因为并非每个条目都有年龄。至少这很容易,因为年龄(如果存在)是该行中唯一的数字数据。因此:
age_format = '(?<age>[\d]+)';
parsed_fields = regexp(name_str, age_format, 'names');
if isempty(parsed_fields)
Age = -1;
else
Age = str2double(parsed_fields.age);
end
格式规范只是标记(?<age>[\d]+),它指定我们正在寻找数字字符(\d),并且我们正在寻找其中的一个或多个(+)。
解析后,我们检查是否匹配。如果不是(parsed_fields 为空),那么我们为 Age 赋值 -1。否则,我们将解析后的年龄字段转换为数字。
所以把它们放在一起:
date_format = 'Date of death:\s*(?<date>.*)';
country_format = '(?<country>\w[ \w]*)[\W]?$';
age_format = '(?<age>[\d]+)';
nEntries = length(date_str_array);
DOD = cell(nEntries, 1);
Country = cell(nEntries, 1);
Age = zeros(nEntries, 1);
for ii = 1:nEntries
name_str = name_str_array{ii};
loc_str = loc_str_array{ii};
date_str = date_str_array{ii};
parsed_fields = regexp(date_str, date_format, 'names');
assert(~isempty(parsed_fields), 'Could not parse date from:\n%s', date_str);
DOD{ii} = parsed_fields.date;
parsed_fields = regexp(loc_str, country_format, 'names');
assert(~isempty(parsed_fields), 'Could not parse country from:\n%s', loc_str);
Country{ii} = parsed_fields.country;
parsed_fields = regexp(name_str, age_format, 'names');
if isempty(parsed_fields)
Age(ii) = -1;
else
Age(ii) = str2double(parsed_fields.age);
end
end
我添加了assert 语句以帮助调试在解析中遇到错误时发生的情况。
例如,您可能还注意到我在国家/地区格式中添加了[\W]?。这是因为在您的示例数据上运行它时,我遇到了一个在行尾包含句点的国家(即,它以“巴西”结尾,而不仅仅是“巴西”)。所以现在我们正在寻找一个非字母数字字符 (\W) 重复零次或 1 次 (?),它在括号之外,因此它不会被捕获为“国家”令牌的一部分。