首页 > 后端开发 > php教程 > php如何实现固定红包以及随机红包算法详解(图)

php如何实现固定红包以及随机红包算法详解(图)

黄舟
发布: 2023-03-14 09:46:01
原创
4209 人浏览过

1 需求

CleverCode最近接到一个需求,需要写一个固定红包 + 随机红包算法。

1 固定红包就是每个红包金额一样,有多少个就发多少个固定红包金额就行。

2 随机红包的需求是。比如红包总金额5元,需要发10个红包。随机范围是 0.01到0.99;5元必需发完,金额需要有一定趋势的正态分布。(0.99可以任意指定,也可以是 avg * 2 - 0.01;比如avg = 5 / 10 = 0.5;(avg * 2 - 0.01 = 0.99))

2 需求分析

2.1 固定红包

 如果是固定红包,则算法是一条直线。t就是固定红包的额度。如图。
 f(x) = t;(1 <= x <= num)


2.2 随机红包

如果我们使用随机函数rand。rand(0.01,0.99);那么10次随机,如果最坏情况都是金额0.99,总金额就是9.9元。会超过5元。金额也会不正态分布。最后思考了一下借助与数学函数来当作随机红包的发生器,可以用抛物线,三角函数。最后选定了等腰三角线性函数。

1 算法原理

如果需要发红包总金额是totalMoney,红包个数是num个,金额范围是[min,max],线性方程如图。


三个点的坐标:

1

2

3

(x1,y1) =  (1,min)

  (x2,y2)  = (num/2,max)

  (x3,y3) = (num,min)

登录后复制

确定的线性方程:

1

2

$y = 1.0 * ($x - $x1) / ($x2 - $x1) * ($y2 - $y1) + $y1 ; (x1 <= x <= x2)

$y = 1.0 * ($x - $x2) / ($x3 - $x2) * ($y3 - $y2) + $y2;  (x2 <= x <= x3)

登录后复制

修数据:
y(合) = y1 + y2 + y3 +...... ynum;
y(合)有可能 > totalMoney ,说明生成金额多了,需要修数据,则从(y1,y2,y3.....ynum)这些每次减少0.01。直到y(合) = totalMoney。
y(合)有可能 < totalMoney ,说明生成金额少了,需要修数据,则从(y1,y2,y3.....ynum)这些每次加上0.01。直到y(合) = totalMoney。

2 算法原理样例

如果需要发红包总金额是11470,红包个数是7400个,金额范围是[0.01,3.09],线性方程如图。


3 需求设计

3.1 类图设计


3.2 源码设计

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

<?php

/**

 * 随机红包+固定红包算法[策略模式]

 * copyright (c) 2016 http://blog.csdn.net/CleverCode

 */

 

//配置传输数据DTO

class OptionDTO

{/*{{{*/

 

    //红包总金额

    public $totalMoney;

 

    //红包数量

    public $num;

 

    //范围开始

    public $rangeStart;

 

    //范围结算

    public $rangeEnd;

 

    //生成红包策略

    public $builderStrategy;

 

    //随机红包剩余规则

    public $randFormatType; //Can_Left:不修数据,可以有剩余;No_Left:不能有剩余

 

    public static function create($totalMoney,$num,$rangeStart,$rangEnd,

        $builderStrategy,$randFormatType = &#39;No_Left&#39;)

    {/*{{{*/

        $self = new self();

        $self->num = $num;

        $self->rangeStart = $rangeStart;

        $self->rangeEnd = $rangEnd;

        $self->totalMoney = $totalMoney;

        $self->builderStrategy = $builderStrategy;

        $self->randFormatType = $randFormatType;

        return $self;

    }/*}}}*/

 

}/*}}}*/

 

//红包生成器接口

interface IBuilderStrategy

{/*{{{*/

    //创建红包

    public function create();   

    //设置配置

    public function setOption(OptionDTO $option);

    //是否可以生成红包

    public function isCanBuilder();

    //生成红包函数

