我刚刚在R上做了一个项目,现在正在用matlab做一些工作。
我需要制作3个载体:
计算并存储一个包含236个数据点的.txt列表,文本文件中的数据如下所示:
Unknown woman
Cause of death: found dead, with eyes removed.
Location of death: Jardim dos Ipês Itaquaquecetuba, São Paulo, Brazil
Date of death: August 9th, 2014
Cris
Cause of death: multiple gunshot wounds
Location of death: Portal da Foz, Foz do Iguaçu, Brazil
Date of death: September 13th, 2014
Betty Skinner (52 years old)
Cause of death: blunt force trauma to the head
Location of death: Cleveland, Ohio, USA
Date of death: December 4th, 2013
Brittany Stergis (22 years old)
Cause of death: gunshot wound to the head
Location of death: Cleveland, Ohio, USA
Date of death: December 5th, 2013
我不知道如何查找字符串并组织它们,但是会欣赏任何想法如何开始。
答案 0 :(得分:1)
您可以使用textscan
将文件读入字符串的单元格数组,然后使用regexp
来解析字符串以获取所需的字段。
首先,我们将文本文件读入字符串的单元格数组中:
fid = fopen('deaths.txt');
scanned_fields = textscan(fid, '%s', 'Delimiter','\n');
text_array = scanned_fields{1};
fclose(fid);
虽然textscan
能够进行一些基本的解析,但它对我们正在做的事情来说还不够复杂。因此我们只是使用它来将每行读取为单个字符串:格式%s
表示我们期待一个字符串,将Delimiter
设置为\n
意味着字符串是分开的换行符。
接下来,我们可以释放正则表达式的强大功能来解析你死去的女人:
format = {
'(?<name>[ \w]*)'
' \('
'(?<age>[\d]*)'
' years old\) - Cause of death: '
'(?<cause>[ \w]*)'
' - Location of death: '
'(?<city>[ \w]*)'
', '
'(?<province>[ \w]*)'
', '
'(?<country>[ \w]*)'
' - Date of death: '
'(?<date>[ ,\w]*)'
};
format = [format{:}];
这里我们只是定义一个格式字符串。我已经像这样打破了它,让它更加清晰。让我们逐行完成:
(?<name>[ \w]*)
括号表示这是我们希望捕获的一大块文本(a.k.a. a&#34; token&#34;)。 ?<name>
表示我们会将此令牌称为&#34; name&#34;。最后,[ \w]*
指定要匹配的文本类型。方括号内的内容指定要查找的字符:空格(
)和/或字母数字字符(\w
)。方括号外的*
表示我们将接受任意数量的这些字符。\(
接下来我们正在寻找一个空格和一个左括号。括号前面的反斜杠表示我们正在寻找文字括号,即不应将此括号解释为另一个要捕获的标记的开头。(?<age>[\d]*)
另一个要捕获的标记。这个被称为&#34;年龄&#34;并包含任意数量的\d
(数字字符)。years old \) - Cause of death:
需要查找更多文字。同样,我们将匹配此文本,但我们不会捕获它(因为它没有括在括号中)。(?<city>[ \w]*)
另一个要捕获的标记。这个被称为&#34; city&#34;并包含任意数量的空格和/或字母数字字符。,
逗号,空间(?<province>[ \w]*), (?<country>[ \w]*) - Date of death:
你明白了(?<date>[ ,\w]*)
我们的最终标记,名为&#34; date&#34;,其中包含任意数量的空格,逗号和/或字母数字字符。然后我们将字符串解析为struct数组:
parsed_fields = regexp(text_array, format, 'names');
parsed_fields = [parsed_fields{:}]'
这就是输出的样子:
>> parsed_fields(1)
ans =
name: 'Jacqueline Cowdrey'
age: '50'
cause: 'unknown'
city: 'Worthing'
province: 'West Sussex'
country: 'United Kingdom'
date: 'November 20th, 2013'
因此,您可以非常直接地了解您的国家/地区:
Country = {parsed_fields.country}';
年龄是一种简单的数字转换:
Age_str = {parsed_fields.age};
Age = cellfun(@str2double, Age_str)';
日期作为字符串非常简单:
Date_str = {parsed_fields.date}';
但将它作为MATLAB&#34;序列日期编号&#34;很高兴,它允许算术计算和重新格式化为不同类型的表示格式。不幸的是,把这一天当作&#34; 20&#34;而不是&#34; 20&#34;与转换功能不兼容,因此我们需要首先剥离&#34; st&#34;,&#34; nd&#34;,&#34; rd&#34;来自&#34; 1st&#34;,&#34; 2nd&#34;,&#34; 3rd&#34;等:
Date_str = regexprep(Date_str, '(?<day>[\d]+)(st|nd|rd|th)', '$<day>');
Date_num = datenum(Date_str, 'mmmm dd, yyyy');
其他一些说明:
如果文件非常大,您可能希望使用fgetl
一次读取一行(然后一次解析一行),而不是将整个文件读入我们上面所做的记忆。
在您的示例中,条目看起来像是一个额外的换行符。我不确定您的实际数据中是否存在这种情况,或者这只是一个stackoverflow事件,但如果您需要删除这些换行符,您可以这样做:
is_empty_line = cellfun(@isempty, text_array);
text_array = text_array(~is_empty_line);
在你的例子中,有很多拼写错误(这里和那里有一个额外的空格,有时冒号或破折号是其他符号)。如果您的实际数据中存在这些拼写错误,则需要调整格式规范以解决此问题。例如,您可以使用-
来匹配(任意数量的空白字符,单个非字母数字字符,任意数量的空白字符),而不是使用\s*\W\s*
来匹配(空格,短划线,空格) )。
如果format = [format{:}];
或Country = {parsed_fields.country}';
这样的语法对您来说很陌生,则相当于:
format = [format{1} format{2} format{3} ... format{end}];
Country = cell(length(parsed_fields),1);
for ii = 1:length(parsed_fields)
Country{ii} = parsed_fields(ii).country;
end
MATLAB R2014b添加了一个新的datetime
类,因此现在可能有更好的方法来解决这个问题。
答案 1 :(得分:0)
对我之前的回答感到抱歉;我误解了数据的确切格式。
和以前一样,我们首先将文本文件读入字符串的单元格数组中:
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>
表示我们希望将此标记称为“日期”,.*
指定要查找的字符。 .
是通用通配符,即它匹配所有可能的字符。 *
表示我们对任意数量的重复都感兴趣。所以从本质上讲,这个.*
意味着“匹配字符串中的所有剩余字符”。
使用regexp
选项调用names
会导致它返回一个名为tokens的结构作为其字段。
接下来,让我们来做这个国家。这个有点棘手,因为有不同数量的城市/地区说明符。但这个国家将永远是最后一个国家,所以这就是我们要抓住的国家。
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
)的非字母数字字符(?
),并且它在括号之外,因此不会被捕获为“国家“令牌。