Есть ли простой способ вывести CSV по столбцам? - PullRequest
4 голосов
/ 02 декабря 2010

Я пытаюсь вывести несколько списков данных различной длины в файл CSV.Каждый список должен быть столбцом в выходном CSV-файле.Есть прямой способ сделать вещь?Если бы я выводил каждый список в виде строки, я бы просто зацикливал каждый список и выводил возврат, когда достигал конца, но этот подход не работает при работе по столбцам.

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

Спасибо за любые идеи!

Ответы [ 8 ]

2 голосов
/ 02 декабря 2010

Стоит взглянуть на http://commons.apache.org/sandbox/csv/

Это также относится к некоторым другим библиотекам CSV.

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

2 голосов
/ 02 декабря 2010

Я думаю, что это довольно просто:

public static void main(String... args) throws IOException {

    ArrayList<ArrayList<String>> rows = getRandomData();

    if (rows.size() == 0)
        throw new RuntimeException("No rows");

    // normalize data
    int longest = 0;
    for (List<String> row : rows)
        if (row.size() > longest)
            longest = row.size();

    for (List<String> row : rows)
        while (row.size() < longest)
            row.add("");

    if (longest == 0)
        throw new RuntimeException("No colums");

    // fix special characters
    for (int i = 0; i < rows.size(); i++)
        for (int j = 0; j < rows.get(i).size(); j++)
            rows.get(i).set(j, fixSpecial(rows.get(i).get(j)));

    // get the maximum size of one column
    int[] maxColumn = new int[rows.get(0).size()];

    for (int i = 0; i < rows.size(); i++)
        for (int j = 0; j < rows.get(i).size(); j++)
            if (maxColumn[j] < rows.get(i).get(j).length())
                maxColumn[j] = rows.get(i).get(j).length();

    // create the format string
    String outFormat = "";
    for (int max : maxColumn)
        outFormat += "%-" + (max + 1) + "s, ";
    outFormat = outFormat.substring(0, outFormat.length() - 2) + "\n";

    // print the data
    for (List<String> row : rows)
        System.out.printf(outFormat, row.toArray());

}

private static String fixSpecial(String s) {

    s = s.replaceAll("(\")", "$1$1");

    if (s.contains("\n") || s.contains(",") || s.contains("\"") || 
            s.trim().length() < s.length()) {
        s = "\"" + s + "\"";
    }

    return s;
}

private static ArrayList<ArrayList<String>> getRandomData() {

    ArrayList<ArrayList<String>> data = new ArrayList<ArrayList<String>>();

    String[] rand = { "Do", "Re", "Song", "David", "Test", "4", "Hohjoh", "a \"h\" o", "tjo,ad" };
    Random r = new Random(5);

    for (int i = 0; i < 10; i++) {

        ArrayList<String> row = new ArrayList<String>();

        for (int j = 0; j < r.nextInt(10); j++)
            row.add(rand[r.nextInt(rand.length)]);

        data.add(row);
    }

    return data;
}

Вывод (довольно некрасиво, поскольку он случайный) ( убегает ):

Re       , 4           , "tjo,ad" , "tjo,ad" ,    
"tjo,ad" , "a ""h"" o" ,          ,          ,    
Re       , "a ""h"" o" , Hohjoh   , "tjo,ad" , 4  
4        , David       ,          ,          ,    
4        , Test        , "tjo,ad" , Hohjoh   , Re 
Do       , Hohjoh      , Test     ,          ,    
Hohjoh   , Song        ,          ,          ,    
4        , Song        ,          ,          ,    
4        , Do          , Song     , Do       ,    
Song     , Test        , Test     ,          ,    
1 голос
/ 02 декабря 2010

Я совсем не знаком с Java, но если у вас есть тип данных, ориентированный на matrix, вы можете заполнить строки, используя простой цикл, затем транспонировать его, а затем записать его, используя простой цикл.Ваша подпрограмма печати может обрабатывать пустые записи путем вывода пустой строки или пробелов фиксированной ширины, если вы предпочитаете.

1 голос
/ 02 декабря 2010

Вы можете использовать String.format ():

System.out.println(String.format("%4s,%4s,%4s", "a", "bb", "ccc"));
System.out.println(String.format("%4s,%4s,%4s", "aaa", "b", "c"));

Результатом будет фиксированная ширина столбца из 4 символов - при условии, что используемые значения короче. В противном случае макет сломается.

   a,  bb, ccc
 aaa,   b,   c
1 голос
/ 02 декабря 2010

Создайте массив итераторов (по одному для каждого списка.) Затем выполните цикл по массиву, проверяя, является ли итератор hasNext();если это так, выведите iterator.next().Вывод запятых и символов новой строки тривиален.Остановитесь, когда все итераторы вернут hasNext()==false.

0 голосов
/ 03 декабря 2010

Если вы хотите сделать это в одной паре циклов и в одном методе, вы можете сделать следующее.

public static void writeCSV(PrintWriter pw, List<List<String>> columnsRows) {
    for(int i=0;;i++) {
        StringBuilder line = new StringBuilder();
        boolean empty = true;
        for (List<String> column : columnsRows) {
            String text = i < column.size() ? column.get(i) : "";
            found &= i >= column.size();
            if (text.contains(",") || text.contains("\"") || text.contains("\n") || text.trim() != text)
                text = '"' + text.replaceAll("\"", "\"\"") + '"';
            line.append(text).append(',');
        }
        if (empty) break;
        pw.println(line.substring(0, line.length()-1));
    }
}

В качестве упражнения вы можете сделать это с помощью одного цикла, но это не будетбыть столь же ясным относительно того, что он делает.

Используя пример данных из @dacwe, этот метод занимает 10 us (микросекунд).

0 голосов
/ 02 декабря 2010

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

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

Но вы все равно не должны думать о счетчике и индексировании; Вы должны думать с точки зрения итераторов (которые обходят вопрос произвольного доступа и упрощают код).

0 голосов
/ 02 декабря 2010

Вы можете сделать что-то вроде этого:

List<List<?>> listOfLists = new LinkedList<List<?>>(); 
List<Iterator<?>> listOfIterators = new LinkedList<Iterator<?>>(); 
for (List<?> aList : listOfLists) {
         listOfIterators.add(aList.iterator()); 
}        
boolean done = false;        
while(!done) 
{   
      done = true;  
      for (Iterator<?> iter : listOfIterators)  
      {         
          if (iter.hasNext())       
          {             
             Object obj = iter.next();          
             //PROCESS OBJ          
             done = false;      
          }         
          else      
          {             
             //PROCESS EMPTY ELEMENT        
          }     
       } 
}

Для обработки CSV я использовал эту библиотеку несколько раз: http://www.csvreader.com/java_csv.php Очень просто и удобно.

Cheerz!

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