【问题标题】:what is an efficient way to transpose a matrix in a text file?在文本文件中转置矩阵的有效方法是什么?
【发布时间】:2016-11-25 04:36:14
【问题描述】:

我有一个包含二维矩阵的文本文件。如下所示。

01 02 03 04 05
06 07 08 09 10
11 12 13 14 15
16 17 18 19 20

如您所见,每一行由一个新行分隔,每一列由一个空格分隔。我需要以一种有效的方式转置这个矩阵。

01 06 11 16
02 07 12 17
03 08 04 05
04 09 14 19
05 10 15 20

实际上,矩阵是 10,000 x 14,000。单个元素是双/浮点数。如果不是不可能的话,尝试将这个文件/矩阵全部转置在内存中的成本会很高。

有没有人知道一个 util API 来做这样的事情或一种有效的方法?

我尝试过的:我天真的方法是为(转置矩阵的)每一列创建一个临时文件。所以,有 10,000 行,我将有 10,000 个临时文件。当我读取每一行时,我标记每个值,并将值附加到相应的文件中。所以对于上面的例子,我会有类似下面的内容。

file-0: 01 06 11 16
file-1: 02 07 12 17
file-3: 03 08 13 18
file-4: 04 09 14 19
file-5: 05 10 15 20

然后我读回每个文件并将它们附加到一个文件中。我想知道是否有更聪明的方法,因为我知道文件 i/o 操作将是一个痛点。

【问题讨论】:

  • 这只是一个千兆字节的触摸;-)
  • 这些天编程是否已简化为寻找 API?

标签: java matrix transpose


【解决方案1】:

最小内存消耗和极低性能的解决方案:

import org.apache.commons.io.FileUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class MatrixTransposer {

  private static final String TMP_DIR = System.getProperty("java.io.tmpdir") + "/";
  private static final String EXTENSION = ".matrix.tmp.result";
  private final String original;
  private final String dst;

  public MatrixTransposer(String original, String dst) {
    this.original = original;
    this.dst = dst;
  }

  public void transpose() throws IOException {

    deleteTempFiles();

    int max = 0;

    FileReader fileReader = null;
    BufferedReader reader = null;
    try {
      fileReader = new FileReader(original);
      reader = new BufferedReader(fileReader);
      String row;
      while((row = reader.readLine()) != null) {

        max = appendRow(max, row, 0);
      }
    } finally {
      if (null != reader) reader.close();
      if (null != fileReader) fileReader.close();
    }


    mergeResultingRows(max);
  }

  private void deleteTempFiles() {
    for (String tmp : new File(TMP_DIR).list()) {
      if (tmp.endsWith(EXTENSION)) {
        FileUtils.deleteQuietly(new File(TMP_DIR + "/" + tmp));
      }
    }
  }

  private void mergeResultingRows(int max) throws IOException {

    FileUtils.deleteQuietly(new File(dst));

    FileWriter writer = null;
    BufferedWriter out = null;

    try {
      writer = new FileWriter(new File(dst), true);
      out = new BufferedWriter(writer);
      for (int i = 0; i <= max; i++) {
        out.write(FileUtils.readFileToString(new File(TMP_DIR + i + EXTENSION)) + "\r\n");
      }
    } finally {
      if (null != out) out.close();
      if (null != writer) writer.close();
    }
  }

  private int appendRow(int max, String row, int i) throws IOException {

    for (String element : row.split(" ")) {

      FileWriter writer = null;
      BufferedWriter out = null;
      try {
        writer = new FileWriter(TMP_DIR + i + EXTENSION, true);
        out = new BufferedWriter(writer);
        out.write(columnPrefix(i) + element);
      } finally {
        if (null != out) out.close();
        if (null != writer) writer.close();
      }
      max = Math.max(i++, max);
    }
    return max;
  }

  private String columnPrefix(int i) {

    return (0 == i ? "" : " ");
  }

  public static void main(String[] args) throws IOException {

    new MatrixTransposer("c:/temp/mt/original.txt", "c:/temp/mt/transposed.txt").transpose();
  }
}

【讨论】:

  • 我在 FileWriter/BufferedWriter 上看到了很多打开/关闭。我们是否应该让这些作家打开并在最后一次关闭它们?还是会是内存问题?
  • 是的,您可以尝试让它们保持打开状态,但最终您应该会遇到内存不足异常
  • 另一种方法是找到矩阵中可能存在的最大数字,并为每个元素保留一个固定长度的字节数组。那么你不需要分隔符,因为记录的长度是固定的。第一步是将原始文件转换为字节文件并使用 java nio FileChannel 及其随机访问能力 (docs.oracle.com/javase/tutorial/essential/io/rafs.html) 跳过原始文件偏移位置以选择目标文件中的下一个数字
