重复数据的连续排序,但行数不同



我有一个以下格式的数据:

2
1
A    11.364500   48.395199   40.160500         5
B    35.067699   30.944700   24.155300         4
4
2
A    26.274099   38.533298   32.624298         4
B    36.459000   29.662701   17.806299         5
A    15.850300   28.130899   24.753901         4
A    32.098000   33.665699   20.372499         4
5
3
A    17.501801   44.279202    8.005670         5
B    35.853001   43.095901   17.402901         4
B     1.326100   17.127600   39.600300         4
A     9.837760   41.103199   13.062300         5
B    31.686800   44.997501   16.619499         4
3
4
B    31.274700    8.726580   25.267599         4
A    19.032400   41.384701   19.456301         5
B    19.441900   24.286400    6.961680         4
1
5
B    48.973999   15.508400    5.099710         4
6
6
A    42.586601   21.343800   23.280701         5
B    30.145800   13.256200   30.713800         4
B    29.186001   44.353699    9.057160         4
A    39.311199   27.371201   35.473999         5
B    31.437799   30.415001   37.454399         4
B    48.501999    1.277730   21.900600         4
...

数据由多个重复的数据块组成。每个数据块具有相同的格式,如下所示:

First line = Number of lines of data block
Second line = ID of data block
From third line = Actual data lines (with number of lines designated in the first line)

例如,如果我们看到第一个数据块:

