использование Lambda
у меня есть программа которая принимает csv файл (там примерно тысяча) строк файл в таком формате 1;Адыгейск;Адыгея;Южный;12248;1973 добавляет это в список, а после нужно перевести в массив и найти количество городов в каждом регионе,
output должен быть такой:
Татарстан - 22
Вологодская область - 15
Хабаровский край - 7
Адыгея - 2
..................
Я это сделал в лоб, топорно, но регионов много и получается очень громоздко и не круто, я понимаю, что это можно реализовать с помощью лямбда выражений, возможно что-то типа:
filter(cityList -> cityList.get(i).getRegion().equals("Татарстан")).count();
Но я не понимаю как это реализовать, так как еще совсем новичок. Подскажите пожалуйста, что можно сделать, и скажите, мой вариант это норма? или совсем плох, вот код:
public class ReaderClass {
private void printFileContent(InputStream is) throws IOException {
try (Scanner scanner = new Scanner(is)) {
List<City> cityList = new ArrayList<>();
while (scanner.hasNextLine()) {
String[] splitLine = scanner.nextLine().split(";");
City city = new City(splitLine);
cityList.add(city);
}
String[] arrayReg = new String[cityList.size()];
for (int i = 0; i < cityList.size(); i++) {
arrayReg[i] = cityList.get(i).getRegion();
}
int count1 = 0;
int count2 = 0;
int count3 = 0;
int count4 = 0;
int count5 = 0;
int count6 = 0;
int count7 = 0;
int count8 = 0;
int count9 = 0;
String reg1 = "Татарстан";
String reg2 = "Вологодская область";
String reg3 = "Хабаровский край";
String reg4 = "Адыгея";
String reg5 = "Алтай";
String reg6 = "Алтайский край";
String reg7 = "Амурская область";
String reg8 = "Архангельская область";
String reg9 = "Башкортостан";
String reg10 = "Белгородская область";
String reg11 = "Брянская область";
for (int i = 0; i < arrayReg.length; i++) {
if (arrayReg[i].equals(reg1)) {
count1++;
}
}
System.out.println(reg1 + " - " + count1);
for (int i = 0; i < arrayReg.length; i++) {
if (arrayReg[i].equals(reg2)) {
count2++;
}
}
System.out.println(reg2 + " - " + count2);
for (int i = 0; i < arrayReg.length; i++) {
if (arrayReg[i].equals(reg3)) {
count3++;
}
}
System.out.println(reg3 + " - " + count3);
for (int i = 0; i < arrayReg.length; i++) {
if (arrayReg[i].equals(reg4)) {
count4++;
}
}
System.out.println(reg4 + " - " + count4);
for (int i = 0; i < arrayReg.length; i++) {
if (arrayReg[i].equals(reg5)) {
count5++;
}
}
System.out.println(reg5 + " - " + count5);
for (int i = 0; i < arrayReg.length; i++) {
if (arrayReg[i].equals(reg6)) {
count6++;
}
}
System.out.println(reg6 + " - " + count6);
for (int i = 0; i < arrayReg.length; i++) {
if (arrayReg[i].equals(reg7)) {
count7++;
}
}
System.out.println(reg7 + " - " + count7);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("problems with the file" + e);
}
is.close();
}
public static void main(String[] args) throws IOException {
ReaderClass read = new ReaderClass();
InputStream is = read.getFileAsIOStream("Файл.csv");
read.printFileContent(is);
}
private InputStream getFileAsIOStream(final String fileName) {
InputStream ioStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
if (ioStream == null) {
throw new IllegalArgumentException(fileName + " is not found");
}
return ioStream;
}
}
Ответы (2 шт):
То, что вы делаете, называется программирование путем исключения. И это антипаттерн. Вам постоянно нужно менять код, когда меняется количество регионов и чем больше регионов, тем длинее код (и он само собой дублирующийся). Это самый плохой подход из существующих.
Пробуйте так:
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.function.Function;
import java.util.stream.Collectors;
public class ReaderClass {
public static void main(String[] args) throws IOException {
ReaderClass read = new ReaderClass();
List<City> cities = parseCsv(read.getFileAsIOStream("Файл.csv"));
Map<String, Long> result = cities.stream().map(City::getRegion)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(result);
}
private InputStream getFileAsIOStream(final String fileName) {
InputStream ioStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
if (ioStream == null) {
throw new IllegalArgumentException(fileName + " is not found");
}
return ioStream;
}
private static List<City> parseCsv(InputStream is) throws IOException {
try (Scanner scanner = new Scanner(is)) {
List<City> cities = new ArrayList<>();
while (scanner.hasNextLine()) {
String[] splitLine = scanner.nextLine().split(";");
City city = new City(splitLine);
cities.add(city);
}
return cities;
}
}
}
Следует использовать средства "нового" ввода/вывода для чтения файлов и Stream API для более лаконичного решения данной задачи, в которой можно выделить следующие подзадачи:
- получить поток строк из входного файла CSV-формата с разделителем
;:Files::linesилиBufferedReader::lines - каждую строку разбить на колонки и преобразовать в экземпляр класса
City:Stream::map - построить мапу городов по регионам, используя коллекторы
Collectors.groupingBy+Collectors.mapping+Collectors.toSet - для полученной мапы отсортировать её содержимое по убыванию количества городов
Вариант реализации:
public static void printRegionStats(String csvFile) throws IOException {
try (Stream<String> input = Files.lines(Paths.get(csvFile))) {
input
.map(line -> line.split(";")) // Stream<String[]>
.map(City::new) // Stream<City>, есть конструктор City(String...args)
.collect(Collectors.groupingBy(
City::getRegion, // ключ - название региона
Collectors.mapping(City::getCity, Collectors.toSet()) // множество уникальных городов
)) // Map<String, Set<String>>
.entrySet()
.stream() // Stream<Map.Entry<String, Set<String>>>
.sorted(Comparator.comparing(
e -> e.getValue().size(), Comparator.reverseOrder()
))
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue().size()));
}
}
Если во входном файле для каждого города гарантированно нет дубликатов, код можно упростить и сразу подсчитывать количество городов:
public static void printRegionStats(String csvFile) throws IOException {
try (Stream<String> input = Files.lines(Paths.get(csvFile))) {
input
.map(line -> line.split(";")) // Stream<String[]>
.map(City::new) // Stream<City>, есть конструктор City(String...args)
.collect(Collectors.groupingBy(
City::getRegion, // ключ - название региона
Collectors.summingInt(c -> 1) // подсчет городов
)) // Map<String, Integer>
.entrySet()
.stream() // Stream<Map.Entry<String, Integer>>
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
}
}
Для получения потока строк из сканера можно использовать метод:
static Stream<String> streamScanner(Scanner scanner) {
Spliterator<String> splt = Spliterators.spliterator(scanner, Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL);
return StreamSupport.stream(splt, false)
.onClose(scanner::close);
}
Соответственно, код может быть изменен так (добавил сортировку ключей регионов по алфавиту при совпадении значений):
public static void printRegionStats(String csvFile) throws IOException {
try (Stream<String> input = streamScanner(new Scanner(new File(csvFile)).useDelimiter("\\R"))) {
input
.map(line -> line.split(";")) // Stream<String[]>
.map(City::new) // Stream<City>, есть конструктор City(String...args)
.collect(Collectors.groupingBy(
City::getRegion, // ключ - название региона
Collectors.summingInt(c -> 1) // подсчет городов
)) // Map<String, Integer>
.entrySet()
.stream() // Stream<Map.Entry<String, Integer>>
.sorted(Map.Entry.<String, Integer>comparingByValue()
.reversed()
.thenComparing(Map.Entry.comparingByKey())
)
.forEach(e -> System.out.println(e.getKey() + " - " + e.getValue()));
}
}
Тестирование данного способа упрощается до следующего:
public static void main(String[] args) throws IOException {
printRegionStats("data.csv");
}
Вывод (тестовый вариант):
Татарстан - 2
Адыгея - 2
Алтайский край - 1
Красноярский край - 1