Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
Harald Wolff | 8baf7c9f49 | |
Harald Wolff | 61a010c666 | |
Harald Wolff | b99b7dcac8 | |
Harald Wolff | da277bd9c0 | |
Harald Wolff | e1eb27ff26 | |
Harald Wolff | 2f31a6cd06 | |
Harald Wolff | 12f733d6f0 | |
Harald Wolff | 7738a4028c |
|
@ -0,0 +1,13 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.ln.collections.iml
|
||||||
|
/modules.xml
|
||||||
|
/contentModel.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
2
build.ln
2
build.ln
|
@ -3,7 +3,7 @@
|
||||||
"dotnet"
|
"dotnet"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"NUGET_SOURCE": "https://nexus.niclas-thobaben.de/repository/l--n.de/",
|
"NUGET_SOURCE": "https://nexus.l--n.de/repository/ln.net/",
|
||||||
"CONFIGURATION": "Release"
|
"CONFIGURATION": "Release"
|
||||||
},
|
},
|
||||||
"stages": [
|
"stages": [
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.26124.0
|
||||||
|
MinimumVisualStudioVersion = 15.0.26124.0
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.collections", "ln.collections\ln.collections.csproj", "{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.collections.test", "ln.collections.test\ln.collections.test.csproj", "{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{8033F5A3-945A-4FA0-8A94-FF9A455BEF29}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C814B1BC-8EAF-4819-BF70-AAAC08235B5C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,72 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace ln.collections.test
|
||||||
|
{
|
||||||
|
public class BenchmarkBTree
|
||||||
|
{
|
||||||
|
|
||||||
|
int[] inputs = new int[100000];
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
Random random = new Random();
|
||||||
|
HashSet<int> inputs = new HashSet<int>();
|
||||||
|
|
||||||
|
while (inputs.Count < this.inputs.Length)
|
||||||
|
inputs.Add(random.Next());
|
||||||
|
this.inputs = inputs.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Benchmark()
|
||||||
|
{
|
||||||
|
int iters = 10;
|
||||||
|
|
||||||
|
BTree<int> btree = new BTree<int>();
|
||||||
|
Meassure(String.Format("BTree({0})", inputs.Length), iters, ()=>{
|
||||||
|
foreach (int n in inputs)
|
||||||
|
btree.Add(n);
|
||||||
|
|
||||||
|
btree.Clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
HashSet<int> hashSet = new HashSet<int>(1000000);
|
||||||
|
Meassure(String.Format("HashSet({0})", inputs.Length), iters, ()=>{
|
||||||
|
foreach (int n in inputs)
|
||||||
|
hashSet.Add(n);
|
||||||
|
|
||||||
|
hashSet.Clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
Dictionary<int, int> dict = new Dictionary<int, int>();
|
||||||
|
Meassure(String.Format("Dictionary({0})", inputs.Length), iters, ()=>{
|
||||||
|
foreach (int n in inputs)
|
||||||
|
dict.Add(n, n);
|
||||||
|
|
||||||
|
dict.Clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Meassure(string name, int iterations, Action action)
|
||||||
|
{
|
||||||
|
TestContext.Error.WriteLine("------ NEASSURE -------------------");
|
||||||
|
|
||||||
|
while (iterations-->0)
|
||||||
|
{
|
||||||
|
DateTime start = DateTime.Now;
|
||||||
|
action();
|
||||||
|
DateTime stop = DateTime.Now;
|
||||||
|
TestContext.Error.WriteLine("Meassure: {0}: {1}ms", name, (stop - start).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace ln.collections.test
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CacheTests
|
||||||
|
{
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_Cache_1()
|
||||||
|
{
|
||||||
|
Cache<int, string> cache = new Cache<int, string>(TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
|
for (int n = 0; n < 10; n++)
|
||||||
|
cache.Add(n, n.ToString());
|
||||||
|
|
||||||
|
cache.Cleanup();
|
||||||
|
Assert.AreEqual(10, cache.Count);
|
||||||
|
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
cache.Cleanup();
|
||||||
|
Assert.AreEqual(0, cache.Count);
|
||||||
|
|
||||||
|
for (int n = 0; n < 10; n++)
|
||||||
|
{
|
||||||
|
cache.Add(n, n.ToString());
|
||||||
|
Thread.Sleep(90);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(10);
|
||||||
|
|
||||||
|
for (int n = 10; n > 0; n--)
|
||||||
|
{
|
||||||
|
cache.Cleanup();
|
||||||
|
Assert.AreEqual(n, cache.Count);
|
||||||
|
Thread.Sleep(90);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Assert.Pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
|
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||||
|
|
||||||
|
<LangVersion>default</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||||
|
<ProjectReference Include="../ln.collections/ln.collections.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
namespace ln.collections
|
namespace ln.collections
|
||||||
{
|
{
|
||||||
public class BTree<K,V> : IDict<K,V>
|
public class BTree<K,V> : IDict<K,V>, IEnumerable<KeyValuePair<K,V>>
|
||||||
{
|
{
|
||||||
public Comparison<K> Comparison { get; }
|
public Comparison<K> Comparison { get; }
|
||||||
public int Count => count;
|
public int Count => count;
|
||||||
|
@ -303,6 +303,43 @@ namespace ln.collections
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetNextOrCurrent(K current, out K nextOrCurrent)
|
||||||
|
{
|
||||||
|
if (Empty)
|
||||||
|
{
|
||||||
|
nextOrCurrent = default(K);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeNode next = FindFirstGE(current);
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
nextOrCurrent = default(K);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextOrCurrent = next.Key;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public bool TryGetNextOrCurrentValue(K current, out V nextOrCurrentValue)
|
||||||
|
{
|
||||||
|
if (Empty)
|
||||||
|
{
|
||||||
|
nextOrCurrentValue = default(V);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeNode next = FindFirstGE(current);
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
nextOrCurrentValue = default(V);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextOrCurrentValue = next.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private TreeNode First(TreeNode node)
|
private TreeNode First(TreeNode node)
|
||||||
|
@ -592,6 +629,22 @@ namespace ln.collections
|
||||||
IEnumerable IDict.Keys => Keys;
|
IEnumerable IDict.Keys => Keys;
|
||||||
IEnumerable IDict.Values => Values;
|
IEnumerable IDict.Values => Values;
|
||||||
|
|
||||||
|
public IEnumerable<KeyValuePair<K, V>> GetInterval(K start, K end) => GetInterval(start, end, null);
|
||||||
|
|
||||||
|
public IEnumerable<KeyValuePair<K, V>> GetInterval(K start, K end, Func<K,V,bool> filter)
|
||||||
|
{
|
||||||
|
TreeNode currentNode = (start is null) ? First(headNode) : FindFirstGE(start);
|
||||||
|
if (currentNode != null)
|
||||||
|
{
|
||||||
|
while ((currentNode is not null) && ((end is null) || (Comparison(currentNode.Key, end) <= 0)))
|
||||||
|
{
|
||||||
|
if (filter is null || filter(currentNode.Key, currentNode.Value))
|
||||||
|
yield return new KeyValuePair<K, V>(currentNode.Key, currentNode.Value);
|
||||||
|
currentNode = Next(currentNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TreeNode
|
class TreeNode
|
||||||
{
|
{
|
||||||
public BTree<K, V> Tree { get; }
|
public BTree<K, V> Tree { get; }
|
||||||
|
@ -683,6 +736,20 @@ namespace ln.collections
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
|
||||||
|
{
|
||||||
|
TreeNode node = First(headNode);
|
||||||
|
while (node != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<K, V>(node.Key, node.Value);
|
||||||
|
node = Next(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BTree<K> : BTree<K,object>
|
public class BTree<K> : BTree<K,object>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
namespace ln.collections
|
namespace ln.collections
|
||||||
{
|
{
|
||||||
public class BTreeValueList<K, V>
|
public class BTreeValueList<K, V> : IEnumerable<KeyValuePair<K,V>>
|
||||||
{
|
{
|
||||||
public bool Empty => bTree.Empty;
|
public bool Empty => bTree.Empty;
|
||||||
|
|
||||||
|
@ -136,24 +137,27 @@ namespace ln.collections
|
||||||
public K First => bTree.First();
|
public K First => bTree.First();
|
||||||
public K Last => bTree.Last();
|
public K Last => bTree.Last();
|
||||||
|
|
||||||
|
public bool TryGetNextOrCurrent(K current, out K nextOrEqual) => bTree.TryGetNextOrCurrent(current, out nextOrEqual);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public IEnumerable<K> Keys => bTree.Keys;
|
public IEnumerable<K> Keys => bTree.Keys;
|
||||||
public IEnumerable<V> Values => bTree.Values.SelectMany(vl => vl);
|
public IEnumerable<V> Values => bTree.Values.SelectMany(vl => vl);
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<K, V>> GetKeyValuePairs()
|
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
|
||||||
{
|
{
|
||||||
foreach (K key in Keys)
|
foreach (KeyValuePair<K, List<V>> vl in this.bTree)
|
||||||
{
|
foreach (V v in vl.Value)
|
||||||
List<V> lv = bTree[key];
|
{
|
||||||
foreach (V value in lv)
|
yield return new KeyValuePair<K, V>(vl.Key, v);
|
||||||
yield return new KeyValuePair<K, V>(key, value);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public void AddRange(IEnumerable<KeyValuePair<K, V>> keyValuePairs)
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
{
|
|
||||||
foreach (KeyValuePair<K, V> keyValuePair in keyValuePairs)
|
public void AddRange(IEnumerable<KeyValuePair<K, V>> keyValuePairs)
|
||||||
Add(keyValuePair.Key, keyValuePair.Value);
|
{
|
||||||
|
foreach (KeyValuePair<K, V> keyValuePair in keyValuePairs)
|
||||||
|
Add(keyValuePair.Key, keyValuePair.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace ln.collections
|
namespace ln.collections
|
||||||
{
|
{
|
||||||
public class BTreeValueSet<K, V>
|
public class BTreeValueSet<K, V> : IEnumerable<KeyValuePair<K, V>>
|
||||||
{
|
{
|
||||||
public bool Empty => bTree.Empty;
|
public bool Empty => bTree.Empty;
|
||||||
|
public Comparison<K> Comparison => bTree.Comparison;
|
||||||
|
|
||||||
BTree<K, HashSet<V>> bTree;
|
BTree<K, HashSet<V>> bTree;
|
||||||
|
|
||||||
|
@ -13,6 +16,7 @@ namespace ln.collections
|
||||||
{
|
{
|
||||||
bTree = new BTree<K, HashSet<V>>();
|
bTree = new BTree<K, HashSet<V>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BTreeValueSet(Comparison<K> comparison)
|
public BTreeValueSet(Comparison<K> comparison)
|
||||||
{
|
{
|
||||||
bTree = new BTree<K, HashSet<V>>(comparison);
|
bTree = new BTree<K, HashSet<V>>(comparison);
|
||||||
|
@ -22,10 +26,11 @@ namespace ln.collections
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (TryGet(key,out IEnumerable<V> values))
|
if (TryGet(key, out IEnumerable<V> values))
|
||||||
{
|
{
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new KeyNotFoundException();
|
throw new KeyNotFoundException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,41 +40,48 @@ namespace ln.collections
|
||||||
if (!TryAdd(key, value))
|
if (!TryAdd(key, value))
|
||||||
throw new ArgumentException("duplicate key");
|
throw new ArgumentException("duplicate key");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(K key, V value)
|
public void Remove(K key, V value)
|
||||||
{
|
{
|
||||||
if (!TryRemove(key, value))
|
if (!TryRemove(key, value))
|
||||||
throw new KeyNotFoundException();
|
throw new KeyNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(K key)
|
public void Remove(K key)
|
||||||
{
|
{
|
||||||
if (!TryRemove(key))
|
if (!TryRemove(key))
|
||||||
throw new KeyNotFoundException();
|
throw new KeyNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryAdd(K key,V value)
|
public bool TryAdd(K key, V value)
|
||||||
{
|
{
|
||||||
if (!bTree.TryGet(key, out HashSet<V> values))
|
if (!bTree.TryGet(key, out HashSet<V> values))
|
||||||
{
|
{
|
||||||
values = new HashSet<V>();
|
values = new HashSet<V>();
|
||||||
bTree.Add(key, values);
|
bTree.Add(key, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
return values.Add(value);
|
return values.Add(value);
|
||||||
}
|
}
|
||||||
public bool TryGet(K key,out IEnumerable<V> values)
|
|
||||||
|
public bool TryGet(K key, out IEnumerable<V> values)
|
||||||
{
|
{
|
||||||
if ((!bTree.TryGet(key, out HashSet<V> v)) || (v.Count == 0))
|
if ((!bTree.TryGet(key, out HashSet<V> v)) || (v.Count == 0))
|
||||||
{
|
{
|
||||||
values = v;
|
values = v;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
values = v;
|
values = v;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRemove(K key)
|
public bool TryRemove(K key)
|
||||||
{
|
{
|
||||||
return bTree.TryRemove(key);
|
return bTree.TryRemove(key);
|
||||||
}
|
}
|
||||||
public bool TryRemove(K key,V value)
|
|
||||||
|
public bool TryRemove(K key, V value)
|
||||||
{
|
{
|
||||||
if (bTree.TryGet(key, out HashSet<V> values))
|
if (bTree.TryGet(key, out HashSet<V> values))
|
||||||
{
|
{
|
||||||
|
@ -78,14 +90,17 @@ namespace ln.collections
|
||||||
bTree.Remove(key);
|
bTree.Remove(key);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsKey(K key)
|
public bool ContainsKey(K key)
|
||||||
{
|
{
|
||||||
if (!bTree.TryGet(key, out HashSet<V> _values))
|
if (!bTree.TryGet(key, out HashSet<V> _values))
|
||||||
return false;
|
return false;
|
||||||
return _values.Count > 0;
|
return _values.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsValue(V value)
|
public bool ContainsValue(V value)
|
||||||
{
|
{
|
||||||
foreach (HashSet<V> _values in bTree.Values)
|
foreach (HashSet<V> _values in bTree.Values)
|
||||||
|
@ -94,6 +109,7 @@ namespace ln.collections
|
||||||
if (v.Equals(value))
|
if (v.Equals(value))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,17 +120,17 @@ namespace ln.collections
|
||||||
return _values.Count;
|
return _values.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
bTree.Clear();
|
bTree.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public K First => bTree.First();
|
public K First => bTree.First();
|
||||||
public K Last => bTree.Last();
|
public K Last => bTree.Last();
|
||||||
|
|
||||||
public IEnumerable<K> Keys => bTree.Keys;
|
public IEnumerable<K> Keys => bTree.Keys;
|
||||||
public IEnumerable<V> Values => bTree.Values.SelectMany(vl => vl);
|
public IEnumerable<V> Values => bTree.Values.SelectMany(vl => vl);
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<K, V>> GetKeyValuePairs()
|
public IEnumerable<KeyValuePair<K, V>> GetKeyValuePairs()
|
||||||
{
|
{
|
||||||
foreach (K key in Keys)
|
foreach (K key in Keys)
|
||||||
|
@ -124,11 +140,36 @@ namespace ln.collections
|
||||||
yield return new KeyValuePair<K, V>(key, value);
|
yield return new KeyValuePair<K, V>(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(IEnumerable<KeyValuePair<K, V>> keyValuePairs)
|
public void AddRange(IEnumerable<KeyValuePair<K, V>> keyValuePairs)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<K, V> keyValuePair in keyValuePairs)
|
foreach (KeyValuePair<K, V> keyValuePair in keyValuePairs)
|
||||||
Add(keyValuePair.Key, keyValuePair.Value);
|
Add(keyValuePair.Key, keyValuePair.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<KeyValuePair<K, V>> GetInterval(K start, K end) => GetInterval(start, end, null);
|
||||||
|
public IEnumerable<KeyValuePair<K, V>> GetInterval(K start, K end, Func<K,V,bool> filter)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<K, HashSet<V>> vl in this.bTree.GetInterval(start, end))
|
||||||
|
foreach (V v in vl.Value)
|
||||||
|
{
|
||||||
|
if (filter is null || filter(vl.Key, v))
|
||||||
|
yield return new KeyValuePair<K, V>(vl.Key, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<K, HashSet<V>> vl in this.bTree)
|
||||||
|
foreach (V v in vl.Value)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<K, V>(vl.Key, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +1,223 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace ln.collections
|
namespace ln.collections
|
||||||
{
|
{
|
||||||
public class Cache<K,V>
|
public class Cache<TKey, TValue>
|
||||||
{
|
{
|
||||||
public int Count => values.Count;
|
public TimeSpan DefaultLifeTime { get; set; }
|
||||||
|
private readonly Dictionary<TKey, Entry> _cache = new Dictionary<TKey, Entry>();
|
||||||
|
|
||||||
BTree<K, V> values;
|
public Cache(TimeSpan defaultLifeTime)
|
||||||
BTree<K, LinkedListItem<K>> itemTree;
|
{
|
||||||
LinkedList<K> items = new LinkedList<K>();
|
DefaultLifeTime = defaultLifeTime;
|
||||||
|
}
|
||||||
int maxCacheSize = 4096;
|
|
||||||
|
|
||||||
public Cache()
|
public Cache()
|
||||||
|
: this(TimeSpan.FromMinutes(10))
|
||||||
{
|
{
|
||||||
values = new BTree<K, V>();
|
|
||||||
itemTree = new BTree<K, LinkedListItem<K>>();
|
|
||||||
}
|
|
||||||
public Cache(Comparison<K> compare)
|
|
||||||
{
|
|
||||||
values = new BTree<K, V>(compare);
|
|
||||||
itemTree = new BTree<K, LinkedListItem<K>>(compare);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MaxCacheSize
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||||
{
|
{
|
||||||
get => maxCacheSize;
|
lock (this)
|
||||||
set {
|
{
|
||||||
maxCacheSize = value;
|
foreach (var cacheEntry in _cache)
|
||||||
while (maxCacheSize < values.Count)
|
|
||||||
{
|
{
|
||||||
Forget();
|
if (cacheEntry.Value.IsValid)
|
||||||
|
array[arrayIndex++] = new KeyValuePair<TKey, TValue>(cacheEntry.Key, cacheEntry.Value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public int Count {
|
||||||
{
|
get
|
||||||
values.Clear();
|
|
||||||
itemTree.Clear();
|
|
||||||
items.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGet(K key,out V value)
|
|
||||||
{
|
|
||||||
return values.TryGet(key, out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Forget()
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
{
|
||||||
Forget(items.FirstItem);
|
lock (this) { return _cache.Count; }
|
||||||
}
|
|
||||||
}
|
|
||||||
public void Forget(K key)
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
if (itemTree.TryGet(key,out LinkedListItem<K> item))
|
|
||||||
Forget(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Forget(LinkedListItem<K> item)
|
public bool IsReadOnly => false;
|
||||||
{
|
public void Clear() { lock (this) _cache.Clear(); }
|
||||||
if (item == null)
|
public void Cleanup()
|
||||||
return;
|
|
||||||
|
|
||||||
values.Remove(item.Value);
|
|
||||||
itemTree.Remove(item.Value);
|
|
||||||
items.Remove(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Ensure(K key,V value)
|
|
||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
if (!itemTree.TryGet(key, out LinkedListItem<K> item))
|
foreach (var key in _cache.Keys.ToArray())
|
||||||
{
|
{
|
||||||
item = new LinkedListItem<K>(key);
|
if (!_cache[key].IsValid)
|
||||||
values.Add(key,value);
|
_cache.Remove(key);
|
||||||
}
|
}
|
||||||
Ensure(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Ensure(LinkedListItem<K> item)
|
public bool Contains(KeyValuePair<TKey, TValue> item) { lock (this) return TryGetValue(item.Key, out TValue value) && object.Equals(item.Value, value); }
|
||||||
|
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||||
|
|
||||||
|
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||||
{
|
{
|
||||||
if (!item.IsLinked)
|
lock (this)
|
||||||
itemTree.Add(item.Value, item);
|
{
|
||||||
items.Add(item);
|
if (_cache.TryGetValue(item.Key, out Entry entry) && object.Equals(item.Value, entry.Value))
|
||||||
|
{
|
||||||
|
_cache.Remove(item.Key);
|
||||||
|
return entry.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Add(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
_cache.Add(key, new Entry(this, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(TKey key)
|
||||||
|
{
|
||||||
|
lock (this) return _cache.ContainsKey(key) && _cache[key].IsValid;
|
||||||
|
}
|
||||||
|
public bool Remove(TKey key)
|
||||||
|
{
|
||||||
|
lock (this) return _cache.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (_cache.TryGetValue(key, out Entry entry))
|
||||||
|
{
|
||||||
|
if (entry.IsValid)
|
||||||
|
{
|
||||||
|
value = entry.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cache.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default(TValue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue this[TKey key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out TValue value))
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new KeyNotFoundException();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (_cache.TryGetValue(key, out Entry entry))
|
||||||
|
entry.Reset(value);
|
||||||
|
else
|
||||||
|
_cache.Add(key, new Entry(this, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ReValidate(TKey key)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (_cache.TryGetValue(key, out Entry entry))
|
||||||
|
{
|
||||||
|
if (entry.IsValid)
|
||||||
|
{
|
||||||
|
entry.UpdateTimestamp();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cache.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue GetOrCreate(TKey key, Func<TKey, TValue> factory)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (!TryGetValue(key, out TValue value))
|
||||||
|
{
|
||||||
|
value = factory(key);
|
||||||
|
Add(key, value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Entry
|
||||||
|
{
|
||||||
|
private Cache<TKey, TValue> _cache;
|
||||||
|
public DateTime TimeOut { get; private set; }
|
||||||
|
public TValue Value { get; private set; }
|
||||||
|
|
||||||
|
public Entry(Cache<TKey,TValue> cache, TValue value)
|
||||||
|
{
|
||||||
|
_cache = cache;
|
||||||
|
Value = value;
|
||||||
|
UpdateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTimestamp() => UpdateTimestamp(_cache.DefaultLifeTime);
|
||||||
|
public void UpdateTimestamp(TimeSpan lifeTime) => TimeOut = DateTime.Now + lifeTime;
|
||||||
|
public bool IsValid => TimeOut > DateTime.Now;
|
||||||
|
|
||||||
|
public void Reset(TValue newValue)
|
||||||
|
{
|
||||||
|
Value = newValue;
|
||||||
|
UpdateTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class KeyedCache<TKey, TValue> : Cache<TKey, TValue>
|
||||||
|
{
|
||||||
|
protected KeyedCache()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
protected KeyedCache(TimeSpan defaultTimeOut)
|
||||||
|
:base(defaultTimeOut)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract TKey GetKey(TValue value);
|
||||||
|
|
||||||
|
public void Add(TValue value) => Add(GetKey(value), value);
|
||||||
|
public bool RemoveValue(TValue value)
|
||||||
|
{
|
||||||
|
TKey key = GetKey(value);
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (TryGetValue(key, out TValue cachedValue) && object.Equals(cachedValue, value))
|
||||||
|
{
|
||||||
|
return Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ln.collections
|
||||||
|
{
|
||||||
|
public delegate bool CacheMayForget<K,V>(ExtendedCache<K,V> cache,V item);
|
||||||
|
|
||||||
|
public class ExtendedCache<K,V>
|
||||||
|
{
|
||||||
|
public event CacheMayForget<K, V> OnCacheMayForget;
|
||||||
|
|
||||||
|
private MappingBTree<K, ExtendedCacheItem<K, V>> cacheItems =
|
||||||
|
new MappingBTree<K, ExtendedCacheItem<K, V>>((i) => i.Key);
|
||||||
|
|
||||||
|
private BTreeValueSet<DateTime, ExtendedCacheItem<K, V>> cacheAges =
|
||||||
|
new BTreeValueSet<DateTime, ExtendedCacheItem<K, V>>();
|
||||||
|
|
||||||
|
private int _targetSize = 4096;
|
||||||
|
public int TargetSize
|
||||||
|
{
|
||||||
|
get => _targetSize;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_targetSize = value;
|
||||||
|
if (Count > _targetSize) Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => cacheItems.Count;
|
||||||
|
|
||||||
|
|
||||||
|
public ExtendedCache()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedCache(int targetSize) : this()
|
||||||
|
{
|
||||||
|
TargetSize = targetSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
cacheItems.Clear();
|
||||||
|
cacheAges.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invalidate()
|
||||||
|
{
|
||||||
|
if (Count <= _targetSize)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (ExtendedCache<K,V>.ExtendedCacheItem<K,V> eci in cacheAges.Values)
|
||||||
|
{
|
||||||
|
if (MayForget(eci.Value))
|
||||||
|
Invalidate(eci);
|
||||||
|
|
||||||
|
if (Count <= _targetSize)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MayForget(V item)
|
||||||
|
{
|
||||||
|
foreach (CacheMayForget<K,V> mayForget in OnCacheMayForget.GetInvocationList() ?? new CacheMayForget<K, V>[0])
|
||||||
|
{
|
||||||
|
if (!mayForget(this, item))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V this[K key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (TryGet(key, out V value))
|
||||||
|
return value;
|
||||||
|
throw new KeyNotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet(K key, out V value)
|
||||||
|
{
|
||||||
|
if (cacheItems.TryGet(key, out ExtendedCacheItem<K, V> eci))
|
||||||
|
{
|
||||||
|
value = eci.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default(V);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCacheItem(ExtendedCacheItem<K, V> eci)
|
||||||
|
{
|
||||||
|
cacheAges.Remove(eci.Timestamp, eci);
|
||||||
|
eci.Timestamp = DateTime.Now;
|
||||||
|
cacheAges.Add(eci.Timestamp, eci);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Ensure(K key, V value)
|
||||||
|
{
|
||||||
|
if (cacheItems.TryGet(key, out ExtendedCacheItem<K, V> eci))
|
||||||
|
{
|
||||||
|
if (!Object.ReferenceEquals(eci.Value, value))
|
||||||
|
return false;
|
||||||
|
UpdateCacheItem(eci);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eci = new ExtendedCacheItem<K, V>(key, value);
|
||||||
|
cacheItems.Add(eci);
|
||||||
|
cacheAges.Add(eci.Timestamp, eci);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Invalidate(K key)
|
||||||
|
{
|
||||||
|
if (cacheItems.TryGet(key, out ExtendedCacheItem<K, V> eci))
|
||||||
|
{
|
||||||
|
Invalidate(eci);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invalidate(ExtendedCacheItem<K, V> eci)
|
||||||
|
{
|
||||||
|
cacheAges.Remove(eci.Timestamp, eci);
|
||||||
|
cacheItems.RemoveKey(eci.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExtendedCacheItem<TK, TV>
|
||||||
|
{
|
||||||
|
public DateTime Timestamp;
|
||||||
|
public TK Key;
|
||||||
|
public TV Value;
|
||||||
|
|
||||||
|
public ExtendedCacheItem(TK key, TV value)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Value = value;
|
||||||
|
Timestamp = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<Version>0.1.3</Version>
|
<Version>0.1.6</Version>
|
||||||
<Authors>Harald Wolff-Thobaben</Authors>
|
<Authors>Harald Wolff-Thobaben</Authors>
|
||||||
<Company>l--n.de</Company>
|
<Company>l--n.de</Company>
|
||||||
<AssemblyVersion>0.0.1.1</AssemblyVersion>
|
<AssemblyVersion>0.0.1.1</AssemblyVersion>
|
||||||
<FileVersion>0.0.1.1</FileVersion>
|
<FileVersion>0.0.1.1</FileVersion>
|
||||||
|
<PackageVersion>0.2.0</PackageVersion>
|
||||||
|
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||||
|
<LangVersion>default</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
Loading…
Reference in New Issue