    public function fx($x);

}/*}}}*/

 

//固定等额红包策略

class EqualPackageStrategy implements IBuilderStrategy

{/*{{{*/

    //单个红包金额

    public $oneMoney;

 

    //数量

    public $num;

 

    public function construct($option = null)

    {

        if($option instanceof OptionDTO)

        {

            $this->setOption($option);

        }

    }

 

    public function setOption(OptionDTO $option)

    {

        $this->oneMoney = $option->rangeStart;

        $this->num = $option->num;

    }

 

    public function create()

    {/*{{{*/

 

        $data = array();

        if(false == $this->isCanBuilder())

        {

            return $data;   

        }

 

        $data = array();

        if(false == is_int($this->num) || $this->num <= 0)

        {

            return $data;   

        }

        for($i = 1;$i <= $this->num;$i++)

        {

            $data[$i] = $this->fx($i);

        }

        return $data;

    }/*}}}*/

     

    /**

     * 等额红包的方程是一条直线

     *

     * @param mixed $x

     * @access public

     * @return void

     */

    public function fx($x)

    {/*{{{*/

        return $this->oneMoney;

    }/*}}}*/

 

    /**

     * 是否能固定红包

     *

     * @access public

     * @return void

     */

    public function isCanBuilder()

    {/*{{{*/

        if(false == is_int($this->num) || $this->num <= 0)

        {

            return false;   

        }

 

        if(false ==  is_numeric($this->oneMoney) || $this->oneMoney <= 0)

        {

            return false;

        }

 

        //单个红包小于1分

        if($this->oneMoney < 0.01)

        {

            return false;

        }

         

        return true;

 

    }/*}}}*/

 

 

}/*}}}*/

 

//随机红包策略(三角形)

class RandTrianglePackageStrategy implements IBuilderStrategy

{/*{{{*/

    //总额

    public $totalMoney;

 

    //红包数量

    public $num;

 

    //随机红包最小值

    public $minMoney;

 

    //随机红包最大值

    public $maxMoney;

 

    //修数据方式:NO_LEFT: 红包总额 = 预算总额;CAN_LEFT: 红包总额 <= 预算总额

    public $formatType;

 

    //预算剩余金额

    public $leftMoney;

 

 

    public function construct($option = null)

    {/*{{{*/

        if($option instanceof OptionDTO)

        {

            $this->setOption($option);

        }

    }/*}}}*/

 

    public function setOption(OptionDTO $option)

    {/*{{{*/

        $this->totalMoney = $option->totalMoney;

        $this->num = $option->num;

        $this->formatType = $option->randFormatType;

        $this->minMoney = $option->rangeStart;

        $this->maxMoney = $option->rangeEnd;

        $this->leftMoney = $this->totalMoney;

    }/*}}}*/

 

    /**

     * 创建随机红包

     *

     * @access public

     * @return void

     */

    public function create()

    {/*{{{*/

         

        $data = array();

        if(false == $this->isCanBuilder())

        {

            return $data;   

        }

         

        $leftMoney = $this->leftMoney;

        for($i = 1;$i <= $this->num;$i++)

        {

            $data[$i] = $this->fx($i);

            $leftMoney = $leftMoney - $data[$i];

        }

 

        //修数据

        list($okLeftMoney,$okData) = $this->format($leftMoney,$data);

 

        //随机排序

        shuffle($okData);

        $this->leftMoney = $okLeftMoney;

 

        return $okData;

    }/*}}}*/

 

    /**

     * 是否能够发随机红包

     *

     * @access public

     * @return void

     */

    public function isCanBuilder()

    {/*{{{*/

        if(false == is_int($this->num) || $this->num <= 0)

        {

            return false;   

        }

 

        if(false ==  is_numeric($this->totalMoney) || $this->totalMoney <= 0)

        {

            return false;

        }

 

        //均值

        $avgMoney = $this->totalMoney / 1.0 / $this->num;

         

        //均值小于最小值

        if($avgMoney < $this->minMoney )

        {

            return false;

        }

         

        return true;

 

    }/*}}}*/

 

