Файл CSV как с ним работать?
Всем добрый день! Вопрос следующего характера: есть файл CSV, из строки нужно выделить название фирмы и посчитать общий расход по этой компании и общий доход Файл CSV имеет примерно вот такой вид на одной строке, с пробелами и всякими символами:
Счёт,40817813206170024534,RUR,31.05.17,CRD_1U34U7,548673++++++1028 809216 /RU/CARD2CARD ALFA_MOBILE>MOSCOW 31.05.17 31.05.17 1500.00 RUR MCC6536,1500,0
Я переношу каждую строку в список List и получаю массив строк. В колонке № 5 (отсчет веду с нуля) содержится название фирмы (ALFA_MOBILE), в колонке № 6 содержится доход, в колонке №7 содержится расход Как выделить название компании из этой строки? Как выделить суммы из колонок 6 и 7?
public static void main(String[] args)
{
List<String[]> print = readFile(SOURCE_FILE);
print.forEach(x -> System.out.println(Arrays.toString(x)));
}
public static List<String[]> readFile(String SOURCE_FILE)
{
List<String[]> stringList = null;
CSVParser csvParser = new CSVParserBuilder().withSeparator(',').build();
try (CSVReader csvReader = new CSVReaderBuilder(new FileReader(SOURCE_FILE))
.withCSVParser(csvParser)
.withSkipLines(1)
.build())
{
stringList = csvReader.readAll();
}
catch (Exception ex)
{
ex.printStackTrace();
}
return stringList;
}
Ответы (2 шт):
Как вариант
public static List<String[]> readFile(String SOURCE_FILE)
{
List<String[]> stringList = null;
CSVParser csvParser = new CSVParserBuilder().withSeparator(',').build();
try (CSVReader csvReader = new CSVReaderBuilder(new FileReader(SOURCE_FILE))
.withCSVParser(csvParser)
.withSkipLines(1)
.build())
{
stringList = csvReader.readAll();
Pattern firmaPattern = Pattern.compile("(?<=CARD2CARD ).+(?=\\>)");
for (String[] strings : stringList) {
Matcher matcher = firmaPattern.matcher(strings[5]);
if (matcher.find()) {
String firma = strings[5].substring(matcher.start(), matcher.end());
System.out.print(firma);
}
int summa = Integer.parseInt(strings[6])+Integer.parseInt(strings[7]);
System.out.println(" "+summa);
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
return stringList;
}
}
Вывод:
ALFA_MOBILE 1500
Ну я так понимаю, что вопрос не о том, как из массива строк получить пятый элемент, который содержит имя? Это просто -- s[5]. Вопрос, видимо, о том, как можно хранить объекты с именами, получать к ним доступ по имени и делать с ними еще что-то.
Для решения такого рода задач существуют отображения, maps. В серьезных больших задачах для этого обычно используются базы данных, а в небольших, типа вашей, можно обойтись стандартными коллекциями из пакета java.util.
Смысл отображений, или как их еще называют, словарей, в том, чтобы связать имя (или какой-то другой уникальный признак объекта) с самим объектом, так, чтобы по имени можно было быстро найти нужный объект и что-то с ним сделать. Эти объекты могут хранить какие-то реквизиты, накапливать какую-то статистику, делать еще что-то.
В вашем случае нужно просто считать цифры, так что вместо объектов можно использовать простые Double, в которых будет накапливаться статистика. По прочтении каждой строки вам нужно увеличить суммы доходов и расходов для той фирмы, название которой находится в пятом элементе строки. Сделать это можно так:
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.opencsv.CSVParserBuilder;
import com.opencsv.CSVParser;
import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
public class Ru_So_1252119 {
private static final String FILE_NAME = "./resources/Ru_So_1252119.csv";
// Для накопления доходов по фирмам:
private static final Map<String, Double> incomes = new HashMap<>();
// Для накопления расходов по фирмам:
private static final Map<String, Double> expenses = new HashMap<>();
public static void main(String[] args) {
final List<String[]> lines = readFile(FILE_NAME);
lines.forEach(ss -> addData(ss));
showResults();
}
public static List<String[]> readFile(String fileName) {
List<String[]> list = new ArrayList<>(); // Don't return null!!! Return empty list instead
final CSVParser csvParser = new CSVParserBuilder().withSeparator(',').build();
try (CSVReader csvReader = new CSVReaderBuilder(new FileReader(fileName))
.withCSVParser(csvParser).withSkipLines(1).build()) {
list = csvReader.readAll();
csvReader.close(); // Don't forget to close!!!
} catch (final Exception ex) { ex.printStackTrace(); }
return list;
}
/** Учтём обороты по фирме, чьё название упомянуто в данной строке */
private static void addData(String[] dataItems) {
if (dataItems.length < 8) return; // Ignore incomplete records
final String comapanyName = dataItems[5];
addValue(incomes, comapanyName, Double.valueOf(dataItems[6])); // Добавим в доходы
addValue(expenses, comapanyName, Double.valueOf(dataItems[7])); // И добавим в расходы
}
/** Учтем сумму в данном реестре на счету данной фирмы */
private static void addValue(Map<String, Double> registry, String comapanyName, Double value) {
final Double prevValue = registry.get(comapanyName);
if (prevValue == null)
registry.put(comapanyName, value);
else
registry.put(comapanyName, prevValue + value);
}
private static void showResults() {
final List<String> companies = incomes.keySet().stream().sorted().collect(Collectors.toList());
System.out.format("%12s %12s %12s\n", "Company |", "Income |", "Expense |");
System.out.format("%1$12s %1$12s %1$12s\n", "-----------|");
double totalIncomes = 0, totalExpenses = 0;
for (final String company: companies) {
System.out.format("%10s | %10.2f | %10.2f |\n", company, incomes.get(company), expenses.get(company));
totalIncomes += incomes.get(company);
totalExpenses += expenses.get(company);
};
System.out.format("%1$12s %1$12s %1$12s\n", "-----------|");
System.out.format("%10s | %10.2f | %10.2f |\n", "Total:", totalIncomes, totalExpenses);
}
}
При чтении вот такого файла
Why the CSVReader is created with "withSkipLines(1)"?
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма1, 5049.75, 6540.95,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма2, 7031.55, 2001.36,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма3, 2127.95, 1773.10,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма2, 3191.48, 7419.69,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма1, 6351.92, 4245.49,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма3, 7177.67, 5557.34,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма4, 6206.07, 5078.23,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма1, 4816.59, 1996.06,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма4, 1345.55, 4081.45,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма1, 7455.33, 0784.67,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма2, 0510.43, 1650.68,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма1, 8554.16, 1432.80,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма3, 5702.76, 2818.87,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма2, 6986.06, 9717.13,
Поле0, Поле1, Поле2, Поле3, Поле4, Фирма2, 8152.02, 1319.76,
Программа выдаст вот такой результат
Company | Income | Expense |
-----------| -----------| -----------|
Фирма1 | 32227,75 | 14999,97 |
Фирма2 | 25871,54 | 22108,62 |
Фирма3 | 15008,38 | 10149,31 |
Фирма4 | 7551,62 | 9159,68 |
-----------| -----------| -----------|
Total: | 80659,29 | 56417,58 |
Надо сказать, что намного более элегантно было бы иметь не два отдельных Map для доходов и расходов, а один Map с объектами, умеющими учитывать расходы и доходы и делать что-нибудь еще, например, генерировать строку отчёта. Тогда функциональность задачи при необходимости можно было бы расширять с гораздо меньшим геморроем (для чего, собственно, ООП и задумано). Это был бы чуть более сложный код. Можете сделать это сами в качестве упражнения.
PS И, как верно заметил автор коммента к вашему вопросу, вместо использования CSVPparser и CSVReader прекрасно можно обойтись простым FileReader-ом и регулярными выражениями. Это позволит избавиться от зависимостей от сторонних разработчиков и упростить код, и, возможно, будет даже быстрее работать.