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