对不起,我以前的答案;我误解了数据的格式。
如前,让我们先读文本文件转换成字符串单元阵列:
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
将是每一个5日线,从线#1。同样,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*
),然后是文本的块(又名“令牌”),我们希望捕捉到:(?<date>.*)
圆括号表示这是我们希望捕获的令牌,?<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
)重复零次或一次(?
),并且它在括号外,因此它不会被捕获为“国家”标记的一部分。
我需要学会提出更准确的问题。我更新了名称列表以更好地显示文件的外观。由于我不了解如何格式化问题中的文本,因此无法准确显示它看起来的样子。 – Dgales4130 2014-11-25 19:31:41
这里是实际数据[链接](https://docs.google.com/a/uic.edu/document/d/1AsCTRuCpJpQ2PRQp-SY7u5iXK9ayLOsuHzOcd5haWgw/edit?usp=sharing) – Dgales4130 2014-11-25 19:39:46
我看到了;格式有些不同,并不是每个条目都有相关的年龄。我将添加一个新的答案,该答案适用于您发布的数据。 – KQS 2014-11-25 22:38:57