删除重复项但保留序列

时间:2015-11-12 22:32:27

标签: python bash perl awk duplicates

如果输入文件是:

400102-25,6:50,90005002,1
400102-25,6:50,90005004,2
400102-25,7:00,90002109,3
400102-25,7:00,90002107,4
400102-25,7:05,90002111,5
400102-25,7:05,90002106,6
6004-10,13:05,90006017,1
6004-10,13:05,90006022,2
6004-10,13:20,90006030,3
6004-10,13:20,90006015,4
6004-10,13:20,90006019,5    
6004-10,13:30,90006034,6
6004-10,13:40,90006033,7
6004-10,13:40,90006002,8

有没有办法获得这个输出:

400102-25,6:50,90005002,1
400102-25,,90005004,2
400102-25,7:00,90002109,3
400102-25,,90002107,4
400102-25,,90002111,5
400102-25,7:05,90002106,6
6004-10,13:05,90006017,1
6004-10,,90006022,2
6004-10,13:20,90006030,3
6004-10,,90006015,4
6004-10,,90006019,5
6004-10,13:30,90006034,6
6004-10,,90006033,7
6004-10,13:40,90006002,8

我想删除字段2的重复项(逗号分隔)并用逗号替换,但保留每个序列的第一个和最后一个记录,由第4列确定。

我认为结果可以通过

部分获得
 awk '{FS=OFS=","} { if(!seen[$1 $2]++) print $0 ; else print $1,","$3, $4 } END{print}' input

但这并不能解决所有问题。有什么建议吗?

5 个答案:

答案 0 :(得分:2)

这个解决方案做了几个假设。对于从1到...的数字序列,第一个字段是相同的,即第一个字段对于每个序列是相同的宽度(字符数),并且第一个字段是按排序顺序。它不使用最后一个字段来确定记录组,而是通过第一个字段来确定。

#!/usr/bin/perl
use strict;
use warnings;

my %data;

while (<DATA>) {
    my ($key, @tmp) = split /,/;
    push @{ $data{$key} }, \@tmp;
}

