天天看點

仿聯系人的排序

本來想模仿做一個聯系人的快速索引,在敲代碼做測試的過程中,想對讀取檔案擷取的聯系人進行排序,結果發現有許多排序上的問題。

于是,轉移注意力到排序的方法上來,發現确實是有很多需要考慮的東西。

下面就把我在調試這個排序方法的過程給展示一下,說明一下排序中要考慮的方方面面。

1,參考網上部分人提供的方式,是預設的字元順序來排序

這也是最容易想到的方法,就是一個按字元串的升序排列:

我将名字存放在一個list中:

List<String> listName;
           
在添加了聯系人名之後,來進行排序:
           
Collections.sort(listName, new SortComparator());

    /**
     * 按字元升序排序,最初的排序方法
     * */
    class SortComparator implements Comparator {
        @Override
        public int compare(Object t1, Object t2) {
            String a=(String)t1;
            String b=(String)t2;
            int flag=a.compareTo(b);
            if (flag==){
                return a.compareTo(b);
            }else{
                return flag;
            }
        }
    };
           

然而,這種排序,對于我們中國人來說,不太合适,我們的名字,當然是使用漢字啦。雖然近來也有用英文名的,畢竟還是少數嘛。而漢字按預設的字元順序排序,拼音相近的姓氏,排序卻相差很遠。

為什麼會這樣呢?

我們來簡單了解下字元集:

Java使用Unicode字元編碼集,它不是按照拼音順序,基本上是按照CJK編碼順序。

什麼是CJK ?其實是Chinese, Japanese, Korean 的縮寫啦,叫做 中日韓統一表意文字(CJK Unified Ideographs)。是采用字根分解與合成的方法,簡單講,就是按類似偏旁筆畫的劃分來排序,而不是按拼音排序的。

是以,我們需要做下一步的工作:

2,對于漢字,轉拼音,按字母排序

這裡使用的是一個庫:pinyin4j-2.5.0.jar

pinyin4j的官方下載下傳位址:http://sourceforge.net/projects/pinyin4j/files/,目前最新的版本就是2.5.0。

庫的使用比較簡單,就是根據漢字,擷取拼音,例如這樣:

String[] pinyinArray =PinyinHelper.toHanyuPinyinStringArray('解');
           

為什麼傳回的是一個字元串數組?因為有多音字啊。

既然說到多音字,就需要選擇恰當的讀音了。這裡采用的是一種比較簡單的替換法,就是遇到多音字,轉換為我們期望的一個同音的單音字。可以做成一個清單,遇到哪些,就更新進去,也還算友善。

還要考慮一種情況,就是拼音相同,字不同的,不能當成同樣的字元來處理,否則會出現這樣的排序:

許戈輝,徐靜蕾,許晴。

是不是感覺很奇怪,同姓的居然沒有挨在一塊。是以,在比較到拼音相同時,要繼續比較原來的漢字是否相同。對于這一步的漢字比較,我是直接使用預設的字元順序比較的,就是區分開同音字就行了,沒有繼續考慮筆畫順序什麼的。

3,要按中國人的習慣來,姓按拼音首字母分段,同一段内漢字優先

我們首先關注的是第一個字,也就是姓,按拼音的首字母分割。

對于漢字拼音與英文字母相同的情況,例如:白,B,如何排序?

按首字母分割後,在同一個段内(同一個首字母的情況下),漢字在前,英文字母在後。

4,不是第一個字元時,漢字在所有的字母之前

這一條和第3條很容易混淆,最大的差別就在于,第一個字元,作為姓氏,要按首字母來進行分段,而後面的字元不需要考慮這點。

舉個例子,有3個聯系人:白雪,Baby,崔健。則白雪和Baby這兩個聯系人要排在B這個段内,而崔健排中C段,是以Baby排中崔健之前。排序為:白雪,Baby,崔健。

又有3個聯系人:楊白雪,楊Baby,楊崔健。由于姓是相同的,後面考慮名字時,先排漢字,後排英文字母,是以楊崔健排在楊Baby之前。排序為:楊白雪,楊崔健,楊Baby。

5,排序:漢字、字母、數字、其他

确定了漢字和字母之後,也考慮下其他字元的情況。這裡我也沒有進行很細緻的區分,隻是劃分成4類:最常用的是漢字,然後外國人使用英文字母,還有就是考慮了數字也會有使用的情況,再剩下的就沒有區分了,都當做是“其他”這一項。其排序的優先級,從前面的描述中已經展現了,就是:漢字>字母>數字>其他字元。

6,姓名前後的空格,要去掉

之是以特别提出來說明,是因為我們可能在輸入聯系人時,不小心就輸入了空格。總之,我用我手機中的聯系人進行測試時,就遇到了這個問題。由于空格不在26個字母的範圍類,搞得那個聯系人排到所有字母段之外去了,當時還奇怪了好一會呢。

當然知道了原因,解決起來也很簡單,就是調用一次trim()就好了。

7,怎麼來比較所有字元

知道了這麼多,似乎還是沒有想明白,到底怎麼進行一個完整的排序。要知道,一個人的名字,可能有好幾個字呢。特别是,漢字、英文字母、數字、其他字元混合排序的,雖然機率小,但是我們寫程式的人,要都考慮進來呀。

還有拼音與英文字母,以及字元個數不定,到底要判斷幾次呢,想想就覺得好麻煩啊!

在經過一段時間的混亂後,我忽然想到一個方法,就是遞歸!

