前一阵子在搞数据分析的时候,碰到了这样的需求,有n种物品,m个人,每个人会买过n种物品中的多个,只要两个人之间有买过相同物品,无论几个,我们就把他当做一类人。即便a和b两个人之间没有买过相同的物品,c都买过a和b中的物品,那么a,b,c都可看成一类人。具体形式如下:list = [{1,4,7},{2,5,7},{1,5,6},{9,11,23},{4,23,39},{34,6,10}]
list中的set代表人的购物情况(该示例中有len(list)个人),一个set中的数据,如{1,4,7}则代表当前人所购物的物品索引是1,4,7,(肯定不会写:苹果,梨,手机之类的真实内容)。我们需要得出的结果是:[{1,4,7,2,5,1,6,34,10},{9,11,23},{4,23,39}] 其含义为:购买1,4,7,2,5,,6,34,10物品的为一类人,购买9,11,23物品的为一类人,购买4,23,39物品的为一类人。emm..就是这样
主体思想就是:如果两个set取交集不为空,那么取两者并集当做一个新的集合。
提供两种解决方式:
其一:递归,代码如下:
# one 为待处理的list集合
finallist = [] # 处理后的存储集合
one.apend({len(one)}) # 在尾部增加一个集合(list的长度,为了保证递归算法的退出)
# 递归算法
def dealwithlist(first, one):
residue = []
if len(one) > 0:
for i in range(0, len(one)):
if first & one[i]:
first = first | one[i]
else:
residue.append(one[i])
# 每次循环完一轮就判断是否含有这个set
if finallist.__contains__(first):
if len(residue) > 1: # 对剩余的list集合做取并
finallist.insert(0, finallist[-1]) # 选择在头部插入finalist最后一个
dealwithlist(residue[0], residue)
return
else: # 如果只剩下无用的那个集合,则退出
return
else:
if len(finallist) > 0:
finallist.pop(-1) # 删除finalist最后一个位置
finallist.append(first)
dealwithlist(first, residue)
one.pop(-1) # 得到最后的结果集
递归的弊端也是很明显:当数据量变大,比较次数增多的时候,会超出最大递归深度,进而出现栈溢出的问题!待大神解决
其二:动态改变list:
list_a为待处理的list
def set_group(list_a):
tgt = []
# 遍历所有元素,进行分组
for i, x in enumerate(list_a):
z = x # 暂存当前元素
flag = [] #
# 如果目标库不为空,遍历目标库,看是否存在有交集的元素
if len(tgt) > 0:
for j, y in enumerate(tgt):
# 判断是否存在交集
if z.intersection(y):
z = z.union(y)
# 若存在并集,则将当前元素记录
flag.append(j)
# 如果存在已有的元素,删除原有的元素
if len(flag) > 0:
flag.reverse() # 之所以要倒排一下,是防止list删除元素后的遍历异常
for k in flag:
tgt.pop(k)
tgt.append(z)
return tgt