glenneroo
glenneroo

Reputation: 2054

LINQ: How to return different field than used by min()

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?

Upvotes: 0

Views: 764

Answers (5)

Matt Ellen
Matt Ellen

Reputation: 11592

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.

Upvotes: 1

Niki
Niki

Reputation: 15867

You could use the custom MinElement extension that was posted here

Upvotes: 1

KeithS
KeithS

Reputation: 71565

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;

Upvotes: 1

Guffa
Guffa

Reputation: 700232

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;

Upvotes: 0

Justin Niessner
Justin Niessner

Reputation: 245399

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

Upvotes: 3

Related Questions