有话要说:

前阵子遇到了一个计算可达用户投资额的问题,觉得非常有趣,故把它记录下来。

问题描述:

某产品可被投资,注册后才可以投资,其注册可以被邀请(并不是每个人都是被邀请的)。邀请人可以邀请多个人注册投资,而被邀请人只能有一位邀请人。也就是说邀请人与被邀请人是一对多的关系。

现给定两组数据:邀请与被邀请的关系;所有用户的投资额。

如下:

1,2
1,4
2,11
2,10
4,5
5,6
6,8
6,9
6,7
13,14
13,15
13,17
14,20
20,21
15,18
15,19

以上数据是处理过后的邀请关系,前者邀请了后者。

1,1200.00
2,2300.00
4,1500.00
5,7300.00
6,4100.00
7,1100.00
8,9000.00
9,1000.00
10,1100.00
11,100.00
12,1000.00
13,4500.00
14,1100.00
15,1200.00
17,700.00
18,100.00
19,200.00
20,100.00
21,0.00

以上数据是处理过后的所有用户投资额。

现在根据这两组数据需要计算每位用户的“用户投资金额”,“可达用户投资金额”,“邀请用户阶数”,“成功邀请用户数”,“一阶推荐用户投资金额”。

用户投资金额:即该用户的投资额,以以上数据举个例子来说:2的投资金额为2300

可达用户投资金额:即该用户的投资额加上该用户邀请的所有人的投资额(包括该用户邀请人所邀请的人),假设A邀请了B和C,B邀请了D和E,C、D、E没有邀请别人,那么A的可达用户投资金额就是A、B、C、D、E投资额的总数

邀请用户阶数:即该用户所邀请人的总数(不包括该用户邀请人所邀请的人),假设A邀请了B和C,B邀请了D和E,C、D、E没有邀请别人,那么A的邀请用户阶数为2

成功邀请用户数:在邀请用户阶数的基础上除去投资额为零的用户

一阶推荐用户投资金额:即该用户所邀请人的总投资额(不包括该用户邀请人所邀请的人),假设A邀请了B和C,B邀请了D和E,C、D、E没有邀请别人,那么A的一阶推荐用户投资金额为B、C投资额的总数

大家可以先想想如何处理该数据

思路分析:

首先根据邀请的关系可以分析出邀请人的关系图可以分解为多棵不相关的树(反证法可证)。

邀请关系如下图所示:

因此可以先将数据生成若干棵树,然后针对于树进行遍历。

对于每棵树来说,父节点的可达用户投资金额就是子节点的可达用户投资金额之和再加上父节点的投资额,根据这一特性,可以对每棵树从叶子节点网上遍历。

代码实现:

获取所有用户的投资额:

直接读取文件,并把用户id与用户投资额放在Map集合中

 // 获取每个人的投资额
public static Map<String, Double> getAmount() {
Map<String, Double> amounts = new HashMap<String, Double>(); File file = new File("D:/amount.txt"); try {
// 创建输入流,读取txt
InputStream is = new FileInputStream(file.getAbsolutePath());
InputStreamReader isr = new InputStreamReader(is, "UTF-8"); BufferedReader br = new BufferedReader(isr);
String line = "";
while ((line = br.readLine()) != null) {
String[] amount = line.trim().split(",");
amount[0] = amount[0].trim();
amounts.put(amount[0], Double.parseDouble(amount[1]));
} br.close();
isr.close();
is.close(); } catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return amounts;
}

获取用户的邀请关系:

直接读取文件,并把用户的邀请关系放在List集合中

 // 获取关系数据集合
public static List<String[]> getRelationship() {
List<String[]> list = new ArrayList<String[]>(); File file = new File("D:/relationship.txt"); try {
// 创建输入流,读取txt
InputStream is = new FileInputStream(file.getAbsolutePath());
InputStreamReader isr = new InputStreamReader(is, "UTF-8"); BufferedReader br = new BufferedReader(isr);
String line = "";
while ((line = br.readLine()) != null) {
String[] relation = line.trim().split(",");
relation[0] = relation[0].trim();
relation[1] = relation[1].trim();
list.add(relation);
} br.close();
isr.close();
is.close(); } catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return list;
}

构造Person对象:

 // 用户邀请关系(前者邀请了后者)
List<String[]> relationships = new ArrayList<>();
relationships = getRelationship(); // 每个用户投资额
Map<String, Double> amounts = new HashMap<String, Double>();
amounts = getAmount(); // 构造用户信息
Map<String, Person> persons = new HashMap<String, Person>();
Iterator<String> it = amounts.keySet().iterator();
while (it.hasNext()) {
String key = it.next(); Person temp = new Person();
temp.setId(key);
temp.setAmount(amounts.get(key)); persons.put(key, temp);
}

