在CSV文件中查找候选主键,该测试要求列<= 4的数字,并且这些列的任何子集都不能作为主键。 有一些例子:
这是对我的考验!我已经完成了 35秒,但是没有达到有效的要求:在大约10000行的csv文件中,在大约 1秒的时间内找到了主键。
测试数据:
此文件的正确答案是: [['column8','column11','column15','column18']]
所以,我的问题是“有没有更有效的方法来查找主键?”
这是我的代码:
# coding: utf-8
import pandas as pd
import numpy as np
import os
import itertools
import time
def is_subkey(newkey,keys):
# test wheather newkey is sub set of any keys
for key in keys:
if set(key).issubset(newkey):
return True
return False
def primarykey_recognition(file,max_num=4):
# file is an file object, returnd by function open()
# doc is a pandas DF
doc = pd.read_csv(file,sep=',')
num = 1
result = []
table_length = len(doc.values)
while num <= max_num:
keys = list(itertools.combinations(doc.columns,num))
# print(keys)
for key in keys:
if is_subkey(key,result):
# if key belong to any sub set of keys in result ,continue
continue
#
bools = np.array(doc.duplicated(subset=list(key)))
if np.sum(bools) > 0:
# sum(bools) means bools has duplicated lines
continue
else:
result.append(list(key))
num += 1
return result
if __name__=="__main__":
with open(r"..\data\Table_C.csv") as file:
tic = time.clock()
keys = primarykey_recognition(file)
toc = time.clock()
print("File {} has primary keys: ".format(filename))
print(keys)
print("Elapsed: {} s".format(round(toc - tic,4)))
我发现有一个类似的问题,How to find a columns set for a primary key candidate in CSV file?,但是代码效率不高,并找到了错误的密钥,例如示例key2。
以下是该问题的代码:
# coding: utf-8
import pandas
from itertools import chain, combinations
import time
def key_options(items):
return chain.from_iterable(combinations(items, r) for r in range(1, len(items)+1) )
tic = time.clock()
df = pandas.read_csv(r"..\data\Table_C.csv");
# iterate over all combos of headings, excluding ID for brevity
for candidate in key_options(list(df)[1:]):
deduped = df.drop_duplicates(candidate)
if len(deduped.index) == len(df.index):
print(','.join(candidate))
toc = time.clock()
print("Elapsed: {} s".format(round(toc - tic,4)))
答案 0 :(得分:1)
以下是一些想法:
python3 -m cProfile -s cumtime key-extractifyer.py < lotsadata.csv
要阐明第一点:
让我们从抽象的角度开始。您正在尝试找到满足某些约束的列组合。在这种问题中,我们将所有候选项称为“ search space”,每个candidate that satisfy the constraints都称为“问题的解决方案”。现在,您的搜索空间是“ itertools.combinations生成的所有组合”。您正在检查对每个候选人的约束。
现在让我们假设检查约束条件是昂贵的。在您的情况下,这是pandas.duplicated()
调用,占执行时间的95%。显然,如果可以最大程度地减少搜索空间,我们可以节省很多时间!
可以通过三种方式来最小化搜索空间:
我专注于“方法2”,但如果需要,也可以随时探索其他方法。
搜索空间的很大一部分是带有二进制值的列。除此之外,您还有一些只包含单个值的列。出于数学原因,仅涉及这些列的组合永远无法满足您的约束(此类组合绝不能解决您的问题)。可以包含2个值的一列最多可以唯一地标识2行。如果您有3行或更多行,则必须至少有两行在该列中共享相同的值。这称为pigeonhole principle。
我们可以将其扩展到更多列。两个二进制列最多可以标识2 * 2 = 4行,并具有以下组合:(0, 0), (0, 1), (1, 0), (1, 1)
。并缩放到大于(或小于)两个值的列。具有3个唯一值的两列最多可以标识3 * 3 = 9行。
这是我们要利用的原理,以减少搜索空间。通过在开始时计算每列中 unique / distinct 值的数量,并将结果保存到数组中,这样就不必在每次循环迭代时都这样做,您可以给出一个上限该列将能够识别多少行-AT MOST。因此,在执行昂贵的pandas.duplicated()
调用之前,请检查较便宜的乘法,以查看此列组合是否有可能满足约束条件。如果不是这样,我们可以避免昂贵的电话。请注意,我们并不是在试图证明您的候选人是,而是在试图证明您的候选人不是。在确定之前,您仍然需要打出昂贵的电话,但是候选人的数量要少得多。
答案 1 :(得分:0)
我仍然不确定我是否清楚地理解了问题,但是以下代码将每个行号映射到可以唯一标识它的列索引列表,并在〜0m0.145s中运行:
from collections import defaultdict
from re import findall
from itertools import combinations
def nest():
return defaultdict(nest)
d = nest()
row_to_key = nest()
with open('table.csv') as f:
for idx, line in enumerate(f):
indices = [idx for idx, i in enumerate(findall(r'[^,]+', line)) \
if i == 'TRUE']
for j, k, l, m in combinations(indices, 4):
if not d[j][k][l][m]:
d[j][k][l][m] = idx
row_to_key[idx] = [j, k, l, m]
break
for row_idx in row_to_key:
print(' * row number', row_idx, 'can be identified by cols', row_to_key[row_idx])
这将输出:
* row number 1 can be identified by cols [2, 5, 7, 10]
* row number 2 can be identified by cols [0, 9, 10, 12]
* row number 3 can be identified by cols [3, 4, 5, 9]
* row number 4 can be identified by cols [0, 4, 5, 7]
...