Без полного примера (как в компиляционном файле 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
, которые могли бы смягчить это.Однако вы никогда не должны оптимизировать таким образом, если только вам действительно не нужно, поскольку это делает код более подверженным ошибкам и менее идиоматичным (и, следовательно, менее читабельным).