文章目录
TANE算法
导入包
from pandas import *
from collections import defaultdict#字典类
import numpy as NP
import sys
# import pdb
# pdb.set_trace()
等价类划分:list_duplicates
def list_duplicates(seq):
tally = defaultdict(list)
for i,item in enumerate(seq):#这两行核心代码实现了合并相同元组:等价类的划分
tally[item].append(i)#只有item一样才能实现合并
return ((key,locs) for key,locs in tally.items()
if len(locs)>0)
#测试:对属性集A的划分结果
for element in list_duplicates(data2D['A'].tolist()):
print(element)
发现右方集:findCplus
Question:计算cplus的过程只用到了借助上层的cplus,那么刚开始符合cplus定义的那一层是如何定义的呢?从第几层开始呢?如何通过调试函数观察变化过程?
def findCplus(x): # this computes the Cplus of x as an intersection of smaller Cplus sets
global dictCplus
thesets=[]
for a in x:
#计算的过程涉及到了递归调用
if x.replace(a,'') in dictCplus.keys():
temp = dictCplus[x.replace(a,'')]
else:
temp=findCplus(x.replace(a,'')) # compute C+(X\{A}) for each A at a time
#dictCplus[x.replace(a,'')] = temp
thesets.insert(0, set(temp))
if list(set.intersection(*thesets)) == []:
cplus = []
else:
cplus = list(set.intersection(*thesets)) # compute the intersection in line 2 of pseudocode
return cplus
计算函数依赖:compute_dependencies
- 初始时候,dictCplus只有{‘NULL’: listofcolumns},计算第1层时,由第0层交集,因此,结果为R,真正的计算cplus是从第二层开始
- Q 按照代码的逻辑,cplus(x)中应该不包含x以外的元素,为什么输出的结果不合格, 因为未通过FD验证,只有一个属性,肯定没法通过。当调用第二层时,输出的结果自然合格了。
- 对L2计算依赖时,AB,BD在代码里面怎么体现这种组合的属性?
通过lever体现,lever为当前层属性集的列表,L[i+1] = generate_next_level(L[i])
- 为什么说A∈X,如果A∈C+(X)并且X\{A}->A成立,则可认为X\{A}->A为最小非平凡函数依赖。
- A∈X,X\{A}->A依赖成立
- A∈C+(X),A不依赖X的子集。解释如下:若A不属于Cplus,则存在B,X\B->B,那么X->A显然不是最小,因为有更新的X\B->A。若A属于Cplus,则不存在这样的B,即A不依赖X的子集,于是,X->A是最小的。
def compute_dependencies(level, listofcols):#参数为Li层,即当前层的属性?还是直接A,B,C,D
global dictCplus#属性-右方集dict
global finallistofFDs#FD List
global listofcolumns#属性集List
#FUN1:计算所有X∈Li的右方集Cplus
#通过上层结点{A})计算当前层的每个X的Cplus(X)
#或通过computeCplus
print(listofcols)
for x in level:
thesets=[]
for a in x:
if x.replace(a,'') in dictCplus.keys():#如果Cplus(X\A)已经在当前右方集List中
temp = dictCplus[x.replace(a,'')]#temp存入的是Cplus(X\A)---即X\A的右集合
else:#否则,计算右方集
temp=computeCplus(x.replace(a,'')) # compute C+(X\{A}) for each A at a time
dictCplus[x.replace(a,'')] = temp#存入dictCplus中
thesets.insert(0, set(temp))#通过set,将temp转换为集合,再将该对象插入到列表的第0个位置
if list(set.intersection(*thesets)) == []:#set.intersection(set1, set2 ... etc)求并集
dictCplus[x] = []
else:
dictCplus[x] = list(set.intersection(*thesets)) # 即伪代码第二行中的计算交集
#FUN2:找到最小函数依赖
#并对Cplus进行剪枝(最小性剪枝):1.删掉已经成立的2.取掉必不可能的 留下的仍然是“有希望的”'''
for x in level:
for a in x:
if a in dictCplus[x]:#即如果A取得X与Cplus的交集
#if x=='BCJ': print "dictCplus['BCJ'] = ", dictCplus[x]
if validfd(x.replace(a,''), a): # 即X\{A}->A函数依赖成立
finallistofFDs.append([x.replace(a,''), a]) # line 6
print ("compute_dependencies:level:%s adding key FD: %s"%(level,[x.replace(a,''),a]))
dictCplus[x].remove(a) # line 7
listofcols=listofcolumns[:]#copy listofcolumns 实则为接下来剪枝做准备
for j in x: # this loop computes R\X
if j in listofcols: listofcols.remove(j)#此时listofcools更新
for b in listofcols: # 在 C+(X)删掉所有属于R\X即不属于X的元素,即所留下的Cpuls元素全部属于X
# print(b)
# print (dictCplus[x])
if b in dictCplus[x]:
# print(b)
# print (dictCplus[x])
dictCplus[x].remove(b)
for x in level:
print (x)
print (dictCplus[x])
#测试:但为什么L1层的右方集和理解中不一样呢?---L1是由公式算出来的
compute_dependencies(L[2], listofcolumns[:])
['A', 'B', 'C', 'D']
AB
['A']
BD
['D']
计算右方集:computeCplus
- 疑问:NULL和‘’什么区别
def computeCplus(x):
# this computes the Cplus from the first definition in section 3.2.2 of TANE paper.
#output should be a list of single attributes
global listofcolumns#始终=[A,B,C,D]
listofcols = listofcolumns[:]#copy
if x=='': return listofcols # because C+{phi} = R(φ=Phi)
cplus = []
for a in listofcols:#A∈R并且满足如下条件:
for b in x:
temp = x.replace(a,'')
temp = temp.replace(b,'')
if not validfd(temp, b):
cplus.append(a)
return cplus
print(listofcolumns)
computeCplus('C')
['A', 'B', 'C', 'D']
['A', 'B', 'C', 'D']
有效性测试:validfd
def validfd(y,z):#验证Y->是否符合函数依赖
if y=='' or z=='': return False
ey = computeE(y)#计算误差e(X)
eyz = computeE(y+z)#计算误差e(XU{A})
if ey == eyz :#引理3.5
return True
else:
return False
e(X)计算:computeE
误差e(X)的计算公式如下:
e
(
X
)
=
(
∥
π
X
^
∥
−
∣
π
X
^
∣
)
/
∣
r
∣
e(X)=\left(\left\|\widehat{\pi_{X}}\right\|-\left|\widehat{\pi_{X}}\right|\right) /|r|
e(X)=(∥πX
∥−∣πX
∣)/∣r∣
-
∥
π
X
^
∥
\left\|\widehat{\pi_{X}}\right\|
∥πX
∥表示剥离分区
π
X
^
\widehat{\pi_{X}}
πX
中所有等价类的大小的和,用
doublenorm
表示 -
∣
π
X
^
∣
\left|\widehat{\pi_{X}}\right|
∣πX
∣表示剥离分区的阶:内部有多少个等价类,用
len(dictpartitions[''.join(sorted(x))])
表示
def computeE(x):#属性集为x
global totaltuples#元组数
global dictpartitions#关于每个属性集的剥离分区
doublenorm = 0
for i in dictpartitions[''.join(sorted(x))]:#''.join(sorted(x))先将x排序--即BCA to ABC,再转换为字符串,用''隔开
#测试 print(i) # i为剥离分区中的等价类,对于testABCD-test,x=D,i取[0,3]、[1,2]
doublenorm = doublenorm + len(i)#doublenorm存储所有等价类的大小的和
e = (doublenorm-len(dictpartitions[''.join(sorted(x))]))/float(totaltuples)
return e
#测试 testdataABCD.csv
print(computeE('A'))#4-2 / 4 = 0.5
print(computeE('C'))#0-0/4 = 0
print(dictpartitions)
超键检查:check_superkey
- (dictpartitions[x] == [[]]) or (dictpartitions[x] == [])区别在哪
#测试
dictpartitions
def check_superkey(x):
global dictpartitions#关于每个属性集的剥离分区
if ((dictpartitions[x] == [[]]) or (dictpartitions[x] == [])):#如果剥离分区为空,则说明π_x只有单例等价类组成
return True
else:
return False
修剪属性格:prune
减掉不满足条件的属性集X,对于某些X,若X->A不成立,则X的超集也不同理,我们从属性包含格中删除X,从而不用考虑超集,节省判断时间
-
步骤梳理
- 第1、2、3行体现的关于右方集的剪枝策略:若Cplus(X)=φ,则删除X----X无用,剪掉是为了不再考虑超集
- 第4-8行体现的是关于键的剪枝策略:若X为(超)键,则删除X----X有用,但已经用过了,剪掉是为了不再考虑超集
-
如果X为超键,如何确定X\{A}->A是否为最小函数依赖呢?
设X是超键,设A∈X 。依赖项X \ {A}→A是有效且最小的,当且仅当X \ {A}是键且对所有B∈X, A∈C+(X \ {B})时。
更直观的说是保证:- X\{A}->A成立(非平凡):X\A只能是键,X\A->A是才有效的。反之,如果X\A不是键,则X\A->A必然不成立。
- A不依赖X的真子集(最小性):所有B∈X,所有B∈X, A∈C+(X \ {B})(由cplus定义 or 引理3.1可知)
-
疑问:line4~line7
- 通过compute_dependencies过程C+(X)只保留了X中的元素,那么,C+(X)\X不就是空了吗。
A:不空只能说明,对于所有属于X与C+(X)交集的A,X\A->A均不成立。否则,若有一项成立,根据line8,C+(X)只保留了X中的元素。因为对不属于X的元素注定没有了希望,必不可能最小。所以对Cplus进行修剪,去掉没有希望的B,以及去掉已经实现愿望的A。
- Question:修剪过程中,依赖X→A在第7行输出当且仅当如果X是超键,A∈C+ (X)\X 并且对于所有的B∈X而言,A∈C + ((X+A)\ {B})。?这句话如何解释,如何与引理4.2扯上关系。
Answer:进行到prune这一步时,C+(X)内属于X的属性均已经考虑过(并且均不成立,如果成立的话C+ (X)\X 就是空了),因此需要考虑不属于X的属性,即C+ (X)\X,那么,既然X是超键,则X+A也是超键,(X+A)相当于引理4.2中的((X))那么X相当于引理4.2中的((X\A)),B与4.2中B相同,(显然这里B!=A),因此((X \ {A}))→A为最小,即X->A为最小!
- 通过compute_dependencies过程C+(X)只保留了X中的元素,那么,C+(X)\X不就是空了吗。
def prune(level):
global dictCplus#属性集的右方集
global finallistofFDs#FD
for x in level: # line 1
'''Angle1:右方集修剪'''
if dictCplus[x]==[]: # line 2
level.remove(x) # line 3:若Cplus(X)=φ,则删除X
'''Angle2:键修剪'''
if check_superkey(x): # line 4 ### should this check for a key, instead of super key??? Not sure.
temp = dictCplus[x][:]# 初始化temp 为 computes cplus(x)
#1. 求得C+(X) \ X
for i in x: # this loop computes C+(X) \ X
if i in temp: temp.remove(i)# temp为C+(X) \ X
#2. line 5:for each a ∈ Cplus(X)\X do
for a in temp:
thesets=[]
#3. 计算Cplus((X+A)\ {B})
for b in x:
if not( ''.join(sorted((x+a).replace(b,''))) in dictCplus.keys()):
# ''.join(sorted((x+a).replace(b,''))表示的就是XU{a}\{b}
dictCplus[''.join(sorted((x+a).replace(b,'')))] = findCplus(''.join(sorted((x+a).replace(b,''))))
thesets.insert(0,set(dictCplus[''.join(sorted((x+a).replace(b,'')))]))
#4. 计算Cplus((X+A)\ {B})交集,判断a是否在其中
if a in list(set.intersection(*thesets)): # line 6 set.intersection(*thesets)为求所有thesets元素的并集
finallistofFDs.append([x, a]) # line 7
#测试
print ("pruning:level:%s adding key FD: %s"%(level,[x,a]))
# 只要x是超键,就要剪掉x。
if x in level:level.remove(x)#如果此时在line3中已经删除X,则不执行.
生成下一级:generate_next_level
def generate_next_level(level):
#首先令 L[i+1] 这一层为空集
nextlevel=[]
for i in range(0,len(level)): # 选择一个属性集
for j in range(i+1, len(level)): # 将其与后面的所有属性集进行比较
#如果这两个元素属于同一个前缀块,那么就可以合并:只有最后一个属性不同,其余都相同
if ((not level[i]==level[j]) and level[i][0:-1]==level[j][0:-1]): # i.e. line 2 and 3
x = level[i]+level[j][-1] #line 4 X = Y U Z
flag = True
for a in x: # this entire for loop is for the 'for all' check in line 5
if not(x.replace(a, '') in level):
flag=False
if flag==True:
nextlevel.append(x)
#计算新的属性集X上的剥离分区
#=pi_y*pi_z(其中y为level[i],z为level[j])
stripped_product(x, level[i] , level[j] ) # compute partition of x as pi_y * pi_z (where y is level[i] and z is level[j])
return nextlevel
#测试generate_next_level
Ltest=['A','B','C','D']#测试'ABCD'和‘ABD’
nextLtest = generate_next_level(Ltest)
# nextnextLtest = generate_next_level(nextLtest)
# nextnextnextLest = generate_next_level(nextnextLtest)
print(nextLtest)
# print(nextnextLtest)
# print(nextnextnextLest)
y:A partitionY:[[0, 1], [2, 3]],z:B partitionZ[[0, 1, 2]]
x=AB,partitionX=[[0, 1]]
y:A partitionY:[[0, 1], [2, 3]],z:C partitionZ[]
x=AC,partitionX=[]
y:A partitionY:[[0, 1], [2, 3]],z:D partitionZ[[0, 3], [1, 2]]
x=AD,partitionX=[]
y:B partitionY:[[0, 1, 2]],z:C partitionZ[]
x=BC,partitionX=[]
y:B partitionY:[[0, 1, 2]],z:D partitionZ[[0, 3], [1, 2]]
x=BD,partitionX=[[1, 2]]
y:C partitionY:[],z:D partitionZ[[0, 3], [1, 2]]
x=CD,partitionX=[]
['AB', 'AC', 'AD', 'BC', 'BD', 'CD']
# 测试 [-1]表示最后一个位置
ltest = 'ABCDEF'
ltest[-1]
'F'
生成剥离分区:stripped_product
在生成下一级的同时,计算得到新的属性集的剥离分区。除了level=1上的属性集为通过computeSingletonPartitions
计算得到的剥离分区,其他level上的属性集的剥离分区均通过stripped_product
得到
def stripped_product(x,y,z):
global dictpartitions#剥离分区
global tableT
tableS = ['']*len(tableT)
#partitionY、partitionZ是属性集Y、Z上的剥离分区,已知!
#partitionY is a list of lists, each list is an equivalence class
partitionY = dictpartitions[''.join(sorted(y))]
partitionZ = dictpartitions[''.join(sorted(z))]
print("y:%s partitionY:%s,z:%s partitionZ%s"%(y,partitionY,z,partitionZ))
partitionofx = [] # line 1
for i in range(len(partitionY)): # line 2
for t in partitionY[i]: # line 3
tableT[t] = i
tableS[i]='' #line 4
for i in range(len(partitionZ)): # line 5
for t in partitionZ[i]: # line 6
if ( not (tableT[t] == 'NULL')): # line 7
tableS[tableT[t]] = sorted(list(set(tableS[tableT[t]]) | set([t])))
for t in partitionZ[i]: # line 8
if (not (tableT[t] == 'NULL')) and len(tableS[tableT[t]])>= 2 : # line 9
partitionofx.append(tableS[tableT[t]])
if not (tableT[t] == 'NULL'): tableS[tableT[t]]='' # line 10
for i in range(len(partitionY)): # line 11
for t in partitionY[i]: # line 12
tableT[t]='NULL'
dictpartitions[''.join(sorted(x))] = partitionofx#生成属性集X上的剥离分区
print('x=%s,partitionX=%s'%(x,partitionofx))
#测试stripped_product
计算剥离分区:computeSingletonPartitions
等价类划分:
list_duplicates(data2D[a].tolist())将每列中,重复的元素合并起来,返回的是一个二元组
- 元素的值
- 索引:存放元素的位置,例如对于A列而言,其中的element为(1, [0, 1])、 (5, [2])
def computeSingletonPartitions(listofcols):
global data2D
global dictpartitions
for a in listofcols:
dictpartitions[a]=[]
for element in list_duplicates(data2D[a].tolist()): # list_duplicates returns 2-tuples, where 1st is a value, and 2nd is a list of indices where that value occurs
if len(element[1])>1: # 忽略单例等价类
dictpartitions[a].append(element[1])
#测试computeSingletonPartitions
#此时考虑的属性集只有A,B,C,D,在单个属性集上面生成剥离分区
'''测试list_duplicates函数的返回值:返回的是每个属性列表中每个属性的剥离分区'''
dictpartitions = {}
dictCplus = {'NULL': listofcolumns}
datatest = read_csv('testdataABCD.csv')
print(datatest)
for a in listofcolumns:#为索引列
print ("a=",a)
dictpartitions[a]=[]
print (a,datatest[a].tolist())
for element in list_duplicates(datatest[a].tolist()):
print ("element=",element)
for element in list_duplicates(datatest[a].tolist()): # list_duplicates returns 2-tuples, where 1st is a value, and 2nd is a list of indices where that value occurs
if len(element[1])>1: # ignore singleton equivalence classes
dictpartitions[a].append(element[1])
print(dictpartitions)#存放的是每个属性集上的剥离分区
print(dictCplus)
A B C D
0 1 1 5 5
1 1 1 1 3
2 5 1 2 3
3 5 2 3 5
a= A
A [1, 1, 5, 5]
element= (1, [0, 1])
element= (5, [2, 3])
a= B
B [1, 1, 1, 2]
element= (1, [0, 1, 2])
element= (2, [3])
a= C
C [5, 1, 2, 3]
element= (5, [0])
element= (1, [1])
element= (2, [2])
element= (3, [3])
a= D
D [5, 3, 3, 5]
element= (5, [0, 3])
element= (3, [1, 2])
{'A': [[0, 1], [2, 3]], 'B': [[0, 1, 2]], 'C': [], 'D': [[0, 3], [1, 2]]}
{'NULL': ['A', 'B', 'C', 'D']}
执行
获取数据属性
data2D = read_csv('testdataABCD.csv')
data2D
A | B | C | D | |
---|---|---|---|---|
0 | 1 | 1 | 5 | 5 |
1 | 1 | 1 | 1 | 3 |
2 | 5 | 1 | 2 | 3 |
3 | 5 | 2 | 3 | 5 |
#------------------------------------------------------- START ---------------------------------------------------
'''
如果嵌入到项目中,需要这么写代码
if len(sys.argv) > 1:
infile=str(sys.argv[1]) # this would be e.g. "testdata.csv"
data2D = read_csv(infile)
'''
totaltuples = len(data2D.index) #return num of tuple:4
#listofcolumns为属性集列表:初始时,只有A,B,C,D..下面为初始化过程
listofcolumns = list(data2D.columns.values) # returns ['A', 'B', 'C', 'D', .....]
print(totaltuples)
print(listofcolumns)
tableT = ['NULL']*totaltuples #这是用于函数stripped_product中的表T
L0 = []
dictCplus = {'NULL': listofcolumns}#右方集字典集,初始时,NULL的右方集为R,即A,B,C,D...
#用于存储剥离分区
dictpartitions = {} # maps 'stringslikethis' to a list of lists, each of which contains indices
tableT
4
['A', 'B', 'C', 'D']
['NULL', 'NULL', 'NULL', 'NULL']
计算剥离分区
'''计算每个属性集上的剥离分区'''
computeSingletonPartitions(listofcolumns)
dictpartitions,listofcolumns
({'A': [[0, 1], [2, 3]], 'B': [[0, 1, 2]], 'C': [], 'D': [[0, 3], [1, 2]]},
['A', 'B', 'C', 'D'])
TANE主要算法
finallistofFDs=[]
#print dictCplus['NULL']
#初始时,L1层包含的属性集为:A,B,C,D...
L1=listofcolumns[:] # L1 is a copy of listofcolumns
i=1
L = [L0,L1]
while (not (L[i] == [])):#第i层的包含的属性集不为空
compute_dependencies(L[i],listofcolumns[:])# 计算该层的函数依赖
prune(L[i])#剪枝,删除Li中的集合,修剪搜索空间
temp = generate_next_level(L[i])
L.append(temp)#将生成的层追加到L集合中
i=i+1
print ("List of all FDs: " , finallistofFDs)
# correct result
# List of all FDs: [['C', 'D'], ['C', 'A'], ['C', 'B'], ['AD', 'B'], ['AD', 'C']]
# Total number of FDs found: 5
print ("Total number of FDs found: ", len(finallistofFDs))
print(L)
['A', 'B', 'C', 'D']
A
['A', 'C', 'B', 'D']
B
['A', 'C', 'B', 'D']
C
['A', 'C', 'B', 'D']
D
['A', 'C', 'B', 'D']
pruning:level:['A', 'B', 'C', 'D'] adding key FD: ['C', 'A']
pruning:level:['A', 'B', 'C', 'D'] adding key FD: ['C', 'B']
pruning:level:['A', 'B', 'C', 'D'] adding key FD: ['C', 'D']
['A', 'B', 'C', 'D']
AB
['A', 'C', 'B', 'D']
AD
['A', 'C', 'B', 'D']
BD
['A', 'C', 'B', 'D']
pruning:level:['AB', 'AD', 'BD'] adding key FD: ['AD', 'C']
pruning:level:['AB', 'AD', 'BD'] adding key FD: ['AD', 'B']
List of all FDs: [['C', 'A'], ['C', 'B'], ['C', 'D'], ['AD', 'C'], ['AD', 'B']]
Total number of FDs found: 5
[[], ['A', 'B', 'D'], ['AB', 'BD'], []]
由输出结果可以看出:
测试
#测试 不执行
# print(listofcolumns)
# L1=listofcolumns[:]
# print (L1)
['A', 'B', 'C', 'D']
['A', 'B', 'C', 'D']
#测试lever
L
[[], ['A', 'B', 'D'], ['AB', 'BD'], []]