Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > LINQ (Language-Integrated Query) > Как выбрать максимальный элемент?


Автор: Bonus 30.6.2008, 20:33
пробую так:
Код

Point maxX = points.Max(p => p.X);

это работает, но возвращаемое значение не Point, а double. Как на выходе получать именно Point (ну или тип который нужен мне)?

Автор: HalkaR 30.6.2008, 23:14
Код

Point maxX = points.Where(p=>p.X==points.Max(p => p.X));

В тупую smile

Автор: Bonus 1.7.2008, 07:13
хм... сработало, спасибо smile

Автор: Partizan 1.7.2008, 11:46
HalkaR,  о_О а просто Point maxX = points.Max(p => p.X); недостаточно?

Автор: HalkaR 1.7.2008, 12:00
Partizan, так Bonus же сказал, что это возвращает X, а не Point.

Автор: Partizan 1.7.2008, 15:24
а....понял...

Автор: Idsa 1.7.2008, 18:17
Кстати-кстати. Тут не все так просто. Это один из тонких моментов в LINQ.
Первый вариант решения, который приходит на ум, - забить на LINQ и воспользоваться старым-добрым foreach. В этом случае за O(n) операций можно найти необходимый элемент. Чем не вариант? Да тем, что после LINQ ну очень не хочется писать подобный "низкоуровневый" перебор.
Если говорить о LINQ решениях этой проблемы, то можно, например, отсортировать список по убыванию и взять первый элемент. Естественно, производительность в этом случае не выдерживает никакой критики (O log n операций и куча понапрасну использованной памяти). Если показать такое решение программисту со старой закалкой, его, наверное, инфаркт хватит smile
Второй LINQ-вариант - использовать код, подобный тому, который написал HalkaR. Только вот, честно говоря, меня удивило, что
Цитата(Bonus @  1.7.2008,  11:13 Найти цитируемый пост)
хм... сработало, спасибо smile 

Ведь этот код возвращает не Point, а коллекцию Point'ов. У меня такой код даже не комплируется (Resharper сразу ругается):
Цитата

Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<System.Drawing.Point>' to 'System.Drawing.Point'

Кстати в том кусочке кода еще одна ошибка: переменная p объявляется два раза. Такое не скомпилируется. И выражение
Код

points.Max(p => p.X)

надо заменить на что-нибудь вроде
Код

points.Max(p1 => p1.X)

Ну это так, мелочи: HalkaR просто опечатался.
Чтобы заставить этот код делать именно то, что нужно, просто добавляем в конец вызов метода First:
Код

Point point = points.Where(p => p.X == points.Max(p1 => p1.X)).First();

Вот. Теперь компилятор не ругается и получает необходимый результат. Казалось бы, вот оно, счастье smile Ан нет. Если присмотреться, код-то неоптимальный. На первый взгляд кажется, что выполняется 2n операций: сначала находится максимальный X, а потом элемент с этим значением X. Напомню, что классически эта задача решается за n операций (как выше через foreach). На самом деле этот код ведет себя куда хуже. Дело в том, что points.Max выполняется при каждой итерации (ну действительно, откуда LINQ знать, что это значение является константой?! "Нет, сынок, это фантастика" smile ). Таким образом, этот скрипт выполняется при помощи n*n операций. Невероятно, но при некоторых n этот вариант может работать даже медленнее бредового варианта с сортировкой (n*n против n*log(n) ).
Конечно, пофиксить баг под кодовым названием "расчет point.Max при каждой итерации" не составит труда:
Код

int maxX = points.Max(p1 => p1.X);
Point point = points.Where(p => p.X == maxX).First();

