对两个元素的嵌套列表进行排序

Sort a nested List on two elements

提问人:sandhu 提问时间:7/8/2022 最后编辑:Alexander Ivanchenkosandhu 更新时间:7/10/2022 访问量:195

问:

我有一个嵌套列表,其内容如下:outputToStore

[[Final, 331, M, 22/03/2020 00:00:00], 
 [Initial, 335, M, 22/06/2022 00:00:00], 
 [Exception, 335, M, 22/05/2022 00:00:00], 
 [Final, 335, M, 20/06/2022 00:00:00], 
 [Keep, 335, M, 02/06/2022 11:00:00], 
 [Final, 335, M, 10/04/2022 02:00:00], 
 [Deleted, 335, M, 22/06/2022 15:55:10],
 [Exception, 335, M, 22/06/2022 15:55:09], 
 [Final, 335, M, 22/06/2022 15:56:00], 
 [Initial, 335, M, 11/06/2022 00:00:00]]

我需要根据 2 个条件对此进行排序:第一个是自定义顺序:“Initial”、“Final”、“Deleted”、“Keep”、“Exception”,然后基于日期时间

我能够做到,但不确定这是否是最好的方法。

我的代码:

List<String> definedOrder = Arrays.asList("Initial","Final","Deleted","Keep","Exception");
Collections.sort(outputToStore, Comparator.comparing(o -> Integer.valueOf(definedOrder.indexOf(o.get(0)))));
Collections.sort(outputToStore,( o1, o2)-> {
    // let your comparator look up your car's color in the custom order
    try {
        if(Integer.valueOf(definedOrder.indexOf(o1.get(0))).compareTo(Integer.valueOf(definedOrder.indexOf(o2.get(0))))==0){
            Date date1=simpleDateFormat.parse(o1.get(3));
            Date date2=simpleDateFormat.parse(o2.get(3));
            return date1.compareTo(date2);
        }
    } catch (ParseException e) {
        e.printStackTrace();
    }

    return 0;
});

我得到了想要的结果:

[[Initial, 335, M, 11/06/2022 00:00:00],
 [Initial, 335, M, 22/06/2022 00:00:00],
 [Final, 331, M, 22/03/2020 00:00:00],
 [Final, 335, M, 10/04/2022 02:00:00],
 [Final, 335, M, 20/06/2022 00:00:00],
 [Final, 335, M, 22/06/2022 15:56:00],
 [Deleted, 335, M, 22/06/2022 15:55:10],
 [Keep, 335, M, 02/06/2022 11:00:00],
 [Exception, 335, M, 22/05/2022 00:00:00],
 [Exception, 335, M, 22/06/2022 15:55:09]]

但是有没有更好或更简洁的方法呢?

Java 列表 排序嵌 套列表 比较器

评论


答:

5赞 Alexander Ivanchenko 7/8/2022 #1

使用物体的力量

表示数据的方式既不方便又容易出错。

显然,它必须是具有适当类型属性的对象,而不是字符串列表。这是对集合的滥用

用作数字、日期等的类型不会给您带来任何优势。除了在控制台上打印之外,如果不进行解析,则无法对字符串执行任何操作。适当的数据类型使您可以访问其无法为您提供的独特行为。StringString

第一个属性,我们称之为枚举,可能是枚枚举 - 是一种特殊的类,当您需要表示一组有限的值时,它们非常方便,并且它们具有自然顺序,与枚举常量的顺序(声明它们的顺序)相同。status

为了表示日期时间信息,我们可以使用包中的 Java 8 类之一。在下面的示例中,我将使用 (class is legacy, avoid use it)。java.timeLocalDateTimeDate

这样的类可能是什么样子的:

public class Foo {
    public enum Status {INITIAL, FINAL, DELETED, KEEP, EXCEPTION}
    
    private CountComponents.Foo.Status status;
    private int value1;
    private String value2;
    private LocalDateTime dateTime;
    
    // constructor, getters, etc.
}

使用 Java 8 构建比较器

为了对对象列表进行排序,我们需要定义一个比较器。Foo

为此,我们可以使用在 Java 8 接口中引入的静态方法。我们可以以流畅的方式链接这些方法,因为它们中的每一个都提供了一个比较器。Comparator

Comparator<Foo> byStatusByDate =
    Comparator.comparing(Foo::getStatus)
        .thenComparing(Foo::getDateTime);

注意:我们需要在单个比较器中定义排序逻辑。不要像在代码中那样对数据进行两次排序,这会导致不必要的性能开销。

要了解如何使用 Java-8 方法构建比较器,请查看本教程

为了对对象列表进行排序,我们可以使用 Java 9 中引入的方法作为更流畅的替代方案:FooList.sort()Collections.sort()

List<Foo> foos = // initializing the list
    
foos.sort(byStatusByDate);

解析数据

如果数据以字符串集合的形式出现,则需要对其进行解析。

为此,我们需要增强及其嵌套枚举(下面的链接中提供了完整的代码)。Foo

public static class Foo {
    public enum Status {
        INITIAL, FINAL, DELETED, KEEP, EXCEPTION;
        
        public static Status parse(String str) {
            return Arrays.stream(values())
                .filter(cons -> cons.name().equalsIgnoreCase(str))
                .findFirst()
                .orElseThrow();
        }
    }
    
    private Status status;
    private int value1;
    private String value2;
    private LocalDateTime dateTime;
    
    public static Foo parse(List<String> strings, DateTimeFormatter formatter) {
        return new Foo(Status.parse(strings.get(0)),
            Integer.parseInt(strings.get(1)),
            strings.get(2),
            LocalDateTime.parse(strings.get(3), formatter));
    }
    
