| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- // tools/hypergeometric_distribution.dart
- import 'package:flutter/material.dart';
- import 'package:rational/rational.dart';
- import 'package:fl_chart/fl_chart.dart';
- import '../utils/math_utils.dart';
- class HypergeometricDistributionPage extends StatefulWidget {
- @override
- _HypergeometricDistributionPageState createState() => _HypergeometricDistributionPageState();
- }
- class _HypergeometricDistributionPageState extends State<HypergeometricDistributionPage> {
- final TextEditingController nController = TextEditingController(); // 抽取数
- final TextEditingController NController = TextEditingController(); // 总数
- final TextEditingController MController = TextEditingController(); // 满足条件数
- String expectation = '';
- String variance = '';
- List<String> probabilities = [];
- List<BarChartGroupData> barGroups = [];
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: Text('超几何分布')),
- body: SingleChildScrollView( // 添加滚动支持
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- children: [
- _buildInputField('总数 N', NController),
- _buildInputField('满足条件数 M', MController),
- _buildInputField('抽取数 n', nController),
- SizedBox(height: 20),
- ElevatedButton(
- onPressed: _calculate,
- child: Text('计算'),
- ),
- SizedBox(height: 20),
- if (expectation.isNotEmpty) ...[
- _buildResultCard('期望', expectation),
- _buildResultCard('方差', variance),
- SizedBox(height: 10),
- Text('概率分布:', style: TextStyle(fontWeight: FontWeight.bold)),
- if (barGroups.isNotEmpty) ...[
- SizedBox(height: 10),
- Container(
- height: 300,
- child: BarChart(
- BarChartData(
- barTouchData: BarTouchData(
- enabled: false,
- ),
- titlesData: FlTitlesData(
- show: true,
- bottomTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- getTitlesWidget: (value, meta) {
- return Padding(
- padding: const EdgeInsets.only(top: 8.0),
- child: Text(
- value.toInt().toString(),
- style: TextStyle(fontSize: 10),
- ),
- );
- },
- reservedSize: 30,
- ),
- ),
- leftTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- getTitlesWidget: (value, meta) {
- if (value % 0.1 == 0) {
- return Text(
- value.toStringAsFixed(1),
- style: TextStyle(fontSize: 10),
- );
- }
- return Text('');
- },
- reservedSize: 40,
- ),
- ),
- topTitles: AxisTitles(
- sideTitles: SideTitles(showTitles: false),
- ),
- rightTitles: AxisTitles(
- sideTitles: SideTitles(showTitles: false),
- ),
- ),
- borderData: FlBorderData(show: true),
- barGroups: barGroups,
- gridData: FlGridData(show: true),
- alignment: BarChartAlignment.spaceAround,
- maxY: _getMaxY(),
- ),
- ),
- ),
- SizedBox(height: 10),
- ],
- // 移除 Expanded 和 ListView.builder,改用简单的 Column
- Column(
- children: probabilities.map((prob) =>
- Card(
- child: ListTile(
- title: Text(prob),
- ),
- )
- ).toList(),
- ),
- ],
- ],
- ),
- ),
- ),
- );
- }
- double _getMaxY() {
- if (barGroups.isEmpty) return 1.0;
- double maxY = 0.0;
- for (var group in barGroups) {
- for (var rod in group.barRods) {
- if (rod.toY > maxY) maxY = rod.toY;
- }
- }
- return maxY * 1.1;
- }
- Widget _buildInputField(String label, TextEditingController controller) {
- return Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: TextField(
- controller: controller,
- keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
- decoration: InputDecoration(
- labelText: label,
- border: OutlineInputBorder(),
- ),
- ),
- );
- }
- Widget _buildResultCard(String title, String value) {
- return Card(
- child: ListTile(
- title: Text(title),
- subtitle: Text(value),
- ),
- );
- }
- Rational _combination(int n, int k) {
- if (k < 0 || k > n) return Rational.zero;
- if (k == 0 || k == n) return Rational.one;
- BigInt result = BigInt.one;
- for (int i = 1; i <= k; i++) {
- result = result * BigInt.from(n - i + 1) ~/ BigInt.from(i);
- }
- return Rational(result);
- }
- void _calculate() {
- try {
- final N = int.parse(NController.text);
- final M = int.parse(MController.text);
- final n = int.parse(nController.text);
- final m = n - (N - M) > 0 ? n - (N - M) : 0;
- final r = n < M ? n : M;
- final EX = MathUtils.fromInt(n * M) / MathUtils.fromInt(N);
- final DX = MathUtils.fromInt(n * M * (N - M) * (N - n)) /
- (MathUtils.fromInt(N) * MathUtils.fromInt(N) * MathUtils.fromInt(N - 1));
- final probs = <String>[];
- final barGroupsData = <BarChartGroupData>[];
- // 首先计算所有概率并找到最大值
- List<double> probValues = [];
- double maxProb = 0.0;
-
- for (int k = m; k <= r; k++) {
- final combMk = _combination(M, k);
- final combNM_nk = _combination(N - M, n - k);
- final combNn = _combination(N, n);
- final prob = combMk * combNM_nk / combNn;
- final probDouble = prob.toDouble();
-
- probValues.add(probDouble);
- if (probDouble > maxProb) {
- maxProb = probDouble;
- }
-
- probs.add('P(X = $k) = ${MathUtils.rationalToString(prob)}');
- }
- // 找到所有具有最大概率的k值
- List<int> maxProbIndices = [];
- for (int i = 0; i < probValues.length; i++) {
- if (probValues[i] == maxProb) {
- maxProbIndices.add(m + i);
- }
- }
- // 确定最高组的范围
- int minMaxIndex = maxProbIndices.reduce((a, b) => a < b ? a : b);
- int maxMaxIndex = maxProbIndices.reduce((a, b) => a > b ? a : b);
- // 构建柱状图数据
- for (int i = 0; i <= r - m; i++) {
- int k = m + i;
- double probDouble = probValues[i];
-
- barGroupsData.add(
- BarChartGroupData(
- x: k,
- barRods: [
- BarChartRodData(
- toY: probDouble,
- color: _getBarColor(k, minMaxIndex, maxMaxIndex),
- width: 16,
- borderRadius: BorderRadius.circular(4),
- ),
- ],
- ),
- );
- }
- setState(() {
- expectation = 'EX = ${MathUtils.rationalToString(EX)}';
- variance = 'DX = ${MathUtils.rationalToString(DX)}';
- probabilities = probs;
- barGroups = barGroupsData;
- });
- } catch (e) {
- setState(() {
- expectation = '错误: $e';
- variance = '';
- probabilities = [];
- barGroups = [];
- });
- }
- }
- Color _getBarColor(int x, int minMaxIndex, int maxMaxIndex) {
- if (x >= minMaxIndex && x <= maxMaxIndex) {
- // 最高组(可能有多组)
- return Colors.red;
- } else if (x < minMaxIndex) {
- // 最高组左边的组
- return Colors.blue.shade300;
- } else {
- // 最高组右边的组
- return Colors.green.shade300;
- }
- }
- @override
- void dispose() {
- nController.dispose();
- NController.dispose();
- MController.dispose();
- super.dispose();
- }
- }
|