既然能比較好一個字元,就可以遞歸調用,來比較多個字元,直到某一個字元串結束。

一個漢字的拼音,與字母的比較,隻在姓名中的第一個字元處使用。對于其他位置上的漢字與字母,使用更基礎的判斷(漢字>字母>數字>其他)就可以确定順序了,根本還用不到拼音呢,隻有在都是漢字時才用到。

8,名字中間的空格,不能去掉

這個問題,是在調試過程中發現的。是由于采用了遞歸,則判斷名稱中每一個字元時,就不能再使用trim了。是以trim隻能最初确定名字時使用一次。

有了上面這麼多考慮,基本上排序算法就梳理清楚了。

下面是實作:

/**
     * 聯系人排序,最終的排序方法
     * */
    private int compareCallNum=;//判讀是否是第一層的比較(遞歸調用中)
    class SortComparator implements Comparator {
        @Override
        public int compare(Object lhs, Object rhs) {

            compareCallNum = ;
            return compareString((String)lhs,(String)rhs);

        }
    }

    //隻比較一個字元,遞歸調用
    public int compareString(String lhs, String rhs) {

        compareCallNum++;

        //判斷第一個字元,漢字最前,其次字母,然後是數字,若有其他符号,放在最後

        String nameA = lhs;//.trim();//注意,由于遞歸調用,是以此處不能再使用trim了
        String nameB = rhs;//.trim();

        //若存在長度為0的情況:
        if((nameA.length()==)&&(nameB.length()==)){
            return ;
        } else if(nameA.length()==){
            return -;
        } else if(nameB.length()==){
            return ;
        }

        String firstStrA = nameA.substring(,);
        String firstStrB = nameB.substring(,);

        //先從類型上來區分:漢字>字母>數字>其他符号,若類型不同,立即出比較的結果
        //但是漢字與字母,由于存在首字母的分段,是以先不區分開
        int typeA = getFirstCharType(nameA);
        int typeB = getFirstCharType(nameB);
        if(typeA>typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return -;//傳回負值,則往前排
        } else if(typeA<typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return ;
        }


        //類型相同,需要進行進一步的比較
        int compareResult ;

        //不是字母與漢字
        if(typeA< && typeB<){
            compareResult = firstStrA.compareTo(firstStrB);
            if(compareResult!=){
                //若不同,立即出來比較結果
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
                return compareResult;
            } else {
                //若相同,則遞歸調用
                return compareString(nameA.substring(),nameB.substring());
            }
        }

        //是字母或漢字

        //若是首字母,先用第一個字母或拼音進行比較
        //否則,先判斷字元類型
        String firstPinyinA = PinYinStringHelper.getFirstPingYin(nameA).substring(, );
        String firstPinyinB = PinYinStringHelper.getFirstPingYin(nameB).substring(, );
        if(compareCallNum==) {
            compareResult = firstPinyinA.compareTo(firstPinyinB);
            if (compareResult != ) {
                LogUtil.logWithMethod(new Exception(), "nameA=" + nameA + " nameB=" + nameB + " compareResult=" + compareResult);
                return compareResult;
            }
        }

        //若首字的第一個字母相同,或不是首字,判斷原字元是漢字還是字母,漢字排在前面
        typeA = getFirstCharType2(nameA);
        typeB = getFirstCharType2(nameB);
        if(typeA>typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return -;
        } else if(typeA<typeB){
            LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
            return ;
        }

        //不是首字母,在字元類型之後判斷,第一個字母或拼音進行比較
        if(compareCallNum!=) {
            compareResult = firstPinyinA.compareTo(firstPinyinB);
            if (compareResult != ) {
                LogUtil.logWithMethod(new Exception(), "nameA=" + nameA + " nameB=" + nameB + " compareResult=" + compareResult);
                return compareResult;
            }
        }

        if(isLetter(nameA)&&isLetter(nameB)) {
            //若是同一個字母,還要比較下大小寫
            compareResult = firstStrA.compareTo(firstStrB);
            if (compareResult != ) {
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB+" compareResult="+compareResult);
                return compareResult;
            }
        }

        if(isHanzi(nameA)&&isHanzi(nameB)) {
            //使用姓的拼音進行比較
            compareResult = PinYinStringHelper.getFirstPingYin(nameA).compareTo(PinYinStringHelper.getFirstPingYin(nameB));
            if (compareResult != ) {
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
                return compareResult;
            }

            //若姓的拼音相同,比較漢字是否相同
            compareResult = firstStrA.compareTo(firstStrB);
            if (compareResult != ) {
                LogUtil.logWithMethod(new Exception(),"nameA="+nameA+" nameB="+nameB);
                return compareResult;
            }
        }
        //若相同,則進行下一個字元的比較(遞歸調用)
        return compareString(nameA.substring(),nameB.substring());
    }
           

排序後的效果,上圖:

仿聯系人的排序

說明:

第一張圖展示了漢字與字母、數字、特殊符号的順序;

第二張圖展示了同音漢字的排序,以及同姓時英文字母排序在後的情況;

第三張圖展示了以數字與特殊字元開頭的聯系人排列位置(就是最後啦);

完整的工程,見如下位址:

https://github.com/lintax2017/ContactListDemo

參考:

http://blog.csdn.net/zpp119/article/details/7976139

http://www.javaapk.com/topics/demo/5894.html

http://www.2cto.com/kf/201311/258190.html