Все, что мы сделали, - вынесли вычисление максимального значения X за цикл. Итого, получаем 2*n операций. Лучше, конечно, но все равно попахивает законом дырявых абстракций. Но ведь если проблему нельзя решить "стандартными средствами", никто не запрещает создать свой собственный метод расширения (хвала С# 3.0).
Упрощенная версия будет выглядеть примерно так:
Код

public static Point MaxX(this IEnumerable<Point> source)
{
 if (source == null)
  throw new ArgumentException("Source is null");
 Point result = default(Point);
 foreach (Point point in source)
  if ((point != default(Point)) || (point.X > result.X))
   result = point;
 return result;
}

Используем вот так:
Код

Point point = points.MaxX();

Это заточенный вариант под этот пример. Конечно, лучше создать более унифицированный метод через дженерики, но мне лень smile

Добавлено @ 18:24
Забыл добавить, что вариант с методом расширения выполняется за n операций.

Автор: Idsa 7.7.2008, 20:09
Метод расширения я немного неправильно написал. Нужно что-нибудь вроде этого:
Код

   public static Point MaxX(this IEnumerable<Point> source)
    {
      if (source == null)
        throw new ArgumentException("Source is null");
      Point result = default(Point);
      bool flag = false;
      foreach (Point point in source)
      {
        if (flag)
          if (point.X > result.X)
            result = point;
        else
        {
          result = point;
          flag = true;
        }
      }
      if (!flag)
        throw new Exception("Source is empty");
      return result;
    }

Этот подход я позаимствовал в Reflector'е smile

Автор: akizelokro 9.7.2008, 14:52
Цитата

Как на выходе получать именно Point (ну или тип который нужен мне)? 


Дружит ли, кстати, DLinq с user-defined типами?

Код

points.Max(p => p.X)


Что вообще здесь пытается сделать автор, вытащить максимальную х-координату или точку максимальной длины? 

Автор: Idsa 9.7.2008, 15:04
Цитата(akizelokro @  9.7.2008,  18:52 Найти цитируемый пост)
Что вообще здесь пытается сделать автор, вытащить максимальную х-координату или точку максимальной длины?  

Точку с максимальным X.

Цитата(akizelokro @  9.7.2008,  18:52 Найти цитируемый пост)
Дружит ли, кстати, DLinq с user-defined типами?

Дружит.

Автор: akizelokro 11.7.2008, 10:19
Цитата

Дружит.


Спасибо. Ну вот и общий ответ на первый вопрос:

Цитата

Как на выходе получать именно Point (ну или тип который нужен мне)?


А вообще, все приведенные решения в общем случае неоптимальны. Но если автору темы хватает, то и бог с ними smile 

Автор: PashaPash 11.7.2008, 12:38
Цитата(akizelokro @  11.7.2008,  10:19 Найти цитируемый пост)
А вообще, все приведенные решения в общем случае неоптимальны. 
Т.е. ты в "общем случае" умеешь находить максимальный элемент быстрее, чем за O(n)? Дэвид Блейн?

Автор: akizelokro 11.7.2008, 14:16
В каждом общем случае не могу. В конкретном общем - может получиться. Например, при частом использовании такой задачи, я бы создал индекс и делал бы выборку по нему (ага).
Предположу даже больше. Решения в общем случае как минимум не быстрее конкретных решений задач с индивидуальной спецификой. Угу?

Автор: PashaPash 11.7.2008, 15:08
akizelokro, решение в общем случае - это решение задачи на условиях, озвученных топикастером. Как раз "в общем случае" приведенные решения оптимальны. А в частных - ес-но нет. И это не повод писать в каждой теме "решение неоптимально".
Цитата(akizelokro @  11.7.2008,  14:16 Найти цитируемый пост)
Например, при частом использовании такой задачи, я бы создал индекс и делал бы выборку по нему (ага).
А потом оказалось бы что на поддержание индекса уходит больше ресурсов, чем на все выборки. Или что выборки производятся из временных результатов. Твое решение в общем случае неоптимально (с). 

Для задачи "выбрать из IEnumerable<Point> элемент с максимальным значением X" - решение оптимально в общем случае. А все предположения "а если бы задача была не такой, а другой", "а если бы мы решали частный случай, то общее решение было бы не оптимально" к решению вообще никакого отношения не имеют.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)