背景:
知乎看到一篇很有意思的文章 https://zhuanlan.zhihu.com/p/624278938,讲述 特别诺贝尔奖论文《天赋与运气:随机性在成功与失败中的作用》的试验过程,结论显得非常新颖,周末闲暇时光,一时兴起动手写了个系统实现demo. 结论与大家共勉。
以下提供核心代码 及数据库表设计,最终metabase呈现报表 =>
1、数据库表设计:
-- 个体信息表dropTABLE if EXISTS `person`;CREATETABLE `person` ( `id` int(11)NOTNULL COMMENT '主键', `imitate_count` int(11) DEFAULT NULL COMMENT '试验模拟次数', `name` varchar(255) DEFAULT '' COMMENT '姓名(随机生成)', `age` int(11) DEFAULT '20' COMMENT '年龄(初始20岁)', `ability` double DEFAULT NULL COMMENT '能力值(在0-1之间,不包含0和1且形成正态分布-主要在0.3-0.9之间)', `asset` double DEFAULT '10' COMMENT '资产值(初始10)', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='个体信息';-- 幸运事件表dropTABLE if EXISTS luck_event;CREATETABLE `luck_event` ( `id` int(11)NOTNULL AUTO_INCREMENT COMMENT '主键', `imitate_count` int(11) DEFAULT NULL COMMENT '试验模拟次数', `person_id` int(11) DEFAULT NULL COMMENT 'person id', `etime` bigint(20) DEFAULT NULL COMMENT '事件发生时间戳', `event` varchar(255) DEFAULT NULL COMMENT '事件(lucky或unlucky)', `idx` int(11) DEFAULT '1' COMMENT '事件次数', `b4_asset` double DEFAULT NULL COMMENT '旧资产', `ct_asset` double DEFAULT NULL COMMENT '最新资产', PRIMARY KEY (`id`), KEY `idx_pid` (`person_id`) USING BTREE, KEY `idx_time` (`etime`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='幸运事件';
2、主要代码ke/2.1.6.1示例:
涉及三个类:
public class LuckMan#test // 测试类
private PersonService personService; // 个体实现类
private LuckEventService luckEventService; // 事件实现类
A: test 模拟测试启动类:
publicclassLuckMan { /*** 预备知识:* 1、人的IQ 95%都集中在70-130之间,最多的就是100附近* 2、财富分布图(帕累托分布or82法则分布):只有很少的拥有了极多的财富* => 能力是呈现正态分布,财富呈现两极分化82分布** 这里的能力:知识 & 技能 & IQ & 判断力 & 毅力。。* 最高能力人财富值->* 最高财富人能力值->* 最幸运人的轨迹-> 财富曲线 & 运气波动* 最惨人的轨迹-> 财富曲线 & 运气波动* 1w次-最富有的人&最幸运的人 &能力最强的人比较* 1w次-最富有的人能力值分布 & 概率占比***/privatePersonServicepersonService; privateLuckEventServiceluckEventService; // 全局参数:事件迭代次数 & 试验迭代次数 & 事件时间privateintidx=0; privateintimitCount=1; privatelongetime= (int) (System.currentTimeMillis()/1000); privateEventDbV1eventDbV1=newEventDbV1(); // 总人数样本是否充足?? 1000// 正太分布是否均匀??// 迭代年限是否足够?? interval// 模拟次数是否足够??// 总人数privateinttotalPerson=10000; // 事件发生间隔 0.5表示每半年privatedoubleinterval=0.5; // 模拟次数privateintimitateCount=1; publicvoidtest() throwsIOException { for (; imitCount<=imitateCount; imitCount++){ // 初始化人类库List<PersonPO>persons=init(totalPerson); // 插入数据库saveToDb(persons); for (intt=1; t<= (10/interval); t++){ updateHalfYear(++idx, persons); } System.out.println(" ======= event size="+eventDbV1.getElistInsert().size() +" ; plist size="+eventDbV1.getPlistUpdate().size()); System.out.println(" ======= updateBatchById start========"); log.info(" ======= updateBatchById start========"); // 更新人类库personService.updateBatchById(eventDbV1.getPlistUpdate(), 100); System.out.println(" ======= updateBatchById end========"); log.info(" ======= updateBatchById end========"); System.out.println(" ======= event saveBatch start ========"); log.info(" ======= event saveBatch start ========"); // 插入事件luckEventService.saveBatch(eventDbV1.getElistInsert(), 100); System.out.println(" ======= event saveBatch end ========"); log.info(" ======= event saveBatch end ========"); // 模拟时钟任务,每半年执行一次/* Timer timer = new Timer();timer.schedule(new TimerTask() {public void run() {updateHalfYear(++idx, persons);}}, 0, 1000); // 毫秒*/// 模拟每半年执行一次更新// ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);// executor.scheduleAtFixedRate(() -> {// updateHalfYear(++idx, persons);// }, 0, 1000, TimeUnit.MILLISECONDS); } System.in.read(); } // 保存person表privatevoidsaveToDb(List<PersonPO>persons) { System.out.println(" ======= personService.saveBatch start========"); log.info(" ======= personService.saveBatch start========"); personService.saveBatch(persons, 100); System.out.println(" ======= personService.saveBatch end========"); log.info(" ======= personService.saveBatch end========"); } // 初始化人类库privateList<PersonPO>init(inttotalPerson) { // 随机生成1000个20岁的Person对象, 能力值按正态分布生成,资产默认为10List<PersonPO>list=newArrayList<>(); for (inti=1 ; i<=totalPerson ; i++){ intwordIndex=newRandom().nextInt(EnWordUtils.getSize()); PersonPOperson=newPersonPO(); person.setId(i); person.setAge(20); person.setName(EnWordUtils.getWord(wordIndex)); person.setAbility(generateAbility()); person.setAsset(10D); person.setImitateCount(imitCount); list.add(person); } returnlist; } // 生成0-1范围内的正态分布随机数publicstaticdoublegenerateAbility() { doublev=Math.abs(newRandomDataGenerator().nextGaussian(0.5, 0.1)) %1; return (((BigDecimal.valueOf(v)).setScale(4, RoundingMode.HALF_UP)).doubleValue()); } // 事件迭代privatevoidupdateHalfYear(intidx, List<PersonPO>persons) { etime= (long) (etime+interval*365*24*60*60); // List<EventDb> elist = new ArrayList<>();List<PersonPO>plistUpdate=newArrayList<>(); List<LuckEventPO>elistInsert=newArrayList<>(); for (PersonPOp : persons) { Doubleb4Asset=p.getAsset(); // 判断好运坏运booleanisLucky=Math.random() <p.getAbility(); // 更新年龄p.setAge((int) Math.floor(p.getAge() +interval)); // 更新资产doublenum; if (isLucky) { num=b4Asset*2; } else { num=b4Asset/2; } p.setAsset(((BigDecimal.valueOf(num)).setScale(4, RoundingMode.HALF_UP)).doubleValue()); // 保存事件记录到数据库// EventDb eventDb = new EventDb();// eventDb.setIdx(idx);// eventDb.setB4Asset(b4Asset);// eventDb.setEtime(etime);// eventDb.setP(p);// eventDb.setEvent(isLucky ? "lucky" : "unlucky");// elist.add(eventDb);LuckEventPOluckEventPO=newLuckEventPO(); luckEventPO.setPersonId(p.getId()); luckEventPO.setEtime(etime); luckEventPO.setEvent(isLucky?"lucky" : "unlucky"); luckEventPO.setIdx(idx); luckEventPO.setB4Asset(b4Asset); luckEventPO.setCtAsset(p.getAsset()); luckEventPO.setImitateCount(imitCount); elistInsert.add(luckEventPO); plistUpdate.add(p); } List<LuckEventPO>elistInsert1=eventDbV1.getElistInsert(); elistInsert1.addAll(elistInsert); eventDbV1.setElistInsert(elistInsert1); eventDbV1.setPlistUpdate(plistUpdate); // saveEventToDb(idx, b4Asset, etime, p, isLucky ? "lucky" : "unlucky");// luckEventService.saveBatch(elistInsert);// personService.updateBatchById(plistUpdate); } classEventDbV1{ List<PersonPO>plistUpdate=newArrayList<>(); List<LuckEventPO>elistInsert=newArrayList<>(); } classEventDb{ intidx;Doubleb4Asset;longetime;PersonPOp;Stringevent; } // 保存事件到数据库publicvoidsaveEventToDb(intidx, Doubleb4Asset, longetime, PersonPOp, Stringevent) { // 构建LuckEvent对象并保存LuckEventPOluckEventPO=newLuckEventPO(); luckEventPO.setPersonId(p.getId()); luckEventPO.setEtime(etime); luckEventPO.setEvent(event); luckEventPO.setIdx(idx); luckEventPO.setB4Asset(b4Asset); luckEventPO.setCtAsset(p.getAsset()); luckEventPO.setImitateCount(imitCount); luckEventService.save(luckEventPO); personService.updateById(p); } }
B. personService; // 个体实现类
(方法全部基于mybatis-plus生成,此处省略)
C. luckEventService; // 事件实现类
publicclassLuckEventServiceImplextendsServiceImpl<LuckEventMapper, LuckEventPO>implementsLuckEventService { /*** 预备知识:* 1、人的IQ 95%都集中在70-130之间,最多的就是100附近* 2、财富分布图(帕累托分布or82法则分布):只有很少的拥有了极多的财富* => 能力是呈现正态分布,财富呈现两极分化82分布** 这里的能力:知识 & 技能 & IQ & 判断力 & 毅力。。* 最高能力人财富值->* 最高财富人能力值->* 最幸运人的轨迹-> 财富曲线 & 运气波动* 最惨人的轨迹-> 财富曲线 & 运气波动* 1w次-最富有的人&最幸运的人 &能力最强的人比较* 1w次-最富有的人能力值分布 & 概率占比***/privatePersonServicepersonService; privateLuckEventServiceluckEventService; LuckEventMapperluckEventMapper; PersonMapperpersonMapper; // 全局参数:事件迭代次数 & 试验迭代次数 & 事件时间privateintidx=0; privateintimitCount=1; privatelongetime= (int) (System.currentTimeMillis()/1000); privateEventDbV1eventDbV1=newEventDbV1(); // 总人数样本是否充足?? 1000// 正太分布是否均匀??// 迭代年限是否足够?? interval// 模拟次数是否足够??// 总人数privateinttotalPerson=10000; // 事件发生间隔 0.5表示每半年privatedoubleinterval=0.5; // 模拟次数privateintimitateCount=1; // 事件次数privateinteventCount=20; privatevoidinitparams(TestVOvo) { if(vo!=null){ inttotalPersonT=vo.getTotalPerson(); doubleintervalT=vo.getInterval(); intimitateCountT=vo.getImitateCount(); inteventCountT=vo.getEventCount(); totalPerson=totalPersonT; interval=intervalT; imitateCount=imitateCountT; eventCount=eventCountT; } } publicvoidtest(TestVOvo) throwsIOException { initparams(vo); truncateData(); for (; imitCount<=imitateCount; imitCount++){ // 初始化人类库List<PersonPO>persons=init(totalPerson); // 插入数据库saveToDb(persons); for (intt=1; t<= (eventCount); t++){ // 40/intervalupdateHalfYearV1(++idx, persons); } System.out.println(" ======= event size="+eventDbV1.getElistInsert().size() +" ; plist size="+eventDbV1.getPlistUpdate().size()); System.out.println(" ======= updateBatchById start========"); log.info(" ======= updateBatchById start========"); // 更新人类库personService.updateBatchById(eventDbV1.getPlistUpdate(), 100); System.out.println(" ======= updateBatchById end========"); log.info(" ======= updateBatchById end========"); System.out.println(" ======= event saveBatch start ========"); log.info(" ======= event saveBatch start ========"); // 插入事件luckEventService.saveBatch(eventDbV1.getElistInsert(), 100); System.out.println(" ======= event saveBatch end ========"); log.info(" ======= event saveBatch end ========"); // 模拟时钟任务,每半年执行一次/* Timer timer = new Timer();timer.schedule(new TimerTask() {public void run() {updateHalfYear(++idx, persons);}}, 0, 1000); // 毫秒*/// 模拟每半年执行一次更新// ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);// executor.scheduleAtFixedRate(() -> {// updateHalfYear(++idx, persons);// }, 0, 1000, TimeUnit.MILLISECONDS); } System.in.read(); } /*** 清空历史数据*/privatevoidtruncateData() { personMapper.truncate(); luckEventMapper.truncate(); // personService.getBaseMapper().delete(Wrappers.<PersonPO>lambdaUpdate().ge(PersonPO::getId,0));// luckEventService.getBaseMapper().delete(Wrappers.<LuckEventPO>lambdaUpdate().ge(LuckEventPO::getId,0)); } // 保存person表privatevoidsaveToDb(List<PersonPO>persons) { System.out.println(" ======= personService.saveBatch start========"); log.info(" ======= personService.saveBatch start========"); personService.saveBatch(persons, 100); System.out.println(" ======= personService.saveBatch end========"); log.info(" ======= personService.saveBatch end========"); } // 初始化人类库privateList<PersonPO>init(inttotalPerson) { // 随机生成1000个20岁的Person对象, 能力值按正态分布生成,资产默认为10List<PersonPO>list=newArrayList<>(); for (inti=1 ; i<=totalPerson ; i++){ // int wordIndex = new Random().nextInt(EnWordUtils.getSize());PersonPOperson=newPersonPO(); person.setId(i); person.setAge(20); // person.setName(EnWordUtils.getWord(wordIndex));person.setAbility(generateAbility()); person.setAsset(10D); person.setImitateCount(imitCount); list.add(person); } returnlist; } // 生成0-1范围内的正态分布随机数publicstaticdoublegenerateAbility() { doublev=Math.abs(newRandomDataGenerator().nextGaussian(0.5, 0.1)) %1; return (((BigDecimal.valueOf(v)).setScale(4, RoundingMode.HALF_UP)).doubleValue()); } // 事件迭代privatevoidupdateHalfYearV1(intidx, List<PersonPO>persons) { intsize=totalPerson/1000*201; // 小人坐标Integer[][] board=newInteger[size][size]; // 幸运点坐标Integer[][] boardPoint=newInteger[size][size]; Randomrand=newRandom(); // 生成小人坐标for (PersonPOp : persons) { intx, y; do { x=rand.nextInt(size); y=rand.nextInt(size); } while (board[x][y] !=null&&board[x][y] >0); p.setX(x); p.setY(y); board[x][y] =1; } // 生成随机点for (inti=0; i<totalPerson/1000*500; i++) { intx, y; do { x=rand.nextInt(size); y=rand.nextInt(size); } while (boardPoint[x][y] !=null&&boardPoint[x][y] >0); inttype=rand.nextInt(2); if (type==0) { // 绿点boardPoint[x][y] =2; } else { // 红点boardPoint[x][y] =3; } } etime= (long) (etime+interval*365*24*60*60); List<PersonPO>plistUpdate=newArrayList<>(); List<LuckEventPO>elistInsert=newArrayList<>(); for (PersonPOp : persons) { Doubleb4Asset=p.getAsset(); // 判断好运坏运// boolean isLucky = Math.random() < p.getAbility();Integerinteger=boardPoint[p.getX()][p.getY()]; // 更新年龄p.setAge((int) Math.floor(p.getAge() +interval)); // 更新资产doublenum=b4Asset; if(integer!=null){ booleanisLucky=Math.random() <=p.getAbility() &&integer==2; if (isLucky) { num=b4Asset*2; } else { num=b4Asset/2; } } p.setAsset(((BigDecimal.valueOf(num)).setScale(4, RoundingMode.HALF_UP)).doubleValue()); // 保存事件记录到数据库LuckEventPOluckEventPO=newLuckEventPO(); luckEventPO.setPersonId(p.getId()); luckEventPO.setEtime(etime); luckEventPO.setEvent(integer==null?"normal":(p.getAsset()>b4Asset?"lucky" : "unlucky")); luckEventPO.setIdx(idx); luckEventPO.setB4Asset(b4Asset); luckEventPO.setCtAsset(p.getAsset()); luckEventPO.setImitateCount(imitCount); elistInsert.add(luckEventPO); plistUpdate.add(p); } List<LuckEventPO>elistInsert1=eventDbV1.getElistInsert(); elistInsert1.addAll(elistInsert); eventDbV1.setElistInsert(elistInsert1); eventDbV1.setPlistUpdate(plistUpdate); } // 事件迭代privatevoidupdateHalfYear(intidx, List<PersonPO>persons) { etime= (long) (etime+interval*365*24*60*60); // List<EventDb> elist = new ArrayList<>();List<PersonPO>plistUpdate=newArrayList<>(); List<LuckEventPO>elistInsert=newArrayList<>(); for (PersonPOp : persons) { Doubleb4Asset=p.getAsset(); // 判断好运坏运booleanisLucky=Math.random() <p.getAbility(); // 更新年龄p.setAge((int) Math.floor(p.getAge() +interval)); // 更新资产doublenum; if (isLucky) { num=b4Asset*2; } else { num=b4Asset/2; } p.setAsset(((BigDecimal.valueOf(num)).setScale(4, RoundingMode.HALF_UP)).doubleValue()); // 保存事件记录到数据库// EventDb eventDb = new EventDb();// eventDb.setIdx(idx);// eventDb.setB4Asset(b4Asset);// eventDb.setEtime(etime);// eventDb.setP(p);// eventDb.setEvent(isLucky ? "lucky" : "unlucky");// elist.add(eventDb);LuckEventPOluckEventPO=newLuckEventPO(); luckEventPO.setPersonId(p.getId()); luckEventPO.setEtime(etime); luckEventPO.setEvent(isLucky?"lucky" : "unlucky"); luckEventPO.setIdx(idx); luckEventPO.setB4Asset(b4Asset); luckEventPO.setCtAsset(p.getAsset()); luckEventPO.setImitateCount(imitCount); elistInsert.add(luckEventPO); plistUpdate.add(p); } List<LuckEventPO>elistInsert1=eventDbV1.getElistInsert(); elistInsert1.addAll(elistInsert); eventDbV1.setElistInsert(elistInsert1); eventDbV1.setPlistUpdate(plistUpdate); // saveEventToDb(idx, b4Asset, etime, p, isLucky ? "lucky" : "unlucky");// luckEventService.saveBatch(elistInsert);// personService.updateBatchById(plistUpdate); } classEventDbV1{ List<PersonPO>plistUpdate=newArrayList<>(); List<LuckEventPO>elistInsert=newArrayList<>(); } classEventDb{ intidx;Doubleb4Asset;longetime;PersonPOp;Stringevent; } // 保存事件到数据库publicvoidsaveEventToDb(intidx, Doubleb4Asset, longetime, PersonPOp, Stringevent) { // 构建LuckEvent对象并保存LuckEventPOluckEventPO=newLuckEventPO(); luckEventPO.setPersonId(p.getId()); luckEventPO.setEtime(etime); luckEventPO.setEvent(event); luckEventPO.setIdx(idx); luckEventPO.setB4Asset(b4Asset); luckEventPO.setCtAsset(p.getAsset()); luckEventPO.setImitateCount(imitCount); luckEventService.save(luckEventPO); personService.updateById(p); } }
3、metabase报表呈现:(如不清晰请查看附件)