views:

515

answers:

4

I have two IEnumerable

IEnumerable<MyObject> allowedObjects = MyService.GetAllowedObjects();
IEnumerable<MyOBject> preferedObjects = MyService.GetPreferedObjects();

We can safely assume that preferedObjects will always be a subset of allowedObjects.
I want to create an IDictionary<MyObject, bool>. Objects where the key is the set of MyObjects from the allowedObjects and the bool is true if the MyObject instance was also in the preferedObjects enumerable.

I can do this by enumerating them and adding them one by one, but I would like to be able to do something like this:

IDictionary<MyObject, bool> selectedObjects = allowedObjects
    .ToDictionary(o => new KeyValuePair<MyObject, bool>()
        { Key = q,
          Value = preferedObjects.Any(q)
        }
     );

UPDATE
Exchanged Contains with Any; The solution that is suggested most was what I tried first, but it isn't accepted for some reason:

IDictionary<MyObject, bool> selectedObjects = allowedObjects
    .ToDictionary<MyObject, bool>(o => o, preferedObjects.Any(o));

Visual studio says the first method does not return a bool. Which is true, but mainly because a bool would not be the correct result to start with...
And then it says it cannot infere the type of the second lambda...
As you can see I tried to explicitly define the types to help the inference, but it doesn't solve a thing..

Suggestions?

Disclaimer: names and code are snippyfied to keep the focus where it should be

+1  A: 

I can't remember whether Linq provides a "Contains" extension method on IEnumerable<T>. If not, you could use Any:

var dict = allowedObjects.ToDictionary(o => o,
    o => preferredObjects.Contains(o));

** Edit ** Yep, just tested it and there is indeed a Contains() extension method, so the above code works.

Matt Hamilton
Bizar, here this syntax seems not to work:My real code:Dictionary<Quality, bool> selectedQualities = allowedQualities .ToDictionary<Quality, bool>(k => k, v => preferedQualities.Any(v));With the first q VS says it cannot convert my Quality to a bool; The second lambda (v) it says it cannot infer the type...
borisCallens
Well that's odd. I tested with a couple of IEnumerable<object> variables, so perhaps your use of the Quality class confuses the compiler.
Matt Hamilton
I guess so, but I fear it goes beyond the scope (and my agreement of non-disclosure) to find what exactly is the problem then..
borisCallens
A: 

Enumerable.ToDictionary has multiple overloads. Some of these take a second delegate (so you can pass a lambda) to return the value to go with the key.

Something like:

var secondDict = firstDictionary
                 .Where(p => SomeCondition(p))
                 .ToDictionary(p => p.Key, p => p.Value);
Richard
A: 

You're almost there:

IDictionary<MyObject, bool> selectedObjects = allowedObjects
    .ToDictionary(o => o, o => preferedObjects.Contains(q));

The ToDictionary extension method works around using two lambda expressions to generate the key/value pair rather than the KeyValuePair type.

You could speed things dramatically by using HashSet<T> objects for your allowedObjects and preferredObjects collections, though I wouldn't bother unless your lists are especially large/performance is important.

Noldorin
Please see OP for more info on the problem with your suggestion.The lists are both rather small and come from a service that returns IEnumerables, so I can indeed pour them in a HashSet, but I don't think this will really be the method that's gonna slow my code down ;)
borisCallens
Because you mentioned it, I just had to code it with hashsets :PSomething about premature optimization and the devil.. ;)
borisCallens
@Boris: Yeah, never optimise prematurely, really. Leaving them as plain IEnumerables isn't a problem if they are small, as you say.
Noldorin
@Noldorin: Even coders are just mere humans ;)
borisCallens
+1  A: 

You can of course create your own extension method. ToDictionary simply creates a dictionary by adding all elements in the source as values and using a lambda as key. You can create your own with a different approach. As you use 'contains' I don't think it's a good idea to do it that way, as it is a slow operation (linear search, for every element in allowedObjects, you're enumerating preferredobjects).

One approach could be to add all preferredobjects to a HashSet and use Contains on that. Another could be to use a linq query where you join actualObjects with preferredObjects, using join into, and use DefaultIfEmpty. This is more efficient:

List<MyObject> allowedObjects = new List<MyObject>() { new MyObject("one"), new MyObject("two"), new MyObject("three")};
List<MyObject> preferredObjects = allowedObjects.Take(2).ToList();

var q = from a in allowedObjects
     join p in preferredObjects on a equals p into ap
     from x in ap.DefaultIfEmpty()
     let isPreferred = (x != null)
     let toUse = x ?? a
     select new { toUse, isPreferred };

Dictionary<MyObject, bool> selectedObjects = new Dictionary<MyObject, bool>();
foreach(var v in q)
{
    selectedObjects.Add(v.toUse, v.isPreferred);

    Console.WriteLine("{0}, {1}", v.toUse.Foo, v.isPreferred);
}
Frans Bouma
Although it doesn't really answer my question, there is some usefull info in here. Not accepted yet upped :) Thx
borisCallens
Thanks, although I think it does answer your question, or better: it answers the underlying question: how to get the dictionary you want ;). (as the goal is IMHO more important here than how to get there).
Frans Bouma