    /**

     * 获取剩余金额

     *

     * @access public

     * @return void

     */

    public function getLeftMoney()

    {/*{{{*/

        return $this->leftMoney;

    }/*}}}*/

 

    /**

     * 随机红包生成函数。三角函数。[(1,0.01),($num/2,$avgMoney),($num,0.01)]

     *

     * @param mixed $x,1 <= $x <= $this->num;

     * @access public

     * @return void

     */

    public function fx($x)

    {/*{{{*/

         

        if(false == $this->isCanBuilder())

        {

            return 0;

        }

 

        if($x < 1 || $x > $this->num)

        {

            return 0;

        }

         

        $x1 = 1;

        $y1 = $this->minMoney;

         

        //我的峰值

        $y2 = $this->maxMoney;

 

        //中间点

        $x2 = ceil($this->num /  1.0 / 2);

 

        //最后点

        $x3 = $this->num;

        $y3 = $this->minMoney; 

 

        //当x1,x2,x3都是1的时候(竖线)

        if($x1 == $x2 && $x2 == $x3)

        {

            return $y2;

        }

 

        // &#39;/_\&#39;三角形状的线性方程

        //&#39;/&#39;部分

        if($x1 != $x2 && $x >= $x1 && $x <= $x2)

        {

 

            $y = 1.0 * ($x - $x1) / ($x2 - $x1) * ($y2 - $y1) + $y1

            return number_format($y, 2, &#39;.&#39;, &#39;&#39;);

        }

 

        //&#39;\&#39;形状

        if($x2 != $x3 && $x >= $x2 && $x <= $x3)

        {

 

            $y = 1.0 * ($x - $x2) / ($x3 - $x2) * ($y3 - $y2) + $y2

            return number_format($y, 2, &#39;.&#39;, &#39;&#39;);

        }

         

        return 0;

 

 

    }/*}}}*/

 

    /**

     * 格式化修红包数据

     *

     * @param mixed $leftMoney

     * @param array $data

     * @access public

     * @return void

     */

    private function format($leftMoney,array $data)

    {/*{{{*/

 

        //不能发随机红包

        if(false == $this->isCanBuilder())

        {

            return array($leftMoney,$data); 

        }

         

        //红包剩余是0

        if(0 == $leftMoney)

        {

            return array($leftMoney,$data); 

        }

 

        //数组为空

        if(count($data) < 1)

        {

            return array($leftMoney,$data); 

        }

 

        //如果是可以有剩余,并且$leftMoney > 0

        if(&#39;Can_Left&#39; == $this->formatType

          && $leftMoney > 0)

        {

            return array($leftMoney,$data); 

        }

 

 

        //我的峰值

        $myMax = $this->maxMoney;

 

        // 如果还有余钱,则尝试加到小红包里,如果加不进去,则尝试下一个。

        while($leftMoney > 0)

        {

            $found = 0;

            foreach($data as $key => $val)

            {

                //减少循环优化

                if($leftMoney <= 0)

                {

                    break;

                }

 

                //预判

                $afterLeftMoney =  (double)$leftMoney - 0.01;

                $afterVal = (double)$val + 0.01;

                if( $afterLeftMoney >= 0  && $afterVal <= $myMax)

                {

                    $found = 1;

                    $data[$key] = number_format($afterVal,2,&#39;.&#39;,&#39;&#39;);

                    $leftMoney = $afterLeftMoney;

                    //精度

                    $leftMoney = number_format($leftMoney,2,&#39;.&#39;,&#39;&#39;);

                }

            }

 

            //如果没有可以加的红包,需要结束,否则死循环

            if($found == 0)

            {

                break;

            }

        }

        //如果$leftMoney < 0 ,说明生成的红包超过预算了,需要减少部分红包金额

        while($leftMoney < 0)

        {

            $found = 0;

            foreach($data as $key => $val)

            {

                if($leftMoney >= 0)

                {

                    break;

                }

                //预判

                 

                $afterLeftMoney =  (double)$leftMoney + 0.01;

                $afterVal = (double)$val - 0.01;

                if( $afterLeftMoney <= 0 && $afterVal >= $this->minMoney)

                {

                    $found = 1;

                    $data[$key] = number_format($afterVal,2,&#39;.&#39;,&#39;&#39;);

                    $leftMoney = $afterLeftMoney;

                    $leftMoney = number_format($leftMoney,2,&#39;.&#39;,&#39;&#39;);

                }

            }

             

            //如果一个减少的红包都没有的话,需要结束,否则死循环

            if($found == 0)

            {

                break;

            }

        }

        return array($leftMoney,$data); 

    }/*}}}*/

 

}/*}}}*/

 

//维护策略的环境类

class RedPackageBuilder

{/*{{{*/

 

    // 实例 

    protected static $_instance = null; 

 

    /**

     * Singleton instance(获取自己的实例)

     *

     * @return MemcacheOperate

     */ 

    public static function getInstance()

    /*{{{*/

        if (null === self::$_instance)

        

            self::$_instance = new self(); 

        

        return self::$_instance

    /*}}}*/

 

    /**

     * 获取策略【使用反射】

     *

     * @param string $type 类型

     * @return void

     */ 

    public function getBuilderStrategy($type)

    /*{{{*/

        $class = $type.&#39;PackageStrategy&#39;;

 

        if(class_exists($class))

        {

            return new $class(); 

        }

        else

        {

            throw new Exception("{$class} 类不存在!");

        }

    /*}}}*/

 

    public function getRedPackageByDTO(OptionDTO $optionDTO)

    {/*{{{*/

        //获取策略

        $builderStrategy = $this->getBuilderStrategy($optionDTO->builderStrategy);

 

        //设置参数

        $builderStrategy->setOption($optionDTO);

 

        return $builderStrategy->create();

    }/*}}}*/

     

}/*}}}*/

 

class Client

{/*{{{*/

    public static function main($argv)

    {

        //固定红包

        $dto = OptionDTO::create(1000,10,100,100,&#39;Equal&#39;);

        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

        //print_r($data);

 

        //随机红包[修数据]

        $dto = OptionDTO::create(5,10,0.01,0.99,&#39;RandTriangle&#39;);

        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

        print_r($data);

 

        //随机红包[不修数据]

        $dto = OptionDTO::create(5,10,0.01,0.99,&#39;RandTriangle&#39;,&#39;Can_Left&#39;);

        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

        //print_r($data);

         

    }

}/*}}}*/

 

Client::main($argv);

登录后复制

3.3 结果展示

1 固定红包

1

2

3

4

//固定红包

$dto = OptionDTO::create(1000,10,100,100,&#39;Equal&#39;);

$data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

print_r($data);

登录后复制


2 随机红包(修数据)

这里使用了php的随机排序函数, shuffle($okData),所以看到的结果不是线性的,这个结果更加随机性。

1

2

3

4

//随机红包[修数据]

 $dto = OptionDTO::create(5,10,0.01,0.99,&#39;RandTriangle&#39;);

 $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

 print_r($data);

登录后复制


3 随机红包(不修数据)

不修数据,1 和num的金额是最小值0.01。

1

2

3

4

//随机红包[不修数据]

 $dto = OptionDTO::create(5,10,0.01,0.99,&#39;RandTriangle&#39;,&#39;Can_Left&#39;);

 $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

 print_r($data);

登录后复制



以上是php如何实现固定红包以及随机红包算法详解(图)的详细内容。更多信息请关注PHP中文网其他相关文章!

相关标签:
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
怎么学好php
来自于 1970-01-01 08:00:00
0
0
0
PHP扩展intl
来自于 1970-01-01 08:00:00
0
0
0
php数据获取?
来自于 1970-01-01 08:00:00
0
0
0
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板