使用位運算實現(xiàn)網(wǎng)頁中的過濾、篩選功能實例
最近屌絲的公司想要為以前的那個網(wǎng)頁產(chǎn)品加上一個過濾的功能,廢話不多說,直接看篩選的界面是啥樣的吧:
可以看出,我們的Message分為Critical、Error等6種類型?,F(xiàn)在需要進行過濾,用戶可以選擇查看其中的一項或多項。
這種需求是很常見的,在大多數(shù)情況下,如果可選項是不固定的(比如對于學生信息管理系統(tǒng),按照班級進行篩選),那么就可能需要借助復雜的SQL語句。例如我們可以寫成如下這種形式:
SELECT * FROM [Student] WHERE [ClassId] = 1 OR [ClassId] = 2 OR...
這樣就要對SQL語句進行拼接,根據(jù)用戶選擇了哪些內(nèi)容來動態(tài)生成SQL語句。
假如不是數(shù)據(jù)庫操作,而是內(nèi)存中一片數(shù)據(jù)的判斷,假如用戶選擇的內(nèi)容存放在Selected[]數(shù)組中,那么對于需要判斷的每一項,我們都需要進行一連串的判斷操作,需要寫一個雙重循環(huán)。不僅寫起來比較復雜,如果項目過多在效率上也令人堪憂。
如果我們需求是像上圖那樣,Type是固定的并且也不會很多,那么有沒有一種比較簡單的方式呢?
從標題可以看出,可以使用位運算。在各類高級語言的源代碼中大量地運用了這種方式。對于可枚舉類型,取值不是順序的1、2、3、4,而是1、2、4、8按照2的倍數(shù)增長,這就是使用二進制位進行判斷。
現(xiàn)在我用我剛做的東西來簡單敘述一下如何實現(xiàn)吧。當然實現(xiàn)這個很簡單,在這里記錄更多地是為了增加博文的數(shù)量^_^。
在.NET中,一個int類型占4個字節(jié),也就是32位,除去符號位(當然也可以使用無符號整數(shù),這里為了簡單期間都使用int),每一位都可以標識一個消息的類型。為此,我們定義枚舉類型如下:
public enum CategoryType: int
{
Unknown = 1,
Critical = 2,
Error = 4,
Warning = 8,
Information = 16,
Verbose = 32,
Other = 1024
}
如果用二進制表示,這些消息分別是0000 0001、0000 0010、0000 0100、0000 1000、00010000、…… 假如用戶選擇了Unknown、Error和Waring三種類型,把這三個數(shù)字相加,就是1+4+8=13,二進制表示就是0000 1101。為1的位正好對應了用戶選擇的類型,為0的位正好對用用戶沒選的類型。
在客戶端,使用Javascript就可以使用下面這種簡單的方式計算用戶選擇的值:
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;
當然,由于這種特殊的設計,使用加法運算的效果是完全相同的。
這樣把用戶選擇的內(nèi)容傳給服務器就是一個簡單的整數(shù),比如上面的13表示用戶選擇的三種類型。而且在數(shù)學上可以證明,對于每一個整數(shù),對應的枚舉類型的組合是唯一的(要證明看似很難,但如果使用二進制進行思考便會顯而易見)。
在服務器端對每種消息進行判斷時,只需要使用與運算,便能知道消息是否在用戶選擇的范圍內(nèi)(只要結(jié)果不等于0,說明某個枚舉類型對應的那一位在相加的結(jié)果中。如果使用符號位,那么可能會出現(xiàn)負數(shù))。
舉兩個簡單的例子吧,假如客戶端傳過來的是13(0000 1101),某一種消息是Waring=8(0000 1000),相與的結(jié)果為8(0000 1000)與消息的類型相同(只需判斷是否不等于0即可)。對于消息為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];
}
對于要選出所有的類型的情況,只需要把上述categoryFilter設置為全部為1的數(shù)字即可(0x7fffffff,如果使用符號位就是0xffffffff)。
在客戶段根據(jù)這個相加后的結(jié)果進行控件綁定的使用,要判斷某個復選框是否應該選中時,按照相同的邏輯相與即可:
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);
需要注意的是,這個方法不是萬能的。如果可選項是不固定的,使用位運算有可能反而會很麻煩,因為我們要編寫程序存儲每一種選項對應的數(shù)字。
如果可選項是固定的但是數(shù)量很多(比如幾千種),那么我們可以用一串整數(shù)進行表示,也會很方便(當然顯示也不會有這樣變態(tài)的篩選功能,即使有用戶也不大可能在幾千種里面去選,如果出現(xiàn)這種情況,只能說設計有問題)。
這種做法有一個比較大的弊端,就是改動篩選內(nèi)容時服務器和客戶端都會改。不過還好改動不會太大,只需要改變服務器的enum和客戶端的控件即可……
當然這種思想也是今后設計程序時的一種很好參考。
相關(guān)文章
C#操作數(shù)據(jù)庫總結(jié)(vs2005+sql2005)
C#操作數(shù)據(jù)庫總結(jié),每次做項目都會用到數(shù)據(jù)庫,對數(shù)據(jù)庫的操作都是糊里糊涂從書里找代碼用。通過昨天晚上與今天早上的努力,把數(shù)據(jù)庫的操作整理了一下,下面把整理結(jié)果做個小結(jié)2012-09-09