在matlab中组织和搜索(日期,字符串,国家/地区)

时间:2014-11-24 20:44:11

标签: arrays string matlab nested-loops

我刚刚在R上做了一个项目,现在正在用matlab做一些工作。

我需要制作3个载体:

  1. DOD
  2. 国家/地区
  3. 年龄
  4. 计算并存储一个包含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
    

    我不知道如何查找字符串并组织它们,但是会欣赏任何想法如何开始。

2 个答案:

答案 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)的非字母数字字符(?),并且它在括号之外,因此不会被捕获为“国家“令牌。