【问题标题】:Organizing and Searching for (dates , strings , countries) in matlab在 matlab 中组织和搜索(日期、字符串、国家)
【发布时间】:2014-11-24 20:44:11
【问题描述】:

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

我需要制作 3 个向量:

  1. 国防部
  2. 国家
  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

我不知道如何查找字符串并组织它们,但如果有任何开始的想法,我将不胜感激。

【问题讨论】:

    标签: arrays string matlab nested-loops


    【解决方案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{:}];
    

    这里我们只是定义了一个格式字符串。我已经把它分解成这样,以便更清楚地知道发生了什么。让我们逐行浏览:

    • (?&lt;name&gt;[ \w]*) 括号表示这是我们希望捕获的一段文本(也称为“令牌”)。 ?&lt;name&gt; 表示我们将把这个令牌称为“名称”。最后,[ \w]* 指定要匹配的文本类型。方括号内的内容指定要查找的字符:空格 () 和/或字母数字字符 (\w)。方括号外的* 表示我们将接受任意数量的这些字符。
    • \( 接下来我们正在寻找一个空格和一个左括号。括号前面的反斜杠表示我们正在寻找文字括号,即不应将这个括号解释为要捕获的另一个标记的开始。
    • (?&lt;age&gt;[\d]*) 另一个要捕获的令牌。这个称为“年龄”,包含任意数量的\d(数字字符)。
    • years old \) - Cause of death: 寻找更多文字。同样,我们将匹配此文本,但我们不会捕获它(因为它没有括在括号中)。
    • (?&lt;city&gt;[ \w]*) 另一个要捕获的令牌。这个称为“城市”,包含任意数量的空格和/或字母数字字符。
    • ,逗号、空格
    • (?&lt;province&gt;[ \w]*), (?&lt;country&gt;[ \w]*) - Date of death:你懂的
    • (?&lt;date&gt;[ ,\w]*) 我们的最终标记,称为“日期”,包含任意数量的空格、逗号和/或字母数字字符。

    然后我们将字符串解析成一个结构体数组:

    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“序列日期号”很好,它允许算术计算和重新格式化为不同类型的表示格式。不幸的是,将日期设置为“20th”而不是“20”与转换函数不兼容,因此我们需要首先从“1st”、“2nd”中去掉“st”、“nd”、“rd” 、“第三”等:

    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 类,因此现在可能有更好的方法来处理这个问题。

    【讨论】:

    • 我需要学会更准确地提出问题。我更新了名称列表以更好地显示文件的外观。由于我不了解如何格式化问题中的文本,我无法准确显示它的外观。
    • 这里是实际数据link
    • 我明白了;格式有点不同,并不是每个条目都有相关的年龄。我将添加一个适用于您发布的数据的新答案。
    【解决方案2】:

    对不起我之前的回答;我误解了数据的格式。

    和以前一样,让我们​​先将文本文件读入字符串元胞数组:

    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 @

    括号表示这是我们希望捕获的标记,?&lt;date&gt; 表示我们希望将此标记称为“日期”,.* 指定要查找的字符。 . 是通用通配符,即它匹配所有可能的字符。 * 表示我们对任意数量的重复感兴趣。所以本质上,这个.* 的意思是“匹配字符串中所有剩余的字符”。

    使用names 选项调用regexp 会导致它返回一个结构体,其中包含命名标记作为其字段。


    接下来,让我们做国家。这个有点棘手,因为城市/地区说明符的数量是可变的。但是这个国家永远是最后一个,所以我们要抓住那个。

    country_format = '(?<country>\w[ \w]*)$';
    parsed_fields = regexp(loc_str, country_format, 'names');
    Country = parsed_fields.country;
    

    此格式规范是标记(?&lt;country&gt;\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
    

    格式规范只是标记(?&lt;age&gt;[\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 次 (?),它在括号之外,因此它不会被捕获为“国家”令牌的一部分。

    【讨论】:

    • 我知道数组结构正在扫描每一行,但之后会变得模糊。再说一次,我对这一切都很陌生。我在哪里保存数组?我运行您发布的更长的帖子 + 数组 ' name_str_array = text_array(1:5:end);'
    • 如何存储和汇总所有国家/地区?
    • 现在我知道国家是一个单元格,我需要一个字符串数组,然后我需要计算每个国家/地区
    • 我试着总结了每个国家的死亡人数,但无法弄清楚
    • tabulate(Country) 有效,但我需要总结国家,然后将其添加到另一个列表中
    猜你喜欢
    • 2018-08-10
    • 2017-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-14
    • 2015-08-03
    • 1970-01-01
    相关资源
    最近更新 更多