如何在Java中编写自定义比较器?

huangapple go评论78阅读模式
英文:

How to write custom comparator in java?

问题

我想在`TreeMap`中存储键值对并根据以下逻辑对条目进行排序
1. "type"不区分大小写键应排在第一位
2."metadata"不区分大小写开头的键应按升序排在最后
3. 其余键不区分大小写应按升序排在中间

我使用的是Java 8版本

程序

```java
public class CustomComparator {
    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")); // 应该为true,但显示为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;
        }
    }
}

期望的输出如下所示:
type: someType
address: someAddress
ContactNo: someContactNumber
DOB: someDOB
name: someName
metadata: someMetaData
metadata.location: someMetaDataLocation
metadata.source: someMetaDataSource


<details>
<summary>英文:</summary>
I want to store key-value pairs in `TreeMap` and sort the entries based on the value of key as per the following logic:
1. &quot;type&quot; (case insensitive) key should be at first.
2. the key start with &quot;metadata&quot; (case insensitive) should be at last in ascending order
3. rest of the keys(case insensitive) should be in middle in ascending order
I am using Java 8 version.
Program:
public class CustomeCamarator  {
public static void main(String[] args) {
CustomComparator comparator = new CustomComparator();
Map&lt;String,Object&gt; empData=new TreeMap&lt;&gt;(comparator);
empData.put(&quot;name&quot;,&quot;someName&quot;);
empData.put(&quot;DOB&quot;,&quot;someDOB&quot;);
empData.put(&quot;address&quot;,&quot;someAddress&quot;);
empData.put(&quot;type&quot;,&quot;employee data&quot;);
empData.put(&quot;ContactNo.&quot;,&quot;someContactNumber&quot;);
empData.put(&quot;metadata.source&quot;,&quot;someMetaDataSource&quot;);
empData.put(&quot;metadata.location&quot;,&quot;someMetaDataLocation&quot;);
empData.put(&quot;metadata&quot;,&quot;someMetaData&quot;);
System.out.println(empData);
System.out.println(empData.containsKey(&quot;metadata&quot;));//should be true but showing false
}
}
class CustomComparator implements Comparator&lt;String&gt;{
@Override
public int compare(String o1, String o2) {
String str1 = o1.toLowerCase();
String str2 = o2.toLowerCase();
if(str1.equalsIgnoreCase(&quot;type&quot;)) {
return -1;
}else if(!str1.contains(&quot;metadata&quot;) &amp;&amp; !str2.contains(&quot;metadata&quot;)) {
return str1.compareTo(str2);
}else if(o1.contains(&quot;metadata&quot;) &amp;&amp; !o2.contains(&quot;metadata&quot;)) {
return 1;
}else if(!o1.contains(&quot;metadata&quot;) &amp;&amp; o2.contains(&quot;metadata&quot;)) {
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
</details>
# 答案1
**得分**: 0
你应该将实现该逻辑的比较器传递给 `TreeMap` 构造函数:
```java
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 的自然顺序。


编辑:
如果你使用的是 Java 8 并且无法使用 Map.of,考虑改为使用传统的 HashMap

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

You should pass a comparator that implements that logic to the TreeMap constructor:

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

I think this should do it. The idea is to use a map to first compare by some Integer value. The value corresponding to type is Integer's min value, so that it always comes first. The value for metadata is Integer's max value, so that it always comes last. Before searching in the map, we first check if the string starts with metadata. It it does, we just change it to metadata, to obtain the Integer value from the map. If there is no entry in the map, we return 0. It there's a tie, we use String's natural order.


EDIT:
If you are on Java8 and cannot use Map.of, consider using a traditional HashMap instead:

Map&lt;String, Integer&gt; order = new HashMap&lt;&gt;();
order.put(&quot;type&quot;, Integer.MIN_VALUE);
order.put(&quot;metadata&quot;, Integer.MAX_VALUE);

答案2

得分: 0

我使用了一个决策表,如下所示:

<!-- language: lang-none -->

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. type
  2. [以] metadata 开头
  3. 以上都不是

以下是实现部分:

