// tools/convolution_calculator.dart import 'package:flutter/material.dart'; import 'package:rational/rational.dart'; class ConvolutionCalculatorPage extends StatefulWidget { @override _ConvolutionCalculatorPageState createState() => _ConvolutionCalculatorPageState(); } class _ConvolutionCalculatorPageState extends State { final List _xValueControllers = []; final List _xProbControllers = []; final List _yValueControllers = []; final List _yProbControllers = []; List> _convolutionResult = []; bool _showResult = false; @override void initState() { super.initState(); // 初始化默认数据 _initializeDefaultData(); } void _initializeDefaultData() { // 添加X的默认数据 _addXRow(value: '0', prob: '1/2'); _addXRow(value: '1', prob: '1/2'); // 添加Y的默认数据 _addYRow(value: '0', prob: '1/3'); _addYRow(value: '1', prob: '1/3'); _addYRow(value: '2', prob: '1/3'); } void _addXRow({String value = '', String prob = ''}) { setState(() { _xValueControllers.add(TextEditingController(text: value)); _xProbControllers.add(TextEditingController(text: prob)); }); } void _removeXRow(int index) { setState(() { _xValueControllers.removeAt(index); _xProbControllers.removeAt(index); }); } void _addYRow({String value = '', String prob = ''}) { setState(() { _yValueControllers.add(TextEditingController(text: value)); _yProbControllers.add(TextEditingController(text: prob)); }); } void _removeYRow(int index) { setState(() { _yValueControllers.removeAt(index); _yProbControllers.removeAt(index); }); } Rational _parseRational(String input) { try { if (input.contains('/')) { List parts = input.split('/'); if (parts.length == 2) { return Rational.parse(parts[0].trim()) / Rational.parse(parts[1].trim()); } } return Rational.parse(input.trim()); } catch (e) { throw FormatException('无效的概率格式: $input'); } } void _calculateConvolution() { try { // 解析X分布 Map xDistribution = {}; Rational xSum = Rational.zero; for (int i = 0; i < _xValueControllers.length; i++) { Rational value = _parseRational(_xValueControllers[i].text); Rational prob = _parseRational(_xProbControllers[i].text); xDistribution[value] = prob; xSum += prob; } // 解析Y分布 Map yDistribution = {}; Rational ySum = Rational.zero; for (int i = 0; i < _yValueControllers.length; i++) { Rational value = _parseRational(_yValueControllers[i].text); Rational prob = _parseRational(_yProbControllers[i].text); yDistribution[value] = prob; ySum += prob; } // 验证概率和是否为1 if (xSum != Rational.one) { throw FormatException('X的概率和必须为1,当前为: $xSum'); } if (ySum != Rational.one) { throw FormatException('Y的概率和必须为1,当前为: $ySum'); } // 计算卷积 Map convolution = {}; for (var xEntry in xDistribution.entries) { for (var yEntry in yDistribution.entries) { Rational sumValue = xEntry.key + yEntry.key; Rational prob = xEntry.value * yEntry.value; convolution[sumValue] = (convolution[sumValue] ?? Rational.zero) + prob; } } // 转换为列表并排序 _convolutionResult = convolution.entries .map((entry) => {'value': entry.key, 'probability': entry.value}) .toList(); _convolutionResult.sort((a, b) => a['value']!.compareTo(b['value']!)); setState(() { _showResult = true; }); } catch (e) { _showErrorDialog(e.toString()); } } void _showErrorDialog(String message) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('计算错误'), content: Text(message), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text('确定'), ), ], ), ); } void _clearAll() { setState(() { _xValueControllers.clear(); _xProbControllers.clear(); _yValueControllers.clear(); _yProbControllers.clear(); _convolutionResult.clear(); _showResult = false; _initializeDefaultData(); }); } @override void dispose() { for (var controller in _xValueControllers) { controller.dispose(); } for (var controller in _xProbControllers) { controller.dispose(); } for (var controller in _yValueControllers) { controller.dispose(); } for (var controller in _yProbControllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('卷积分布列计算'), backgroundColor: Colors.white, elevation: 1, actions: [ IconButton( icon: Icon(Icons.clear_all), onPressed: _clearAll, tooltip: '清空所有', ), ], ), body: SingleChildScrollView( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDistributionInput('随机变量 X 的分布列', _xValueControllers, _xProbControllers, _addXRow, _removeXRow), SizedBox(height: 24), _buildDistributionInput('随机变量 Y 的分布列', _yValueControllers, _yProbControllers, _addYRow, _removeYRow), SizedBox(height: 24), Center( child: ElevatedButton.icon( onPressed: _calculateConvolution, icon: Icon(Icons.calculate), label: Text('计算卷积分布列'), style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), ), ), SizedBox(height: 24), if (_showResult) _buildResultTable(), ], ), ), ); } Widget _buildDistributionInput( String title, List valueControllers, List probControllers, VoidCallback onAdd, Function(int) onRemove, ) { return Card( elevation: 2, child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 16), _buildTableHeader(), SizedBox(height: 8), ...List.generate(valueControllers.length, (index) { return _buildInputRow( valueControllers[index], probControllers[index], index, onRemove, valueControllers.length, ); }), SizedBox(height: 16), Center( child: OutlinedButton.icon( onPressed: onAdd, icon: Icon(Icons.add, size: 18), label: Text('添加一行'), ), ), ], ), ), ); } Widget _buildTableHeader() { return Row( children: [ Expanded( flex: 2, child: Text('取值', style: TextStyle(fontWeight: FontWeight.bold)), ), SizedBox(width: 16), Expanded( flex: 3, child: Text('概率 (支持分数如 1/2)', style: TextStyle(fontWeight: FontWeight.bold)), ), SizedBox(width: 16), Expanded( flex: 1, child: Text('操作', style: TextStyle(fontWeight: FontWeight.bold)), ), ], ); } Widget _buildInputRow( TextEditingController valueController, TextEditingController probController, int index, Function(int) onRemove, int totalRows, ) { return Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Expanded( flex: 2, child: TextField( controller: valueController, decoration: InputDecoration( hintText: '如: 0, 1, 2...', border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true), ), ), SizedBox(width: 16), Expanded( flex: 3, child: TextField( controller: probController, decoration: InputDecoration( hintText: '如: 1/2, 0.25, 0.3', border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), ), SizedBox(width: 16), Expanded( flex: 1, child: IconButton( icon: Icon(Icons.remove_circle, color: Colors.red), onPressed: totalRows > 1 ? () => onRemove(index) : null, tooltip: '删除此行', ), ), ], ), ); } Widget _buildResultTable() { return Card( elevation: 2, color: Colors.green[50], child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '卷积分布列 X + Y', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.green[800], ), ), SizedBox(height: 16), _buildResultTableHeader(), ..._convolutionResult.map(_buildResultRow).toList(), SizedBox(height: 16), _buildProbabilitySum(), ], ), ), ); } Widget _buildResultTableHeader() { return Container( padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: Colors.green[100], borderRadius: BorderRadius.vertical(top: Radius.circular(8)), ), child: Row( children: [ Expanded( child: Text('X + Y 取值', style: TextStyle(fontWeight: FontWeight.bold)), ), Expanded( child: Text('概率 (分数)', style: TextStyle(fontWeight: FontWeight.bold)), ), Expanded( child: Text('概率 (小数)', style: TextStyle(fontWeight: FontWeight.bold)), ), ], ), ); } Widget _buildResultRow(Map entry) { Rational value = entry['value']!; Rational probability = entry['probability']!; double decimalProb = probability.toDouble(); return Container( padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.green[100]!)), ), child: Row( children: [ Expanded( child: Text( value.toString(), style: TextStyle(fontSize: 16), ), ), Expanded( child: Text( _toFractionString(probability), style: TextStyle(fontSize: 16, fontFamily: 'Monospace'), ), ), Expanded( child: Text( decimalProb.toStringAsFixed(6), style: TextStyle(fontSize: 16, fontFamily: 'Monospace'), ), ), ], ), ); } String _toFractionString(Rational rational) { if (rational.denominator == BigInt.one) { return rational.numerator.toString(); } return '${rational.numerator}/${rational.denominator}'; } Widget _buildProbabilitySum() { Rational total = _convolutionResult.fold( Rational.zero, (sum, entry) => sum + entry['probability']!, ); return Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.green[100], borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('概率总和:', style: TextStyle(fontWeight: FontWeight.bold)), Text( '${_toFractionString(total)} = ${total.toDouble().toStringAsFixed(6)}', style: TextStyle(fontWeight: FontWeight.bold, fontFamily: 'Monospace'), ), ], ), ); } }