其中Person类为:

 class Person {

     /**
* 用户id
*/
private String id; /**
* 邀请人列表
*/
private List<Person> invitedPersons = new ArrayList<Person>(); /**
* 投资额
*/
private double amount; /**
* 所有下线投资额总额(包括自身)
*/
private double allAmount; public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public double getAmount() {
return amount;
} public void setAmount(double amount) {
this.amount = amount;
} public List<Person> getInvitedPersons() {
return invitedPersons;
} public void setInvitedPersons(List<Person> invitedPersons) {
this.invitedPersons = invitedPersons;
} public double getAllAmount() {
return allAmount;
} public void setAllAmount(double allAmount) {
this.allAmount = allAmount;
} }

构建邀请关系:

 for (int i = 0; i < relationships.size(); i++) {
String[] relationship = relationships.get(i); // 获取邀请人
Person person1 = persons.get(relationship[0]);
// 获取被邀请人
Person person2 = persons.get(relationship[1]);
// 加上关联关系
person1.getInvitedPersons().add(person2);
}

根据以上的构造,已经针对于每个用户都生成了一棵树。

但事实上这些树有大量的重合,如果逐一遍历的话会导致不必要的浪费。

于是准备找出所有的根树,遍历根数即可。

找到所有的根树:

 // 所有person集合
List<Person> allPerson = new ArrayList<>();
allPerson.addAll(persons.values()); // 找到根节点,去除非根节点
for (int i = 0; i < relationships.size(); i++) {
String[] relationship = relationships.get(i); // 如果是被邀请人,则去除
persons.remove(relationship[1]);
}

根据以上精简,persons集合里只有根节点。

遍历根数,找出所有人的可达用户投资金额

Iterator<Person> it4 = persons.values().iterator();
while (it4.hasNext()) {
Person person = it4.next();
addAllChildren(person);
}

其中addAllChildren为

 // 可达用户投资金额
public static double addAllChildren(Person person) {
double childrenAmount = 0; // 先加自己的
childrenAmount += person.getAmount(); // 再加孩子的
for (int i = 0; i < person.getInvitedPersons().size(); i++) {
Person child = person.getInvitedPersons().get(i); if (child.getInvitedPersons().size() > 0) {
childrenAmount += addAllChildren(child);
} else {
childrenAmount += child.getAmount();
}
} person.setAllAmount(childrenAmount); return childrenAmount;
}

通过此遍历,每个person对象的可达用户投资金额均已被算出,又因为java是值传递的,所以allPerson内的person对象的值都已经被计算出来了。

接下来就可以遍历所有的person对象了。

遍历person集合,算出需要的所有数据:

 try {
String path = "D:/result.txt";
File file = new File(path);
if (!file.exists()) {
file.getParentFile().mkdirs();
}
file.createNewFile(); // write
FileWriter fw = new FileWriter(file, true);
BufferedWriter bw = new BufferedWriter(fw); Iterator<Person> it3 = allPerson.iterator(); while (it3.hasNext()) {
Person person = it3.next(); if (person.getAllAmount() == 0) {
bw.write("可达用户投资总额:" + person.getAmount() + ";");
} else {
bw.write("可达用户投资总额:" + person.getAllAmount() + ";");
} bw.write("邀请用户阶数:" + person.getInvitedPersons().size() + ";"); int count = 0;
double sum = 0;
for (Person temp : person.getInvitedPersons()) {
if (temp.getAmount() != 0) {
sum += temp.getAmount();
count++;
}
} bw.write("成功邀请用户数:" + count + ";");
bw.write("一阶推荐用户投资金额:" + sum + ";\n");
} bw.flush();
bw.close();
fw.close();
} catch (Exception e) {
e.printStackTrace();
}

结束语:

以上便是我这次遇到问题的解决思路以及解决过程,对于树的构造以及java值传递的应用值得学习。

本题的解决思路以及解决过程比较仓促,只用了一下午的时间,可能会有更好的解决办法,如果大家有什么更好的解决办法欢迎留言~

大家如果有什么疑问或者建议可以通过评论或者邮件的方式联系我,欢迎大家的评论~

