摘要:通過這個方法,我們可以非常順利的在一個數據表中用一個值保存多種情況。了解了這個原理之后,我們只需要在數據庫中保存二進制轉換而來的十進制值,在查詢時,用對比值二進制轉換而來的十進制值去按位異或一下,即可得到我們想要的結果。
例如,某個房間可從[燈,床,桌,椅,杯子,飲水機……]這些器具中挑選,從而組成這個房間的裝潢。我們可能會設計一個房間表,再設計一個器具表,再設計一個關系表,通過這個關系表來保存它們之間的對應關系。但是這樣的效率明顯是比較差的,需要同時查詢三張表才能完成。
為了不適用關系表,我們還可以在房間表中設計一個字段,通過一個有規律的字符串來保存器具表的器具ID,例如:
1,2,3,7
下面,我們提供一種通過一個值來計算即可獲得這一器具組合的結果,方法如下:
array( "1" => "燈" "2" => "床", "4" => "桌", "8" => "椅子", "16" => "飲水機", …… );
如果我們將5保存到數據庫中,我們可以立馬知道,這個房間有“燈”和“桌”,而如果保存的是23,則一定有“燈”“床”“桌”和“飲水機”。
給每一個器具一個給定的值,這個值一定是2的n次方(n>=0),這樣就可以保證相加之后的值可以反解。這個情況的核心原理在于,給定任何數值的前面數值相加和,一定小于當前數值。如何進行反解呢?
例如我們拿到一個值為N,那么我們可以首先找到最大的2^n,確定2^n是一定有的,如果沒有2^n,就不可能相加得到N。
接下來我們獲得M = N - 2^n,找到最大的2^m,再進行M - 2^m,如此推論下去,直到減完為止。
那么怎獲得最大的2^n呢?
$n = (int)log(N,2);
log函數在PHP4+之后內置,用于取對數,返回值為float類型,但我們僅需要整數部分,因此前面加(int)。
例如N=22,那么$n=4,再去計算2^4,就是16。
通過這個方法,我們可以非常順利的在一個數據表中用一個值保存多種情況。但是,這也有一定的適用范圍,比如這些情況最好是固定不變的,2n值不能太大等等。通過這種方法可以用該值進行權重設計,進行排序,但是不能用于條件檢索,比如你想檢索數據庫中包含“床”的房間,你就不好進行檢索,因為大部分房間的該值可能都大于2.所以,在使用這種方法時,應該根據實際需要進行考慮。
更新:
在數據庫中,我們可以使用一種序列化的類二進制字符串來保存多個值,當這個二進制值是以01組成時,實際上就可以換算成為一個十進制數,從而也就實現了一個十進制值保存多種情況的目的。
下面我們來做一個演示。
例如我們在訂票系統中,規定某一個活動每天分為6個場次,每個場次2個小時,因此實際上就把一天的12個小時分為了6份,分別是9:00-11:00,11:00-13:00,13:00-15:00,15:00-17:00,17:00-19:00:19:00-21:00,我們用“xxxxxx”(x取0或1)來表示,現在,我們要記錄這些場次是否全部被定完了,用1表示全部被訂完,所以“010110”就表示11:00-13:00,15:00-17:00,17:00-19:00這三個場次已經被訂完了,不能再對外售票。
我們在數據庫中怎么保存呢?
php提供了將二進制轉換為十進制的函數bindec(),我們先將二進制值轉換為十進制值后,再保存到數據庫中。而當我們要使用時,從數據庫中取出十進制值,再使用decbin()將值轉換為二進制值,當然,我們要補全最后得到的二進制值的位數,也就是前面加0,然后再進行字符串數組處理,進行對比。
在編程世界中,還有一個比較好玩的算法,叫“按位異或”。按位,就是以二進制的形式進行計算,“按位異或”就是兩個位的值不同時返回1,否則返回0。通過這個運算,我們可以得到看上去非常復雜的結果。在php中,運算為“^”。下面我們來進行一下演算。
001011 ^ 011010 = 010001 (1式,注意,開頭的0會被忽略,因此不要把開頭的0也算進來)
提按位異或有什么意義呢?因為二進制值可以和十進制值進行轉換,因此我們將二進制值轉換為十進制值進行按位異或之后,得到的值也是十進制的,我們只有將這些十進制數轉換為二進制字串后,才能發現規律,但是如果我們直接用十進制進行計算,卻能快速得到結果。
下面我們就來演算一次,我們拿(1式)來看。如果將二進制數轉換為十進制,我們就能得到
11 ^ 26 = 17
那事實的結果是不是這樣呢?你可以在你的php程序中寫上:
是的,結果就是這樣。可是,這個復雜的運算有什么用呢?它可以用于比較。比如我們的數據庫中存放了11,轉換為二進制就是“001011”,也就是表示這一天的場次中,對應的那三個時段已經滿票了。但是如果我們現在正好要進行對比,看看這一天中17:00-19:00這個時段是否滿票,我們怎么能準確知道11這個值轉換為001011后,第5個位上的值是否為1呢?
我們只需要用這種思路來解決即可:
xxxxxx ^ 000010 = ?
其中xxxxxx是我們要對比的值,比如當它等于11時,也就是001011時,等式的右邊會得到001001(9)。我們再來看另一個算式:
xxxxxx ^ 000000 = ?
等式右邊會得到本身。
如果我們再用001001(9)去按位異或000010,則會得到001011(11)。
我們得到的結論就是,凡是用xxxxx去按位異或yyyyyy(其中只有一個y為1,其他全為0),得到的結果比自身小的,則對應位置上的值為1,得到的結果比自身大的,對應的位置上為0。通過這種方法,也就找到了哪個時間段是被訂滿票的。
為什么大于自身的,對應的位置上就一定為0呢?因為0^1=1,而二進制數是01構成的,也就是說0和1碰上0時,都不會變化,而只有0碰上1時才會變化。說白了,用任何一個二進制數去按位異或000100,結果發生的情況就兩種,一種是第四個位置上的值由1變為0(結果值相對于本身值而言),這種情況下該值變小,一種是第四個位置上的值由0變為1,這種情況下該值變大。了解了這個原理之后,我們只需要在數據庫中保存二進制轉換而來的十進制值,在查詢時,用對比值(二進制轉換而來的十進制值)去按位異或一下,即可得到我們想要的結果。
我們創建如下表結構,sale_over在實際存儲時,我們轉換為十進制整數進行存儲,這里方便演示用二進制表示。每次在用戶下訂單時對票數進行檢查,如果該時段已經有20張票被訂出,就在下表中更新一條記錄,把對應的時段改為1.
tablename = objectorder
id | object_id | day | sale_over |
1 | 5 | 2015-08-23 | 011000 |
2 | 8 | 2015-08-24 | 100101 |
3 | 5 | 2015-08-25 | 010001 |
例如:
SELECT COUNT(id) FROM object_order WHERE object_id=8 AND day="2015-08-20" AND (hours ^ 2)這樣就可以判斷出8月20號這天17:00-19:00這個時間段是否被訂滿(如果返回1,則表示被訂滿了)。
如果我們不滿意用大小比較來進行判斷,我們還可以深入發現,按位異或結果與原值之間的差值,正好是用來異或的值,也就是滿足下面的等式:
|m ^ n - m| = n (n為yyyyyy,只有一個y為1,其他為0)|x|是指絕對值,當不取覺得值,得到的為負數時,說明結果變小了,那么原值對應的位置上也就是1,而如果得到的為正數,說明結果變大,對應的位置上就為0。所以,上述sql,我們還可以這樣去改:
**SELECT COUNT(id) FROM object_order WHERE object_id=8 AND day="2015-08-20" AND (hours ^ 2 + 2)=hours;**如果查到了結果,說明8這個活動8月20號這天17:00-19:00這個時間段被訂滿。
這種魔術般的使用方法,你是否思考過呢?
再議
實際上,一個二進制數,我們將它轉換為十進制時,將它的各個位置值(從右往左,以0為開始)作為次數求2的次冪,再乘以該位置上的數,再相加,即得到該二進制數對應的十進制數,例如:
10100 = 0(2^0) + 0(2^1) + 1(2^3) + 0(2^4) + 1*(2^5) = 8 + 32 = 40
這樣去觀察,就發現實際上8和32,就是我們第一次接觸這種算法時,將它們作為一個數組的索引值,進行物品的索引進行計算。
接下來,我們要更換場景,每個時段僅可以被一個人預訂,用戶每一次下訂單完成之后,形成一條記錄,這些記錄以上述形式存儲,得到如下訂單數據表:
tablename = userorder
id user_id object_id day hours 1 2 5 2015-08-23 011000 2 3 8 2015-08-24 100000 3 2 5 2015-08-24 000001 類似這樣的訂單記錄,hours字段中每個位置上的1最多出現1次,怎么樣確定某一天的所有票都已經定出去了呢?
其實這是最簡單的,就是對該字段進行求和,例如:
SELECT SUM(hours) FROM user_order WHERE object_id=8 AND day="2015-08-20";如果最終得到的值為111111,也就是十進制的63,則說明該天各個時段已訂滿,不能再進行預訂。
最后一種情況則是對上面兩張場景的結合,也就是每個時段最多可以被預訂20張票,數據庫中記錄的是單個用戶的訂單。
當然,遇到這種情況,其實我們可以準備兩張表,一張是用戶的訂單表:
tablename = userorder
id user_id object_id day hours 1 2 5 2015-08-23 011000 2 3 8 2015-08-24 100000 3 2 5 2015-08-24 000001 (第一條記錄表示用戶2在2015-08-23這天預訂了5這個活動的11點13點這兩個時段的票)
一張用來在每次用戶訂單完成時,對該時段進行判斷,如果這個時段已經賣出20張,就改為1,進行更新操作的場次預訂情況表:
tablename = objectorder
id object_id day sale_over 1 5 2015-08-23 011000 2 8 2015-08-24 100101 3 5 2015-08-25 010001 但是這樣的話,我們通過該表,僅能判斷是否賣完,而不知道已經賣了多少張。為了解決這個問題,我們夸張的做法是,直接在這個表的基礎上進行擴展,增加20個字段,每個字段對應一個時段,用來記錄所賣出的票數,但是這樣實在太蠢了。由于二進制方式,無法在每個位置上表示實際的值,例如在第2個位置上用3來表示賣出3張,這是我們無法做到的,所以,我們可以通過前面一張用戶下的訂單列表來進行計算,從而找出某個位置上是否已經存在20個1.
實際上,我們現在要解決的,就是查出每個時段已經訂出了多少張票。
我們可以用
SELECT COUNT(id) FROM user_order WHERE object_id=8 AND day="2015-08-20" AND (hours ^ 2 + 2)=hours;這種方法就可以查出來某個時段的被訂數量,如果返回值等于20,則說明該時段已經被定完了。但是,我們如何從所有的記錄中,找出那些天的席位被全部定光呢?因為我們不打算使用objectorder表來記錄,而是想直接通過userorder進行查詢,所以我們不僅要判斷某個位置上的為1的記錄數是否為20,而且要判斷所有的位置。
最笨的方法就是連續判斷6次,對每個位置都進行統計,最終進行判斷。但是這明顯不符合我們的要求。
實際上,我們仍然使用求和即可完成,我們在前面進行求和時,只需要用111111進行對比,也就是十進制的63進行對比,而這次,我們用20個111111進行對比,也就是63*20 = 1260進行對比即可。
SELECT SUM(hours) FROM user_order WHERE object_id=8 AND day="2015-08-20";如果得到的返回值等于1260,說明這一天的所有場次已經完全訂出去了。
用這種方法處理數據庫中保存有規律的多種情況保存,就變得輕松有趣了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/21003.html
摘要:通過這個方法,我們可以非常順利的在一個數據表中用一個值保存多種情況。了解了這個原理之后,我們只需要在數據庫中保存二進制轉換而來的十進制值,在查詢時,用對比值二進制轉換而來的十進制值去按位異或一下,即可得到我們想要的結果。 例如,某個房間可從[燈,床,桌,椅,杯子,飲水機……]這些器具中挑選,從而組成這個房間的裝潢。我們可能會設計一個房間表,再設計一個器具表,再設計一個關系表,通過這個關...
摘要:有符號的右移操作符由兩個大于符號表示這個操作符的含義就是將數值的位向右移指定的位數同時保留符號位的值正負號標記有符號的右移操作符與左移操作符剛好相反比如向右移動位就是同樣的在移位的過程中也會出 位操作符的基本概念 因為ECMAscript中所有數值都是以IEEE-75464格式存儲,所以才會誕生了位操作符的概念. 位操作符作用于最基本的層次上,因為數值按位存儲,所以位操作符的作用也就是...
摘要:總結對于原二進制數來說,是不變,是反轉。的位數對應原二進制數的位數,對各位進行屏蔽,全部置。左移左移與右移比較類似,是將目標二進制數字向左右移動相應的位數。語言中的邏輯運算符按位與,按位或,按位異或,取反,左右移位不完全手冊立創開源 ...
閱讀 3753·2021-08-11 11:16
閱讀 1621·2019-08-30 15:44
閱讀 1995·2019-08-29 18:45
閱讀 2267·2019-08-26 18:18
閱讀 996·2019-08-26 13:37
閱讀 1565·2019-08-26 11:43
閱讀 2109·2019-08-26 11:34
閱讀 372·2019-08-26 10:59