Как записать в CSV-файл в Scala? - PullRequest
0 голосов
/ 05 октября 2018

Я пытаюсь записать данные в CSV-файл, у меня есть четыре столбца, которые я создал как

val csvFields = Array("Serial Number", "Record Type", First File value", Second file value") ', 

, отличные от серийного номера, остальные три поля являются списками

Second_file_value = List ("B", "gjgbn", "fgbhjf", "dfjf")

First_File_Value = List ("A","abhc","agch","mknk")

Record_type = List('1','2',3','4');

 val outputFile = new BufferedWriter(new FileWriter("Resulet.csv")
 val csvWriter = new CSVWriter(outputFile)
 val listOfRecords = new ListBuffer[Array[String]]()
 listOfRecords :+ csvFields

Я использую этот цикл для записи в столбцы

for ( i <- 1 until 30){
listOfRecords += Array(i.toString, Record_type , First_File_Value , Second_file_value )}
csvWriter.writeAll(listOfRecords.toList)
output.close()

Проблема, с которой я сталкиваюсь, заключается в заполнении файла CSVс 30 строками одинаковых значений (значение 1-й строки) значения в списках не повторяются.

Любые ссылки также будут полезны

1 Ответ

0 голосов
/ 05 октября 2018

Без полного примера (как в компиляционном файле Main) нельзя сказать, почему вы получаете одну и ту же строку снова и снова.Размещенный вами фрагмент верен в отдельности.

scala> val lb: ListBuffer[Array[String]] = new ListBuffer[Array[String]]()
lb: scala.collection.mutable.ListBuffer[Array[String]] = ListBuffer()

scala> for (i <- 1 until 30){lb += Array(i.toString)}

scala> lb.toList
res5: List[Array[String]] = List(Array(1), Array(2), Array(3), Array(4), Array(5), Array(6), Array(7), Array(8), Array(9), Array(10), Array(11), Array(12), Array(13), Array(14), Array(15), Array(16), Array(17), Array(18), Array(19), Array(20), Array(21), Array(22), Array(23), Array(24), Array(25), Array(26), Array(27), Array(28), Array(29))

Тем не менее, существует ряд способов улучшить это в целом, которые могут помочь вам избежать этой и других ошибок.

ДобавлениеСерийный префикс ко всем строкам

В Scala считается, что лучше использовать неизменяемые структуры, а не изменяемые в качестве идиомы.Учитывая это, я бы предложил вам создать функцию для добавления серийного префикса в ваши строки, используя неизменный метод.Есть несколько способов сделать это, но самым фундаментальным является операция fold.Если вы не знакомы с ним, fold можно рассматривать как преобразование над структурой, например, функциональную версию цикла for.

Имея это в виду, вот как вы можете взять некоторыестроки, которые являются List[List[String]] и добавляют числовой префикс ко всем из них.

def addPrefix(lls: List[List[String]]): List[List[String]] =
  lls.foldLeft((1, List.empty[List[String]])){
    // You don't need to annotate the types here, I just did that for clarity.
    case ((serial: Int, acc: List[List[String]]), value: List[String]) =>
      (serial + 1, (serial.toString +: value) +: acc)
  }._2.reverse

A foldLeft создает список в противоположность тому, что мы хотим, поэтому я и называю .reverse в конце.Причиной этого является артефакт того, как стеки работают при обходе структур, и он выходит за рамки этого вопроса, но есть много хороших статей о том, почему использовать foldLeft или foldRight.

Из чегоЯ читал выше, это то, как выглядят ваши строки в примере.

val columnOne: List[String] =
  List('1','2','3','4').map(_.toString)
val columnTwo: List[String] =
  List("A","abhc","agch","mknk")
val columnThree: List[String] =
  List("B", "gjgbn", "fgbhjf", "dfjf")

val rows: List[List[String]] =
  columnOne.zip(columnTwo.zip(columnThree)).foldLeft(List.empty[List[String]]){
    case (acc, (a, (b, c))) => List(a, b, c) +: acc
  }.reverse

, что приводит к этому.

scala> rows.foreach(println)
List(1, A, B)
List(2, abhc, gjgbn)
List(3, agch, fgbhjf)
List(4, mknk, dfjf)

Давайте попробуем вызвать нашу функцию с этим в качестве ввода.

scala> addPrefix(rows).foreach(println)
List(1, 1, A, B)
List(2, 2, abhc, gjgbn)
List(3, 3, agch, fgbhjf)
List(4, 4, mknk, dfjf)

Хорошо, это выглядит хорошо.

Запись файла CSV

Теперь, чтобы записать файл CSV.Поскольку CSVWriter работает в терминах типов коллекций Java, нам необходимо преобразовать наши типы Scala в коллекции Java.В Scala вы должны сделать это в последний возможный момент .Причиной этого является то, что типы Scala разработаны так, чтобы хорошо работать со Scala, и мы не хотим терять эту способность рано.Они также более безопасны, чем параллельные типы Java, с точки зрения неизменности (если вы используете неизменяемые варианты, что и в этом примере).

Давайте определим функцию writeCsvFile, которая принимает имя файла, строку заголовка,и список строк и записывает его.Опять же, есть много способов сделать это правильно, но вот простой пример.

def writeCsvFile(
  fileName: String,
  header: List[String],
  rows: List[List[String]]
): Try[Unit] =
  Try(new CSVWriter(new BufferedWriter(new FileWriter(fileName)))).flatMap((csvWriter: CSVWriter) =>
    Try{
      csvWriter.writeAll(
        (header +: rows).map(_.toArray).asJava
      )
      csvWriter.close()
    } match {
      case f @ Failure(_) =>
        // Always return the original failure.  In production code we might
        // define a new exception which wraps both exceptions in the case
        // they both fail, but that is omitted here.
        Try(csvWriter.close()).recoverWith{
          case _ => f
        }
      case success =>
        success
    }
  )

Давайте разберем это на мгновение.Я использую тип данных Try из пакета scala.util.Он похож на блоки try/catch/finally уровня языка, но вместо использования специальной конструкции для перехвата исключений используется обычное значение.Это еще одна распространенная идиома в Scala, предпочитающая значения на простом языке над конструкциями потока специального языка управления.

Давайте подробнее рассмотрим это выражение (header +: rows).map(_.toArray).asJava.Это небольшое выражение выполняет довольно много операций.Сначала мы добавляем нашу строку header в начало нашего списка строк (header +: rows).Затем, поскольку CSVWriter хочет Iterable<Array<String>>, мы сначала преобразуем внутренний тип в Array, затем внешний тип в Iterable.Вызов .asJava - это то, что выполняет внешнее преобразование типов, и вы получаете его путем импорта scala.collection.JavaConverters._, который имеет неявные преобразования между типами Scala и Java.

Остальная часть функции довольно проста.Мы записываем строки, затем проверяем, произошел ли сбой.Если это так, мы гарантируем, что мы все еще пытаемся закрыть CSVWriter.

Пример полной компиляции

Я включил здесь пример полной компиляции.

import com.opencsv._
import java.io._
import scala.collection.JavaConverters._
import scala.util._

object Main {

  val header: List[String] =
    List("Serial Number", "Record Type", "First File value", "Second file value")

  val columnOne: List[String] =
    List('1','2','3','4').map(_.toString)
  val columnTwo: List[String] =
    List("A","abhc","agch","mknk")
  val columnThree: List[String] =
    List("B", "gjgbn", "fgbhjf", "dfjf")

  val rows: List[List[String]] =
    columnOne.zip(columnTwo.zip(columnThree)).foldLeft(List.empty[List[String]]){
      case (acc, (a, (b, c))) => List(a, b, c) +: acc
    }.reverse

  def addPrefix(lls: List[List[String]]): List[List[String]] =
    lls.foldLeft((1, List.empty[List[String]])){
      case ((serial: Int, acc: List[List[String]]), value: List[String]) =>
        (serial + 1, (serial.toString +: value) +: acc)
    }._2.reverse

  def writeCsvFile(
    fileName: String,
    header: List[String],
    rows: List[List[String]]
  ): Try[Unit] =
    Try(new CSVWriter(new BufferedWriter(new FileWriter(fileName)))).flatMap((csvWriter: CSVWriter) =>
      Try{
        csvWriter.writeAll(
          (header +: rows).map(_.toArray).asJava
        )
        csvWriter.close()
      } match {
        case f @ Failure(_) =>
          // Always return the original failure.  In production code we might
          // define a new exception which wraps both exceptions in the case
          // they both fail, but that is omitted here.
          Try(csvWriter.close()).recoverWith{
            case _ => f
          }
        case success =>
          success
      }
    )

  def main(args: Array[String]): Unit = {
    println(writeCsvFile("/tmp/test.csv", header, addPrefix(rows)))
  }
}

Вот содержимое файла после запуска этой программы.

"Serial Number","Record Type","First File value","Second file value"
"1","1","A","B"
"2","2","abhc","gjgbn"
"3","3","agch","fgbhjf"
"4","4","mknk","dfjf"

Заключительные замечания

Устаревшая библиотека

Я заметил в комментариях к исходному сообщению, что вы былииспользуя "au.com.bytecode" % "opencsv" % "2.4".Я не знаком с библиотекой opencsv в целом, но, по словам Maven Central, это очень старый форк основного репо.Я бы посоветовал вам использовать основной репо.https://search.maven.org/search?q=opencsv

Производительность

Люди часто беспокоятся о том, что при использовании неизменяемых структур данных и методов нам необходимо компромисс производительности.Это может иметь место, но обычно асимптотическая сложность не изменяется.Приведенное выше решение - O(n), где n - количество строк.Он имеет более высокую константу, чем изменяемое решение, но, как правило, это несущественно.Если бы это было так, есть методы, которые можно было бы использовать, такие как более явная рекурсия в addPrefix, которые могли бы смягчить это.Однако вы никогда не должны оптимизировать таким образом, если только вам действительно не нужно, поскольку это делает код более подверженным ошибкам и менее идиоматичным (и, следовательно, менее читабельным).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...