宏光PLUS上市拉新活动-技术总结

mac2024-06-06  55

文章目录

简介弹弹车游戏方法弹弹车涉及到的技术主要有弹射里程计算划分区间正态分布Java实现正态分布 爆出旅途奖品(开奖、抽奖)抛均匀硬币抛不均匀硬币Alias改进算法Alias算法实现(Java版本) 奖品库存控制参考

简介

弹弹车是为五菱宏光PLUS上市而诞生的,其目的主要在于

拉新提高用户活跃度庆祝宏光plus上市,提高产品知名度

弹弹车游戏方法

弹车。按下力度条,车子弹射出去一段距离,然后停止。

弹车距离影响因素有:

力度大小。

1.1 力度区间为:(0,0.5] 和 (1,0.5],即中间(0.5)处,力度最大。

1.2 同等条件下,力度越大,弹射距离越大。

是否具备助推剂。

2.1 获得方式:分享拉新成功则得一瓶氮气

2.2 同等条件下,有助推器可以弹车更远的距离。

宏光plus座驾还是旧座驾。

3.1 获得方式:进行爱车评估

3.2 宏光plus比旧座驾在同等条件下弹射更远距离。

爆旅途奖品。弹车到停止的过程会随机获取奖品。累计弹车里程达到绕地球20圈,则赠与油卡券。

弹弹车涉及到的技术主要有

弹射里程计算。路途如何爆出奖品。奖品库存控制。

弹射里程计算

里程的计算会影响到油卡券的发放,故需要严格控制每次弹车的距离,做到

油卡券能发放完毕。即用户在活动结束后有部分人达到目标里程(绕地球20圈);拉新与否、爱车评估与否、弹射力度大小能通过弹车距离来明显感知。例如,同等条件下,弹射力为0.1的弹车距离理应小于弹射力为0.5的弹车距离。
划分区间

划分区间可以规范好弹车距离,使之不重叠,即能让用户明显感知条件的不同会影响弹车距离。

弹车距离影响因子: 1. 是否进行爱车评估,设代码为plus_car 2. 是否有助推器,设代码为props 3. 力度大小,设代码为pressure eg. plus_car props pressure distance 1 1 1 (0.8,1] 0 1 1 (0.8*0.9,1*0.9]即(0.72,0.9] ... 注: 力度转成0-1的范围:[2x , 2(1-x])
正态分布

虽然划分区间能够明确知道每个用户弹车的距离区间在哪里,但是其具体值是随机的。从自然界的一些现象,如

某年某地区降雨量某年某高校大学生的身高测量误差射击目标的水平或者垂直偏差(跟弹车有点类似)

等等都近似或者服从正态分布,虽然没有进行实际进行弹车测试,我们可以做个取巧,即认为弹车距离就是服从正态分布(模拟也要近似于常识、自然现象)。

这里,实现方式采用GB4086.1-83这个规范来实现正态分布。其原理如下图所示

Java实现正态分布

其中k的值,我个人认为,只要其满足GB4086.1-83规范给出表格的得出即可。即将u=1.33带入公式,能得出0.908241值即可。

