【问题标题】:Building sorted data table using hash使用哈希构建排序数据表
【发布时间】:2013-04-18 08:12:09
【问题描述】:

我已经构建了几个脚本来组织测试设备的数据输出,但是我遇到了这个问题。

测试设备监控来自多个受试者(标识符为 ID1、ID2 等)的四种类型的输入(Data1、Data2、Data3、Data4),并以日期和时间戳为间隔记录每一种输入。设备转储的 CSV 文件是这样组织的:

Start,Date,Time0
Subject,ID1,ID2,[...],ID#

Date,Time1
Data1,aa1,aa2,[...],aa#
Data2,ba1,ba2,[...],ba#
Data3,ca1,ca2,[...],ca#
Data4,da1,da2,[...],da#

Date,Time2
Data1,ab1,ab2,[...],ab#
Data2,bb1,bb2,[...],bb#
Data3,cb1,cb2,[...],cb#
Data4,db1,db2,[...],db#

...等等。

“开始”将此行标识为数据的开头; “主题”将该行标识为包含主题 ID 的行; “Data1”-“Data4”将该行标识为包含该数据类型的数据的行,该数据类型在前面的日期和时间指示的特定时间间隔内。

因此,输出数据被分成多个块,这对于设备制造商来说确实是一个不幸的选择,尤其是在几天或几周内每隔几分钟收集一次数据的情况下。为了无需手动选择每 6 行来分析数据,我们需要将所有数据类型分组到块中,如下所示:

Data1,Subject,ID1,ID2,[...],ID#
Date,Time1,aa1,aa2,[...],aa#
Date,Time2,ab1,ab2,[...],ab#
...

Data2,Subject,ID1,ID2,[...],ID#
Date,Time1,ba1,ba2,[...],ba#
Date,Time2,bb1,bb2,[...],bb#
...

目标是将四种数据类型中的每一种都放在单独的块中,这样任何给定主题(ID1 到 ID#)的时间过程数据都将位于单个列中,日期和时间作为初始列。 (上面的“DataX”和“Subject”只是简单地用作列标题。)

目前我通过将每一行放入一个单独的数组来做到这一点。这是一种快速而肮脏的完成工作的方式。该脚本获取时间和日期,并将 ID 行推入四个数组中的每一个(每种数据类型一个),然后根据数据类型依次添加每个数据行。输出只是逐行打印每个数组,添加一个空白行,然后打印下一个数组。这可行,但理想情况下,我想按主题 ID 对数据列进行排序,然后打印出数据,而不会丢失按日期和时间戳进行的垂直排序。 (因为数据已经垂直排序了,所以我目前在打印之前没有对数组进行排序功能。)

最简单的方法是什么?在精神上,我在尝试解析如何将 Y 行、X 列中的数据与 CSV 文件中 X 列中的主题 ID 相关联时遇到了麻烦。我使用的所有其他数据输出文件要么将主题 ID 作为每行中的第一项,要么每个主题有一个文件,这样更容易。

注意:因为时间/日期在他们自己的一行,我为每个使用一个变量;如果脚本检测到包含新时间和/或日期的行,它会更新变量值。

编辑—— 我采纳了 Borodin 的一些建议(让 FH 按行而不是按段落处理)。我将主题行中的数据拉入一个数组 (@ids),并使用日期/时间和 ID 作为键将数据行推入散列:

my ($datatype, @fields) = @line;
push @keys, $datatype unless exists $data{$datatype};
my $datetime = "$date\,$time";
push @timestamps, $datetime unless exists $data{$datetime};
for my $i ( 0 .. $#fields) {
    push @{$data{$datetime}{$ids[$i]}}=>$fields[$i]
    };

我还将日期-时间对放入第二个数组以维护顺序 (@timestamps)。 此时的问题是我在将值打印出来时遇到问题。目前正在尝试:

    foreach my $date (keys %data) {
    print OUT $date;
    foreach my $id (@ids) {
        foreach my $s (keys %{$data{$date}}) {
            if ( exists($data{$date}{$id}) ) {
                print OUT ",", $data{$date}{$id}
                }
            else {
                print OUT ",";
                }
            }
        }
    print OUT "\n"; # close printing on a given date
    }

不断得到垃圾输出(打印哈希引用,而不是实际值!)。 Dumper 输出如下所示:

$VAR1 = {
    'date,time' => [
                    'ID1' => [
                            '0.00'
                            ]
                    'ID2' => [
                            '0.12',
                            ]
                    'ID3' => [
                            '0.17',
                            ]
                    'ID4' => [
                            '0.22',
                            ]
                    ]
            }
    };

打印输出是这样的:

date,time,ARRAY(0x7f91c1030f60),ARRAY(0x7f91c1030f60),ARRAY(0x7f91c1030f60),ARRAY(0x7f91c1030f60)

抱歉,到目前为止的示例一直在解释上造成问题。输入文件中有很多多余的数据和文本,我只包含了我试图提取和排序的部分的高度简化版本。

