【问题标题】:Find-or-insert with only one lookup in c# dictionary仅在 C# 字典中查找一次的查找或插入
【发布时间】:2011-09-18 12:44:39
【问题描述】:

我是一名前 C++/STL 程序员,尝试使用 c#/.NET 技术编写快速行进算法...

我正在寻找等效于 STL 方法“map::insert”的方法,如果不存在,则在给定键处插入值,否则返回现有键值对的迭代器。

我发现的唯一方法是通过两种查找来实现这一点:一种在 TryGetValue 中,另一种在 Add 方法中:

List<Point> list;
if (!_dictionary.TryGetValue (pcost, out list))
{
    list = new List<Point> ();
    dictionary.Add (pcost, list);
}
list.Add (new Point { X = n.x, Y = n.y });

有没有什么可以解释为什么使用 .NET 容器无法做到这一点?还是我错过了什么?

谢谢。

【问题讨论】:

  • 你确定它即使在生产代码中也会进行两次查找?
  • 双重查找真的很重要吗?时间上的差异是微不足道的。
  • @Chris:什么?您没有任何基准来支持这一点,更不用说与 OP 的使用模式相关的基准了;)——我可以向您展示重要的代码(哦等等,我不能法律原因...)
  • 其实我还没有做基准测试:也许cli机器缓存了最后一个结果作为下次查找的提示,这可能会抑制添加操作的成本。

标签: c# performance stl comparison lookup


【解决方案1】:

您可以为此创建扩展方法:

IDictionary<string, Point> _dictionary = GetDictionary();
_dictionary.GetOrAdd( "asdf" ).Add( new Point(14, 15) );

// ... elsewhere ...
public static class DictionaryExtensions {
    public static List<TValue> GetOrAdd<TKey, TValue>( this IDictionary<TKey, List<TValue>> self, TKey key ) {
        List<TValue> result;
        self.TryGetValue( key, out result );
        if ( null == result ) {
            // the key value can be set to the null
            result = new List<TValue>();
            self[key] = result;
        }

        return result;
    }
}

【讨论】:

  • 你假设 TValue 是一个引用类型,你应该检查 TryGetValue 的返回值。
  • 这与问题中的代码相同,只是现在进行了扩展。这有什么帮助?仍然是 2 个调用,trygetvalue 和 add。
  • @PowerRoy:这是唯一的方法,如何使用 Dictionary 来实现。如果你愿意,你可以自己实现 IDictionary 并实现更有效的方式。
  • TryGetValue 有返回值。 null 检查结果无效,您可能添加了 null 作为值。
  • 添加 null 作为 value 不会导致异常,只使用 null 作为 key。但是,TryGetValue 有一个返回值,必须对其进行评估以使代码可靠。
【解决方案2】:

您可以通过以下方式分配您的值:

var dict = new Dictionary<int, int>();
dict[2] = 11;

如果键 2 的值不存在 - 它将被添加,否则将被覆盖。

Dictionary 没有 GetOrAdd 方法,但 C# 4.0 中的 ConcurrentDictionary 有:

var dict = new ConcurrentDictionary<int, int>();
dict[2] = 10;
int a = dict.GetOrAdd(2, 11);// a == 10

【讨论】:

  • @gorik 使用 Dictionary 就像你展示的那样,如果密钥不存在会抛出异常
  • @amichai gerstl 不,请尝试一下
  • 第一个是“AddorUpdate”。为“GetOrAdd”+1。
  • 这并不能解决问题,因为在我的情况下,字典的值是一个列表,我不想丢失已经为特定键添加的元素。
【解决方案3】:

标准通用字典不支持这一点,需要 2 次查找。虽然查找的成本通常可以忽略不计,所以这不是问题,而且您通常可以通过调整系统的其他部分而不是尝试微优化字典查找来获得更好的结果。

据我所知,.net 附带的唯一支持此功能的字典是ConcurrentDictionary,方法为GetOrAdd。虽然现在您要支付同步成本。

【讨论】:

  • false,检查这个答案:stackoverflow.com/a/16193323/893406 只查找一次。
  • @v.oddou 对dict.Add 的调用在内部执行查找,因此进行了 2 次查找。一个由 TryGetValue 查找,一个由 Add 查找。
【解决方案4】:

有什么可以解释为什么 使用 .NET 这是不可能的 容器?

在不知道真实背景的情况下,我认为这是因为字典的简单性。只有基本的、易于理解的函数:AddRemove a.s.o.,而索引运算符有一点魔力,这可能被认为是直观的。

【讨论】:

    【解决方案5】:

    遗憾的是,bcl 的实现中没有一个。最接近的替代方法是进行两次查找,但可以使用通用扩展方法使其变得容易,as shown here

    public static T GetOrAdd<S, T>(this IDictionary<S, T> dict, S key, 
                                   Func<T> valueCreator)
    {
        T value;
        return dict.TryGetValue(key, out value) ? value : dict[key] = valueCreator();
    }
    

    但是有 C5's implementation 开箱即用。方法定义如下所示:

    public virtual bool FindOrAdd(K key, ref V value)
    {
    
    }
    

    我不知道他们为什么不接受 Func&lt;V&gt; 而不是 V 来推迟对象创建。 C5 有很多不错的类似技巧,例如,

    public virtual bool Remove(K key, out V value)
    
    public virtual bool Update(K key, V value, out V oldvalue)
    
    public virtual bool UpdateOrAdd(K key, V value, out V oldvalue)
    

    【讨论】:

      【解决方案6】:

      老问题,但我可能只是偶然发现了一个可以接受的解决方案。我使用 TryGetValue、三元运算符和索引赋值的组合。

      var thing = _dictionary.TryGetValue(key, out var existing) ? existing : _dictionary[key] = new Thing(); 
      

      我为此写了一个小例子。

      class Program
      {
          private static readonly Dictionary<string, string> _translations
              = new Dictionary<string, string>() { { "en", "Hello world!" } };
      
          private static string AddOrGetTranslation(string locale, string defaultText)
              => _translations.TryGetValue(locale, out var existingTranslation)
                  ? existingTranslation
                  : _translations[locale] = defaultText;
      
          static void Main()
          {
              var defaultText = "#hello world#";
      
              Console.WriteLine(AddOrGetTranslation("en", defaultText)); // -> Hello world!
      
              Console.WriteLine(AddOrGetTranslation("de", defaultText)); // -> #hello world#
              Console.WriteLine(AddOrGetTranslation("de", "differentDefaultText")); // -> #hello world#
      
              _translations["de"] = "Hallo Welt!";
              Console.WriteLine(AddOrGetTranslation("de", defaultText)); // -> Hallo Welt!
          }
      }
      

      【讨论】:

      • 这个解决方案仍然使用两次查找,还是我漏掉了什么?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-30
      • 1970-01-01
      • 1970-01-01
      • 2018-02-21
      • 1970-01-01
      • 1970-01-01
      • 2016-09-12
      相关资源
      最近更新 更多