2

I'm attempting to intersect a list with a dictionary, which works perfectly:

    public static IDictionary<string, string> GetValues(IReadOnlyList<string> keys, IHeaderDictionary headers)
    {
        return keys.Intersect(headers.Keys)
            .Select(k => new KeyValuePair<string, string>(k, headers[k]))
            .ToDictionary(p => p.Key, p => p.Value);
    }

The usage of the above method would be something like this:

    [TestMethod]
    public void GetValues_returns_dictionary_of_header_values()
    {
        var headers = new List<string> { { "trackingId" }, { "SourceParty" }, { "DestinationParty" } };
        var trackingIdValue = "thisismytrackingid";
        var sourcePartyValue = "thisismysourceparty";
        var destinationPartyValue = "thisismydestinationparty";
        var requestHeaders = new HeaderDictionary
        {
            {"trackingId", new Microsoft.Extensions.Primitives.StringValues(trackingIdValue) },
            {"SourceParty", new Microsoft.Extensions.Primitives.StringValues(sourcePartyValue) },
            {"DestinationParty", new Microsoft.Extensions.Primitives.StringValues(destinationPartyValue) },
            {"randomHeader", new Microsoft.Extensions.Primitives.StringValues("dontcare") }
        };
        var headerValues = HeaderOperators.GetValues(headers, requestHeaders);
        Assert.IsTrue(headerValues.ContainsKey("trackingId"));
        Assert.IsTrue(headerValues.ContainsKey("SourceParty"));
        Assert.IsTrue(headerValues.ContainsKey("DestinationParty"));
        Assert.IsTrue(headerValues.Count == headers.Count);
    }

enter image description here

However, rather than an intersect I would like to do a left join, where for example if I search the dictionary for a value that does not exist it would still return that key with some default value.

For example, if we input oneMoreKey:

        var headers = new List<string> { { "trackingId" }, { "SourceParty" }, { "DestinationParty" }, {"oneMoreKey"} };

Then I would expect that the result of this would be something like this:

        var headerValues = HeaderOperators.GetValues(headers, requestHeaders, "myDefaultValue");

Where headerValues is:

 {"trackingId", "thisismytrackingid"}
 {"SourceParty", "thisismysourceparty"}
 {"DestinationParty", "thisismydestinationparty"}
 {"oneMoreKey", "myDefaultValue"}

How do I add a default value to the intersection if one does not exist?

Alex Gordon
  • 57,446
  • 287
  • 670
  • 1,062
  • You could use [`Union`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.union?view=netframework-4.7.2) if you want to create a list from multiple other lists with unique values. You just have to provide a equality comparor as well as figure out what to do if a value (for the same key) is different between 2 sources. – Igor Feb 27 '19 at 21:01

3 Answers3

2

You could use TryGetValue when creating the new dictionary to populate it with the defaults.

public static IDictionary<string, string> GetValues(IReadOnlyList<string> keys, IHeaderDictionary headers, string defaultValue) 
{
    return keys.ToDictionary(k => k, k => headers.TryGetValue(k, out string val) ? val : defaultValue);
}
ScottyD0nt
  • 348
  • 2
  • 8
2

Based on this example: Left join on two Lists and maintain one property from the right with Linq you can also solve it with GroupJoin like this:

public static IDictionary<string, string> GetValues(IReadOnlyList<string> keys, IHeaderDictionary headers, string defaultValue)
{
    return keys.GroupJoin(headers, key => key, header => header.Key, (key, header) => new { key, header })
        .SelectMany(x => x.header.DefaultIfEmpty(), (x, header) => new { x.key, header.Value })
        .Select(x => new KeyValuePair<string, string>(x.key, x.Value))
        .ToDictionary(p => p.Key, p => p.Value ?? defaultValue);
}
hujtomi
  • 1,540
  • 2
  • 17
  • 23
  • thanks! any way to make these case-insensitive comparisons? i want to intersect but not care about casing – Alex Gordon Feb 27 '19 at 23:26
  • It is case insensitive already, but if you want to change it GroupJoin has an IEqualityComparer comparer parameter, so you can change it like this: keys.GroupJoin(headers, key => key, header => header.Key, (key, header) => new { key, header }, StringComparer.OrdinalIgnoreCase) – hujtomi Feb 28 '19 at 09:16
1

Try this Linq in your GetValues function:

return keys.Intersect(headers.Keys)
    .Select(k => new KeyValuePair<string, string>(k, headers[k]))
    .Union(keys.Where(k => !headers.Keys.Contains(k)).Select(k => new KeyValuePair<string, string>(k, "myDefaultValue")))
    .ToDictionary(p => p.Key, p => p.Value);

It is doing a union with any values in the header dictionary keys that can't be found in the key list, and pairing these with the default value.

PRS
  • 741
  • 1
  • 7
  • 27
  • thanks! any way to make these case-insensitive comparisons? i want to intersect but not care about casing – Alex Gordon Feb 27 '19 at 23:26
  • Yep - you would have to define your dictionary as follows: var requestHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase), then also do the Intersect as case insensitive: keys.Intersect(headers.Keys, StringComparer.OrdinalIgnoreCase) – PRS Feb 28 '19 at 11:04