Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/323.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 字典:查找给定键范围内的最小/最大值并返回它们的键_C#_Vb.net - Fatal编程技术网

C# 字典:查找给定键范围内的最小/最大值并返回它们的键

C# 字典:查找给定键范围内的最小/最大值并返回它们的键,c#,vb.net,C#,Vb.net,我有一本共有50万项的词典。 键是整数,项是单个的 例如: 1, 8.65 2, 7.65 3, 8.89 4, 8.90 5, 7.95 ... 500000, 7.68 如何检索此词典指定键范围内的最小值和最大值及其各自的键 示例:查找key=25和key=477之间的最小/最大数据值并返回它们的键 我找到了一些LINQ的例子,但作者警告说,它可能比foreach慢,而且不能完全按照我的意愿进行 性能在我的应用程序中至关重要 更新1: 我想知道最大/最小值对应的键 这本词典包含一个时间序列

我有一本共有50万项的词典。 键是整数,项是单个的

例如:

1, 8.65
2, 7.65
3, 8.89
4, 8.90
5, 7.95
...
500000, 7.68
如何检索此词典指定键范围内的最小值和最大值及其各自的键

示例:查找key=25和key=477之间的最小/最大数据值并返回它们的键

我找到了一些LINQ的例子,但作者警告说,它可能比foreach慢,而且不能完全按照我的意愿进行

性能在我的应用程序中至关重要

更新1:

我想知道最大/最小值对应的键

这本词典包含一个时间序列。单个值按其键按时间顺序排列。键值越高,数据越新

更新2:基准

我做了一些基准测试,用929452条记录填充了一个并发字典

我的CPU是i7-8550U,这意味着它在单线程3.8GHz上有增强功能,当4核8线程运行时,它的频率会降低,大约2.6GHz。所以,我从来没有期望多线程比单线程快4倍

对于字典中的每一项,我都会回过头来查找之前最多800条记录

发布构建模式,x64:

单线程,用于循环:14149毫秒 多线程,并行for循环:4731毫秒 单线程,linq只有1000条记录:17609毫秒。对不起,linq。 林克出去了。我肯定会选择for循环。现在我想比较concurrentdictionary和带有for循环的列表

更新3:简化和基准

使用其他容器修改我的代码。如果没有其他线程同时进行修改,则所有线程都是线程安全的

并发字典1-my objects datetime线程,2D单线程:14682毫秒 我的对象列表日期时间,二维单帧:2071毫秒 并发字典4线程:4611毫秒 对象数组datetime,二维单个:1030毫秒 1维单x4阵列和1维datetime阵列:784毫秒 1维单x4和1维datetime 4线程阵列:229毫秒。
为了使输入对象保持只读并尽可能快,我必须将处理结果写入另一个对象。现在这是另一个主题。

我不确定dictionary是否知道如何根据键之间的任何关系进行优化。 因此,我认为您必须自己进行优化。通过一次浏览字典,您应该能够:

int max = Int32.MinValue;
int min = Int32.MaxValue,
foreach (var k in dictionary.keys) {
       if (k<minIndex | k>maxIndex) continue;
       max = Math.Max(max,dictionary[k]);
       min = Math.Min(min,dictionary[k]);
}
现在,如果您的词典提前排序,意味着键“50”将始终位于键“60”之前,您可以尽快中止,并尽可能晚地开始

事实上,你应该看到

因为你更新了你的描述


使用a,k是列表的索引号,值是double。

我不确定字典是否知道如何根据键之间的任何关系进行优化。 因此,我认为您必须自己进行优化。通过一次浏览字典,您应该能够:

int max = Int32.MinValue;
int min = Int32.MaxValue,
foreach (var k in dictionary.keys) {
       if (k<minIndex | k>maxIndex) continue;
       max = Math.Max(max,dictionary[k]);
       min = Math.Min(min,dictionary[k]);
}
现在,如果您的词典提前排序,意味着键“50”将始终位于键“60”之前,您可以尽快中止,并尽可能晚地开始

事实上,你应该看到

因为你更新了你的描述


使用a,k是列表的索引号,值是double。

下面是一个LinqPad5示例,但是您不想要这样的东西吗

  var inst = new Dictionary<int, double>();
inst.Add(1, 82.65);
inst.Add(2, 8.65);
inst.Add(3, 8.89);
inst.Add(4, 84.90);
inst.Add(5, 7.95);

