-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathValueCollection.cs
More file actions
136 lines (117 loc) · 4.96 KB
/
Copy pathValueCollection.cs
File metadata and controls
136 lines (117 loc) · 4.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace JSONstat.IO;
/// <summary>
/// The dataset <c>value</c> property, which on the wire is either a dense
/// array (with <c>null</c> holes) or a sparse object keyed by linear index.
/// See <c>../wiki/sparse-cubes.md</c>.
/// </summary>
[JsonConverter(typeof(ValueCollectionConverter))]
public sealed class ValueCollection
{
private readonly double?[]? _dense;
private readonly Dictionary<string, double?>? _sparse;
private ValueCollection(double?[]? dense, Dictionary<string, double?>? sparse)
{
_dense = dense;
_sparse = sparse;
IsSparse = dense is null;
}
/// <summary>True when the value is stored as a sparse object.</summary>
public bool IsSparse { get; }
/// <summary>The dense array, when not sparse; otherwise <c>null</c>.</summary>
internal double?[]? DenseValues => _dense;
/// <summary>The sparse map, when sparse; otherwise <c>null</c>.</summary>
internal IReadOnlyDictionary<string, double?>? SparseValues => _sparse;
/// <summary>Builds a dense value collection.</summary>
public static ValueCollection DenseOf(double?[] values) => new ValueCollection(values, null);
/// <summary>Builds a sparse value collection.</summary>
public static ValueCollection SparseOf(Dictionary<string, double?> values) => new ValueCollection(null, values);
/// <summary>Builds an empty dense value collection.</summary>
public static ValueCollection Empty() => new ValueCollection(System.Array.Empty<double?>(), null);
/// <summary>Returns the value at the given linear index, or <c>null</c> when missing.</summary>
public double? GetValue(int linear)
{
if (IsSparse)
{
return _sparse != null && _sparse.TryGetValue(linear.ToString(CultureInfo.InvariantCulture), out var d)
? d
: (double?)null;
}
if (_dense is null) return null;
return linear >= 0 && linear < _dense.Length ? _dense[linear] : (double?)null;
}
/// <summary>Enumerates <paramref name="count"/> values in linear order.</summary>
public IEnumerable<double?> Enumerate(int count)
{
for (int i = 0; i < count; i++) yield return GetValue(i);
}
/// <summary>Converts this collection to dense form (used by Dice).</summary>
public ValueCollection ToDense(int count)
{
if (!IsSparse) return this;
var arr = new double?[count];
for (int i = 0; i < count; i++) arr[i] = GetValue(i);
return DenseOf(arr);
}
}
/// <summary>Reads/writes <see cref="ValueCollection"/> (array or object).</summary>
internal sealed class ValueCollectionConverter : JsonConverter<ValueCollection>
{
public override ValueCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
var list = new List<double?>();
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
list.Add(reader.TokenType == JsonTokenType.Null ? (double?)null : reader.GetDouble());
}
return ValueCollection.DenseOf(list.ToArray());
}
if (reader.TokenType == JsonTokenType.StartObject)
{
var dict = new Dictionary<string, double?>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject) break;
string key = reader.GetString()!;
if (!reader.Read()) throw new JsonException("Unexpected end of value object.");
dict[key] = reader.TokenType == JsonTokenType.Null ? (double?)null : reader.GetDouble();
}
return ValueCollection.SparseOf(dict);
}
throw new JsonException("JSON-stat 'value' must be an array or an object.");
}
public override void Write(Utf8JsonWriter writer, ValueCollection value, JsonSerializerOptions options)
{
if (value.IsSparse && value.SparseValues != null)
{
writer.WriteStartObject();
foreach (var kv in value.SparseValues.OrderBy(p => ParseKey(p.Key)))
{
writer.WritePropertyName(kv.Key);
WriteNumber(writer, kv.Value);
}
writer.WriteEndObject();
return;
}
writer.WriteStartArray();
if (value.DenseValues != null)
{
foreach (var v in value.DenseValues) WriteNumber(writer, v);
}
writer.WriteEndArray();
}
private static int ParseKey(string key) =>
int.TryParse(key, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n) ? n : 0;
private static void WriteNumber(Utf8JsonWriter writer, double? v)
{
if (v.HasValue) writer.WriteNumberValue(v.Value);
else writer.WriteNullValue();
}
}