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、修复切换书源对话框内存泄漏的问题 |
||||
* 2、优化导入书源选择 |
||||
* 3、修复已知bug |
||||
* 1、修复从[文档或最近]导入书源时文件读取失败的问题 |
||||
* 2、新增实验性功能:书籍字数计算(书籍详情页菜单) |
@ -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 |
||||
VERSION_CODE=222 |
||||
NEED_CREATE_RELEASE=true |
||||
VERSION_CODE=223 |
||||
NEED_CREATE_RELEASE=false |
||||
|
Loading…
Reference in new issue