【问题标题】:Count unique values in each column of pipe delimited text file using Perl使用 Perl 计算管道分隔文本文件的每一列中的唯一值
【发布时间】:2015-07-16 04:04:30
【问题描述】:

我的 Solaris 服务器主目录中有两个示例管道分隔文件,如下所示:

file1.txt:

ticker|sedol|cusip|exchnage  
ibm   |ibm_1|ib   |london    
hcl   |     |hcl_02|london    
hp    |hp_01|hpm  |newyork
      |lkp   |lk_2 |newyork

file2.txt:

exchnage|ticker|sedol|cusip  
london  |goo   |goo_1|gm  
london  |hcl   |     |hcl_02  
newyork |hp    |hp_01|hpm  
newyork |tyl   |     |ty_2

我需要一个结果文件,我们将在其中按交换对 ticker、sedol、cusip 的唯一计数进行分组:

预期结果文件如下:

exchnage|ticker|sedol|cusip  
london  |3     |2    |3  
newyork |3     |2    |2

我知道使用 SQL 很容易,但遗憾的是不能涉及数据库。每个文件大小可能高达 300-400 MB。我们最好使用 Perl 来完成它,或者如果非常困难,那么使用 Python。主要环境是 Solaris,但我们也可以在 Unix 服务器上试用。现在增加的需要是“交换”列的位置可以在两个文件中的任何位置。

