| | | 1 | | // ----------------------------------------------------------------------- |
| | | 2 | | // <copyright file="DefaultRandomGenerator.cs" company="Stéphane ANDRE"> |
| | | 3 | | // Copyright (c) Stéphane ANDRE. All rights reserved. |
| | | 4 | | // </copyright> |
| | | 5 | | // ----------------------------------------------------------------------- |
| | | 6 | | |
| | | 7 | | using System; |
| | | 8 | | using System.Collections.Generic; |
| | | 9 | | using System.Linq; |
| | | 10 | | using MyNet.Primitives.Helpers; |
| | | 11 | | |
| | | 12 | | namespace MyNet.Generator; |
| | | 13 | | |
| | | 14 | | /// <summary> |
| | | 15 | | /// Current implementation of <see cref="IRandomGenerator"/>. |
| | | 16 | | /// </summary> |
| | | 17 | | public sealed class DefaultRandomGenerator(IRandomSource source) : IRandomGenerator |
| | | 18 | | { |
| | 19 | 19 | | private readonly IRandomSource _source = source ?? throw new ArgumentNullException(nameof(source)); |
| | | 20 | | |
| | | 21 | | /// <inheritdoc /> |
| | 609 | 22 | | public bool Bool() => Weighted(0.5f); |
| | | 23 | | |
| | | 24 | | /// <inheritdoc /> |
| | | 25 | | public bool Weighted(float probability) |
| | 1215 | 26 | | => probability is < 0.0f or > 1.0f |
| | 1215 | 27 | | ? throw new ArgumentOutOfRangeException(nameof(probability), probability, "Probability must be between 0.0 a |
| | 1215 | 28 | | : _source.NextDouble() < probability; |
| | | 29 | | |
| | | 30 | | /// <inheritdoc /> |
| | | 31 | | public byte[] Bytes(int count) |
| | | 32 | | { |
| | 6 | 33 | | if (count < 0) |
| | 3 | 34 | | throw new ArgumentOutOfRangeException(nameof(count), count, "Count cannot be negative."); |
| | | 35 | | |
| | 3 | 36 | | var buffer = new byte[count]; |
| | 3 | 37 | | _source.NextBytes(buffer); |
| | 3 | 38 | | return buffer; |
| | | 39 | | } |
| | | 40 | | |
| | | 41 | | /// <inheritdoc /> |
| | | 42 | | public T Item<T>(IReadOnlyCollection<T> list) |
| | | 43 | | { |
| | 216 | 44 | | ArgumentNullException.ThrowIfNull(list); |
| | | 45 | | |
| | 216 | 46 | | var finalList = list as IReadOnlyList<T> ?? [.. list]; |
| | 216 | 47 | | return finalList.Count == 0 ? throw new ArgumentException("The list cannot be empty.", nameof(list)) : finalList |
| | | 48 | | } |
| | | 49 | | |
| | | 50 | | /// <inheritdoc /> |
| | | 51 | | public IEnumerable<T> Shuffle<T>(IEnumerable<T> source) |
| | | 52 | | { |
| | 36 | 53 | | ArgumentNullException.ThrowIfNull(source); |
| | | 54 | | |
| | 36 | 55 | | var buffer = source.ToList(); |
| | | 56 | | |
| | 384 | 57 | | for (var i = buffer.Count - 1; i > 0; i--) |
| | | 58 | | { |
| | 156 | 59 | | var j = _source.NextInt32(0, i + 1); |
| | 156 | 60 | | (buffer[i], buffer[j]) = (buffer[j], buffer[i]); |
| | | 61 | | } |
| | | 62 | | |
| | 36 | 63 | | return buffer; |
| | | 64 | | } |
| | | 65 | | |
| | | 66 | | /// <inheritdoc /> |
| | | 67 | | public IReadOnlyList<T> Subset<T>(IReadOnlyCollection<T> list, int count) |
| | | 68 | | { |
| | 36 | 69 | | ArgumentNullException.ThrowIfNull(list); |
| | | 70 | | |
| | 36 | 71 | | return count < 0 || count > list.Count |
| | 36 | 72 | | ? throw new ArgumentOutOfRangeException(nameof(count), count, $"Count must be between 0 and the list size ({ |
| | 36 | 73 | | : [.. Shuffle(list).Take(count)]; |
| | | 74 | | } |
| | | 75 | | |
| | | 76 | | /// <inheritdoc /> |
| | | 77 | | public DateTime Date(DateTime min, DateTime max) |
| | | 78 | | { |
| | 378 | 79 | | if (min > max) |
| | 3 | 80 | | throw new ArgumentOutOfRangeException(nameof(min), "min must be less than or equal to max."); |
| | | 81 | | |
| | 375 | 82 | | var range = max.Ticks - min.Ticks; |
| | 375 | 83 | | if (range == 0) |
| | 3 | 84 | | return min; |
| | | 85 | | |
| | 372 | 86 | | var offset = (long)(_source.NextDouble() * range); |
| | 372 | 87 | | return new(min.Ticks + offset, min.Kind); |
| | | 88 | | } |
| | | 89 | | |
| | | 90 | | /// <inheritdoc /> |
| | | 91 | | public T Enum<T>(params T[] exclude) |
| | | 92 | | where T : struct, Enum |
| | | 93 | | { |
| | 2103 | 94 | | var values = System.Enum.GetValues<T>(); |
| | | 95 | | |
| | 2103 | 96 | | if (exclude is { Length: > 0 }) |
| | 453 | 97 | | values = [.. values.Where(x => !exclude.Contains(x))]; |
| | | 98 | | |
| | 2103 | 99 | | return values.Length == 0 ? throw new ArgumentException($"All values of enum '{typeof(T).Name}' have been exclud |
| | | 100 | | } |
| | | 101 | | |
| | | 102 | | /// <inheritdoc /> |
| | | 103 | | public int Int(int minInclusive = int.MinValue, int maxExclusive = int.MaxValue) |
| | | 104 | | { |
| | 303 | 105 | | ValidateExclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 303 | 106 | | return _source.NextInt32(minInclusive, maxExclusive); |
| | | 107 | | } |
| | | 108 | | |
| | | 109 | | /// <inheritdoc /> |
| | | 110 | | public int Even(int minInclusive = int.MinValue, int maxExclusive = int.MaxValue) |
| | | 111 | | { |
| | 303 | 112 | | ValidateExclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | | 113 | | |
| | 303 | 114 | | var start = (minInclusive & 1) == 0 ? minInclusive : minInclusive + 1L; |
| | 303 | 115 | | var end = (long)maxExclusive - 1; |
| | | 116 | | |
| | 303 | 117 | | if (start > end) |
| | 3 | 118 | | throw new ArgumentOutOfRangeException(nameof(maxExclusive), "The range does not contain any even number."); |
| | | 119 | | |
| | 300 | 120 | | var count = checked((int)(((end - start) / 2) + 1)); |
| | 300 | 121 | | var offset = _source.NextInt32(0, count); |
| | | 122 | | |
| | 300 | 123 | | return checked((int)(start + (2L * offset))); |
| | | 124 | | } |
| | | 125 | | |
| | | 126 | | /// <inheritdoc /> |
| | | 127 | | public int Odd(int minInclusive = int.MinValue, int maxExclusive = int.MaxValue) |
| | | 128 | | { |
| | 303 | 129 | | ValidateExclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | | 130 | | |
| | 303 | 131 | | var start = (minInclusive & 1) == 1 ? minInclusive : minInclusive + 1L; |
| | 303 | 132 | | var end = (long)maxExclusive - 1; |
| | | 133 | | |
| | 303 | 134 | | if (start > end) |
| | 3 | 135 | | throw new ArgumentOutOfRangeException(nameof(maxExclusive), "The range does not contain any odd number."); |
| | | 136 | | |
| | 300 | 137 | | var count = checked((int)(((end - start) / 2) + 1)); |
| | 300 | 138 | | var offset = _source.NextInt32(0, count); |
| | | 139 | | |
| | 300 | 140 | | return checked((int)(start + (2L * offset))); |
| | | 141 | | } |
| | | 142 | | |
| | | 143 | | /// <inheritdoc /> |
| | | 144 | | public double Double(double minInclusive = int.MinValue, double maxExclusive = int.MaxValue) |
| | | 145 | | { |
| | 300 | 146 | | ValidateExclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 300 | 147 | | return (_source.NextDouble() * (maxExclusive - minInclusive)) + minInclusive; |
| | | 148 | | } |
| | | 149 | | |
| | | 150 | | /// <inheritdoc /> |
| | | 151 | | public decimal Decimal(decimal minInclusive = 0.0m, decimal maxExclusive = 1.0m) |
| | | 152 | | { |
| | 606 | 153 | | ValidateExclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 606 | 154 | | return (Convert.ToDecimal(_source.NextDouble()) * (maxExclusive - minInclusive)) + minInclusive; |
| | | 155 | | } |
| | | 156 | | |
| | | 157 | | /// <inheritdoc /> |
| | | 158 | | public float Float(float minInclusive = 0.0f, float maxExclusive = 1.0f) |
| | | 159 | | { |
| | 600 | 160 | | ValidateExclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 600 | 161 | | return (Convert.ToSingle(_source.NextDouble()) * (maxExclusive - minInclusive)) + minInclusive; |
| | | 162 | | } |
| | | 163 | | |
| | | 164 | | /// <inheritdoc /> |
| | | 165 | | public byte Byte(byte minInclusive = byte.MinValue, byte maxExclusive = byte.MaxValue) |
| | | 166 | | { |
| | 60 | 167 | | ValidateInclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 60 | 168 | | return Convert.ToByte(_source.NextInt32(minInclusive, maxExclusive + 1)); |
| | | 169 | | } |
| | | 170 | | |
| | | 171 | | /// <inheritdoc /> |
| | | 172 | | public sbyte SByte(sbyte minInclusive = sbyte.MinValue, sbyte maxExclusive = sbyte.MaxValue) |
| | | 173 | | { |
| | 60 | 174 | | ValidateInclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 60 | 175 | | return Convert.ToSByte(_source.NextInt32(minInclusive, maxExclusive + 1)); |
| | | 176 | | } |
| | | 177 | | |
| | | 178 | | /// <inheritdoc /> |
| | | 179 | | public uint UInt(uint minInclusive = uint.MinValue, uint maxExclusive = uint.MaxValue) |
| | | 180 | | { |
| | 303 | 181 | | ValidateInclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 303 | 182 | | if (minInclusive == maxExclusive) |
| | 0 | 183 | | return minInclusive; |
| | | 184 | | |
| | 303 | 185 | | var value = (_source.NextDouble() * ((double)maxExclusive - minInclusive + 1)) + minInclusive; |
| | 303 | 186 | | return (uint)Math.Floor(value); |
| | | 187 | | } |
| | | 188 | | |
| | | 189 | | /// <inheritdoc /> |
| | | 190 | | public ulong ULong(ulong minInclusive = ulong.MinValue, ulong maxExclusive = ulong.MaxValue) |
| | | 191 | | { |
| | 300 | 192 | | ValidateInclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 300 | 193 | | if (minInclusive == maxExclusive) |
| | 0 | 194 | | return minInclusive; |
| | | 195 | | |
| | 300 | 196 | | var value = (_source.NextDouble() * ((double)maxExclusive - minInclusive + 1)) + minInclusive; |
| | 300 | 197 | | return (ulong)Math.Floor(value); |
| | | 198 | | } |
| | | 199 | | |
| | | 200 | | /// <inheritdoc /> |
| | | 201 | | public long Long(long minInclusive = long.MinValue, long maxExclusive = long.MaxValue) |
| | | 202 | | { |
| | 303 | 203 | | ValidateInclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 303 | 204 | | if (minInclusive == maxExclusive) |
| | 0 | 205 | | return minInclusive; |
| | | 206 | | |
| | 303 | 207 | | var range = ((decimal)maxExclusive - minInclusive) + 1; |
| | 303 | 208 | | var value = ((decimal)_source.NextDouble() * range) + minInclusive; |
| | 303 | 209 | | return decimal.ToInt64(decimal.Floor(value)); |
| | | 210 | | } |
| | | 211 | | |
| | | 212 | | /// <inheritdoc /> |
| | | 213 | | public short Short(short minInclusive = short.MinValue, short maxExclusive = short.MaxValue) |
| | | 214 | | { |
| | 303 | 215 | | ValidateInclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 303 | 216 | | return minInclusive == maxExclusive ? minInclusive : Convert.ToInt16(_source.NextInt32(minInclusive, maxExclusiv |
| | | 217 | | } |
| | | 218 | | |
| | | 219 | | /// <inheritdoc /> |
| | | 220 | | public ushort UShort(ushort minInclusive = ushort.MinValue, ushort maxExclusive = ushort.MaxValue) |
| | | 221 | | { |
| | 300 | 222 | | ValidateInclusiveRange(minInclusive, maxExclusive, nameof(minInclusive), nameof(maxExclusive)); |
| | 300 | 223 | | return minInclusive == maxExclusive ? minInclusive : Convert.ToUInt16(_source.NextInt32(minInclusive, maxExclusi |
| | | 224 | | } |
| | | 225 | | |
| | | 226 | | /// <inheritdoc /> |
| | | 227 | | public string String(int length, ReadOnlySpan<char> chars = default) |
| | | 228 | | { |
| | 3 | 229 | | if (length < 0) |
| | 0 | 230 | | throw new ArgumentOutOfRangeException(nameof(length), length, "Length cannot be negative."); |
| | | 231 | | |
| | 3 | 232 | | if (chars.IsEmpty) |
| | 3 | 233 | | chars = CharHelper.Alphabet; |
| | | 234 | | |
| | 3 | 235 | | var buffer = new char[length]; |
| | | 236 | | |
| | 54 | 237 | | for (var i = 0; i < length; i++) |
| | 24 | 238 | | buffer[i] = chars[_source.NextInt32(0, chars.Length)]; |
| | | 239 | | |
| | 3 | 240 | | return new(buffer); |
| | | 241 | | } |
| | | 242 | | |
| | | 243 | | /// <inheritdoc /> |
| | 15 | 244 | | public char Char(char minInclusive = char.MinValue, char maxExclusive = char.MaxValue) => minInclusive >= maxExclusi |
| | | 245 | | |
| | | 246 | | /// <inheritdoc /> |
| | 150 | 247 | | public char Letter(bool uppercase = true) => (char)((uppercase ? 'A' : 'a') + _source.NextInt32(0, 26)); |
| | | 248 | | |
| | | 249 | | /// <inheritdoc /> |
| | 150 | 250 | | public char Digit() => (char)('0' + _source.NextInt32(0, 10)); |
| | | 251 | | |
| | | 252 | | /// <inheritdoc /> |
| | | 253 | | public char[] Chars(char minInclusive = char.MinValue, char maxExclusive = char.MaxValue, int count = 5) |
| | | 254 | | { |
| | 3 | 255 | | if (count < 0) |
| | 0 | 256 | | throw new ArgumentOutOfRangeException(nameof(count), count, "Count cannot be negative."); |
| | | 257 | | |
| | 3 | 258 | | var arr = new char[count]; |
| | 30 | 259 | | for (var i = 0; i < count; i++) |
| | 12 | 260 | | arr[i] = Char(minInclusive, maxExclusive); |
| | | 261 | | |
| | 3 | 262 | | return arr; |
| | | 263 | | } |
| | | 264 | | |
| | | 265 | | /// <summary> |
| | | 266 | | /// Validates that min is less than max. Throws an ArgumentOutOfRangeException if the condition is not met. |
| | | 267 | | /// </summary> |
| | | 268 | | /// <param name="min">The minimum value.</param> |
| | | 269 | | /// <param name="max">The maximum value.</param> |
| | | 270 | | /// <param name="minName">The name of the minimum value parameter.</param> |
| | | 271 | | /// <param name="maxName">The name of the maximum value parameter.</param> |
| | | 272 | | /// <typeparam name="T">The type of the values being compared.</typeparam> |
| | | 273 | | /// <exception cref="ArgumentOutOfRangeException">Thrown if min is not less than max.</exception> |
| | | 274 | | private static void ValidateExclusiveRange<T>(T min, T max, string minName, string maxName) |
| | | 275 | | where T : IComparable<T> |
| | | 276 | | { |
| | 2415 | 277 | | if (min.CompareTo(max) >= 0) |
| | 0 | 278 | | throw new ArgumentOutOfRangeException(minName, $"{minName} must be less than {maxName}."); |
| | 2415 | 279 | | } |
| | | 280 | | |
| | | 281 | | /// <summary> |
| | | 282 | | /// Validates that min is less than or equal to max. Throws an ArgumentOutOfRangeException if the condition is not m |
| | | 283 | | /// </summary> |
| | | 284 | | /// <param name="min">The minimum value.</param> |
| | | 285 | | /// <param name="max">The maximum value.</param> |
| | | 286 | | /// <param name="minName">The name of the minimum value parameter.</param> |
| | | 287 | | /// <param name="maxName">The name of the maximum value parameter.</param> |
| | | 288 | | /// <typeparam name="T">The type of the values being compared.</typeparam> |
| | | 289 | | /// <exception cref="ArgumentOutOfRangeException">Thrown if min is not less than or equal to max.</exception> |
| | | 290 | | private static void ValidateInclusiveRange<T>(T min, T max, string minName, string maxName) |
| | | 291 | | where T : IComparable<T> |
| | | 292 | | { |
| | 1629 | 293 | | if (min.CompareTo(max) > 0) |
| | 0 | 294 | | throw new ArgumentOutOfRangeException(minName, $"{minName} must be less than or equal to {maxName}."); |
| | 1629 | 295 | | } |
| | | 296 | | } |
| | | 297 | | |