var min = inst.Where(x => x.Value > 8).Min(x => x.Value);
Console.WriteLine(min);
var max = inst.Where(x => x.Value < 80).Max(x => x.Value);
Console.WriteLine(max);
或者,如果您正在寻找钥匙,您可以这样做:

var min = inst.Where(x => x.Value > 8).OrderBy(x => x.Value).First();
Console.WriteLine(min.Key);
var max = inst.Where(x => x.Value < 80).OrderByDescending(x => x.Value).First();
Console.WriteLine(max.Key);
var max = data.GetMaxInRange(3, 7);
var min = data.GetMinInRange(3, 7);
foreach (var entry in list.GetRange(25, 477))
{
    if (entry.Value > maxValue)
    {
        maxValue = entry.Value;
        maxIndex = entry.Index;
    }
}

然而。。。肥皂泡有毛病。毫无疑问,你如何定义你需要的第一把钥匙?但那不是我的问题。。下面是一个LinqPad5示例,只是一个附带问题,但您不想要这样的东西吗

  var inst = new Dictionary<int, double>();
inst.Add(1, 82.65);
inst.Add(2, 8.65);
inst.Add(3, 8.89);
inst.Add(4, 84.90);
inst.Add(5, 7.95);

var min = inst.Where(x => x.Value > 8).Min(x => x.Value);
Console.WriteLine(min);
var max = inst.Where(x => x.Value < 80).Max(x => x.Value);
Console.WriteLine(max);
或者,如果您正在寻找钥匙,您可以这样做:

var min = inst.Where(x => x.Value > 8).OrderBy(x => x.Value).First();
Console.WriteLine(min.Key);
var max = inst.Where(x => x.Value < 80).OrderByDescending(x => x.Value).First();
Console.WriteLine(max.Key);
var max = data.GetMaxInRange(3, 7);
var min = data.GetMinInRange(3, 7);
foreach (var entry in list.GetRange(25, 477))
{
    if (entry.Value > maxValue)
    {
        maxValue = entry.Value;
        maxIndex = entry.Index;
    }
}
然而。。。肥皂泡有毛病。毫无疑问,你如何定义你需要的第一把钥匙?但那不是我的问题。。只是一个附带问题,

Where将返回所有元素,其中的键位于您的范围内,然后Max和Min方法将返回对应的最小值和最大值

var data = new Dictionary<int, double>();
for (int i = 1; i <= 10; i++)
{
    data.Add(i, i * 1.1);
}

var minKey = 3;
var maxKey = 7;
var max = data.Where(x => x.Key >= minKey && x.Key <= maxKey).Max(y => y.Value);
var min = data.Where(x => x.Key >= minKey && x.Key <= maxKey).Min(y => y.Value);
编辑2: 如果您想要KeyValuePair,那么这将是一个选项

public static class Extensions
{
    public static KeyValuePair<int, double> GetMaxInRange(this Dictionary<int, double> data, int minKey, int maxKey)
    {
        return data.Where(x => x.Key >= minKey && x.Key <= maxKey).OrderByDescending(y => y.Value).FirstOrDefault();
    }

    public static KeyValuePair<int, double> GetMinInRange(this Dictionary<int, double> data, int minKey, int maxKey)
    {
        return data.Where(x => x.Key >= minKey && x.Key <= maxKey).OrderBy(y => y.Value).FirstOrDefault();
    }
}
Where将返回所有元素,其中键位于您的范围内,然后Max和Min方法将返回rage中相应的Min和Max值

var data = new Dictionary<int, double>();
for (int i = 1; i <= 10; i++)
{
    data.Add(i, i * 1.1);
}

var minKey = 3;
var maxKey = 7;
var max = data.Where(x => x.Key >= minKey && x.Key <= maxKey).Max(y => y.Value);
var min = data.Where(x => x.Key >= minKey && x.Key <= maxKey).Min(y => y.Value);
编辑2: 如果您想要KeyValuePair,那么这将是一个选项

public static class Extensions
{
    public static KeyValuePair<int, double> GetMaxInRange(this Dictionary<int, double> data, int minKey, int maxKey)
    {
        return data.Where(x => x.Key >= minKey && x.Key <= maxKey).OrderByDescending(y => y.Value).FirstOrDefault();
    }

    public static KeyValuePair<int, double> GetMinInRange(this Dictionary<int, double> data, int minKey, int maxKey)
    {
        return data.Where(x => x.Key >= minKey && x.Key <= maxKey).OrderBy(y => y.Value).FirstOrDefault();
    }
}

