如何将 Array[Array[Array[Double]]] 导出到 scala 中的文件中?

问题描述 投票:0回答:2

我有一个代码需要很长时间才能计算 Array[Array[Array[Double]]],但我需要结果来计算具有不同参数但相同 Array[Array[Array[Double]]] 的其他内容,因此它将其放在我的文件中并导入会更方便。我可以制作一个长字符串,将每一行依次放置,但我想有一种更复杂的方法可以做到这一点。只需导入此文件就可以了,我可以将其用作 Array[Array[Array[Double]]]。所以我需要你的帮助。谢谢

我尝试过将其转换为长字符串并导出,但结果很混乱。

scala export export-to-csv export-to-excel
2个回答
0
投票

这取决于您将使用什么语言进行其他计算。 我想提出两种变体

  1. HDF5文件格式——对于科学计算非常方便,支持n维数组。你会很容易找到 scala/java 库。
  2. 或者任何二进制格式,如 protobuf 或 avro - 它们也支持数组。

如果您不打算使用 scala 以外的其他语言,存储数组的最快方法将是使用 Serialized 进行纯 JVM 序列化。


0
投票

一个简单的解决方案是按照某种自定义格式编写一个纯文件。

这是一个示例,如果您的数据是对称的(例如所有内部

Arrays
具有相同的大小)
它首先写入一个标题行,其中包含每个
Array
的大小,然后每个元素写入一行。

def writeData(data: Data)(writeLine: String => Unit): Unit =
  val tensors = data.length
  val rows = if (tensors > 0) data.head.length else 0
  val columns = if (rows > 0) data.head.head.length else 0

  // Print header line.
  writeLine(s"${tensors},${rows},${columns}")

  // You may use while loops here if you prefer.
  data.foreach { row =>
    row.foreach { column =>
      column.foreach { value =>
        writeLine(value.toString)
      }
    }
  }
end writeData

您可以使用更好的名称。

然后,阅读功能也非常简单:

def readData(lines: Iterator[String]): Data =
  // We are assuming here the file has at least the header line.
  val header = lines.next().split(',')
  val tensors = header(0).toInt
  val rows = header(1).toInt
  val columns = header(2).toInt

  // You may use while loops here if you prefer.
  ArraySeq.fill(tensors, rows, columns) {
    // We are assuming here the file only has double values.
    lines.next().toDouble
  }
end readData

如果您认为错误处理很重要,您可以添加错误处理。


您可以在Scastie
中看到代码运行 它使用

List
作为内存中的文件只是为了表明它可以工作,真正的代码将使用真正的 I/O API,无论您喜欢哪种; Java
nio
Java
io
Scala
Source
(仅适用于阅读)better-filesakkafs2


另一件事要考虑的是直接使用

Bytes
而不是
Strings
,这会节省大量的编码和解码时间和文件大小;但代价是它不可读。
由于
Ints
Doubles
具有固定大小,因此您可以将前三个
Ints
,然后将所有
Doubles
写入二进制文件。然后,在阅读时,您将得到
Iterator[String]
,而不是
Iterator[Byte]
,然后您将使用
ByteBuffer
进行转换:

def writeData(data: Data)(writeByte: Byte => Unit): Unit =
  val tensors = data.length
  val rows = if (tensors > 0) data.head.length else 0
  val columns = if (rows > 0) data.head.head.length else 0

  def writeInt(value: Int): Unit =
    ByteBuffer.allocate(4).putInt(value).array.foreach(writeByte)
  end writeInt

  writeInt(tensors)
  writeInt(rows)
  writeInt(columns)

  def writeDouble(value: Double): Unit =
    ByteBuffer.allocate(8).putDouble(value).array.foreach(writeByte)
  end writeDouble
  
  data.foreach { row =>
    row.foreach { column =>
      column.foreach(writeDouble)
    }
  }
end writeData

def readData(lines: Iterator[Byte]): Data =
  def readInt(): Int =
    val bb = ByteBuffer.allocate(4)
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.getInt(0)
  end readInt

  val tensors = readInt()
  val rows = readInt()
  val columns = readInt()

  def readDouble(): Double =
    val bb = ByteBuffer.allocate(8)
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.put(lines.next())
    bb.getDouble(0)
  end readDouble

  ArraySeq.fill(tensors, rows, columns)(readDouble())
end readData

您可以在Scastie

中看到代码运行

如果您的数据不对称,您将需要稍微调整代码。您需要有多个 “开始部分” 类型的行来说明接下来将有多少个元素,而不是单个标题。该版本甚至可以通用以写入和读取任意数量的嵌套

Arrays


最后,您可以考虑使用像 scodec 这样的库来处理二进制数据。

© www.soinside.com 2019 - 2024. All rights reserved.