Comparator&lt;String&gt; comparator = (k1, k2) -&gt; {
    Objects.requireNonNull(k1);
    Objects.requireNonNull(k2);
    int k1Val;
    int k2Val;
    if (k1.equalsIgnoreCase(&quot;type&quot;)) {
        k1Val = 2;
    }
    else if (k1.matches(&quot;(?i)^metadata.*$&quot;)) {
        k1Val = 1;
    }
    else {
        k1Val = 0;
    }
    if (k2.equalsIgnoreCase(&quot;type&quot;)) {
        k2Val = 2;
    }
    else if (k2.matches(&quot;(?i)^metadata.*$&quot;)) {
        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(&quot;Unhandled: &quot; + index);
    }
    return retVal;
};
Map&lt;String, Object&gt; empData = new TreeMap&lt;&gt;(comparator);
empData.put(&quot;name&quot;,&quot;someName&quot;);
empData.put(&quot;address&quot;,&quot;someAddress&quot;);
empData.put(&quot;type&quot;,&quot;employee data&quot;);
empData.put(&quot;ContactNo.&quot;,&quot;someContactNumber&quot;);
empData.put(&quot;Metadata.source&quot;,&quot;someMetaDataSource&quot;);
empData.put(&quot;metadata.location&quot;,&quot;someMetaDataLocation&quot;);
empData.put(&quot;metadata&quot;,&quot;someMetaData&quot;);
System.out.println(empData);

运行上述代码的输出。

<!-- language: lang-none -->

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

I used a Decision Table as follows

<!-- language: lang-none -->

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 1 and Key 2 are the parameters to method compare() of interface Comparator. Each key can have one of three values:

  1. type
  2. [starts with] metadata
  3. none of the above

Here is the implementation:

Comparator&lt;String&gt; comparator = (k1, k2) -&gt; {
    Objects.requireNonNull(k1);
    Objects.requireNonNull(k2);
    int k1Val;
    int k2Val;
    if (k1.equalsIgnoreCase(&quot;type&quot;)) {
        k1Val = 2;
    }
    else if (k1.matches(&quot;(?i)^metadata.*$&quot;)) {
        k1Val = 1;
    }
    else {
        k1Val = 0;
    }
    if (k2.equalsIgnoreCase(&quot;type&quot;)) {
        k2Val = 2;
    }
    else if (k2.matches(&quot;(?i)^metadata.*$&quot;)) {
        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(&quot;Unhandled: &quot; + index);
    }
    return retVal;
};
Map&lt;String, Object&gt; empData = new TreeMap&lt;&gt;(comparator);
empData.put(&quot;name&quot;,&quot;someName&quot;);
empData.put(&quot;address&quot;,&quot;someAddress&quot;);
empData.put(&quot;type&quot;,&quot;employee data&quot;);
empData.put(&quot;ContactNo.&quot;,&quot;someContactNumber&quot;);
empData.put(&quot;Metadata.source&quot;,&quot;someMetaDataSource&quot;);
empData.put(&quot;metadata.location&quot;,&quot;someMetaDataLocation&quot;);
empData.put(&quot;metadata&quot;,&quot;someMetaData&quot;);
System.out.println(empData);

Output from running above code.

<!-- language: lang-none -->

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

答案3

得分: 0

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

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;
    // 完全匹配!
    if (left.equals(right)) return 0;
    // 非完全相同,考虑 'type' 匹配
    if ("type".equals(left)) return LEFT;
    if ("type".equals(right)) return RIGHT;
    // 到这一步,我们知道都不匹配 'type',所以检查是否有 'metadata' 前缀
    if (left.startsWith("metadata")) {
      // 如果两者都以 'metadata' 开头,则使用自然顺序
      if (right.startsWith("metadata")) return left.compareTo(right);
      // 只有左侧以 'metadata' 开头,因此右侧较前
      else return RIGHT;
    }
    // 只有右侧以 'metadata' 开头,因此左侧较前
    if (right.startsWith("metadata")) return LEFT;
    // 到这一步,我们知道它们不相等,但既不包含 'text',也不以 'metadata' 开头,因此使用自然顺序
    return left.compareTo(right);
  }
}
英文:

I think the following covers your cases

public class CustomComparator implements Comparator&lt;String&gt; {
@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 &#39;type&#39; match
if (&quot;type&quot;.equals(left)) return LEFT;
if (&quot;type&quot;.equals(right)) return RIGHT;
// at this point we know neither matches &#39;type&#39; so lets check for &#39;metadata&#39; prefix
if (left.startsWith(&quot;metadata&quot;)) {
// if both start with metadata use natural ordering
if (right.startsWith(&quot;metadata&quot;)) return left.compareTo(right);
// only left starts with &#39;metadata&#39; so right comes first
else return RIGHT;
}
// only right starts with &#39;metadata&#39; so left comes first
if (right.startsWith(&quot;metadata&quot;)) return LEFT;
// at this point we know they are not equal but neither contains &#39;text&#39; nor starts with &#39;metadata&#39; so use natural ordering
return left.compareTo(right);
}
}

答案4

得分: -1

TreeMap在其构造函数中支持自定义的Comparator。只需实现Comparator接口并将其传递给构造函数。

英文:

TreeMap supports a custom Comparator in its constructor. Just implement the Comparator interface and pass it to the constructor.

huangapple
  • 本文由 发表于 2020年10月1日 12:28:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/64149084.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定