我认为字典的这种扩展方法可以帮助你

static class DctExt {

    public static void GetKeysByValueInRange(this Dictionary<int,float> baseDct, int start, int end, out List<int> byMinValue, out List<int> byMaxValue) {
        byMinValue = new List<int>();
        byMaxValue = new List<int>();

        float max = GetMaxValue(baseDct, start, end);
        float min = GetMinValue(baseDct, start, end);

        foreach (KeyValuePair<int, float> kvp in baseDct) {
            if(kvp.Value == min) {
                byMinValue.Add(kvp.Key);
            }
            else if(kvp.Value == max) {
                byMaxValue.Add(kvp.Key);
            }
        }

    }

    private static float GetMaxValue(Dictionary<int,float> baseDct, int start, int end) {
        List<float> valuesOnRange = GetSpecificRange(baseDct, start, end);
        return valuesOnRange.Max();
    }

    private static float GetMinValue(Dictionary<int,float> baseDct, int start, int end) {
        List<float> valuesOnRange = GetSpecificRange(baseDct, start, end);
        return valuesOnRange.Min();
    }

    private static List<float> GetSpecificRange(Dictionary<int,float> dct, int start, int end) {
        List<float> res = new List<float>();

        for (int i = start; i < end; i++) {
            res.Add(dct.ElementAt(i).Value);
        }

        return res;
    }

}
下面是用法

private static void Main() {

        Dictionary<int, float> dct = new Dictionary<int, float> {
            {1, 8.65f},
            {2, 7.65f},
            {3, 7.65f},
            {4, 8.90f},
            {5, 7.95f}
        };


        List<int> keysByMax = new List<int>();
        List<int> keysByMin = new List<int>();

        dct.GetKeysByValueInRange(1, 4, out keysByMin, out keysByMax);

        foreach (var item in keysByMin) {
            Console.Write($"min {item} ");
            // printst min 2 min 3
        }
        Console.WriteLine();
        foreach (var item in keysByMax) {
            Console.Write($"max {item} ");
            //prints max 4
        }
        Console.ReadLine();

    }

我认为字典的这种扩展方法可以帮助你

static class DctExt {

    public static void GetKeysByValueInRange(this Dictionary<int,float> baseDct, int start, int end, out List<int> byMinValue, out List<int> byMaxValue) {
        byMinValue = new List<int>();
        byMaxValue = new List<int>();

        float max = GetMaxValue(baseDct, start, end);
        float min = GetMinValue(baseDct, start, end);

        foreach (KeyValuePair<int, float> kvp in baseDct) {
            if(kvp.Value == min) {
                byMinValue.Add(kvp.Key);
            }
            else if(kvp.Value == max) {
                byMaxValue.Add(kvp.Key);
            }
        }

    }

    private static float GetMaxValue(Dictionary<int,float> baseDct, int start, int end) {
        List<float> valuesOnRange = GetSpecificRange(baseDct, start, end);
        return valuesOnRange.Max();
    }

    private static float GetMinValue(Dictionary<int,float> baseDct, int start, int end) {
        List<float> valuesOnRange = GetSpecificRange(baseDct, start, end);
        return valuesOnRange.Min();
    }

    private static List<float> GetSpecificRange(Dictionary<int,float> dct, int start, int end) {
        List<float> res = new List<float>();

        for (int i = start; i < end; i++) {
            res.Add(dct.ElementAt(i).Value);
        }

        return res;
    }

}
下面是用法

private static void Main() {

        Dictionary<int, float> dct = new Dictionary<int, float> {
            {1, 8.65f},
            {2, 7.65f},
            {3, 7.65f},
            {4, 8.90f},
            {5, 7.95f}
        };


        List<int> keysByMax = new List<int>();
        List<int> keysByMin = new List<int>();

        dct.GetKeysByValueInRange(1, 4, out keysByMin, out keysByMax);

        foreach (var item in keysByMin) {
            Console.Write($"min {item} ");
            // printst min 2 min 3
        }
        Console.WriteLine();
        foreach (var item in keysByMax) {
            Console.Write($"max {item} ");
            //prints max 4
        }
        Console.ReadLine();

    }

如果仅获取最大值和最小值,则可以使用:

    Dim myResult = Aggregate order In myDict Into Max(order.Value), Min(order.Value)

    'myResult.max for max and myResult.min as min