【解决方案2】:

总大小为 1.12GB(如果是双倍),如果是浮点型,则为一半。这对于今天的机器来说已经足够小了,你可以在内存中完成它。不过,您可能希望就地进行转置,这是一项相当重要的任务。 wikipedia article 提供更多链接。

【讨论】:

  • 谢谢。我试图避免学习新东西,因为我要解决的问题不是矩阵转置(这是一个绊脚石)。但我想值得考虑一下这些以前的方法。
【解决方案3】:

我建议在不消耗太多内存的情况下评估您可以阅读的列数。然后,您通过涉及列数的块数次读取源文件来编写最终文件。假设您有 10000 列。首先,您读取集合中源文件的 0 到 250 列,然后写入最终文件。然后对第 250 到 500 列再次执行此操作,依此类推。

public class TransposeMatrixUtils {

    private static final Logger logger = LoggerFactory.getLogger(TransposeMatrixUtils.class);

    // Max number of bytes of the src file involved in each chunk
    public static int MAX_BYTES_PER_CHUNK = 1024 * 50_000;// 50 MB

    public static File transposeMatrix(File srcFile, String separator) throws IOException {
        File output = File.createTempFile("output", ".txt");
        transposeMatrix(srcFile, output, separator);
        return output;
    }

    public static void transposeMatrix(File srcFile, File destFile, String separator) throws IOException {
        long bytesPerColumn = assessBytesPerColumn(srcFile, separator);// rough assessment of bytes par column
        int nbColsPerChunk = (int) (MAX_BYTES_PER_CHUNK / bytesPerColumn);// number of columns per chunk according to the limit of bytes to be used per chunk
        if (nbColsPerChunk == 0) nbColsPerChunk = 1;// in case a single column has more bytes than the limit ...
        logger.debug("file length : {} bytes. max bytes per chunk : {}. nb columns per chunk : {}.", srcFile.length(), MAX_BYTES_PER_CHUNK, nbColsPerChunk);
        try (FileWriter fw = new FileWriter(destFile); BufferedWriter bw = new BufferedWriter(fw)) {
            boolean remainingColumns = true;
            int offset = 0;
            while (remainingColumns) {
                remainingColumns = writeColumnsInRows(srcFile, bw, separator, offset, nbColsPerChunk);
                offset += nbColsPerChunk;
            }
        }
    }

    private static boolean writeColumnsInRows(File srcFile, BufferedWriter bw, String separator, int offset, int nbColumns) throws IOException {
        List<String>[] newRows;
        boolean remainingColumns = true;
        try (FileReader fr = new FileReader(srcFile); BufferedReader br = new BufferedReader(fr)) {
            String[] split0 = br.readLine().split(separator);
            if (split0.length <= offset + nbColumns) remainingColumns = false;
            int lastColumnIndex = Math.min(split0.length, offset + nbColumns);
            logger.debug("chunk for column {} to {} among {}", offset, lastColumnIndex, split0.length);
            newRows = new List[lastColumnIndex - offset];
            for (int i = 0; i < newRows.length; i++) {
                newRows[i] = new ArrayList<>();
                newRows[i].add(split0[i + offset]);
            }
            String line;
            while ((line = br.readLine()) != null) {
                String[] split = line.split(separator);
                for (int i = 0; i < newRows.length; i++) {
                    newRows[i].add(split[i + offset]);
                }
            }
        }
        for (int i = 0; i < newRows.length; i++) {
            bw.write(newRows[i].get(0));
            for (int j = 1; j < newRows[i].size(); j++) {
                bw.write(separator);
                bw.write(newRows[i].get(j));
            }
            bw.newLine();
        }
        return remainingColumns;
    }

    private static long assessBytesPerColumn(File file, String separator) throws IOException {
        try (FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr)) {
            int nbColumns = br.readLine().split(separator).length;
            return file.length() / nbColumns;
        }
    }

}

它应该比创建大量会产生大量 I/O 的临时文件更有效。

对于 10000 x 14000 矩阵的示例,此代码需要 3 分钟来创建转置文件。如果你设置MAX_BYTES_PER_CHUNK = 1024 * 100_000而不是1024 * 50_000,这需要2分钟,但当然会消耗更多的RAM。

【讨论】:

    猜你喜欢
    • 2013-03-05
    • 2013-05-20
    • 1970-01-01
    • 2019-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多