【问题讨论】:

  • 你的描述很不清楚。整个文件是否只有一个Subject?为什么要复制四次?哪些字段可以用作键?例如字符串Data1 每次都一样吗? Date 是实际日期还是字符串 'Date'?如果可能的话,查看一些真实数据会有所帮助。
  • “主题”是数据文件中该行的第一项。我当前的脚本使用它来识别这是包含 ID 列表的行。 “日期”和“时间”是示例中实际日期和时间戳的占位符。四种数据类型中的每一种的字符串在每个数据块中都是相同的,正如上面使用占位符“Data1”到“Data4”所呈现的那样。上面的主题 ID 由“ID1”、“ID2”等表示 - 为清楚起见,将对其进行编辑。

标签: perl sorting hash


【解决方案1】:

这个程序将所有数据读入一个散列,并以您需要的转换状态再现它。这很好,除非数据非常庞大并且不容易放入您可用的内存中,在这种情况下您将需要不同的解决方案。

程序需要输入文件的名称作为命令行参数,如果没有提供则默认为data.csv。它将$/ 设置为空字符串以启用Perl 的“段落模式”输入,其中数据被读取到下一个空白行或文件末尾。这意味着一次读取给定时间间隔的所有数据,并且在处理之前必须进一步拆分为单独的行。

use strict;
use warnings;

my ($subject, @ids);
my @sort_order;
my (%data, @keys);

my ($file) = @ARGV;
$file //= 'data.csv';

open my $fh, '<', $file or die qq{Unable to open file "$file" for reading: $!};
local $/ = '';
while (<$fh>) {

  my @rows = split /\n/;

  unless ($subject) {
    ($subject, @ids) = split /,/, $rows[1];
    @sort_order = sort { $ids[$a] cmp $ids[$b] } 0 .. $#ids;
    next;
  }

  my ($date, $time) = split /,/, shift @rows;
  for (@rows) {
    my ($id, @fields) = split /,/;
    push @keys, $id unless exists $data{$id};
    push @{ $data{$id} }, [$date, $time, @fields[@sort_order]];
  }
}

for my $key (@keys) {
  print join(',', $key, $subject, @ids[@sort_order]), "\n";
  print join(',', @$_), "\n" for @{ $data{$key} };
  print "\n";
}

输出

Data1,Subject,ID#,ID1,ID2,[...]
Date,Time1,aa#,aa1,aa2,[...]
Date,Time2,ab#,ab1,ab2,[...]

Data2,Subject,ID#,ID1,ID2,[...]
Date,Time1,ba#,ba1,ba2,[...]
Date,Time2,bb#,bb1,bb2,[...]

Data3,Subject,ID#,ID1,ID2,[...]
Date,Time1,ca#,ca1,ca2,[...]
Date,Time2,cb#,cb1,cb2,[...]

Data4,Subject,ID#,ID1,ID2,[...]
Date,Time1,da#,da1,da2,[...]
Date,Time2,db#,db1,db2,[...]

【讨论】:

  • 我将以上内容改编成我的脚本(目前逐行读取)。输出和想要的一样,而且比我一直做的简单。但是输出与我当前的脚本完全相同:没有排序。如果我更改主题 ID 的顺序(例如,将 ID1 移到末尾,在 ID# 之后),主题行会更改,但数据不会重新排序。
  • 再一次,你没有给我任何工作。什么 ID1ID2 等。它们是出现在数据中的文字字符串还是真实 ID 的占位符。如果是后者,那么 ID 采用什么形式,您希望它们如何排序?
  • ID(ID1、ID2 等)是字母数字,由我以外的其他人输入,不受我控制。它们是运行测试的人用来识别主题的任何东西。排序旨在使用字母数字(排序 {$a cmp $b})。请参阅下面的扩展答案。
  • @dr.nixon:我已经修改了我的答案以满足您的新要求。它与您提供的数据的唯一区别是ID# 和相应的数据被排序到第一列。它应该适用于您的真实文件。
  • 感谢您的帮助。在您的输入和一些时间测试之间,我已经能够让它工作;非常感谢!
【解决方案2】:

您是否考虑过将数据放入 SQL 数据库中? [例如。 “无服务器”SQLite]

这可能有点矫枉过正,但它应该为您提供更好的大数据集灵活性。

【讨论】:

  • 目标是将脚本打包成一个独立的应用程序。我已经为我们使用的其他四个数据收集系统完成了这项工作。在数据库中设置它可以防止这种情况发生。
  • DB_File 包提供的“btree (file) as hash”可以接受吗?它会给你按键排序的哈希。
猜你喜欢
  • 2023-03-27
  • 2015-02-23
  • 2011-01-01
  • 2011-09-05
  • 2020-04-17
  • 2013-03-07
  • 2015-10-06
  • 2016-05-08
  • 1970-01-01
相关资源
最近更新 更多