    // constructor, getters, etc.
}

注意:类是旧版,现代替代项是 DateTimeFormatterSimpleDateFormat

main()

public static void main(String[] args) {
    List<List<String>> strings = List.of(
        List.of("Final", "331", "M", "22/03/2020 00:00:00"),
        List.of("Initial", "335", "M", "22/06/2022 00:00:00"),
        List.of("Exception", "335", "M", "22/05/2022 00:00:00"),
        List.of("Final", "335", "M", "20/06/2022 00:00:00"),
        List.of("Keep", "335", "M", "02/06/2022 11:00:00"),
        List.of("Final", "335", "M", "10/04/2022 02:00:00"),
        List.of("Deleted", "335", "M", "22/06/2022 15:55:10"),
        List.of("Exception", "335", "M", "22/06/2022 15:55:09"),
        List.of("Final", "335", "M", "22/06/2022 15:56:00"),
        List.of("Initial", "335", "M", "11/06/2022 00:00:00")
    );

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
    
    Comparator<Foo> byStatusByDate =
        Comparator.comparing(Foo::getStatus)
            .thenComparing(Foo::getDateTime);
    
    List<Foo> foos = strings.stream()           // Stream<List<String>>
        .map(list -> Foo.parse(list,formatter)) // Stream<Foo>
        .sorted(byStatusByDate)
        .collect(Collectors.toList());          // or .toList() for Java 16 +
    
    foos.forEach(System.out::println); // printing the result
}

输出:

Foo{status=INITIAL, value1=335, value2='M', dateTime=2022-06-11T00:00}
Foo{status=INITIAL, value1=335, value2='M', dateTime=2022-06-22T00:00}
Foo{status=FINAL, value1=331, value2='M', dateTime=2020-03-22T00:00}
Foo{status=FINAL, value1=335, value2='M', dateTime=2022-04-10T02:00}
Foo{status=FINAL, value1=335, value2='M', dateTime=2022-06-20T00:00}
Foo{status=FINAL, value1=335, value2='M', dateTime=2022-06-22T15:56}
Foo{status=DELETED, value1=335, value2='M', dateTime=2022-06-22T15:55:10}
Foo{status=KEEP, value1=335, value2='M', dateTime=2022-06-02T11:00}
Foo{status=EXCEPTION, value1=335, value2='M', dateTime=2022-05-22T00:00}
Foo{status=EXCEPTION, value1=335, value2='M', dateTime=2022-06-22T15:55:09}

在线演示链接

0赞 user17233545 7/8/2022 #2

试试这个。

static final List<String> definedOrder = List.of(
    "Initial","Final","Deleted","Keep","Exception");
static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
record Rec(int key0, Date key1, List<String> row) {}

static Date parseDate(String input) {
    try {
        return simpleDateFormat.parse(input);
    } catch (ParseException e) { throw new RuntimeException(e); }
}

public static void main(String[] args) {
    List<List<String>> outputToStore = Arrays.asList(
        Arrays.asList("Final", "331", "M", "22/03/2020 00:00:00"), 
        Arrays.asList("Initial", "335", "M", "22/06/2022 00:00:00"), 
        Arrays.asList("Exception", "335", "M", "22/05/2022 00:00:00"), 
        Arrays.asList("Final", "335", "M", "20/06/2022 00:00:00"), 
        Arrays.asList("Keep", "335", "M", "02/06/2022 11:00:00"), 
        Arrays.asList("Final", "335", "M", "10/04/2022 02:00:00"), 
        Arrays.asList("Deleted", "335", "M", "22/06/2022 15:55:10"),
        Arrays.asList("Exception", "335", "M", "22/06/2022 15:55:09"), 
        Arrays.asList("Final", "335", "M", "22/06/2022 15:56:00"), 
        Arrays.asList("Initial", "335", "M", "11/06/2022 00:00:00")
    );

    List<List<String>> sorted = outputToStore.stream()
        .map(e -> new Rec(definedOrder.indexOf(e.get(0)), parseDate(e.get(3)), e))
        .sorted(Comparator.comparingInt(Rec::key0).thenComparing(Rec::key1))
        .map(Rec::row)
        .toList();

    for (List<String> row : sorted)
        System.out.println(row);
}

输出:

[Initial, 335, M, 11/06/2022 00:00:00]
[Initial, 335, M, 22/06/2022 00:00:00]
[Final, 335, M, 10/04/2022 02:00:00]
[Final, 335, M, 20/06/2022 00:00:00]
[Final, 331, M, 22/03/2020 00:00:00]
[Final, 335, M, 22/06/2022 15:56:00]
[Deleted, 335, M, 22/06/2022 15:55:10]
[Keep, 335, M, 02/06/2022 11:00:00]
[Exception, 335, M, 22/05/2022 00:00:00]
[Exception, 335, M, 22/06/2022 15:55:09]

评论

0赞 Alexander Ivanchenko 7/8/2022
由于 Java 8 被认为是遗留的,鼓励使用。应改用 package 中的类。DateSimpleDateFormatjava.time
0赞 Eritrean 7/8/2022 #3

地图可能更适合用于预定义的顺序,并用于按日期进行比较。但除此之外,你走在正确的轨道上;你可以链接到你的比较器:LocalDateTimethenComparing

Map<String,Integer> definedOrder = Map.of(
        "Initial", 1, "Final", 2, "Deleted", 3, "Keep", 4, "Exception", 5);

final DateTimeFormatter df = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");

outputToStore.sort(Comparator.comparing((List<String> list) -> definedOrder.get(list.get(0)))
        .thenComparing(Comparator.comparing(list -> LocalDateTime.parse(list.get(3), df)))
);