可达用户投资额的计算(Java)的更多相关文章

  1. 计算Java对象内存大小

    摘要 本文以如何计算Java对象占用内存大小为切入点,在讨论计算Java对象占用堆内存大小的方法的基础上,详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍,涉及内存模型.锁原理 ...

  2. Win7网上邻居提示未授予用户在此计算机上的请求登录类型解决办法

        内容简介 装了Win7之后很多人遇到这样的问题,网上邻居访问Win7的电脑时出现“未授予用户在此计算机上的请求登录类型”问题.打开“控制面板”--“管理工具”--“本地安全策略”--“本地策略 ...

  3. windows 7 共享,未授予用户在此计算机上的请求登录类型

    刚刚重装了windows7,新下载的一个ghost版本,结果却不能共享,每次访问这台机器的共享都提示, 未授予用户在此计算机上的请求登录类型 这个情况好像是存在于win7访问win7,我用一台XP系统 ...

  4. win7基于mahout推荐之用户相似度计算

    http://www.douban.com/note/319219518/?type=like win7基于mahout推荐之用户相似度计算 2013-12-03 09:19:11    事情回到半年 ...

  5. 如何准确计算Java对象的大小

    如何准确计算Java对象的大小 原创文章,转载请注明:博客园aprogramer 原文链接:如何准确计算Java对象的大小      有时,我们需要知道Java对象到底占用多少内存,有人通过连续调用两 ...

  6. 两种计算Java对象大小的方法

    之前想研究一下unsafe类,碰巧在网上看到了这篇文章,觉得写得很好,就转载过来.原文出处是: http://blog.csdn.net/iter_zc/article/details/4182271 ...

  7. Ehcache计算Java对象内存大小

    在EHCache中,可以设置maxBytesLocalHeap.maxBytesLocalOffHeap.maxBytesLocalDisk值,以控制Cache占用的内存.磁盘的大小(注:这里Off ...

  8. 简单复利计算java板

    一.要求: 1.客户说:帮我开发一个复利计算软件. 2如果按照单利计算,本息又是多少呢? 3.假如30年之后要筹措到300万元的养老金,平均的年回报率是3%,那么,现在必须投入的本金是多少呢? 4.利 ...

  9. CCF 201612-2 工资计算 java 解题

    问题描述 小明的公司每个月给小明发工资,而小明拿到的工资为交完个人所得税之后的工资.假设他一个月的税前工资(扣除五险一金后.未扣税前的工资)为S元,则他应交的个人所得税按如下公式计算: 1) 个人所得 ...

随机推荐

  1. Spring Boot 中关于自定义异常处理的套路!

    在 Spring Boot 项目中 ,异常统一处理,可以使用 Spring 中 @ControllerAdvice 来统一处理,也可以自己来定义异常处理方案.Spring Boot 中,对异常的处理有 ...

  2. python爬虫踩坑教程

    我们的目标是爬取下面这个个网址上的2010~2018年的数据 http://stockdata.stock.hexun.com/zrbg/Plate.aspx?date=2015-12-31 获取我们 ...

  3. C#采用vony.Html.AIO插件批量爬MM网站图片

    一.创建项目 1.创建一个.netframework的控制台项目命名为Crawler 2.安装nuget包搜索名称Ivony.Html.AIO,使用该类库什么方便类似jqury的选择器可以根据类名或者 ...

  4. Java-每日编程练习题②(数组练习)

    1.有一个已经排好序的数组.现输入一个数,要求按原来的规律将它插入数组中. 分析思路: 先通过Random类随机创建一个数组,再调用Arrays类中的排序方法sort排好序,然后再开始实现功能. 按原 ...

  5. Restframe_work 回顾记忆集

    目录 Restframe_work 回顾记忆集 rest_framework主要功能介绍 rest_framework主要模块介绍 记忆集 错题集 混淆集 重点集 难点集 Restframe_work ...

  6. 解决ruby安装后无法添加淘宝gem源------------学习记录

    使用sass ,需要安装ruby,会建议移除gem源,添加淘宝的gem源,但是淘宝的镜像源已经停止维护啦!!用https://gems.ruby-china.com 代替即可. 操作如下: 1)删除原 ...

  7. cesium 之图层管理器篇(附源码下载)

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...

  8. canvas百分比加载动画

    window.onload = function(){ var canvas = document.getElementById('canvas'), //获取canvas元素 context = c ...

  9. SpringBoot2.0之六 多环境配置

    开发过程中面对不同的环境,例如数据库.redis服务器等的不同,可能会面临一直需要修改配置的麻烦中,在以前的项目中,曾通过Tomcat的配置来实现,有的项目甚至需要手动修改相关配置,这种方式费时费力, ...

  10. PHP中$GLOBALS和global的区别

    很多人都认为$GLOBALS['var']和global $var只是写法上不同,其实并不是这样 根据官方的解释是  $GLOBALS['var']是外部全局变量$var的本身, 而global $v ...