实现城市列表的排序及模糊查询
作者 |?wustor
地址 |?https://www.jianshu.com/p/0ea45116f475
聲明 |?本文是 wustor?原創(chuàng),已獲授權(quán)發(fā)布,未經(jīng)原作者允許請勿轉(zhuǎn)載
概述
項目需求中有一個需求,是用戶輸入的地址進行智能匹配,包含拼音匹配跟文字匹配,下面先展示一下需要實現(xiàn)的效果
其實看到這個需求,最開始的想法其實是很偷懶的,就是讓服務(wù)端寫一個接口,然后進行接口調(diào)用,不過在沒網(wǎng)的時候就尷尬了,輸入是沒有提示的,所以這種方式其實不大好,再加上城市地址庫一旦確定基本上就是不會輕易改變的,基于這幾點考慮,打算做一個本地搜索。
正文
確定實現(xiàn)方式之后,其實思路就比較清晰了,首先請求一次接口的數(shù)據(jù),然后直接放在本地,再加上項目的需求,所以基本的功能點如下:
主要有以下2點:
對接口返回的數(shù)據(jù)進行排序
根據(jù)排序進行分組
對用戶的輸入進行智能匹配
排序的實現(xiàn)
提到排序,其實首先會點到Java中的兩個接口Comparable跟Comparator
Comparable
public interface Comparable<T> {? ?public int compareTo(T o);
}
Comparable實際上就只是個接口,定義了一個compareTo方法,挺簡單的,不過在使用的時候需要注意一下幾點:
兩個元素排序:需要實現(xiàn)compareTo方法,并且有一個int返回值,表明返回的結(jié)果,具體比較的規(guī)則可以根據(jù)需求自己定義,可以實現(xiàn)相同類型的參數(shù)進行比較.
多個元素排序:這里用地比較多的情況就是排序,JDK提供了一個工具類Arrays,調(diào)用Arrays.sort(Object[] a);只需要傳入的數(shù)組實現(xiàn)了Comparable接口即可對傳入的數(shù)組進行排序,這個時候我們注意到,Arrays.sort有很多重載方法,我們可以看一下
有很多我們熟悉的基本類型,int,byte,char,這些貌似跟Comparable沒有什么關(guān)系,不過由于Java是面向?qū)ο蟮?#xff0c;所以對于基本類型有一個裝箱拆箱操作,當(dāng)看到基本類型的時候,應(yīng)該多跟他們的包裝類聯(lián)系起來,那就隨便找?guī)讉€,int的包裝類Integer進行byte的包裝類Byte
? ? ?public int compareTo(Integer anotherInteger) {
? ? ? ?return compare(this.value, anotherInteger.value);
? ?}
}
public final class Byte extends Number implements Comparable<Byte>{
? ? ?public int compareTo(Byte anotherByte) {
? ? ? ?return compare(this.value, anotherByte.value);
? ?}
}
public finalclass Character implements java.io.Serializable, Comparable<Character>{
? ? public int compareTo(Character anotherCharacter) {
? ? ? ?return compare(this.value, anotherCharacter.value);
? ?}
}
原來,他們的包裝類都實現(xiàn)了Comparable接口,所以理清了,可以直接調(diào)用Arrays的sort方法對這些基本類型進行排序,當(dāng)然,這里的排序都是基于包裝類自身實現(xiàn)的排序算法,是固定不變的,如果是我們自定義的對象的話,需要重寫compare方法。
Comparator
public interface Comparator<T> {? ?int compare(T o1, T o2);
? ?boolean equals(Object obj);
}
Comparator的方法比Comparable要多地多,這里選擇了compare跟equals兩個方法,compare很好理解,用來比較兩個對象,equals是用來比較兩個comparator的,如果傳入的對象也是一個Comparator并且他們的排序規(guī)則也是一樣的,則equals方法返回true,否則返回false.
兩個元素:直接傳入對象,即可比較
多個元素:Collections提供了sort方法,傳入一個list,跟一個comparator
? ? ? ?if (list.getClass() == ArrayList.class) {
? ? ? ? ? ?Arrays.sort(((ArrayList) list).elementData, 0, list.size(), (Comparator) c);
? ? ? ? ? ?return;
? ? ? ?}
? ? ? ?Object[] a = list.toArray();
? ? ? ?Arrays.sort(a, (Comparator)c);
? ? ? ?ListIterator<T> i = list.listIterator();
? ? ? ?for (int j=0; j<a.length; j++) {
? ? ? ? ? ?i.next();
? ? ? ? ? ?i.set((T)a[j]);
? ? ? ?}
? ?}
然后方法里面還是調(diào)用了Arrays.sort,畢竟集合也是數(shù)組,最終還是調(diào)用了數(shù)組的排序方法。
對比分析
Comparator是在類的外部進行排序,Comparable是在類的內(nèi)部進行排序
Comparator比較適合對于多個類進行排序,只需要實現(xiàn)一個Comparator就可以,Comparable則需要在每個類中實現(xiàn)Comparable接口
開始排序
排序通常的做法是對字母進行排序,但是接口返回的是文字,所以需要將文字轉(zhuǎn)換成拼音,并且拿到首字母,才能進行排序,這里用到了一個第三方庫TinyPinyin,適用于Java和Android的快速漢字轉(zhuǎn)拼音庫。
以武漢為例
用tinypinyin將所有的城市名稱轉(zhuǎn)換成拼音,用3個字段分別保存W,WH,WUHAN,其中W用來進行排序分組,WH是用來進行簡拼匹配,WUHAN是用來進行全拼匹配
將城市列表的數(shù)據(jù)根據(jù)首字母安裝ABCD的順序進行排序,對于無法獲取拼音的通過"#"進行標(biāo)識
然后再進行二次分組,ABCD各位一大組,插入一個titleA,titleB,titleC,通過不同的type來在Recyclerview中進行type區(qū)分
這些其實沒什么難度,下面貼一下Comparator的代碼,自定義了compare方法,
@Override
? ? ? ?public int compare(CityBean c1, CityBean c2) {
? ? ? ? ? ?if (c1.getPinyinFirst().equals("#")) {
? ? ? ? ? ? ? ?return 1;
? ? ? ? ? ?} else if (c2.getPinyinFirst().equals("#")) {
? ? ? ? ? ? ? ?return -1;
? ? ? ? ? ?}
? ? ? ? ? ?return c1.getPinyinFirst().compareTo(c2.getPinyinFirst());
? ? ? ?}
? ?}
查找算法
先定義一下查找規(guī)則
如果是漢字,則采用精準(zhǔn)查找
如果是字母,當(dāng)字母數(shù)量較小(3個以內(nèi))的時候,優(yōu)先進行簡拼,然后全拼,字母較多,使用全拼查找
正則匹配查找算法
? ? ? ?if (RegexUtils.isEnglishAlphabet(inputStr)) {
? ? ? ? ? ?//拼音模糊匹配
? ? ? ? ? ?findByEN(inputStr, old, target);
? ? ? ?} else {
? ? ? ? ? ?//含有中文精準(zhǔn)匹配
? ? ? ? ? ?findByCN(inputStr, old, target);
? ? ? ?}
? ?}
中文匹配
? ? ? ?for (int i = 0; i < mBodyDatas.size(); i++) {
? ? ? ? ? ?CityBean cityBean = mBodyDatas.get(i);
? ? ? ? ? ?if (!TextUtils.isEmpty(cityBean.getRegionName()) && cityBean.getRegionName().contains(inputStr)) {
? ? ? ? ? ? ? ?searchResult.add(cityBean);
? ? ? ? ? ?}
? ? ? ?}
? ?}
字母匹配
? ? ? ?//把輸入的內(nèi)容變?yōu)榇髮?/span>
? ? ? ?String searPinyin = PinYinUtil.transformPinYin(inputStr);
? ? ? ?//搜索字符串的長度
? ? ? ?int searLength = searPinyin.length();
? ? ? ?//搜索的第一個大寫字母
? ? ? ?for (int i = 0; i < mBodyDatas.size(); i++) {
? ? ? ? ? ?CityBean cityBean = mBodyDatas.get(i);
? ? ? ? ? ?//如果輸入的每一個字母都和名字的首字母一樣,那就可以匹配比如:武漢,WH
? ? ? ? ? ?if (cityBean.getMatchPin().contains(searPinyin)) {
? ? ? ? ? ? ? ?searchResult.add(cityBean);
? ? ? ? ? ?} else {
? ? ? ? ? ? ? ?boolean isMatch = false;
? ? ? ? ? ? ? ?//先去匹配單個字,比如武漢WU,HAN.輸入WU,肯定匹配第一個
? ? ? ? ? ? ? ?for (int j = 0; j < cityBean.getNamePinyinList().size(); j++) {
? ? ? ? ? ? ? ? ? ?String namePinyinPer = cityBean.getNamePinyinList().get(j);
? ? ? ? ? ? ? ? ? ?if (!TextUtils.isEmpty(namePinyinPer) && namePinyinPer.startsWith(searPinyin)) {
? ? ? ? ? ? ? ? ? ? ? ?//符合的話就是當(dāng)前字匹配成功
? ? ? ? ? ? ? ? ? ? ? ?searchResult.add(cityBean);
? ? ? ? ? ? ? ? ? ? ? ?isMatch = true;
? ? ? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?if (isMatch) {
? ? ? ? ? ? ? ? ? ?continue;
? ? ? ? ? ? ? ?}
// ? ? ? ? ? ? ? ?根據(jù)拼音包含來實現(xiàn),比如武漢:WUHAN,輸入WUHA或者WUHAN。
? ? ? ? ? ? ? ?if (!TextUtils.isEmpty(cityBean.getNamePinYin()) && cityBean.getNamePinYin().contains(searPinyin)) {
? ? ? ? ? ? ? ? ? ?//這樣的話就要從每個字的拼音開始匹配起
? ? ? ? ? ? ? ? ? ?for (int j = 0; j < cityBean.getNamePinyinList().size(); j++) {
? ? ? ? ? ? ? ? ? ? ? ?StringBuilder sbMatch = new StringBuilder();
? ? ? ? ? ? ? ? ? ? ? ?for (int k = j; k < cityBean.getNamePinyinList().size(); k++) {
? ? ? ? ? ? ? ? ? ? ? ? ? ?sbMatch.append(cityBean.getNamePinyinList().get(k));
? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ? ? ?if (sbMatch.toString().startsWith(searPinyin)) {
? ? ? ? ? ? ? ? ? ? ? ? ? ?//匹配成功
? ? ? ? ? ? ? ? ? ? ? ? ? ?int length = 0;
? ? ? ? ? ? ? ? ? ? ? ? ? ?//比如輸入是WUH,或者WUHA,或者WUHAN,這些都可以匹配上
? ? ? ? ? ? ? ? ? ? ? ? ? ?for (int k = j; k < cityBean.getNamePinyinList().size(); k++) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?length = length + cityBean.getNamePinyinList().get(k).length();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?if (length >= searLength) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ? ? ? ? ?//有可能重復(fù)匹配
? ? ? ? ? ? ? ? ? ? ? ? ? ?if (!searchResult.contains(cityBean))
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?searchResult.add(cityBean);
? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
? ?}
由于我是在內(nèi)存中進行匹配查找的,這樣雖然效率比較高,但是進行匹配的時候,過多地使用了for循環(huán),整體的性能不是很好,后續(xù)會嘗試著通過Sqlite進行查找,這樣的話,效率可能會高一下,感興趣的可以優(yōu)化一下。
源碼下載
https://github.com/wustor/Localsearchdemo
與之相關(guān)
2017 | 我在 5 個月時間里分享了 98 篇文章
總結(jié)
以上是生活随笔為你收集整理的实现城市列表的排序及模糊查询的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ffmpeg4.4项目学习--音视频基本
- 下一篇: 什么是E-mark认证?ECE认证区别及