Сортировка 2D-списка по заголовку столбца в Java

Я делаю простой фрейм данных, который может читать и записывать 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);
}
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
Что такое управление транзакциями JDBC и как оно используется для поддержания согласованности данных?
Что такое управление транзакциями JDBC и как оно используется для поддержания согласованности данных?
Управление транзакциями JDBC - это мощная функция, которая позволяет рассматривать группу операций с базой данных как единую единицу работы. Оно...
Выполнение HTTP-запроса с помощью Spring WebClient: GET
Выполнение HTTP-запроса с помощью Spring WebClient: GET
WebClient - это реактивный веб-клиент, представленный в Spring 5. Это реактивное, неблокирующее решение, работающее по протоколу HTTP/1.1.
Gradle за прокси-сервером
Gradle за прокси-сервером
Создайте проект Gradle под сетевым прокси.
3
0
1 350
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы все сделали правильно (кроме имени переменной, которое не должно быть в верхнем регистре).

Перед сортировкой просто удалите первый элемент. Затем отсортируйте и добавьте заголовок обратно в список:

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;
}
sortedData1 sortedData2
[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?

Другие вопросы по теме