【问题标题】:How to write custom comparator in java?如何在java中编写自定义比较器?
【发布时间】:2021-01-16 19:18:56
【问题描述】:

我想将键值对存储在TreeMap 中,并按照以下逻辑根据键的值对条目进行排序:

  1. “type”(不区分大小写)键应该在第一个。
  2. 以“元数据”开头的键(不区分大小写)最后应按升序排列
  3. 其余键(不区分大小写)应按升序排列在中间

我使用的是 Java 8 版本。

程序:

public class CustomeCamarator  {
    public static void main(String[] args) {
        CustomComparator comparator = new CustomComparator();
        Map<String,Object> empData=new TreeMap<>(comparator);
        empData.put("name","someName");
        empData.put("DOB","someDOB");
        empData.put("address","someAddress");
        empData.put("type","employee data");
        empData.put("ContactNo.","someContactNumber");
        empData.put("metadata.source","someMetaDataSource");
        empData.put("metadata.location","someMetaDataLocation");
        empData.put("metadata","someMetaData");
        System.out.println(empData);
        System.out.println(empData.containsKey("metadata"));//should be true but showing false
    }
}
class CustomComparator implements Comparator<String>{
    @Override
    public int compare(String o1, String o2) {
        String str1 = o1.toLowerCase();
        String str2 = o2.toLowerCase();
        if(str1.equalsIgnoreCase("type")) {
            return -1;
        }else if(!str1.contains("metadata") && !str2.contains("metadata")) {
            return str1.compareTo(str2);
        }else if(o1.contains("metadata") && !o2.contains("metadata")) {
            return 1;
        }else if(!o1.contains("metadata") && o2.contains("metadata")) {
            return -1;
        }else {
            return 1;
        }
    }
}





**Expected Output like this:**
type: someType
address: someAddress
ContactNo: someContactNumber
DOB: someDOB
name: someName
metadata: someMetaData
metadata.location: someMetaDataLocation
metadata.source: someMetaDataSource

