PS:本次油耗计算,是基于对标产品小熊油耗进行测试研究输出的实现逻辑。
小熊油耗算法测试:
次 | 加油量 | 当前总公里 | 是否灯亮 | 是否跳枪 | 是否漏计上次 | 油耗(升/百公里) | 算法 | 公式 |
1 | 2 | 100 | y | y | n | 0 | ||
2 | 2 | 200 | y | y | n | 2 | 1 | 2/(200-100)*100 |
3 | 1.8 | 280 | y | n | n | 2.5 | 2 | 2/(280-200)*100 |
4 | 1.6 | 340 | n | n | n | 0 | ||
5 | 1.6 | 400 | n | n | n | 0 | ||
6 | 1.8 | 480 | y | n | n | 2.5 | 4 | (1.8+1.6+1.6)/(480-280)*100 |
7 | 1.6 | 560 | n | n | n | 0 | ||
8 | 1.8 | 640 | n | y | n | 2.32 | 3 | (1.8+1.6+1.6+1.8+1.6+1.8)/(640-200)*100 |
··· | ||||||||
9 | 1.8 | 720 | n | y | y | 0 | 1.8/(720-640)*100 | |
10 | 1.8 | 800 | n | y | n | 2.25 | 1 | 1.8/(800-720)*100 |
1 | 35 | 100 | y | y | y | 0 | ||
2 | 35 | 200 | y | y | y | 2.73 | ||
3 | 40 | 300 | y | y | y | 3.12 | ||
4 | 36 | 400 | 2.81 | |||||
5 | 55 | 500 | 4.29 | |||||
6 |
油耗计算业务流程图:(程序参考)
程序实现:
package com.xiulian.open;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
/**
* 程序实现油耗算法
* 注意:
* 1、测试中的历史数据应以数据库为准,当前数据以当前输入的数据为准
* 2、static方法方便测试,实际程序应去掉
*/
public class TestOilConsumptionResult {
public static void main(String[] args) {
// OilRecord currentRecord = new OilRecord(1, 2, 100, 0, true, true, true);
// OilRecord currentRecord = new OilRecord(2, 2, 200, 0, true, true, true);
// OilRecord currentRecord = new OilRecord(3, 1.8, 280, 0, false, true, true);
// OilRecord currentRecord = new OilRecord(4, 1.6, 340, 0, false, false, true);
// OilRecord currentRecord = new OilRecord(5, 1.6, 400, 0, false, false, true);
// OilRecord currentRecord = new OilRecord(6, 1.8, 480, 0, false, true, true);
// OilRecord currentRecord = new OilRecord(7, 1.6, 560, 0, false, false, true);
// OilRecord currentRecord = new OilRecord(8, 1.8, 640, 0, true, false, true);
// //···中间漏记
// OilRecord currentRecord = new OilRecord(9, 1.8, 720, 0, true, false, false);
OilRecord currentRecord = new OilRecord(10, 1.8, 800, 0, true, false, true);
//计算逻辑
double oilConsumptionResult = oilConsumptionResultLogic(currentRecord);
System.out.println("油耗结果:" + oilConsumptionResult);
}
private static List<OilRecord> getHistoryRecords() {
List<OilRecord> historyRecordList = new ArrayList<>();
historyRecordList.add(new OilRecord(1, 2, 100, 0, true, true, true));
historyRecordList.add(new OilRecord(2, 2, 200, 0, true, true, true));
historyRecordList.add(new OilRecord(3, 1.8, 280, 0, false, true, true));
historyRecordList.add(new OilRecord(4, 1.6, 340, 0, false, false, true));
historyRecordList.add(new OilRecord(5, 1.6, 400, 0, false, false, true));
historyRecordList.add(new OilRecord(6, 1.8, 480, 0, false, true, true));
historyRecordList.add(new OilRecord(7, 1.6, 560, 0, false, false, true));
historyRecordList.add(new OilRecord(8, 1.8, 640, 0, true, false, true));
// //···中间漏记
historyRecordList.add(new OilRecord(9, 1.8, 720, 0, true, false, false));
// historyRecordList.add(new OilRecord(10, 1.8, 800, 0, true, false, true));
return historyRecordList;
}
/**
* 业务逻辑方法
* @param currentRecord
* @return
*/
private static double oilConsumptionResultLogic(OilRecord currentRecord) {
if (currentRecord.isForgetLastTime()) {
if (currentRecord.isJumpGun()) {
OilRecordLogic lastJumpGunData = getLastJumpGunRecord(currentRecord);
if (null == lastJumpGunData) {
return noJumpGunLogic(currentRecord);
} else {
if (currentRecord.isOilLampWarning()) {
OilRecordLogic lastOilLampWarningData = getLastOilLampWarningData(currentRecord);
if (null == lastOilLampWarningData) {
return selectJumpGunAlgorithm(lastJumpGunData);
} else {
if (lastJumpGunData.getSpanNum() <= lastOilLampWarningData.getSpanNum()) {
return selectJumpGunAlgorithm(lastJumpGunData);
} else {
return selectOilLampWarningAlgorithm(lastOilLampWarningData);
}
}
} else {
return selectJumpGunAlgorithm(lastJumpGunData);
}
}
} else {
return noJumpGunLogic(currentRecord);
}
} else {
return 0;
}
}
/**
* 没跳枪逻辑
*
* @param currentRecord
* @return
*/
private static double noJumpGunLogic(OilRecord currentRecord) {
if (currentRecord.isOilLampWarning()) {
OilRecordLogic lastOilLampWarningData = getLastOilLampWarningData(currentRecord);
if (null == lastOilLampWarningData) {
return 0;
} else {
return selectOilLampWarningAlgorithm(lastOilLampWarningData);
}
} else {
return 0;
}
}
/**
* 跳枪,选择算法
*
* @param lastJumpGunData
* @return
*/
private static double selectJumpGunAlgorithm(OilRecordLogic lastJumpGunData) {
if (lastJumpGunData.getSpanNum() > 1) {
//算法3
return algorithm3(lastJumpGunData.getSpanAddOilNum(), lastJumpGunData.getSpanMileage());
} else {
//算法1
return algorithm1(lastJumpGunData.getSpanAddOilNum(), lastJumpGunData.getSpanMileage());
}
}
/**
* 灯亮,选择算法
*
* @param lastOilLampWarningData
* @return
*/
private static double selectOilLampWarningAlgorithm(OilRecordLogic lastOilLampWarningData) {
if (lastOilLampWarningData.getSpanNum() > 1) {
//算法4
return algorithm4(lastOilLampWarningData.getSpanAddOilNum(), lastOilLampWarningData.getSpanMileage());
} else {
//算法2
return algorithm2(lastOilLampWarningData.getSpanAddOilNum(), lastOilLampWarningData.getSpanMileage());
}
}
/**
* 获取上次跳枪数据
*
* @return
*/
private static OilRecordLogic getLastJumpGunRecord(OilRecord currentRecord) {
OilRecordLogic oilRecordLogic = new OilRecordLogic();
int spanNum = 0;//跨度数量
double spanAddOilNum = 0;//阶段油耗
List<OilRecord> historyRecordList = getHistoryRecords();//TODO 模拟数据,真实应从数据库获取
int historyRecordListSize = historyRecordList.size();
for (int i = historyRecordListSize - 1; i >= 0; i--) {
OilRecord oilRecord = historyRecordList.get(i);
spanNum++;
if (oilRecord.isJumpGun) {
oilRecordLogic.setOilRecord(oilRecord);
oilRecordLogic.setSpanNum(spanNum);
oilRecordLogic.setSpanMileage(currentRecord.getTotalMileage() - oilRecord.getTotalMileage());
oilRecordLogic.setSpanAddOilNum(spanAddOilNum+currentRecord.getAddOilNum());
return oilRecordLogic;
}
spanAddOilNum += oilRecord.getAddOilNum();//跳枪,阶段油量,含后、不含前
}
return null;
}
/**
* 获取上次灯亮数据
*
* @return
*/
private static OilRecordLogic getLastOilLampWarningData(OilRecord currentRecord) {
OilRecordLogic oilRecordLogic = new OilRecordLogic();
int spanNum = 0;//跨度数量
double spanAddOilNum = 0;//阶段油耗
List<OilRecord> historyRecordList = getHistoryRecords();//TODO 模拟数据,真实应从数据库获取
int historyRecordListSize = historyRecordList.size();
for (int i = historyRecordListSize - 1; i >= 0; i--) {
OilRecord oilRecord = historyRecordList.get(i);
spanNum++;
spanAddOilNum += oilRecord.getAddOilNum();//灯亮,阶段油量,不含后、含前
if (oilRecord.isOilLampWarning) {
oilRecordLogic.setOilRecord(oilRecord);
oilRecordLogic.setSpanNum(spanNum);
oilRecordLogic.setSpanMileage(currentRecord.getTotalMileage() - oilRecord.getTotalMileage());
oilRecordLogic.setSpanAddOilNum(spanAddOilNum);
return oilRecordLogic;
}
}
return null;
}
/**
* 算法1:计算两次跳枪
*
* @param addOilNumNext
* @param mileageNext
* @return
*/
private static double algorithm1(double addOilNumNext, double mileageNext) {
//油耗 = 第二次加油量/这两次记录之间的行驶里程
double oilConsumption = addOilNumNext / mileageNext * 100;
return Double.valueOf(new DecimalFormat("#.00").format(oilConsumption));//保留两位小数点(四舍五入)
}
/**
* 算法2:计算两次灯亮
*
* @param addOilNumLast
* @param mileageLast
* @return
*/
private static double algorithm2(double addOilNumLast, double mileageLast) {
//油耗 = 第一次加油量/这两次记录之间的行驶里程
double oilConsumption = addOilNumLast / mileageLast * 100;
return Double.valueOf(new DecimalFormat("#.00").format(oilConsumption));//保留两位小数点(四舍五入)
}
/**
* 算法3:计算两点跳枪
*
* @param spanOilConsumptionNext
* @param spanMileageNext
* @return
*/
private static double algorithm3(double spanOilConsumptionNext, double spanMileageNext) {
//阶段油耗 = 上一次加满(不含)到下一次加满(含)之间加油总量/上一次加满到下一次加满之间的里程
double oilConsumptionCalculate = spanOilConsumptionNext / spanMileageNext * 100;
return Double.valueOf(new DecimalFormat("#.00").format(oilConsumptionCalculate));
}
/**
* 算法4:计算两点灯亮
*
* @param spanOilConsumptionLast
* @param spanMileageLast
* @return
*/
private static double algorithm4(double spanOilConsumptionLast, double spanMileageLast) {
//阶段油耗 = 上一次油灯亮(含)到下一次油灯亮(不含)之间加油总量/上一次油灯亮到下一次油灯亮之间的里程。
double oilConsumptionCalculate = spanOilConsumptionLast / spanMileageLast * 100;
return Double.valueOf(new DecimalFormat("#.00").format(oilConsumptionCalculate));
}
private static class OilRecordLogic {
private OilRecord OilRecord;//加油记录
private int spanNum;//跨度数量
private double spanMileage;//阶段里程
private double spanAddOilNum;//阶段油耗
public TestOilConsumptionResult.OilRecord getOilRecord() {
return OilRecord;
}
public void setOilRecord(TestOilConsumptionResult.OilRecord oilRecord) {
OilRecord = oilRecord;
}
public int getSpanNum() {
return spanNum;
}
public void setSpanNum(int spanNum) {
this.spanNum = spanNum;
}
public double getSpanMileage() {
return spanMileage;
}
public void setSpanMileage(double spanMileage) {
this.spanMileage = spanMileage;
}
public double getSpanAddOilNum() {
return spanAddOilNum;
}
public void setSpanAddOilNum(double spanAddOilNum) {
this.spanAddOilNum = spanAddOilNum;
}
}
private static class OilRecord {
private double addOilNum;//加油量
private double totalMileage;//总公里
private double oilConsumption;//油耗
private boolean isJumpGun;//是否跳枪
private boolean isOilLampWarning;//是否灯亮
private boolean isForgetLastTime;//是否忘记上次
private int id;
public OilRecord(int id, double addOilNum, double totalMileage, double oilConsumption, boolean isJumpGun, boolean isOilLampWarning, boolean isForgetLastTime) {
this.addOilNum = addOilNum;
this.totalMileage = totalMileage;
this.oilConsumption = oilConsumption;
this.isJumpGun = isJumpGun;
this.isOilLampWarning = isOilLampWarning;
this.isForgetLastTime = isForgetLastTime;
this.id = id;
}
public double getAddOilNum() {
return addOilNum;
}
public void setAddOilNum(double addOilNum) {
this.addOilNum = addOilNum;
}
public double getTotalMileage() {
return totalMileage;
}
public void setTotalMileage(double totalMileage) {
this.totalMileage = totalMileage;
}
// public double getOilConsumption() {
// return oilConsumption;
// }
//
// public void setOilConsumption(double oilConsumption) {
// this.oilConsumption = oilConsumption;
// }
public boolean isJumpGun() {
return isJumpGun;
}
public void setJumpGun(boolean jumpGun) {
isJumpGun = jumpGun;
}
public boolean isOilLampWarning() {
return isOilLampWarning;
}
public void setOilLampWarning(boolean oilLampWarning) {
isOilLampWarning = oilLampWarning;
}
public boolean isForgetLastTime() {
return isForgetLastTime;
}
public void setForgetLastTime(boolean forgetLastTime) {
isForgetLastTime = forgetLastTime;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
}
结果对比:
小熊油耗 | 车有料 | ||
最新油耗 | 7.9升/百公里 | 最新油耗 | 7.88升/百公里 |
平均油耗 | 7.43升/百公里 | 平均油耗 | 7.66升/百公里 |
研究记录:
平均油耗:
平均油耗=所有单次油耗的平均值
每公里油费计算方法:
如果能计算出当次油耗,则能计算出当次每公里油费,公式为:
两点间加满,实付金额之和(不含第一个点记录的金额)/两点间的里程差。
两点间油灯亮,实付金额之和(不含当前记录的金额)/两点间的里程差。
历史每公里油费=所有当次每公里油费的均值。
注:另一个小熊油耗计算方式:当前行程每公里油费= 油耗(L/100KM) * 上次加油单价(元/L) / 100
场景算法:
理想状态下的油耗计算:(数值最精准)
描述:每次记账时,都存在跳枪,或都存在灯亮,这样任意两点之间都可以计算油耗,否则就会采用半理想状态下的算法。
算法1:两次跳枪
油耗 = 第二次加油量/这两次记录之间的行驶里程
算法2:两次亮灯
油耗 = 第一次加油量/这两次记录之间的行驶里程
注意:算法1和算法2同时满足,优先选择算法1
半理想状态下的油耗计算:(数值较精准)
描述:从有跳枪,或者灯亮的点开始,直到下一次跳枪或灯亮(中间可以存在多次加油,但不满足计算),算这之间的总加油/总里程。
算法3:两点跳枪
阶段油耗 = 上一次加满(不含)到下一次加满(含)之间加油总量/上一次加满到下一次加满之间的里程
算法4:两点亮灯
阶段油耗 = 上一次油灯亮(含)到下一次油灯亮(不含)之间加油总量/上一次油灯亮到下一次油灯亮之间的里程。
注意:算法3和算法4同时满足,优先采用时间跨度小的算法。
小结:
1、油耗的计算严重依赖于两次计算的跳枪或灯亮,可计算的两点(可以存在多次加油)必需满足都跳枪,或者都灯亮。
2、无法计算油耗说明:暂时无法计算油耗,需要加油加满两次,才能算出一次油耗,或者等油耗警告灯一亮就去加油,加两次后也能算出一次油耗。3、上次没记录,则当次油耗不能计算