4

我有 40,000 个数据文件。每个文件在单列中包含 1445 行浮点数。现在我需要以不同的顺序重新排列数据。

每个数据文件的第一个数字需要收集并转储到一个新文件中(比如说 abc1.dat)。此特定文件 (abc1.dat) 将包含 40,000 个数字。

每个数据文件中的第二个数字需要被提取并转储到另一个新文件中(比如 abc2.dat)。这个新文件也将包含 40,000 个数字。但每个数据文件中只有第二个数字。

在此操作结束时,我应该有 1445 个文件(abc1.dat、abc2.dat、...abc40000.dat),每个文件包含 40,000 个数据。

如何做到这一点?(使用 Linux Ubuntu 11.10 - 64 位)

感谢任何帮助。提前谢谢。

4

8 回答 8

5

40,000 * 1445 不算多,应该能塞进内存。因此,在 Perl(未经测试)中:

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

my @nums;
# Reading:
for my $file (0 .. 40_000) {
    open my $IN, '<', "file-$file" or die $!;
    while (<$IN>) {
        chomp;
        $nums[$file][$.-1] = $_;
    }
}

# Writing:
for my $line (0 .. 1444) {
    open my $OUT, '>', "abc$line.dat" or die $!;
    for my $file (0 .. 40_000) {
        print $OUT $nums[$file][$line], "\n";
    }
}
于 2013-01-23T00:10:47.370 回答
3

如果您可以一次打开所有 1445 个输出文件,这很容易:

paths = ['abc{}.dat'.format(i) for i in range(1445)]
files = [open(path, 'w') for path in paths]
for inpath in ('input{}.dat'.format(i) for i in range(40000)):
    with infile as open(inpath, 'r') as infile:
        for linenum, line in enumerate(infile):
            files[linenum].write(line)
for f in files:
    f.close()

如果您可以将所有内容都放入内存(听起来这应该是大约 0.5-5.0 GB 的数据,这对于具有 8GB RAM 的 64 位机器来说可能没问题……),您可以这样做:

data = [[] for _ in range(1445)]
for inpath in ('input{}.dat'.format(i) for i in range(40000)):
    with infile as open(inpath, 'r') as infile:
        for linenum, line in enumerate(infile):
            data[linenum].append(line)
for i, contents in enumerate(data):
    with open('abc{}.dat'.format(i), 'w') as outfile:
        outfile.write(''.join(contents)

如果这些都不合适,您可能需要某种混合。例如,如果您一次可以处理 250 个文件,则执行 6 个批处理,并跳过batchnum每个infile.

如果批处理解决方案太慢,则在每个文件中的每个批处理结束时, stash infile.tell(),当您再次回到文件时,使用infile.seek()回到那里。像这样的东西:

seekpoints = [0 for _ in range(40000)]
for batch in range(6):
    start = batch * 250
    stop = min(start + 250, 1445)
    paths = ['abc{}.dat'.format(i) for i in range(start, stop)]
    files = [open(path, 'w') for path in paths]
    for infilenum, inpath in enumerate('input{}.dat'.format(i) for i in range(40000)):
        with infile as open(inpath, 'r') as infile:
            infile.seek(seekpoints[infilenum])
            for linenum, line in enumerate(infile):
                files[linenum].write(line)
            seekpoints[infilenum] = infile.tell()
    for f in files:
        f.close()
于 2013-01-23T00:16:48.187 回答
2

你应该能够摆脱这样的单线:

perl -nwe 'open my $fh, ">>", "abc${.}.dat" or die $!; 
           print $fh $_; close ARGV if eof;' input*.dat

它将为输入文件的每一行打开一个新的输出文件。输出文件将根据输入文件的当前行号命名。最后,我们需要显式关闭 ARGV 文件句柄以重置行号变量$.

您可以使用 glob 或 perl(如果您愿意)来控制输入文件的顺序。我选择了通用 glob,因为您没有指定行应按特定顺序排列。

效率方面,我认为为每一行打开一个新文件不会过于耗时,因为 perl 在文件操作方面相当快。

请注意,您不需要关闭输出文件句柄,因为它会在超出范围时自动关闭。另请注意,它不会关心您的文件大小。

于 2013-01-23T00:38:56.603 回答
2

重击:

cat file1 file2 ... file40000 | split -n r/1445 -d - outputprefix

假设所有文件正好有 1445 行,写入 outputprefix0000、outputprefix0001、... outputprefix1444。

有点慢,但它有效:)

于 2013-01-23T01:04:17.830 回答
1

创建文件后,运行大约需要 4 分钟,并在我的笔记本电脑上使用了 3.6GB 的 RAM。如果您的机器中有 8GB 的​​ RAM,那应该没问题。

#!/usr/bin/env python2.7

import random

NUMFILES = 40000
NUMLINES = 1445

# create test files
for i in range(1, NUMFILES + 1):
    with open('abc%s.dat' % i, 'w') as f:
        for j in range(NUMLINES):
            f.write('%f\n' % random.random())

data = []

# load all data into memory
for i in range(1, NUMFILES + 1):
    print i
    with open('abc%s.dat' % i) as f:
        lines = f.readlines()
        data.append(lines)

# write it back out
for j in range(len(data[0])):
    with open('new_abc%s.dat' % (j + 1), 'w') as f:
        for i in range(len(data)):
            f.write(data[i][j])

我将所有内容都保存为字符串,以避免在反序列化然后重新序列化浮点数时出现精度错误。


您是否需要可以定期运行的更快、更少资源密集型的东西,或者这是一次性的转换?

于 2013-01-23T00:33:17.553 回答
1

出于完整性考虑,因为 [fortran] 标签是 Fortran 中迟来的示例。它一个一个地打开文件并将所有数据存储在内存中。

program copy
  implicit none

  character(1024) :: filename
  integer :: i, unit, infiles, outfiles
  parameter (infiles = 40000, outfiles = 1445)
  real :: data(infiles, outfiles)

  do i = 1, infiles
    write(filename, '("path/to/file", I0, ".dat")') i
    open(newunit = unit, file = filename, action = 'read')
    read(unit, *) data(i,:)
    close(unit)
  enddo

  do i = 1, outfiles
    write(filename, '("path/to/abc", I0, ".dat")') i
    open(newunit = unit, file = filename, action = 'write')
    write(unit, '(G0)') data(:,i)
    close(unit)
  enddo
end program

注意:它可能会很慢。

于 2013-01-23T16:24:43.580 回答
0

在 awk 中,非常简单:

awk '{print >> "abc" FNR ".dat}' files*

我不确定 awk 是否能够处理 40,000 个打开的文件句柄。

于 2013-01-23T00:56:53.750 回答
0

以下适用于solaris。

nawk '{x="abc"FNR".txt";print $1>x}' file1 file2

你无论如何都可以这样做:

nawk '{x="abc"FNR".txt";print $1>x}' file*

用于引用所有 40k 文件

于 2013-01-23T06:03:34.767 回答