parent
4bd520cd2f
commit
c480adbf8f
@ -0,0 +1,36 @@ |
|||||||
|
<component name="InspectionProjectProfileManager"> |
||||||
|
<profile version="1.0"> |
||||||
|
<option name="myName" value="Project Default" /> |
||||||
|
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true"> |
||||||
|
<option name="TOP_LEVEL_CLASS_OPTIONS"> |
||||||
|
<value> |
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||||
|
<option name="REQUIRED_TAGS" value="" /> |
||||||
|
</value> |
||||||
|
</option> |
||||||
|
<option name="INNER_CLASS_OPTIONS"> |
||||||
|
<value> |
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||||
|
<option name="REQUIRED_TAGS" value="" /> |
||||||
|
</value> |
||||||
|
</option> |
||||||
|
<option name="METHOD_OPTIONS"> |
||||||
|
<value> |
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||||
|
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" /> |
||||||
|
</value> |
||||||
|
</option> |
||||||
|
<option name="FIELD_OPTIONS"> |
||||||
|
<value> |
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> |
||||||
|
<option name="REQUIRED_TAGS" value="" /> |
||||||
|
</value> |
||||||
|
</option> |
||||||
|
<option name="IGNORE_DEPRECATED" value="false" /> |
||||||
|
<option name="IGNORE_JAVADOC_PERIOD" value="true" /> |
||||||
|
<option name="IGNORE_DUPLICATED_THROWS" value="false" /> |
||||||
|
<option name="IGNORE_POINT_TO_ITSELF" value="false" /> |
||||||
|
<option name="myAdditionalJavadocTags" value="date" /> |
||||||
|
</inspection_tool> |
||||||
|
</profile> |
||||||
|
</component> |
@ -1,3 +1,2 @@ |
|||||||
* 1、修复切换书源对话框内存泄漏的问题 |
* 1、修复从[文档或最近]导入书源时文件读取失败的问题 |
||||||
* 2、优化导入书源选择 |
* 2、新增实验性功能:书籍字数计算(书籍详情页菜单) |
||||||
* 3、修复已知bug |
|
@ -0,0 +1,90 @@ |
|||||||
|
package xyz.fycz.myreader.experiment |
||||||
|
|
||||||
|
import xyz.fycz.myreader.common.APPCONST |
||||||
|
import xyz.fycz.myreader.greendao.entity.Book |
||||||
|
import xyz.fycz.myreader.greendao.service.ChapterService |
||||||
|
import xyz.fycz.myreader.util.IOUtils |
||||||
|
import xyz.fycz.myreader.util.utils.FileUtils |
||||||
|
import java.io.BufferedReader |
||||||
|
import java.io.File |
||||||
|
import java.io.FileReader |
||||||
|
import java.io.IOException |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/4 10:33 |
||||||
|
*/ |
||||||
|
class BookWCEstimate { |
||||||
|
/** |
||||||
|
* -1:本地文件不存在 |
||||||
|
* -2:书籍章节缓存数量少于20 |
||||||
|
*/ |
||||||
|
fun getWordCount(book: Book): Int { |
||||||
|
if (book.type == "本地书籍") { |
||||||
|
return getLocalWordCount(book) |
||||||
|
} else if (book.type.isNotEmpty()) { |
||||||
|
return getNetWordCount(book) |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
private fun getLocalWordCount(book: Book): Int { |
||||||
|
val file = File(book.chapterUrl) |
||||||
|
if (!file.exists()) return -1 |
||||||
|
return -countChar(file) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 计算方式:使用最小二乘法进行线性回归预测 |
||||||
|
*/ |
||||||
|
private fun getNetWordCount(book: Book): Int { |
||||||
|
val chapterService = ChapterService.getInstance() |
||||||
|
val chapters = chapterService.findBookAllChapterByBookId(book.id) |
||||||
|
val map = mutableMapOf<Double, Double>() |
||||||
|
var sum = 0.0 |
||||||
|
var i = 0 |
||||||
|
var cachedChapterSize = 0 |
||||||
|
for (chapter in chapters) { |
||||||
|
if (cachedChapterSize >= 20) break |
||||||
|
if (ChapterService.isChapterCached(chapter.bookId, chapter.title)) { |
||||||
|
cachedChapterSize++ |
||||||
|
} |
||||||
|
} |
||||||
|
if (cachedChapterSize < 20 && chapters.size > 50) return -2 |
||||||
|
chapters.forEach { chapter -> |
||||||
|
if (ChapterService.isChapterCached(chapter.bookId, chapter.title)) { |
||||||
|
sum += countChar( |
||||||
|
File( |
||||||
|
APPCONST.BOOK_CACHE_PATH + chapter.bookId |
||||||
|
+ File.separator + chapter.title + FileUtils.SUFFIX_FY |
||||||
|
) |
||||||
|
) |
||||||
|
map[i++.toDouble()] = sum |
||||||
|
} |
||||||
|
} |
||||||
|
if (map.size == chapters.size) return sum.toInt() |
||||||
|
val lr = LinearRegression(map) |
||||||
|
return lr.getY(chapters.size.toDouble()).toInt() |
||||||
|
} |
||||||
|
|
||||||
|
private fun countChar(file: File): Int { //统计字符数 |
||||||
|
var charnum = 0 //字符数 |
||||||
|
var x: Int |
||||||
|
var fReader: FileReader? = null |
||||||
|
try { |
||||||
|
fReader = FileReader(file) |
||||||
|
while ((fReader.read().also { x = it }) != -1) { //按字符读文件,判断,符合则字符加一 |
||||||
|
val a = x.toChar() |
||||||
|
if (a != '\n' && a != '\r') { |
||||||
|
charnum++ |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (e: IOException) { |
||||||
|
e.printStackTrace() |
||||||
|
} finally { |
||||||
|
IOUtils.close(fReader) |
||||||
|
} |
||||||
|
return charnum //返回结果 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
package xyz.fycz.myreader.experiment; |
||||||
|
|
||||||
|
import java.math.BigDecimal; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/4 12:16 |
||||||
|
*/ |
||||||
|
public class DoubleUtils { |
||||||
|
private static final int DEF_DIV_SCALE = 10; |
||||||
|
|
||||||
|
/** |
||||||
|
* * 两个Double数相加 * |
||||||
|
* |
||||||
|
* @param v1 * |
||||||
|
* @param v2 * |
||||||
|
* @return Double |
||||||
|
*/ |
||||||
|
public static Double add(Double v1, Double v2) { |
||||||
|
BigDecimal b1 = new BigDecimal(v1.toString()); |
||||||
|
BigDecimal b2 = new BigDecimal(v2.toString()); |
||||||
|
return b1.add(b2).doubleValue(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* * 两个Double数相减 * |
||||||
|
* |
||||||
|
* @param v1 * |
||||||
|
* @param v2 * |
||||||
|
* @return Double |
||||||
|
*/ |
||||||
|
public static Double sub(Double v1, Double v2) { |
||||||
|
BigDecimal b1 = new BigDecimal(v1.toString()); |
||||||
|
BigDecimal b2 = new BigDecimal(v2.toString()); |
||||||
|
return b1.subtract(b2).doubleValue(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* * 两个Double数相乘 * |
||||||
|
* |
||||||
|
* @param v1 * |
||||||
|
* @param v2 * |
||||||
|
* @return Double |
||||||
|
*/ |
||||||
|
public static Double mul(Double v1, Double v2) { |
||||||
|
BigDecimal b1 = new BigDecimal(v1.toString()); |
||||||
|
BigDecimal b2 = new BigDecimal(v2.toString()); |
||||||
|
return b1.multiply(b2).doubleValue(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* * 两个Double数相除 * |
||||||
|
* |
||||||
|
* @param v1 * |
||||||
|
* @param v2 * |
||||||
|
* @return Double |
||||||
|
*/ |
||||||
|
public static Double div(Double v1, Double v2) { |
||||||
|
BigDecimal b1 = new BigDecimal(v1.toString()); |
||||||
|
BigDecimal b2 = new BigDecimal(v2.toString()); |
||||||
|
return b1.divide(b2, DEF_DIV_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* * 两个Double数相除,并保留scale位小数 * |
||||||
|
* |
||||||
|
* @param v1 * |
||||||
|
* @param v2 * |
||||||
|
* @param scale * |
||||||
|
* @return Double |
||||||
|
*/ |
||||||
|
public static Double div(Double v1, Double v2, int scale) { |
||||||
|
if (scale < 0) { |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"The scale must be a positive integer or zero"); |
||||||
|
} |
||||||
|
BigDecimal b1 = new BigDecimal(v1.toString()); |
||||||
|
BigDecimal b2 = new BigDecimal(v2.toString()); |
||||||
|
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public static int max(int a, int b) { |
||||||
|
return Math.max(a, b); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public static int min(int a, int b) { |
||||||
|
return Math.min(a, b); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,128 @@ |
|||||||
|
package xyz.fycz.myreader.experiment; |
||||||
|
|
||||||
|
import static xyz.fycz.myreader.experiment.DoubleUtils.add; |
||||||
|
import static xyz.fycz.myreader.experiment.DoubleUtils.div; |
||||||
|
import static xyz.fycz.myreader.experiment.DoubleUtils.mul; |
||||||
|
import static xyz.fycz.myreader.experiment.DoubleUtils.sub; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 使用最小二乘法实现线性回归预测 |
||||||
|
* |
||||||
|
* @author fengyue |
||||||
|
* @date 2021/12/4 12:15 |
||||||
|
*/ |
||||||
|
public class LinearRegression { |
||||||
|
|
||||||
|
/** |
||||||
|
* 训练集数据 |
||||||
|
*/ |
||||||
|
private Map<Double, Double> initData = new HashMap<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 截距 |
||||||
|
*/ |
||||||
|
private double intercept = 0.0; |
||||||
|
//斜率
|
||||||
|
private double slope = 0.0; |
||||||
|
|
||||||
|
/** |
||||||
|
* x、y平均值 |
||||||
|
*/ |
||||||
|
private double averageX, averageY; |
||||||
|
/** |
||||||
|
* 求斜率的上下两个分式的值 |
||||||
|
*/ |
||||||
|
private double slopeUp, slopeDown; |
||||||
|
|
||||||
|
|
||||||
|
public LinearRegression(Map<Double, Double> initData) { |
||||||
|
this.initData = initData; |
||||||
|
initData(); |
||||||
|
} |
||||||
|
|
||||||
|
public LinearRegression() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据训练集数据进行训练预测 |
||||||
|
* 并计算斜率和截距 |
||||||
|
*/ |
||||||
|
public void initData() { |
||||||
|
if (initData.size() > 0) { |
||||||
|
//数据个数
|
||||||
|
int number = 0; |
||||||
|
//x值、y值总和
|
||||||
|
double sumX = 0; |
||||||
|
double sumY = 0; |
||||||
|
averageX = 0; |
||||||
|
averageY = 0; |
||||||
|
slopeUp = 0; |
||||||
|
slopeDown = 0; |
||||||
|
for (Double x : initData.keySet()) { |
||||||
|
if (x == null || initData.get(x) == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
number++; |
||||||
|
sumX = add(sumX, x); |
||||||
|
sumY = add(sumY, initData.get(x)); |
||||||
|
} |
||||||
|
//求x,y平均值
|
||||||
|
averageX = div(sumX, (double) number); |
||||||
|
averageY = div(sumY, (double) number); |
||||||
|
|
||||||
|
for (Double x : initData.keySet()) { |
||||||
|
if (x == null || initData.get(x) == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
slopeUp = add(slopeUp, mul(sub(x, averageX), sub(initData.get(x), averageY))); |
||||||
|
slopeDown = add(slopeDown, mul(sub(x, averageX), sub(x, averageX))); |
||||||
|
|
||||||
|
} |
||||||
|
initSlopeIntercept(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 计算斜率和截距 |
||||||
|
*/ |
||||||
|
private void initSlopeIntercept() { |
||||||
|
if (slopeUp != 0 && slopeDown != 0) { |
||||||
|
slope = slopeUp / slopeDown; |
||||||
|
} |
||||||
|
intercept = averageY - averageX * slope; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据x值预测y值 |
||||||
|
* |
||||||
|
* @param x x值 |
||||||
|
* @return y值 |
||||||
|
*/ |
||||||
|
public Double getY(Double x) { |
||||||
|
return add(intercept, mul(slope, x)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据y值预测x值 |
||||||
|
* |
||||||
|
* @param y y值 |
||||||
|
* @return x值 |
||||||
|
*/ |
||||||
|
public Double getX(Double y) { |
||||||
|
return div(sub(y, intercept), slope); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Map<Double, Double> getInitData() { |
||||||
|
return initData; |
||||||
|
} |
||||||
|
|
||||||
|
public void setInitData(Map<Double, Double> initData) { |
||||||
|
this.initData = initData; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
* 实验性功能 |
||||||
|
* 回归模型预测小说字数 |
@ -0,0 +1,12 @@ |
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:width="24dp" |
||||||
|
android:height="24dp" |
||||||
|
android:viewportWidth="1024" |
||||||
|
android:viewportHeight="1024"> |
||||||
|
<path |
||||||
|
android:pathData="M832.04,64.66H192.03C121.26,64.66 63.94,121.99 63.94,192.69v446.79C63.94,710.21 121.26,767.64 192.03,767.64h133.35a63.94,63.94 0,0 1,55.22 31.58l76.1,129.84c12.36,21.09 33.79,31.63 55.22,31.63s42.83,-10.54 55.2,-31.62l76.22,-129.87a63.94,63.94 0,0 1,55.21 -31.55h133.54c70.58,0 127.73,-57.29 127.73,-127.8V192.39C959.83,121.85 902.64,64.66 832.04,64.66zM895.88,639.84A63.85,63.85 0,0 1,832.09 703.7h-133.54a127.75,127.75 0,0 0,-110.35 63.1l-76.22,129.86a0.27,0.27 0,0 1,0 -0.05h-0.03s-0.02,0.06 -0.03,0.06l-76.15,-129.85A127.8,127.8 0,0 0,325.38 703.7H192.03A64.21,64.21 0,0 1,127.88 639.49V192.69A64.1,64.1 0,0 1,192.03 128.6h640.01A63.8,63.8 0,0 1,895.88 192.39v447.45z" |
||||||
|
android:fillColor="#ffffff"/> |
||||||
|
<path |
||||||
|
android:pathData="M608.15,288.09A31.97,31.97 0,0 0,576.18 320.06v160.08l-134.65,-179.28A31.97,31.97 0,0 0,384 320.06v255.76a31.97,31.97 0,0 0,63.94 0v-159.96l134.65,179.27a31.97,31.97 0,0 0,57.53 -19.2V320.06a31.97,31.97 0,0 0,-31.97 -31.97z" |
||||||
|
android:fillColor="#ffffff"/> |
||||||
|
</vector> |
@ -1,3 +1,3 @@ |
|||||||
#Fri Jun 18 21:45:31 CST 2021 |
#Fri Jun 18 21:45:31 CST 2021 |
||||||
VERSION_CODE=222 |
VERSION_CODE=223 |
||||||
NEED_CREATE_RELEASE=true |
NEED_CREATE_RELEASE=false |
||||||
|
Loading…
Reference in new issue