我有一個包含這樣的數據串矢量:如何對包含數字的字符串集合進行排序?
5:34,5:38,17:21,22:11,...
如果我嘗試合併這使用Collections.sort(...);會出現這樣的:
17:21,22:11,5:34,5:38
其實我希望它看起來是這樣的:
5 :34,5:38,17點21分,22時11分
所以想要的元素根據冒號前的數排序:如果某些元件具有前相同的數目,然後「」「:」的按照「:」後面的數字對它們進行排序。
這樣做的最簡單方法是什麼?
我有一個包含這樣的數據串矢量:如何對包含數字的字符串集合進行排序?
5:34,5:38,17:21,22:11,...
如果我嘗試合併這使用Collections.sort(...);會出現這樣的:
17:21,22:11,5:34,5:38
其實我希望它看起來是這樣的:
5 :34,5:38,17點21分,22時11分
所以想要的元素根據冒號前的數排序:如果某些元件具有前相同的數目,然後「」「:」的按照「:」後面的數字對它們進行排序。
這樣做的最簡單方法是什麼?
您既可以創建自定義Comparator
拆分String
並將其解析爲兩個整數,或創建一個定製類來代表每個String
並將其存儲在Collection
中。我贊成後一種方法,因爲你只會招致分裂/解析字符串一次的開銷;例如
public class Data implements Comparable<Data> {
private final int prefix;
private final int suffix;
public Data(String str) {
String[] arr = str.split(":");
if (arr.length != 2) {
throw new IllegalArgumentException();
}
this.prefix = Integer.parseInt(arr[0]);
this.suffix = Integer.parseInt(arr[1]);
}
public int compareTo(Data data) {
// Should really avoid subtraction in case of overflow but done to keep code brief.
int ret = this.prefix - data.prefix;
if (ret == 0) {
ret = this.suffix - data.suffix;
}
return ret;
}
// TODO: Implement equals and hashCode (equals to be consistent with compareTo).
public String toString() { return String.format("%d:%d", prefix, suffix); }
}
然後它只是在你的Collection
存儲一些Data
對象的情況;例如
List<Data> l = new ArrayList<Data>();
l.add(new Data("13:56"));
l.add(new Data("100:16"));
l.add(new Data("9:1"));
Collections.sort(l);
一兩件事 - 你提到你使用的是Vector
。您應該儘量避免使用Vector
/Hashtable
,因爲它們已被List
/Map
取代,它們是作爲JDK 1.2的集合框架的一部分引入的。
創建一個java.util.Comparator
並將其提供給sort
方法。
實現您自己的Comparator
類,比較兩個值並調用Collections.sort(List list, Comparator c)
。
實施您自己的比較儀並將其作爲Colelctions.sort方法的第二個參數。
這是非常低效的,但它應該做的工作。
Collections.sort(data, new Comparator<String>(){
public int compare(String a, String b){
String[] as = a.split(":");
String[] bs = b.split(":");
int result = Integer.valueOf(as[0]).compareTo(Integer.valueOf(bs[0]));
if(result==0)
result = Integer.valueOf(as[1]).compareTo(Integer.valueOf(bs[1]));
return result;
}
})
(提示:如果它是我的代碼,我會優化它使用子代替String.split(),但我懶得)
我想寫相同的代碼;) –
正確這樣做的方法是不將非字符串值存儲爲字符串。
集合中的數據有一些結構和規則,不能是任意字符串。因此,您不應使用String
數據類型。
讓我們定義一個名爲TwoNumbers
型(因爲我不知道是什麼類型應該代表,即使我能猜出):
class TwoNumbers implements Comparable<TwoNumbers> {
private final int num1;
private final int num2;
public TwoNumbers(int num1, int num2) {
if (num1 <= 0 || num2 <= 0) {
throw new IllegalArgumentException("Numbers must be positive!");
}
this.num1 = num1;
this.num2 = num2;
}
public static TwoNumbers parse(String s) {
String[] parts = s.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("String format must be '<num>:<num>'");
}
try {
return new TwoNumbers(Integer.parseInt(parts[0]), Integer.parseInt(parts[0]));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("parts must be numeric!", e);
}
}
public int getNum1() {
return num1;
}
public int getNum2() {
return num2;
}
@Override
public int compareTo(TwoNumbers o) {
if (o == null) {
return 1;
}
int diff = Integer.compare(o.num1, this.num1);
if (diff == 0) {
diff = Integer.compare(o.num2, this.num2);
}
return diff;
}
}
的compareTo
方法存在爲the Comparable
interface實現:它定義瞭如何這種類型的對象是有序的。
我已經使用了final
字段(並且不提供setter),因爲類實現了immutable objects。
這樣你可以直接對數據進行排序,不需要額外的Comparator
,也不需要在程序中分發所有的「拆分和解析」代碼。相反,您有一個單個類負責處理該特定格式,所有其他代碼段可以使用該類。
+1使用對象 –
@Joachim ..感謝您的好解決方案。只有2個問題:你爲什麼使用「最終」字段?和什麼是比較(...)方法? – Brad
@Brad:我已經用一些細節和幾個鏈接更新了我的答案。 –
通常,將Java中的對象(包括集合)與其默認的hashCode()和equals()方法進行比較。對於內置的對象和數據類型(如String,Integet等),hashCode()是內部計算的,因此它們被JLS(Java語言規範)保證使用。由於我們不能總是依賴於默認/內置對象,我們需要處理自己的自定義對象(如Employee,Customer等),所以我們應該重寫hashCode()和equals() )方法,以便我們可以根據自定義類的對象的「最佳」相等來提供真/假。類似地,sort()涉及一個比較行爲,它確實需要一個比較器(這是一個實現比較方法的重載方法的比較器接口的類)。您還應該重寫比較方法,該方法需要比較兩個對象並返回一個結果(第一個對象大於第二個,第二個對於第一種情況的反向,結果爲0)。
現在,您的數據應該以不同於正常比較的不同方式處理。您需要將數據拆分爲兩部分(使用可拆分方法),然後您可以對這兩個部分(冒號前的第一部分,冒號後的第二部分)進行個別比較。
最後,你應該提供這個自定義比較的排序方法的一個實例,最終將做自定義排序爲您的自定義數據:)
我覺得這是非常簡單的:
public class NumericalStringSort {
public static void main(String[] args) {
List<String> input = Arrays.asList(new String[] {"17:21", "22:11", "5:34", "5:38"});
Collections.sort(input, new NumericalStringComparator());
System.out.println(input);
}
public static class NumericalStringComparator implements Comparator<String> {
public int compare(String object1, String object2) {
return pad(object1).compareTo(pad(object2));
}
private String pad(String input) {
return input.indexOf(":") == 1 ? "0" + input : input;
}
}
}
此比較器僅根據「:」之前的數字進行排序,並忽略「:」之後的數字。如果還有「5:7」呢?它會在你的例子中的「5:38」之後出現。 – Brad
@Brad沒有給出冒號後面一個數字的例子。我認爲這些是沒有前導零的時間字符串。確實,如果您對輸入格式做出其他假設,則需要使用不同的比較器實現。 –
剛發現這個(相當陳舊)的帖子,答案並沒有完全解決我的問題。我需要一個更通用的解決方案,因爲這些值是用戶輸入,像「abc 1 a 12」和「abc 1 a 1」這樣的東西應按照包含的數字排序。所以我寫了下面的比較:
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
String[] s1=splitNumeric(o1);
String[] s2=splitNumeric(o2);
for (int x=0;x<s1.length&&x<s2.length;x++){
if (!s1[x].equals(s2[x])){
if (s1[x].charAt(0)=='N' && s2[x].charAt(0)=='N'){
long l1=Long.parseLong(s1[x].substring(1));
long l2=Long.parseLong(s2[x].substring(1));
return (int)Math.signum(l1-l2);
}
break;
}
}
return o1.compareTo(o2);
}
}
雖然功能splitNumeric定義如下:
private String[] splitNumeric(String s){
final String numbers="";
LinkedList<String> out=new LinkedList<String>();
int state=-1;
for (int x=0;x<s.length();x++){
if (numbers.contains(s.charAt(x)+"")){
if (state==1)
out.set(out.size()-1,out.getLast()+s.charAt(x));
else{
state=1;
out.add("N"+s.charAt(x));
}
}
else{
if (state==0)
out.set(out.size()-1,out.getLast()+s.charAt(x));
else{
state=0;
out.add("S"+s.charAt(x)+"");
}
}
}
return out.toArray(new String[0]);
}
如下的代碼將排序字符串
"X 124 B"
"X 1 Y"
"X 111 Z"
"X 12 Y"
"12:15"
"12:13"
"12:1"
"1:1"
"2:2"
:
"1:1"
"2:2"
"12:1"
"12:13"
"12:15"
"X 1 Y"
"X 12 Y"
"X 111 Z"
"X 124 B"
享受:)
我試過你的解決方案,它的工作原理非常完美。我可以使用ArrayList而不是Vector。但最後一件事:如果我在Vector或ArrayList中有2個重複項像「13:56」和「13:56」,我怎樣才能跳過重複並插入一次只保留列表排序? – Brad
如果您希望避免重複的問題,您可能需要考慮將Comparable對象插入SortedSet而不是List(例如TreeSet)。這將避免必須對數據進行明確排序。 – Adamski
非常感謝。很好地工作。 – Brad