【问题标题】:Split file by equal parts based on count根据计数将文件分成相等的部分
【发布时间】:2019-12-26 06:00:10
【问题描述】:

我有一个包含节点列表的文件 (node_list.txt)。

nod_1
nod_2
nod_3
nod_4
nod_5

我有主机 IP 地址列表(这个计数可能会有所不同),需要将 node_list 分成相等数量的部分,并将这些拆分的节点文件发送到每个主机。 host_ip1 host_ip2 host_ip3

文件中节点的划分基于可用的 host_ip 数量。

在我的示例中,我应该得到:

node_list_file_1.txt
nod_1
nod_2

node_list_file_2.txt
nod_3
nod_4

node_list_file_3.txt
nod_5

我的代码如下所示:

print Dumper(\@list_of_hosts);

my $node_file = "node_list.txt";
open(NODE_FILE, "< $node_file") or die "can't open $node_file: $!";
my $count;
$count += tr/\n/\n/ while sysread(NODE_FILE, $_, 2 ** 16);
print "COUNT:$count\n";

my $res = $count / scalar @list_of_ips;

$res 我正在计算每个文件应该有多少行。但是如何将其放入文件中。

【问题讨论】:

  • 打开输出文件,将句柄存储在一个数组中。单步执行输入文件,根据数组将每一行写入适当的文件。继续,直到完成。请注意,您不需要知道输入文件有多大(它包含多少行);你只需要知道你想要多少个输出文件。
  • 另外,您应该避免使用老式的NODE_FILE 文件句柄样式并使用词法范围的文件句柄:open my $fh, "&lt;", $node_file or die;
  • 要检查当要写入的文件数不均分行数时需要做什么:对于 10 行分成 3 个文件,您是否需要每个文件的行数为 4-4-2 或4-3-3 ?
  • @zdim,或者换一种说法,如果有 5 台主机和 26 个节点,是 6-5-5-5-5 还是 6-6-6-6-2? (就个人而言,我不知道后者如何接近 OP 要求的“平等”。但我现在会给你怀疑的好处)
  • @ikegami 是的,一个更好的例子。虽然他们确实说“零件数量相等”(我一开始错过了),但这对于规范来说有点薄,所以我要求确认。

标签: file perl count


【解决方案1】:

这会拆分行,以便除最后一个文件之外的每个文件都接收最大相等的数量,从而最后一个获得余数。所以用 10 行来分割 3 个文件,它们会变成 4-4-2。

use warnings;
use strict;
use feature 'say';
use autodie qw(open);

my @lines = <>;
my $num_files = 3;
my $lines_per_file = int @lines/$num_files;
$lines_per_file += 1  if @lines % $num_files;

my @chunks;
push @chunks, [ splice @lines, 0, $lines_per_file ] while @lines;

my @fhs_out = map { open my $fh, ">fout_$_.txt"; $fh } 1..$num_files;

for my $i (0..$#chunks) { 
    print {$fhs_out[$i]} $_ for @{$chunks[$i]};
};

注意事项

  • &lt;&gt; 从命令行提交的文件中读取所有行

  • 如果要写入的文件数不能平均分割它们之间的行数,我们需要在每个文件中多写一行(最后一个接收剩余部分)

    李>
  • 带有行的数组依次为splice-ed,以便生成行块,每个行进入一个文件,因此最终清空

  • 我打开所有需要的输出文件并将文件句柄存储到一个数组中,以便以后方便地将行块写入它们的文件。这绝不是必要的,因为可以遍历 @chunks 并打开一个文件并为每组(“块”)行写入该文件

  • 当写入文件句柄时,需要从一个比基本标量更复杂的表达式求值,我们必须在块中包含它,例如{ $fhs_out[$i] }。来自print

    如果您将句柄存储在数组或散列中,或者一般来说,当您使用比裸字句柄或普通的无下标标量变量更复杂的表达式来检索它时,您将不得不使用返回的块文件句柄值,[...]

    请参阅this post 了解其他方式和更多讨论。


如果这种情况下线的分布必须是4-3-3,所以尽量平均分割,上面的代码需要修改成

my $lines_per_file = int @lines/$num_files;
my $extra = @lines % $num_files;

my @chunks;
push @chunks,
     [ splice @lines, 0, $lines_per_file + ( $extra-- > 0 ? 1 : 0 ) ] 
         while @lines;

其余的都一样。

