在 bash 中对两个数组的数字范围进行排序

在 bash 中对两个数组的数字范围进行排序

我有两个数组,它们的元素数量与我要操作的元素数量相同。它们从文件读入两个数组(奇数行进入数组 1,偶数行进入数组 2):

arr1=("1" "1" "3" "2" "4" "7" "7" "7" "1" "2" "3" "3" "3" "3" "7" "5")
arr2=("4" "1" "3" "5" "7" "1" "2" "3" "2" "9" "2" "6" "8" "9" "4" "6")

该数据是指两个数组中同一位置的季数和集数。因此数组 1 ( arr1) 是季节,数组 2 ( arr2) 是剧集,它们按元素编号对齐。所以${arr1[0]}对应于${arr2[0]}

我想做的就是对它们进行排序,以便它们首先按季节排序,然后按集排序。所以原始数组(带有注释哪个项目是哪个元素):

       1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
arr1=("1" "1" "3" "2" "4" "7" "7" "7" "1" "2" "3" "3" "3" "3" "7" "5")
arr2=("4" "1" "3" "5" "7" "1" "2" "3" "2" "9" "2" "6" "8" "9" "4" "6")
       1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16

变成:

       2   9   1   4   10  12  3   12  13  14  5   16  6   7   8   15
arr1=("1" "1" "1" "2" "2" "3" "3" "3" "3" "3" "4" "5" "7" "7" "7" "7")
arr2=("1" "2" "4" "5" "9" "2" "3" "6" "8" "9" "7" "6" "1" "2" "3" "4")
       2   9   1   4   10  12  3   12  13  14  5   16  6   7   8   15

可能的想法:

  1. i对于中的每一项${arr1[@]},将 的相应元素写入${arr2[n]}文件中。然后该文件就可以sort在其上运行。
n="0"
for i in "${arr1[@]}"; do
    echo "${arr2[${n}]}" >> "${i}.txt"
    (( n++ ))
done

但我想尽量避免涉及磁盘写入(sort如果不需要的话)。

  1. 将数据排序到某种单独的数组中?每个季节都有自己的数组,然后可以使用类似的方法对数组进行排序sort -n "${season1Arr[@]}"- 但我不知道这是如何完成的。

  2. 改变数据的处理方式?我无法更改输入文件,但可以更改其处理方式。也许不是根据偶数/奇数行号将行读入两个数组,而是可以通过其他方式管理它们?

为了兼容性,我试图使其尽可能保持纯 bash,但我知道很可能需要使用外部程序。欣赏任何想法。

答案1

这是我在评论中讨论的一个示例,使用 perl 和关联数组(哈希),但使用版本排序而不是我提到的自然排序(简单的数字或字母数字排序,例如 1.10 之前) 1.2,这对于“series.episode”来说显然是错误的):

#!/usr/bin/perl

use strict;

use Sort::Versions;

my %data;
my ($key, $series, $episode);

while (<>) {
  chomp; # remove trailing newline from input

  if ($. % 2 == 1) {
    $series = $_;
  } else {
    $episode = $_;
    $key = "$series.$episode";
    $data{$key} = 1;
  };
}

print join(", ", sort { versioncmp($a, $b) } keys %data), "\n";

这可以缩短一点 - 真正需要的唯一变量是%data哈希值和$series.我使用了$key$episode来使输入值的使用方式变得显而易见。顺便说一句,$_是默认的输入/值/迭代器。它在 perl 中有很多用途,如果未提供另一个变量,许多函数和语法元素都会使用它 - 在此脚本中,它是循环读取的当前行的值while (<>)。查看man perlvar并搜索“常规变量”。

#!/usr/bin/perl

use strict;
use Sort::Versions;

my (%data, $series);

while (<>) {
  chomp; 

  if ($. % 2 == 1) {
    $series = $_;
  } else {
    $data{"$series.$_"} = 1;
  };
}

print join(", ", sort { versioncmp($a, $b) } keys %data), "\n";

示例运行(两个版本产生相同的输出):

$ ./sort-series-episode.pl input.txt  
1.1, 1.2, 1.4, 1.15, 2.5, 2.9, 3.2, 3.3, 3.6, 3.8, 3.9, 4.7, 5.6, 7.1, 7.2, 7.3, 7.4

注:排序::版本module 不是核心 perl 模块,需要通过cpan发行版包单独安装 - 例如在 Debian 中,apt-get install libsort-versions-perl

答案2

虽然perl对于此类任务来说要好得多,但几乎可以在纯 bash 中完成它(对于外部来说是安全的sort):

#!/bin/bash
arr1=( "1" "1" "3" "2" "4" "7" "7" "7" "1" "2" "3" "3" "3" "3" "7" "5" )
arr2=( "4" "1" "3" "5" "7" "1" "2" "3" "2" "9" "2" "6" "8" "9" "4" "6" )

# make an array with strings from both initial arrays:
arr_length=${#arr1[@]}
for ((i=0; i< $arr_length; i++)); do
   arr_combined[$i]="${arr1[$i]}!${arr2[$i]}"
done

# sort the combined strings
arr_sorted=( $(printf "%s\n" "${arr_combined[@]}" | sort) )

# split the elements of sorted array back into two arrays
for pair in ${arr_sorted[@]} ; do
    arr1n+="${pair%!*} "
    arr2n+="${pair#*!} "
done

# print the results
printf "%s\n" "${arr1n[@]}"
printf "%s\n" "${arr2n[@]}"

答案3

如果您首先对原始文件中的数字进行排序,然后填充数组,则可以使生活变得更加轻松。我假设你的原始文件包含这样的内容:

$ cat file1
1 1 3 2 4 7 7 7 1 2 3 3 3 3 7 5
4 1 3 5 7 1 2 3 2 9 2 6 8 9 4 6

所以这个命令:

transpose file1 | sort | transpose

会产生你想要的:

1 1 1 2 2 3 3 3 3 3 4 5 7 7 7 7
1 2 4 5 9 2 3 6 8 9 7 6 1 2 3 4

转置函数非常简单。你可以自己写。这是一个例子:

transpose () 
{ 
    awk '{ for (i=1; i<=NF; i++) a[i]= (i in a?a[i] OFS :"") $i; } 
    END{ for (i=1; i<=NF; i++) print a[i] }' $1
}

答案4

根据您的描述,以下可能是原始输入数据:

1
4
1
1
3
3
2
5
4
7
7
1
7
2
7
3
1
2
2
9
3
2
3
6
3
8
3
9
7
4
5
6

要以您想要的方式处理此问题:

paste - - <file | sort -k 1,1n -k 2,2n

首先将数据转换为两个制表符分隔的列,第一列是季节,第二列是剧集。

这些行在第一列上按数字排序,任何具有相同第一列的行都在第二列上按数字排序。

使用上面显示的输入,将产生:

1       1
1       2
1       4
2       5
2       9
3       2
3       3
3       6
3       8
3       9
4       7
5       6
7       1
7       2
7       3
7       4

相关内容