hypergeometric_distribution.dart 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // tools/hypergeometric_distribution.dart
  2. import 'package:flutter/material.dart';
  3. import 'package:rational/rational.dart';
  4. import 'package:fl_chart/fl_chart.dart';
  5. import '../utils/math_utils.dart';
  6. class HypergeometricDistributionPage extends StatefulWidget {
  7. @override
  8. _HypergeometricDistributionPageState createState() => _HypergeometricDistributionPageState();
  9. }
  10. class _HypergeometricDistributionPageState extends State<HypergeometricDistributionPage> {
  11. final TextEditingController nController = TextEditingController(); // 抽取数
  12. final TextEditingController NController = TextEditingController(); // 总数
  13. final TextEditingController MController = TextEditingController(); // 满足条件数
  14. String expectation = '';
  15. String variance = '';
  16. List<String> probabilities = [];
  17. List<BarChartGroupData> barGroups = [];
  18. @override
  19. Widget build(BuildContext context) {
  20. return Scaffold(
  21. appBar: AppBar(title: Text('超几何分布')),
  22. body: SingleChildScrollView( // 添加滚动支持
  23. child: Padding(
  24. padding: EdgeInsets.all(16),
  25. child: Column(
  26. children: [
  27. _buildInputField('总数 N', NController),
  28. _buildInputField('满足条件数 M', MController),
  29. _buildInputField('抽取数 n', nController),
  30. SizedBox(height: 20),
  31. ElevatedButton(
  32. onPressed: _calculate,
  33. child: Text('计算'),
  34. ),
  35. SizedBox(height: 20),
  36. if (expectation.isNotEmpty) ...[
  37. _buildResultCard('期望', expectation),
  38. _buildResultCard('方差', variance),
  39. SizedBox(height: 10),
  40. Text('概率分布:', style: TextStyle(fontWeight: FontWeight.bold)),
  41. if (barGroups.isNotEmpty) ...[
  42. SizedBox(height: 10),
  43. Container(
  44. height: 300,
  45. child: BarChart(
  46. BarChartData(
  47. barTouchData: BarTouchData(
  48. enabled: false,
  49. ),
  50. titlesData: FlTitlesData(
  51. show: true,
  52. bottomTitles: AxisTitles(
  53. sideTitles: SideTitles(
  54. showTitles: true,
  55. getTitlesWidget: (value, meta) {
  56. return Padding(
  57. padding: const EdgeInsets.only(top: 8.0),
  58. child: Text(
  59. value.toInt().toString(),
  60. style: TextStyle(fontSize: 10),
  61. ),
  62. );
  63. },
  64. reservedSize: 30,
  65. ),
  66. ),
  67. leftTitles: AxisTitles(
  68. sideTitles: SideTitles(
  69. showTitles: true,
  70. getTitlesWidget: (value, meta) {
  71. if (value % 0.1 == 0) {
  72. return Text(
  73. value.toStringAsFixed(1),
  74. style: TextStyle(fontSize: 10),
  75. );
  76. }
  77. return Text('');
  78. },
  79. reservedSize: 40,
  80. ),
  81. ),
  82. topTitles: AxisTitles(
  83. sideTitles: SideTitles(showTitles: false),
  84. ),
  85. rightTitles: AxisTitles(
  86. sideTitles: SideTitles(showTitles: false),
  87. ),
  88. ),
  89. borderData: FlBorderData(show: true),
  90. barGroups: barGroups,
  91. gridData: FlGridData(show: true),
  92. alignment: BarChartAlignment.spaceAround,
  93. maxY: _getMaxY(),
  94. ),
  95. ),
  96. ),
  97. SizedBox(height: 10),
  98. ],
  99. // 移除 Expanded 和 ListView.builder,改用简单的 Column
  100. Column(
  101. children: probabilities.map((prob) =>
  102. Card(
  103. child: ListTile(
  104. title: Text(prob),
  105. ),
  106. )
  107. ).toList(),
  108. ),
  109. ],
  110. ],
  111. ),
  112. ),
  113. ),
  114. );
  115. }
  116. double _getMaxY() {
  117. if (barGroups.isEmpty) return 1.0;
  118. double maxY = 0.0;
  119. for (var group in barGroups) {
  120. for (var rod in group.barRods) {
  121. if (rod.toY > maxY) maxY = rod.toY;
  122. }
  123. }
  124. return maxY * 1.1;
  125. }
  126. Widget _buildInputField(String label, TextEditingController controller) {
  127. return Padding(
  128. padding: EdgeInsets.symmetric(vertical: 8),
  129. child: TextField(
  130. controller: controller,
  131. keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
  132. decoration: InputDecoration(
  133. labelText: label,
  134. border: OutlineInputBorder(),
  135. ),
  136. ),
  137. );
  138. }
  139. Widget _buildResultCard(String title, String value) {
  140. return Card(
  141. child: ListTile(
  142. title: Text(title),
  143. subtitle: Text(value),
  144. ),
  145. );
  146. }
  147. Rational _combination(int n, int k) {
  148. if (k < 0 || k > n) return Rational.zero;
  149. if (k == 0 || k == n) return Rational.one;
  150. BigInt result = BigInt.one;
  151. for (int i = 1; i <= k; i++) {
  152. result = result * BigInt.from(n - i + 1) ~/ BigInt.from(i);
  153. }
  154. return Rational(result);
  155. }
  156. void _calculate() {
  157. try {
  158. final N = int.parse(NController.text);
  159. final M = int.parse(MController.text);
  160. final n = int.parse(nController.text);
  161. final m = n - (N - M) > 0 ? n - (N - M) : 0;
  162. final r = n < M ? n : M;
  163. final EX = MathUtils.fromInt(n * M) / MathUtils.fromInt(N);
  164. final DX = MathUtils.fromInt(n * M * (N - M) * (N - n)) /
  165. (MathUtils.fromInt(N) * MathUtils.fromInt(N) * MathUtils.fromInt(N - 1));
  166. final probs = <String>[];
  167. final barGroupsData = <BarChartGroupData>[];
  168. // 首先计算所有概率并找到最大值
  169. List<double> probValues = [];
  170. double maxProb = 0.0;
  171. for (int k = m; k <= r; k++) {
  172. final combMk = _combination(M, k);
  173. final combNM_nk = _combination(N - M, n - k);
  174. final combNn = _combination(N, n);
  175. final prob = combMk * combNM_nk / combNn;
  176. final probDouble = prob.toDouble();
  177. probValues.add(probDouble);
  178. if (probDouble > maxProb) {
  179. maxProb = probDouble;
  180. }
  181. probs.add('P(X = $k) = ${MathUtils.rationalToString(prob)}');
  182. }
  183. // 找到所有具有最大概率的k值
  184. List<int> maxProbIndices = [];
  185. for (int i = 0; i < probValues.length; i++) {
  186. if (probValues[i] == maxProb) {
  187. maxProbIndices.add(m + i);
  188. }
  189. }
  190. // 确定最高组的范围
  191. int minMaxIndex = maxProbIndices.reduce((a, b) => a < b ? a : b);
  192. int maxMaxIndex = maxProbIndices.reduce((a, b) => a > b ? a : b);
  193. // 构建柱状图数据
  194. for (int i = 0; i <= r - m; i++) {
  195. int k = m + i;
  196. double probDouble = probValues[i];
  197. barGroupsData.add(
  198. BarChartGroupData(
  199. x: k,
  200. barRods: [
  201. BarChartRodData(
  202. toY: probDouble,
  203. color: _getBarColor(k, minMaxIndex, maxMaxIndex),
  204. width: 16,
  205. borderRadius: BorderRadius.circular(4),
  206. ),
  207. ],
  208. ),
  209. );
  210. }
  211. setState(() {
  212. expectation = 'EX = ${MathUtils.rationalToString(EX)}';
  213. variance = 'DX = ${MathUtils.rationalToString(DX)}';
  214. probabilities = probs;
  215. barGroups = barGroupsData;
  216. });
  217. } catch (e) {
  218. setState(() {
  219. expectation = '错误: $e';
  220. variance = '';
  221. probabilities = [];
  222. barGroups = [];
  223. });
  224. }
  225. }
  226. Color _getBarColor(int x, int minMaxIndex, int maxMaxIndex) {
  227. if (x >= minMaxIndex && x <= maxMaxIndex) {
  228. // 最高组(可能有多组)
  229. return Colors.red;
  230. } else if (x < minMaxIndex) {
  231. // 最高组左边的组
  232. return Colors.blue.shade300;
  233. } else {
  234. // 最高组右边的组
  235. return Colors.green.shade300;
  236. }
  237. }
  238. @override
  239. void dispose() {
  240. nController.dispose();
  241. NController.dispose();
  242. MController.dispose();
  243. super.dispose();
  244. }
  245. }