如何发放优惠券的一个小项目
需求分析:
公司里有很多客户,客户之所以不继续用我们的产品了,是因为他账户余额是负的,所以,为了重新赢回这些客户,公司决定发放优惠券cover掉客户账户的负余额。
具体细节:
- 只有8元,80元,200元的优惠券
- 发放给一个客户的优惠券总张数不能超过15张
- 要既能cover掉客户的负余额,又要保证发放给客户的优惠券张数最少
- 发放给客户的总金额-客户的亏损额不能大于8,且越小越好。(不能送太多便宜了)
思路:
开始把这个问题理解为线性规划,觉得不好解,遂放弃。之后想用一大堆的逻辑判断来解决这个问题,总能解出来了吧(实在是烦 写一堆的 if else,最后都被绕晕了,遂放弃)。最后的思路,先列出所有送券的组合,然后把亏损和送券金额相加(亏损是负值),得到gap最小的券组合,就可以搞定了。有点暴力破解法的味道。
下面是代码:
## 检查是否安装 sqldf 和 reshape2 这两个包,若没有则安装,若已安装,则跳过。 if(!'sqldf' %in% installed.packages()[,1]) (install.packages('sqldf')) if(!'reshape2' %in% installed.packages()[,1]) (install.packages('reshape2')) ## 如何发优惠券 ## step 1 ####################### 构造一个数据框,里面包含所有可能的送券组合################################ x=data.frame(x=rep(0:15,1)) # 表示 200的券 的张数 y=data.frame(y=rep(0:15,1)) # 表示 80的券 的张数 z=data.frame(z=rep(0:15,1)) # 表示 8的券 的张数 library(sqldf) # 做笛卡尔积 df <- sqldf('select * from x,y,z') df$coupon_sum <-apply(df,1,sum) df$amt_sum <- df$x*200+df$y*80+df$z*8 #过滤掉 sum>15的 组合 df <- sqldf('select * from df where coupon_sum<=15 order by amt_sum asc') ## step 2 ####################################################### ### 下面是给出任意一个 亏损 比如 loss=-987,则 fun2(-987) 返回出用200,80,8各几张,能获得gap最小 fun2 <- function(i){ if(i< -3000){ return(data.frame(loss=i,x=15,y=0,z=0,coupon_sum=15,amt_sum=3000,gap=3000+i)) } else { df$gap <- i+df$amt_sum df_positive <- sqldf('select * from df where gap>=0') res <- sqldf('select * from df where gap in (select gap from df_positive order by gap limit 1) order by gap,coupon_sum limit 1') return(cbind(loss=i,res)) } } ## step 3 # #### 建一个 函数 fun3,其中调用了fun2 fun3 <- function(original.df){ final.res <- data.frame() for(m in 1:length(original.df[,1])){ row.res <- cbind(original.df[m,],fun2(original.df[m,2])) final.res <- rbind(final.res,row.res) } return(final.res) } # n <- length(test.df[,2]) # res <- lapply(test.df[2:100,2],fun2) # do.call(rbind,res) ## step 4 # 构造一个测试数据集 test.df 进行测试 test.df <- data.frame(phone_num=rep(1:200,1),loss=abs(rnorm(200))*(-2000)) final_res <- fun3(dd) ## step 5 ## 变换数据形式 宽表变窄表,然后写出结果数据集 library(reshape2) final <- melt(final_res[,c(1,3:5)],id=c('phone_num')) ## 重新排序一下 final <- sqldf('select * from final order by phone_num') # final # write.csv() for(i in 1:length(dd[,2])){ print(paste0('round ',i)) print(fun2(dd[i,2])) }
总结:
- 由于代码是发给别人用的,但是经常别人有些包没安装,用不了。所以学会了一句code,搞定。
## 检查是否安装 sqldf 和 reshape2 这两个包,若没有则安装,若已安装,则跳过。 if(!'sqldf' %in% installed.packages()[,1]) (install.packages('sqldf')) if(!'reshape2' %in% installed.packages()[,1]) (install.packages('reshape2'))
- sqldf 真是一个好用的包。10-R-packages-I-wish-I-knew-about-earlier 排第一名。
- sql 对数据框做 笛卡尔积 好用到哭。