java 基于未知字段列表对Map项目列表进行排序

guicsvcw  于 2023-02-02  发布在  Java
关注(0)|答案(3)|浏览(142)

我有一个很大的HashMap〈String,Object〉项目列表,需要根据字段列表动态排序。
如果字段列表是静态的,我知道我可以很容易地使用Comparator.compare(..)后跟thenComparing(..)
但是,如果我不知道有多少字段以及哪些字段,我就不明白如何根据字段的数量动态链接thenComparion
目前我的数据只有字符串,但我希望它以后会更改为其他字段类型,所以这里是一个未来的证明示例:

[
  {
     "destination": "tokyo",
     "origin": "paris",
     "model": "speedflight 3000",
     "id": 5000047632459593,
     "speed": 502.5,
     "altitude": 5001,
     "time": "2023-01-30T13:35:23Z"
  },
  ....
]

例如,请求可以是按照id, speed, time的顺序过滤字段,或者也可以只是id。没有给定排序方向(升序,降序),因此总是使用默认值。

mgdq6dx1

mgdq6dx11#

我会改变方法,根据字段列表编写一个普通的Comparator实现,而不是链接thenComparing()

public class MapComparator implements Comparator<Map<String, Object>> {

  private final List<String> fields;

  public MapComparator(List<String> fields) {
    this.fields = fields;
  }

  @Override
  public int compare(Map<String, Object> map1, Map<String, Object> map2) {
    for (String field : this.fields) {
      Object value1 = map1.get(field);
      Object value2 = map2.get(field);
      //don't forget to handle nulls, if applicable
      int cmp;
      if (value1 instanceof Comparable comparable) {
        cmp = comparable.compareTo(value2);
      } else {
        cmp = // deal with non-Comparable types, if there are such
        // may make them implement Comparable, if they are custom
        // or depend on Comparators
      }
      if (cmp != 0) {
        return cmp;
      }
    }
    return 0;
  }
}

或者更好的方法是创建一个自定义类来表示元素,然后使用特定字段将字段名(String)Map到Comparator函数。

public class MyClassComparator implements Comparator<MyClassA> {

  private final List<String> fields;
  private final Map<String, Comparator<MyClassA>> map;

  public MyClassComparator(List<String> fields, Map<String, Comparator<MyClassA>> map) {
    this.fields = fields;
    this.map = map;
  }

  @Override
  public int compare(MyClassA o1, MyClassA o2) {
    for (String field : this.fields) {
      int cmp = this.map.get(field).compare(o1, o2);
      if (cmp != 0) {
        return cmp;
      }
    }
    return 0;
  }
}

您也可以使map静态化,因为它很可能在初始化后就不会更改。

oug3syen

oug3syen2#

您可以创建所有可能的比较器,并将它们存储在一个Map中,使用您的字段名作为键,然后根据输入,您可以合并所需的比较器。

static void sortBy(List<Map<String, Object>> maps, List<String> sortOrder) {
    Comparator<Map<String, Object>> byDest   = Comparator.comparing(m -> (String) m.get("destination"));
    Comparator<Map<String, Object>> byOrigin = Comparator.comparing(m -> (String) m.get("origin"));
    Comparator<Map<String, Object>> byModel  = Comparator.comparing(m -> (String) m.get("model"));
    Comparator<Map<String, Object>> byId     = Comparator.comparing(m -> (long)   m.get("id"));
    Comparator<Map<String, Object>> bySpeed  = Comparator.comparing(m -> (double) m.get("speed"));
    Comparator<Map<String, Object>> byAlt    = Comparator.comparing(m -> (int)    m.get("altitude"));
    Comparator<Map<String, Object>> byTime   = Comparator.comparing(m -> (String) m.get("time"));

    Map<String, Comparator<Map<String, Object>>> compMap = Map.of(
            "destination", byDest,
            "origin", byOrigin,
            "model", byModel,
            "id", byId,
            "speed", bySpeed,
            "altitude", byAlt,
            "time", byTime);

    Comparator<Map<String, Object>> combined = sortOrder.stream().map(compMap::get).reduce(Comparator::thenComparing).get();

    maps.sort(combined);
}

假设你有你的Map和字符串列表中的排序顺序:

List<Map<String, Object>> yourListOfMaps = ...
List<String> sortByFields = List.of("destination", "origin", "speed");

将其用作:

sortBy(yourListOfMaps, sortByFields);

如果排序顺序列表为空,则会得到一个NoSuchElementException,为了避免这种情况,可以定义一个默认的比较器,而不是盲目地对reduce返回的可选值调用get。

Comparator<Map<String, Object>> combined = sortOrder.stream()
                                                    .map(compMap::get)
                                                    .reduce(Comparator::thenComparing)
                                                    .orElse(byId);
bd1hkmkf

bd1hkmkf3#

这可能有助于演示你能做什么。我做了一些数据来方便演示。

List<Map<String, Object>> maps = new ArrayList<>(List.of(
        Map.of("integer", 2, "string", "process", "double", 6.8),
        Map.of("integer", 5, "string", "test", "double", 7.8),
        Map.of("integer", 10, "string", "think", "double", 2.8),
        Map.of("integer", 7, "string", "think", "double", 2.8),
        Map.of("integer", 7, "string", "data", "double", 9.9)));

将一些函数定义为keyExtractors以访问Map信息。

Comparator<Map<String, Object>> icomp = Comparator
                .comparing(m -> (Integer) m.get("integer"));
Comparator<Map<String, Object>> dcomp = Comparator
                .comparing(m -> (Double) m.get("double"));
Comparator<Map<String, Object>> scomp = Comparator
                .comparing(m -> (String) m.get("string"));

现在构建一个比较器,如果两个整数比较结果相同,那么整数将按升序排序,而双精度数则按逆序排序。

Comparator<Map<String, Object>> comp = 
        icomp.thenComparing(dcomp.reversed());
              

maps.sort(comp);
maps.forEach(System.out::println);

印刷品

{integer=2, double=6.8, string=process}
{integer=5, double=7.8, string=test}
{integer=7, double=9.9, string=data}
{integer=7, double=2.8, string=think}
{integer=10, double=3.8, string=think}

相关问题