【问题讨论】:

    标签: java comparator treemap


    【解决方案1】:

    TreeMap supports a custom Comparator in its constructor。只需实现Comparator 接口并将其传递给构造函数即可。

    【讨论】:

    • 我认为@VinayakKanade 要求实际实施。问题标题指出:如何在 java 中编写自定义比较器
    • @Abra 他没有尝试在他的代码中使用 Comparator。
    • 添加了我的尝试
    【解决方案2】:

    您应该将实现该逻辑的比较器传递给TreeMap 构造函数:

    Map<String, Integer> order = Map.of(
        "type", Integer.MIN_VALUE, 
        "metadata", Integer.MAX_VALUE);
    
    Map<String, Object> map = new TreeMap<>(
        Comparator.comparing(a -> order.getOrDefault(
            a.toLowerCase().startsWith("metadata") ?
                "metadata" :
                a.toLowerCase(), 
            0))
        .thenComparing(Comparator.naturalOrder()));
    

    我认为应该这样做。这个想法是使用地图首先比较一些Integer 值。 type 对应的值是Integer 的最小值,所以它总是在第一位。 metadata 的值是Integer 的最大值,所以它总是排在最后。在地图中搜索之前,我们首先检查字符串是否以metadata 开头。确实如此,我们只需将其更改为metadata,即可从地图中获取Integer 值。如果地图中没有条目,我们返回0。如果有平局,我们使用String 的自然顺序。


    编辑: 如果您使用的是 Java8 并且无法使用 Map.of,请考虑改用传统的 HashMap

    Map<String, Integer> order = new HashMap<>();
    order.put("type", Integer.MIN_VALUE);
    order.put("metadata", Integer.MAX_VALUE);
    

    【讨论】:

    • 感谢@fps 的回复,但我们使用的是 JAVA 8 并且 Map.of 是在 JAVA 9 中引入的。
    • @VinayakKanade 添加了 java8 兼容版本
    【解决方案3】:

    我使用Decision Table如下

    type  metadata.*  others
    2     1           0
    
    Key 1    Key 2    Result    Action
      0        0        0       String.compare
      0        1        1       return -1
      0        2        2       return 1
      1        0        3       return 1
      1        1        4       String.compare
      1        2        5       return 1
      2        0        6       return -1
      2        1        7       return -1
      2        2        8       return 0
    

    Key 1Key 2 是接口Comparator 的方法compare() 的参数。每个键可以具有以下三个值之一:

    1. 类型
    2. [开头]元数据
    3. 以上都不是

    这里是实现:

    Comparator<String> comparator = (k1, k2) -> {
        Objects.requireNonNull(k1);
        Objects.requireNonNull(k2);
        int k1Val;
        int k2Val;
        if (k1.equalsIgnoreCase("type")) {
            k1Val = 2;
        }
        else if (k1.matches("(?i)^metadata.*$")) {
            k1Val = 1;
        }
        else {
            k1Val = 0;
        }
        if (k2.equalsIgnoreCase("type")) {
            k2Val = 2;
        }
        else if (k2.matches("(?i)^metadata.*$")) {
            k2Val = 1;
        }
        else {
            k2Val = 0;
        }
        int retVal;
        int index = k1Val * 3 + k2Val;
        switch (index) {
            case 0:
            case 4:
                retVal = k1.compareToIgnoreCase(k2);
                break;
            case 1:
            case 6:
            case 7:
                retVal = -1;
                break;
            case 2:
            case 3:
            case 5:
                retVal = 1;
                break;
            case 8:
                retVal = 0;
                break;
            default:
                throw new RuntimeException("Unhandled: " + index);
        }
        return retVal;
    };
    Map<String, Object> empData = new TreeMap<>(comparator);
    empData.put("name","someName");
    empData.put("address","someAddress");
    empData.put("type","employee data");
    empData.put("ContactNo.","someContactNumber");
    empData.put("Metadata.source","someMetaDataSource");
    empData.put("metadata.location","someMetaDataLocation");
    empData.put("metadata","someMetaData");
    System.out.println(empData);
    

    运行上述代码的输出。

    {type=employee data, address=someAddress, ContactNo.=someContactNumber, name=someName, metadata=someMetaData, metadata.location=someMetaDataLocation, Metadata.source=someMetaDataSource}
    

    【讨论】:

      【解决方案4】:

      我认为以下内容涵盖了您的情况

      public class CustomComparator implements Comparator<String> {
        @Override public int compare(String left, String right) {
          left = left.toLowerCase();
          right = right.toLowerCase();
          final int LEFT = -1;
          final int RIGHT = 1;
          // exact match!
          if (left.equals(right)) return 0;
          // not identical, so consider 'type' match
          if ("type".equals(left)) return LEFT;
          if ("type".equals(right)) return RIGHT;
          // at this point we know neither matches 'type' so lets check for 'metadata' prefix
          if (left.startsWith("metadata")) {
            // if both start with metadata use natural ordering
            if (right.startsWith("metadata")) return left.compareTo(right);
            // only left starts with 'metadata' so right comes first
            else return RIGHT;
          }
          // only right starts with 'metadata' so left comes first
          if (right.startsWith("metadata")) return LEFT;
          // at this point we know they are not equal but neither contains 'text' nor starts with 'metadata' so use natural ordering
          return left.compareTo(right);
        }
      }
      

      【讨论】:

      • compare 方法的第一行 final left = left.toLowerCase(); final right = right.toLowerCase(); 是无效语法。对于变量声明,它们需要一个类型,但已经有具有该名称的变量。对于分配,final 修饰符是不合适的。除此之外,为了不区分大小写的比较可能已经在第一个字符处停止,潜在地创建具有小写版本的新字符串效率非常低,甚至对于所有字符和语言环境都不正确。
      • 您可以实现与Comparator&lt;String&gt; comparator = Comparator .comparing((String s) -&gt; !"type".equalsIgnoreCase(s)) .thenComparing(s -&gt; s.regionMatches(true, 0, "metadata", 0, 8)) .thenComparing(String.CASE_INSENSITIVE_ORDER); 一样简单的逻辑,不仅更简单,而且避免了在比较过程中创建新字符串。
      猜你喜欢
      • 2019-06-28
      • 2020-11-27
      • 1970-01-01
      • 2018-12-22
      • 1970-01-01
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 2020-02-24
      相关资源
      最近更新 更多