using System.Collections.Generic;
public static void Main()
const int itemsCount = 100;
const int minWeight = 10;
const int maxWeight = 100;
var source = Enumerable.Range(1, itemsCount).ToArray();
var random = new Random(0);
var sizes = source.ToDictionary(x => x, _ => (long)random.Next(minWeight, maxWeight + 1));
long maxConcurrentSize = (long)Math.Ceiling(sizes.Values.Sum() * 0.10);
Console.WriteLine($"Items Count: {source.Length:#,0}");
Console.WriteLine($"Size Sum: {sizes.Values.Sum():#,0}, Avg: {sizes.Values.Average():#,0}");
Console.WriteLine($"MaxConcurrentSize: {maxConcurrentSize:#,0}");
int sizeSelectorCount = 0;
int loadedCount = 0, unloadedCount = 0;
long totalLoadedSize = 0;
long currentLoadedSize = 0;
SortedSet<int> loaded = new();
Func<int, long> sizeSelector = key =>
Func<int, Resource<int>> itemLoader = key =>
var item = new Resource<int>(key, () =>
bool removed = loaded.Remove(key);
if (!removed) throw new InvalidOperationException($"Not removed #{key}, Loaded: [{String.Join(", ", loaded)}]");
currentLoadedSize -= sizes[key];
currentLoadedSize += sizes[key];
if (currentLoadedSize > maxConcurrentSize && loaded.Count > 2) throw new InvalidOperationException($"Max size violation, {currentLoadedSize} > {maxConcurrentSize}.");
totalLoadedSize += sizes[key];
var query = GetPairs(source, sizeSelector, itemLoader, maxConcurrentSize);
List<(int, int)> pairs = new();
foreach (var (x, y) in query)
pairs.Add((x.Key, y.Key));
var expectedPairsCount = ((long)itemsCount * (itemsCount - 1)) / 2;
Console.WriteLine($"Pairs: {pairs.Count:#,0}" + (pairs.Count == expectedPairsCount ? "" : $" Error! (expected: {expectedPairsCount:#,0})"));
var baseLoadedCount = source.Select((x, i) => i + 1).Sum();
var baseLoadedSize = source.Select((x, i) => sizes[x] * (i + 1)).Sum();
Console.WriteLine($"Loaded: {loadedCount:#,0} (discount: {(loadedCount - baseLoadedCount) / (double)baseLoadedCount:0.0%} / {(totalLoadedSize - baseLoadedSize) / (double)baseLoadedSize:0.0%})");
Console.WriteLine($"Unloaded: {unloadedCount:#,0}" + (unloadedCount == loadedCount && loaded.Count == 0 ? "" : $" Error! (non-disposed count: {loaded.Count:#,0})"));
Console.WriteLine($"Size selector Count: {sizeSelectorCount:#,0}");
var orderedPairs = pairs.OrderBy(p => p.Item1).ThenBy(p => p.Item2);
var expectedPairs = source.SelectMany((x, i) => source.Skip(i + 1), (x, y) => (x, y));
var pairsOK = orderedPairs.SequenceEqual(expectedPairs);
Console.WriteLine($"Validation: {(pairsOK ? "OK" : "Error!")}");
foreach (var (x, y) in orderedPairs.Zip(expectedPairs))
Console.WriteLine($"- First Error: {x} instead of {y}");
private static IEnumerable<(TItem, TItem)> GetPairs<TSource, TItem>(
IReadOnlyList<TSource> source,
Func<TSource, long> sizeSelector,
Func<TSource, TItem> itemLoader,
long maxConcurrentSize) where TItem : IDisposable
for (int i = 0; i < source.Count; i++)
using var first = itemLoader(source[i]);
for (int j = i + 1; j < source.Count; j++)
using var second = itemLoader(source[j]);
yield return (first, second);
private class Resource<TKey> : IDisposable
private Action _disposeAction;
public Resource(TKey key, Action disposeAction)
_disposeAction = disposeAction ?? throw new ArgumentNullException();
if (_disposeAction == null) throw new InvalidOperationException($"Key #{Key} disposed twice.");