Я делаю простой фрейм данных, который может читать и записывать CSV и включает функцию сортировки для сортировки по столбцу. Как я могу отсортировать правильный столбец, введя заголовок столбца, и исключить строку заголовка столбца из сортировки?
Это пример данных файла CSV:
Name,Age,Salary
Lim,20,2000
Tan,20,3000
Mah,19,2500
Roger,10,4000
Я объявил свой 2D-список, данные будут выглядеть так:
List<List<String>> COLUMNDATA = new ArrayList();
COLUMNDATA = [[Name, Age, Salary], [Lim, 20, 2000], [Tan, 20, 3000], [Mah, 19, 2500], [Roger, 10, 4000]]
Я хочу отсортировать правильный столбец, передав заголовок столбца, а строка заголовка столбца не включена в сортировку. например:
COLUMNDATA.sort(“Age”)
Чтоб стало так:
Name,Age,Salary
Roger,10,4000
Mah,19,2500
Lim,20,2000
Tan,20,3000
Я использовал Comparator и Collections.sort, и теперь я застрял. Как я могу добиться желаемой функции?
final Comparator<List<String>> comparator = new Comparator<List<String>>() {
@Override
public int compare(List<String> object1, List<String> object2) {
return object1.get(1).compareTo(object2.get(1));
}
};
Collections.sort(COLUMNDATA, comparator);
for (List<String> list : COLUMNDATA) {
System.out.println(list);
}
Вы все сделали правильно (кроме имени переменной, которое не должно быть в верхнем регистре).
Перед сортировкой просто удалите первый элемент. Затем отсортируйте и добавьте заголовок обратно в список:
List<String> header = columnData.get(0);
columnData.remove(0);
columnData.sort(getComparator("Age", header));
columnData.add(0, header);
Как передать номер столбца в компаратор:
private Comparator<List<String>> getComparator(String column,
List<String> header) {
int index = header.indexOf(column);
return new Comparator<List<String>>() {
@Override
public int compare(List<String> object1, List<String> object2) {
return object1.get(index).compareTo(object2.get(index));
}
};
}
Вот как это сделать, как вам нужно. Как только компаратор определен, просто отсортируйте по sublist, начиная со списка 1, пропуская заголовки. Поскольку это представление исходного списка, он по-прежнему сортирует необходимые элементы.
Сначала сделайте карту полей, по какому полю сортировать. Вы можете сделать этот регистр нечувствительным, если хотите. В данном примере важен регистр.
static Map<String, Integer> sortingFields = new HashMap<>();
static {
List<String> columns = List.of("Name", "Age", "Salary");
for (int i = 0; i < columns.size(); i++) {
sortingFields.put(columns.get(i), i);
}
}
Создать список списков.
List<List<String>> data = new ArrayList<>();
data.add(new ArrayList<>(List.of("Name" ,"Age", "Salary")));
data.add(new ArrayList<>(List.of("Lim", "20", "4000")));
data.add(new ArrayList<>(List.of("Tan", "20", "3000")));
data.add(new ArrayList<>(List.of("Mah", "19", "2500")));
data.add(new ArrayList<>(List.of("Roger", "10", "3500")));
Теперь вызовите сортировку и распечатайте
sort("Age", data);
data.forEach(System.out::println);
Отпечатки
[Name, Age, Salary]
[Roger, 10, 3500]
[Mah, 19, 2500]
[Lim, 20, 4000]
[Tan, 20, 3000]
Вот метод сортировки.
public static void sort(String Column, List<List<String>> data) {
// use the column string to select the column number to sort.
Comparator<List<String>> comp =
(a, b) -> a.get(sortingFields.get(column))
.compareTo(b.get(sortingFields.get(column)));
data.subList(1,data.size()).sort(comp);
}
И вот как я бы рекомендовал вам организовать ваши данные и выполнить сортировку.
Сначала создайте класс, как показано. Затем заполните список экземплярами класса, используя данные. Затем просто укажите геттер для сортировки. Вы можете добавить столько дополнительных полей и их геттеров, сколько потребуется.
Причина в том, что он позволяет хранить смешанные типы в одном и том же объекте и при этом сортировать их. Если вы сортируете по String number, он будет сортировать lexcally, а не numerically. Это будет проблемой, если вы не конвертируете в целое число (чтобы увидеть это, измените 4000 на 400 и отсортируйте по зарплате выше). Но если вы хотите отсортировать по имени, вам понадобится другой компаратор, поскольку преобразование не-int в int вызовет исключение. Все это можно было бы в какой-то степени смягчить, но это не так просто, как создание класса.
Просто изменив ссылку на метод на нужный getter, вы можете отсортировать List в любом поле. Если геттер отсутствует, а поле является общедоступным (не рекомендуется), вы можете использовать лямбду.
public class SortingByColumn {
public static void main(String[] args) {
List<Person> data = new ArrayList<>();
data.add(new Person("Lim", 20, 2000));
data.add(new Person("Tan", 20, 3000));
data.add(new Person("Mah", 19, 2500));
data.add(new Person("Roger", 10, 4000));
List<Person> sorted = data.stream()
.sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());
System.out.printf("%10s %10s %10s%n", "Name","Age","Salary");
sorted.forEach(System.out::println);
}
static class Person {
private String name;
private int age;
private int salary;
public Person(String name, int age, int salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getSalary() {
return salary;
}
@Override
public String toString() {
return String.format("%10s %10s %10s", name, age,
salary);
}
}
}
Отпечатки
Name Age Salary
Roger 10 4000
Mah 19 2500
Lim 20 2000
Tan 20 3000
Вы можете сделать часть этого списка отсортированной начиная со второй строки, а затем собрать из него новый список следующим образом:
public static void main(String[] args) {
List<List<String>> columnData = List.of(
List.of("Name", "Age", "Salary"),
List.of("Lim", "20", "2000"),
List.of("Tan", "20", "3000"),
List.of("Mah", "19", "2500"),
List.of("Roger", "10", "4000"));
List<List<String>> sortedData1 = sortByColumn(columnData, "Age");
List<List<String>> sortedData2 = sortByColumn(columnData, 2);
}
public static List<List<String>> sortByColumn(List<List<String>> list,
String name) {
// finding index of column by name
int index = IntStream.range(0, list.get(0).size())
.filter(i -> list.get(0).get(i).equals(name))
.findFirst()
.getAsInt();
// sorting by index
return sortByColumn(list, index);
}
public static List<List<String>> sortByColumn(List<List<String>> list,
int index) {
// preparing a new sorted list
List<List<String>> sorted = new ArrayList<>(list.size());
// header row
sorted.add(list.get(0));
// other rows, sorting by a specific column
sorted.addAll(list.stream().skip(1)
.sorted(Comparator.comparing(row -> row.get(index)))
.collect(Collectors.toList()));
return sorted;
}
[Name, Age, Salary]
[Roger, 10, 4000]
[Mah, 19, 2500]
[Lim, 20, 2000]
[Tan, 20, 3000]
[Name, Age, Salary]
[Lim, 20, 2000]
[Mah, 19, 2500]
[Tan, 20, 3000]
[Roger, 10, 4000]
В этом случае полезнее иметь 2D-массив, а не 2D-список, чтобы можно было сортировать определенный диапазон от индекса к индексу с помощью Arrays.sort(T[],int,int,Comparator ) способ:
List<List<String>> columnData = List.of(
List.of("Name", "Age", "Salary"),
List.of("Lim", "20", "2000"),
List.of("Tan", "20", "3000"),
List.of("Mah", "19", "2500"),
List.of("Roger", "10", "4000"));
String[][] arr = columnData.stream()
.map(list -> list.toArray(String[]::new))
.toArray(String[][]::new);
Arrays.sort(arr, 1, arr.length, Comparator.comparing(row -> row[1]));
[Name, Age, Salary]
[Lim, 20, 2000]
[Tan, 20, 3000]
[Mah, 19, 2500]
[Roger, 10, 4000]
[Name, Age, Salary]
[Roger, 10, 4000]
[Mah, 19, 2500]
[Lim, 20, 2000]
[Tan, 20, 3000]
Я предлагаю не использовать List, я думаю, что использование class с относительными именами намного понятнее. В этом классе вы можете определить необходимые компараторы.
public class Foo {
public static void main(String... args) throws IOException {
List<DataLine> data =
readFile(Path.of("e:/data.csv"), StandardCharsets.UTF_8);
List<DataLine> sortedByName = DataLine.Field.NAME.sort(data);
List<DataLine> sortedByAge = DataLine.Field.AGE.sort(data);
List<DataLine> sortedBySalary = DataLine.Field.SALARY.sort(data);
}
public static List<DataLine> readFile(Path path, Charset charset)
throws IOException {
try (Scanner scan = new Scanner(path, charset)) {
scan.useDelimiter("[,\n]");
scan.nextLine(); // skip header
List<DataLine> data = new ArrayList<>();
while (scan.hasNext()) {
String name = scan.next();
int age = scan.nextInt();
int salary = scan.nextInt();
data.add(new DataLine(name, age, salary));
}
return data;
}
}
public static final class DataLine {
enum Field {
NAME(Comparator.comparing(one -> one.name)),
AGE(Comparator.comparingInt(one -> one.age)),
SALARY(Comparator.comparingInt(one -> one.salary));
private final Comparator<DataLine> comparator;
Field(Comparator<DataLine> comparator) {
this.comparator = comparator;
}
public final List<DataLine> sort(List<DataLine> data) {
return data.stream()
.sorted(comparator)
.collect(Collectors.toList());
}
}
private final String name;
private final int age;
private final int salary;
public DataLine(String name, int age, int salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
}
}
Вы можете использовать метод List.subList(int,int) , чтобы получить часть этого списка, которая поддерживается этим списком между указанными индексами, а затем использовать метод Collections.sort(List,Comparator). Этот код должен работать на Java 7:
List<List<String>> columnData = Arrays.asList(
Arrays.asList("Name", "Age", "Salary"),
Arrays.asList("Lim", "20", "2000"),
Arrays.asList("Tan", "20", "3000"),
Arrays.asList("Mah", "19", "2500"),
Arrays.asList("Roger", "10", "4000"));
Collections.sort(columnData.subList(1, columnData.size()),
new Comparator<List<String>>() {
@Override
public int compare(List<String> o1, List<String> o2) {
return o1.get(1).compareTo(o2.get(1));
}
});
[Name, Age, Salary]
[Lim, 20, 2000]
[Tan, 20, 3000]
[Mah, 19, 2500]
[Roger, 10, 4000]
[Name, Age, Salary]
[Roger, 10, 4000]
[Mah, 19, 2500]
[Lim, 20, 2000]
[Tan, 20, 3000]
See also: • Sort List<Map<String,Object>> based on value • How do I rotate a matrix 90 degrees counterclockwise in java?