2 (=> This data block has 2 number of lines) 
1 (=> This data block's ID is 1 (very first data block)) 
A    11.364500   48.395199   40.160500         5
B    35.067699   30.944700   24.155300         4
(Third and fourth lines of the data block are actual data. 
The integer of the first line, the number of lines for this data block, is 2, so the actual data consist of 2 lines. 
All other data blocks follow the same format. ) 
  1. 前两行是每个重复数据块的标头。第一行表示数据块的行数,第二行是数据块的ID。
  2. 然后,真实数据来自每个数据块的第三行。正如我所解释的,"真实数据"的行数在第一行中被指定为整数。
  3. 因此,每个数据块的行总数将为 number_of_lines+2。在此示例中,数据块 1 的总行数为 4,数据块 2 的成本为 6 行...

此格式重复 100,000 次。换句话说,我在单个文本文件中有 100,000 个数据块,就像此示例一样。我可以提供数据块的总数。

我希望做的是按第四列对每个数据块的"实际数据行"进行排序,并以与原始数据文件相同的格式打印出数据。我希望从上面的示例中实现的内容如下所示:

2
1
A    11.364500   48.395199   40.160500         5
B    35.067699   30.944700   24.155300         4
4
2
A    26.274099   38.533298   32.624298         4
A    15.850300   28.130899   24.753901         4
A    32.098000   33.665699   20.372499         4
B    36.459000   29.662701   17.806299         5
5
3
B     1.326100   17.127600   39.600300         4
B    35.853001   43.095901   17.402901         4
B    31.686800   44.997501   16.619499         4
A     9.837760   41.103199   13.062300         5
A    17.501801   44.279202    8.005670         5
3
4
B    31.274700    8.726580   25.267599         4
A    19.032400   41.384701   19.456301         5
B    19.441900   24.286400    6.961680         4
1
5
B    48.973999   15.508400    5.099710         4
6
6
B    31.437799   30.415001   37.454399         4
A    39.311199   27.371201   35.473999         5
B    30.145800   13.256200   30.713800         4
A    42.586601   21.343800   23.280701         5
B    48.501999    1.277730   21.900600         4
B    29.186001   44.353699    9.057160         4
...

我知道在 Linux 中使用第 4 列对数据进行排序的典型命令,即:

sort -gk4 data.txt > data_4sort.txt

但是,在这种情况下,我需要对每个数据块执行排序。另外,每个数据块没有统一的行数,但每个数据块的数据行数各不相同。

我真的不知道我该如何解决这个问题。我正在考虑使用带有 for 循环和/或 awk、sed、split 等的 shell 脚本,以及排序命令。但我什至不确定如何使用 for 循环来解决这个问题,或者 for 循环和 shell 脚本是否真的需要执行这种逐块排序。

这可以使用awksort的组合来完成。您可以使用awk作为非常简单的状态机来驱动整个过程,它遵循以下规则:

  • 有两种状态,指示您正在处理的线型组。
  • 线
  • 类型为短线(状态 0,一个字段)和长线(状态 1,更多字段)。
  • 根据当前状态和正在处理的当前行,会发生不同的操作。
  • 需要排序的项目将临时写入排序文件,并在需要时进行排序。

操作基本上是针对每一行的。

  • 简而言之:
    • 对于一行短行,只需打印即可。
    • 否则(一长行),只用该行创建新的排序文件,将状态设置为 long。
  • 否则(在长状态下):
    • 如果行很长,只需将其附加到排序文件即可。
    • 否则(短行),对文件进行排序,打印短行,将状态设置为短。

最后,如果需要,可以排序/打印/删除排序文件(因为在最后一个块之后不会有从长行到短行的最终过渡,此操作会处理这个问题)。

该处理按如下方式实现,这是一个调用awk来完成工作的 shell 脚本:

awk '
function sort() {
close(tmp_file)
system("sort -rnk4 "tmp_file"; rm -rf "tmp_file)
}
BEGIN { tmp_file = "/tmp/tmp_file" }
state == 0 && NF == 1 { print ; next }
state == 0 && NF != 1 { print >tmp_file ; state = 1 ; next }
state == 1 && NF != 1 { print >>tmp_file ; next }
state == 1 && NF == 1 { sort() ; print ; state = 0 ; next }
END { if (state == 1) { sort() } }
' input_file

您也可以在没有具有足够高级的awk变体的临时文件的情况下执行此操作,该文件允许管道:

awk 'NF > 1 { print | "sort -rnk4"; next }
{ close("sort -rnk4"); print }
' input_file

这样做的优点是一个更简单、更短的awk程序,它隐式地执行了上一个脚本中的大部分内容。

在输入文件上运行这些脚本中的任何一个都会根据请求生成在第四列(最终浮点数)上排序的每个单独分组:

2
1
A    11.364500   48.395199   40.160500         5
B    35.067699   30.944700   24.155300         4
4
2
A    26.274099   38.533298   32.624298         4
A    15.850300   28.130899   24.753901         4
A    32.098000   33.665699   20.372499         4
B    36.459000   29.662701   17.806299         5
5
3
B     1.326100   17.127600   39.600300         4
B    35.853001   43.095901   17.402901         4
B    31.686800   44.997501   16.619499         4
A     9.837760   41.103199   13.062300         5
A    17.501801   44.279202    8.005670         5
3
4
B    31.274700    8.726580   25.267599         4
A    19.032400   41.384701   19.456301         5
B    19.441900   24.286400    6.961680         4
1
5
B    48.973999   15.508400    5.099710         4
6
6
B    31.437799   30.415001   37.454399         4
A    39.311199   27.371201   35.473999         5
B    30.145800   13.256200   30.713800         4
A    42.586601   21.343800   23.280701         5
B    48.501999    1.277730   21.900600         4
B    29.186001   44.353699    9.057160         4

使用 awk 准备要排序的文本,然后将其通过管道传输到 1 次调用sort,不生成子 shell,也不创建临时文件:

$ cat tst.sh
#!/usr/bin/env bash
awk '
BEGIN { OFS="t"; recNr=1 }
NF < pNF { ++recNr }
{
print recNr, NF, NR, $0
pNF = NF
}
' "${@:--}" |
sort -k1,1n -k2,2n -k7,7nr -k3,3n |
cut -f4-

$ ./tst.sh file
2
1
A    11.364500   48.395199   40.160500         5
B    35.067699   30.944700   24.155300         4
4
2
A    26.274099   38.533298   32.624298         4
A    15.850300   28.130899   24.753901         4
A    32.098000   33.665699   20.372499         4
B    36.459000   29.662701   17.806299         5
5
3
B     1.326100   17.127600   39.600300         4
B    35.853001   43.095901   17.402901         4
B    31.686800   44.997501   16.619499         4
A     9.837760   41.103199   13.062300         5
A    17.501801   44.279202    8.005670         5
3
4
B    31.274700    8.726580   25.267599         4
A    19.032400   41.384701   19.456301         5
B    19.441900   24.286400    6.961680         4
1
5
B    48.973999   15.508400    5.099710         4
6
6
B    31.437799   30.415001   37.454399         4
A    39.311199   27.371201   35.473999         5
B    30.145800   13.256200   30.713800         4
A    42.586601   21.343800   23.280701         5
B    48.501999    1.277730   21.900600         4
B    29.186001   44.353699    9.057160         4

以下是上述 OPs 输入文件执行速度的第三次运行时序差异:

$ time ./tst.sh file >/dev/null
real    0m0.105s
user    0m0.030s
sys     0m0.091s

当前的awk答案为每个要排序的输入块生成一个子shell:

@IceCreamTucan的回答是:

$ cat tst2.sh
#!/usr/bin/env bash
awk '{
if(NF > 1){
print | "sort -k4,4nr"
}
else{
close("sort -k4,4nr")
print
}
}' "${@:--}"