如果仅获取最大值和最小值,则可以使用:

    Dim myResult = Aggregate order In myDict Into Max(order.Value), Min(order.Value)

    'myResult.max for max and myResult.min as min

如果您想获得每个dic的详细信息,对于最小值和最大值,您可以尝试以下方法:

    Dim myMinResult = From dic In myDic Where dic.Value = (Aggregate dicAgg In myDic Into Min(dicAgg.Value))
    Dim myMaxResult = From dic In myDic Where dic.Value = (Aggregate dicAgg In myDic Into Max(dicAgg.Value))
    MessageBox.Show("Min = key : " & myMinResult(0).Key.ToString & ", Value : " & myMinResult(0).Value.ToString)
    MessageBox.Show("Max = key : " & myMaxResult(0).Key.ToString & ", Value : " & myMaxResult(0).Value.ToString)

如果您想获得每个dic的详细信息,对于最小值和最大值,您可以尝试以下方法:

    Dim myMinResult = From dic In myDic Where dic.Value = (Aggregate dicAgg In myDic Into Min(dicAgg.Value))
    Dim myMaxResult = From dic In myDic Where dic.Value = (Aggregate dicAgg In myDic Into Max(dicAgg.Value))
    MessageBox.Show("Min = key : " & myMinResult(0).Key.ToString & ", Value : " & myMinResult(0).Value.ToString)
    MessageBox.Show("Max = key : " & myMaxResult(0).Key.ToString & ", Value : " & myMaxResult(0).Value.ToString)

这是一个封装列表和线程的类,它使用起来是线程安全的,并且将执行很多操作 比ConcurrentDictionary更适合范围查询。如果避免了单元素操作,它的性能会更好,这样在搜索或批量更新期间不会多次获取ReaderWriterLock。例如,而不是:

for (int i = 25; i < 477; i++)
{
    if (list[i] > maxValue)
    {
        maxValue = list[i];
        maxIndex = i;
    }
}
…因为GetRange方法只获取并释放锁一次。这不仅更快,而且结果也更一致,因为可以保证在枚举范围期间不会发生更新

public class ConcurrentList<T> : IEnumerable<T>
{
    private readonly List<T> _list;
    private readonly ReaderWriterLock _lock = new ReaderWriterLock();

    public ConcurrentList()
    {
        _list = new List<T>();
    }

    public ConcurrentList(IEnumerable<T> collection)
    {
        _list = new List<T>(collection);
    }

    public int Count => ReadSafe(list => list.Count);

    public T this[int index]
    {
        get => ReadSafe(list => list[index]);
        set => WriteSafe(list => list[index] = value);
    }

    public IEnumerable<(int Index, T Value)> GetRange(int from, int to)
    {
        using (new DisposableReader(_lock))
        {
            for (int i = from; i < to; i++)
            {
                yield return (i, _list[i]);
            }
        }
    }

    public void Add(T item) => WriteSafe(list => list.Add(item));
    public void AddRange(IEnumerable<T> r) => WriteSafe(list => list.AddRange(r));
    public void Clear() => WriteSafe(list => list.Clear());

    public void UpdateRange(IEnumerable<(int Index, T Value)> changes)
    {
        WriteSafe(list =>
        {
            foreach (var change in changes)
            {
                list[change.Index] = change.Value;
            }
        });
    }