for my $key (sort keys %data) {
    my $aref = $data{$key};
    my $end = $aref->[-1][0];

    for my $rec (reverse @$aref[1 .. $#$aref - 1]) {
        if ($rec->[0] eq $end) {
            $rec->[0] = ''; 
        }
        else {
            last;   
        }
    }
    my $beg = $aref->[0][0];

    for my $rec (@$aref[1 .. $#$aref - 1]) {
        if ($rec->[0] eq $beg) {
            $rec->[0] = ''; 
        }
        else {
            $beg = $rec->[0];
        }
    }
    for my $line (@$aref) {
        print join ",", $key, @$line;   
    }
}


__DATA__
400102-25,6:50,90005002,1
400102-25,6:50,90005004,2
400102-25,7:00,90002109,3
400102-25,7:00,90002107,4
400102-25,7:05,90002111,5
400102-25,7:05,90002106,6
600004-10,13:05,90006017,1
600004-10,13:05,90006022,2
600004-10,13:20,90006030,3
600004-10,13:20,90006015,4
600004-10,13:30,90006034,5
600004-10,13:40,90006033,6
600004-10,13:40,90006002,7

输出

400102-25,6:50,90005002,1
400102-25,,90005004,2
400102-25,7:00,90002109,3
400102-25,,90002107,4
400102-25,,90002111,5
400102-25,7:05,90002106,6
600004-10,13:05,90006017,1
600004-10,,90006022,2
600004-10,13:20,90006030,3
600004-10,,90006015,4
600004-10,13:30,90006034,5
600004-10,,90006033,6
600004-10,13:40,90006002,7

更新

为了适应您的新数据要求,我取出了哈希并改为使用数组(以保留输入文件的原始顺序)。请注意,用于运行程序的命令将类似于:

perl test.pl dat3.txt

其中test.pl是您的程序名称,dat3.txt是要处理的输入文件。

#!/usr/bin/perl
use strict;
use warnings;

my @data = [split /,/, <>];
my $i = $data[0][3];

while (<>) {
    my @temp = split /,/;
    if ($temp[3] == ++$i) {
        push @data, \@temp; 
    }
    else {
        process(@data);
        @data = \@temp;
        $i = $data[0][3];   
    }
}

process(@data);

sub process {
    my @data = @_;

    my $end = $data[-1][1];
    for my $rec (reverse @data[1 .. $#data - 1]) {
        if ($rec->[1] eq $end) {
            $rec->[1] = ''; 
        }
        else {
            last;   
        }
    }
    my $beg = $data[0][1];

    for my $rec (@data[1 .. $#data - 1]) {
        if ($rec->[1] eq $beg) {
            $rec->[1] = ''; 
        }
        else {
            $beg = $rec->[1];
        }
    }
    print map join(",", @$_), @data;
}

输出是:

400102-25,6:50,90005002,1
400102-25,,90005004,2
400102-25,7:00,90002109,3
400102-25,,90002107,4
400102-25,,90002111,5
400102-25,7:05,90002106,6
6004-10,13:05,90006017,1
6004-10,,90006022,2
6004-10,13:20,90006030,3
6004-10,,90006015,4
6004-10,,90006019,5
6004-10,13:30,90006034,6
6004-10,,90006033,7
6004-10,13:40,90006002,8

答案 1 :(得分:1)

awk救援

$ awk 'BEGIN{FS=OFS=","} 
            {if(a[$1]==$2) $2=""; 
             else a[$1]=$2}1' file       

400102-25,6:50,90005002,1
400102-25,,90005004,2
400102-25,7:00,90002109,3
400102-25,,90002107,4
400102-25,7:05,90002111,5
400102-25,,90002106,6
600004-10,13:05,90006017,1
600004-10,,90006022,2
600004-10,13:20,90006030,3
600004-10,,90006015,4
600004-10,13:30,90006034,5
600004-10,13:40,90006033,6
600004-10,,90006002,7

请注意,您的示例输出有时会删除重复项的第一个实例而不是第二个实例。这将保留第一个并删除重复项。

答案 2 :(得分:1)

<强>更新

awk 'BEGIN{FS=OFS=","}
function w(k){
    for(i in a){
        s=split(a[i],t)
        delete a[i]
    }
    for(i=1;i<=s;i+=4){
        if((k || $1!=t[1])&& i+3!=s){
            t[i+1]=""
        }else{
            t[i+5]=""
        }
        print t[i],t[i+1],t[i+2],t[i+3]
    }
}
{if($1$2 in a){a[$1$2]=a[$1$2]","$0}else{w();a[$1$2]=$0}}
END{w(1)}' file

400102-25,6:50,90005002,1
400102-25,,90005004,2
400102-25,7:00,90002109,3
400102-25,,90002107,4
400102-25,,90002111,5
400102-25,7:05,90002106,6
6004-10,13:05,90006017,1
6004-10,,90006022,2
6004-10,13:20,90006030,3
6004-10,,90006015,4
6004-10,,90006019,5
6004-10,13:30,90006034,6
6004-10,,90006033,7
6004-10,13:40,90006002,8

答案 3 :(得分:0)

对于lua-shell,请这样写:

wws$ `cat demo/7.lua
vim:open("demo/7.txt")
lnum_of_b=new()     --line count of block
lineid = 0
function collect()
    last_id = -1
    bid = 0     --block id
    for i = 0, vim.lmax do
        vim:Gn(i)
        :$
        if(vim:atoi() -1 ~= last_id) 
            bid = bid + 1
            lnum_of_b[bid] = 0;
        lnum_of_b[bid] = lnum_of_b[bid] + 1 
        last_id = vim:atoi()
    end
end
function do_block(lnum)
    prev_time = ""
    for i = 1, lnum do
        vim:Gn( lineid )
        :f,wvf,y:
        if( vim:clipboard() == prev_time)
            if i == lnum then vim:k()   end
            :^f,wvf,hx: 
        else prev_time = vim:clipboard();
        lineid = lineid + 1
    end
end

collect()
for i = 1, #blocks do   do_block( lnum_of_b[i] )    end

vim:print()

这是我机器上的演示:

wws$ source demo/7.lua
400102-25,6:50,90005002,1
400102-25,,90005004,2
400102-25,7:00,90002109,3
400102-25,,90002107,4
400102-25,,90002111,5
400102-25,7:05,90002106,6
600004-10,13:05,90006017,1
600004-10,,90006022,2
600004-10,13:20,90006030,3
600004-10,,90006015,4
600004-10,13:30,90006034,5
600004-10,,90006033,6
600004-10,13:40,90006002,7

我认为我没有弄错你的意思:你想要一个块的最后一行保持并转向删除超出它的重复行。是? 上面的脚本不是最好的,因为luashell远非完整,但它可以实现尴尬措施的目标。 这只是他的理念:你总能实现目标。

答案 4 :(得分:0)

一些导入点

awk '{FS=OFS=","} { if(!seen[$1 $2]++) 

.. 将FS=OFS=","替换为BEGIN{FS=OFS=","},或者可以在awk语句之外进行声明

从您的数组++中删除seen[$1 $2]++ ..此++为变量提供计数器值

下面的短一个衬垫也许可以起作用

awk -v FS=',' -v OFS=',' '{if(($1 in a) && (a[$1]==$2)){$2="";print}else{print;a[$1]=$2;}}'

输出

400102-25,6:50,90005002,1
400102-25,,90005004,2
400102-25,7:00,90002109,3
400102-25,,90002107,4
400102-25,7:05,90002111,5
400102-25,,90002106,6
600004-10,13:05,90006017,1
600004-10,,90006022,2
600004-10,13:20,90006030,3
600004-10,,90006015,4
600004-10,13:30,90006034,5
600004-10,13:40,90006033,6
600004-10,,90006002,7