Concise way to do a plus equals operation on a Dictionary element that might not be initialized

可紊 提交于 2019-12-10 17:44:50

问题


I'm looking for an extension method or any other suggestion that can help me make this code as concise as possible.

foreach( Layer lyr in this.ProgramLayers )
  foreach( UWBCEvent evt in this.BcEvents.IncludedEvents )
    EventGroupLayerLosses[new EventGroupIDLayerTuple(evt.EventGroupID, lyr)] += 
       GetEL(evt.AsIfs, lyr.LimitInMillions, lyr.AttachmentInMillions);

The above code has a fairly clear purpose, I'm bucketing values into groups with a compound key. However, this code will fail because the dictionary is initially empty and the += operator won't know to start the bucket off at 0.

The best I can come up with is this:

public V AddOrSet<K, V>(this Dictionary<K, V> dict, K key, V value)
{
    if( dict.ContainsKey(key) )
        dict[key] += value;
    else
        dict[key] = value;
}

But of course, even that won't compile because there's no way to restrict the type of V such that the operator += exists.

Rules

  • Only one iteration through the double for loop. Not allowed to loop through once before to initialize the dictionary with 0 values.
  • Helper method or extension method can be used, but I want the inner loop to be a one liner.
  • Be as generic and reusable as possible so that I don't need to create a bunch of identical functions for similar bucketing with different types (decimals, ints, etc).

For reference - elsewhere in the class the key is defined as an actual Tuple (just with named parameters), which is why it can be used as a dictionary key:

private Dictionary<EventGroupIDLayerTuple, Decimal> _EventGroupLayerLosses;
public class EventGroupIDLayerTuple : Tuple<Int32, Layer>
{
    public EventGroupIDLayerTuple(Int32 EventGroupID, Layer Layer) : base(EventGroupID, Layer) { }
    public Int32 EventGroupID { get { return this.Item1; } }
    public Layer Layer { get { return this.Item2; } }
}

Solution

Thanks to Jon Skeet for the idea of passing a Lambda function as a third parameter to my extension method. No need to even restrict it to a += operation anymore. It's generic enough any operation can be passed to set the new value if a value already exists.

//Sets dictionary value using the provided value. If a value already exists, 
//uses the lambda function provided to compute the new value.
public static void UpdateOrSet<K, V>(this Dictionary<K, V> dict, K key, V value, Func<V, V, V> operation)
{
    V currentValue;
    if( dict.TryGetValue(key, out currentValue) )
        dict[key] = operation(currentValue, value);
    else
        dict[key] = value;
}

Examples:

mySums.UpdateOrSet("Bucket1", 12, (x, y) => x + y);
myStrs.UpdateOrSet("Animals", "Dog", (x, y) => x + ", " + y);
myLists.UpdateOrSet("Animals", (List<T>) Dogs, (x, y) => x.AddRange(y));

Endless fun!


回答1:


Firstly, I'd advise against doing everything you can to make something as short as possible at the potential cost of readability. For example, I'd add braces around the foreach bodies, and if a more readable solution ended up being two lines rather than one, I'd be happy with that.

Secondly, I'm going to assume that for any of the types you're interested in, the default value is a natural zero.

Now, you can write:

public static void AddOrSet<K, V>(this Dictionary<K, V> dict,
                                  K key, V value, Func<V, V, V> addition)
{
    V existing;
    dict.TryGetValue(key, out existing);
    dict[key] = addition(existing, value);
}

Then you can use:

EventGroupLayerLosses.AddOrSet(new EventGroupIDLayerTuple(evt.EventGroupID, lyr),
    GetEL(evt.AsIfs, lyr.LimitInMillions, lyr.AttachmentInMillions),
    (x, y) => x + y);

Using ConcurrentDictionary would work well too.

Additionally, I would try to rework this as a LINQ query if you can. I wouldn't be surprised if a mixture of GroupBy, Sum and ToDictionary allowed you to express the whole thing declaratively.




回答2:


.NET 4 has a new type of dictionary class, ConcurrentDictionary<TKey, TValue>. This class has the extremely helpful AddOrUpdate method (with a few overloads) that exhibits the behavior you're looking for.

MSDN documentation on ConcurrentDictionary




回答3:


Could we just use a null coalescence here?

foreach( Layer lyr in this.ProgramLayers )
   foreach( UWBCEvent evt in this.BcEvents.IncludedEvents )
    EventGroupLayerLosses[new EventGroupIDLayerTuple(evt.EventGroupID, lyr)] = (EventGroupLayerLosses[new EventGroupIDLayerTuple(evt.EventGroupID, lyr)] ?? 0) + 
   GetEL(evt.AsIfs, lyr.LimitInMillions, lyr.AttachmentInMillions);



回答4:


You can try something like this.

private void AddOrUpdate<K, V>(this Dictionary<K, V> dict, K key, Func<V> newValue, Func<V, V> updateValue) 
{ 
    V value;

    if( !dict.TryGetValue(key, out value) ) 
        value = newValue();
    else 
        value = updateValue(value); 

    dict[key] = value; 
}


来源:https://stackoverflow.com/questions/11905539/concise-way-to-do-a-plus-equals-operation-on-a-dictionary-element-that-might-not

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!