├── .github ├── FUNDING.yml └── workflows │ └── dart.yml ├── analysis_options.yaml ├── .gitignore ├── run_coverage.sh ├── pubspec.yaml ├── dart_test.yaml ├── statistics.iml ├── lib ├── statistics.dart └── src │ ├── statistics_equality_simd.dart │ ├── statistics_equality.dart │ ├── statistics_benchmark.dart │ ├── statistics_extension_simd.dart │ ├── statistics_prime.dart │ ├── statistics_combination.dart │ ├── statistics_tools_csv.dart │ ├── statistics_metric.dart │ ├── statistics_tools.dart │ └── statistics_forecast.dart ├── example ├── statistics_example.dart ├── dynamic_int_benchmark_example.dart ├── bayesnet_example.dart └── bayesnet_dependency_example.dart ├── test ├── statistics_equality_test.dart ├── statistics_benchmark_test.dart ├── statistics_tools_csv_test.dart ├── statistics_extension_simd_test.dart ├── statistics_forecast_even_xor_test.dart ├── statistics_metric_test.dart ├── statistics_forecast_even_product_test.dart ├── statistics_prime_test.dart ├── statistics_combination_test.dart └── statistics_tools_test.dart ├── CHANGELOG.md ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [gmpassos] 2 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | 2 | include: package:lints/recommended.yaml 3 | 4 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 5 | 6 | # Uncomment to specify additional rules. 7 | # linter: 8 | # rules: 9 | # - camel_case_types 10 | 11 | # analyzer: 12 | # exclude: 13 | # - path/to/excluded/files/** 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # test_cov files: 9 | coverage/ 10 | test/.test_cov.dart 11 | 12 | # Omit committing pubspec.lock for library packages; see 13 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 14 | pubspec.lock 15 | 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /run_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf ./coverage/test 4 | rm -rf ./coverage/report 5 | rm ./coverage/lcov.info 6 | 7 | dart run test --coverage=./coverage 8 | 9 | dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage 10 | 11 | genhtml -o ./coverage/report ./coverage/lcov.info 12 | open ./coverage/report/index.html 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: statistics 2 | description: Statistics package for easy and efficient data manipulation with built-in Bayesian Network (Bayes Net), many mathematical functions and tools. 3 | version: 1.2.0 4 | homepage: https://github.com/gmpassos/statistics 5 | 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | 9 | dependencies: 10 | intl: ^0.20.2 11 | collection: ^1.19.0 12 | data_serializer: ^1.2.1 13 | 14 | dev_dependencies: 15 | lints: ^5.1.1 16 | test: ^1.25.15 17 | dependency_validator: ^3.2.3 18 | coverage: ^1.11.1 19 | 20 | #dependency_overrides: 21 | # data_serializer: 22 | # path: ../data_serializer 23 | 24 | -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | 2 | tags: 3 | # Number tests. 4 | num: 5 | timeout: 30s 6 | # Decimal tests. 7 | decimal: 8 | timeout: 30s 9 | # Specific browser tests. 10 | browser: 11 | timeout: 30s 12 | # Platform tests. 13 | platform: 14 | timeout: 30s 15 | 16 | timeout: 3x 17 | 18 | concurrency: 1 19 | 20 | override_platforms: 21 | chrome: 22 | settings: 23 | headless: true 24 | firefox: 25 | settings: 26 | arguments: -headless 27 | 28 | define_platforms: 29 | firefox-esr: 30 | name: Firefox-ESR 31 | extends: firefox 32 | settings: 33 | executable: 34 | linux: firefox-esr 35 | 36 | 37 | -------------------------------------------------------------------------------- /statistics.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/statistics.dart: -------------------------------------------------------------------------------- 1 | /// Statistics library. 2 | library; 3 | 4 | export 'package:data_serializer/data_serializer.dart'; 5 | 6 | export 'src/statistics_base.dart'; 7 | export 'src/statistics_bayesnet.dart'; 8 | export 'src/statistics_benchmark.dart'; 9 | export 'src/statistics_combination.dart'; 10 | export 'src/statistics_decimal.dart'; 11 | export 'src/statistics_dynamic_int.dart'; 12 | export 'src/statistics_equality.dart'; 13 | export 'src/statistics_equality_simd.dart'; 14 | export 'src/statistics_extension.dart'; 15 | export 'src/statistics_extension_num.dart'; 16 | export 'src/statistics_extension_simd.dart'; 17 | export 'src/statistics_forecast.dart'; 18 | export 'src/statistics_metric.dart'; 19 | export 'src/statistics_prime.dart'; 20 | export 'src/statistics_tools.dart'; 21 | export 'src/statistics_tools_csv.dart'; 22 | -------------------------------------------------------------------------------- /lib/src/statistics_equality_simd.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:collection/collection.dart'; 4 | 5 | import 'statistics_extension_simd.dart'; 6 | 7 | /// [Equality] for [Int32x4]. 8 | class Int32x4Equality implements Equality { 9 | @override 10 | bool equals(Int32x4 e1, Int32x4 e2) => e1.equalsValues(e2); 11 | 12 | @override 13 | int hash(Int32x4 e) => 14 | e.x.hashCode ^ e.y.hashCode ^ e.z.hashCode ^ e.w.hashCode; 15 | 16 | @override 17 | bool isValidKey(Object? o) { 18 | return o is Int32x4; 19 | } 20 | } 21 | 22 | /// [Equality] for [Float32x4]. 23 | class Float32x4Equality implements Equality { 24 | @override 25 | bool equals(Float32x4 e1, Float32x4 e2) => e1.equalsValues(e2); 26 | 27 | @override 28 | int hash(Float32x4 e) => 29 | e.x.hashCode ^ e.y.hashCode ^ e.z.hashCode ^ e.w.hashCode; 30 | 31 | @override 32 | bool isValidKey(Object? o) { 33 | return o is Float32x4; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/statistics_equality.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | /// [Equality] for 'double'. 4 | class DoubleEquality implements Equality { 5 | @override 6 | bool equals(double e1, double e2) => e1 == e2; 7 | 8 | @override 9 | int hash(double e) => e.hashCode; 10 | 11 | @override 12 | bool isValidKey(Object? o) { 13 | return o is double; 14 | } 15 | } 16 | 17 | /// [Equality] for 'int'. 18 | class IntEquality implements Equality { 19 | @override 20 | bool equals(int e1, int e2) => e1 == e2; 21 | 22 | @override 23 | int hash(int e) => e.hashCode; 24 | 25 | @override 26 | bool isValidKey(Object? o) { 27 | return o is int; 28 | } 29 | } 30 | 31 | /// [Equality] for 'num'. 32 | class NumEquality implements Equality { 33 | @override 34 | bool equals(num e1, num e2) => e1 == e2; 35 | 36 | @override 37 | int hash(num e) => e.hashCode; 38 | 39 | @override 40 | bool isValidKey(Object? o) { 41 | return o is num; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/statistics_benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'statistics_tools.dart'; 2 | 3 | typedef BenchMarkFunction = BenchmarkFunctionResult Function(int loops); 4 | 5 | /// Performas a benchmark of a [set] of [BenchMarkFunction]s. 6 | List> benchmarkSet( 7 | int loops, Map> set) { 8 | var results = set.entries.map((e) { 9 | var result = benchmark(e.key, loops, e.value); 10 | return result; 11 | }).toList(); 12 | results.sort(); 13 | return results; 14 | } 15 | 16 | /// Performas a benchmark of [BenchMarkFunction] [f]. 17 | BenchmarkResult benchmark(String name, int loops, BenchMarkFunction f, 18 | {bool verbose = false, Function(Object? o)? printer}) { 19 | var chronometer = BenchmarkResult(name)..start(); 20 | var result = f(loops); 21 | chronometer.stop(operations: result.operations); 22 | chronometer._result = result.result; 23 | 24 | if (verbose) { 25 | if (printer != null) { 26 | printer(chronometer); 27 | } else { 28 | print(chronometer); 29 | } 30 | } 31 | 32 | return chronometer; 33 | } 34 | 35 | class BenchmarkResult extends Chronometer { 36 | late R _result; 37 | 38 | BenchmarkResult(super.name); 39 | 40 | R get result => _result; 41 | 42 | Duration get duration => elapsedTime; 43 | 44 | @override 45 | String toString({bool withStartTime = true}) { 46 | var s = super.toString(withStartTime: withStartTime); 47 | return '$s -> $result'; 48 | } 49 | } 50 | 51 | class BenchmarkFunctionResult { 52 | final int operations; 53 | final R result; 54 | 55 | BenchmarkFunctionResult(this.operations, this.result); 56 | } 57 | -------------------------------------------------------------------------------- /example/statistics_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | 3 | void main() { 4 | var ns = [10, 20.0, 25, 30]; 5 | print('ns: $ns'); 6 | 7 | // Numeric extension: 8 | 9 | var mean = ns.mean; 10 | print('mean: $mean'); 11 | 12 | var sdv = ns.standardDeviation; 13 | print('sdv: $sdv'); 14 | 15 | var squares = ns.square; 16 | print('squares: $squares'); 17 | 18 | // Statistics: 19 | 20 | var statistics = ns.statistics; 21 | 22 | print('Statistics.max: ${statistics.max}'); 23 | print('Statistics.min: ${statistics.min}'); 24 | print('Statistics.mean: ${statistics.mean}'); 25 | print('Statistics.standardDeviation: ${statistics.standardDeviation}'); 26 | print('Statistics.sum: ${statistics.sum}'); 27 | print('Statistics.center: ${statistics.center}'); 28 | print( 29 | 'Statistics.median: ${statistics.median} -> ${statistics.medianLow} , ${statistics.medianHigh}'); 30 | print('Statistics.squaresSum: ${statistics.squaresSum}'); 31 | 32 | print('Statistics: $statistics'); 33 | 34 | // CSV: 35 | 36 | var categories = >{ 37 | 'a': [10.0, 20.0, null], 38 | 'b': [100.0, 200.0, 300.0] 39 | }; 40 | 41 | var csv = categories.generateCSV(); 42 | print('---'); 43 | print('CSV:'); 44 | print(csv); 45 | } 46 | 47 | // --------------------------------------------- 48 | // OUTPUT: 49 | // --------------------------------------------- 50 | // ns: [10, 20.0, 25, 30] 51 | // mean: 21.25 52 | // sdv: 6.931585316505886 53 | // squares: [100, 400.0, 625, 900] 54 | // Statistics.max: 30 55 | // Statistics.min: 10 56 | // Statistics.mean: 21.25 57 | // Statistics.standardDeviation: 7.39509972887452 58 | // Statistics.sum: 85.0 59 | // Statistics.center: 25 60 | // Statistics.median: 22.5 -> 20.0 , 25 61 | // Statistics.squaresSum: 2025.0 62 | // Statistics: {~21.25 +-7.3950 [10..(25)..30] #4} 63 | // --- 64 | // CSV: 65 | // #,a,b 66 | // 1,10,100 67 | // 2,20,200 68 | // 3,0,300 69 | // 70 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: dart-lang/setup-dart@v1 15 | - name: Dart version 16 | run: | 17 | dart --version 18 | uname -a 19 | - name: Install dependencies 20 | run: dart pub get 21 | - name: Upgrade dependencies 22 | run: dart pub upgrade 23 | - name: dart format 24 | run: dart format -o none --set-exit-if-changed . 25 | - name: dart analyze 26 | run: dart analyze --fatal-infos --fatal-warnings . 27 | - name: dependency_validator 28 | run: dart run dependency_validator 29 | - name: dart doc 30 | run: dart doc --dry-run 31 | - name: dart pub publish --dry-run 32 | run: dart pub publish --dry-run 33 | 34 | test_vm: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v3 38 | - uses: dart-lang/setup-dart@v1 39 | - name: Dart version 40 | run: | 41 | dart --version 42 | uname -a 43 | - name: Install dependencies 44 | run: dart pub get 45 | - name: Upgrade dependencies 46 | run: dart pub upgrade 47 | - name: Run tests (VM) 48 | run: dart test --platform vm --coverage=./coverage 49 | - name: Generate coverage report 50 | run: | 51 | dart pub global activate coverage 52 | dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage 53 | - name: Upload coverage to Codecov 54 | uses: codecov/codecov-action@v3 55 | env: 56 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 57 | with: 58 | directory: ./coverage/ 59 | flags: unittests 60 | env_vars: OS,DART 61 | fail_ci_if_error: true 62 | verbose: true 63 | 64 | 65 | test_chrome: 66 | runs-on: ubuntu-latest 67 | steps: 68 | - uses: actions/checkout@v3 69 | - uses: dart-lang/setup-dart@v1 70 | - name: Dart version 71 | run: | 72 | dart --version 73 | uname -a 74 | - name: Install dependencies 75 | run: dart pub get 76 | - name: Upgrade dependencies 77 | run: dart pub upgrade 78 | - name: Run tests (Chrome) 79 | run: dart test --platform chrome 80 | -------------------------------------------------------------------------------- /test/statistics_equality_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:statistics/statistics.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('Equality', () { 8 | setUp(() {}); 9 | 10 | test('IntEquality', () { 11 | var eq = IntEquality(); 12 | 13 | expect(eq.equals(10, 10), isTrue); 14 | expect(eq.equals(10, -10), isFalse); 15 | 16 | expect(eq.hash(10) == eq.hash(10), isTrue); 17 | expect(eq.hash(10) == eq.hash(11), isFalse); 18 | 19 | expect(eq.isValidKey(10), isTrue); 20 | expect(eq.isValidKey(10.1), isFalse); 21 | }); 22 | 23 | test('DoubleEquality', () { 24 | var eq = DoubleEquality(); 25 | 26 | expect(eq.equals(10.0, 10.0), isTrue); 27 | expect(eq.equals(10.0, -10.0), isFalse); 28 | expect(eq.equals(10.0, 10.00001), isFalse); 29 | 30 | expect(eq.hash(10) == eq.hash(10), isTrue); 31 | expect(eq.hash(10) == eq.hash(11), isFalse); 32 | 33 | expect(eq.isValidKey(10.0), isTrue); 34 | expect(eq.isValidKey('x'), isFalse); 35 | }); 36 | 37 | test('NumEquality', () { 38 | var eq = NumEquality(); 39 | 40 | expect(eq.equals(10, 10), isTrue); 41 | expect(eq.equals(10, -10), isFalse); 42 | 43 | expect(eq.hash(10) == eq.hash(10), isTrue); 44 | expect(eq.hash(10) == eq.hash(11), isFalse); 45 | 46 | expect(eq.isValidKey(10), isTrue); 47 | expect(eq.isValidKey(10.0), isTrue); 48 | 49 | expect(eq.isValidKey('10'), isFalse); 50 | }); 51 | 52 | test('Int32x4Equality', () { 53 | var eq = Int32x4Equality(); 54 | 55 | expect( 56 | eq.equals(Int32x4(10, 20, 30, 40), Int32x4(10, 20, 30, 40)), isTrue); 57 | expect(eq.equals(Int32x4(10, 20, 30, 40), Int32x4(-10, 20, 30, 40)), 58 | isFalse); 59 | 60 | expect(eq.hash(Int32x4(10, 20, 30, 40)), 61 | equals(eq.hash(Int32x4(10, 20, 30, 40)))); 62 | expect( 63 | eq.hash(Int32x4(10, 20, 30, 40)) == eq.hash(Int32x4(-10, 20, 30, 40)), 64 | isFalse); 65 | 66 | expect(eq.isValidKey(Int32x4(10, 20, 30, 40)), isTrue); 67 | expect(eq.isValidKey(Float32x4(10, 20, 30, 40)), isFalse); 68 | expect(eq.isValidKey('10'), isFalse); 69 | }); 70 | 71 | test('Float32x4Equality', () { 72 | var eq = Float32x4Equality(); 73 | 74 | expect(eq.equals(Float32x4(10, 20, 30, 40), Float32x4(10, 20, 30, 40)), 75 | isTrue); 76 | expect(eq.equals(Float32x4(10, 20, 30, 40), Float32x4(-10, 20, 30, 40)), 77 | isFalse); 78 | 79 | expect(eq.hash(Float32x4(10, 20, 30, 40)), 80 | equals(eq.hash(Float32x4(10, 20, 30, 40)))); 81 | expect( 82 | eq.hash(Float32x4(10, 20, 30, 40)) == 83 | eq.hash(Float32x4(-10, 20, 30, 40)), 84 | isFalse); 85 | 86 | expect(eq.isValidKey(Float32x4(10, 20, 30, 40)), isTrue); 87 | expect(eq.isValidKey(Int32x4(10, 20, 30, 40)), isFalse); 88 | expect(eq.isValidKey('10'), isFalse); 89 | }); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /example/dynamic_int_benchmark_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | 3 | void main() { 4 | var set = { 5 | 'int': testInt, 6 | 'DynamicInt': testDynamicInt, 7 | 'BigInt': testBigInt, 8 | }; 9 | 10 | // Warm up: 11 | benchmarkSet(2000000, set); 12 | 13 | var results = benchmarkSet(2000000, set); 14 | 15 | print('RESULTS (${results.length}):'); 16 | for (var r in results) { 17 | print('-> $r'); 18 | } 19 | 20 | var fastest = results.last; 21 | 22 | print('\nFASTEST: $fastest'); 23 | } 24 | 25 | const m1Value = 2; 26 | const m2Value = 3; 27 | const m3Value = 4; 28 | const m4Value = 10; 29 | 30 | BenchmarkFunctionResult testInt(int loops) { 31 | var ops = 0; 32 | 33 | var total = 0; 34 | ops++; 35 | 36 | var m1 = m1Value; 37 | ops++; 38 | var m2 = m2Value; 39 | ops++; 40 | var m3 = m3Value; 41 | ops++; 42 | var m4 = m4Value; 43 | ops++; 44 | 45 | for (var i = 0; i < loops; ++i) { 46 | var n1 = i; 47 | ops++; 48 | 49 | var n2 = n1 * m1; 50 | ops++; 51 | 52 | var n3 = n1 * m2; 53 | ops++; 54 | 55 | var n4 = n1 * m3; 56 | ops++; 57 | 58 | var n5 = (n1 * n3) + (n2 * n4); 59 | ops += 3; 60 | 61 | var n6 = n5 % m4; 62 | ops++; 63 | 64 | total += n6; 65 | ops++; 66 | } 67 | return BenchmarkFunctionResult(ops, total.toBigInt()); 68 | } 69 | 70 | BenchmarkFunctionResult testDynamicInt(int loops) { 71 | var ops = 0; 72 | 73 | var total = DynamicInt.zero; 74 | ops++; 75 | 76 | var m1 = DynamicInt.fromInt(m1Value); 77 | ops++; 78 | var m2 = DynamicInt.fromInt(m2Value); 79 | ops++; 80 | var m3 = DynamicInt.fromInt(m3Value); 81 | ops++; 82 | var m4 = DynamicInt.fromInt(m4Value); 83 | ops++; 84 | 85 | for (var i = 0; i < loops; ++i) { 86 | var n1 = DynamicInt.fromInt(i); 87 | ops++; 88 | 89 | var n2 = n1 * m1; 90 | ops++; 91 | 92 | var n3 = n1 * m2; 93 | ops++; 94 | 95 | var n4 = n1 * m3; 96 | ops++; 97 | 98 | var n5 = (n1 * n3) + (n2 * n4); 99 | ops += 3; 100 | 101 | var n6 = n5 % m4; 102 | ops++; 103 | 104 | total = (total + n6).toDynamicInt(); 105 | ops++; 106 | } 107 | return BenchmarkFunctionResult(ops, total.toBigInt()); 108 | } 109 | 110 | BenchmarkFunctionResult testBigInt(int loops) { 111 | var ops = 0; 112 | 113 | var total = BigInt.zero; 114 | ops++; 115 | 116 | var m1 = BigInt.from(m1Value); 117 | ops++; 118 | var m2 = BigInt.from(m2Value); 119 | ops++; 120 | var m3 = BigInt.from(m3Value); 121 | ops++; 122 | var m4 = BigInt.from(m4Value); 123 | ops++; 124 | 125 | for (var i = 0; i < loops; ++i) { 126 | var n1 = BigInt.from(i); 127 | ops++; 128 | 129 | var n2 = n1 * m1; 130 | ops++; 131 | 132 | var n3 = n1 * m2; 133 | ops++; 134 | 135 | var n4 = n1 * m3; 136 | ops++; 137 | 138 | var n5 = (n1 * n3) + (n2 * n4); 139 | ops += 3; 140 | 141 | var n6 = n5 % m4; 142 | ops++; 143 | 144 | total += n6; 145 | ops++; 146 | } 147 | 148 | return BenchmarkFunctionResult(ops, total); 149 | } 150 | 151 | // --------------------------------------------- 152 | // OUTPUT: 153 | // --------------------------------------------- 154 | // RESULTS (3): 155 | // -> BigInt{ 892.00 ms · hertz: 20,179,377 Hz · ops: 18,000,005 · start: 2022-03-30 04:24:29.924215 .. 30.822949 } -> 9000000 156 | // -> DynamicInt{ 252.00 ms · hertz: 71,428,591 Hz · ops: 18,000,005 · start: 2022-03-30 04:24:29.672963 .. 30.842626 } -> 9000000 157 | // -> int{ 30.00 ms · hertz: 600,000,166 Hz · ops: 18,000,005 · start: 2022-03-30 04:24:29.642038 .. 30.842715 } -> 9000000 158 | // 159 | // FASTEST: int{ 30.00 ms · hertz: 600,000,166 Hz · ops: 18,000,005 · start: 2022-03-30 04:24:29.642038 .. 30.842783 } -> 9000000 160 | // 161 | -------------------------------------------------------------------------------- /test/statistics_benchmark_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | import 'package:test/expect.dart'; 3 | import 'package:test/scaffolding.dart'; 4 | 5 | void main() { 6 | group('Benchmark', () { 7 | test('benchmarkSet', () { 8 | var set = { 9 | 'int': testInt, 10 | 'DynamicInt': testDynamicInt, 11 | 'BigInt': testBigInt, 12 | }; 13 | 14 | benchmarkSet(2000000, set); 15 | 16 | var results = benchmarkSet(2000000, set); 17 | 18 | for (var e in results) { 19 | print('-> $e'); 20 | } 21 | 22 | expect( 23 | results.map((e) => e.name), equals(['BigInt', 'DynamicInt', 'int'])); 24 | }); 25 | 26 | test('benchmark: verbose', () { 27 | var result = 28 | benchmark('DynamicInt', 2000000, testDynamicInt, verbose: true); 29 | 30 | expect(result.hertz, greaterThan(1000)); 31 | expect(result.duration.inMilliseconds, greaterThan(1)); 32 | }); 33 | 34 | test('benchmark: verbose+printer', () { 35 | var result = benchmark('DynamicInt', 2000000, testDynamicInt, 36 | verbose: true, printer: (o) => print('>> $o')); 37 | expect(result.hertz, greaterThan(1000)); 38 | }); 39 | }); 40 | } 41 | 42 | const m1Value = 2; 43 | const m2Value = 3; 44 | const m3Value = 4; 45 | const m4Value = 10; 46 | 47 | BenchmarkFunctionResult testInt(int loops) { 48 | var ops = 0; 49 | 50 | var total = 0; 51 | ops++; 52 | 53 | var m1 = m1Value; 54 | ops++; 55 | var m2 = m2Value; 56 | ops++; 57 | var m3 = m3Value; 58 | ops++; 59 | var m4 = m4Value; 60 | ops++; 61 | 62 | for (var i = 0; i < loops; ++i) { 63 | var n1 = i; 64 | ops++; 65 | 66 | var n2 = n1 * m1; 67 | ops++; 68 | 69 | var n3 = n1 * m2; 70 | ops++; 71 | 72 | var n4 = n1 * m3; 73 | ops++; 74 | 75 | var n5 = (n1 * n3) + (n2 * n4); 76 | ops += 3; 77 | 78 | var n6 = n5 % m4; 79 | ops++; 80 | 81 | total += n6; 82 | ops++; 83 | } 84 | return BenchmarkFunctionResult(ops, total.toBigInt()); 85 | } 86 | 87 | BenchmarkFunctionResult testDynamicInt(int loops) { 88 | var ops = 0; 89 | 90 | var total = DynamicInt.zero; 91 | ops++; 92 | 93 | var m1 = DynamicInt.fromInt(m1Value); 94 | ops++; 95 | var m2 = DynamicInt.fromInt(m2Value); 96 | ops++; 97 | var m3 = DynamicInt.fromInt(m3Value); 98 | ops++; 99 | var m4 = DynamicInt.fromInt(m4Value); 100 | ops++; 101 | 102 | for (var i = 0; i < loops; ++i) { 103 | var n1 = DynamicInt.fromInt(i); 104 | ops++; 105 | 106 | var n2 = n1 * m1; 107 | ops++; 108 | 109 | var n3 = n1 * m2; 110 | ops++; 111 | 112 | var n4 = n1 * m3; 113 | ops++; 114 | 115 | var n5 = (n1 * n3) + (n2 * n4); 116 | ops += 3; 117 | 118 | var n6 = n5 % m4; 119 | ops++; 120 | 121 | total = (total + n6).toDynamicInt(); 122 | ops++; 123 | } 124 | return BenchmarkFunctionResult(ops, total.toBigInt()); 125 | } 126 | 127 | BenchmarkFunctionResult testBigInt(int loops) { 128 | var ops = 0; 129 | 130 | var total = BigInt.zero; 131 | ops++; 132 | 133 | var m1 = BigInt.from(m1Value); 134 | ops++; 135 | var m2 = BigInt.from(m2Value); 136 | ops++; 137 | var m3 = BigInt.from(m3Value); 138 | ops++; 139 | var m4 = BigInt.from(m4Value); 140 | ops++; 141 | 142 | for (var i = 0; i < loops; ++i) { 143 | var n1 = BigInt.from(i); 144 | ops++; 145 | 146 | var n2 = n1 * m1; 147 | ops++; 148 | 149 | var n3 = n1 * m2; 150 | ops++; 151 | 152 | var n4 = n1 * m3; 153 | ops++; 154 | 155 | var n5 = (n1 * n3) + (n2 * n4); 156 | ops += 3; 157 | 158 | var n6 = n5 % m4; 159 | ops++; 160 | 161 | total += n6; 162 | ops++; 163 | } 164 | 165 | return BenchmarkFunctionResult(ops, total); 166 | } 167 | -------------------------------------------------------------------------------- /example/bayesnet_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | 3 | void main() { 4 | // Monitor events to then build a Bayesian Network: 5 | // ** Note that this example is NOT USING REAL probabilities for Cancer! 6 | var eventMonitor = BayesEventMonitor('cancer'); 7 | 8 | // The prevalence of Cancer in the population: 9 | // - 1% (10:990): 10 | 11 | for (var i = 0; i < 990; ++i) { 12 | eventMonitor.notifyEvent(['CANCER=false']); 13 | } 14 | 15 | for (var i = 0; i < 10; ++i) { 16 | eventMonitor.notifyEvent(['CANCER=true']); 17 | } 18 | 19 | // The Exam performance when the person have cancer: 20 | // - 90% Sensitivity. 21 | // - 10% false negative (1:9). 22 | 23 | for (var i = 0; i < 9; ++i) { 24 | eventMonitor.notifyEvent(['EXAM=positive', 'CANCER=true']); 25 | } 26 | 27 | for (var i = 0; i < 1; ++i) { 28 | eventMonitor.notifyEvent(['EXAM=negative', 'CANCER=true']); 29 | } 30 | 31 | // The Exam performance when the person doesn't have cancer: 32 | // - 91% Specificity 33 | // - 9% false positive (89:901). 34 | 35 | for (var i = 0; i < 901; ++i) { 36 | eventMonitor.notifyEvent(['EXAM=negative', 'CANCER=false']); 37 | } 38 | for (var i = 0; i < 89; ++i) { 39 | eventMonitor.notifyEvent(['EXAM=positive', 'CANCER=false']); 40 | } 41 | 42 | var bayesNet = eventMonitor.buildBayesianNetwork(); 43 | 44 | print('$bayesNet\n'); 45 | 46 | var analyser = bayesNet.analyser; 47 | 48 | print('- Analyser: $analyser\n'); 49 | 50 | var answerCancerWithoutExam = analyser.ask('P(cancer)'); 51 | print('- Cancer probability without an Exam:'); 52 | print(' $answerCancerWithoutExam'); 53 | 54 | var answerNoCancerWithoutExam = analyser.ask('P(-cancer)'); 55 | print('- Not having Cancer probability without an Exam:'); 56 | print(' $answerNoCancerWithoutExam'); 57 | 58 | var answerCancerWithPositiveExam = analyser.ask('P(cancer|exam)'); 59 | print('- Cancer probability with a positive Exam:'); 60 | print(' $answerCancerWithPositiveExam'); 61 | 62 | var answerCancerWithNegativeExam = analyser.ask('P(cancer|-exam)'); 63 | print('- Cancer probability with a negative Exam:'); 64 | print(' $answerCancerWithNegativeExam'); 65 | 66 | var answerNoCancerWithPositiveExam = analyser.ask('P(-cancer|exam)'); 67 | print('- Not having Cancer probability with a positive Exam:'); 68 | print(' $answerNoCancerWithPositiveExam'); 69 | 70 | var answerNoCancerWithNegativeExam = analyser.ask('P(-cancer|-exam)'); 71 | print('- Not having Cancer probability with a negative Exam:'); 72 | print(' $answerNoCancerWithNegativeExam'); 73 | 74 | print('\n** NOTE: This example is NOT USING REAL probabilities for Cancer!'); 75 | } 76 | 77 | // --------------------------------------------- 78 | // OUTPUT: 79 | // --------------------------------------------- 80 | // BayesianNetwork[cancer]{ variables: 2 }< 81 | // CANCER: [] 82 | // CANCER = FALSE: 0.99 83 | // CANCER = TRUE: 0.01 84 | // EXAM: [CANCER] 85 | // EXAM = NEGATIVE, CANCER = FALSE: 0.9101010101010101 86 | // EXAM = POSITIVE, CANCER = FALSE: 0.0898989898989899 87 | // EXAM = NEGATIVE, CANCER = TRUE: 0.1 88 | // EXAM = POSITIVE, CANCER = TRUE: 0.9 89 | // 90 | // > 91 | // 92 | // - Analyser: BayesAnalyserVariableElimination{network: cancer} 93 | // 94 | // - Cancer probability without an Exam: 95 | // P(cancer) -> CANCER = TRUE | -> 0.01 >> 100.00% 96 | // - Not having Cancer probability without an Exam: 97 | // P(-cancer) -> CANCER = FALSE | -> 0.99 >> 100.00% 98 | // - Cancer probability with a positive Exam: 99 | // P(cancer|exam) -> CANCER = TRUE | EXAM = POSITIVE -> 0.09183673469387756 (0.009000000000000001) >> 918.37% 100 | // - Cancer probability with a negative Exam: 101 | // P(cancer|-exam) -> CANCER = TRUE | EXAM = NEGATIVE -> 0.0011086474501108647 (0.001) >> 11.09% 102 | // - Not having Cancer probability with a positive Exam: 103 | // P(-cancer|exam) -> CANCER = FALSE | EXAM = POSITIVE -> 0.9081632653061223 (0.089) >> 91.73% 104 | // - Not having Cancer probability with a negative Exam: 105 | // P(-cancer|-exam) -> CANCER = FALSE | EXAM = NEGATIVE -> 0.9988913525498891 (0.901) >> 100.90% 106 | // 107 | // ** NOTE: This example is NOT USING REAL probabilities for Cancer! 108 | // 109 | -------------------------------------------------------------------------------- /example/bayesnet_dependency_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | 3 | void main() { 4 | // ** Note that this example is NOT USING REAL probabilities for Cancer! 5 | 6 | var bayesNet = BayesianNetwork('cancer'); 7 | 8 | // C (cancer) = T (true) ; F (false) 9 | bayesNet.addVariable("C", [ 10 | 'F', 11 | 'T', 12 | ], [], [ 13 | "C = F: 0.99", 14 | "C = T: 0.01", 15 | ]); 16 | 17 | // X (exam) = P (positive) ; N (negative) 18 | bayesNet.addVariable("X", [ 19 | '+P', 20 | '-N', 21 | ], [ 22 | "C" 23 | ], [ 24 | "X = N, C = F: 0.91", 25 | "X = P, C = F: 0.09", 26 | "X = N, C = T: 0.10", 27 | "X = P, C = T: 0.90", 28 | ]); 29 | 30 | // D (Doctor diagnosis) = P (positive) ; N (negative) 31 | bayesNet.addVariable("D", [ 32 | '+P', 33 | '-N', 34 | ], [ 35 | "C" 36 | ], [ 37 | "D = N, C = F: 0.99", 38 | "D = P, C = F: 0.01", 39 | "D = N, C = T: 0.75", 40 | "D = P, C = T: 0.25", 41 | ]); 42 | 43 | // Add dependency between D (Doctor diagnosis) and X (Exam), 44 | // where the probability of a correct diagnosis is improved: 45 | bayesNet.addDependency([ 46 | 'D', 47 | 'X' 48 | ], [ 49 | "D = N, X = N, C = F: 0.364", 50 | "D = P, X = N, C = F: 0.546", 51 | "D = N, X = P, C = F: 0.036", 52 | "D = P, X = P, C = F: 0.054", 53 | "D = N, X = N, C = T: 0.025", 54 | "D = N, X = P, C = T: 0.075", 55 | "D = P, X = N, C = T: 0.225", 56 | "D = P, X = P, C = T: 0.675", 57 | ]); 58 | 59 | // Show the network nodes and probabilities: 60 | print(bayesNet); 61 | 62 | var analyser = bayesNet.analyser; 63 | 64 | // Ask the probability to have cancer with a positive exame (X = P): 65 | var answer1 = analyser.ask('P(c|x)'); 66 | print( 67 | answer1); // P(c|x) -> C = T | X = P -> 0.09174311926605506 (0.009000000000000001) >> 917.43% 68 | 69 | // Ask the probability to have cancer with a negative exame (X = N): 70 | var answer2 = analyser.ask('P(c|-x)'); 71 | print( 72 | answer2); // P(c|-x) -> C = T | X = N -> 0.0011087703736556158 (0.001) >> 11.09% 73 | 74 | // Ask the probability to have cancer with a positive diagnosis from the Doctor (D = P): 75 | var answer3 = analyser.ask('P(c|d)'); 76 | print( 77 | answer3); // P(c|d) -> C = T | D = P -> 0.20161290322580644 (0.0025) >> 2016.13% 78 | 79 | // Ask the probability to have cancer with a negative diagnosis from the Doctor (D = N): 80 | var answer4 = analyser.ask('P(c|-d)'); 81 | print( 82 | answer4); // P(c|-d) -> C = T | D = N -> 0.007594167679222358 (0.0075) >> 75.94% 83 | 84 | // Ask the probability to have cancer with a positive diagnosis from the Doctor and a positive exame (D = P, X = P): 85 | var answer5 = analyser.ask('P(c|d,x)'); 86 | print( 87 | answer5); // P(c|d,x) -> C = T | D = P, X = P -> 0.11210762331838567 (0.006750000000000001) >> 1121.08% 88 | 89 | // Ask the probability to have cancer with a negative diagnosis from the Doctor and a negative exame (D = N, X = N): 90 | var answer6 = analyser.ask('P(c|-d,-x)'); 91 | print( 92 | answer6); // P(c|-d,-x) -> C = T | D = N, X = N -> 0.0006932697373894235 (0.00025) >> 6.93% 93 | 94 | print('-- Generating all possible questions:'); 95 | 96 | var questions = analyser.generateQuestions('C', 97 | addPriorQuestions: true, combinationsLevel: 2); 98 | var answers = analyser.quiz(questions); 99 | 100 | answers.sortByQuery(); 101 | 102 | for (var answer in answers) { 103 | print(answer); 104 | } 105 | } 106 | 107 | // --------------------------------------------- 108 | // OUTPUT: 109 | // --------------------------------------------- 110 | // BayesianNetwork[cancer]{ variables: 3 }< 111 | // C: [] 112 | // C = F: 0.99 113 | // C = T: 0.01 114 | // X: [C] 115 | // X = N, C = F: 0.91 116 | // X = P, C = F: 0.09 117 | // X = N, C = T: 0.1 118 | // X = P, C = T: 0.9 119 | // D: [C] 120 | // D = N, C = F: 0.99 121 | // D = P, C = F: 0.01 122 | // D = N, C = T: 0.75 123 | // D = P, C = T: 0.25 124 | // D <-> X: 125 | // D = N, C = F, X = N: 0.364 126 | // D = N, C = F, X = P: 0.036 127 | // D = P, C = F, X = N: 0.546 128 | // D = P, C = F, X = P: 0.054 129 | // D = N, C = T, X = N: 0.025 130 | // D = N, C = T, X = P: 0.075 131 | // D = P, C = T, X = N: 0.225 132 | // D = P, C = T, X = P: 0.675 133 | // > 134 | // P(c|x) -> C = T | X = P -> 0.09174311926605506 (0.009000000000000001) >> 917.43% 135 | // P(c|-x) -> C = T | X = N -> 0.0011087703736556158 (0.001) >> 11.09% 136 | // P(c|d) -> C = T | D = P -> 0.20161290322580644 (0.0025) >> 2016.13% 137 | // P(c|-d) -> C = T | D = N -> 0.007594167679222358 (0.0075) >> 75.94% 138 | // P(c|d,x) -> C = T | D = P, X = P -> 0.11210762331838567 (0.006750000000000001) >> 1121.08% 139 | // P(c|-d,-x) -> C = T | D = N, X = N -> 0.0006932697373894235 (0.00025) >> 6.93% 140 | // -- Generating all possible questions: 141 | // P(-C) -> C = F | -> 0.99 >> 100.00% 142 | // P(C) -> C = T | -> 0.01 >> 100.00% 143 | // P(C|-D) -> C = T | D = N -> 0.007594167679222358 (0.0075) >> 75.94% 144 | // P(C|-X,-D) -> C = T | D = N, X = N -> 0.0006932697373894235 (0.00025) >> 6.93% 145 | // P(C|X,-D) -> C = T | D = N, X = P -> 0.020610057708161583 (0.00075) >> 206.10% 146 | // P(C|D) -> C = T | D = P -> 0.20161290322580644 (0.0025) >> 2016.13% 147 | // P(C|-X,D) -> C = T | D = P, X = N -> 0.00414524954402255 (0.0022500000000000003) >> 41.45% 148 | // P(C|X,D) -> C = T | D = P, X = P -> 0.11210762331838567 (0.006750000000000001) >> 1121.08% 149 | // P(C|-X) -> C = T | X = N -> 0.0011087703736556158 (0.001) >> 11.09% 150 | // P(C|X) -> C = T | X = P -> 0.09174311926605506 (0.009000000000000001) >> 917.43% 151 | // 152 | -------------------------------------------------------------------------------- /test/statistics_tools_csv_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('CSV', () { 6 | setUp(() {}); 7 | 8 | test('>', () { 9 | var categories = >{ 10 | 'a': [10, 20, 30], 11 | 'b': [100, 200, 300] 12 | }; 13 | 14 | var csv1 = categories.generateCSV(); 15 | 16 | expect( 17 | csv1, 18 | equals('#,a,b\n' 19 | '1,10,100\n' 20 | '2,20,200\n' 21 | '3,30,300\n')); 22 | 23 | var csv2 = categories.generateCSV(valueNormalizer: (v) => v * 2); 24 | 25 | expect( 26 | csv2, 27 | equals('#,a,b\n' 28 | '1,20,200\n' 29 | '2,40,400\n' 30 | '3,60,600\n')); 31 | 32 | expect(categories.csvFileName('test', 'list'), 33 | matches(RegExp(r'^test--list--\d+\.csv$'))); 34 | }); 35 | 36 | test('>', () { 37 | var categories = >{ 38 | 'a': [10, 20, null], 39 | 'b': [100, 200, 300] 40 | }; 41 | 42 | var csv1 = categories.generateCSV(); 43 | 44 | expect( 45 | csv1, 46 | equals('#,a,b\n' 47 | '1,10,100\n' 48 | '2,20,200\n' 49 | '3,0,300\n')); 50 | 51 | var csv2 = categories.generateCSV(valueNormalizer: (v) => (v ?? 0) * 2); 52 | 53 | expect( 54 | csv2, 55 | equals('#,a,b\n' 56 | '1,20,200\n' 57 | '2,40,400\n' 58 | '3,0,600\n')); 59 | 60 | expect(categories.csvFileName('test', 'list'), 61 | matches(RegExp(r'^test--list--\d+\.csv$'))); 62 | }); 63 | 64 | test('>', () { 65 | var categories = >{ 66 | 'a': [10.0, 20.512345, null], 67 | 'b': [100.0, 200.0, 300.0] 68 | }; 69 | 70 | var csv1 = categories.generateCSV(); 71 | 72 | expect( 73 | csv1, 74 | equals('#,a,b\n' 75 | '1,10,100\n' 76 | '2,20.5123,200\n' 77 | '3,0,300\n')); 78 | 79 | var csv2 = categories.generateCSV(valueNormalizer: (v) => (v ?? 0) * 2); 80 | 81 | expect( 82 | csv2.replaceAll('.0,', ',').replaceAll('.0\n', '\n'), 83 | equals('#,a,b\n' 84 | '1,20,200\n' 85 | '2,41.02469,400\n' 86 | '3,0,600\n')); 87 | 88 | expect(categories.csvFileName('test', 'list'), 89 | matches(RegExp(r'^test--list--\d+\.csv$'))); 90 | 91 | expect(categories.csvFileName('test', 'foo'), 92 | matches(RegExp(r'^test--foo--\d+.csv$'))); 93 | }); 94 | 95 | test('>', () { 96 | var list = >[ 97 | [10, 20, 30], 98 | [30, 40, 50] 99 | ]; 100 | 101 | var list2 = list.map((e) => e.statistics); 102 | 103 | var csv1 = list2.generateCSV(decimalPrecision: -1); 104 | 105 | expect( 106 | csv1, 107 | equals('mean,standardDeviation,length,min,max,sum,squaresSum\n' 108 | '20,8.16496580927726,3,10,30,60,1400\n' 109 | '40,8.16496580927726,3,30,50,120,5000\n')); 110 | 111 | var csv2 = list2.generateCSV(decimalPrecision: 2); 112 | 113 | expect( 114 | csv2, 115 | equals('mean,standardDeviation,length,min,max,sum,squaresSum\n' 116 | '20,8.16,3,10,30,60,1400\n' 117 | '40,8.16,3,30,50,120,5000\n')); 118 | 119 | var csv3 = list2.generateCSV(decimalPrecision: 0); 120 | 121 | expect( 122 | csv3, 123 | equals('mean,standardDeviation,length,min,max,sum,squaresSum\n' 124 | '20,8,3,10,30,60,1400\n' 125 | '40,8,3,30,50,120,5000\n')); 126 | 127 | var csv4 = 128 | list2.generateCSV(decimalPrecision: 2, commaAsDecimalSeparator: true); 129 | 130 | expect( 131 | csv4, 132 | equals('mean,standardDeviation,length,min,max,sum,squaresSum\n' 133 | '20,"8,16",3,10,30,60,1400\n' 134 | '40,"8,16",3,30,50,120,5000\n')); 135 | 136 | var csv5 = 137 | list2.generateCSV(valueNormalizer: (v) => v is num ? v * 2 : v!); 138 | 139 | expect( 140 | csv5.replaceAll('.0,', ',').replaceAll('.0\n', '\n'), 141 | equals('mean,standardDeviation,length,min,max,sum,squaresSum\n' 142 | '40,16.32993161855452,6,20,60,120,2800\n' 143 | '80,16.32993161855452,6,60,100,240,10000\n')); 144 | 145 | expect(list2.csvFileName('test', 'list'), 146 | matches(RegExp(r'^test--list--\d+\.csv$'))); 147 | 148 | expect(list2.csvFileName('test', 'foo'), 149 | matches(RegExp(r'^test--foo--\d+.csv$'))); 150 | }); 151 | 152 | test('[]', () { 153 | var l = [ 154 | {'a': 1, 'b': 2}, 155 | {'a': 10, 'b': 20}, 156 | {'a': 100, 'b': 200} 157 | ]; 158 | 159 | var csv1 = l.generateCSV(); 160 | 161 | expect( 162 | csv1, 163 | equals('a,b\n' 164 | '1,2\n' 165 | '10,20\n' 166 | '100,200\n')); 167 | 168 | var csv2 = l.generateCSV(valueNormalizer: (v) => int.parse('$v') * 2); 169 | 170 | expect( 171 | csv2, 172 | equals('a,b\n' 173 | '2,4\n' 174 | '20,40\n' 175 | '200,400\n')); 176 | 177 | var csv3 = l.generateCSV(fieldsNames: ['b', 'a']); 178 | 179 | expect( 180 | csv3, 181 | equals('b,a\n' 182 | '2,1\n' 183 | '20,10\n' 184 | '200,100\n')); 185 | 186 | var csv4 = l.generateCSV( 187 | fieldsNames: ['b', 'a'], valueNormalizer: (v) => int.parse('$v') * 2); 188 | 189 | expect( 190 | csv4, 191 | equals('b,a\n' 192 | '4,2\n' 193 | '40,20\n' 194 | '400,200\n')); 195 | 196 | expect(l.csvFileName('test', 'foo'), 197 | matches(RegExp(r'^test--foo--\d+.csv$'))); 198 | }); 199 | }); 200 | } 201 | -------------------------------------------------------------------------------- /lib/src/statistics_extension_simd.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | /// extension for [Int32x4]. 4 | extension Int32x4Extension on Int32x4 { 5 | /// Converts to a [Float32x4]. 6 | Float32x4 toFloat32x4() => 7 | Float32x4(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble()); 8 | 9 | /// Filter this with [mapper]. 10 | Int32x4 filter(Int32x4 Function(Int32x4 e) mapper) => mapper(this); 11 | 12 | /// Filter each value with [mapper] and return a [Int32x4]. 13 | Int32x4 filterValues(int Function(int e) mapper) { 14 | return Int32x4( 15 | mapper(x), 16 | mapper(y), 17 | mapper(z), 18 | mapper(w), 19 | ); 20 | } 21 | 22 | /// Filter each value with [mapper] and return a [Float32x4]. 23 | Float32x4 filterToDoubleValues(double Function(int e) mapper) { 24 | return Float32x4( 25 | mapper(x), 26 | mapper(y), 27 | mapper(z), 28 | mapper(w), 29 | ); 30 | } 31 | 32 | /// Map using [mapper]. 33 | T map(T Function(Int32x4 e) mapper) => mapper(this); 34 | 35 | /// Returns values as `List`. 36 | List toInts() => [x, y, z, w]; 37 | 38 | Int32x4 operator *(Int32x4 other) => Int32x4( 39 | x * other.x, 40 | y * other.y, 41 | z * other.z, 42 | w * other.w, 43 | ); 44 | 45 | Int32x4 operator ~/(Int32x4 other) => Int32x4( 46 | x ~/ other.x, 47 | y ~/ other.y, 48 | z ~/ other.z, 49 | w ~/ other.w, 50 | ); 51 | 52 | /// Returns the minimal lane value. 53 | int get minInLane { 54 | var min = x; 55 | if (y < min) min = y; 56 | if (z < min) min = z; 57 | if (w < min) min = w; 58 | return min; 59 | } 60 | 61 | /// Returns the maximum lane value. 62 | int get maxInLane { 63 | var max = x; 64 | if (y > max) max = y; 65 | if (z > max) max = z; 66 | if (w > max) max = w; 67 | return max; 68 | } 69 | 70 | /// Sum lane. 71 | int get sumLane => x + y + z + w; 72 | 73 | /// Sum part of the lane, until [size]. 74 | int sumLanePartial(int size) { 75 | switch (size) { 76 | case 1: 77 | return x; 78 | case 2: 79 | return x + y; 80 | case 3: 81 | return x + y + z; 82 | case 4: 83 | return x + y + z + w; 84 | default: 85 | throw StateError('Invalid length: $size / 4'); 86 | } 87 | } 88 | 89 | /// Sum lane squares. 90 | int get sumSquaresLane => (x * x) + (y * y) + (z * z) + (w * w); 91 | 92 | /// Sum part of the lane squares, until [size]. 93 | int sumSquaresLanePartial(int size) { 94 | switch (size) { 95 | case 1: 96 | return (x * x); 97 | case 2: 98 | return (x * x) + (y * y); 99 | case 3: 100 | return (x * x) + (y * y) + (z * z); 101 | case 4: 102 | return (x * x) + (y * y) + (z * z) + (w * w); 103 | default: 104 | throw StateError('Invalid length: $size / 4'); 105 | } 106 | } 107 | 108 | /// Returns true if equals to [other] values. 109 | bool equalsValues(Int32x4 other, {num tolerance = 0.0}) { 110 | var diff = this - other; 111 | 112 | if (tolerance == 0) { 113 | return diff.x == 0 && diff.y == 0 && diff.z == 0 && diff.w == 0; 114 | } else { 115 | tolerance = tolerance.abs(); 116 | return diff.x.abs() <= tolerance && 117 | diff.y.abs() <= tolerance && 118 | diff.z.abs() <= tolerance && 119 | diff.w.abs() <= tolerance; 120 | } 121 | } 122 | } 123 | 124 | /// extension for [Float32x4]. 125 | extension Float32x4Extension on Float32x4 { 126 | /// Converts to a [Int32x4]. 127 | Int32x4 toInt32x4() => Int32x4(x.toInt(), y.toInt(), z.toInt(), w.toInt()); 128 | 129 | /// Perform a `toInt()` in each value and return a [Float32x4]. 130 | Float32x4 toIntAsFloat32x4() => Float32x4(x.toInt().toDouble(), 131 | y.toInt().toDouble(), z.toInt().toDouble(), w.toInt().toDouble()); 132 | 133 | /// Filter this with [mapper]. 134 | Float32x4 filter(Float32x4 Function(Float32x4 e) filter) => filter(this); 135 | 136 | /// Filter each value with [mapper] and return a [Float32x4]. 137 | Float32x4 filterValues(double Function(double e) mapper) { 138 | return Float32x4( 139 | mapper(x), 140 | mapper(y), 141 | mapper(z), 142 | mapper(w), 143 | ); 144 | } 145 | 146 | /// Filter each value with [mapper] and return a [Int32x4]. 147 | Int32x4 filterToIntValues(int Function(double e) mapper) { 148 | return Int32x4( 149 | mapper(x), 150 | mapper(y), 151 | mapper(z), 152 | mapper(w), 153 | ); 154 | } 155 | 156 | /// Map using [mapper]. 157 | T map(T Function(Float32x4 e) mapper) => mapper(this); 158 | 159 | /// Returns values as `List`. 160 | List toDoubles() => [x, y, z, w]; 161 | 162 | Int32x4 operator ~/(Float32x4 other) => Int32x4( 163 | x ~/ other.x, 164 | y ~/ other.y, 165 | z ~/ other.z, 166 | w ~/ other.w, 167 | ); 168 | 169 | /// Returns the minimum lane value. 170 | double get minInLane { 171 | var min = x; 172 | if (y < min) min = y; 173 | if (z < min) min = z; 174 | if (w < min) min = w; 175 | return min; 176 | } 177 | 178 | /// Returns the maximum lane value. 179 | double get maxInLane { 180 | var max = x; 181 | if (y > max) max = y; 182 | if (z > max) max = z; 183 | if (w > max) max = w; 184 | return max; 185 | } 186 | 187 | /// Sum lane. 188 | double get sumLane => x + y + z + w; 189 | 190 | /// Sum part of the lane, until [size]. 191 | double sumLanePartial(int size) { 192 | switch (size) { 193 | case 1: 194 | return x; 195 | case 2: 196 | return x + y; 197 | case 3: 198 | return x + y + z; 199 | case 4: 200 | return x + y + z + w; 201 | default: 202 | throw StateError('Invalid length: $size / 4'); 203 | } 204 | } 205 | 206 | /// Sum lane squares. 207 | double get sumSquaresLane => (x * x) + (y * y) + (z * z) + (w * w); 208 | 209 | /// Sum part of the lane squares, until [size]. 210 | double sumSquaresLanePartial(int size) { 211 | switch (size) { 212 | case 1: 213 | return (x * x); 214 | case 2: 215 | return (x * x) + (y * y); 216 | case 3: 217 | return (x * x) + (y * y) + (z * z); 218 | case 4: 219 | return (x * x) + (y * y) + (z * z) + (w * w); 220 | default: 221 | throw StateError('Invalid length: $size / 4'); 222 | } 223 | } 224 | 225 | /// Returns true if equals to [other] values. 226 | bool equalsValues(Float32x4 other, {num tolerance = 0.0}) { 227 | var diff = this - other; 228 | 229 | if (tolerance == 0) { 230 | return diff.x == 0.0 && diff.y == 0.0 && diff.z == 0.0 && diff.w == 0.0; 231 | } else { 232 | tolerance = tolerance.abs(); 233 | return diff.x.abs() <= tolerance && 234 | diff.y.abs() <= tolerance && 235 | diff.z.abs() <= tolerance && 236 | diff.w.abs() <= tolerance; 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /lib/src/statistics_prime.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:statistics/statistics.dart'; 3 | 4 | /// Utils for prime numbers. 5 | class PrimeUtils { 6 | static final List _knownPrimes = [ 7 | 2, 8 | 3, 9 | 5, 10 | 7, 11 | 11, 12 | 13, 13 | 17, 14 | 19, 15 | 23, 16 | 29, 17 | 31, 18 | 37, 19 | 41, 20 | 43, 21 | 47, 22 | 53, 23 | 59, 24 | 61, 25 | 67, 26 | 71, 27 | 73, 28 | 79, 29 | 83, 30 | 89, 31 | 97 32 | ]; 33 | 34 | /// A list of known primes. 35 | /// Used to compute if a number is prime or not. 36 | static UnmodifiableListView get knownPrimes => 37 | UnmodifiableListView(_knownPrimes); 38 | 39 | static int get knownPrimesLength => _knownPrimes.length; 40 | 41 | static Iterator get knownPrimesIterator => _knownPrimes.iterator; 42 | 43 | /// Returns the last known prime in the [knownPrimes] list. 44 | static int get lastKnownPrime { 45 | assert(_knownPrimes.isNotEmpty, '`_knownPrimes` must never be empty.'); 46 | return _knownPrimes.last; 47 | } 48 | 49 | /// Expands the [knownPrimes] list to length [knownPrimesLength]. 50 | static void expandKnownPrimes(int knownPrimesLength) { 51 | if (_knownPrimes.length >= knownPrimesLength) return; 52 | 53 | while (_knownPrimes.length < knownPrimesLength) { 54 | var n = knownPrimes.last + 2; 55 | 56 | do { 57 | if (n.isPrime) { 58 | _knownPrimes.add(n); 59 | break; 60 | } else { 61 | n += 2; 62 | } 63 | } while (n > 1); 64 | } 65 | } 66 | 67 | /// Contracts the [knownPrimes] list length to [knownPrimesLength]. 68 | static void contractKnownPrimes(int knownPrimesLength) { 69 | if (knownPrimesLength < 5) { 70 | knownPrimesLength = 5; 71 | } 72 | 73 | if (_knownPrimes.length <= knownPrimesLength) return; 74 | 75 | _knownPrimes.removeRange(knownPrimesLength, _knownPrimes.length); 76 | } 77 | 78 | /// Returns `true` if [n] is in the [knownPrimes] list. 79 | static bool? isKnownPrime(int n) { 80 | assert(_knownPrimes.isNotEmpty, '`_knownPrimes` must never be empty.'); 81 | 82 | if (n > _knownPrimes.last) { 83 | return null; 84 | } 85 | 86 | var idx = _knownPrimes.binarySearch(n, (a, b) => a.compareTo(b)); 87 | return idx >= 0; 88 | } 89 | 90 | /// Generates a [List] of prime numbers of [length] and below [primeLimit] (if provided). 91 | static List generatePrimes(int length, {int? primeLimit}) { 92 | if (length <= 0) return []; 93 | 94 | if (length == 1) return [2]; 95 | if (length == 2) return [2, 3]; 96 | 97 | if (primeLimit == null || primeLimit <= 0) { 98 | primeLimit = Statistics.maxSafeInt; 99 | } 100 | 101 | if (primeLimit <= 3) { 102 | primeLimit = 3; 103 | } 104 | 105 | var primes = [2, 3]; 106 | 107 | for (var n = 5; n < primeLimit && primes.length < length; n += 2) { 108 | if (n.isPrime) { 109 | primes.add(n); 110 | } 111 | } 112 | 113 | return primes; 114 | } 115 | } 116 | 117 | /// Prime numbers extension on [int]. 118 | extension PrimeIntExtension on int { 119 | /// Returns `true` if this [int] is a prime number. 120 | bool get isPrime { 121 | var n = this; 122 | 123 | if (n <= 0) return false; 124 | if (n == 1) return false; 125 | if (n == 2) return true; 126 | 127 | var knownPrime = PrimeUtils.isKnownPrime(n); 128 | 129 | if (knownPrime != null) { 130 | return knownPrime; 131 | } 132 | 133 | var b = n.squareRoot; 134 | 135 | var itr = PrimeUtils.knownPrimesIterator; 136 | itr.moveNext(); // it's never empty. 137 | 138 | var p = itr.current; 139 | if (n % p == 0) return false; 140 | 141 | while (itr.moveNext()) { 142 | p = itr.current; 143 | if (p > b) break; 144 | 145 | if (n % p == 0) return false; 146 | } 147 | 148 | for (p += 2; p <= b; p += 2) { 149 | if (n % p == 0) return false; 150 | } 151 | 152 | return true; 153 | } 154 | 155 | /// Returns [val] when this number [isPrime] otherwise returns [def]. 156 | T? whenPrime(T val, [T? def]) { 157 | if (isPrime) { 158 | return val; 159 | } else { 160 | return def; 161 | } 162 | } 163 | } 164 | 165 | /// Prime numbers extension on [DynamicNumber]. 166 | extension PrimeDynamicNumberExtension on DynamicNumber { 167 | /// Returns `true` if this [DynamicNumber] is a prime number. 168 | bool get isPrime { 169 | var self = this; 170 | if (self is DynamicInt) { 171 | return self.isPrime; 172 | } else if (self is Decimal) { 173 | return self.isPrime; 174 | } else { 175 | throw StateError("Unknown type: $runtimeType"); 176 | } 177 | } 178 | 179 | /// Returns [val] when this number [isPrime] otherwise returns [def]. 180 | T? whenPrime(T val, [T? def]) { 181 | if (isPrime) { 182 | return val; 183 | } else { 184 | return def; 185 | } 186 | } 187 | } 188 | 189 | extension PrimeDecimalExtension on Decimal { 190 | /// Returns `true` if this [Decimal] [isWholeNumber] and is a prime number. 191 | bool get isPrime { 192 | if (isWholeNumber) { 193 | return toDynamicInt().isPrime; 194 | } else { 195 | return false; 196 | } 197 | } 198 | } 199 | 200 | /// Prime numbers extension on [DynamicInt]. 201 | extension PrimeDynamicIntExtension on DynamicInt { 202 | /// Returns `true` if this [DynamicInt] is a prime number. 203 | bool get isPrime { 204 | var n = this; 205 | 206 | if (n.isZero) return false; 207 | if (n.isNegative) return false; 208 | if (n.isOne) return false; 209 | if (n == DynamicInt.two) return true; 210 | 211 | if (n.isSafeInteger) { 212 | return n.toInt().isPrime; 213 | } 214 | 215 | var b = n.squareRoot.toDynamicInt(); 216 | 217 | // Faster: 218 | if (b.isSafeInteger) { 219 | var bI = b.toInt(); 220 | 221 | var itr = PrimeUtils.knownPrimesIterator; 222 | itr.moveNext(); // it's never empty. 223 | 224 | var p = itr.current; 225 | if (n.moduloInt(p).isZero) return false; 226 | 227 | while (itr.moveNext()) { 228 | p = itr.current; 229 | if (p > bI) break; 230 | 231 | if (n.moduloInt(p).isZero) return false; 232 | } 233 | 234 | for (p = p + 2; p <= bI; p += 2) { 235 | if (n.moduloInt(p).isZero) return false; 236 | } 237 | } 238 | // For a very big `n`. 239 | // Only using `DynamicInt` (slower): 240 | else { 241 | var itr = PrimeUtils.knownPrimesIterator; 242 | itr.moveNext(); // it's never empty. 243 | 244 | var p = itr.current.toDynamicInt(); 245 | if (n.moduloDynamicInt(p).isZero) return false; 246 | 247 | while (itr.moveNext()) { 248 | p = itr.current.toDynamicInt(); 249 | if (p > b) break; 250 | 251 | if (n.moduloDynamicInt(p).isZero) return false; 252 | } 253 | 254 | for (p = p.sumInt(2); p <= b; p = p.sumInt(2)) { 255 | if (n.moduloDynamicInt(p).isZero) return false; 256 | } 257 | } 258 | 259 | return true; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /lib/src/statistics_combination.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | /// A combination cache. See [generateCombinations]. 4 | class CombinationCache { 5 | final bool allowRepetition; 6 | final bool allowSharedCombinations; 7 | final Iterable Function(T e)? mapper; 8 | final bool Function(List combination)? validator; 9 | 10 | CombinationCache( 11 | {required this.allowRepetition, 12 | this.mapper, 13 | this.validator, 14 | this.allowSharedCombinations = false}); 15 | 16 | final Map<_CombinationCacheKey, List>> _cache = 17 | <_CombinationCacheKey, List>>{}; 18 | 19 | /// Returns the total number of combinations in the cache. 20 | int get totalCachedCombinations => _cache.length; 21 | 22 | /// Clears the combinations cache. 23 | void clear() => _cache.clear(); 24 | 25 | /// Returns a cached combination or generates it. 26 | List> getCombinations( 27 | Set alphabet, int minimumSize, int maximumSize) => 28 | _getCombinationsImpl(alphabet, minimumSize, maximumSize) 29 | .map((e) => e.toList()) 30 | .toList(); 31 | 32 | /// Same of [getCombinations], but the returned [List] won't be isolated from the 33 | /// cache (is the actual instance inside the cache). 34 | /// 35 | /// - [allowSharedCombinations] needs to be true, or a [StateError] will be thrown. 36 | /// 37 | /// - Note: any modification in the returned list can corrupt the combination 38 | /// integrity for a future return of this cached combination cache. 39 | List> getCombinationsShared( 40 | Set alphabet, int minimumSize, int maximumSize) { 41 | if (!allowSharedCombinations) { 42 | throw StateError('Shared combinations not allowed: $this'); 43 | } 44 | 45 | return _getCombinationsImpl(alphabet, minimumSize, maximumSize); 46 | } 47 | 48 | List> _getCombinationsImpl( 49 | Set alphabet, int minimumSize, int maximumSize) { 50 | return _cache.putIfAbsent( 51 | _CombinationCacheKey(alphabet, minimumSize, maximumSize), 52 | () => _generateCombinationsImpl(alphabet, minimumSize, maximumSize)); 53 | } 54 | 55 | int _computedCombinations = 0; 56 | 57 | /// Returns the number of computed combinations. 58 | /// 59 | /// - [clear] won't reset this value. 60 | int get computedCombinations => _computedCombinations; 61 | 62 | List> _generateCombinationsImpl( 63 | Set alphabet, int minimumSize, int maximumSize) { 64 | ++_computedCombinations; 65 | return generateCombinations(alphabet, minimumSize, maximumSize, 66 | allowRepetition: allowRepetition, 67 | checkAlphabet: false, 68 | mapper: mapper, 69 | validator: validator); 70 | } 71 | 72 | @override 73 | String toString() { 74 | return 'CombinationCache<$T,$E>{ allowRepetition: $allowRepetition, allowSharedCombinations: $allowSharedCombinations, cache: $totalCachedCombinations, computedCombinations: $_computedCombinations }'; 75 | } 76 | } 77 | 78 | class _CombinationCacheKey { 79 | final Set _alphabet; 80 | 81 | final int _minimumSize; 82 | final int _maximumSize; 83 | 84 | late final int _alphabetHash = _setEquality.hash(_alphabet); 85 | 86 | _CombinationCacheKey(this._alphabet, this._minimumSize, this._maximumSize); 87 | 88 | static final _setEquality = SetEquality(); 89 | 90 | @override 91 | bool operator ==(Object other) => 92 | identical(this, other) || 93 | other is _CombinationCacheKey && 94 | runtimeType == other.runtimeType && 95 | _minimumSize == other._minimumSize && 96 | _maximumSize == other._maximumSize && 97 | _setEquality.equals(_alphabet, other._alphabet); 98 | 99 | @override 100 | int get hashCode => 101 | _alphabetHash ^ _minimumSize.hashCode ^ _maximumSize.hashCode; 102 | } 103 | 104 | /// Generate combinations using the [alphabet] elements. 105 | /// 106 | /// - [alphabet] of possible elements per combination. 107 | /// - The [minimumSize] of the generated combinations. 108 | /// - The [maximumSize] of the generated combinations. 109 | /// - If [allowRepetition] is `true` will allow the repetition of elements for each combination. 110 | /// - If [checkAlphabet] is `true` it will check if the [alphabet] has duplicated elements. 111 | /// - An optional [mapper] can be used to expand or map each [alphabet] element. 112 | /// - An optional combination [validator]. 113 | /// - Note that an alphabet can't have duplicated elements. 114 | List> generateCombinations( 115 | Iterable alphabet, int minimumSize, int maximumSize, 116 | {bool allowRepetition = true, 117 | bool checkAlphabet = true, 118 | Iterable Function(T e)? mapper, 119 | bool Function(List combination)? validator}) { 120 | if (minimumSize < 1) minimumSize = 1; 121 | 122 | var combinations = >[]; 123 | if (alphabet.isEmpty || maximumSize <= 0) return combinations; 124 | 125 | if (alphabet is! List && alphabet is! Set) { 126 | alphabet = alphabet.toList(); 127 | } 128 | 129 | if (checkAlphabet && alphabet is! Set) { 130 | var set = alphabet.toSet(); 131 | if (set.length != alphabet.length) { 132 | throw ArgumentError('Invalid alphabet: found duplicated element!'); 133 | } 134 | } 135 | 136 | mapper ??= (e) => [e as E]; 137 | validator ??= (c) => true; 138 | 139 | if (allowRepetition) { 140 | for (var size = minimumSize; size <= maximumSize; ++size) { 141 | _fillWithRepetition( 142 | alphabet, [], size, combinations, mapper, validator); 143 | } 144 | } else { 145 | if (maximumSize > alphabet.length) { 146 | maximumSize = alphabet.length; 147 | } 148 | 149 | for (var size = minimumSize; size <= maximumSize; ++size) { 150 | _fillNoRepetition( 151 | alphabet, [], 0, size, combinations, mapper, validator); 152 | } 153 | } 154 | 155 | return combinations; 156 | } 157 | 158 | void _fillNoRepetition( 159 | Iterable alphabet, 160 | List dst, 161 | int offset, 162 | int limit, 163 | List> output, 164 | Iterable Function(T e) mapper, 165 | bool Function(List combination) validator) { 166 | var length = alphabet.length; 167 | 168 | for (var i = offset; i < length; ++i) { 169 | var e = alphabet.elementAt(i); 170 | 171 | var values = mapper(e); 172 | 173 | for (var v in values) { 174 | var dst2 = [...dst, v]; 175 | if (dst2.length < limit) { 176 | _fillNoRepetition( 177 | alphabet, dst2, i + 1, limit, output, mapper, validator); 178 | } else if (validator(dst2)) { 179 | output.add(dst2); 180 | } 181 | } 182 | } 183 | } 184 | 185 | void _fillWithRepetition( 186 | Iterable alphabet, 187 | List dst, 188 | int limit, 189 | List> output, 190 | Iterable Function(T e) mapper, 191 | bool Function(List combination) validator) { 192 | var length = alphabet.length; 193 | 194 | for (var i = 0; i < length; ++i) { 195 | var e = alphabet.elementAt(i); 196 | 197 | var values = mapper(e); 198 | 199 | for (var v in values) { 200 | var dst2 = [...dst, v]; 201 | if (dst2.length < limit) { 202 | _fillWithRepetition(alphabet, dst2, limit, output, mapper, validator); 203 | } else if (validator(dst2)) { 204 | output.add(dst2); 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /lib/src/statistics_tools_csv.dart: -------------------------------------------------------------------------------- 1 | import 'statistics_base.dart'; 2 | import 'statistics_extension.dart'; 3 | import 'statistics_extension_num.dart'; 4 | 5 | extension DataEntryExtension on Iterable { 6 | /// Generates a `CSV` document. 7 | String generateCSV( 8 | {String separator = ',', 9 | List? fieldsNames, 10 | Object Function(Object? e)? valueNormalizer, 11 | bool commaAsDecimalSeparator = false, 12 | int decimalPrecision = 4}) { 13 | if (isEmpty) return ''; 14 | 15 | var csv = StringBuffer(); 16 | 17 | fieldsNames ??= first.getDataFields(); 18 | 19 | { 20 | var head = fieldsNames.map(_normalizeLine).join(separator); 21 | csv.write(head); 22 | csv.write('\n'); 23 | } 24 | 25 | if (valueNormalizer != null) { 26 | for (var e in this) { 27 | var values = e.getDataValues(); 28 | var line = values 29 | .map((e) => _normalizeLine(valueNormalizer(e).toString())) 30 | .join(separator); 31 | csv.write(line); 32 | csv.write('\n'); 33 | } 34 | } else { 35 | for (var e in this) { 36 | var values = e.getDataValues(); 37 | var line = values.map((e) { 38 | var s = normalizeCSVValue(e, 39 | separator: separator, 40 | commaAsDecimalSeparator: commaAsDecimalSeparator, 41 | decimalPrecision: decimalPrecision); 42 | return _normalizeLine(s); 43 | }).join(separator); 44 | csv.write(line); 45 | csv.write('\n'); 46 | } 47 | } 48 | 49 | return csv.toString(); 50 | } 51 | 52 | /// Creates a `CSV` file name. 53 | String csvFileName(String prefix, String name) => _csvFileName(prefix, name); 54 | } 55 | 56 | extension IterableMapExtensionCSV on Iterable> { 57 | /// Generates a `CSV` document. 58 | String generateCSV( 59 | {String separator = ',', 60 | List? fieldsNames, 61 | Object Function(Object? e)? valueNormalizer, 62 | bool commaAsDecimalSeparator = false, 63 | int decimalPrecision = 4}) { 64 | if (isEmpty) return ''; 65 | 66 | var csv = StringBuffer(); 67 | 68 | List? keys; 69 | 70 | if (fieldsNames == null) { 71 | keys = first.keys.toList(); 72 | } 73 | 74 | { 75 | var head = (fieldsNames ?? keys!) 76 | .toStringsList() 77 | .map(_normalizeLine) 78 | .join(separator); 79 | csv.write(head); 80 | csv.write('\n'); 81 | } 82 | 83 | if (valueNormalizer != null) { 84 | for (var e in this) { 85 | var values = keys != null 86 | ? e.getValuesInKeysOrder(keys) 87 | : e.getValuesInStringKeysOrder(fieldsNames!); 88 | 89 | var line = values 90 | .map((e) => _normalizeLine(valueNormalizer(e).toString())) 91 | .join(separator); 92 | csv.write(line); 93 | csv.write('\n'); 94 | } 95 | } else { 96 | for (var e in this) { 97 | var values = keys != null 98 | ? e.getValuesInKeysOrder(keys) 99 | : e.getValuesInStringKeysOrder(fieldsNames!); 100 | 101 | var line = values.map((e) { 102 | var s = normalizeCSVValue(e, 103 | separator: separator, 104 | commaAsDecimalSeparator: commaAsDecimalSeparator, 105 | decimalPrecision: decimalPrecision); 106 | return _normalizeLine(s); 107 | }).join(separator); 108 | csv.write(line); 109 | csv.write('\n'); 110 | } 111 | } 112 | 113 | return csv.toString(); 114 | } 115 | 116 | /// Creates a `CSV` file name. 117 | String csvFileName(String prefix, String name) => _csvFileName(prefix, name); 118 | } 119 | 120 | /// Normalizes a CSV column value. 121 | String normalizeCSVValue(V value, 122 | {String separator = ',', 123 | bool commaAsDecimalSeparator = false, 124 | int decimalPrecision = 4}) { 125 | String s; 126 | 127 | if (value is double) { 128 | var d = formatDecimal(value, 129 | decimalSeparator: commaAsDecimalSeparator ? ',' : '.', 130 | precision: decimalPrecision); 131 | if (d == null) { 132 | d = '$value'; 133 | if (commaAsDecimalSeparator) { 134 | d = d.replaceFirst('.', ','); 135 | } 136 | } 137 | s = d; 138 | } else { 139 | s = '$value'; 140 | } 141 | 142 | if (s.contains(separator)) { 143 | s = '"$s"'; 144 | } 145 | 146 | return s; 147 | } 148 | 149 | final _regexpNewLine = RegExp(r'[\r\n]'); 150 | 151 | String _normalizeLine(String e) => e.replaceAll(_regexpNewLine, ' '); 152 | 153 | extension SeriesMapExtension on Map?> { 154 | static Type _toType() => T; 155 | 156 | static final Type _intNullable = _toType(); 157 | static final Type _doubleNullable = _toType(); 158 | static final Type _numNullable = _toType(); 159 | static final Type _stringNullable = _toType(); 160 | 161 | E _toDefault() { 162 | if (E == int || E == _intNullable) { 163 | return 0 as E; 164 | } else if (E == double || E == _doubleNullable) { 165 | return 0.0 as E; 166 | } else if (E == num || E == _numNullable) { 167 | return 0 as E; 168 | } else if (E == String || E == _stringNullable) { 169 | return '' as E; 170 | } else { 171 | throw StateError('No default value for type: $E'); 172 | } 173 | } 174 | 175 | /// Generates a `CSV` document. 176 | String generateCSV( 177 | {String separator = ',', 178 | E? nullValue, 179 | int firstEntryIndex = 1, 180 | Object Function(E e)? valueNormalizer, 181 | bool commaAsDecimalSeparator = false, 182 | int decimalPrecision = 4}) { 183 | if (isEmpty) return ''; 184 | 185 | var csv = StringBuffer(); 186 | 187 | var keys = this.keys; 188 | 189 | { 190 | var head = keys.map(_normalizeLine).join(separator); 191 | csv.write('#$separator'); 192 | csv.write(head); 193 | csv.write('\n'); 194 | } 195 | 196 | var totalLines = values.map((e) => e?.length ?? 0).toList().statistics.max; 197 | 198 | if (valueNormalizer != null) { 199 | for (var i = 0; i < totalLines; ++i) { 200 | var line = StringBuffer(); 201 | line.write('${i + firstEntryIndex}'); 202 | 203 | for (var k in keys) { 204 | var e = this[k]?.getValueIfExists(i); 205 | e ??= nullValue; 206 | e ??= (nullValue = _toDefault())!; 207 | 208 | var v = valueNormalizer(e as E); 209 | 210 | line.write(separator); 211 | line.write(v); 212 | } 213 | 214 | csv.write(line); 215 | csv.write('\n'); 216 | } 217 | } else { 218 | for (var i = 0; i < totalLines; ++i) { 219 | var line = StringBuffer(); 220 | line.write('${i + firstEntryIndex}'); 221 | 222 | for (var k in keys) { 223 | var e = this[k]?.getValueIfExists(i); 224 | e ??= nullValue; 225 | e ??= (nullValue = _toDefault())!; 226 | 227 | var s = normalizeCSVValue(e, 228 | separator: separator, 229 | commaAsDecimalSeparator: commaAsDecimalSeparator, 230 | decimalPrecision: decimalPrecision); 231 | 232 | line.write(separator); 233 | line.write(s); 234 | } 235 | 236 | csv.write(line); 237 | csv.write('\n'); 238 | } 239 | } 240 | 241 | return csv.toString(); 242 | } 243 | 244 | /// Creates a `CSV` file name. 245 | String csvFileName(String prefix, String name) => _csvFileName(prefix, name); 246 | } 247 | 248 | String _csvFileName(String prefix, String name) { 249 | var now = DateTime.now().millisecondsSinceEpoch; 250 | var csvFileName = '$prefix--$name--$now.csv'; 251 | return csvFileName; 252 | } 253 | -------------------------------------------------------------------------------- /test/statistics_extension_simd_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:statistics/statistics.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('extension SIMD', () { 8 | setUp(() {}); 9 | 10 | test('Int32x4', () { 11 | expect(Int32x4(10, 20, 30, 40).maxInLane, equals(40)); 12 | expect(Int32x4(10, 20, 30, 40).minInLane, equals(10)); 13 | 14 | expect(Int32x4(50, 20, 30, 40).maxInLane, equals(50)); 15 | expect(Int32x4(50, 20, 30, 40).minInLane, equals(20)); 16 | 17 | expect(Int32x4(50, 20, 30, 40).sumLane, equals(140)); 18 | expect(Int32x4(50, 20, 30, 40).sumSquaresLane, equals(5400)); 19 | 20 | expect(Int32x4(50, 20, 30, 40).sumLanePartial(4), equals(140)); 21 | expect(Int32x4(50, 20, 30, 40).sumLanePartial(3), equals(100)); 22 | expect(Int32x4(50, 20, 30, 40).sumLanePartial(2), equals(70)); 23 | expect(Int32x4(50, 20, 30, 40).sumLanePartial(1), equals(50)); 24 | 25 | expect(Int32x4(50, 20, 30, 40).sumSquaresLanePartial(4), equals(5400)); 26 | expect(Int32x4(50, 20, 30, 40).sumSquaresLanePartial(3), equals(3800)); 27 | expect(Int32x4(50, 20, 30, 40).sumSquaresLanePartial(2), equals(2900)); 28 | expect(Int32x4(50, 20, 30, 40).sumSquaresLanePartial(1), equals(2500)); 29 | 30 | expect(() => Int32x4(50, 20, 30, 40).sumLanePartial(5), throwsStateError); 31 | expect(() => Int32x4(50, 20, 30, 40).sumSquaresLanePartial(5), 32 | throwsStateError); 33 | 34 | expect(Int32x4(50, 20, 30, 40).equalsValues(Int32x4(50, 20, 30, 40)), 35 | isTrue); 36 | 37 | expect( 38 | Int32x4(50, 20, 30, 40) 39 | .equalsValues(Int32x4(50, 20, 30, 41), tolerance: 1), 40 | isTrue); 41 | 42 | expect( 43 | Int32x4(50, 20, 30, 40) 44 | .equalsValues(Int32x4(50, 20, 30, 41), tolerance: 0.1), 45 | isFalse); 46 | 47 | expect( 48 | (Int32x4(50, 20, 30, 40) - Int32x4(10, 30, 5, 40)) 49 | .equalsValues(Int32x4(40, -10, 25, 0)), 50 | isTrue); 51 | expect( 52 | (Int32x4(50, 20, 30, 40) * Int32x4(1, 2, 3, 4)) 53 | .equalsValues(Int32x4(50, 40, 90, 160)), 54 | isTrue); 55 | expect( 56 | (Int32x4(50, 20, 30, 40) ~/ Int32x4(1, 2, 3, 4)) 57 | .equalsValues(Int32x4(50, 10, 10, 10)), 58 | isTrue); 59 | 60 | expect(Int32x4(50, 20, 30, 40).toInts(), equals([50, 20, 30, 40])); 61 | 62 | expect( 63 | Int32x4(50, 20, 30, 40) 64 | .toFloat32x4() 65 | .equalsValues(Float32x4(50, 20, 30, 40)), 66 | isTrue); 67 | 68 | expect( 69 | Int32x4(50, 20, 30, 40).toFloat32x4().equalsValues( 70 | Float32x4(50.01, 20.01, 30.01, 40.01), 71 | tolerance: 0.1), 72 | isTrue); 73 | 74 | expect( 75 | Int32x4(50, 20, 30, 40).toFloat32x4().equalsValues( 76 | Float32x4(50.01, 20.01, 30.01, 40.01), 77 | tolerance: 0.001), 78 | isFalse); 79 | 80 | expect( 81 | Int32x4(50, 20, 30, 40) 82 | .filterValues((n) => n * 2) 83 | .equalsValues(Int32x4(100, 40, 60, 80)), 84 | isTrue); 85 | 86 | expect( 87 | Int32x4(50, 20, 30, 40) 88 | .filterToDoubleValues((n) => n * 2) 89 | .equalsValues(Float32x4(100, 40, 60, 80)), 90 | isTrue); 91 | 92 | expect( 93 | Int32x4(50, 20, 30, 40) 94 | .filter((e) => Int32x4(e.x * 3, e.y * 3, e.z * 3, e.w * 3)) 95 | .equalsValues(Int32x4(150, 60, 90, 120)), 96 | isTrue); 97 | 98 | expect(Int32x4(50, 20, 30, 40).map((e) => '${e.x};${e.y};${e.z};${e.w}'), 99 | equals('50;20;30;40')); 100 | }); 101 | 102 | test('Float32x4', () { 103 | expect(Float32x4(10, 20, 30, 40).maxInLane, equals(40)); 104 | expect(Float32x4(10, 20, 30, 40).minInLane, equals(10)); 105 | 106 | expect(Float32x4(50, 20, 30, 40).maxInLane, equals(50)); 107 | expect(Float32x4(50, 20, 30, 40).minInLane, equals(20)); 108 | 109 | expect(Float32x4(50, 20, 30, 40).sumLane, equals(140)); 110 | expect(Float32x4(50, 20, 30, 40).sumSquaresLane, equals(5400)); 111 | 112 | expect(Float32x4(50, 20, 30, 40).sumLanePartial(4), equals(140)); 113 | expect(Float32x4(50, 20, 30, 40).sumLanePartial(3), equals(100)); 114 | expect(Float32x4(50, 20, 30, 40).sumLanePartial(2), equals(70)); 115 | expect(Float32x4(50, 20, 30, 40).sumLanePartial(1), equals(50)); 116 | 117 | expect(Float32x4(50, 20, 30, 40).sumSquaresLanePartial(4), equals(5400)); 118 | expect(Float32x4(50, 20, 30, 40).sumSquaresLanePartial(3), equals(3800)); 119 | expect(Float32x4(50, 20, 30, 40).sumSquaresLanePartial(2), equals(2900)); 120 | expect(Float32x4(50, 20, 30, 40).sumSquaresLanePartial(1), equals(2500)); 121 | 122 | expect( 123 | () => Float32x4(50, 20, 30, 40).sumLanePartial(5), throwsStateError); 124 | expect(() => Float32x4(50, 20, 30, 40).sumSquaresLanePartial(5), 125 | throwsStateError); 126 | 127 | expect(Float32x4(50, 20, 30, 40).equalsValues(Float32x4(50, 20, 30, 40)), 128 | isTrue); 129 | 130 | expect( 131 | Float32x4(50, 20, 30, 40) 132 | .equalsValues(Float32x4(50, 20, 30, 40.01), tolerance: 0.1), 133 | isTrue); 134 | 135 | expect( 136 | Float32x4(50, 20, 30, 40) 137 | .equalsValues(Float32x4(50, 20, 30, 40.01), tolerance: 0.001), 138 | isFalse); 139 | 140 | expect( 141 | (Float32x4(50, 20, 30, 40) - Float32x4(10, 30, 5, 40)) 142 | .equalsValues(Float32x4(40, -10, 25, 0)), 143 | isTrue); 144 | expect( 145 | (Float32x4(50, 20, 30, 40) * Float32x4(1, 2, 3, 4)) 146 | .equalsValues(Float32x4(50, 40, 90, 160)), 147 | isTrue); 148 | expect( 149 | (Float32x4(50, 20, 30, 40) ~/ Float32x4(1, 2, 3, 4)) 150 | .equalsValues(Int32x4(50, 10, 10, 10)), 151 | isTrue); 152 | 153 | expect(Float32x4(50, 20, 30, 40).toDoubles(), 154 | equals([50.0, 20.0, 30.0, 40.0])); 155 | 156 | expect( 157 | Float32x4(50, 20, 30, 40) 158 | .toInt32x4() 159 | .equalsValues(Int32x4(50, 20, 30, 40)), 160 | isTrue); 161 | 162 | expect( 163 | Float32x4(50, 20, 30, 40) 164 | .toInt32x4() 165 | .equalsValues(Int32x4(50, 20, 30, 41), tolerance: 1), 166 | isTrue); 167 | 168 | expect( 169 | Float32x4(50, 20, 30, 40) 170 | .toInt32x4() 171 | .equalsValues(Int32x4(50, 20, 30, 41), tolerance: 0.1), 172 | isFalse); 173 | 174 | expect( 175 | Float32x4(50.01, 20, 30.2, 41.01) 176 | .toIntAsFloat32x4() 177 | .equalsValues(Float32x4(50, 20, 30, 41)), 178 | isTrue); 179 | 180 | expect( 181 | Float32x4(50, 20, 30, 40) 182 | .filterValues((n) => n * 2) 183 | .equalsValues(Float32x4(100, 40, 60, 80)), 184 | isTrue); 185 | 186 | expect( 187 | Float32x4(50, 20, 30, 40) 188 | .filterToIntValues((n) => (n * 2).toInt()) 189 | .equalsValues(Int32x4(100, 40, 60, 80)), 190 | isTrue); 191 | 192 | expect( 193 | Float32x4(50, 20, 30, 40) 194 | .filter((e) => Float32x4(e.x * 3, e.y * 3, e.z * 3, e.w * 3)) 195 | .equalsValues(Float32x4(150, 60, 90, 120)), 196 | isTrue); 197 | 198 | expect( 199 | Float32x4(50, 20, 30, 40) 200 | .map((e) => Float32x4(e.x * 3, e.y * 3, e.z * 3, e.w * 3)) 201 | .equalsValues(Float32x4(150, 60, 90, 120)), 202 | isTrue); 203 | 204 | expect( 205 | Float32x4(50.1, 20.1, 30.1, 40.1).map((e) => 206 | '${e.x.toStringAsFixed(1)};${e.y.toStringAsFixed(1)};${e.z.toStringAsFixed(1)};${e.w.toStringAsFixed(1)}'), 207 | equals('50.1;20.1;30.1;40.1')); 208 | }); 209 | }); 210 | } 211 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.2.0 2 | 3 | - sdk: '>=3.6.0 <4.0.0' 4 | 5 | - intl: ^0.20.2 6 | - collection: ^1.19.0 7 | - data_serializer: ^1.2.1 8 | 9 | - lints: ^5.1.1 10 | - test: ^1.25.15 11 | - dependency_validator: ^3.2.3 12 | - coverage: ^1.11.1 13 | 14 | ## 1.1.3 15 | 16 | - `Decimal`: 17 | - `_divideOperationByDynamicIntImpl2`: fix operation with therms of different signals (`-/+` or `+/-`). 18 | 19 | ## 1.1.2 20 | 21 | - sdk: '>=3.4.0 <4.0.0' 22 | 23 | - data_serializer: ^1.2.0 24 | - coverage: ^1.9.0 25 | 26 | ## 1.1.1 27 | 28 | - `DynamicNumber`: 29 | - Added `sin` and `cos`. 30 | 31 | - test: ^1.25.8 32 | - coverage: ^1.8.0 33 | 34 | ## 1.1.0 35 | 36 | - sdk: '>=3.3.0 <4.0.0' 37 | 38 | - intl: ^0.19.0 39 | - collection: ^1.18.0 40 | - data_serializer: ^1.1.0 41 | 42 | - lints: ^3.0.0 43 | - test: ^1.25.2 44 | - coverage: ^1.7.2 45 | 46 | ## 1.0.26 47 | 48 | - Fix: 49 | - `IterableNumExtension.standardDeviation`. 50 | - `IterableDoubleExtension.standardDeviation`. 51 | - `DecimalOnIterableDecimalExtension.standardDeviation`. 52 | - `DynamicIntOnIterableDynamicNumberExtension.standardDeviation`. 53 | 54 | - sdk: '>=2.14.0 <4.0.0' 55 | - intl: ^0.18.1 56 | - dependency_validator: ^3.2.3 57 | 58 | ## 1.0.25 59 | 60 | - `Decimal`: 61 | - Added `withMaximumPrecision`, `withMinimumPrecision` and `clipPrecision`. 62 | - collection: ^1.17.0 63 | - test: ^1.24.0 64 | - coverage: ^1.6.3 65 | 66 | ## 1.0.24 67 | 68 | - intl: ^0.18.0 69 | - lints: ^2.0.1 70 | - test: ^1.22.1 71 | - coverage: ^1.6.1 72 | 73 | ## 1.0.23 74 | 75 | - `DynamicNumber`, `DynamicInt`, `Decimal`: 76 | - Added `isWholeNumber`, `isOdd`, `isEven`. 77 | - Added prime numbers support: 78 | - `PrimeUtils`, `PrimeIntExtension`, `PrimeDynamicNumberExtension`, `PrimeDecimalExtension`, `PrimeDynamicIntExtension`. 79 | 80 | ## 1.0.22 81 | 82 | - Added extension `NumericTypeExtension`: 83 | - Methods: `isNumericType`, `isNumericOrDynamicNumberType`, `isDynamicNumberType`. 84 | - Added extension `NumericTypeExtension`: 85 | - Methods: `isNumericValue`, `isNumericOrDynamicNumberValue`, `isDynamicNumberValue`, `whenNull`. 86 | 87 | ## 1.0.21 88 | 89 | - Added `StandardDeviationComputer`: 90 | - `StandardDeviationComputerNum` 91 | - `StandardDeviationComputerBigInt` 92 | - `StandardDeviationComputerDynamicNumber` 93 | - Fix standard deviation online calculation. 94 | - sdk: '>=2.14.0 <3.0.0' 95 | - collection: ^1.16.0 96 | - lints: ^2.0.0 97 | - test: ^1.21.4 98 | - dependency_validator: ^3.2.2 99 | - coverage: ^1.2.0 100 | 101 | ## 1.0.20 102 | 103 | - Added `Decimal.tryParse` and `DynamicInt.tryParse`. 104 | 105 | ## 1.0.19 106 | 107 | - Added High-Precision Arithmetic: `DynamicInt` and `Decimal`. 108 | - Added benchmark tools. 109 | - Added browser tests to CI. 110 | - Fixed some tests for browser. 111 | 112 | ## 1.0.18 113 | 114 | - New `CountTable`. 115 | - `BayesianNetwork` and `BayesEventMonitor`: 116 | - Significant performance improvement for high number of variables and dependencies. 117 | - Allow variables nodes with multiple root paths. 118 | - `Pair`: 119 | - Added `hashCode` cache. 120 | - `generateCombinations`: 121 | - Added parameter `validator`. 122 | - Improved `Chronometer`: 123 | - New field `totalOperation` and method `timeToComplete`. 124 | - Added time marks. 125 | - Better `toString`: 126 | - Changed parameter `withTime` to `withStartTime`. 127 | - `Duration` extension: 128 | - `toStringUnit`: 129 | - New parameter `decimal`. 130 | - Better output for `zero` `Duration`. 131 | - Added `toStringDifference` 132 | - `String` extension 133 | - Added `headEqualsLength`, `tailEqualsLength`, `headEquals`, `tailDifferent`. 134 | - `ListExtension`: 135 | - Added `equalsElements`, `removeAll`, `retainAll`, `toDistinctList`, `shuffleCopy`, `randomSelection`. 136 | - `SetExtension`: 137 | - Added `copy`, `equalsElements`. 138 | - `IterableExtension`: 139 | - Added `copy`, `asList`, `asSet`, `whereIn`, `whereNotIn`, `equalsElements`, `computeHashcode`. 140 | - `MapEntryExtension`: 141 | - Added `copy`, `equals`, `toPair`. 142 | - `MapExtension`: 143 | - Added `copy`. 144 | 145 | ## 1.0.17 146 | 147 | - New type `Pair`. 148 | - New `EventForecaster` framework. 149 | - `BayesianNetwork`: 150 | - New method `addDependency`. 151 | - Now allows probability dependency between variables. 152 | - Added test with `XOR`. 153 | - `BayesEventMonitor`: 154 | - Now allows out-of-order events from the Bayesian Network topology. 155 | - New `CombinationCache` and function `generateCombinations`. 156 | - coverage: ^1.0.4 157 | 158 | ## 1.0.16 159 | 160 | - `ListComparablesExtension` changed to `IterableComparablesExtension`. 161 | - New `IterableUint8ListExtension`, `ListAnswerExtension` and `ListOfListAnswer`. 162 | 163 | ## 1.0.15 164 | 165 | - `Variable` node optimization: 166 | - `ancestors`, `rootNodes`, `rootChain`, `rootChain`. 167 | - `VariableElimination`: 168 | - Optimize resolution of nodes needed to answer a query. 169 | 170 | ## 1.0.14 171 | 172 | - Improved `BayesianNetwork`. 173 | - Optimized parsing and building of network. 174 | - Added `EventMonitor` to help to build a `BayesianNetwork` and probabilities. 175 | - Improved documentation. 176 | - Fix some typos. 177 | 178 | ## 1.0.13 179 | 180 | - Added `BayesianNetwork`. 181 | 182 | ## 1.0.12 183 | 184 | - Added `parseBigInt`. 185 | - Moved some extension methods to package `data_serializer`. 186 | - Moved `StatisticsPlatform` to package `data_serializer` as `DataSerializerPlatform`. 187 | - Exporting `data_serializer ^1.0.3` 188 | 189 | ## 1.0.11 190 | 191 | - Improved performance: `toUint8List32Reversed` and `reverseBytes`. 192 | - Added `BigInt.thousands`. 193 | 194 | ## 1.0.10 195 | 196 | - Optimize `Statistics` to allow computation of big numbers without overflow issues. 197 | 198 | ## 1.0.9 199 | 200 | - Added extensions: 201 | - `String: `encodeLatin1`, `encodeUTF8`, `truncate`. 202 | - `Uint8List`: `copyAsUnmodifiable`, `asUnmodifiableView`, `toStringLatin1/bytes`, `toStringUTF8/bytes`, 203 | `setUint8/16/32/64`, `setInt8/16/32/64`. 204 | - `List`: `toUint8List`, `asUint8List`, `compareWith`. 205 | - `int`: `isSafeInteger`, `checkSafeInteger`, `int16/32/64ToBytes`, `uInt16/32/64ToBytes`. 206 | - `BigInt`: `isSafeInteger`, `checkSafeInteger`. 207 | - Improved documentation. 208 | - Fix typo: renamed extension with `UInt` to `Uint` to follow Dart style. 209 | 210 | ## 1.0.8 211 | 212 | - Added extensions: 213 | - `DateTime.elapsedTime`. 214 | - `int`: `bits/8/16/32/64`, `toUint8List32Reversed`, `toUint8List64Reversed`. 215 | - `Uint8List`: `bits/8/16/32/64`. 216 | 217 | ## 1.0.7 218 | 219 | - `Chronometer.toString`: added parameter `withTime`. 220 | - Added `StatisticsPlatform` to ensure safe `int` serialization in any Dart platform. 221 | - `double` extension: added `toPercentage`. 222 | - `int` extension: added `toBigInt`, `toUint8List32`, `toUint8List64`, `toHex32`, `toHex64`, `toStringPadded`. 223 | - Added extension for `BigInt` and `Uint8List`. 224 | - Added numeric extension for `String`. 225 | - Migrated code coverage to use package `coverage`. 226 | - base_codecs: ^1.0.1 227 | - coverage: ^1.0.3 228 | 229 | ## 1.0.6 230 | 231 | - `Chronometer.elapsedTimeMs`: now returns the elapsed time even if not stopped. 232 | 233 | ## 1.0.5 234 | 235 | - `Statistics`: 236 | - Added `medianHigh`, `medianLow` and `median`. 237 | - Change constructor: required `medianHigh`. 238 | - `center` points to `medianHigh`. 239 | - `NumExtension`: 240 | - Added: `num.cast`. 241 | 242 | ## 1.0.4 243 | 244 | - New metric tools: 245 | - `UnitLength` and `UnitLengthExtension`. 246 | - Added: 247 | - `parseDateTime`. 248 | - `DateTimeExtension`: 249 | - `formatToYMD`, `formatToYMDHm`, `formatToYMDHms`, `formatToYMDHmZ` and `formatToYMDHmsZ`. 250 | - `MapExtension`: 251 | - `equalsKeysValues`. 252 | - `ListMapExtension`: 253 | - `sortByKey`. 254 | - `IterableMapExtension`: 255 | - `sortedByKey`. 256 | - `DoubleExtension`: 257 | - `truncateDecimals`. 258 | - Optimize: 259 | - Optimize `splitColumns` with `acceptsQuotedValues`. 260 | - Improved API Documentation. 261 | 262 | ## 1.0.3 263 | 264 | - New extension methods: 265 | - `head`, `tail` and `sublistReversed`. 266 | - `searchInsertSortedIndex` and `binarySearchPoint`. 267 | - `resampleByIndex` and `resampleByValue`. 268 | - Changed from package `pedantic` (deprecated) to `lints`. 269 | - lints: ^1.0.1 270 | 271 | ## 1.0.2 272 | 273 | - Improve extensions. 274 | - generateCSV: 275 | - Added `commaAsDecimalSeparator` and `decimalPrecision`. 276 | 277 | ## 1.0.1 278 | 279 | - Added: 280 | - `DoubleEquality`, `IntEquality` and `NumEquality`. 281 | - Improve unit tests and coverage up to 98%. 282 | - Improve example. 283 | - Improve `README.md`. 284 | 285 | ## 1.0.0 286 | 287 | - `extension` for `int`, `double` and `num` collections. 288 | - Initial version. 289 | -------------------------------------------------------------------------------- /test/statistics_forecast_even_xor_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:statistics/statistics.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('Forecast', () { 8 | test('forecast: EvenXOR{no phases}', () { 9 | var evenForecaster = EvenXorForecaster(); 10 | 11 | expect( 12 | evenForecaster.getPhaseOperations('').map((o) => '$o'), 13 | equals([ 14 | 'ForecastOperation{id: last_bit_A}', 15 | 'ForecastOperation{id: last_bit_B}' 16 | ])); 17 | 18 | expect(evenForecaster.getPhaseOperations('A').map((o) => '$o'), 19 | equals(['ForecastOperation{id: last_bit_A}'])); 20 | 21 | expect(evenForecaster.getPhaseOperations('B').map((o) => '$o'), 22 | equals(['ForecastOperation{id: last_bit_B}'])); 23 | 24 | var p1 = Pair(10, 2); 25 | var p2 = Pair(10, 3); 26 | var p3 = Pair(3, 3); 27 | var p4 = Pair(5, 5); 28 | var p5 = Pair(2, 4); 29 | var p6 = Pair(3, 4); 30 | 31 | expect(evenForecaster.forecast(p1), isTrue); 32 | expect(evenForecaster.forecast(p2), isFalse); 33 | expect(evenForecaster.forecast(p3), isTrue); 34 | 35 | expect(evenForecaster.allObservations.length, equals(6)); 36 | 37 | evenForecaster.concludeObservations(p1, {'EVEN': p1.xorIsEven}); 38 | expect(evenForecaster.allObservations.length, equals(4)); 39 | 40 | evenForecaster.concludeObservations(p2, {'EVEN': p2.xorIsEven}); 41 | expect(evenForecaster.allObservations.length, equals(2)); 42 | 43 | evenForecaster.concludeObservations(p3, {'EVEN': p3.xorIsEven}); 44 | expect(evenForecaster.allObservations.length, equals(0)); 45 | 46 | expect(evenForecaster.forecast(p4), isTrue); 47 | expect(evenForecaster.forecast(p5), isTrue); 48 | expect(evenForecaster.forecast(p6), isFalse); 49 | 50 | expect(evenForecaster.allObservations.length, equals(6)); 51 | 52 | evenForecaster.concludeObservations(p4, {'EVEN': p4.xorIsEven}); 53 | expect(evenForecaster.allObservations.length, equals(4)); 54 | 55 | evenForecaster.concludeObservations(p5, {'EVEN': p5.xorIsEven}); 56 | expect(evenForecaster.allObservations.length, equals(2)); 57 | 58 | evenForecaster.concludeObservations(p6, {'EVEN': p6.xorIsEven}); 59 | expect(evenForecaster.allObservations.length, equals(0)); 60 | 61 | var random = Random(123); 62 | 63 | var totalForecasts = 100000; 64 | 65 | for (var i = 0; i < totalForecasts; ++i) { 66 | var p = Pair(random.nextInt(100), random.nextInt(100)); 67 | 68 | expect(evenForecaster.forecast(p), equals(p.xorIsEven), reason: '$p'); 69 | evenForecaster.concludeObservations(p, {'EVEN': p.xorIsEven}); 70 | } 71 | 72 | expect(evenForecaster.allObservations.length, equals(0)); 73 | 74 | { 75 | var p = Pair(random.nextInt(100), random.nextInt(100)); 76 | expect(evenForecaster.forecast(p), equals(p.xorIsEven)); 77 | } 78 | 79 | expect(evenForecaster.allObservations.length, equals(2)); 80 | 81 | evenForecaster.disposeObservations(); 82 | expect(evenForecaster.allObservations.length, equals(0)); 83 | 84 | _testEvenProductProbabilities(evenForecaster.eventMonitor); 85 | }); 86 | 87 | test('forecast: EvenXOR{phases: [A, B]}', () { 88 | var evenForecaster = EvenXorForecaster(); 89 | 90 | var p1 = Pair(10, 2); 91 | var p2 = Pair(10, 3); 92 | var p3 = Pair(3, 3); 93 | var p4 = Pair(5, 5); 94 | var p5 = Pair(2, 4); 95 | var p6 = Pair(3, 4); 96 | 97 | expect(evenForecaster.forecast(p1, phase: 'A'), isTrue); 98 | expect(evenForecaster.forecast(p2, phase: 'A'), isTrue); 99 | expect(evenForecaster.forecast(p3, phase: 'A'), isFalse); 100 | expect(evenForecaster.forecast(p4, phase: 'A'), isFalse); 101 | expect(evenForecaster.forecast(p5, phase: 'A'), isTrue); 102 | expect(evenForecaster.forecast(p6, phase: 'A'), isFalse); 103 | 104 | expect(evenForecaster.allObservations.length, equals(6)); 105 | 106 | expect(evenForecaster.forecast(p1, phase: 'B', previousPhases: {'A'}), 107 | isTrue); 108 | expect(evenForecaster.forecast(p2, phase: 'B', previousPhases: {'A'}), 109 | isFalse); 110 | expect(evenForecaster.forecast(p3, phase: 'B', previousPhases: {'A'}), 111 | isTrue); 112 | expect(evenForecaster.forecast(p4, phase: 'B', previousPhases: {'A'}), 113 | isTrue); 114 | expect(evenForecaster.forecast(p5, phase: 'B', previousPhases: {'A'}), 115 | isTrue); 116 | expect(evenForecaster.forecast(p6, phase: 'B', previousPhases: {'A'}), 117 | isFalse); 118 | 119 | evenForecaster.concludeObservations(p1, {'EVEN': p1.xorIsEven}); 120 | evenForecaster.concludeObservations(p2, {'EVEN': p2.xorIsEven}); 121 | evenForecaster.concludeObservations(p3, {'EVEN': p3.xorIsEven}); 122 | evenForecaster.concludeObservations(p4, {'EVEN': p4.xorIsEven}); 123 | evenForecaster.concludeObservations(p5, {'EVEN': p5.xorIsEven}); 124 | evenForecaster.concludeObservations(p6, {'EVEN': p6.xorIsEven}); 125 | 126 | var random = Random(123); 127 | 128 | var totalForecasts = 100000; 129 | 130 | for (var i = 0; i < totalForecasts; ++i) { 131 | var p = Pair(random.nextInt(100), random.nextInt(100)); 132 | 133 | evenForecaster.forecast(p, phase: 'A'); 134 | evenForecaster.forecast(p, phase: 'B', previousPhases: {'A'}); 135 | evenForecaster.concludeObservations(p, {'EVEN': p.xorIsEven}); 136 | } 137 | 138 | _testEvenProductProbabilities(evenForecaster.eventMonitor); 139 | }); 140 | }); 141 | } 142 | 143 | void _testEvenProductProbabilities(BayesEventMonitor eventMonitor) { 144 | print(eventMonitor); 145 | 146 | var bayesNet = eventMonitor.buildBayesianNetwork( 147 | unseenMinimalProbability: 0.0, verbose: true); 148 | print(bayesNet); 149 | 150 | var analyser = bayesNet.analyser; 151 | 152 | expect(analyser.showAnswer('P(EVEN)').probability, _isNear(0.50, 0.01)); 153 | expect(analyser.showAnswer('P(-EVEN)').probability, _isNear(0.50, 0.01)); 154 | 155 | expect(analyser.showAnswer('P(EVEN|LAST_BIT_A)').probability, 156 | _isNear(0.50, 0.01)); 157 | expect(analyser.showAnswer('P(EVEN|-LAST_BIT_A)').probability, _isNear(0.50)); 158 | 159 | expect(analyser.showAnswer('P(EVEN|LAST_BIT_B)').probability, 160 | _isNear(0.50, 0.01)); 161 | expect(analyser.showAnswer('P(EVEN|-LAST_BIT_B)').probability, _isNear(.50)); 162 | 163 | expect(analyser.showAnswer('P(EVEN|-LAST_BIT_A,-LAST_BIT_B)').probability, 164 | _isNear(1.0)); 165 | 166 | expect(analyser.showAnswer('P(EVEN|LAST_BIT_A,-LAST_BIT_B)').probability, 167 | _isNear(0.0)); 168 | 169 | expect(analyser.showAnswer('P(EVEN|-LAST_BIT_A,LAST_BIT_B)').probability, 170 | _isNear(0.0)); 171 | 172 | expect(analyser.showAnswer('P(EVEN|LAST_BIT_A,LAST_BIT_B)').probability, 173 | _isNear(1.0)); 174 | } 175 | 176 | class EvenXorForecaster extends EventForecaster, bool, bool> { 177 | EvenXorForecaster() : super('EvenXOR'); 178 | 179 | @override 180 | List, bool>> generatePhaseOperations( 181 | String phase) { 182 | var opA = ObservationOperation, bool>('last_bit(A)', 183 | computer: _extractLastBitA); 184 | 185 | var opB = ObservationOperation, bool>('last_bit(B)', 186 | computer: _extractLastBitB); 187 | 188 | switch (phase) { 189 | case '': 190 | return [opA, opB]; 191 | case 'A': 192 | return [opA]; 193 | case 'B': 194 | return [opB]; 195 | default: 196 | throw StateError('Unknown phase: $phase'); 197 | } 198 | } 199 | 200 | static bool _extractLastBitA(Pair ns) => _extractBit(ns.a, 0); 201 | 202 | static bool _extractLastBitB(Pair ns) => _extractBit(ns.b, 0); 203 | 204 | static bool _extractBit(num n, int bit) { 205 | var i = n.toInt(); 206 | return (i >> bit) & 0x01 == 1; 207 | } 208 | 209 | @override 210 | bool computeForecast( 211 | String phase, List, bool>> observations) { 212 | var opLastBitA = observations.getByOpID('last_bit(A)')!; 213 | var opLastBitB = observations.getByOpID('last_bit(B)'); 214 | 215 | if (opLastBitB == null) { 216 | var odd = opLastBitA.value; 217 | return !odd; 218 | } else { 219 | var even = (opLastBitA.value == opLastBitB.value); 220 | return even; 221 | } 222 | } 223 | } 224 | 225 | extension _PairExtension on Pair { 226 | int get xor => a ^ b; 227 | 228 | bool get xorIsEven => xor % 2 == 0; 229 | } 230 | 231 | Matcher _isNear(double value, [double tolerance = 0.01]) => 232 | inInclusiveRange(value - tolerance, value + tolerance); 233 | -------------------------------------------------------------------------------- /test/statistics_metric_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('UnitLength', () { 6 | setUp(() {}); 7 | 8 | test('km', () { 9 | expect(UnitLength.km.name, equals('kilometre')); 10 | expect(UnitLength.km.unit, equals('km')); 11 | 12 | expect(UnitLength.nm.isMetric, isTrue); 13 | expect(UnitLength.nm.isSI, isTrue); 14 | expect(UnitLength.nm.isImperial, isFalse); 15 | 16 | expect(UnitLength.km.convertTo(UnitLength.km, 1), equals(1)); 17 | expect(UnitLength.km.convertTo(UnitLength.m, 1), equals(1000)); 18 | expect(UnitLength.km.convertTo(UnitLength.dm, 1), equals(10000)); 19 | expect(UnitLength.km.convertTo(UnitLength.cm, 1), equals(100000)); 20 | expect(UnitLength.km.convertTo(UnitLength.mm, 1), equals(1000000)); 21 | expect(UnitLength.km.convertTo(UnitLength.mic, 1), equals(1e+9)); 22 | expect(UnitLength.km.convertTo(UnitLength.nm, 1), equals(1e+12)); 23 | 24 | expect(UnitLength.km.convertTo(UnitLength.inch, 1), equals(39370.1)); 25 | expect(UnitLength.km.convertTo(UnitLength.mi, 1), equals(0.62137119224)); 26 | }); 27 | 28 | test('m', () { 29 | expect(UnitLength.m.name, equals('metre')); 30 | expect(UnitLength.m.unit, equals('m')); 31 | 32 | expect(UnitLength.nm.isMetric, isTrue); 33 | expect(UnitLength.nm.isSI, isTrue); 34 | expect(UnitLength.nm.isImperial, isFalse); 35 | 36 | expect(UnitLength.m.convertTo(UnitLength.km, 1), equals(0.001)); 37 | expect(UnitLength.m.convertTo(UnitLength.m, 1), equals(1)); 38 | expect(UnitLength.m.convertTo(UnitLength.dm, 1), equals(10)); 39 | expect(UnitLength.m.convertTo(UnitLength.cm, 1), equals(100)); 40 | expect(UnitLength.m.convertTo(UnitLength.mm, 1), equals(1000)); 41 | expect(UnitLength.m.convertTo(UnitLength.mic, 1), equals(1000000)); 42 | expect(UnitLength.m.convertTo(UnitLength.nm, 1), equals(1e+9)); 43 | 44 | expect(UnitLength.m.convertTo(UnitLength.inch, 1), equals(39.3701)); 45 | expect( 46 | UnitLength.m.convertTo(UnitLength.mi, 1), equals(0.00062137119224)); 47 | }); 48 | 49 | test('dm', () { 50 | expect(UnitLength.dm.name, equals('decimetre')); 51 | expect(UnitLength.dm.unit, equals('dm')); 52 | 53 | expect(UnitLength.nm.isMetric, isTrue); 54 | expect(UnitLength.nm.isSI, isTrue); 55 | expect(UnitLength.nm.isImperial, isFalse); 56 | 57 | expect(UnitLength.dm.convertTo(UnitLength.km, 1), equals(0.0001)); 58 | expect(UnitLength.dm.convertTo(UnitLength.m, 1), equals(0.1)); 59 | expect(UnitLength.dm.convertTo(UnitLength.dm, 1), equals(1)); 60 | expect(UnitLength.dm.convertTo(UnitLength.cm, 1), equals(10)); 61 | expect(UnitLength.dm.convertTo(UnitLength.mm, 1), equals(100)); 62 | expect(UnitLength.dm.convertTo(UnitLength.mic, 1), equals(100000)); 63 | expect(UnitLength.dm.convertTo(UnitLength.nm, 1), equals(1e+8)); 64 | 65 | expect(UnitLength.dm.convertTo(UnitLength.inch, 1), equals(3.93701)); 66 | expect(UnitLength.dm.convertTo(UnitLength.mi, 1), equals(6.21371e-5)); 67 | }); 68 | 69 | test('cm', () { 70 | expect(UnitLength.cm.name, equals('centimetre')); 71 | expect(UnitLength.cm.unit, equals('cm')); 72 | 73 | expect(UnitLength.nm.isMetric, isTrue); 74 | expect(UnitLength.nm.isSI, isTrue); 75 | expect(UnitLength.nm.isImperial, isFalse); 76 | 77 | expect(UnitLength.cm.convertTo(UnitLength.km, 1), equals(0.00001)); 78 | expect(UnitLength.cm.convertTo(UnitLength.m, 1), equals(0.01)); 79 | expect(UnitLength.cm.convertTo(UnitLength.dm, 1), equals(0.1)); 80 | expect(UnitLength.cm.convertTo(UnitLength.cm, 1), equals(1)); 81 | expect(UnitLength.cm.convertTo(UnitLength.mm, 1), equals(10)); 82 | expect(UnitLength.cm.convertTo(UnitLength.mic, 1), equals(10000)); 83 | expect(UnitLength.cm.convertTo(UnitLength.nm, 1), equals(1e+7)); 84 | 85 | expect(UnitLength.cm.convertTo(UnitLength.inch, 1), equals(0.393701)); 86 | expect(UnitLength.cm.convertTo(UnitLength.mi, 1), equals(6.21371e-6)); 87 | }); 88 | 89 | test('mm', () { 90 | expect(UnitLength.mm.name, equals('millimetre')); 91 | expect(UnitLength.mm.unit, equals('mm')); 92 | 93 | expect(UnitLength.nm.isMetric, isTrue); 94 | expect(UnitLength.nm.isSI, isTrue); 95 | expect(UnitLength.nm.isImperial, isFalse); 96 | 97 | expect(UnitLength.mm.convertTo(UnitLength.km, 1), equals(0.000001)); 98 | expect(UnitLength.mm.convertTo(UnitLength.m, 1), equals(0.001)); 99 | expect(UnitLength.mm.convertTo(UnitLength.dm, 1), equals(0.01)); 100 | expect(UnitLength.mm.convertTo(UnitLength.cm, 1), equals(0.1)); 101 | expect(UnitLength.mm.convertTo(UnitLength.mm, 1), equals(1)); 102 | expect(UnitLength.mm.convertTo(UnitLength.mic, 1), equals(1000)); 103 | expect(UnitLength.mm.convertTo(UnitLength.nm, 1), equals(1000000)); 104 | 105 | expect(UnitLength.mm.convertTo(UnitLength.inch, 1), equals(0.0393701)); 106 | expect(UnitLength.mm.convertTo(UnitLength.mi, 1), equals(6.21371e-7)); 107 | }); 108 | 109 | test('mic', () { 110 | expect(UnitLength.mic.name, equals('micrometre')); 111 | expect(UnitLength.mic.unit, equals('μm')); 112 | 113 | expect(UnitLength.nm.isMetric, isTrue); 114 | expect(UnitLength.nm.isSI, isTrue); 115 | expect(UnitLength.nm.isImperial, isFalse); 116 | 117 | expect(UnitLength.mic.convertTo(UnitLength.km, 1), equals(1e-9)); 118 | expect(UnitLength.mic.convertTo(UnitLength.m, 1), equals(0.000001)); 119 | expect(UnitLength.mic.convertTo(UnitLength.dm, 1), equals(0.00001)); 120 | expect(UnitLength.mic.convertTo(UnitLength.cm, 1), equals(0.0001)); 121 | expect(UnitLength.mic.convertTo(UnitLength.mm, 1), equals(0.001)); 122 | expect(UnitLength.mic.convertTo(UnitLength.mic, 1), equals(1)); 123 | expect(UnitLength.mic.convertTo(UnitLength.nm, 1), equals(1000)); 124 | 125 | expect(UnitLength.mic.convertTo(UnitLength.inch, 1), equals(3.93701e-5)); 126 | expect(UnitLength.mic.convertTo(UnitLength.mi, 1), equals(6.21371e-10)); 127 | }); 128 | 129 | test('nm', () { 130 | expect(UnitLength.nm.name, equals('nanometre')); 131 | expect(UnitLength.nm.unit, equals('nm')); 132 | 133 | expect(UnitLength.nm.isMetric, isTrue); 134 | expect(UnitLength.nm.isSI, isTrue); 135 | expect(UnitLength.nm.isImperial, isFalse); 136 | 137 | expect(UnitLength.nm.convertTo(UnitLength.km, 1), equals(1e-12)); 138 | expect(UnitLength.nm.convertTo(UnitLength.m, 1), equals(1e-9)); 139 | expect(UnitLength.nm.convertTo(UnitLength.dm, 1), equals(1e-8)); 140 | expect(UnitLength.nm.convertTo(UnitLength.cm, 1), equals(1e-7)); 141 | expect(UnitLength.nm.convertTo(UnitLength.mm, 1), equals(0.000001)); 142 | expect(UnitLength.nm.convertTo(UnitLength.mic, 1), equals(0.001)); 143 | expect(UnitLength.nm.convertTo(UnitLength.nm, 1), equals(1)); 144 | 145 | expect(UnitLength.nm.convertTo(UnitLength.inch, 1), equals(3.93701e-8)); 146 | expect(UnitLength.nm.convertTo(UnitLength.mi, 1), equals(6.21371e-13)); 147 | }); 148 | 149 | test('inch', () { 150 | expect(UnitLength.inch.name, equals('inch')); 151 | expect(UnitLength.inch.unit, equals('in')); 152 | 153 | expect(UnitLength.inch.isMetric, isFalse); 154 | expect(UnitLength.inch.isSI, isFalse); 155 | expect(UnitLength.inch.isImperial, isTrue); 156 | 157 | expect(UnitLength.inch.convertTo(UnitLength.km, 1), equals(2.54e-5)); 158 | expect(UnitLength.inch.convertTo(UnitLength.m, 1), equals(0.0254)); 159 | expect(UnitLength.inch.convertTo(UnitLength.dm, 1), equals(0.254)); 160 | expect(UnitLength.inch.convertTo(UnitLength.cm, 1), equals(2.54)); 161 | expect(UnitLength.inch.convertTo(UnitLength.mm, 1), equals(25.4)); 162 | expect(UnitLength.inch.convertTo(UnitLength.mic, 1), equals(25400)); 163 | expect(UnitLength.inch.convertTo(UnitLength.nm, 1), equals(2.54e+7)); 164 | 165 | expect(UnitLength.inch.convertTo(UnitLength.mi, 1), equals(1.57828e-5)); 166 | }); 167 | 168 | test('mile', () { 169 | expect(UnitLength.mi.name, equals('mile')); 170 | expect(UnitLength.mi.unit, equals('mi')); 171 | 172 | expect(UnitLength.mi.isMetric, isFalse); 173 | expect(UnitLength.mi.isSI, isFalse); 174 | expect(UnitLength.mi.isImperial, isTrue); 175 | 176 | expect(UnitLength.mi.convertTo(UnitLength.km, 1), equals(1.60934)); 177 | expect(UnitLength.mi.convertTo(UnitLength.m, 1), equals(1609.34)); 178 | expect(UnitLength.mi.convertTo(UnitLength.dm, 1), equals(16093.4)); 179 | expect(UnitLength.mi.convertTo(UnitLength.cm, 1), equals(160934)); 180 | expect(UnitLength.mi.convertTo(UnitLength.mm, 1), equals(1.609e+6)); 181 | expect(UnitLength.mi.convertTo(UnitLength.mic, 1), equals(1.609e+9)); 182 | expect(UnitLength.mi.convertTo(UnitLength.nm, 1), equals(1.609e+12)); 183 | 184 | expect(UnitLength.mi.convertTo(UnitLength.inch, 1), equals(63360)); 185 | }); 186 | }); 187 | } 188 | -------------------------------------------------------------------------------- /test/statistics_forecast_even_product_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:statistics/statistics.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('Forecast', () { 8 | test('forecast: EvenProduct{no phases}', () { 9 | var evenForecaster = EvenProductForecaster(); 10 | 11 | expect( 12 | evenForecaster.getPhaseOperations('').map((o) => '$o'), 13 | equals([ 14 | 'ForecastOperation{id: last_bit_A}', 15 | 'ForecastOperation{id: last_bit_B}' 16 | ])); 17 | 18 | expect(evenForecaster.getPhaseOperations('A').map((o) => '$o'), 19 | equals(['ForecastOperation{id: last_bit_A}'])); 20 | 21 | expect(evenForecaster.getPhaseOperations('B').map((o) => '$o'), 22 | equals(['ForecastOperation{id: last_bit_B}'])); 23 | 24 | var p1 = Pair(10, 2); 25 | var p2 = Pair(10, 3); 26 | var p3 = Pair(3, 3); 27 | var p4 = Pair(5, 5); 28 | var p5 = Pair(2, 4); 29 | var p6 = Pair(3, 4); 30 | 31 | expect(evenForecaster.forecast(p1), isTrue); 32 | expect(evenForecaster.forecast(p2), isTrue); 33 | expect(evenForecaster.forecast(p3), isFalse); 34 | 35 | expect(evenForecaster.allObservations.length, equals(6)); 36 | 37 | evenForecaster.concludeObservations(p1, {'EVEN': p1.productIsEven}); 38 | expect(evenForecaster.allObservations.length, equals(4)); 39 | 40 | evenForecaster.concludeObservations(p2, {'EVEN': p2.productIsEven}); 41 | expect(evenForecaster.allObservations.length, equals(2)); 42 | 43 | evenForecaster.concludeObservations(p3, {'EVEN': p3.productIsEven}); 44 | expect(evenForecaster.allObservations.length, equals(0)); 45 | 46 | expect(evenForecaster.forecast(p4), isFalse); 47 | expect(evenForecaster.forecast(p5), isTrue); 48 | expect(evenForecaster.forecast(p6), isTrue); 49 | 50 | expect(evenForecaster.allObservations.length, equals(6)); 51 | 52 | evenForecaster.concludeObservations(p4, {'EVEN': p4.productIsEven}); 53 | expect(evenForecaster.allObservations.length, equals(4)); 54 | 55 | evenForecaster.concludeObservations(p5, {'EVEN': p5.productIsEven}); 56 | expect(evenForecaster.allObservations.length, equals(2)); 57 | 58 | evenForecaster.concludeObservations(p6, {'EVEN': p6.productIsEven}); 59 | expect(evenForecaster.allObservations.length, equals(0)); 60 | 61 | var random = Random(123); 62 | 63 | for (var i = 0; i < 100000; ++i) { 64 | var p = Pair(random.nextInt(100), random.nextInt(100)); 65 | 66 | expect(evenForecaster.forecast(p), equals(p.productIsEven)); 67 | evenForecaster.concludeObservations(p, {'EVEN': p.productIsEven}); 68 | } 69 | 70 | expect(evenForecaster.allObservations.length, equals(0)); 71 | 72 | { 73 | var p = Pair(random.nextInt(100), random.nextInt(100)); 74 | expect(evenForecaster.forecast(p), equals(p.productIsEven)); 75 | } 76 | 77 | expect(evenForecaster.allObservations.length, equals(2)); 78 | 79 | evenForecaster.disposeObservations(); 80 | expect(evenForecaster.allObservations.length, equals(0)); 81 | 82 | _testEvenProductProbabilities(evenForecaster.eventMonitor, 83 | populateDependencies: false, withPhases: false); 84 | _testEvenProductProbabilities(evenForecaster.eventMonitor, 85 | populateDependencies: true, withPhases: false); 86 | }); 87 | 88 | test('forecast: EvenProduct{phases: [A, B]}', () { 89 | var evenForecaster = EvenProductForecaster(); 90 | 91 | var p1 = Pair(10, 2); 92 | var p2 = Pair(10, 3); 93 | var p3 = Pair(3, 3); 94 | var p4 = Pair(5, 5); 95 | var p5 = Pair(2, 4); 96 | var p6 = Pair(3, 4); 97 | 98 | expect(evenForecaster.forecast(p1, phase: 'A'), isTrue); 99 | expect(evenForecaster.forecast(p2, phase: 'A'), isTrue); 100 | expect(evenForecaster.forecast(p3, phase: 'A'), isFalse); 101 | expect(evenForecaster.forecast(p4, phase: 'A'), isFalse); 102 | expect(evenForecaster.forecast(p5, phase: 'A'), isTrue); 103 | expect(evenForecaster.forecast(p6, phase: 'A'), isFalse); 104 | 105 | expect(evenForecaster.allObservations.length, equals(6)); 106 | 107 | expect(evenForecaster.forecast(p1, phase: 'B', previousPhases: {'A'}), 108 | isTrue); 109 | expect(evenForecaster.forecast(p2, phase: 'B', previousPhases: {'A'}), 110 | isTrue); 111 | expect(evenForecaster.forecast(p3, phase: 'B', previousPhases: {'A'}), 112 | isFalse); 113 | expect(evenForecaster.forecast(p4, phase: 'B', previousPhases: {'A'}), 114 | isFalse); 115 | expect(evenForecaster.forecast(p5, phase: 'B', previousPhases: {'A'}), 116 | isTrue); 117 | expect(evenForecaster.forecast(p6, phase: 'B', previousPhases: {'A'}), 118 | isTrue); 119 | 120 | evenForecaster.concludeObservations(p1, {'EVEN': p1.productIsEven}); 121 | evenForecaster.concludeObservations(p2, {'EVEN': p2.productIsEven}); 122 | evenForecaster.concludeObservations(p3, {'EVEN': p3.productIsEven}); 123 | evenForecaster.concludeObservations(p4, {'EVEN': p4.productIsEven}); 124 | evenForecaster.concludeObservations(p5, {'EVEN': p5.productIsEven}); 125 | evenForecaster.concludeObservations(p6, {'EVEN': p6.productIsEven}); 126 | 127 | var random = Random(123); 128 | 129 | for (var i = 0; i < 100000; ++i) { 130 | var p = Pair(random.nextInt(100), random.nextInt(100)); 131 | 132 | evenForecaster.forecast(p, phase: 'A'); 133 | evenForecaster.forecast(p, phase: 'B', previousPhases: {'A'}); 134 | evenForecaster.concludeObservations(p, {'EVEN': p.productIsEven}); 135 | } 136 | 137 | _testEvenProductProbabilities(evenForecaster.eventMonitor, 138 | populateDependencies: false, withPhases: true); 139 | _testEvenProductProbabilities(evenForecaster.eventMonitor, 140 | populateDependencies: true, withPhases: true); 141 | }); 142 | }); 143 | } 144 | 145 | void _testEvenProductProbabilities(BayesEventMonitor eventMonitor, 146 | {required bool populateDependencies, required withPhases}) { 147 | print('----------------'); 148 | print(eventMonitor); 149 | 150 | var bayesNet = eventMonitor.buildBayesianNetwork( 151 | unseenMinimalProbability: 0.0, 152 | populateDependencies: populateDependencies); 153 | print(bayesNet); 154 | 155 | var phaseA = withPhases ? 'A.' : ''; 156 | var phaseB = withPhases ? '.' : ''; 157 | 158 | var analyser = bayesNet.analyser; 159 | 160 | expect(analyser.showAnswer('P(EVEN)').probability, _isNear(0.75, 0.01)); 161 | expect(analyser.showAnswer('P(-EVEN)').probability, _isNear(0.25, 0.01)); 162 | 163 | expect(analyser.showAnswer('P(EVEN|${phaseA}LAST_BIT_A)').probability, 164 | _isNear(0.50, 0.01)); 165 | expect(analyser.showAnswer('P(EVEN|-${phaseA}LAST_BIT_A)').probability, 166 | _isNear(1.0)); 167 | 168 | expect(analyser.showAnswer('P(EVEN|${phaseB}LAST_BIT_B)').probability, 169 | _isNear(0.50, 0.01)); 170 | expect(analyser.showAnswer('P(EVEN|-${phaseB}LAST_BIT_B)').probability, 171 | _isNear(1.0)); 172 | 173 | expect( 174 | analyser 175 | .showAnswer('P(EVEN|-${phaseA}LAST_BIT_A,-${phaseB}LAST_BIT_B)') 176 | .probability, 177 | _isNear(1.0)); 178 | } 179 | 180 | class EvenProductForecaster extends EventForecaster, bool, bool> { 181 | EvenProductForecaster() : super('EvenProduct'); 182 | 183 | @override 184 | List, bool>> generatePhaseOperations( 185 | String phase) { 186 | var opA = ObservationOperation, bool>('last_bit(A)', 187 | computer: _extractLastBitA); 188 | 189 | var opB = ObservationOperation, bool>('last_bit(B)', 190 | computer: _extractLastBitB); 191 | 192 | switch (phase) { 193 | case '': 194 | return [opA, opB]; 195 | case 'A': 196 | return [opA]; 197 | case 'B': 198 | return [opB]; 199 | default: 200 | throw StateError('Unknown phase: $phase'); 201 | } 202 | } 203 | 204 | static bool _extractLastBitA(Pair ns) => _extractBit(ns.a, 0); 205 | 206 | static bool _extractLastBitB(Pair ns) => _extractBit(ns.b, 0); 207 | 208 | static bool _extractBit(num n, int bit) { 209 | var i = n.toInt(); 210 | return (i >> bit) & 0x01 == 1; 211 | } 212 | 213 | @override 214 | bool computeForecast( 215 | String phase, List, bool>> observations) { 216 | var opLastBitA = observations.getByOpID('last_bit(A)')!; 217 | var opLastBitB = observations.getByOpID('last_bit(B)'); 218 | 219 | if (opLastBitB == null) { 220 | return !opLastBitA.value; 221 | } else { 222 | return !opLastBitA.value || !opLastBitB.value; 223 | } 224 | } 225 | } 226 | 227 | extension _PairExtension on Pair { 228 | int get product => a * b; 229 | 230 | bool get productIsEven => product % 2 == 0; 231 | } 232 | 233 | Matcher _isNear(double value, [double tolerance = 0.0001]) => 234 | inInclusiveRange(value - tolerance, value + tolerance); 235 | -------------------------------------------------------------------------------- /test/statistics_prime_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:statistics/statistics.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | const primes = [ 6 | 2, 7 | 3, 8 | 5, 9 | 7, 10 | 11, 11 | 13, 12 | 17, 13 | 19, 14 | 23, 15 | 29, 16 | 31, 17 | 37, 18 | 41, 19 | 43, 20 | 47, 21 | 53, 22 | 59, 23 | 61, 24 | 67, 25 | 71, 26 | 73, 27 | 79, 28 | 83, 29 | 89, 30 | 97, 31 | 101, 32 | 103, 33 | 107, 34 | 109, 35 | 113, 36 | 127, 37 | 131, 38 | 137, 39 | 139, 40 | 149, 41 | 151, 42 | 157, 43 | 163, 44 | 167, 45 | 173, 46 | 179, 47 | 181, 48 | 191, 49 | 193, 50 | 197, 51 | 199, 52 | 211, 53 | 223, 54 | 227, 55 | 229, 56 | 233, 57 | 239, 58 | 241, 59 | 251, 60 | 257, 61 | 263, 62 | 269, 63 | 271, 64 | 277, 65 | 281, 66 | 283, 67 | 293, 68 | 307, 69 | 311, 70 | 313, 71 | 317, 72 | 331, 73 | 337, 74 | 347, 75 | 349, 76 | 353, 77 | 359, 78 | 367, 79 | 373, 80 | 379, 81 | 383, 82 | 389, 83 | 397, 84 | 401, 85 | 409, 86 | 419, 87 | 421, 88 | 431, 89 | 433, 90 | 439, 91 | 443, 92 | 449, 93 | 457, 94 | 461, 95 | 463, 96 | 467, 97 | 479, 98 | 487, 99 | 491, 100 | 499, 101 | 503, 102 | 509, 103 | 521, 104 | 523, 105 | 541, 106 | 547, 107 | 557, 108 | 563, 109 | 569, 110 | 571, 111 | 577, 112 | 587, 113 | 593, 114 | 599, 115 | 601, 116 | 607, 117 | 613, 118 | 617, 119 | 619, 120 | 631, 121 | 641, 122 | 643, 123 | 647, 124 | 653, 125 | 659, 126 | 661, 127 | 673, 128 | 677, 129 | 683, 130 | 691, 131 | 701, 132 | 709, 133 | 719, 134 | 727, 135 | 733, 136 | 739, 137 | 743, 138 | 751, 139 | 757, 140 | 761, 141 | 769, 142 | 773, 143 | 787, 144 | 797, 145 | 809, 146 | 811, 147 | 821, 148 | 823, 149 | 827, 150 | 829, 151 | 839, 152 | 853, 153 | 857, 154 | 859, 155 | 863, 156 | 877, 157 | 881, 158 | 883, 159 | 887, 160 | 907, 161 | 911, 162 | 919, 163 | 929, 164 | 937, 165 | 941, 166 | 947, 167 | 953, 168 | 967, 169 | 971, 170 | 977, 171 | 983, 172 | 991, 173 | 997, 174 | 1009, 175 | 1013, 176 | 1019, 177 | 1021, 178 | 1031, 179 | 1033, 180 | 1039, 181 | 1049, 182 | 1051, 183 | 1061, 184 | 1063, 185 | 1069, 186 | 1087, 187 | 1091, 188 | 1093, 189 | 1097, 190 | 1103, 191 | 1109, 192 | 1117, 193 | 1123, 194 | 1129, 195 | 1151, 196 | 1153, 197 | 1163, 198 | 1171, 199 | 1181, 200 | 1187, 201 | 1193, 202 | 1201, 203 | 1213, 204 | 1217, 205 | 1223, 206 | ]; 207 | 208 | void main() { 209 | group('Prime', () { 210 | setUp(() {}); 211 | 212 | test('basic', () { 213 | expect(PrimeUtils.lastKnownPrime, equals(PrimeUtils.knownPrimes.last)); 214 | 215 | expect(11.whenPrime('p', 'n'), equals('p')); 216 | expect(12.whenPrime('p', 'n'), equals('n')); 217 | 218 | expect(11.toDynamicInt().whenPrime('p', 'n'), equals('p')); 219 | expect(12.toDynamicInt().whenPrime('p', 'n'), equals('n')); 220 | 221 | expect(11.toDecimal().whenPrime('p', 'n'), equals('p')); 222 | expect(12.toDecimal().whenPrime('p', 'n'), equals('n')); 223 | expect(11.0.toDecimal().whenPrime('p', 'n'), equals('p')); 224 | expect(11.5.toDecimal().whenPrime('p', 'n'), equals('n')); 225 | 226 | print('PrimeUtils.knownPrimesLength: ${PrimeUtils.knownPrimesLength}'); 227 | PrimeUtils.contractKnownPrimes(25); 228 | expect(PrimeUtils.lastKnownPrime, equals(97)); 229 | 230 | { 231 | var np = 97 + 2; 232 | expect((np).toDynamicInt().whenPrime('p', 'n'), equals('n'), 233 | reason: 'n: $np'); 234 | } 235 | 236 | { 237 | var np = 97 + 4; 238 | expect((np).toDynamicInt().whenPrime('p', 'n'), equals('p'), 239 | reason: 'n: $np'); 240 | } 241 | 242 | PrimeUtils.contractKnownPrimes(6); 243 | expect(PrimeUtils.lastKnownPrime, equals(13)); 244 | }); 245 | 246 | test('PrimeUtils.generatePrimes', () { 247 | expect(PrimeUtils.generatePrimes(-1), isEmpty); 248 | expect(PrimeUtils.generatePrimes(0), isEmpty); 249 | expect(PrimeUtils.generatePrimes(1), equals([2])); 250 | expect(PrimeUtils.generatePrimes(2), equals([2, 3])); 251 | expect(PrimeUtils.generatePrimes(3), equals([2, 3, 5])); 252 | expect(PrimeUtils.generatePrimes(4), equals([2, 3, 5, 7])); 253 | expect(PrimeUtils.generatePrimes(5), equals([2, 3, 5, 7, 11])); 254 | 255 | expect(PrimeUtils.generatePrimes(primes.length), equals(primes)); 256 | }); 257 | 258 | test('int', () { 259 | expect((-3).isPrime, isFalse); 260 | expect((-2).isPrime, isFalse); 261 | expect((-1).isPrime, isFalse); 262 | expect(0.isPrime, isFalse); 263 | expect(1.isPrime, isFalse); 264 | expect(2.isPrime, isTrue); 265 | expect(3.isPrime, isTrue); 266 | expect(4.isPrime, isFalse); 267 | expect(5.isPrime, isTrue); 268 | 269 | int nComparator(int a, int b) => a.compareTo(b); 270 | 271 | for (var n = 0; n < primes.last; ++n) { 272 | if (primes.binarySearch(n, nComparator) >= 0) { 273 | expect(n.isPrime, isTrue, reason: "$n is prime!"); 274 | } else { 275 | expect(n.isPrime, isFalse, reason: "$n is NOT prime!"); 276 | } 277 | } 278 | 279 | expect(Statistics.maxSafeInt.isPrime, isFalse, 280 | reason: "${Statistics.maxSafeInt} is NOT prime!"); 281 | 282 | var mersennePrime2 = 283 | 2.toDynamicInt().powerInt(31).toDynamicInt().subtractInt(1).toInt(); 284 | print('mersennePrime2: $mersennePrime2'); 285 | 286 | expect(mersennePrime2.isPrime, isTrue, 287 | reason: "`$mersennePrime2`is a Mersenne prime!"); 288 | expect((mersennePrime2 - 2).isPrime, isFalse, 289 | reason: "`$mersennePrime2 - 2`is a NOT prime!"); 290 | }); 291 | 292 | test('DynamicInt', () { 293 | expect((-3).toDynamicInt().isPrime, isFalse); 294 | expect((-2).toDynamicInt().isPrime, isFalse); 295 | expect((-1).toDynamicInt().isPrime, isFalse); 296 | expect(0.toDynamicInt().isPrime, isFalse); 297 | expect(1.toDynamicInt().isPrime, isFalse); 298 | expect(2.toDynamicInt().isPrime, isTrue); 299 | expect(3.toDynamicInt().isPrime, isTrue); 300 | expect(4.toDynamicInt().isPrime, isFalse); 301 | expect(5.toDynamicInt().isPrime, isTrue); 302 | 303 | var primesDN = primes.toDynamicIntList(); 304 | 305 | int nComparator(DynamicInt a, DynamicInt b) => a.compareTo(b); 306 | 307 | for (var n = DynamicInt.zero; n < primesDN.last; n = n.sumInt(1)) { 308 | if (primesDN.binarySearch(n, nComparator) >= 0) { 309 | expect(n.isPrime, isTrue, reason: "$n is prime!"); 310 | } else { 311 | expect(n.isPrime, isFalse, reason: "$n is NOT prime!"); 312 | } 313 | } 314 | 315 | var maxSafeDN = Statistics.maxSafeInt.toDynamicInt(); 316 | print('maxSafeDN: $maxSafeDN'); 317 | 318 | expect(maxSafeDN.isPrime, isFalse, 319 | reason: "${Statistics.maxSafeInt} is NOT prime!"); 320 | 321 | var bigDN = maxSafeDN.multiplyInt(100000000000000); 322 | 323 | print('bigDN: $bigDN'); 324 | 325 | expect(bigDN.isPrime, isFalse, 326 | reason: "${Statistics.maxSafeInt} is NOT prime!"); 327 | 328 | expect(bigDN.sumInt(1).isPrime, isFalse, 329 | reason: "${Statistics.maxSafeInt} is NOT prime!"); 330 | 331 | var mersennePrime1 = 332 | 2.toDynamicInt().powerInt(17).toDynamicInt().subtractInt(1); 333 | print('mersennePrime1: $mersennePrime1'); 334 | 335 | expect(mersennePrime1.isPrime, isTrue, 336 | reason: "`$mersennePrime1`is a Mersenne prime!"); 337 | expect(mersennePrime1.subtractInt(2).isPrime, isFalse, 338 | reason: "`$mersennePrime1 - 2`is NOT prime!"); 339 | 340 | var mersennePrime2 = 341 | 2.toDynamicInt().powerInt(31).toDynamicInt().subtractInt(1); 342 | print('mersennePrime2: $mersennePrime2'); 343 | 344 | expect(mersennePrime2.isPrime, isTrue, 345 | reason: "`$mersennePrime2`is a Mersenne prime!"); 346 | expect(mersennePrime2.subtractInt(2).isPrime, isFalse, 347 | reason: "`$mersennePrime2 - 2`is a NOT prime!"); 348 | 349 | print('PrimeUtils.knownPrimesLength: ${PrimeUtils.knownPrimesLength}'); 350 | PrimeUtils.expandKnownPrimes(10000); 351 | 352 | print('PrimeUtils.knownPrimesLength: ${PrimeUtils.knownPrimesLength}'); 353 | expect(PrimeUtils.knownPrimesLength, greaterThanOrEqualTo(10000)); 354 | 355 | PrimeUtils.contractKnownPrimes(9000); 356 | 357 | print('PrimeUtils.knownPrimesLength: ${PrimeUtils.knownPrimesLength}'); 358 | expect(PrimeUtils.knownPrimesLength, greaterThanOrEqualTo(9000)); 359 | 360 | void testMersennePrime(int mersennePower) { 361 | var initTime = DateTime.now(); 362 | 363 | var mersennePrime = 2 364 | .toDynamicInt() 365 | .powerInt(mersennePower) 366 | .toDynamicInt() 367 | .subtractInt(1); 368 | 369 | var s = '(2^$mersennePower - 1 = $mersennePrime)'; 370 | 371 | print('mersennePrime: $s'); 372 | 373 | expect(mersennePrime.isPrime, isTrue, 374 | reason: "`$s`is a Mersenne prime!"); 375 | 376 | expect(mersennePrime.subtractInt(1).isPrime, isFalse, 377 | reason: "`$s - 1`is NOT prime!"); 378 | 379 | expect(mersennePrime.subtractInt(2).isPrime, isFalse, 380 | reason: "`$s - 2`is NOT prime!"); 381 | 382 | var time = DateTime.now().difference(initTime); 383 | 384 | print('mersennePrime time: ${time.inMilliseconds}ms'); 385 | } 386 | 387 | testMersennePrime(7); 388 | testMersennePrime(13); 389 | testMersennePrime(17); 390 | testMersennePrime(19); 391 | testMersennePrime(31); 392 | 393 | if (DataSerializerPlatform.instance.supportsFullInt64) { 394 | testMersennePrime(61); 395 | } 396 | }); 397 | }); 398 | } 399 | -------------------------------------------------------------------------------- /lib/src/statistics_metric.dart: -------------------------------------------------------------------------------- 1 | /// Unit for lengths. 2 | enum UnitLength { 3 | km, 4 | m, 5 | dm, 6 | cm, 7 | mm, 8 | mic, 9 | nm, 10 | inch, 11 | mi, 12 | } 13 | 14 | /// Extension for [UnitLength] enum. 15 | extension UnitLengthExtension on UnitLength { 16 | /// Converts [value] from this [UnitLength] to [targetUnit]. 17 | num convertTo(UnitLength targetUnit, num value) { 18 | if (this == targetUnit) return value; 19 | 20 | switch (this) { 21 | case UnitLength.km: 22 | { 23 | switch (targetUnit) { 24 | case UnitLength.m: 25 | return value * 1000; 26 | case UnitLength.dm: 27 | return value * 10000; 28 | case UnitLength.cm: 29 | return value * 100000; 30 | case UnitLength.mm: 31 | return value * 1000000; 32 | case UnitLength.mic: 33 | return value * 1e+9; 34 | case UnitLength.nm: 35 | return value * 1e+12; 36 | 37 | case UnitLength.inch: 38 | return value * 39370.1; 39 | case UnitLength.mi: 40 | return value * 0.62137119224; 41 | 42 | default: 43 | break; 44 | } 45 | break; 46 | } 47 | case UnitLength.m: 48 | { 49 | switch (targetUnit) { 50 | case UnitLength.km: 51 | return value * 0.001; 52 | case UnitLength.dm: 53 | return value * 10; 54 | case UnitLength.cm: 55 | return value * 100; 56 | case UnitLength.mm: 57 | return value * 1000; 58 | case UnitLength.mic: 59 | return value * 1000000; 60 | case UnitLength.nm: 61 | return value * 1e+9; 62 | 63 | case UnitLength.inch: 64 | return value * 39.3701; 65 | case UnitLength.mi: 66 | return value * 0.00062137119224; 67 | 68 | default: 69 | break; 70 | } 71 | break; 72 | } 73 | case UnitLength.dm: 74 | { 75 | switch (targetUnit) { 76 | case UnitLength.km: 77 | return value * 0.0001; 78 | case UnitLength.m: 79 | return value * 0.1; 80 | case UnitLength.cm: 81 | return value * 10; 82 | case UnitLength.mm: 83 | return value * 100; 84 | case UnitLength.mic: 85 | return value * 100000; 86 | case UnitLength.nm: 87 | return value * 1e+8; 88 | 89 | case UnitLength.inch: 90 | return value * 3.93701; 91 | case UnitLength.mi: 92 | return value * 6.21371e-5; 93 | 94 | default: 95 | break; 96 | } 97 | break; 98 | } 99 | case UnitLength.cm: 100 | { 101 | switch (targetUnit) { 102 | case UnitLength.km: 103 | return value * 1e-5; 104 | case UnitLength.m: 105 | return value * 0.01; 106 | case UnitLength.dm: 107 | return value * 0.1; 108 | case UnitLength.mm: 109 | return value * 10; 110 | case UnitLength.mic: 111 | return value * 10000; 112 | case UnitLength.nm: 113 | return value * 1e+7; 114 | 115 | case UnitLength.inch: 116 | return value * 0.393701; 117 | case UnitLength.mi: 118 | return value * 6.21371e-6; 119 | 120 | default: 121 | break; 122 | } 123 | break; 124 | } 125 | case UnitLength.mm: 126 | { 127 | switch (targetUnit) { 128 | case UnitLength.km: 129 | return value * 1e-6; 130 | case UnitLength.m: 131 | return value * 0.001; 132 | case UnitLength.dm: 133 | return value * 0.01; 134 | case UnitLength.cm: 135 | return value * 0.1; 136 | case UnitLength.mic: 137 | return value * 1000; 138 | case UnitLength.nm: 139 | return value * 1000000; 140 | 141 | case UnitLength.inch: 142 | return value * 0.0393701; 143 | case UnitLength.mi: 144 | return value * 6.21371e-7; 145 | 146 | default: 147 | break; 148 | } 149 | break; 150 | } 151 | case UnitLength.mic: 152 | { 153 | switch (targetUnit) { 154 | case UnitLength.km: 155 | return value * 1e-9; 156 | case UnitLength.m: 157 | return value * 1e-6; 158 | case UnitLength.dm: 159 | return value * 1e-5; 160 | case UnitLength.cm: 161 | return value * 1e-4; 162 | case UnitLength.mm: 163 | return value * 0.001; 164 | case UnitLength.nm: 165 | return value * 1000; 166 | 167 | case UnitLength.inch: 168 | return value * 3.93701e-5; 169 | case UnitLength.mi: 170 | return value * 6.21371e-10; 171 | 172 | default: 173 | break; 174 | } 175 | break; 176 | } 177 | case UnitLength.nm: 178 | { 179 | switch (targetUnit) { 180 | case UnitLength.km: 181 | return value * 1e-12; 182 | case UnitLength.m: 183 | return value * 1e-9; 184 | case UnitLength.dm: 185 | return value * 1e-8; 186 | case UnitLength.cm: 187 | return value * 1e-7; 188 | case UnitLength.mm: 189 | return value * 1e-6; 190 | case UnitLength.mic: 191 | return value * 0.001; 192 | 193 | case UnitLength.inch: 194 | return value * 3.93701e-8; 195 | case UnitLength.mi: 196 | return value * 6.21371e-13; 197 | 198 | default: 199 | break; 200 | } 201 | break; 202 | } 203 | 204 | case UnitLength.inch: 205 | { 206 | switch (targetUnit) { 207 | case UnitLength.km: 208 | return value * 2.54e-5; 209 | case UnitLength.m: 210 | return value * 0.0254; 211 | case UnitLength.dm: 212 | return value * 0.254; 213 | case UnitLength.cm: 214 | return value * 2.54; 215 | case UnitLength.mm: 216 | return value * 25.4; 217 | case UnitLength.mic: 218 | return value * 25400; 219 | case UnitLength.nm: 220 | return value * 2.54e+7; 221 | 222 | case UnitLength.mi: 223 | return value * 1.57828e-5; 224 | 225 | default: 226 | break; 227 | } 228 | break; 229 | } 230 | 231 | case UnitLength.mi: 232 | { 233 | switch (targetUnit) { 234 | case UnitLength.km: 235 | return value * 1.60934; 236 | case UnitLength.m: 237 | return value * 1609.34; 238 | case UnitLength.dm: 239 | return value * 16093.4; 240 | case UnitLength.cm: 241 | return value * 160934; 242 | case UnitLength.mm: 243 | return value * 1.609e+6; 244 | case UnitLength.mic: 245 | return value * 1.609e+9; 246 | case UnitLength.nm: 247 | return value * 1.609e+12; 248 | 249 | case UnitLength.inch: 250 | return value * 63360; 251 | 252 | default: 253 | break; 254 | } 255 | break; 256 | } 257 | } 258 | 259 | throw StateError('Unknown conversion: $this -> $targetUnit'); 260 | } 261 | 262 | /// Returns `true` if this unit is a International System of Units (SI), 263 | /// commonly known as the Metric System. 264 | bool get isSI { 265 | switch (this) { 266 | case UnitLength.km: 267 | case UnitLength.m: 268 | case UnitLength.dm: 269 | case UnitLength.cm: 270 | case UnitLength.mm: 271 | case UnitLength.nm: 272 | case UnitLength.mic: 273 | return true; 274 | default: 275 | return false; 276 | } 277 | } 278 | 279 | /// Returns `true` if this unit is a Metric System Unit 280 | /// (officially called International System of Units - SI). 281 | /// 282 | /// - Same as [isSI] 283 | bool get isMetric => isSI; 284 | 285 | /// Returns `true` if this unit is a Imperial System of Units. 286 | bool get isImperial { 287 | switch (this) { 288 | case UnitLength.inch: 289 | case UnitLength.mi: 290 | return true; 291 | default: 292 | return false; 293 | } 294 | } 295 | 296 | /// Returns the name of this unit. 297 | String get name { 298 | switch (this) { 299 | case UnitLength.km: 300 | return 'kilometre'; 301 | case UnitLength.m: 302 | return 'metre'; 303 | case UnitLength.dm: 304 | return 'decimetre'; 305 | case UnitLength.cm: 306 | return 'centimetre'; 307 | case UnitLength.mm: 308 | return 'millimetre'; 309 | case UnitLength.nm: 310 | return 'nanometre'; 311 | case UnitLength.mic: 312 | return 'micrometre'; 313 | case UnitLength.inch: 314 | return 'inch'; 315 | case UnitLength.mi: 316 | return 'mile'; 317 | } 318 | } 319 | 320 | /// Returns the abbreviation of this unit. 321 | String get unit { 322 | switch (this) { 323 | case UnitLength.km: 324 | return 'km'; 325 | case UnitLength.m: 326 | return 'm'; 327 | case UnitLength.dm: 328 | return 'dm'; 329 | case UnitLength.cm: 330 | return 'cm'; 331 | case UnitLength.mm: 332 | return 'mm'; 333 | case UnitLength.nm: 334 | return 'nm'; 335 | case UnitLength.mic: 336 | return 'μm'; 337 | case UnitLength.inch: 338 | return 'in'; 339 | case UnitLength.mi: 340 | return 'mi'; 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /test/statistics_combination_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('Combinations', () { 6 | test('CombinationCache', () { 7 | var cache = CombinationCache(allowRepetition: false); 8 | 9 | var alphabet = {0, 1}; 10 | 11 | expect(cache.totalCachedCombinations, equals(0)); 12 | expect(cache.computedCombinations, equals(0)); 13 | 14 | expect( 15 | cache.getCombinations(alphabet, 1, 1), 16 | equals([ 17 | [0], 18 | [1] 19 | ])); 20 | 21 | expect(cache.totalCachedCombinations, equals(1)); 22 | expect(cache.computedCombinations, equals(1)); 23 | 24 | expect( 25 | cache.getCombinations(alphabet, 1, 1), 26 | equals([ 27 | [0], 28 | [1] 29 | ])); 30 | 31 | expect(cache.totalCachedCombinations, equals(1)); 32 | expect(cache.computedCombinations, equals(1)); 33 | 34 | expect(() => cache.getCombinationsShared(alphabet, 1, 1), 35 | throwsA(isA())); 36 | 37 | expect( 38 | cache.getCombinations(alphabet, 1, 2), 39 | equals([ 40 | [0], 41 | [1], 42 | [0, 1] 43 | ])); 44 | 45 | expect(cache.totalCachedCombinations, equals(2)); 46 | expect(cache.computedCombinations, equals(2)); 47 | 48 | expect( 49 | cache.getCombinations(alphabet, 1, 1), 50 | equals([ 51 | [0], 52 | [1] 53 | ])); 54 | 55 | expect(cache.totalCachedCombinations, equals(2)); 56 | expect(cache.computedCombinations, equals(2)); 57 | 58 | expect( 59 | cache.getCombinations(alphabet, 1, 2), 60 | equals([ 61 | [0], 62 | [1], 63 | [0, 1] 64 | ])); 65 | 66 | expect(cache.totalCachedCombinations, equals(2)); 67 | expect(cache.computedCombinations, equals(2)); 68 | 69 | cache.clear(); 70 | 71 | expect(cache.totalCachedCombinations, equals(0)); 72 | expect(cache.computedCombinations, equals(2)); 73 | 74 | expect( 75 | cache.getCombinations(alphabet, 1, 2), 76 | equals([ 77 | [0], 78 | [1], 79 | [0, 1] 80 | ])); 81 | 82 | expect(cache.totalCachedCombinations, equals(1)); 83 | expect(cache.computedCombinations, equals(3)); 84 | }); 85 | 86 | test('CombinationCache: shared', () { 87 | var cache = CombinationCache( 88 | allowRepetition: false, allowSharedCombinations: true); 89 | 90 | var alphabet = {0, 1}; 91 | 92 | expect(cache.totalCachedCombinations, equals(0)); 93 | expect(cache.computedCombinations, equals(0)); 94 | 95 | expect( 96 | cache.getCombinationsShared(alphabet, 1, 1), 97 | equals([ 98 | [0], 99 | [1] 100 | ])); 101 | 102 | expect(cache.totalCachedCombinations, equals(1)); 103 | expect(cache.computedCombinations, equals(1)); 104 | 105 | expect( 106 | cache.getCombinationsShared(alphabet, 1, 1), 107 | equals([ 108 | [0], 109 | [1] 110 | ])); 111 | 112 | expect(cache.totalCachedCombinations, equals(1)); 113 | expect(cache.computedCombinations, equals(1)); 114 | 115 | cache.getCombinationsShared(alphabet, 1, 1)[1][0] = 10; 116 | 117 | expect( 118 | cache.getCombinationsShared(alphabet, 1, 1), 119 | equals([ 120 | [0], 121 | [10] 122 | ])); 123 | 124 | expect(cache.totalCachedCombinations, equals(1)); 125 | expect(cache.computedCombinations, equals(1)); 126 | }); 127 | 128 | test('error: invalid alphabet', () { 129 | expect(() => generateCombinations([10, 10], 2, 2, allowRepetition: false), 130 | throwsA(isA())); 131 | }); 132 | 133 | test('[0,1]', () { 134 | var alphabet = [0, 1]; 135 | 136 | expect(generateCombinations(alphabet, 0, 0), equals([])); 137 | 138 | expect( 139 | generateCombinations(alphabet, 1, 1, allowRepetition: false), 140 | equals([ 141 | [0], 142 | [1] 143 | ])); 144 | 145 | expect( 146 | generateCombinations(alphabet, 1, 1, allowRepetition: true), 147 | equals([ 148 | [0], 149 | [1] 150 | ])); 151 | 152 | expect( 153 | generateCombinations(alphabet, 2, 2, allowRepetition: false), 154 | equals([ 155 | [0, 1] 156 | ])); 157 | 158 | expect( 159 | generateCombinations(alphabet, 2, 2, allowRepetition: true), 160 | equals([ 161 | [0, 0], 162 | [0, 1], 163 | [1, 0], 164 | [1, 1] 165 | ])); 166 | 167 | expect( 168 | generateCombinations(alphabet, 2, 3, allowRepetition: false), 169 | equals([ 170 | [0, 1] 171 | ])); 172 | 173 | expect(generateCombinations(alphabet, 3, 3, allowRepetition: false), 174 | equals([])); 175 | 176 | expect( 177 | generateCombinations(alphabet, 3, 3, allowRepetition: true), 178 | equals([ 179 | [0, 0, 0], 180 | [0, 0, 1], 181 | [0, 1, 0], 182 | [0, 1, 1], 183 | [1, 0, 0], 184 | [1, 0, 1], 185 | [1, 1, 0], 186 | [1, 1, 1] 187 | ])); 188 | 189 | expect(alphabet.combinations(3, 3, allowRepetition: true), 190 | equals(generateCombinations(alphabet, 3, 3, allowRepetition: true))); 191 | }); 192 | 193 | test('[a,b,c]', () { 194 | var alphabet = ['a', 'b', 'c']; 195 | 196 | expect(generateCombinations(alphabet, 0, 0), equals([])); 197 | 198 | expect( 199 | generateCombinations(alphabet, 1, 1, allowRepetition: false), 200 | equals([ 201 | ['a'], 202 | ['b'], 203 | ['c'] 204 | ])); 205 | 206 | expect( 207 | generateCombinations(alphabet, 1, 1, allowRepetition: true), 208 | equals([ 209 | ['a'], 210 | ['b'], 211 | ['c'] 212 | ])); 213 | 214 | expect( 215 | generateCombinations(alphabet.map((e) => e.toUpperCase()), 2, 2, 216 | allowRepetition: false), 217 | equals([ 218 | ['A', 'B'], 219 | ['A', 'C'], 220 | ['B', 'C'] 221 | ])); 222 | 223 | expect( 224 | generateCombinations(alphabet, 2, 2, allowRepetition: false), 225 | equals([ 226 | ['a', 'b'], 227 | ['a', 'c'], 228 | ['b', 'c'] 229 | ])); 230 | 231 | expect( 232 | generateCombinations(alphabet, 2, 2, allowRepetition: true), 233 | equals([ 234 | ['a', 'a'], 235 | ['a', 'b'], 236 | ['a', 'c'], 237 | ['b', 'a'], 238 | ['b', 'b'], 239 | ['b', 'c'], 240 | ['c', 'a'], 241 | ['c', 'b'], 242 | ['c', 'c'] 243 | ])); 244 | 245 | expect( 246 | generateCombinations(alphabet, 3, 3, allowRepetition: false), 247 | equals([ 248 | ['a', 'b', 'c'] 249 | ])); 250 | 251 | expect( 252 | generateCombinations(alphabet, 3, 3, allowRepetition: true), 253 | equals([ 254 | ['a', 'a', 'a'], 255 | ['a', 'a', 'b'], 256 | ['a', 'a', 'c'], 257 | ['a', 'b', 'a'], 258 | ['a', 'b', 'b'], 259 | ['a', 'b', 'c'], 260 | ['a', 'c', 'a'], 261 | ['a', 'c', 'b'], 262 | ['a', 'c', 'c'], 263 | ['b', 'a', 'a'], 264 | ['b', 'a', 'b'], 265 | ['b', 'a', 'c'], 266 | ['b', 'b', 'a'], 267 | ['b', 'b', 'b'], 268 | ['b', 'b', 'c'], 269 | ['b', 'c', 'a'], 270 | ['b', 'c', 'b'], 271 | ['b', 'c', 'c'], 272 | ['c', 'a', 'a'], 273 | ['c', 'a', 'b'], 274 | ['c', 'a', 'c'], 275 | ['c', 'b', 'a'], 276 | ['c', 'b', 'b'], 277 | ['c', 'b', 'c'], 278 | ['c', 'c', 'a'], 279 | ['c', 'c', 'b'], 280 | ['c', 'c', 'c'], 281 | ])); 282 | 283 | expect(alphabet.combinations(3, 3, allowRepetition: true), 284 | equals(generateCombinations(alphabet, 3, 3, allowRepetition: true))); 285 | }); 286 | 287 | test('with mapper', () { 288 | var alphabet = [MapEntry('A', Pair(0, 1)), MapEntry('B', Pair('T', 'F'))]; 289 | List mapper(MapEntry e) => 290 | ['${e.key}:${e.value.a}', '${e.key}:${e.value.b}']; 291 | 292 | expect(generateCombinations(alphabet, 0, 0, mapper: mapper), equals([])); 293 | 294 | expect( 295 | generateCombinations, String>(alphabet, 1, 1, 296 | allowRepetition: false, mapper: mapper), 297 | equals([ 298 | ['A:0'], 299 | ['A:1'], 300 | ['B:T'], 301 | ['B:F'] 302 | ])); 303 | 304 | expect( 305 | generateCombinations(alphabet, 1, 1, 306 | allowRepetition: true, mapper: mapper), 307 | equals([ 308 | ['A:0'], 309 | ['A:1'], 310 | ['B:T'], 311 | ['B:F'] 312 | ])); 313 | 314 | expect( 315 | generateCombinations(alphabet, 2, 2, 316 | allowRepetition: false, mapper: mapper), 317 | equals([ 318 | ['A:0', 'B:T'], 319 | ['A:0', 'B:F'], 320 | ['A:1', 'B:T'], 321 | ['A:1', 'B:F'] 322 | ])); 323 | 324 | expect( 325 | generateCombinations(alphabet, 2, 2, 326 | allowRepetition: true, mapper: mapper), 327 | equals([ 328 | ['A:0', 'A:0'], 329 | ['A:0', 'A:1'], 330 | ['A:0', 'B:T'], 331 | ['A:0', 'B:F'], 332 | ['A:1', 'A:0'], 333 | ['A:1', 'A:1'], 334 | ['A:1', 'B:T'], 335 | ['A:1', 'B:F'], 336 | ['B:T', 'A:0'], 337 | ['B:T', 'A:1'], 338 | ['B:T', 'B:T'], 339 | ['B:T', 'B:F'], 340 | ['B:F', 'A:0'], 341 | ['B:F', 'A:1'], 342 | ['B:F', 'B:T'], 343 | ['B:F', 'B:F'] 344 | ])); 345 | 346 | expect(alphabet.combinations(3, 3, allowRepetition: true), 347 | equals(generateCombinations(alphabet, 3, 3, allowRepetition: true))); 348 | }); 349 | }); 350 | } 351 | -------------------------------------------------------------------------------- /lib/src/statistics_tools.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | 3 | import 'statistics_extension.dart'; 4 | import 'statistics_extension_num.dart'; 5 | 6 | /// A Chronometer useful for benchmarks. 7 | class Chronometer implements Comparable { 8 | /// The name/title of this chronometer. 9 | String name; 10 | 11 | Chronometer([this.name = 'Chronometer']); 12 | 13 | Chronometer._(this.name, this.operations, this.failedOperations, 14 | this._startTime, this._stopTime); 15 | 16 | DateTime? _startTime; 17 | 18 | /// The start [DateTime] of this chronometer. 19 | DateTime? get startTime => _startTime; 20 | 21 | /// Starts the chronometer. 22 | Chronometer start() { 23 | _startTime = DateTime.now(); 24 | return this; 25 | } 26 | 27 | bool get isStarted => _startTime != null; 28 | 29 | DateTime? _stopTime; 30 | 31 | /// The stop [DateTime] of this chronometer. 32 | DateTime? get stopTime => _stopTime; 33 | 34 | /// Stops the chronometer. 35 | Chronometer stop({int? operations, int? failedOperations}) { 36 | _stopTime = DateTime.now(); 37 | 38 | if (operations != null) { 39 | this.operations = operations; 40 | } 41 | 42 | if (failedOperations != null) { 43 | this.failedOperations = failedOperations; 44 | } 45 | 46 | return this; 47 | } 48 | 49 | bool get isFinished => _stopTime != null && isStarted; 50 | 51 | /// Elapsed time in milliseconds ([stopTime] - [startTime]). 52 | int get elapsedTimeMs { 53 | var startTime = _startTime; 54 | if (startTime == null) { 55 | return 0; 56 | } 57 | 58 | var stopTime = _stopTime; 59 | 60 | return stopTime == null 61 | ? (DateTime.now().millisecondsSinceEpoch - 62 | startTime.millisecondsSinceEpoch) 63 | : (stopTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch); 64 | } 65 | 66 | /// Elapsed time in seconds ([stopTime] - [startTime]). 67 | double get elapsedTimeSec => elapsedTimeMs / 1000; 68 | 69 | /// Elapsed time ([stopTime] - [startTime]). 70 | Duration get elapsedTime => Duration(milliseconds: elapsedTimeMs); 71 | 72 | /// Operations performed while this chronometer was running. 73 | /// Used to compute [hertz]. 74 | int operations = 0; 75 | 76 | /// Failed operations performed while this chronometer was running. 77 | int failedOperations = 0; 78 | 79 | /// Returns the [operations] hertz: 80 | /// The average operations per second of 81 | /// the period ([elapsedTimeSec]) of this chronometer. 82 | double get hertz => computeHertz(operations); 83 | 84 | String get hertzAsString => '${_formatNumber(hertz)} Hz'; 85 | 86 | int? _totalOperation; 87 | 88 | /// The total number of [operations] to complete this chronometer. 89 | int? get totalOperation => _totalOperation; 90 | 91 | set totalOperation(int? total) { 92 | if (total == null) { 93 | _totalOperation = null; 94 | } else { 95 | if (total < 0) throw ArgumentError("totalOperation` must be >= 0"); 96 | _totalOperation = total; 97 | } 98 | } 99 | 100 | /// Returns the time to complete [totalOperation] with the current [hertz]. 101 | Duration timeToComplete({int? totalOperation}) { 102 | if (totalOperation == null) { 103 | totalOperation = this.totalOperation; 104 | if (totalOperation == null) { 105 | throw ArgumentError( 106 | 'Parameter or field `totalOperation` should be provided'); 107 | } 108 | } 109 | 110 | var hertz = this.hertz; 111 | if (hertz == 0) return Duration.zero; 112 | 113 | return Duration(seconds: totalOperation ~/ hertz); 114 | } 115 | 116 | String get operationsAsString => _formatNumber(operations); 117 | 118 | String get failedOperationsAsString => _formatNumber(failedOperations); 119 | 120 | static final NumberFormat _numberFormatDecimal = 121 | NumberFormat.decimalPattern('en_US'); 122 | 123 | String _formatNumber(num n) { 124 | var s = n.isFinite && n > 10000 125 | ? _numberFormatDecimal.format(n.toInt()) 126 | : _numberFormatDecimal.format(n); 127 | return s; 128 | } 129 | 130 | /// Computes hertz for n [operations]. 131 | double computeHertz(int operations) { 132 | return operations / elapsedTimeSec; 133 | } 134 | 135 | final Map _marks = {}; 136 | 137 | /// Clears all previous set time marks. 138 | void clearMarks() => _marks.clear(); 139 | 140 | /// Gets a previous set time mark. 141 | DateTime? getMarkTime(String markKey) => _marks[markKey]; 142 | 143 | /// Returns the elapsed time of the time mark [markKey]. 144 | /// If the mark is not set returns a `zero` [Duration]. 145 | Duration getMarkElapsedTime(String markKey) { 146 | var markTime = getMarkTime(markKey); 147 | if (markTime == null) return Duration.zero; 148 | return markTime.elapsedTime; 149 | } 150 | 151 | /// Removes a previous set time mark. 152 | DateTime? removeMarkTime(String markKey) => _marks.remove(markKey); 153 | 154 | /// Sets a time mark in this chronometer with the current [DateTime]. 155 | /// 156 | /// - If [overwrite] is `false` won't save a new [DateTime] for a mark already set. 157 | DateTime markTime(String markKey, {bool overwrite = true}) { 158 | if (!overwrite) { 159 | var prev = _marks[markKey]; 160 | if (prev != null) return prev; 161 | } 162 | 163 | var now = DateTime.now(); 164 | _marks[markKey] = now; 165 | return now; 166 | } 167 | 168 | /// Executes [operations] when [markKey] elapsed time passes [period] at the moment 169 | /// that this method is called. 170 | bool executeOnMarkPeriod( 171 | String markKey, Duration period, void Function() operation) { 172 | var markTime = getMarkTime(markKey); 173 | if (markTime == null || markTime.elapsedTime >= period) { 174 | this.markTime(markKey); 175 | operation(); 176 | return true; 177 | } else { 178 | return false; 179 | } 180 | } 181 | 182 | /// Resets this chronometer for a future [start] and [stop]. 183 | void reset() { 184 | _startTime = null; 185 | _stopTime = null; 186 | operations = 0; 187 | } 188 | 189 | /// Returns a [String] with information of this chronometer: 190 | /// 191 | /// - If [withTime] is `true` will add `startTime` and `elapsedTime` to the [String]. 192 | /// 193 | /// Example: 194 | /// ``` 195 | /// Backpropagation{elapsedTime: 2955 ms, hertz: 2030456.8527918782 Hz, ops: 6000000, startTime: 2021-04-30 22:16:54.437147, stopTime: 2021-04-30 22:16:57.392758} 196 | /// ``` 197 | @override 198 | String toString({bool withStartTime = true}) { 199 | var timeStr = ''; 200 | 201 | if (withStartTime && _startTime != null) { 202 | var start = _startTime.toString(); 203 | var now = DateTime.now().toStringDifference(_startTime!); 204 | timeStr = ' · start: $start .. $now'; 205 | } 206 | 207 | var totalOperation = this.totalOperation; 208 | 209 | var timeToCompleteStr = totalOperation != null 210 | ? ' · ETOC: ${timeToComplete().toStringUnit(decimal: true)}' 211 | : ''; 212 | 213 | var opsRatio = totalOperation != null 214 | ? ' » ${(operations / totalOperation).toPercentage()}' 215 | : ''; 216 | 217 | var opsFails = 218 | failedOperations != 0 ? ' (fails: $failedOperationsAsString)' : ''; 219 | 220 | return '$name{ ${elapsedTime.toStringUnit(decimal: true)} · hertz: $hertzAsString · ops: $operationsAsString$opsRatio$opsFails$timeToCompleteStr$timeStr }'; 221 | } 222 | 223 | Chronometer operator +(Chronometer other) { 224 | DateTime? end; 225 | if (_stopTime != null && other._stopTime != null) { 226 | end = _stopTime!.add(other.elapsedTime); 227 | } else if (_stopTime != null) { 228 | end = _stopTime; 229 | } else if (other._stopTime != null) { 230 | end = other._stopTime; 231 | } 232 | 233 | return Chronometer._(name, operations + other.operations, 234 | failedOperations + other.failedOperations, _startTime, end); 235 | } 236 | 237 | @override 238 | int compareTo(Chronometer other) => hertz.compareTo(other.hertz); 239 | } 240 | 241 | /// A count table for [K] elements. 242 | class CountTable { 243 | final Map> _table = >{}; 244 | 245 | CountTable copy({bool Function(K key, int count)? filter}) { 246 | var copy = CountTable(); 247 | 248 | if (filter != null) { 249 | for (var e in _table.entries) { 250 | var key = e.key; 251 | var value = e.value; 252 | 253 | if (filter(key, value.count)) { 254 | copy._table[key] = value.copy(); 255 | } 256 | } 257 | } else { 258 | for (var e in _table.entries) { 259 | copy._table[e.key] = e.value.copy(); 260 | } 261 | } 262 | 263 | return copy; 264 | } 265 | 266 | /// The number of entries in the counting table. 267 | int get length => _table.length; 268 | 269 | /// Returns `true` if the counting table is empty. 270 | bool get isEmpty => length == 0; 271 | 272 | /// Same as ![isEmpty]. 273 | bool get isNotEmpty => !isEmpty; 274 | 275 | _Counter _get(K key) => _table.putIfAbsent(key, () => _Counter(key)); 276 | 277 | /// Returns the keys [K] in the counting table. 278 | Iterable get keys => _table.keys; 279 | 280 | /// Returns the entries in the counting table. 281 | Iterable> get entries => 282 | _table.values.map((e) => e.asMapEntry); 283 | 284 | /// Returns the keys [K] in the counting table sorted by count value. 285 | Iterable get keysSorted { 286 | var counters = _table.values.toList(); 287 | counters.sort(); 288 | return counters.map((e) => e.key); 289 | } 290 | 291 | /// Increments the counter of [key]. 292 | void increment(K key) => _get(key).count++; 293 | 294 | /// Increments the counter of [key] by [amount]. 295 | void incrementBy(K key, int amount) => _get(key).count += amount; 296 | 297 | /// Decrements the counter of [key]. 298 | void decrement(K key) => _get(key).count--; 299 | 300 | /// Decrements the counter of [key] by [amount]. 301 | void decrementBy(K key, int amount) => _get(key).count -= amount; 302 | 303 | /// Sets the counter of [key] with [count]. 304 | void set(K key, int count) => _get(key).count = count; 305 | 306 | /// Returns the counting value of [key]. 307 | int? get(K key) => _table[key]?.count; 308 | 309 | /// Operator alias to [get]. 310 | operator [](K key) => get(key); 311 | 312 | /// Operator alias to [set]. 313 | operator []=(K key, int count) => set(key, count); 314 | 315 | /// Removes the counter of [key]. 316 | int? remove(K key) => _table.remove(key)?.count; 317 | 318 | /// Clears the counting table. 319 | void clear() => _table.clear(); 320 | 321 | /// Converts this counting table to a [Map]. 322 | Map toMap() => 323 | Map.fromEntries(_table.values.map((e) => e.asMapEntry)); 324 | 325 | /// Returns the [K] element with highest counting value. 326 | K get highest => _table.values 327 | .reduce((value, element) => element.count > value.count ? element : value) 328 | .key; 329 | 330 | /// Returns the [K] element with lowest counting value. 331 | K get lowest => _table.values 332 | .reduce((value, element) => element.count < value.count ? element : value) 333 | .key; 334 | 335 | @override 336 | String toString() => 'CountTable{ length: $length }'; 337 | } 338 | 339 | class _Counter implements Comparable<_Counter> { 340 | final K key; 341 | int count = 0; 342 | 343 | _Counter(this.key); 344 | 345 | _Counter copy() => _Counter(key)..count = count; 346 | 347 | MapEntry get asMapEntry => MapEntry(key, count); 348 | 349 | @override 350 | String toString() => '$key: $count'; 351 | 352 | @override 353 | int compareTo(_Counter other) => count.compareTo(other.count); 354 | } 355 | -------------------------------------------------------------------------------- /lib/src/statistics_forecast.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' show Random; 2 | 3 | import 'package:collection/collection.dart'; 4 | 5 | import 'statistics_bayesnet.dart'; 6 | import 'statistics_combination.dart'; 7 | import 'statistics_extension.dart'; 8 | 9 | typedef ForecastConclusionListener = void Function(T source, 10 | List>? observations, String event, V conclusion); 11 | 12 | /// A Probabilistic Forecast producer base class. 13 | abstract class EventForecaster { 14 | final BayesEventMonitor eventMonitor; 15 | 16 | final int maxDependencyLevel; 17 | final double dependencyNotificationRatio; 18 | 19 | final Random _random; 20 | 21 | EventForecaster.withEventMonitor(this.eventMonitor, 22 | {this.maxDependencyLevel = 2, 23 | this.dependencyNotificationRatio = 0.50, 24 | Random? random}) 25 | : _random = random ?? Random(); 26 | 27 | EventForecaster(String name, 28 | {this.maxDependencyLevel = 2, 29 | this.dependencyNotificationRatio = 0.50, 30 | Random? random}) 31 | : eventMonitor = BayesEventMonitor(name), 32 | _random = random ?? Random(); 33 | 34 | final Map>> _operations = 35 | >>{}; 36 | 37 | /// The [ObservationOperation] [List] for [phase]. 38 | List> getPhaseOperations(String phase) => 39 | _operations.putIfAbsent( 40 | phase, () => generatePhaseOperations(phase).toList()); 41 | 42 | /// Generates the operations for [phase]. 43 | Iterable> generatePhaseOperations(String phase); 44 | 45 | final Map>> _phasesObservations = 46 | >>{}; 47 | 48 | /// Disposes all the non-concluded observations of all phases. 49 | /// 50 | /// See [concludeObservations]. 51 | void disposeObservations() { 52 | _phasesObservations.clear(); 53 | } 54 | 55 | /// Observe [source] for [phase]. Calls [getPhaseOperations] to generated 56 | /// the observations. 57 | List> observe(source, {String phase = ''}) { 58 | var operations = getPhaseOperations(phase); 59 | 60 | var observations = operations.map((op) { 61 | var value = op.compute(source); 62 | return ForecastObservation(phase, source, op, value); 63 | }).toList(); 64 | 65 | var phaseObs = _phasesObservations.putIfAbsent( 66 | phase, () => >[]); 67 | phaseObs.addAll(observations); 68 | 69 | return observations; 70 | } 71 | 72 | /// Returns all the non-concluded observations of all phases. 73 | List> get allObservations => 74 | _phasesObservations.values.expand((e) => e).toList(); 75 | 76 | /// Selects the non-concluded observations that matches the criteria: 77 | /// - If [phases] is define selects only for observations of [phases]. (Overrides [allPhases]). 78 | /// - If [allPhases] is `true` selects all observations of all phases. 79 | /// - If [source] is defined will selects only observations for [source]. 80 | /// 81 | /// See [concludeObservations]. 82 | List> selectedObservations( 83 | {Iterable? phases, bool allPhases = false, T? source}) { 84 | Iterable> selector; 85 | 86 | if (phases != null) { 87 | var phasesSet = phases is Set ? phases : phases.toSet(); 88 | selector = phasesSet.expand( 89 | (p) => _phasesObservations[p] ?? >[]); 90 | } else { 91 | if (allPhases || source != null) { 92 | selector = _phasesObservations.values.expand((e) => e); 93 | } else { 94 | return >[]; 95 | } 96 | } 97 | 98 | if (source != null) { 99 | selector = selector.where((o) => o.source == source); 100 | } 101 | 102 | return selector.toList(); 103 | } 104 | 105 | /// Removes [observation] of the non-concluded list. 106 | /// 107 | /// See [concludeObservations]. 108 | void removeObservation(ForecastObservation observation) { 109 | var phase = _phasesObservations[observation.phase]; 110 | if (phase != null) { 111 | phase.remove(observation); 112 | } 113 | } 114 | 115 | /// Removes [observations] of the non-concluded list. 116 | /// 117 | /// See [concludeObservations]. 118 | void removeObservations(List> observations) { 119 | var observationsByPhase = observations.groupBy((e) => e.phase); 120 | 121 | for (var e in observationsByPhase.entries) { 122 | var phase = _phasesObservations[e.key]; 123 | if (phase == null) continue; 124 | 125 | var list = e.value; 126 | phase.removeAll(list); 127 | } 128 | } 129 | 130 | final CombinationCache _combinationCache = 131 | CombinationCache( 132 | allowRepetition: false, allowSharedCombinations: true); 133 | 134 | /// Concludes observations for [source] with [value]. 135 | /// 136 | /// - If [phases] is provided, concludes only for observations in [phases]. 137 | List> concludeObservations( 138 | T source, Map conclusions, 139 | {List? phases}) { 140 | var selectedObservations = 141 | this.selectedObservations(phases: phases, source: source); 142 | 143 | Map> observationsByID; 144 | List> dependencies; 145 | 146 | if (dependencyNotificationRatio > 0) { 147 | observationsByID = 148 | Map.fromEntries(selectedObservations.map((e) => MapEntry(e.id, e))); 149 | 150 | var ids = observationsByID.keys.toSet(); 151 | dependencies = ids.isEmpty 152 | ? >[] 153 | : _combinationCache.getCombinationsShared(ids, 2, maxDependencyLevel); 154 | } else { 155 | observationsByID = >{}; 156 | dependencies = >[]; 157 | } 158 | 159 | for (var e in conclusions.entries) { 160 | var event = e.key; 161 | var value = e.value; 162 | 163 | for (var o in selectedObservations) { 164 | _notifyConclusion(source, [o], event, value); 165 | } 166 | 167 | for (var combination in dependencies) { 168 | if (dependencyNotificationRatio >= 1 || 169 | _random.nextDouble() < dependencyNotificationRatio) { 170 | var dependentObservations = 171 | combination.map((id) => observationsByID[id]).nonNulls; 172 | 173 | _notifyConclusion(source, dependentObservations, event, value, 174 | dependency: true); 175 | } 176 | } 177 | 178 | _notifyConclusion(source, null, event, value); 179 | } 180 | 181 | removeObservations(selectedObservations); 182 | 183 | return selectedObservations; 184 | } 185 | 186 | /// Performes a forecast over [source]. Calls [computeForecast] with the selected 187 | /// observations. 188 | /// 189 | /// - If [phase] is defined, it will make a forecast for this phase. 190 | /// - If [previousPhases] is defined the observations for this [source] in 191 | /// the [previousPhases]es will be selected too. 192 | F forecast(T source, {String phase = '', Iterable? previousPhases}) { 193 | List> observations = 194 | observe(source, phase: phase); 195 | 196 | if (previousPhases != null && previousPhases.isNotEmpty) { 197 | var selPhases = {phase, ...previousPhases}; 198 | 199 | if (selPhases.length > 1) { 200 | observations = selectedObservations(phases: selPhases, source: source); 201 | } 202 | } 203 | 204 | var f = computeForecast(phase, observations); 205 | return f; 206 | } 207 | 208 | F computeForecast(String phase, List> observations); 209 | 210 | ForecastConclusionListener? conclusionListener; 211 | 212 | void _notifyConclusion( 213 | T source, 214 | Iterable>? observations, 215 | String event, 216 | V conclusion, 217 | {bool dependency = false}) { 218 | var eventValues = { 219 | if (observations != null && observations.isNotEmpty) 220 | ...observations.map((o) => 221 | ObservedEventValue(o.id, o.value!, networkCache: eventMonitor)), 222 | ObservedEventValue(event, conclusion!, networkCache: eventMonitor) 223 | }; 224 | 225 | if (dependency) { 226 | var dependentVariables = observations?.map((o) => o.id).toList(); 227 | if (dependentVariables == null || dependentVariables.length < 2) { 228 | throw StateError( 229 | "Dependency requires at least 2 variables: $dependentVariables"); 230 | } 231 | 232 | eventMonitor.notifyDependency(dependentVariables, eventValues); 233 | } else { 234 | eventMonitor.notifyEvent(eventValues); 235 | } 236 | 237 | var conclusionListener = this.conclusionListener; 238 | if (conclusionListener != null) { 239 | conclusionListener(source, observations?.toList(), event, conclusion); 240 | } 241 | } 242 | } 243 | 244 | typedef OperationComputer = V Function(T source); 245 | 246 | /// A Forecast observation operation over source [T] that produces value [V]. 247 | class ObservationOperation { 248 | static final RegExp _regExpNonWord = RegExp(r'\W+'); 249 | 250 | /// Normalizes the [id]. 251 | /// 252 | /// Substitutes `\W` by `_` (trimmed). 253 | static String normalizeID(String id) { 254 | id = id.replaceAll(_regExpNonWord, '_'); 255 | if (id.startsWith('_')) id = id.substring(1); 256 | if (id.endsWith('_')) id = id.substring(0, id.length - 1); 257 | return id; 258 | } 259 | 260 | /// Operation ID. 261 | final String id; 262 | 263 | /// Operation description. 264 | final String description; 265 | 266 | /// The [Function] that computes the operation. 267 | final OperationComputer? computer; 268 | 269 | ObservationOperation(String id, {this.computer, this.description = ''}) 270 | : id = normalizeID(id); 271 | 272 | V compute(T source) => computer!(source); 273 | 274 | @override 275 | String toString() { 276 | var descriptionStr = 277 | description.isNotEmpty ? ', description: $description' : ''; 278 | return 'ForecastOperation{id: $id$descriptionStr}'; 279 | } 280 | } 281 | 282 | /// A Forecast value [V] observation over [source] [T]. 283 | class ForecastObservation { 284 | /// The forecast phase. 285 | final String phase; 286 | 287 | /// The source of the [value] observed. 288 | final T source; 289 | 290 | /// The operation performed over [source] to produce the observed [value]. 291 | final ObservationOperation operation; 292 | 293 | /// The value observed during a forecast performed over [source]. 294 | final V value; 295 | 296 | ForecastObservation(this.phase, this.source, this.operation, this.value); 297 | 298 | /// The [operation.id]. 299 | String get opID => operation.id; 300 | 301 | String? _id; 302 | 303 | /// ID of the observation. 304 | /// 305 | /// It's the [opID] prefixed with the [phase] (if the [phase] is not empty). 306 | String get id => _id ??= phase.isNotEmpty ? '$phase.$opID' : opID; 307 | 308 | @override 309 | String toString() { 310 | var phaseStr = phase.isNotEmpty ? '$phase.' : ''; 311 | return '$phaseStr${operation.id}($source) -> $value'; 312 | } 313 | } 314 | 315 | extension ListForecastObservationExtension 316 | on List> { 317 | /// Gets an observation by operation ID [opID]. 318 | /// 319 | /// - If [opIdAlreadyNormalized] is `true` own't normalize [opID]. 320 | ForecastObservation? getByOpID(String opID, 321 | {bool opIdAlreadyNormalized = false}) { 322 | if (!opIdAlreadyNormalized) { 323 | opID = ObservationOperation.normalizeID(opID); 324 | } 325 | return firstWhereOrNull((e) => e.opID == opID); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /test/statistics_tools_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:statistics/statistics.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('int', () { 6 | setUp(() {}); 7 | 8 | test('toIntsList', () { 9 | expect([].toIntsList(), isEmpty); 10 | expect([].toIntsList(), isEmpty); 11 | expect([10].toIntsList(), equals([10])); 12 | expect([10, 20].toIntsList(), equals([10, 20])); 13 | }); 14 | 15 | test('toDoublesList', () { 16 | expect([].toDoublesList(), isEmpty); 17 | expect([].toDoublesList(), isEmpty); 18 | expect([10].toDoublesList(), equals([10.0])); 19 | expect([10, 20].toDoublesList(), equals([10.0, 20.0])); 20 | }); 21 | 22 | test('toStringsList', () { 23 | expect([].toStringsList(), isEmpty); 24 | expect([].toStringsList(), isEmpty); 25 | expect([10].toStringsList(), equals(['10'])); 26 | expect([10, 20].toStringsList(), equals(['10', '20'])); 27 | }); 28 | 29 | test('mean', () { 30 | expect([].mean, isNaN); 31 | expect([0].mean, equals(0)); 32 | expect([10].mean, equals(10)); 33 | expect([10, 20].mean, equals(15)); 34 | expect([10, 20, 30].mean, equals(20)); 35 | }); 36 | 37 | test('sum', () { 38 | expect([].sum, equals(0)); 39 | expect([0].sum, equals(0)); 40 | expect([10].sum, equals(10)); 41 | expect([10, 20].sum, equals(30)); 42 | expect([10, 20, 30].sum, equals(60)); 43 | }); 44 | 45 | test('sumSquares', () { 46 | expect([].sumSquares, equals(0)); 47 | expect([0].sumSquares, equals(0)); 48 | expect([10].sumSquares, equals(100)); 49 | expect([10, 20].sumSquares, equals(500)); 50 | expect([10, 20, 30].sumSquares, equals(1400)); 51 | }); 52 | 53 | test('square', () { 54 | expect([].square, isEmpty); 55 | expect([0].square, equals([0])); 56 | expect([10].square, equals([100])); 57 | expect([10, 20].square, equals([100, 400])); 58 | expect([10, 20, 30].square, equals([100, 400, 900])); 59 | 60 | expect(11.square, equals(121)); 61 | }); 62 | 63 | test('squareRoot', () { 64 | expect([].squareRoot, isEmpty); 65 | expect([0].squareRoot, equals([0])); 66 | expect([9].squareRoot, equals([3])); 67 | expect([100, 121].squareRoot, equals([10, 11])); 68 | expect([10000, 400, 900].squareRoot, equals([100, 20, 30])); 69 | 70 | expect(100.squareRoot, equals(10)); 71 | }); 72 | 73 | test('squaresMean', () { 74 | expect([].squaresMean, isNaN); 75 | expect([0].squaresMean, equals(0)); 76 | expect([10].squaresMean, equals(100)); 77 | expect([10, 20].squaresMean, equals(250)); 78 | expect([10, 20, 30].squaresMean, equals(466.6666666666667)); 79 | }); 80 | 81 | test('standardDeviation', () { 82 | expect([].standardDeviation, equals(0)); 83 | expect([0].standardDeviation, equals(0)); 84 | expect([10].standardDeviation, equals(0)); 85 | expect([10, 20].standardDeviation, equals(5)); 86 | expect([10, 20, 30].standardDeviation, equals(8.16496580927726)); 87 | }); 88 | 89 | test('median', () { 90 | expect([].median, isNull); 91 | expect([0].median, equals(0)); 92 | expect([10].median, equals(10)); 93 | expect([10, 20].median, equals(15)); 94 | expect([10, 20, 30].median, equals(20)); 95 | expect([30, 20, 10].median, equals(20)); 96 | expect([5, 10, 20, 30].median, equals(15)); 97 | expect([30, 20, 10, 5].median, equals(15)); 98 | }); 99 | 100 | test('medianLow', () { 101 | expect([].medianLow, isNull); 102 | expect([0].medianLow, equals(0)); 103 | expect([10].medianLow, equals(10)); 104 | expect([10, 20].medianLow, equals(10)); 105 | expect([10, 20, 30].medianLow, equals(20)); 106 | expect([30, 20, 10].medianLow, equals(20)); 107 | expect([5, 10, 20, 30].medianLow, equals(10)); 108 | expect([30, 20, 10, 5].medianLow, equals(10)); 109 | }); 110 | 111 | test('medianHigh', () { 112 | expect([].medianHigh, isNull); 113 | expect([0].medianHigh, equals(0)); 114 | expect([10].medianHigh, equals(10)); 115 | expect([10, 20].medianHigh, equals(20)); 116 | expect([10, 20, 30].medianHigh, equals(20)); 117 | expect([30, 20, 10].medianHigh, equals(20)); 118 | expect([5, 10, 20, 30].medianHigh, equals(20)); 119 | expect([30, 20, 10, 5].medianHigh, equals(20)); 120 | }); 121 | 122 | test('abs', () { 123 | expect([].abs, isEmpty); 124 | expect([0].abs, equals([0])); 125 | expect([10].abs, equals([10])); 126 | expect([-10, 20].abs, equals([10, 20])); 127 | expect([10, -20, 30].abs, equals([10, 20, 30])); 128 | }); 129 | 130 | test('movingAverage', () { 131 | expect([].movingAverage(2), isEmpty); 132 | expect([0].movingAverage(2), equals([0])); 133 | expect([10].movingAverage(3), equals([10])); 134 | expect([-10, 20].movingAverage(3), equals([5.0])); 135 | expect([10, -20, 30].movingAverage(3), equals([6.666666666666667])); 136 | expect([10, -20, 30, 40, 50, 60].movingAverage(3), 137 | equals([6.666666666666667, 16.666666666666668, 40.0, 50.0])); 138 | }); 139 | 140 | test('operator +', () { 141 | expect([] + [], isEmpty); 142 | expect([10] + [20], equals([10, 20])); 143 | expect([100, 200] + [10, 20], equals([100, 200, 10, 20])); 144 | }); 145 | 146 | test('operator -', () { 147 | expect([] - [], isEmpty); 148 | expect([10] - [20], equals([-10])); 149 | expect([100, 200] - [10, 20], equals([90, 180])); 150 | expect([100, 200, 300] - [10, 20], equals([90, 180])); 151 | expect([100, 200, 300] - [10, 20, 30], equals([90, 180, 270])); 152 | }); 153 | 154 | test('operator *', () { 155 | expect([] * [], isEmpty); 156 | expect([10] * [20], equals([200])); 157 | expect([100, 200] * [10, 20], equals([1000, 4000])); 158 | expect([100, 200, 300] * [10, 20], equals([1000, 4000])); 159 | expect( 160 | [100, 200, 300] * [10, 20, 30], equals([1000, 4000, 9000])); 161 | }); 162 | 163 | test('operator /', () { 164 | expect([] / [], isEmpty); 165 | expect([10] / [20], equals([0.5])); 166 | expect([100, 200] / [10, 20], equals([10, 10])); 167 | expect([100, 200, 300] / [10, 20], equals([10, 10])); 168 | expect([100, 200, 300] / [10, 20, 30], equals([10, 10, 10])); 169 | expect([100, 200, 300] / [40, 50, 30], equals([2.5, 4, 10])); 170 | }); 171 | 172 | test('operator ~/', () { 173 | expect([] ~/ [], isEmpty); 174 | expect([10] ~/ [20], equals([0])); 175 | expect([100, 200] ~/ [10, 20], equals([10, 10])); 176 | expect([100, 200, 300] ~/ [10, 20], equals([10, 10])); 177 | expect([100, 200, 300] ~/ [10, 20, 30], equals([10, 10, 10])); 178 | expect([100, 200, 300] ~/ [40, 50, 30], equals([2, 4, 10])); 179 | }); 180 | 181 | test('isSorted', () { 182 | expect([].isSorted, isFalse); 183 | 184 | expect([0].isSorted, isTrue); 185 | expect([10].isSorted, isTrue); 186 | expect([-10, 20].isSorted, isTrue); 187 | expect([10, 20, 30].isSorted, isTrue); 188 | 189 | expect([10, 5].isSorted, isFalse); 190 | expect([10, 200, 30].isSorted, isFalse); 191 | }); 192 | 193 | test('equalsValues', () { 194 | expect([].equalsValues([]), isTrue); 195 | expect([].equalsValues([10]), isFalse); 196 | 197 | expect([10].equalsValues([10]), isTrue); 198 | expect([10].equalsValues([10.0], tolerance: 0.000001), isTrue); 199 | expect([10].equalsValues([10.0001]), isFalse); 200 | expect([10].equalsValues([10.0], tolerance: 0.01), isTrue); 201 | expect([10].equalsValues([10.0001], tolerance: 0.01), isTrue); 202 | expect([10].equalsValues([10.0001], tolerance: 0.000001), isFalse); 203 | 204 | expect([10, 20].equalsValues([10.0001, 20.001], tolerance: 0.01), 205 | isTrue); 206 | expect([10, 20].equalsValues([10.0001, 20.1], tolerance: 0.01), 207 | isFalse); 208 | }); 209 | }); 210 | 211 | group('tools', () { 212 | setUp(() {}); 213 | 214 | test('Chronometer', () async { 215 | var chronometer = Chronometer('test'); 216 | 217 | expect(chronometer.isStarted, isFalse); 218 | expect(chronometer.isFinished, isFalse); 219 | expect(chronometer.elapsedTimeMs, equals(0)); 220 | 221 | chronometer.start(); 222 | 223 | expect(chronometer.isStarted, isTrue); 224 | expect(chronometer.isFinished, isFalse); 225 | 226 | await Future.delayed(Duration(seconds: 1)); 227 | chronometer.stop(operations: 10); 228 | 229 | expect(chronometer.isStarted, isTrue); 230 | expect(chronometer.isFinished, isTrue); 231 | 232 | expect(chronometer.elapsedTimeMs >= 1000, isTrue); 233 | expect(chronometer.elapsedTimeMs < 2000, isTrue); 234 | 235 | expect(chronometer.elapsedTimeSec >= 1, isTrue); 236 | expect(chronometer.elapsedTimeSec < 2, isTrue); 237 | 238 | expect(chronometer.elapsedTime.inMilliseconds, 239 | equals(chronometer.elapsedTimeMs)); 240 | 241 | expect(chronometer.operations, equals(10)); 242 | expect(chronometer.failedOperations, equals(0)); 243 | 244 | expect(chronometer.hertz <= 10, isTrue); 245 | expect(chronometer.hertz >= 9, isTrue); 246 | 247 | expect( 248 | chronometer.stopTime!.millisecondsSinceEpoch > 249 | chronometer.startTime!.millisecondsSinceEpoch, 250 | isTrue); 251 | 252 | expect(chronometer.hertzAsString, matches(RegExp(r'^[\d.]+ Hz$'))); 253 | expect(chronometer.operationsAsString, equals('10')); 254 | expect(chronometer.failedOperationsAsString, equals('0')); 255 | 256 | print(chronometer); 257 | print(chronometer.toString(withStartTime: false)); 258 | 259 | expect( 260 | chronometer.toString(), 261 | matches(RegExp( 262 | r'^test\{ [\d.]+ \w+ · hertz: [\d.]+ Hz · ops: \d+ · start: [\d-]+ [\d:.-]+ \.\. \d+\.\d+ \}$'))); 263 | 264 | expect( 265 | chronometer.toString(withStartTime: false), 266 | matches( 267 | RegExp(r'^test\{ [\d.]+ \w+ · hertz: [\d.]+ Hz · ops: \d+ \}$'))); 268 | 269 | chronometer.totalOperation = 100; 270 | 271 | print(chronometer); 272 | print(chronometer.toString(withStartTime: false)); 273 | 274 | expect( 275 | chronometer.toString(), 276 | matches(RegExp( 277 | r'^test\{ [\d.]+ \w+ · hertz: [\d.]+ Hz · ops: [\d,]+ » [\d.]+% · ETOC: [\d.]+ \w+ · start: [\d-]+ [\d:.-]+ \.\. \d+\.\d+ \}$'))); 278 | 279 | var chronometer2 = Chronometer('test2'); 280 | 281 | print(chronometer2); 282 | print(chronometer2.toString(withStartTime: true)); 283 | 284 | expect(chronometer2.toString(), matches(RegExp(r'^test2\{ 0 .*'))); 285 | 286 | chronometer2.start(); 287 | await Future.delayed(Duration(milliseconds: 100)); 288 | 289 | expect(chronometer2.elapsedTimeMs >= 100, isTrue); 290 | expect(chronometer2.elapsedTimeMs < 200, isTrue); 291 | 292 | chronometer2.stop(operations: 1); 293 | 294 | var elapsedTime = chronometer2.elapsedTimeMs; 295 | 296 | await Future.delayed(Duration(milliseconds: 100)); 297 | 298 | expect(chronometer2.elapsedTimeMs, equals(elapsedTime)); 299 | 300 | expect(chronometer2.elapsedTimeSec >= 0.1, isTrue); 301 | expect(chronometer2.elapsedTimeSec < 0.2, isTrue); 302 | 303 | expect(chronometer2.elapsedTime.inMilliseconds, 304 | equals(chronometer2.elapsedTimeMs)); 305 | 306 | expect(chronometer2.operations, equals(1)); 307 | expect(chronometer2.failedOperations, equals(0)); 308 | 309 | expect(chronometer2.hertz <= 10, isTrue); 310 | expect(chronometer2.hertz >= 9, isTrue); 311 | 312 | expect( 313 | chronometer.compareTo(chronometer2), 314 | equals(chronometer.hertz > chronometer2.hertz 315 | ? 1 316 | : (chronometer.hertz == chronometer2.hertz ? 0 : -1))); 317 | 318 | var chronometer3 = chronometer + chronometer2; 319 | 320 | expect(chronometer3.hertz <= 10, isTrue); 321 | expect(chronometer3.hertz >= 9, isTrue); 322 | 323 | expect(chronometer3.elapsedTimeMs > chronometer.elapsedTimeMs, isTrue); 324 | 325 | expect(chronometer3.elapsedTimeMs >= 1100, isTrue); 326 | expect(chronometer3.elapsedTimeMs < 2000, isTrue); 327 | 328 | chronometer.reset(); 329 | expect(chronometer.isStarted, isFalse); 330 | expect(chronometer.isFinished, isFalse); 331 | expect(chronometer.operations, equals(0)); 332 | }, retry: 3); 333 | 334 | test('CountTable', () { 335 | var counter = CountTable(); 336 | 337 | expect(counter.isEmpty, isTrue); 338 | expect(counter.isNotEmpty, isFalse); 339 | expect(counter.length, equals(0)); 340 | expect(counter.keys, isEmpty); 341 | expect(counter.keysSorted, isEmpty); 342 | 343 | expect(counter.keysSorted, isEmpty); 344 | 345 | expect(counter.get('x'), isNull); 346 | 347 | counter.increment('x'); 348 | expect(counter.get('x'), equals(1)); 349 | expect(counter.isEmpty, isFalse); 350 | expect(counter.isNotEmpty, isTrue); 351 | expect(counter.length, equals(1)); 352 | 353 | counter.increment('x'); 354 | expect(counter.get('x'), equals(2)); 355 | 356 | counter.incrementBy('x', 3); 357 | expect(counter.get('x'), equals(5)); 358 | 359 | counter.incrementBy('y', 2); 360 | expect(counter.get('y'), equals(2)); 361 | 362 | expect(counter.get('x'), equals(5)); 363 | 364 | counter.increment('x'); 365 | expect(counter.get('x'), equals(6)); 366 | 367 | expect(counter.length, equals(2)); 368 | expect(counter.keys, equals(['x', 'y'])); 369 | expect(counter.keysSorted, equals(['y', 'x'])); 370 | 371 | expect(counter.isEmpty, isFalse); 372 | 373 | expect( 374 | counter.entries.toList().map((e) => '${e.key}=${e.value}').toList(), 375 | equals(['x=6', 'y=2'])); 376 | expect(counter.toMap(), equals({'x': 6, 'y': 2})); 377 | 378 | expect(counter.highest, equals('x')); 379 | expect(counter.lowest, equals('y')); 380 | 381 | counter.decrement('x'); 382 | expect(counter.toMap(), equals({'x': 5, 'y': 2})); 383 | 384 | counter.incrementBy('x', 10); 385 | expect(counter.toMap(), equals({'x': 15, 'y': 2})); 386 | 387 | counter.decrementBy('x', 5); 388 | expect(counter.toMap(), equals({'x': 10, 'y': 2})); 389 | 390 | counter.decrementBy('x', 5); 391 | expect(counter.toMap(), equals({'x': 5, 'y': 2})); 392 | 393 | expect(counter.highest, equals('x')); 394 | expect(counter.lowest, equals('y')); 395 | 396 | expect(counter['x'], equals(5)); 397 | expect(counter['y'], equals(2)); 398 | 399 | counter.set('y', 100); 400 | expect(counter.toMap(), equals({'x': 5, 'y': 100})); 401 | 402 | counter['y'] = 10; 403 | expect(counter.toMap(), equals({'x': 5, 'y': 10})); 404 | 405 | expect(counter.toString(), equals('CountTable{ length: 2 }')); 406 | 407 | expect(counter.copy().toMap(), equals({'x': 5, 'y': 10})); 408 | 409 | expect( 410 | counter.copy(filter: (k, v) => v >= 10).toMap(), equals({'y': 10})); 411 | 412 | expect(counter.highest, equals('y')); 413 | expect(counter.lowest, equals('x')); 414 | 415 | expect(counter.remove('x'), equals(5)); 416 | 417 | expect(counter.highest, equals('y')); 418 | expect(counter.lowest, equals('y')); 419 | 420 | counter.clear(); 421 | expect(counter.toMap(), equals({})); 422 | expect(counter.isEmpty, isTrue); 423 | expect(counter.length, equals(0)); 424 | 425 | expect(counter.toString(), equals('CountTable{ length: 0 }')); 426 | }); 427 | }); 428 | } 429 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # statistics 2 | 3 | [![pub package](https://img.shields.io/pub/v/statistics.svg?logo=dart&logoColor=00b9fc)](https://pub.dev/packages/statistics) 4 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 5 | [![Codecov](https://img.shields.io/codecov/c/github/gmpassos/statistics)](https://app.codecov.io/gh/gmpassos/statistics) 6 | [![Dart CI](https://github.com/gmpassos/statistics/actions/workflows/dart.yml/badge.svg?branch=master)](https://github.com/gmpassos/statistics/actions/workflows/dart.yml) 7 | [![GitHub Tag](https://img.shields.io/github/v/tag/gmpassos/statistics?logo=git&logoColor=white)](https://github.com/gmpassos/statistics/releases) 8 | [![New Commits](https://img.shields.io/github/commits-since/gmpassos/statistics/latest?logo=git&logoColor=white)](https://github.com/gmpassos/statistics/network) 9 | [![Last Commits](https://img.shields.io/github/last-commit/gmpassos/statistics?logo=git&logoColor=white)](https://github.com/gmpassos/statistics/commits/master) 10 | [![Pull Requests](https://img.shields.io/github/issues-pr/gmpassos/statistics?logo=github&logoColor=white)](https://github.com/gmpassos/statistics/pulls) 11 | [![Code size](https://img.shields.io/github/languages/code-size/gmpassos/statistics?logo=github&logoColor=white)](https://github.com/gmpassos/statistics) 12 | [![License](https://img.shields.io/github/license/gmpassos/statistics?logo=open-source-initiative&logoColor=green)](https://github.com/gmpassos/statistics/blob/master/LICENSE) 13 | 14 | Statistics package for easy and efficient data manipulation with built-in Bayesian Network (Bayes Net), 15 | many mathematical functions and tools. 16 | 17 | ## API Documentation 18 | 19 | See the [API Documentation][api_doc] for a full list of functions, classes and extension. 20 | 21 | [api_doc]: https://pub.dev/documentation/statistics/latest/ 22 | 23 | ## Usage 24 | 25 | ### Numeric extension: 26 | 27 | ```dart 28 | import 'package:statistics/statistics.dart'; 29 | 30 | void main() { 31 | var ns = [10, 20.0, 30]; 32 | print('ns: $ns'); 33 | 34 | var mean = ns.mean; 35 | print('mean: $mean'); 36 | 37 | var sdv = ns.standardDeviation; 38 | print('sdv: $sdv'); 39 | 40 | var squares = ns.square; 41 | print('squares: $squares'); 42 | } 43 | ``` 44 | 45 | OUTPUT: 46 | 47 | ```text 48 | ns: [10, 20.0, 30] 49 | mean: 20.0 50 | sdv: 8.16496580927726 51 | squares: [100.0, 400.0, 900.0] 52 | ``` 53 | 54 | ### Statistics 55 | 56 | The class [Statistics][statistics_class], that have many pre-computed statistics, can be generated from a numeric collection: 57 | 58 | [statistics_class]: https://pub.dev/documentation/statistics/latest/statistics/Statistics-class.html 59 | 60 | ```dart 61 | import 'package:statistics/statistics.dart'; 62 | 63 | void main() { 64 | var ns = [10, 20.0, 25, 30]; 65 | var statistics = ns.statistics; 66 | 67 | print('Statistics.max: ${ statistics.max }'); 68 | print('Statistics.min: ${ statistics.min }'); 69 | print('Statistics.mean: ${ statistics.mean }'); 70 | print('Statistics.standardDeviation: ${ statistics.standardDeviation }'); 71 | print('Statistics.sum: ${ statistics.sum }'); 72 | print('Statistics.center: ${ statistics.center }'); 73 | print('Statistics.median: ${statistics.median} -> ${statistics.medianLow} , ${statistics.medianHigh}'); 74 | print('Statistics.squaresSum: ${ statistics.squaresSum }'); 75 | 76 | print('Statistics: $statistics'); 77 | } 78 | ``` 79 | 80 | OUTPUT: 81 | 82 | ```text 83 | Statistics.max: 30 84 | Statistics.min: 10 85 | Statistics.mean: 21.25 86 | Statistics.standardDeviation: 7.39509972887452 87 | Statistics.sum: 85.0 88 | Statistics.center: 25 89 | Statistics.median: 22.5 -> 20.0 , 25 90 | Statistics.squaresSum: 2025.0 91 | Statistics: {~21.25 +-7.3950 [10..(25)..30] #4} 92 | ``` 93 | 94 | ### Bayesian Network 95 | 96 | Bayesian Network, also known as Bayes Network, is a very important tool in understanding the dependency 97 | among events and assigning probabilities to them. 98 | 99 | Here's an example of how to build a `BayesianNetwork`. 100 | 101 | ```dart 102 | import 'package:statistics/statistics.dart'; 103 | 104 | void main(){ 105 | var bayesNet = BayesianNetwork('cancer'); 106 | 107 | // C (cancer) = T (true) ; F (false) 108 | bayesNet.addVariable("C", [ 109 | 'F', 110 | 'T', 111 | ], [], [ 112 | "C = F: 0.99", 113 | "C = T: 0.01", 114 | ]); 115 | 116 | // X (exam) = P (positive) ; N (negative) 117 | bayesNet.addVariable("X", [ 118 | '+P', 119 | '-N', 120 | ], [ 121 | "C" 122 | ], [ 123 | "X = N, C = F: 0.91", 124 | "X = P, C = F: 0.09", 125 | "X = N, C = T: 0.10", 126 | "X = P, C = T: 0.90", 127 | ]); 128 | 129 | // Show the network nodes and probabilities: 130 | print(bayesNet); 131 | 132 | var analyser = bayesNet.analyser; 133 | 134 | // Ask the probability to have cancer with a positive exame (X = P): 135 | var answer1 = analyser.ask('P(c|x)'); 136 | print(answer1); // P(c|x) -> C = T | X = P -> 0.09174311926605506 (0.009000000000000001) >> 917.43% 137 | 138 | // Ask the probability to have cancer with a negative exame (X = N): 139 | var answer2 = analyser.ask('P(c|-x)'); 140 | print(answer2); // P(c|-x) -> C = T | X = N -> 0.0011087703736556158 (0.001) >> 11.09% 141 | } 142 | ``` 143 | 144 | #### Variable Dependency 145 | 146 | To support variables dependencies you can use the method `addDependency`: 147 | 148 | ```dart 149 | import 'package:statistics/statistics.dart'; 150 | 151 | void main() { 152 | // ** Note that this example is NOT USING REAL probabilities for Cancer! 153 | 154 | var bayesNet = BayesianNetwork('cancer'); 155 | 156 | // C (cancer) = T (true) ; F (false) 157 | bayesNet.addVariable("C", [ 158 | 'F', 159 | 'T', 160 | ], [], [ 161 | "C = F: 0.99", 162 | "C = T: 0.01", 163 | ]); 164 | 165 | // X (exam) = P (positive) ; N (negative) 166 | bayesNet.addVariable("X", [ 167 | '+P', 168 | '-N', 169 | ], [ 170 | "C" 171 | ], [ 172 | "X = N, C = F: 0.91", 173 | "X = P, C = F: 0.09", 174 | "X = N, C = T: 0.10", 175 | "X = P, C = T: 0.90", 176 | ]); 177 | 178 | // D (Doctor diagnosis) = P (positive) ; N (negative) 179 | bayesNet.addVariable("D", [ 180 | '+P', 181 | '-N', 182 | ], [ 183 | "C" 184 | ], [ 185 | "D = N, C = F: 0.99", 186 | "D = P, C = F: 0.01", 187 | "D = N, C = T: 0.75", 188 | "D = P, C = T: 0.25", 189 | ]); 190 | 191 | // Add dependency between D (Doctor diagnosis) and X (Exam), 192 | // where the probability of a correct diagnosis is improved: 193 | bayesNet.addDependency([ 194 | 'D', 195 | 'X' 196 | ], [ 197 | "D = N, X = N, C = F: 0.364", 198 | "D = P, X = N, C = F: 0.546", 199 | "D = N, X = P, C = F: 0.036", 200 | "D = P, X = P, C = F: 0.054", 201 | 202 | "D = N, X = N, C = T: 0.025", 203 | "D = N, X = P, C = T: 0.075", 204 | "D = P, X = N, C = T: 0.225", 205 | "D = P, X = P, C = T: 0.675", 206 | ]); 207 | 208 | // Show the network nodes and probabilities: 209 | print(bayesNet); 210 | 211 | var analyser = bayesNet.analyser; 212 | 213 | // Ask the probability to have cancer with a positive exame (X = P): 214 | var answer1 = analyser.ask('P(c|x)'); 215 | print(answer1); // P(c|x) -> C = T | X = P -> 0.09174311926605506 (0.009000000000000001) >> 917.43% 216 | 217 | // Ask the probability to have cancer with a negative exame (X = N): 218 | var answer2 = analyser.ask('P(c|-x)'); 219 | print(answer2); // P(c|-x) -> C = T | X = N -> 0.0011087703736556158 (0.001) >> 11.09% 220 | 221 | // Ask the probability to have cancer with a positive diagnosis from the Doctor (D = P): 222 | var answer3 = analyser.ask('P(c|d)'); 223 | print(answer3); // P(c|d) -> C = T | D = P -> 0.20161290322580644 (0.0025) >> 2016.13% 224 | 225 | // Ask the probability to have cancer with a negative diagnosis from the Doctor (D = N): 226 | var answer4 = analyser.ask('P(c|-d)'); 227 | print(answer4); // P(c|-d) -> C = T | D = N -> 0.007594167679222358 (0.0075) >> 75.94% 228 | 229 | // Ask the probability to have cancer with a positive diagnosis from the Doctor and a positive exame (D = P, X = P): 230 | var answer5 = analyser.ask('P(c|d,x)'); 231 | print(answer5); // P(c|d,x) -> C = T | D = P, X = P -> 0.11210762331838567 (0.006750000000000001) >> 1121.08% 232 | 233 | // Ask the probability to have cancer with a negative diagnosis from the Doctor and a negative exame (D = N, X = N): 234 | var answer6 = analyser.ask('P(c|-d,-x)'); 235 | print(answer6); // P(c|-d,-x) -> C = T | D = N, X = N -> 0.0006932697373894235 (0.00025) >> 6.93% 236 | } 237 | ``` 238 | See a full [example for Bayes Net with Variable Dependency at GitHub][bayes_dependency_example]: 239 | 240 | [bayes_dependency_example]: https://github.com/gmpassos/statistics/blob/master/example/bayesnet_dependency_example.dart 241 | 242 | #### Event Monitoring 243 | 244 | To help to generate the probabilities you can use the `BayesEventMonitor` class and 245 | then build the `BayesianNetwork`: 246 | 247 | ```dart 248 | import 'package:statistics/statistics.dart'; 249 | 250 | void main() { 251 | // Monitor events to then build a Bayesian Network: 252 | // ** Note that this example is NOT USING REAL probabilities for Cancer! 253 | var eventMonitor = BayesEventMonitor('cancer'); 254 | 255 | // The prevalence of Cancer in the population: 256 | // - 1% (10:990): 257 | 258 | for (var i = 0; i < 990; ++i) { 259 | eventMonitor.notifyEvent(['CANCER=false']); 260 | } 261 | 262 | for (var i = 0; i < 10; ++i) { 263 | eventMonitor.notifyEvent(['CANCER=true']); 264 | } 265 | 266 | // The Exam performance when the person have cancer: 267 | // - 90% Sensitivity. 268 | // - 10% false negative (1:9). 269 | 270 | for (var i = 0; i < 9; ++i) { 271 | eventMonitor.notifyEvent(['EXAM=positive', 'CANCER=true']); 272 | } 273 | 274 | for (var i = 0; i < 1; ++i) { 275 | eventMonitor.notifyEvent(['EXAM=negative', 'CANCER=true']); 276 | } 277 | 278 | // The Exam performance when the person doesn't have cancer: 279 | // - 91% Specificity 280 | // - 9% false positive (89:901). 281 | 282 | for (var i = 0; i < 901; ++i) { 283 | eventMonitor.notifyEvent(['EXAM=negative', 'CANCER=false']); 284 | } 285 | for (var i = 0; i < 89; ++i) { 286 | eventMonitor.notifyEvent(['EXAM=positive', 'CANCER=false']); 287 | } 288 | 289 | var bayesNet = eventMonitor.buildBayesianNetwork(); 290 | 291 | var analyser = bayesNet.analyser; 292 | 293 | var answer1 = analyser.ask('P(cancer)'); 294 | print('- Cancer probability without an Exam:'); 295 | print(' $answer1'); // P(cancer) -> CANCER = TRUE | -> 0.01 >> 100.00% 296 | 297 | var answer2 = analyser.ask('P(cancer|exam)'); 298 | print('- Cancer probability with a positive Exam:'); 299 | print(' $answer2'); // P(cancer|exam) -> CANCER = TRUE | EXAM = POSITIVE -> 0.09183673469387756 (0.009000000000000001) >> 918.37% 300 | } 301 | ``` 302 | 303 | See a full [example for Bayes Net at GitHub][bayes_example]: 304 | 305 | [bayes_example]: https://github.com/gmpassos/statistics/blob/master/example/bayesnet_example.dart 306 | 307 | ### CSV 308 | 309 | To generate a [CSV][csv_wikipedia] document, just use the extension [generateCSV][generate_csv] in your data collection. 310 | You can pass the parameter `separator` to change the value separator (default: `,`). 311 | 312 | [generate_csv]:https://pub.dev/documentation/statistics/latest/statistics/IterableMapExtensionCSV/generateCSV.html 313 | [csv_wikipedia]: https://en.wikipedia.org/wiki/Comma-separated_values 314 | 315 | ```dart 316 | import 'package:statistics/statistics.dart'; 317 | 318 | void main() { 319 | var categories = >{ 320 | 'a': [10.0, 20.0, null], 321 | 'b': [100.0, 200.0, 300.0] 322 | }; 323 | 324 | var csv = categories.generateCSV(); 325 | print(csv); 326 | } 327 | ``` 328 | 329 | OUTPUT: 330 | 331 | ```text 332 | #,a,b 333 | 1,10.0,100.0 334 | 2,20.0,200.0 335 | 3,0.0,300.0 336 | ``` 337 | 338 | ## High-Precision Arithmetic 339 | 340 | For high-precision arithmetics you can use `DynamicInt` and `Decimal` classes. 341 | Both implements `DynamicNumber` and are interchangeable in operations. 342 | 343 | - [DynamicInt][api_doc_dynamic_int]: 344 | - An integer that internally uses a native or `BigInt` representation. 345 | - When a native `DynamicInt` operation will overflow `DynamicInt.isSafeInteger` it will expand 346 | the internal representation using a `BigInt`. 347 | 348 | - [Decimal][api_doc_decimal]: 349 | - A decimal number with variable decimal precision. The precision can be defined in the constructor or is identified 350 | automatically while parsing or converting a number to `Decimal`. 351 | - If an operation needs more precision to correctly represent a `Decimal` the precision will be expanded. 352 | - The internal representation uses a `DynamicInt`. 353 | 354 | The main motivation of this high-Precision Arithmetic implementation is to have an internal representation that 355 | avoid the use of `BigInt` unless is really needed, avoiding slow `BigInt` operations and extra memory. Also, 356 | this implementation allows `power` with high precision and `power` with decimal exponents, what is not present 357 | int many libraries. 358 | 359 | Example: 360 | 361 | ```dart 362 | import 'package:statistics/statistics.dart'; 363 | 364 | void main(){ 365 | var n1 = DynamicInt.fromInt(123) + Decimal.parse('0.456'); 366 | print(n1); // 123.456 367 | 368 | var n2 = Decimal.parse('0.1') + Decimal.parse('0.2'); 369 | print(n2); // 0.3 370 | 371 | // Using `toDecimal` extension to convert an `int` to `Decimal`: 372 | var n3 = 123.toDecimal().powerInt(41); // power with an integer exponent. 373 | print(n3); // 48541095000524544750127162673405880068636916264012200797813591925035550682238127143323.0 374 | 375 | var n4 = 2.toDecimal().powerDouble(2.2); // power with a double exponent. 376 | print(n4); // 4.594793419988 377 | 378 | // Using `toDynamicInt` extension to convert an `int` to `DynamicInt`: 379 | var n5 = 2.toDynamicInt().powerInt(-1); // power with a negative exponent. 380 | print(n5); // 0.5 381 | } 382 | ``` 383 | 384 | See the [API Documentation][api_doc] for a full documentation of [DynamicInt][api_doc_dynamic_int] and 385 | [Decimal][api_doc_decimal]. 386 | 387 | [api_doc_dynamic_int]: https://pub.dev/documentation/statistics/latest/statistics/DynamicInt-class.html 388 | [api_doc_decimal]: https://pub.dev/documentation/statistics/latest/statistics/Decimal-class.html 389 | 390 | ## Tools 391 | 392 | Parsers: 393 | - [parseDouble](https://pub.dev/documentation/statistics/latest/statistics/parseDouble.html) 394 | - [parseInt](https://pub.dev/documentation/statistics/latest/statistics/parseInt.html) 395 | - [parseNum](https://pub.dev/documentation/statistics/latest/statistics/parseNum.html) 396 | - [parseDateTime](https://pub.dev/documentation/statistics/latest/statistics/parseDateTime.html) 397 | - *All parses accepts a `dynamic` value as input and have a default value parameter `def`.* 398 | 399 | Formatters: 400 | - [formatDecimal](https://pub.dev/documentation/statistics/latest/statistics/formatDecimal.html) 401 | - [DateTimeExtension.formatToYMD](https://pub.dev/documentation/statistics/latest/statistics/DateTimeExtension/formatToYMD.html) 402 | - [DateTimeExtension.formatToYMDHm](https://pub.dev/documentation/statistics/latest/statistics/DateTimeExtension/formatToYMDHm.html) 403 | - [DateTimeExtension.formatToYMDHms](https://pub.dev/documentation/statistics/latest/statistics/DateTimeExtension/formatToYMDHms.html) 404 | - [DateTimeExtension.formatToYMDHmZ](https://pub.dev/documentation/statistics/latest/statistics/DateTimeExtension/formatToYMDHmZ.html) 405 | - [DateTimeExtension.formatToYMDHmsZ](https://pub.dev/documentation/statistics/latest/statistics/DateTimeExtension/formatToYMDHmsZ.html) 406 | 407 | Extension: 408 | - [StringExtension.splitLines](https://pub.dev/documentation/statistics/latest/statistics/StringExtension/splitLines.html) 409 | - [StringExtension.splitColumns](https://pub.dev/documentation/statistics/latest/statistics/StringExtension/splitColumns.html) 410 | - [StringExtension.containsAny](https://pub.dev/documentation/statistics/latest/statistics/StringExtension/containsAny.html) 411 | - [IterableIterableExtension.toKeysMap](https://pub.dev/documentation/statistics/latest/statistics/IterableIterableExtension/toKeysMap.html) 412 | - [IterableStringExtension.filterLines](https://pub.dev/documentation/statistics/latest/statistics/IterableStringExtension/filterLines.html) 413 | - [IterableStringExtension.toIntsList](https://pub.dev/documentation/statistics/latest/statistics/IterableStringExtension/toIntsList.html) 414 | - [IterableStringExtension.toDoublesList](https://pub.dev/documentation/statistics/latest/statistics/IterableStringExtension/toDoublesList.html) 415 | - [IterableStringExtension.toNumsList](https://pub.dev/documentation/statistics/latest/statistics/IterableStringExtension/toNumsList.html) 416 | 417 | 418 | See the [API Documentation][api_doc] for a full list of functions, extension and classes. 419 | 420 | ## data_serializer 421 | 422 | The `statistics` package exports the package [data_serializer][data_serializer] to help the handling of primitives, data, bytes and files. 423 | 424 | - ***Some extension in `data_serializer` were originally in the `statistics` package.*** 425 | 426 | [data_serializer]: https://pub.dev/packages/data_serializer 427 | 428 | ## Test Coverage 429 | 430 | [![Codecov](https://img.shields.io/codecov/c/github/gmpassos/statistics)](https://app.codecov.io/gh/gmpassos/statistics) 431 | 432 | This package aims to always have a high test coverage percentage, over 95%. 433 | With that the package can be a reliable tool to support your important projects. 434 | 435 | ## Source 436 | 437 | The official source code is [hosted @ GitHub][github_async_field]: 438 | 439 | - https://github.com/gmpassos/statistics 440 | 441 | [github_async_field]: https://github.com/gmpassos/statistics 442 | 443 | # Features and bugs 444 | 445 | Please file feature requests and bugs at the [issue tracker][tracker]. 446 | 447 | # Contribution 448 | 449 | Any help from the open-source community is always welcome and needed: 450 | 451 | - Found an issue? 452 | - Please fill a bug report with details. 453 | - Wish a feature? 454 | - Open a feature request with use cases. 455 | - Are you using and liking the project? 456 | - Promote the project: create an article, do a post or make a donation. 457 | - Are you a developer? 458 | - Fix a bug and send a pull request. 459 | - Implement a new feature. 460 | - Improve the Unit Tests. 461 | - Have you already helped in any way? 462 | - **Many thanks from me, the contributors and everybody that uses this project!** 463 | 464 | *If you donate 1 hour of your time, you can contribute a lot, 465 | because others will do the same, just be part and start with your 1 hour.* 466 | 467 | [tracker]: https://github.com/gmpassos/statistics/issues 468 | 469 | # Author 470 | 471 | Graciliano M. Passos: [gmpassos@GitHub][github]. 472 | 473 | [github]: https://github.com/gmpassos 474 | 475 | ## License 476 | 477 | [Apache License - Version 2.0][apache_license] 478 | 479 | [apache_license]: https://www.apache.org/licenses/LICENSE-2.0.txt 480 | 481 | ## See Also 482 | 483 | Take a look at [SciDart][scidart], an experimental cross-platform scientific library for Dart by [Angelo Polotto](https://github.com/polotto). 484 | 485 | [scidart]: https://pub.dev/packages/scidart 486 | --------------------------------------------------------------------------------