0

I'm working with an IEnumerable Dictionary (data.Tags) inside a List (masterList).

Here is a typical query I'm doing (which works):

var tagList = 
    (from data in masterList
    from tag in data.Tags
    where tag.id == 0x10
    select tag).Distinct(new TagComparer());

The tag class has fields Id, Value and TranslatedValue. I want to search based on Id, use Value to determine which is the minimum and then return the TranslatedValue (instead of Value).

All of my attempts thus far throw an ArgumentException, such as this:

var tagList = 
    (from data in masterList
    from tag in data.Tags
    where tag.id == 0x10
    select new
    {
       tag.Value,
       tag.TranslatedValue
    };
return tagList.Min().TranslatedValue;

Is there an elegant solution to this?

glenneroo
  • 1,908
  • 5
  • 30
  • 49

5 Answers5

3

You can use OrderBy to order the elements in ascending order (putting the minimum value at the first position), Select to get your translated value, and then First to take that minimum element:

var minVal = (from data in masterList 
             from tag in data.Tags
             where tag.id == 0x10
             select tag)
             .Distinct(new TagComparer())
             .OrderBy(t => t.Value) // lowest value will be first in the list
             .First() // take the first element, which is the min
             .TranslatedValue
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
1

You could implement IComparable<T> for your tag class, and base the comparison on the Value property. Then instead of selecting the minimum Value you can select the minimum tag and take the TranslatedValue from that.

Some code to demonstrate what I mean:

class tag : IComparable<tag>
{
    public int Id { get; set; }

    public int Value { get; set; }

    public int TranslatedValue { get; set; }

    public int CompareTo(tag other)
    {
        if (other.Value > this.Value)
            return -1;
        if (other.Value < this.Value)
            return 1;
        return 0;
    }

    public override string ToString()
    {
        return string.Format("Id:{0}, Value: {1}, TranslatedValue: {2}", Id, Value, TranslatedValue);
    }
}

That set your tag class up to implement IComparable<T>. To use it to get the TranslatedValue it's as simple as:

int minTranslatedValue = tags.Min().TranslatedValue;

Where tags is some sort of IEnumerable<Tag> collection.

Matt Ellen
  • 11,268
  • 4
  • 68
  • 90
1

My answer when I encountered this exact problem before was a little more involved than the others here, but it works very well in these situations and is a little more performant; develop an ObjectWithMin() extension method that will return the entire object that contains the minimum value of a specified projection. Here's a basic algorithm; it iterates the enumerable only once and executes linearly instead of in nlogn-time:

public static T ObjectWithMin<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
        where TResult : IComparable<TResult>
    {
        if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
        if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");

        var seed = elements.Select(t => new { Object = t, Projection = projection(t) }).First();

        return elements.Aggregate(seed,
                                  (s, x) =>
                                  projection(x).CompareTo(s.Projection) < 0
                                      ? new {Object = x, Projection = projection(x)}
                                      : s
            ).Object;
    }

Usage:

var tagList = 
    (from data in masterList
    from tag in data.Tags
    where tag.id == 0x10
    select new
    {
       tag.Value,
       tag.TranslatedValue
    };
return tagList.ObjectWithMin(x=>x.Value).TranslatedValue;
KeithS
  • 70,210
  • 21
  • 112
  • 164
1

You could use the custom MinElement extension that was posted here

Community
  • 1
  • 1
Niki
  • 15,662
  • 5
  • 48
  • 74
0

You should be able to sort by one property, take the first item, and get another property:

return
  masterList
  .SelectMany(data => data.Tags)
  .Where(tag => tag.id == 0x10)
  .OrderBy(tag => tag.Value)
  .First()
  .TranslatedValue;
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • Wouldn't OrderByDescending put the Max Value in the first position (I think he wants OrderBy which does Ascending order). – Justin Niessner Dec 08 '10 at 14:48
  • @Justin Niessner: Yes, you are right. I was thinking backwards. :P It should of course be `OrderBy`. – Guffa Dec 08 '10 at 14:58