问题描述
我想将键值对存储在TreeMap
中,并根据以下逻辑基于键的值对条目进行排序:
- “类型”(不区分大小写)键应放在首位。
- 以“元数据”(不区分大小写)开头的密钥应最后以升序排列
- 其余键(不区分大小写)应按升序排列在中间
我正在使用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
解决方法
TreeMap
supports a custom Comparator
in its constructor。只需实现Comparator
接口并将其传递给构造函数即可。
您应该将实现该逻辑的比较器传递给Prepare()
构造函数:
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
的自然顺序。
编辑:
如果您使用的是Java8,并且无法使用String
,请考虑改用传统的Map.of
:
HashMap
,
我按如下方式使用了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
键1 和键2 是接口Comparator
的方法compare()的参数。每个键可以具有以下三个值之一:
- 类型
- [以]元数据开头
- 以上都不是
这里是实现:
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}
,
我认为以下内容涵盖了您的情况
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);
}
}