blob: ab038b036aaef4686a3917b8e66ddc515427b10f [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:math' as math;
import 'package:analysis_server/src/status/pages.dart';
import 'package:analyzer/src/generated/utilities_general.dart';
import 'output_utilities.dart';
/// https://en.wikipedia.org/wiki/Average#Arithmetic_mean
class ArithmeticMeanComputer {
final String name;
int sum = 0;
int count = 0;
int? min;
int? max;
ArithmeticMeanComputer(this.name);
double get mean => sum / count;
/// Add the data from the given [computer] to this computer.
void addData(ArithmeticMeanComputer computer) {
sum += computer.sum;
count += computer.count;
min = _min(min, computer.min);
max = _max(max, computer.max);
}
void addValue(int val) {
sum += val;
count++;
min = _min(min, val);
max = _max(max, val);
}
void clear() {
sum = 0;
count = 0;
}
/// Set the state of this computer to the state recorded in the decoded JSON
/// [map].
void fromJson(Map<String, dynamic> map) {
sum = map['sum'] as int;
count = map['count'] as int;
min = map['min'] as int?;
max = map['max'] as int?;
}
/// Return a map used to represent this computer in a JSON structure.
Map<String, dynamic> toJson() {
return {
'sum': sum,
'count': count,
if (min != null) 'min': min,
if (max != null) 'max': max,
};
}
int? _max(int? first, int? second) {
if (first == null) {
return second;
} else if (second == null) {
return first;
} else {
return math.max(first, second);
}
}
int? _min(int? first, int? second) {
if (first == null) {
return second;
} else if (second == null) {
return first;
} else {
return math.min(first, second);
}
}
}
/// A simple counter class. A [String] name is passed to name the counter. Each
/// time something is counted, a non-null, non-empty [String] key is passed to
/// [count] to increment the amount from zero. [printCounterValues] is provided
/// to have a [String] summary of the generated counts, example:
///
/// ```
/// Counts for 'counter example':
/// [bucket-1] 60 (60.0%)
/// [bucket-2] 25 (25.0%)
/// [bucket-3] 5 (5.0%)
/// [bucket-4] 10 (10.0%)
/// ```
class Counter {
final String name;
final Map<String, int> _buckets = {};
int _totalCount = 0;
Counter(this.name);
/// Return a copy of all the current count data, this getter copies and
/// returns the data to ensure that the data is only modified with the public
/// accessors in this class.
Map<String, int> get map => Map.from(_buckets);
int get totalCount => _totalCount;
/// Add the data from the given [counter] to this counter.
void addData(Counter counter) {
for (var entry in counter._buckets.entries) {
var bucket = entry.key;
_buckets[bucket] = (_buckets[bucket] ?? 0) + entry.value;
}
_totalCount += counter._totalCount;
}
void clear() {
_buckets.clear();
_totalCount = 0;
}
void count(String id, [int countNumber = 1]) {
assert(id.isNotEmpty && 1 <= countNumber);
_buckets.update(
id,
(value) => value + countNumber,
ifAbsent: () => countNumber,
);
_totalCount += countNumber;
}
/// Set the state of this counter to the state recorded in the decoded JSON
/// [map].
void fromJson(Map<String, dynamic> map) {
for (var entry in (map['buckets'] as Map<String, dynamic>).entries) {
_buckets[entry.key] = entry.value as int;
}
_totalCount = map['totalCount'] as int;
}
int getCountOf(String id) => _buckets[id] ?? 0;
void printCounterValues() {
if (_totalCount > 0) {
var table = [
['', 'count', 'percent']
];
var entries = _buckets.entries.toList();
entries.sort((first, second) => second.value - first.value);
for (var entry in entries) {
var id = entry.key;
var count = entry.value;
table.add(
[id, count.toString(), printPercentage(count / _totalCount, 2)]);
}
printTable(table);
} else {
print('<no counts>');
}
}
/// Return a map used to represent this counter in a JSON structure.
Map<String, dynamic> toJson() {
return {
'buckets': _buckets,
'totalCount': _totalCount,
};
}
}
class DistributionComputer {
/// The buckets in which values are counted: [0..9], [10..19], ... [100..].
List<int> buckets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
/// Add the data from the given [computer] to this computer.
void addData(DistributionComputer computer) {
for (var i = 0; i < buckets.length; i++) {
buckets[i] += computer.buckets[i];
}
}
/// Add a millisecond value to the list of buckets.
void addValue(int value) {
var bucket = math.min(value ~/ 10, buckets.length - 1);
buckets[bucket]++;
}
/// Return a textual representation of the distribution.
String displayString() {
var buffer = StringBuffer();
for (var i = 0; i < buckets.length; i++) {
if (i > 0) {
buffer.write(' ');
}
buffer.write('[');
buffer.write(i * 10);
buffer.write('] ');
buffer.write(buckets[i]);
}
return buffer.toString();
}
/// Set the state of this computer to the state recorded in the decoded JSON
/// [map].
void fromJson(Map<String, dynamic> map) {
buckets = map['buckets'] as List<int>;
}
/// Return a map used to represent this computer in a JSON structure.
Map<String, dynamic> toJson() {
return {
'buckets': buckets,
};
}
}
/// A computer for the mean reciprocal rank. The MRR as well as the MRR only
/// if the item was in the top 5 in the list see [MAX_RANK], is computed.
/// https://en.wikipedia.org/wiki/Mean_reciprocal_rank.
class MeanReciprocalRankComputer {
static final int MAX_RANK = 5;
final String name;
double _sum = 0;
double _sum_5 = 0;
int _count = 0;
MeanReciprocalRankComputer(
this.name,
);
int get count => _count;
double get mrr {
if (count == 0) {
return 0;
}
return _sum / count;
}
double get mrr_5 {
if (count == 0) {
return 0;
}
return _sum_5 / count;
}
/// Add the data from the given [computer] to this computer.
void addData(MeanReciprocalRankComputer computer) {
_sum += computer._sum;
_sum_5 += computer._sum_5;
_count += computer._count;
}
void addRank(int rank) {
if (rank != 0) {
_sum += 1 / rank;
if (rank <= MAX_RANK) {
_sum_5 += 1 / rank;
}
}
_count++;
}
void clear() {
_sum = 0;
_sum_5 = 0;
_count = 0;
}
/// Set the state of this computer to the state recorded in the decoded JSON
/// [map].
void fromJson(Map<String, dynamic> map) {
_sum = map['sum'] as double;
_sum_5 = map['sum_5'] as double;
_count = map['count'] as int;
}
void printMean() {
print('Mean Reciprocal Rank \'$name\' (total = $count)');
print('mrr = ${mrr.toStringAsFixed(6)} '
'(inverse = ${(1 / mrr).toStringAsFixed(3)})');
print('mrr_5 = ${mrr_5.toStringAsFixed(6)} '
'(inverse = ${(1 / mrr_5).toStringAsFixed(3)})');
}
/// Return a map used to represent this computer in a JSON structure.
Map<String, dynamic> toJson() {
return {
'sum': _sum,
'sum_5': _sum_5,
'count': _count,
};
}
}
/// An immutable class to represent the placement in some list, for example '2nd
/// place out of 5'.
class Place {
/// A 1-indexed place in a list
final int _numerator;
/// The total number of possible places.
final int _denominator;
const Place(this._numerator, this._denominator)
: assert(_numerator > 0),
assert(_denominator >= _numerator);
/// Return an instance extracted from the decoded JSON [map].
factory Place.fromJson(Map<String, dynamic> map) {
return Place(map['numerator'] as int, map['denominator'] as int);
}
const Place.none()
: _numerator = 0,
_denominator = 0;
int get denominator => _denominator;
@override
int get hashCode => JenkinsSmiHash.hash2(_numerator, _denominator);
int get numerator => _numerator;
int get rank => _numerator;
@override
bool operator ==(dynamic other) =>
other is Place &&
_numerator == other._numerator &&
_denominator == other._denominator;
/// Return a map used to represent this place in a JSON structure.
Map<String, dynamic> toJson() {
return {
'numerator': _numerator,
'denominator': _denominator,
};
}
}