2

I'm trying to write some AI for a game of checkers. I want to select the move with the highest board score.

Need something like this:

var bestMove = from m in validMoves
                where BoardScore(opp, board.Clone().ApplyMove(m)) is max
                select m

Except I can't figure out the "is max" part. Would prefer it returns a single item rather than an enumerable too.


Basically, the equivalent of this:

Move bestMove = null;
float highestScore = float.MinValue;
foreach (var move in validMoves)
{
    float score = BoardScore(opp, board.Clone().ApplyMove(move));
    if (score > highestScore)
    {
        highestScore = score;
        bestMove = move;
    }
}
mpen
  • 237,624
  • 230
  • 766
  • 1,119
  • 1
    possible duplicate of [How to use LINQ to select object with minimum or maximum property value](http://stackoverflow.com/questions/914109/how-to-use-linq-to-select-object-with-minimum-or-maximum-property-value) – mpen Sep 12 '10 at 00:38
  • And http://stackoverflow.com/questions/3188693/how-can-i-get-linq-to-return-the-object-which-has-the-max-value-for-a-given-prope – Chris Moschini May 08 '11 at 15:43
  • possible duplicate of [LINQ: How to perform .Max() on a property of all objects in a collection and return the object with maximum value](http://stackoverflow.com/questions/1101841/linq-how-to-perform-max-on-a-property-of-all-objects-in-a-collection-and-ret) – nawfal Jul 19 '14 at 18:21

4 Answers4

3

Don't you basically already have this figured out? If you write your own extension method you can implement this functionality in typical LINQ fashion:

public static T MaxFrom<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector, IComparer<TValue> comparer)
{
    T itemWithMax = default(T);
    TValue max = default(TValue);

    using (var e = source.GetEnumerator())
    {
        if (e.MoveNext())
        {
            itemWithMax = e.Current;
            max = selector(itemWithMax);
        }

        while (e.MoveNext())
        {
            T item = e.Current;
            TValue value = selector(item);
            if (comparer.Compare(value, max) > 0)
            {
                itemWithMax = item;
                max = value;
            }
        }
    }

    return itemWithMax;
}

public static T MaxFrom<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector)
{
    return source.MaxFrom(selector, Comparer<TValue>.Default);
}

This way you could just do:

var bestMove = validMoves
    .MaxFrom(m => BoardScore(opp, board.Clone().ApplyMove(m)));
Dan Tao
  • 119,009
  • 50
  • 280
  • 431
  • Yeah... I just figured there had to be a built-in way to do it with Linq already. Not that hard to write myself :) Still, you get the check for doing my dirty work. – mpen Sep 04 '10 at 23:40
2
var scores = from m in validMoves
                select BoardScore(opp, board.Clone().ApplyMove(m));
var bestMove = scores.Max();

EDIT:

I should have read more closely. You can do:

var descendingMoves = validMoves.OrderByDescending(m=>BoardScore(opp, board.Clone().ApplyMove(m)));
var bestMove = descendingMoves.First();

But this is O(n log n). I found a blog post with discussion on this issue. It proposes a Max extension function that returns the maximum original value, rather than the transformed one. Unfortunately, I don't know of a way to do this in LINQ.

Matthew Flaschen
  • 255,933
  • 45
  • 489
  • 528
  • Sorry, no. That's exactly the problem I'm having -- I found the `Max()` function, but it returns a `float`. I need the `move` with the highest score, not the score itself. – mpen Sep 04 '10 at 04:32
  • There's gotta be a way without sorting the whole list, no? – mpen Sep 04 '10 at 04:53
  • 1
    @Mark, you could use the code I linked, or just stick with the loop. – Matthew Flaschen Sep 04 '10 at 04:59
1
    float max = validMoves.Max(m => BoardScore(opp, board.Clone().ApplyMove(m)));
    var bestMove = validMoves
          .Where(m => BoardScore(opp, board.Clone().ApplyMove(m)) == max)
          .First();
Grozz
  • 7,736
  • 2
  • 35
  • 53
  • Looks like it should work, but I'm pretty sure you're iterating over the moves twice, which shouldn't be necessary. – mpen Sep 04 '10 at 23:39
0

You're going to want to look at Enumerable.Max(TSource) Method (IEnumerable(TSource), Func(TSource, Single)). i.e.:

var bestMove = validMoves.Max((m)=>BoardScore(opp, board.Clone().ApplyMove(m)));

If the scores are expensive to compute, you might compute all scores up-front, pairing them with their corresponding move, but I doubt this will be necessary. Example:

var scores = from m in validMoves
             select new {Move=m, Score=BoardScore(opp, board.Clone().ApplyMove(m))};
var bestMove = scores.Max(s=>s.Score).Move;
Nathan Ernst
  • 4,300
  • 21
  • 37
  • Neither of those solutions work. `Max()` returns a score. The second example won't even compile. – mpen Sep 04 '10 at 06:18