    public IEnumerator<T> GetEnumerator()
    {
        using (new DisposableReader(_lock))
        {
            foreach (var item in _list)
            {
                yield return item;
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public TResult ReadSafe<TResult>(Func<List<T>, TResult> function)
    {
        _lock.AcquireReaderLock(Timeout.Infinite);
        try
        {
            return function(_list);
        }
        finally
        {
            _lock.ReleaseReaderLock();
        }
    }

    public void WriteSafe(Action<List<T>> action)
    {
        _lock.AcquireWriterLock(Timeout.Infinite);
        try
        {
            action(_list);
        }
        finally
        {
            _lock.ReleaseWriterLock();
        }
    }

    private struct DisposableReader : IDisposable
    {
        private readonly ReaderWriterLock _lock;
        public DisposableReader(ReaderWriterLock obj)
        {
            _lock = obj;
            _lock.AcquireReaderLock(Timeout.Infinite);
        }
        public void Dispose() => _lock.ReleaseReaderLock();
    }
}

我使用了helper方法来获取和释放锁,以避免在每个属性和方法中重复try-finally块。当然,这不是必需的,只是风格问题。

这里有一个类,它封装了列表和,使用起来是线程安全的,对于范围查询,它的性能比ConcurrentDictionary好得多。如果避免了单元素操作,它的性能会更好,这样在搜索或批量更新期间不会多次获取ReaderWriterLock。例如,而不是:

for (int i = 25; i < 477; i++)
{
    if (list[i] > maxValue)
    {
        maxValue = list[i];
        maxIndex = i;
    }
}
…因为GetRange方法只获取并释放锁一次。这不仅更快,而且结果也更一致,因为可以保证在枚举范围期间不会发生更新

public class ConcurrentList<T> : IEnumerable<T>
{
    private readonly List<T> _list;
    private readonly ReaderWriterLock _lock = new ReaderWriterLock();

    public ConcurrentList()
    {
        _list = new List<T>();
    }

    public ConcurrentList(IEnumerable<T> collection)
    {
        _list = new List<T>(collection);
    }

    public int Count => ReadSafe(list => list.Count);

    public T this[int index]
    {
        get => ReadSafe(list => list[index]);
        set => WriteSafe(list => list[index] = value);
    }

    public IEnumerable<(int Index, T Value)> GetRange(int from, int to)
    {
        using (new DisposableReader(_lock))
        {
            for (int i = from; i < to; i++)
            {
                yield return (i, _list[i]);
            }
        }
    }

    public void Add(T item) => WriteSafe(list => list.Add(item));
    public void AddRange(IEnumerable<T> r) => WriteSafe(list => list.AddRange(r));
    public void Clear() => WriteSafe(list => list.Clear());

    public void UpdateRange(IEnumerable<(int Index, T Value)> changes)
    {
        WriteSafe(list =>
        {
            foreach (var change in changes)
            {
                list[change.Index] = change.Value;
            }
        });
    }

    public IEnumerator<T> GetEnumerator()
    {
        using (new DisposableReader(_lock))
        {
            foreach (var item in _list)
            {
                yield return item;
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public TResult ReadSafe<TResult>(Func<List<T>, TResult> function)
    {
        _lock.AcquireReaderLock(Timeout.Infinite);
        try
        {
            return function(_list);
        }
        finally
        {
            _lock.ReleaseReaderLock();
        }
    }

    public void WriteSafe(Action<List<T>> action)
    {
        _lock.AcquireWriterLock(Timeout.Infinite);
        try
        {
            action(_list);
        }
        finally
        {
            _lock.ReleaseWriterLock();
        }
    }

    private struct DisposableReader : IDisposable
    {
        private readonly ReaderWriterLock _lock;
        public DisposableReader(ReaderWriterLock obj)
        {
            _lock = obj;
            _lock.AcquireReaderLock(Timeout.Infinite);
        }
        public void Dispose() => _lock.ReleaseReaderLock();
    }
}


我使用了helper方法来获取和释放锁,以避免在每个属性和方法中重复try-finally块。当然这不是必须的,这只是风格的问题。

你不是说如果k>min&&kmin&&k最小和最大LINQ评估的每次迭代都会引起性能问题,因为OP已经提到了算法复杂性问题!使用我给出的第二种扩展方法,两个查询的执行时间都不到300毫秒。500000条记录的执行时间不到300毫秒?是的,linq紧凑而简单,但通常比foreach/for循环慢。但我可以用这两种解决方案进行基准测试。对500K记录来说是的。Theodor,我想测试一下你的建议,但我目前正在努力将类代码翻译成VB.NET。Readwriterlock是否可以替代ReadWriterLockSim?是的,ReadWriterLockSim绝对是首选!只要不修改集合,列表可以同时支持多个读取器。因此,如果我设法避免在创建列表后写入,我就可以在上面运行parallelfor而不会出现问题。我将再次使用列表和多线程读取进行基准测试。是的,如果您的数据是不可变的,那么您确实可以完全避免线程同步!Theodor,我想测试一下你的建议,但我目前正在努力将类代码翻译成VB.NET。Readwriterlock是否可以替代ReadWriterLockSim?是的,ReadWriterLockSim绝对是首选!只要不修改集合,列表可以同时支持多个读取器。因此,如果我设法避免在创建列表后写入,我就可以在上面运行parallelfor而不会出现问题。我将再次使用列表和多线程读取进行基准测试。是的,如果您的数据是不可变的,那么您确实可以完全避免线程同步!