【问题讨论】:

    标签: python perl unix solaris-10


    【解决方案1】:

    不是最优雅的,但我为您的新要求设计的最快的:

    import glob
    import os
    import sys
    
    path = "/tmp"
    file_mask = "file*.txt"
    results = {}
    
    for file in glob.glob(os.path.join(path, file_mask)):
        column_names = {}
        exchange_col = None
        with open(file, "r") as f:
            for line_num, line in enumerate(f.xreadlines()):
                # process header
                if not line_num:
                    line_parsed = line.strip().split("|")
                    for column_num, column in enumerate(line_parsed):
                        if column.strip() == "exchnage":
                            exchange_col = column_num
                        else:
                            column_names[column_num] = column.strip()
                    if exchange_col is None:
                        print "Can't find exchnage field"
                        sys.exit(1)
                    continue
                line_parsed = line.strip().split("|")
                if len(line_parsed) != len(column_names) + 1:
                    continue
                # prepare empty structure for excahnge, if not added yet
                if not line_parsed[exchange_col].strip() in results:
                    results[line_parsed[exchange_col].strip()] = {column_name:set() for column_name in column_names.values()}
                # add uniq items to exchange
                for column_num, column in enumerate(line_parsed):
                    column_val = column.strip()
                    # add only non empty values
                    if column_val and column_num != exchange_col:  
                        results[line_parsed[exchange_col].strip()][column_names[column_num]].add(column_val)
    
    column_names = column_names.values()
    print "exchnage|" + "|".join("%8s" %c for c in column_names)
    for exchange, values in results.iteritems():
        print "%8s|" % exchange + "|".join("%8s" % str(len(values[column])) for column in column_names)
    

    程序输出(作为输入使用了具有不同列顺序的新文件):

    $ python parser.py
    exchnage|  ticker|   sedol|   cusip
     newyork|       2|       2|       3
      london|       3|       2|       3
    

    【讨论】:

    • @purnendumaity,请注意它与您的示例中的文件结构匹配,并且操作列号,而不是名称。因此,如果文件中的列数或列顺序不同,它可能不起作用。
    • @Dieselist..你是对的。现在“exchange”列在第 30 列,所以我应该修改如下: for i in xrange(len(line_parsed[30:]))] # 添加 uniq 项以交换 column_num, column in enumerate(line_parsed[30:]):
    • @purnendumaity,你能提供新的格式吗?会是持久性(列号可以硬编码)吗?我们应该按 no 列还是按名称操作?
    • 你非常有帮助...我们将按列进行操作,如果我们使用 pandas 然后直接 SQL 根...可能但随后涉及额外的包安装等事情...我不'认为他们不会同意
    • @purnendumaity,我已经用新代码更新了我的答案,以满足您的新要求。现在它会自动计算文件中的列顺序。
    【解决方案2】:
    #!/usr/bin/perl
    
    use strict;
    use warnings;
    use Data::Dumper;
    
    my %unique_ticker_count;
    my %unique_sedol_count;
    my %unique_cusip_count;
    
    my @headers = split( '|', <DATA> );
    print @headers;
    
    while ( my $line = <DATA> ) {
        my ( $exchange, $ticker, $sedol, $cusip ) = split( /\|/, $line );
        if ( $ticker =~ m/\w/ ) { $unique_ticker_count{$exchange}{$ticker}++; }
        if ( $sedol =~ m/\w/ )  { $unique_sedol_count{$exchange}{$sedol}++; }
        if ( $cusip =~ m/\w/ )  { $unique_cusip_count{$exchange}{$cusip}++; }
    }
    
    print Dumper \%unique_ticker_count;
    
    foreach my $exchange ( keys %unique_ticker_count ) {
        print join( "|",
            $exchange,
            scalar keys %{ $unique_ticker_count{$exchange} } || 0,
            scalar keys %{ $unique_sedol_count{$exchange} }  || 0,
            scalar keys %{ $unique_cusip_count{$exchange} }  || 0,
            ),
            "\n";
    }
    
    __DATA__
    exchnage|ticker|sedol|cusip  
    london  |ibm   |ibm_1|ib  
    london  |hcl   |     |hcl_02  
    newyork |hp    |hp_01|hpm  
    newyork |lkp   |lk_2 |
    london  |goo   |goo_1|gm  
    london  |hcl   |     |hcl_02  
    newyork |hp    |hp_01|hpm  
    newyork |tyl   |     |ty_2
    

    将打印:

    newyork |3|2|2
    london  |3|2|3
    

    我将由您来处理文件打开和处理 - 这只是说明您可以采取的方法。

    【讨论】:

    • 这个 DATA 您是否将假设视为单个文件?我有两个文件..那么你建议合并它们吗?一个文件可能比另一个文件有更多的列然后合并工作吗? ...我只知道要检查/计算哪一列
    • DATA 是一种在 perl 中内联数据的方法,它使插图和发布代码更容易。你可以很容易地open ( my $input, "&lt;", $filename ) or die $!;然后是while ( &lt;$input&gt; ) { 。这将适用于正在处理的多个文件,因为整理发生在最后。 (请记住丢弃每个文件的标题行)
    • @Sobrique..根据新场景,我必须按列位置而不是名称工作
    【解决方案3】:

    我认为,首先您需要从文件中创建一个字典列表,例如: {'exchnage':'london','ticker':'ibm','sedol':'ibm_1','cusip':'ib'}。比您需要一个 for-each 语句来将所有值添加到列表中,但仅当值不是 None 时,空 & 值不在列表中。然后,您将获得列表中的所有唯一值,您需要对它们进行计数。
    您需要对文件中的所有列执行此操作。
    之后,您需要将其写入文件中。

    【讨论】:

    • 我更喜欢 R 编程,而很少使用 shell...Perl 对我来说是一个全新的世界。您能否发布一些代码以及我们需要使用哪个 Perl 包?如果你觉得它很大..你可以给我发邮件至 purnendumaity@rediffmail.com。我真的需要这个。
    • @purnendumaity 当你问这个问题时,你写了“python”。我看到了,你删除了它。我不知道,你怎么能用perl解决。这是问题的逻辑,大多数语言都可以实现。
    • 我没有删除 Python...根据上面的要求 Perl 是第一优先级,但 Python 解决方案也可以...我可以看到 Python 标记仍然存在于我的问题中。实际上在那个特定的系统中已经有很多东西在 Perl..希望你理解
    猜你喜欢
    • 2013-07-07
    • 2020-12-15
    • 1970-01-01
    • 2014-04-07
    • 1970-01-01
    • 2018-12-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多