【讨论】:

    【解决方案2】:
    my $num_buckets = 3;
    
    my @lines = <>;
    
    my $per_bucket = int( @lines / $num_buckets );
    my $num_extras =      @lines % $num_buckets;
    
    for my $bucket_num (0..$num_buckets-1) {
       my $num_lines = $per_bucket;
       if ($num_extras) {
          ++$num_lines;
          --$num_extras;
       }
    
       my $qfn = "node_list_file_${bucket_num}.txt";
       open(my $fh, '>', $qfn)
          or die("Can't create \"$qfn\": $!\n");
    
       $fh->print(splice(@lines, 0, $num_lines));
    }
    

    $per_bucket 是每个文件的节点数。
    $num_extras 是有一个额外节点的文件数。

    请注意,$num_lines 的计算可以简化为以下内容(为了便于阅读,我避免使用):

    my $num_lines = $per_bucket + ( $num_extras-- > 0 );
    

    上面将整个文件加载到内存中。以下是一个替代解决方案:

    my $num_buckets = 3;
    
    my @fhs;
    for my $bucket_num (1..$num_buckets) {
       my $qfn = "node_list_file_${bucket_num}.txt";
       open(my $fh, '>', $qfn)
          or die("Can't create \"$qfn\": $!\n");
    
       push @fhs, $fh;
    }
    
    $fhs[ ( $. - 1 ) % @fhs ]->print($_) while <>;
    

    但是,当它执行请求的任务时,输出与指定的不完全相同:

    node_list_file_1.txt
    --------------------
    nod_1
    nod_4
    
    node_list_file_2.txt
    --------------------
    nod_2
    nod_5
    
    node_list_file_3.txt
    --------------------
    nod_3
    

    【讨论】:

    • 谢谢@ikegami。因此,在您的第一个解决方案中,我应该将所有输入文件行内容存储到@lines。在第二个解决方案中,您能否详细说明此条件的含义 $fhs[ ( $. - 1 ) % @fhs ]-&gt;print($_) while &lt;&gt;; 以及如何获取输入文件?
    • &lt;&gt;&lt;ARGV&gt;的缩写,是readline(ARGV)的缩写,ARGV是一个特殊的句柄,从路径在@ARGV的文件中读取,或者从@ 987654335@ 如果@ARGV 为空。简而言之,它就像几乎所有的 unix 程序(例如,catgrep 等)一样,可以随意使用不同的句柄。
    • ... 和 while &lt;&gt;while defined($_ = &lt;&gt;) 的缩写
    • 在这两种情况下,如果我们提到$num_buckets = 3;,那么它显然会创建3个文件。但是,如果我在 node_list.txt 中只有 2 行数据,比如 nod_1 node_2 并且它应该只创建 2 个文件呢?由于它也在创建第三个文件,其中包含空数据。如何避免?
    • 使用第一种方法,只需检查要打印的行数是否为零。 /// 使用第二个,它有点复杂。您必须延迟创建文件,直到需要它。你会在循环中使用类似$fh[$i] //= do { ... }; 的东西。
    【解决方案3】:

    也许下面的代码符合你的要求

    use strict;
    use warnings;
    
    use feature 'say';
    
    use Data::Dumper;
    
    my $debug = 1;                          # $debug = 1 -- debug mode
    
    my $node_file = "node_list.txt";        # input filename
    
    my @hosts = qw(host_ip1 host_ip2 host_ip3); # Hosts to distribute between
    
    my $num_hosts = @hosts;                 # Number of hosts to distribute between
    
    open(my $fh, "<", $node_file) 
            or die "can't open $node_file: $!";
    
    my @nodes =  <$fh>;                     # read input lines into @nodes array
    
    chomp @nodes;                           # trim newline from each element @nodes array
    
    close $fh;
    
    print Dumper(\@nodes) if $debug;        # print @nodes content in debug mode
    
    my $count = @nodes;                     # count number nodes in @nodes array
    
    print "COUNT: $count lines in the input file\n";
    
    # How many lines store in out files
    my $lines_in_file = int($count/$num_hosts + 0.5);
    
    my $lines_out   = $lines_in_file;       # how many line to output per file
    my $file_index  = 1;                    # index for output filenames
    my $filename    = "node_list_file_${file_index}.txt";
    
    # open OUT file
    open(my $out, ">", $filename)
            or die "Couldn't open $filename";
    
    foreach my $node_name (@nodes) {        # process each element of @nodes array
        say $out $node_name;                # store node in OUT file
    
        $lines_out--;                       # decrease number of left lines for output
    
        if( $lines_out == 0 ) {             # all lines per file stored
            close $out;                     # close file
    
            $lines_out = $lines_in_file;    # reinitialize number of lines for output
    
            $file_index++;                  # increase index for filename
            $filename = "node_list_file_${file_index}.txt";
    
            open($out, ">", $filename)      # open new OUT file
                or die "Couldn't open $filename";
        }
    }
    
    close $out;                             # close OUT file
    

    【讨论】:

    • 谢谢。如果输入节点文件中有奇数行怎么办?例如:如果我在输入文件中有 11 个节点,需要放入 6 个文件(每个文件 2 个),最后一个应该包含 1 个节点。这里可以吗?
    • 请避免不必要地使用全局变量 (NODE_FILE, OUT)
    • 请避免不必要地使用 2-arg open
    • 不用chomp
    • 更重要的是,它仅在输入文件中恰好有 5 或 6 个节点时才有效。 (4 执行 2/2/0 而不是 2/1/1,7 执行 2/2/2 而不是 3/2/2。)
    猜你喜欢
    • 1970-01-01
    • 2019-05-06
    • 1970-01-01
    • 2015-05-23
    • 1970-01-01
    • 2020-09-04
    • 2021-07-09
    • 2011-07-19
    相关资源
    最近更新 更多