From d26c29613bfc17c399f982613ad408ec7959395a Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 14 Sep 2025 14:33:14 +0800 Subject: [PATCH] :recycle: Refactor graph calculating --- lib/screens/calculator_home_page.dart | 4 +- lib/solver.dart | 104 +++++++++++++++++++++++--- lib/widgets/graph_card.dart | 13 ++-- test/solver_test.dart | 26 +++++++ 4 files changed, 131 insertions(+), 16 deletions(-) diff --git a/lib/screens/calculator_home_page.dart b/lib/screens/calculator_home_page.dart index f2061a3..8348f54 100644 --- a/lib/screens/calculator_home_page.dart +++ b/lib/screens/calculator_home_page.dart @@ -47,7 +47,9 @@ class _CalculatorHomePageState extends State { final input = _controller.text.trim(); final normalizedInput = input.replaceAll(' ', ''); - if (normalizedInput.toLowerCase().startsWith('y=')) { + + // 使用solver检查是否为可绘制的函数表达式 + if (_solverService.isGraphableExpression(normalizedInput)) { setState(() { _isFunctionMode = true; _result = null; diff --git a/lib/solver.dart b/lib/solver.dart index 199336f..fbc507d 100644 --- a/lib/solver.dart +++ b/lib/solver.dart @@ -1,9 +1,9 @@ -import 'dart:math'; -import 'package:flutter/foundation.dart'; +import 'dart:developer' show log; +import 'dart:math' hide log; import 'package:rational/rational.dart'; +import 'package:simple_math_calc/calculator.dart'; +import 'package:simple_math_calc/parser.dart'; import 'models/calculation_step.dart'; -import 'calculator.dart'; -import 'parser.dart'; /// 帮助解析一元一次方程 ax+b=cx+d 的辅助类 class LinearEquationParts { @@ -512,6 +512,92 @@ ${b1}y &= ${c1 - a1 * x.toDouble()} } } + /// 检查表达式是否可绘制(包含变量x且可以被求值) + bool isGraphableExpression(String expression) { + try { + // 移除空格并转换为小写 + String cleanExpr = expression.replaceAll(' ', '').toLowerCase(); + + // 如果以 y= 开头,去掉前缀 + if (cleanExpr.startsWith('y=')) { + cleanExpr = cleanExpr.substring(2); + } + + // 不能包含等号(方程而不是函数表达式) + if (cleanExpr.contains('=')) { + return false; + } + + // 必须包含变量x + if (!cleanExpr.contains('x')) { + return false; + } + + // 尝试展开表达式(如果包含括号) + String processedExpr = cleanExpr; + if (processedExpr.contains('(')) { + processedExpr = _expandExpressions(processedExpr); + } + + // 尝试解析表达式 + final parser = Parser(processedExpr); + final expr = parser.parse(); + + // 测试在几个点上是否可以求值 + final testPoints = [-1.0, 0.0, 1.0]; + for (final x in testPoints) { + try { + final substituted = expr.substitute('x', DoubleExpr(x)); + final evaluated = substituted.evaluate(); + if (evaluated is DoubleExpr && + evaluated.value.isFinite && + !evaluated.value.isNaN) { + // 至少有一个点可以求值就算成功 + return true; + } + } catch (e) { + // 继续测试其他点 + continue; + } + } + + return false; + } catch (e) { + return false; + } + } + + /// 准备函数表达式用于绘图(展开因式形式) + String prepareFunctionForGraphing(String expression) { + // 移除空格并转换为小写 + String cleanExpr = expression.replaceAll(' ', '').toLowerCase(); + + // 如果以 y= 开头,去掉前缀 + if (cleanExpr.startsWith('y=')) { + cleanExpr = cleanExpr.substring(2); + } + + // 如果表达式包含括号,进行展开 + if (cleanExpr.contains('(')) { + cleanExpr = _expandExpressions(cleanExpr); + } + + // 清理格式:移除不必要的.0后缀和简化格式 + cleanExpr = cleanExpr + .replaceAll('.0', '') // 移除所有.0 + .replaceAll('+0', '') // 移除+0 + .replaceAll('-0', '') // 移除-0 + .replaceAll('1x^2', 'x^2') // 1x^2 -> x^2 + .replaceAll('1x', 'x'); // 1x -> x + + // 移除开头的+号 + if (cleanExpr.startsWith('+')) { + cleanExpr = cleanExpr.substring(1); + } + + return cleanExpr; + } + /// ---- 辅助函数 ---- String _expandExpressions(String input) { @@ -554,26 +640,26 @@ ${b1}y &= ${c1 - a1 * x.toDouble()} if (factorMulMatch != null) { final factor1 = factorMulMatch.group(1)!; final factor2 = factorMulMatch.group(2)!; - debugPrint('Expanding: ($factor1) * ($factor2)'); + log('Expanding: ($factor1) * ($factor2)'); final coeffs1 = _parsePolynomial(factor1); final coeffs2 = _parsePolynomial(factor2); - debugPrint('Coeffs1: $coeffs1, Coeffs2: $coeffs2'); + log('Coeffs1: $coeffs1, Coeffs2: $coeffs2'); final a = coeffs1[1] ?? 0; final b = coeffs1[0] ?? 0; final c = coeffs2[1] ?? 0; final d = coeffs2[0] ?? 0; - debugPrint('a=$a, b=$b, c=$c, d=$d'); + log('a=$a, b=$b, c=$c, d=$d'); final newA = a * c; final newB = a * d + b * c; final newC = b * d; - debugPrint('newA=$newA, newB=$newB, newC=$newC'); + log('newA=$newA, newB=$newB, newC=$newC'); final expanded = '${newA}x^2${newB >= 0 ? '+' : ''}${newB}x${newC >= 0 ? '+' : ''}$newC'; - debugPrint('Expanded result: $expanded'); + log('Expanded result: $expanded'); result = result.replaceFirst(factorMulMatch.group(0)!, expanded); iterationCount++; diff --git a/lib/widgets/graph_card.dart b/lib/widgets/graph_card.dart index cf5a484..5dea0c9 100644 --- a/lib/widgets/graph_card.dart +++ b/lib/widgets/graph_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:simple_math_calc/parser.dart'; import 'package:simple_math_calc/calculator.dart'; +import 'package:simple_math_calc/solver.dart'; import 'dart:math'; class GraphCard extends StatefulWidget { @@ -23,15 +24,15 @@ class GraphCard extends StatefulWidget { } class _GraphCardState extends State { + final SolverService _solverService = SolverService(); + /// 生成函数图表的点 List _generatePlotPoints(String expression, double zoomFactor) { try { - // 只处理 y=... 格式的函数 - String normalized = expression.replaceAll(' ', ''); - if (!normalized.toLowerCase().startsWith('y=')) { - return []; - } - String functionExpr = normalized.substring(2); + // 使用solver准备函数表达式(展开因式形式) + String functionExpr = _solverService.prepareFunctionForGraphing( + expression, + ); // 如果表达式不包含 x,返回空列表 if (!functionExpr.contains('x') && !functionExpr.contains('X')) { diff --git a/test/solver_test.dart b/test/solver_test.dart index 6f66156..5d657b0 100644 --- a/test/solver_test.dart +++ b/test/solver_test.dart @@ -105,5 +105,31 @@ void main() { reason: '应该提供复数根', ); }); + + test('可绘制函数表达式检测', () { + // 测试可绘制的函数表达式 + expect(solver.isGraphableExpression('y=x^2'), true); + expect(solver.isGraphableExpression('x^2+2x+1'), true); + expect(solver.isGraphableExpression('(x-1)(x+3)'), true); + + // 测试不可绘制的表达式 + expect(solver.isGraphableExpression('2+3'), false); + expect(solver.isGraphableExpression('hello'), false); + expect(solver.isGraphableExpression('x^2=4'), false); // 方程而不是函数 + }); + + test('函数表达式预处理', () { + // 测试因式展开 + final expanded = solver.prepareFunctionForGraphing('y=(x-1)(x+3)'); + expect(expanded, 'x^2+2x-3'); + + // 测试已展开的表达式 + final alreadyExpanded = solver.prepareFunctionForGraphing('x^2+2x+1'); + expect(alreadyExpanded, 'x^2+2x+1'); + + // 测试无y=前缀的表达式 + final noPrefix = solver.prepareFunctionForGraphing('(x-1)(x+3)'); + expect(noPrefix, 'x^2+2x-3'); + }); }); }