python 大数据量合并集(如:多种会员活动合并集)

 

公司最近有个需求,要处理大量会员的交并集,一下是一些总结:

第一种:利用mysql合并集操作:

交集:

SELECT
	cno 
FROM
	( 
    SELECT DISTINCT cno AS cno FROM mc_data_merge_tmp WHERE task_id = '1' 
    UNION ALL 
    SELECT DISTINCT cno AS cno FROM mc_data_merge_tmp WHERE task_id = '2' 
    ) TEMP 
GROUP BY
	cno

并集:

SELECT
	cno 
FROM
	( 
    SELECT DISTINCT cno AS cno FROM mc_data_merge_tmp WHERE task_id = '1' 
    UNION ALL 
    SELECT DISTINCT cno AS cno FROM mc_data_merge_tmp WHERE task_id = '2' 
    ) TEMP 
GROUP BY
	cno
HAVING
      COUNT( cno ) = 2

这个方法,数据量大后会变慢,而且占用mysql存储空间比较大

第二个方法:利用mongodb,理论上可以实现,提供一个简单的思路:

db.mc_187.aggregate(
   [
     { $project: {"task0": { $setUnion: [ "$task1", "$task12m" ] }, _id: 0} },{"$out": "mc"}
   ]
)

mongodb缺点是操作并不太符合常用习惯,暂时先寻求更好的方法

 第三种方法:利用redis 无序集合做合并集,

redis 无序集合缺点是redis是内存型数据库,千万级数据对数据库压力比较大

第四种:同样利用redis,但是利用redis的位图存储:

每个会员都有对应的唯一id, 那么只需要更改查询出来的task1条件的会员所在索引为1:

例如,查询到task1 会员id [1,3,5,6], task2会员id[1,2,4]

用setbit操作redis,

task1=110101, task2 = 1011,使用相位与,和相位或操作即可得到 task_and = 1 和task_or = 111111,取出,对应id分别为[1], [1,2,3,4,5,6]

这个优点很明显,省空间,运算速度也可以接受.对我来说的缺点:我不会将task1=110101, task2 = 1011一次存入redis, 只能循环存入,即使redis是内存行数据库,也会有频繁io,而且公司用的是阿里云redis,io不好保证

第五种:也是在第四种上的一种处理吧!

既然我能得到二进制,那我直接可以通过 python进行按位与或者按位或操作就可以了,完全可以跳过redis.

伪代码如下:

# 这个只是模拟,如果觉得千万数据,内存扛不住,可以limit 分段取,但是应该先取出最大id
# task_1 ,task_2 假定可以得到最大id,都是一千万
max_id_task_1 = 10000000
max_id_task_2 = 10000000


# 生成一个1千万'0'的list
task_1_list = ['0' for i in range(max_id_task_1 + 1)]
task_2_list = ['0' for i in range(max_id_task_2 + 1)]

# 因为mysqlid没有0的位置,不过无所谓,操作不到,不会影响最终结果
# 模拟task1,task2,符合条件的id
for i in range(max_id_task_1 + 1):
    task_1_list[i] = str(random.randint(0, 1))

for i in range(max_id_task_2 + 1):
    task_2_list[i] = str(random.randint(0, 1))

# 现在得到的就是0101,或者10110这样的集合了,这个理论上来说不会0开头,但是保险点还是做下处理
# list拼接成字符串去掉首字母的'0', 开头加上'0b' ,这样就是伪2进制字符串了
s1 = '0b' + ''.join(task_1_list).lstrip('0')
s2 = '0b' + ''.join(task_2_list).lstrip('0')

# 我为了其他程序再用,存储成了文件
# with open('test1', 'w') as f:
#     f.write(s)
# with open('test2', 'w') as f:
#     f.write(s)
# 然后读取文件 一千万数据也就10m大小的文件
with open('test1', 'r') as f:
    data1 = f.read()
with open('test2', 'r') as f:
    data2 = f.read()
# 取出的数据转换为二进制
data1 = int(data1, 2)
data2 = int(data2, 2)
# 取交集
data3 = data1 & data2

data3 = bin(data3)  # 转换为字符串
#现在for循环字符串取为1部分的id,就是符合条件的交集用户了
task_end_id_lt = [index for index in range(len(data3)) if data[3] == '1']


这个的缺点就是占用cup,我cup单核一度到了100%,但是速度还是不错,可以接受,一千万数据我测试的结果是105.482196903s 

有没有什么能够直接设置二进制位偏移位置为1的办法呢?

这个排序是左向右的,记得反转list和字符串(不细说了,测试一下)

大概代码是这样:

c = int('0b10110', 2)
print(c)
print(bin(c))
print(type(bin(c)))
d = bin(c)[::-1]
for id in range(len(d)):
    if d[id] == '1':
        print(id)

 

结果

22
0b10110
<type 'str'>
1
2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值