$ time ./tst2.sh file >/dev/null
real    0m0.405s
user    0m0.120s
sys     0m0.121s

@paxdiablo的回答是:

$ cat tst3.sh
#!/usr/bin/env bash
awk '
function sort() {
close(tmp_file)
system("sort -rnk4 "tmp_file"; rm -rf "tmp_file)
}
BEGIN { tmp_file = "/tmp/tmp_file" }
state == 0 && NF == 1 { print ; next }
state == 0 && NF != 1 { print >tmp_file ; state = 1 ; next }
state == 1 && NF != 1 { print >>tmp_file ; next }
state == 1 && NF == 1 { sort() ; print ; state = 0 ; next }
END { if (state == 1) { sort() } }
' "${@:--}"

$ time ./tst3.sh file >/dev/null
real    0m0.804s
user    0m0.060s
sys     0m0.396s

一个选项,用于通过管道传输到命令,如下所示,而无需显式创建临时文件。

awk '
BEGIN {
cmd = "sort -k4,4nr"
}
{
if(NF > 1){
print | cmd
}
else{
close(cmd)
print
}
}' input_file

注意:更改为数字排序并将cmd移动到变量,这要归功于@Ed莫顿的有用提示

输出:

2
1
A    11.364500   48.395199   40.160500         5
B    35.067699   30.944700   24.155300         4
4
2
A    26.274099   38.533298   32.624298         4
A    15.850300   28.130899   24.753901         4
A    32.098000   33.665699   20.372499         4
B    36.459000   29.662701   17.806299         5
5
3
B     1.326100   17.127600   39.600300         4
B    35.853001   43.095901   17.402901         4
B    31.686800   44.997501   16.619499         4
A     9.837760   41.103199   13.062300         5
A    17.501801   44.279202    8.005670         5
3
4
B    31.274700    8.726580   25.267599         4
A    19.032400   41.384701   19.456301         5
B    19.441900   24.286400    6.961680         4
1
5
B    48.973999   15.508400    5.099710         4
6
6
B    31.437799   30.415001   37.454399         4
A    39.311199   27.371201   35.473999         5
B    30.145800   13.256200   30.713800         4
A    42.586601   21.343800   23.280701         5
B    48.501999    1.277730   21.900600         4
B    29.186001   44.353699    9.057160         4

我知道,老问题但是...另一个想法,使用更新的gawk能力性能更高......


一个想法使用gawk及其预定义的阵列扫描顺序(即,消除对外部sort的需求)...

awk '
BEGIN { delete arr }                            # hack to pre-declare variable arr as an array
function sort_block() {
if ( length(arr) > 0 ) {                    # if array is not empty then ...
PROCINFO["sorted_in"]="@ind_num_desc"    # desginate sorting by index/descending and then ...
for (i in arr)                           # loop through array ...
print arr[i]                         # printing arry entries
}
delete arr                                  # clear array
}
NF==1 { sort_block()
print
}
NF>1  { arr[$4]=$0
next
}
END   { sort_block() }                          # flush anything still in array
' block.dat

针对 OPs 示例数据集运行此命令会生成:

2
1
A    11.364500   48.395199   40.160500         5
B    35.067699   30.944700   24.155300         4
4
2
A    26.274099   38.533298   32.624298         4
A    15.850300   28.130899   24.753901         4
A    32.098000   33.665699   20.372499         4
B    36.459000   29.662701   17.806299         5
5
3
B     1.326100   17.127600   39.600300         4
B    35.853001   43.095901   17.402901         4
B    31.686800   44.997501   16.619499         4
A     9.837760   41.103199   13.062300         5
A    17.501801   44.279202    8.005670         5
3
4
B    31.274700    8.726580   25.267599         4
A    19.032400   41.384701   19.456301         5
B    19.441900   24.286400    6.961680         4
1
5
B    48.973999   15.508400    5.099710         4
6
6
B    31.437799   30.415001   37.454399         4
A    39.311199   27.371201   35.473999         5
B    30.145800   13.256200   30.713800         4
A    42.586601   21.343800   23.280701         5
B    48.501999    1.277730   21.900600         4
B    29.186001   44.353699    9.057160         4

在笔记本电脑级 i7 上的 VM 内的cygwin环境中重复运行此代码(即,这绝不是一个快速系统)可提供以下最佳时机:

real    0m0.026s
user    0m0.000s
sys     0m0.015s

最新更新