-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSparseInteger.cs
461 lines (393 loc) · 16.2 KB
/
SparseInteger.cs
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
using System;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.InteropServices;
namespace Oeis.A002845
{
/// <summary>
/// An value of this type represents a non-negative integer that can be very large (like those that occur as
/// numeric values of high power towers, much larger than those that can be represented by <see cref="BigInteger"/>
/// within feasible memory limits), subject to a condition that either:
/// <list type="bullet">
/// <item>
/// the number fits into <see cref="ulong"/> type verbatim, or
/// </item>
/// <item>
/// the number of 1's in its binary form is a moderate number (such that an array of that size can be allocated),
/// and each of the positions of 1's in its binary form is, recursively, a number that can be represented
/// by <see cref="SparseInteger"/>.
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// <para>This type is an immutable value type.</para>
/// <para>
/// An value of this type stores its numeric value as a sorted array of positions of 1's in its binary form,
/// where each position is stored, recursively, as <see cref="SparseInteger"/>, except that the numeric value
/// is stored verbatim in the field <see cref="value"/> of type <see cref="ulong"/> if it fits into that type
/// (in the latter case property <see cref="IsSmall"/> returns <c>true</c>).
/// </para>
/// </remarks>
[StructLayout(LayoutKind.Auto)]
public readonly partial struct SparseInteger : IEquatable<SparseInteger>, IComparable<SparseInteger>
{
/// <summary>
/// The numeric value of this value if it fits into <see cref="ulong"/> type, otherwise 0.
/// </summary>
private readonly ulong value;
/// <summary>
/// An sorted array of positions of 1's in the binary form of this number,
/// stored from lowest (least significant) to highest (most significant).
/// </summary>
/// <remarks>
/// If this field is <c>null</c>, then the field <see cref="value"/> of type <see cref="ulong"/> is used
/// to represent such "small" number verbatim.
/// </remarks>
private readonly SparseInteger[] positions;
/// <summary>
/// Initializes a value of <see cref="SparseInteger"/> type,
/// from either:
/// <list type="bullet">
/// <item>
/// a list of positions of 1's in its binary form (provided through <paramref name="positions"/>) or
/// </item>
/// <item>
/// its verbatim representation in <see cref="ulong"/> type (provided through <paramref name="value"/>).
/// </item>
/// </list>
/// </summary>
/// <remarks>At most one of the parameters can have a non-default value.</remarks>
private SparseInteger(SparseInteger[] positions = default, ulong value = default)
{
Debug.Assert(value == 0 || positions == null);
this.value = value;
this.positions = positions;
}
public static implicit operator SparseInteger(ulong x) => new SparseInteger(value: x);
/// <summary>
/// Gets a boolean value indicating whether this value stores its numeric value verbatim
/// in the field <see cref="value"/>. We refer to such values as "small".
/// </summary>
private bool IsSmall => this.positions == null;
/// <summary>
/// Returns the number of 1's in binary form of <paramref name="value"/> (i.e. the sum of its binary digits).
/// </summary>
/// <remarks>
/// This is a classic implementation: https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation.
/// </remarks>
private static byte GetHammingWeight(ulong value)
{
value = (value & 0x5555555555555555UL) + ((value >> 0x01) & 0x5555555555555555UL);
value = (value & 0x3333333333333333UL) + ((value >> 0x02) & 0x3333333333333333UL);
value = (value & 0x0F0F0F0F0F0F0F0FUL) + ((value >> 0x04) & 0x0F0F0F0F0F0F0F0FUL);
value = (value & 0x00FF00FF00FF00FFUL) + ((value >> 0x08) & 0x00FF00FF00FF00FFUL);
value = (value & 0x0000FFFF0000FFFFUL) + ((value >> 0x10) & 0x0000FFFF0000FFFFUL);
value = (value & 0x00000000FFFFFFFFUL) + ((value >> 0x20) & 0x00000000FFFFFFFFUL);
return (byte) value;
}
/// <summary>Gets a sorted array of positions of 1's in binary form of this number.</summary>
/// <remarks>
/// This property can be used both on "small" and "large" numbers (<see cref="IsSmall"/>).
/// </remarks>
private SparseInteger[] Positions
{
get
{
if (!this.IsSmall)
{
return this.positions;
}
var v = this.value;
var positions = new SparseInteger[GetHammingWeight(v)];
byte position = 0;
byte index = 0;
while (v > 0)
{
if ((v & 1) == 1)
{
positions[index++] = position;
}
v >>= 1;
position++;
}
return positions;
}
}
#region Hash, equality and comparison
/// <summary>
/// Returns a hash code for this value.
/// </summary>
public override int GetHashCode()
{
if (this.IsSmall)
{
return HashCode.Combine(this.value);
}
var hash = new HashCode();
foreach (var position in this.positions)
{
hash.Add(position);
}
return hash.ToHashCode();
}
/// <summary>
/// Determines whether this value of type <see cref="SparseInteger"/> is equal to another value
/// of the same type.
/// </summary>
/// <param name="other">Another value to compare this value with.</param>
public bool Equals(SparseInteger other) => this.CompareTo(other) == 0;
/// <summary>
/// Determines whether this value of type <see cref="SparseInteger"/> is equal to a provided argument
/// of type <see cref="ulong"/>.
/// </summary>
/// <param name="other">A <see cref="ulong"/> value to compare this value with.</param>
public bool Equals(ulong other) => this.IsSmall && this.value == other;
/// <summary>
/// Determines whether this value of type <see cref="SparseInteger"/> is equal to another boxed value
/// of the same type.
/// </summary>
/// <returns>
/// <c>true</c> if parameter <paramref name="obj"/> is a boxed value of the type
/// <see cref="SparseInteger"/> and this value is equal to that value; otherwise, <c>false</c>.
/// </returns>
/// <param name="obj">
/// An object to compare this value with. Can be <c>null</c> (in which case the method returns <c>false</c>).
/// </param>
public override bool Equals(object obj) => obj is SparseInteger other && this.Equals(other);
/// <summary>
/// Compares this value of type <see cref="SparseInteger"/> with another value of the same type.
/// </summary>
/// <returns>
/// An <see cref="int"/> value indicating the relative order of the values being compared:
/// <list type="bullet">
/// <item>
/// A negative value means that the current value is numerically less than the <paramref name="other"/>.
/// </item>
/// <item>
/// The value 0 means that the current value is numerically equal to the <paramref name="other"/>.
/// </item>
/// <item>
/// A positive value means that the current value is numerically greater than the <paramref name="other"/>.
/// </item>
/// </list>
/// </returns>
/// <param name="other">A value of type <see cref="SparseInteger"/> to compare with this value.</param>
/// <remarks>
/// </remarks>
public int CompareTo(SparseInteger other)
{
if (this.IsSmall)
{
if (other.IsSmall)
{
return this.value.CompareTo(other.value);
}
// values that fit into ulong type are always stored in the value field
return -1;
}
if (other.IsSmall)
{
// values that fit into ulong type are always stored in the value field
return 1;
}
// fast reference equality check
if (this.positions == other.positions)
{
return 0;
}
// Compare bitwise starting from the highest (most significant) bit
for (int thisPosition = this.positions.Length - 1, otherPosition = other.positions.Length - 1;;)
{
var result = this.positions[thisPosition].CompareTo(other.positions[otherPosition]);
if (result != 0)
{
return result;
}
if (--thisPosition < 0)
{
return
--otherPosition < 0
? 0
: -1; // all compared bits are identical, but the other number has more bits
}
if (--otherPosition < 0)
{
// all compared bits are identical, but this number has more bits
return 1;
}
}
}
public static bool operator ==(SparseInteger x, ulong y) => x.Equals(y);
public static bool operator !=(SparseInteger x, ulong y) => !x.Equals(y);
public static bool operator ==(SparseInteger x, SparseInteger y) => x.CompareTo(y) == 0;
public static bool operator !=(SparseInteger x, SparseInteger y) => x.CompareTo(y) != 0;
public static bool operator <(SparseInteger x, SparseInteger y) => x.CompareTo(y) < 0;
public static bool operator >(SparseInteger x, SparseInteger y) => x.CompareTo(y) > 0;
public static bool operator <=(SparseInteger x, SparseInteger y) => x.CompareTo(y) <= 0;
public static bool operator >=(SparseInteger x, SparseInteger y) => x.CompareTo(y) >= 0;
#endregion
#region Arithmetic
/// <summary>
/// Returns this number plus 1.
/// </summary>
private SparseInteger PlusOne()
{
if (this.IsSmall)
{
return this.value < ulong.MaxValue
? this.value + 1
: new SparseInteger(new SparseInteger[] {64});
}
var positionsNew = ArrayHelpers.RemoveElement(this.positions, (SparseInteger) 0, out bool removed);
return removed
// the bit was not set, set it
? new SparseInteger(positionsNew) + 2
// the bit was set, carry to the next position
: new SparseInteger(ArrayHelpers.InsertElement(positionsNew, (SparseInteger) 0));
}
/// <summary>
/// Adds numbers <paramref name="x"/> and <paramref name="y"/> and returns the result.
/// </summary>
public static SparseInteger operator +(SparseInteger x, SparseInteger y)
{
if (x == 0)
{
return y;
}
if (y == 0)
{
return x;
}
if (x.IsSmall && y.IsSmall)
{
var sum = x.value + y.value;
if (sum > x.value) // if no overflow
{
return sum;
}
}
var xPositions = x.Positions;
var yPositions = y.Positions;
// swap if necessary to make yPositions shorter
if (yPositions.Length > xPositions.Length)
{
(xPositions, yPositions) = (yPositions, xPositions);
}
foreach (var position in yPositions)
{
var xPositionsNew = ArrayHelpers.RemoveElement(xPositions, position, out bool removed);
var x1 = position.PlusOne();
xPositions = removed
? (new SparseInteger(xPositionsNew) + x1.Exp2()).positions
: ArrayHelpers.InsertElement(xPositions, position);
}
return new SparseInteger(xPositions);
}
/// <summary>
/// Multiplies numbers <paramref name="x"/> and <paramref name="y"/> and returns the result.
/// </summary>
public static SparseInteger operator *(SparseInteger x, SparseInteger y)
{
if (x == 0 || y == 0)
{
return 0;
}
if (x == 1)
{
return y;
}
if (y == 1)
{
return x;
}
if (x.IsSmall && y.IsSmall)
{
var product = x.value * y.value;
if (product / y.value == x.value) // if no overflow
{
return product;
}
}
SparseInteger result = 0;
foreach (var position in y.Positions)
{
result += x.MultiplyByExp2(position);
}
return result;
}
#endregion
#region Powers and logarithms
/// <summary>
/// Returns 2 raised to the power of this number.
/// </summary>
public SparseInteger Exp2()
{
return this.IsSmall && this.value < 64
? 1UL << (byte) this.value
: new SparseInteger(new[] {this});
}
/// <summary>
/// Returns a base-2 logarithm of this number if is an exact power of 2.
/// </summary>
/// <exception cref="InvalidOperationException">This number is not an exact power of 2.</exception>
public SparseInteger Log2()
{
if (!this.IsSmall)
{
if (this.positions.Length != 1)
{
throw new InvalidOperationException("This number is not an exact power of two.");
}
return this.positions[0];
}
var v = this.value;
if (GetHammingWeight(v) != 1)
{
throw new InvalidOperationException("This number is not an exact power of two.");
}
byte log2 = 0;
while ((v & 1) == 0)
{
v >>= 1;
log2++;
}
return log2;
}
#endregion
/// <summary>
/// The result of <c>this.MultiplyByAntiLog2(x)</c> is equivalent to <c>this * x.Exp2()</c>,
/// but can be faster in many cases.
/// </summary>
private SparseInteger MultiplyByExp2(SparseInteger power)
{
if (this == 0 || power == 0)
{
return this;
}
if (this.IsSmall && power.IsSmall && power.value < 63)
{
var result = this.value << (byte) power.value;
if (result >> (byte) power.value == this.value) // if no overflow
{
return result;
}
}
var positions = this.Positions;
var positionsNew = new SparseInteger[positions.Length];
for (int i = 0; i < positions.Length; i++)
{
positionsNew[i] = positions[i] + power;
}
return new SparseInteger(positionsNew);
}
/// <summary>
/// Raises this value to the power <paramref name="exponent"/>.
/// Requires this value to be an exact power of 2.
/// </summary>
/// <exception cref="InvalidOperationException">This value is not an exact power of 2.</exception>
public SparseInteger Power(SparseInteger exponent)
{
return (this.Log2() * exponent).Exp2();
}
}
}