hong.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. <?php
  2. /**
  3. * 随机红包+固定红包算法[策略模式]
  4. * copyright (c) 2016 http://blog.csdn.net/CleverCode
  5. */
  6. //配置传输数据DTO
  7. class OptionDTO
  8. { /*{{{*/
  9. //红包总金额
  10. public $totalMoney;
  11. //红包数量
  12. public $num;
  13. //范围开始
  14. public $rangeStart;
  15. //范围结算
  16. public $rangeEnd;
  17. //生成红包策略
  18. public $builderStrategy;
  19. //随机红包剩余规则
  20. public $randFormatType; //Can_Left:不修数据,可以有剩余;No_Left:不能有剩余
  21. public static function create($totalMoney,$num,$rangeStart,$rangEnd,
  22. $builderStrategy,$randFormatType = 'No_Left')
  23. {/*{{{*/
  24. $self = new self();
  25. $self->num = $num;
  26. $self->rangeStart = $rangeStart;
  27. $self->rangeEnd = $rangEnd;
  28. $self->totalMoney = $totalMoney;
  29. $self->builderStrategy = $builderStrategy;
  30. $self->randFormatType = $randFormatType;
  31. return $self;
  32. }/*}}}*/
  33. }/*}}}*/
  34. //红包生成器接口
  35. interface IBuilderStrategy
  36. {/*{{{*/
  37. //创建红包
  38. public function create();
  39. //设置配置
  40. public function setOption(OptionDTO $option);
  41. //是否可以生成红包
  42. public function isCanBuilder();
  43. //生成红包函数
  44. public function fx($x);
  45. }/*}}}*/
  46. //固定等额红包策略
  47. class EqualPackageStrategy implements IBuilderStrategy
  48. {/*{{{*/
  49. //单个红包金额
  50. public $oneMoney;
  51. //数量
  52. public $num;
  53. public function __construct($option = null)
  54. {
  55. if($option instanceof OptionDTO)
  56. {
  57. $this->setOption($option);
  58. }
  59. }
  60. public function setOption(OptionDTO $option)
  61. {
  62. $this->oneMoney = $option->rangeStart;
  63. $this->num = $option->num;
  64. }
  65. public function create()
  66. {/*{{{*/
  67. $data = array();
  68. if(false == $this->isCanBuilder())
  69. {
  70. return $data;
  71. }
  72. $data = array();
  73. if(false == is_int($this->num) || $this->num <= 0)
  74. {
  75. return $data;
  76. }
  77. for($i = 1;$i <= $this->num;$i++)
  78. {
  79. $data[$i] = $this->fx($i);
  80. }
  81. return $data;
  82. }/*}}}*/
  83. /**
  84. * 等额红包的方程是一条直线
  85. *
  86. * @param mixed $x
  87. * @access public
  88. * @return void
  89. */
  90. public function fx($x)
  91. {/*{{{*/
  92. return $this->oneMoney;
  93. }/*}}}*/
  94. /**
  95. * 是否能固定红包
  96. *
  97. * @access public
  98. * @return void
  99. */
  100. public function isCanBuilder()
  101. {/*{{{*/
  102. if(false == is_int($this->num) || $this->num <= 0)
  103. {
  104. return false;
  105. }
  106. if(false == is_numeric($this->oneMoney) || $this->oneMoney <= 0)
  107. {
  108. return false;
  109. }
  110. //单个红包小于1分
  111. if($this->oneMoney < 0.01)
  112. {
  113. return false;
  114. }
  115. return true;
  116. }/*}}}*/
  117. }/*}}}*/
  118. //随机红包策略(三角形)
  119. class RandTrianglePackageStrategy implements IBuilderStrategy
  120. {/*{{{*/
  121. //总额
  122. public $totalMoney;
  123. //红包数量
  124. public $num;
  125. //随机红包最小值
  126. public $minMoney;
  127. //随机红包最大值
  128. public $maxMoney;
  129. //修数据方式:NO_LEFT: 红包总额 = 预算总额;CAN_LEFT: 红包总额 <= 预算总额
  130. public $formatType;
  131. //预算剩余金额
  132. public $leftMoney;
  133. public function __construct($option = null)
  134. {/*{{{*/
  135. if($option instanceof OptionDTO)
  136. {
  137. $this->setOption($option);
  138. }
  139. }/*}}}*/
  140. public function setOption(OptionDTO $option)
  141. {/*{{{*/
  142. $this->totalMoney = $option->totalMoney;
  143. $this->num = $option->num;
  144. $this->formatType = $option->randFormatType;
  145. $this->minMoney = $option->rangeStart;
  146. $this->maxMoney = $option->rangeEnd;
  147. $this->leftMoney = $this->totalMoney;
  148. }/*}}}*/
  149. /**
  150. * 创建随机红包
  151. *
  152. * @access public
  153. * @return void
  154. */
  155. public function create()
  156. {/*{{{*/
  157. $data = array();
  158. if(false == $this->isCanBuilder())
  159. {
  160. return $data;
  161. }
  162. $leftMoney = $this->leftMoney;
  163. for($i = 1;$i <= $this->num;$i++)
  164. {
  165. $data[$i] = $this->fx($i);
  166. $leftMoney = $leftMoney - $data[$i];
  167. }
  168. //修数据
  169. list($okLeftMoney,$okData) = $this->format($leftMoney,$data);
  170. //随机排序
  171. shuffle($okData);
  172. $this->leftMoney = $okLeftMoney;
  173. return $okData;
  174. }/*}}}*/
  175. /**
  176. * 是否能够发随机红包
  177. *
  178. * @access public
  179. * @return void
  180. */
  181. public function isCanBuilder()
  182. {/*{{{*/
  183. if(false == is_int($this->num) || $this->num <= 0)
  184. {
  185. return false;
  186. }
  187. if(false == is_numeric($this->totalMoney) || $this->totalMoney <= 0)
  188. {
  189. return false;
  190. }
  191. //均值
  192. $avgMoney = $this->totalMoney / 1.0 / $this->num;
  193. //均值小于最小值
  194. if($avgMoney < $this->minMoney )
  195. {
  196. return false;
  197. }
  198. return true;
  199. }/*}}}*/
  200. /**
  201. * 获取剩余金额
  202. *
  203. * @access public
  204. * @return void
  205. */
  206. public function getLeftMoney()
  207. {/*{{{*/
  208. return $this->leftMoney;
  209. }/*}}}*/
  210. /**
  211. * 随机红包生成函数。三角函数。[(1,0.01),($num/2,$avgMoney),($num,0.01)]
  212. *
  213. * @param mixed $x,1 <= $x <= $this->num;
  214. * @access public
  215. * @return void
  216. */
  217. public function fx($x)
  218. {/*{{{*/
  219. if(false == $this->isCanBuilder())
  220. {
  221. return 0;
  222. }
  223. if($x < 1 || $x > $this->num)
  224. {
  225. return 0;
  226. }
  227. $x1 = 1;
  228. $y1 = $this->minMoney;
  229. //我的峰值
  230. $y2 = $this->maxMoney;
  231. //中间点
  232. $x2 = ceil($this->num / 1.0 / 2);
  233. //最后点
  234. $x3 = $this->num;
  235. $y3 = $this->minMoney;
  236. //当x1,x2,x3都是1的时候(竖线)
  237. if($x1 == $x2 && $x2 == $x3)
  238. {
  239. return $y2;
  240. }
  241. // '/_\'三角形状的线性方程
  242. //'/'部分
  243. if($x1 != $x2 && $x >= $x1 && $x <= $x2)
  244. {
  245. $y = 1.0 * ($x - $x1) / ($x2 - $x1) * ($y2 - $y1) + $y1;
  246. return number_format($y, 2, '.', '');
  247. }
  248. //'\'形状
  249. if($x2 != $x3 && $x >= $x2 && $x <= $x3)
  250. {
  251. $y = 1.0 * ($x - $x2) / ($x3 - $x2) * ($y3 - $y2) + $y2;
  252. return number_format($y, 2, '.', '');
  253. }
  254. return 0;
  255. }/*}}}*/
  256. /**
  257. * 格式化修红包数据
  258. *
  259. * @param mixed $leftMoney
  260. * @param array $data
  261. * @access public
  262. * @return void
  263. */
  264. private function format($leftMoney,array $data)
  265. {/*{{{*/
  266. //不能发随机红包
  267. if(false == $this->isCanBuilder())
  268. {
  269. return array($leftMoney,$data);
  270. }
  271. //红包剩余是0
  272. if(0 == $leftMoney)
  273. {
  274. return array($leftMoney,$data);
  275. }
  276. //数组为空
  277. if(count($data) < 1)
  278. {
  279. return array($leftMoney,$data);
  280. }
  281. //如果是可以有剩余,并且$leftMoney > 0
  282. if('Can_Left' == $this->formatType
  283. && $leftMoney > 0)
  284. {
  285. return array($leftMoney,$data);
  286. }
  287. //我的峰值
  288. $myMax = $this->maxMoney;
  289. // 如果还有余钱,则尝试加到小红包里,如果加不进去,则尝试下一个。
  290. while($leftMoney > 0)
  291. {
  292. $found = 0;
  293. foreach($data as $key => $val)
  294. {
  295. //减少循环优化
  296. if($leftMoney <= 0)
  297. {
  298. break;
  299. }
  300. //预判
  301. $afterLeftMoney = (double)$leftMoney - 0.01;
  302. $afterVal = (double)$val + 0.01;
  303. if( $afterLeftMoney >= 0 && $afterVal <= $myMax)
  304. {
  305. $found = 1;
  306. $data[$key] = number_format($afterVal,2,'.','');
  307. $leftMoney = $afterLeftMoney;
  308. //精度
  309. $leftMoney = number_format($leftMoney,2,'.','');
  310. }
  311. }
  312. //如果没有可以加的红包,需要结束,否则死循环
  313. if($found == 0)
  314. {
  315. break;
  316. }
  317. }
  318. //如果$leftMoney < 0 ,说明生成的红包超过预算了,需要减少部分红包金额
  319. while($leftMoney < 0)
  320. {
  321. $found = 0;
  322. foreach($data as $key => $val)
  323. {
  324. if($leftMoney >= 0)
  325. {
  326. break;
  327. }
  328. //预判
  329. $afterLeftMoney = (double)$leftMoney + 0.01;
  330. $afterVal = (double)$val - 0.01;
  331. if( $afterLeftMoney <= 0 && $afterVal >= $this->minMoney)
  332. {
  333. $found = 1;
  334. $data[$key] = number_format($afterVal,2,'.','');
  335. $leftMoney = $afterLeftMoney;
  336. $leftMoney = number_format($leftMoney,2,'.','');
  337. }
  338. }
  339. //如果一个减少的红包都没有的话,需要结束,否则死循环
  340. if($found == 0)
  341. {
  342. break;
  343. }
  344. }
  345. return array($leftMoney,$data);
  346. }/*}}}*/
  347. }/*}}}*/
  348. //维护策略的环境类
  349. class RedPackageBuilder
  350. {/*{{{*/
  351. // 实例
  352. protected static $_instance = null;
  353. /**
  354. * Singleton instance(获取自己的实例)
  355. *
  356. * @return MemcacheOperate
  357. */
  358. public static function getInstance()
  359. { /*{{{*/
  360. if (null === self::$_instance)
  361. {
  362. self::$_instance = new self();
  363. }
  364. return self::$_instance;
  365. } /*}}}*/
  366. /**
  367. * 获取策略【使用反射】
  368. *
  369. * @param string $type 类型
  370. * @return void
  371. */
  372. public function getBuilderStrategy($type)
  373. { /*{{{*/
  374. $class = $type.'PackageStrategy';
  375. if(class_exists($class))
  376. {
  377. return new $class();
  378. }
  379. else
  380. {
  381. throw new Exception("{$class} 类不存在!");
  382. }
  383. } /*}}}*/
  384. public function getRedPackageByDTO(OptionDTO $optionDTO)
  385. {/*{{{*/
  386. //获取策略
  387. $builderStrategy = $this->getBuilderStrategy($optionDTO->builderStrategy);
  388. //设置参数
  389. $builderStrategy->setOption($optionDTO);
  390. return $builderStrategy->create();
  391. }/*}}}*/
  392. }/*}}}*/
  393. class Client
  394. {/*{{{*/
  395. public static function main($argv)
  396. {
  397. //固定红包
  398. $dto = OptionDTO::create(1000,10,100,100,'Equal');
  399. $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);
  400. //print_r($data);
  401. //随机红包[修数据]
  402. $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle');
  403. $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);
  404. print_r($data);
  405. //随机红包[不修数据]
  406. $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle','Can_Left');
  407. $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);
  408. //print_r($data);
  409. }
  410. }/*}}}*/
  411. //Client::main($argv);