使用位運(yùn)算實(shí)現(xiàn)網(wǎng)頁中的過濾、篩選功能實(shí)例
最近屌絲的公司想要為以前的那個(gè)網(wǎng)頁產(chǎn)品加上一個(gè)過濾的功能,廢話不多說,直接看篩選的界面是啥樣的吧:
可以看出,我們的Message分為Critical、Error等6種類型。現(xiàn)在需要進(jìn)行過濾,用戶可以選擇查看其中的一項(xiàng)或多項(xiàng)。
這種需求是很常見的,在大多數(shù)情況下,如果可選項(xiàng)是不固定的(比如對(duì)于學(xué)生信息管理系統(tǒng),按照班級(jí)進(jìn)行篩選),那么就可能需要借助復(fù)雜的SQL語句。例如我們可以寫成如下這種形式:
SELECT * FROM [Student] WHERE [ClassId] = 1 OR [ClassId] = 2 OR...
這樣就要對(duì)SQL語句進(jìn)行拼接,根據(jù)用戶選擇了哪些內(nèi)容來動(dòng)態(tài)生成SQL語句。
假如不是數(shù)據(jù)庫操作,而是內(nèi)存中一片數(shù)據(jù)的判斷,假如用戶選擇的內(nèi)容存放在Selected[]數(shù)組中,那么對(duì)于需要判斷的每一項(xiàng),我們都需要進(jìn)行一連串的判斷操作,需要寫一個(gè)雙重循環(huán)。不僅寫起來比較復(fù)雜,如果項(xiàng)目過多在效率上也令人堪憂。
如果我們需求是像上圖那樣,Type是固定的并且也不會(huì)很多,那么有沒有一種比較簡(jiǎn)單的方式呢?
從標(biāo)題可以看出,可以使用位運(yùn)算。在各類高級(jí)語言的源代碼中大量地運(yùn)用了這種方式。對(duì)于可枚舉類型,取值不是順序的1、2、3、4,而是1、2、4、8按照2的倍數(shù)增長,這就是使用二進(jìn)制位進(jìn)行判斷。
現(xiàn)在我用我剛做的東西來簡(jiǎn)單敘述一下如何實(shí)現(xiàn)吧。當(dāng)然實(shí)現(xiàn)這個(gè)很簡(jiǎn)單,在這里記錄更多地是為了增加博文的數(shù)量^_^。
在.NET中,一個(gè)int類型占4個(gè)字節(jié),也就是32位,除去符號(hào)位(當(dāng)然也可以使用無符號(hào)整數(shù),這里為了簡(jiǎn)單期間都使用int),每一位都可以標(biāo)識(shí)一個(gè)消息的類型。為此,我們定義枚舉類型如下:
public enum CategoryType: int
{
Unknown = 1,
Critical = 2,
Error = 4,
Warning = 8,
Information = 16,
Verbose = 32,
Other = 1024
}
如果用二進(jìn)制表示,這些消息分別是0000 0001、0000 0010、0000 0100、0000 1000、00010000、…… 假如用戶選擇了Unknown、Error和Waring三種類型,把這三個(gè)數(shù)字相加,就是1+4+8=13,二進(jìn)制表示就是0000 1101。為1的位正好對(duì)應(yīng)了用戶選擇的類型,為0的位正好對(duì)用用戶沒選的類型。
在客戶端,使用Javascript就可以使用下面這種簡(jiǎn)單的方式計(jì)算用戶選擇的值:
var category = 0;
if (filterVM.ckType2Checked()) category |= 2;
if (filterVM.ckType4Checked()) category |= 4;
if (filterVM.ckType8Checked()) category |= 8;
if (filterVM.ckType16Checked()) category |= 16;
if (filterVM.ckType32Checked()) category |= 32;
if (filterVM.ckType1024Checked()) category |= 1024;
當(dāng)然,由于這種特殊的設(shè)計(jì),使用加法運(yùn)算的效果是完全相同的。
這樣把用戶選擇的內(nèi)容傳給服務(wù)器就是一個(gè)簡(jiǎn)單的整數(shù),比如上面的13表示用戶選擇的三種類型。而且在數(shù)學(xué)上可以證明,對(duì)于每一個(gè)整數(shù),對(duì)應(yīng)的枚舉類型的組合是唯一的(要證明看似很難,但如果使用二進(jìn)制進(jìn)行思考便會(huì)顯而易見)。
在服務(wù)器端對(duì)每種消息進(jìn)行判斷時(shí),只需要使用與運(yùn)算,便能知道消息是否在用戶選擇的范圍內(nèi)(只要結(jié)果不等于0,說明某個(gè)枚舉類型對(duì)應(yīng)的那一位在相加的結(jié)果中。如果使用符號(hào)位,那么可能會(huì)出現(xiàn)負(fù)數(shù))。
舉兩個(gè)簡(jiǎn)單的例子吧,假如客戶端傳過來的是13(0000 1101),某一種消息是Waring=8(0000 1000),相與的結(jié)果為8(0000 1000)與消息的類型相同(只需判斷是否不等于0即可)。對(duì)于消息為16(0001 0000)的情況,相與的結(jié)果肯定是0。代碼如下:
for (int i = 0; i < list.Count; i++)
{
if (((int)list[i].Category & categoryFilter) > 0 && ((int)list[i].Direction & directionFilter) > 0)
yield return list[i];
}
對(duì)于要選出所有的類型的情況,只需要把上述categoryFilter設(shè)置為全部為1的數(shù)字即可(0x7fffffff,如果使用符號(hào)位就是0xffffffff)。
在客戶段根據(jù)這個(gè)相加后的結(jié)果進(jìn)行控件綁定的使用,要判斷某個(gè)復(fù)選框是否應(yīng)該選中時(shí),按照相同的邏輯相與即可:
this.ckType2Checked((category & 2) > 0);
this.ckType4Checked((category & 4) > 0);
this.ckType8Checked((category & 8) > 0);
this.ckType16Checked((category & 16) > 0);
this.ckType32Checked((category & 32) > 0);
this.ckType1024Checked((category & 1024) > 0);
需要注意的是,這個(gè)方法不是萬能的。如果可選項(xiàng)是不固定的,使用位運(yùn)算有可能反而會(huì)很麻煩,因?yàn)槲覀円帉懗绦虼鎯?chǔ)每一種選項(xiàng)對(duì)應(yīng)的數(shù)字。
如果可選項(xiàng)是固定的但是數(shù)量很多(比如幾千種),那么我們可以用一串整數(shù)進(jìn)行表示,也會(huì)很方便(當(dāng)然顯示也不會(huì)有這樣變態(tài)的篩選功能,即使有用戶也不大可能在幾千種里面去選,如果出現(xiàn)這種情況,只能說設(shè)計(jì)有問題)。
這種做法有一個(gè)比較大的弊端,就是改動(dòng)篩選內(nèi)容時(shí)服務(wù)器和客戶端都會(huì)改。不過還好改動(dòng)不會(huì)太大,只需要改變服務(wù)器的enum和客戶端的控件即可……
當(dāng)然這種思想也是今后設(shè)計(jì)程序時(shí)的一種很好參考。
相關(guān)文章
詳細(xì)聊聊C#的并發(fā)機(jī)制優(yōu)秀在哪
并發(fā)其實(shí)是一個(gè)很泛的概念,字面意思就是"同時(shí)做多件事",不過方式有所不同,下面這篇文章主要給大家介紹了關(guān)于C#并發(fā)機(jī)制的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02C#發(fā)送Get、Post請(qǐng)求(帶參數(shù))
本文主要介紹了C#發(fā)送Get、Post請(qǐng)求,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09基于Aforge攝像頭調(diào)用簡(jiǎn)單實(shí)例
這篇文章主要為大家詳細(xì)介紹了基于Aforge攝像頭調(diào)用的簡(jiǎn)單實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10C# 中文簡(jiǎn)體轉(zhuǎn)繁體實(shí)現(xiàn)代碼
C# 中文簡(jiǎn)體轉(zhuǎn)繁體實(shí)現(xiàn)代碼,需要的朋友可以參考一下2013-02-02C#操作數(shù)據(jù)庫總結(jié)(vs2005+sql2005)
C#操作數(shù)據(jù)庫總結(jié),每次做項(xiàng)目都會(huì)用到數(shù)據(jù)庫,對(duì)數(shù)據(jù)庫的操作都是糊里糊涂從書里找代碼用。通過昨天晚上與今天早上的努力,把數(shù)據(jù)庫的操作整理了一下,下面把整理結(jié)果做個(gè)小結(jié)2012-09-09