/** * 正态分布,详细见https://max.book118.com/html/2018/1213/6144221053001235.shtm * @param u 范围是0-5 * @return 范围是[0.5-1] */ public static double normalDistribution(double u) { double y = Math.abs(u); double y2 = y * y; double z = Math.exp(-0.5 * y2) * 0.398942280401432678; double p = 0; //循环次数(连分式的项数)控制精度 int k = 28; double s = -1; double fj = k; if (y > 3) { //当y>3时 for (int i = 1; i <= k; i++) { p = fj / (y + p); fj = fj - 1.0; } p = z / (y + p); } else { //当y<3时 for (int i = 1; i <= k; i++) { p = fj * y2 / (2.0 * fj + 1.0 + s * p); s = -s; fj = fj - 1.0; } p = 0.5 - z * y / (1 - p); } if (u > 0) { p = 1.0 - p; } return p; }

爆出旅途奖品(开奖、抽奖)

抽奖算法有很多种,但是

时间、空间复杂度低(性能好)数据结构恰当(对象建模)算法易于理解

的就比较少了,本活动抽奖算法使用《darts-dice-coins》文章里介绍的Alias改进算法来实现的。下面介绍其算法的主要思想

抛均匀硬币

假设硬币是均匀的,则其结果和概率为

硬币正面:概率为1/2。硬币反面:概率为1/2。

算法(伪代码)

Algorithm: Simulating a Fair Die //可以用(new Random().nextDouble())来生成[0,1)的伪随机数 1. Generate a uniformly-random value x in the range [0,1). //n:产生多少种结果,丢硬币只有正反面2种,故n=2 //⌊⌋是向下取证。 2. Return ⌊xn⌋.

举个栗子

1. 随机生成一个[0,1)的数为0.82 2. 代入算法得⌊xn⌋=⌊0.82 * 2⌋=⌊1.64⌋=1,是正面 // another example 1. 随机生成一个[0,1)的数为0.1 2. 代入算法得⌊xn⌋=⌊0.1 * 2⌋=⌊0.2⌋=0,是反面
抛不均匀硬币

假设硬币是均匀的,则其结果和概率为

硬币正面:概率为1/4。硬币反面:概率为3/4。

算法(伪代码)

Algorithm: Simulating a Biased Coin 1. Generate a uniformly-random value x in the range [0,1). //pheads就是正面的概率,即pheads=1/4 2. If x<pheads, return "heads." 3. If x≥pheads, return "tails."
Alias改进算法

其实它的算法来源思想就是由

抛均匀硬币抛不均匀硬币

而来的。要详细介绍,请移步darts-dice-coins文章。要注意的点是概率总和为1,因为这是Alias算法的前提。

Alias算法实现(Java版本)
/****************************************************************************** * File: AliasMethod.java * Author: Keith Schwarz (htiek@cs.stanford.edu) * * An implementation of the alias method implemented using Vose's algorithm. * The alias method allows for efficient sampling of random values from a * discrete probability distribution (i.e. rolling a loaded die) in O(1) time * each after O(n) preprocessing time. * * For a complete writeup on the alias method, including the intuition and * important proofs, please see the article "Darts, Dice, and Coins: Smpling * from a Discrete Distribution" at * * http://www.keithschwarz.com/darts-dice-coins/ */ import java.util.*; public final class AliasMethod { /* The random number generator used to sample from the distribution. */ private final Random random; /* The probability and alias tables. */ private final int[] alias; private final double[] probability; /** * Constructs a new AliasMethod to sample from a discrete distribution and * hand back outcomes based on the probability distribution. * <p> * Given as input a list of probabilities corresponding to outcomes 0, 1, * ..., n - 1, this constructor creates the probability and alias tables * needed to efficiently sample from this distribution. * * @param probabilities The list of probabilities. */ public AliasMethod(List<Double> probabilities) { this(probabilities, new Random()); } /** * Constructs a new AliasMethod to sample from a discrete distribution and * hand back outcomes based on the probability distribution. * <p> * Given as input a list of probabilities corresponding to outcomes 0, 1, * ..., n - 1, along with the random number generator that should be used * as the underlying generator, this constructor creates the probability * and alias tables needed to efficiently sample from this distribution. * * @param probabilities The list of probabilities. * @param random The random number generator */ public AliasMethod(List<Double> probabilities, Random random) { /* Begin by doing basic structural checks on the inputs. */ if (probabilities == null || random == null) throw new NullPointerException(); if (probabilities.size() == 0) throw new IllegalArgumentException("Probability vector must be nonempty."); /* Allocate space for the probability and alias tables. */ probability = new double[probabilities.size()]; alias = new int[probabilities.size()]; /* Store the underlying generator. */ this.random = random; /* Compute the average probability and cache it for later use. */ final double average = 1.0 / probabilities.size(); /* Make a copy of the probabilities list, since we will be making * changes to it. */ probabilities = new ArrayList<Double>(probabilities); /* Create two stacks to act as worklists as we populate the tables. */ Deque<Integer> small = new ArrayDeque<Integer>(); Deque<Integer> large = new ArrayDeque<Integer>(); /* Populate the stacks with the input probabilities. */ for (int i = 0; i < probabilities.size(); ++i) { /* If the probability is below the average probability, then we add * it to the small list; otherwise we add it to the large list. */ if (probabilities.get(i) >= average) large.add(i); else small.add(i); } /* As a note: in the mathematical specification of the algorithm, we * will always exhaust the small list before the big list. However, * due to floating point inaccuracies, this is not necessarily true. * Consequently, this inner loop (which tries to pair small and large * elements) will have to check that both lists aren't empty. */ while (!small.isEmpty() && !large.isEmpty()) { /* Get the index of the small and the large probabilities. */ int less = small.removeLast(); int more = large.removeLast(); /* These probabilities have not yet been scaled up to be such that * 1/n is given weight 1.0. We do this here instead. */ probability[less] = probabilities.get(less) * probabilities.size(); alias[less] = more; /* Decrease the probability of the larger one by the appropriate * amount. */ probabilities.set(more, (probabilities.get(more) + probabilities.get(less)) - average); /* If the new probability is less than the average, add it into the * small list; otherwise add it to the large list. */ if (probabilities.get(more) >= 1.0 / probabilities.size()) large.add(more); else small.add(more); } /* At this point, everything is in one list, which means that the * remaining probabilities should all be 1/n. Based on this, set them * appropriately. Due to numerical issues, we can't be sure which * stack will hold the entries, so we empty both. */ while (!small.isEmpty()) probability[small.removeLast()] = 1.0; while (!large.isEmpty()) probability[large.removeLast()] = 1.0; } /** * Samples a value from the underlying distribution. * * @return A random value sampled from the underlying distribution. */ public int next() { /* Generate a fair die roll to determine which column to inspect. */ int column = random.nextInt(probability.length); /* Generate a biased coin toss to determine which option to pick. */ boolean coinToss = random.nextDouble() < probability[column]; /* Based on the outcome, return either the column or its alias. */ return coinToss? column : alias[column]; } }

奖品库存控制

库存是共享资源,多线程下不加以控制会造成多减库存。控制库存减去主要利用

库存数据来源唯一,即都由redis控制。利用redis自带的原子性操作函数来减库存。 //减库存 redisTemplate.opsForValue().increment(key, -1) // 加库存 redisTemplate.opsForValue().increment(key, 1) 库存从db放入redis缓存中需要加以控制(用分布式锁),因为项目是分布式的。 参考一章中已经给出具体的实现方案了,这里不赘余。

分布式锁建议:单实例redis锁 < redisson < zookepper

参考

从拉新、留存、促活、营收四要素谈谈APP用户运营

正态分布—PPT课件

正态分布及其应用

规范《GB4086.1-1983-统计分布数值表正态分布》

抽奖概率-三种算法

darts-dice-coins:飞镖、色子、硬币

分布式环境下,怎么保证线程安全

单实例redis分布式锁-官网

https://github.com/redisson/redisson/wiki

redisson实践

分布式线程安全(redis、zookeeper、数据库)

最新回复(0)