Мне очень нравится Last()
и будет использовать его все время для List<T>
s. Но поскольку он определен для IEnumerable<T>
, я предполагаю, что он перечисляет сначала перечисление - это должно быть O (n), а не O (1) для прямой индексации последнего элемента a List<T>
.
Знают ли стандартные методы расширения (Linq) об этом?
STL в С++ знает об этом в силу целого "дерева наследования" для итераторов и еще чего-то.
Ответ 1
Я использовал рефлектор для просмотра кода для Last и он проверяет, является ли он IList<T>
первым и выполняет соответствующий вызов O (1):
EDIT:
По совету моего адвоката я удалил фрагмент кода из рефлектора. Если вы хотите увидеть код для себя, я предлагаю вам либо получить Reflector, либо сделать это самостоятельно (что каждый должен иметь уже), или более просто посмотреть в истории изменений этого сообщения (поскольку я ничего не могу с этим поделать)
(Или, действительно, посмотрите на ответ Марка, у которого явно нет такой же правовой команды, которая защитила его)
Итак, у вас есть легкие накладные расходы, но не огромные издержки перечисления.
Ответ 2
Вы можете просто использовать Last с List<T>
, не беспокоясь:)
Enumerable.Last пытается сбрасывать экземпляр IEnumerable<T>
на IList<T>
. Если это возможно, он использует свойство indexer и Count.
Вот часть реализации, поскольку Reflector видит это:
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
int count = list.Count;
if (count > 0)
{
return list[count - 1];
}
}
Ответ 3
Он содержит оптимизацию для всего, что реализует IList<T>
, и в этом случае он просто ищет элемент длиной -1.
Имейте в виду, что подавляющее большинство материалов, которые вы отправите, будет реализовывать IList<T>
List<int>
int[]
и т.д.... все реализуют IList<T>
Для тех, кто не может проверить код для подтверждения, вы можете подтвердить его с помощью наблюдения:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace ConsoleApplication4 {
class Program {
static void Profile(string description, int iterations, Action func) {
// clean up
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
// warm up
func();
var watch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++) {
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
static void Main(string[] args) {
int[] nums = Enumerable.Range(1, 1000000).ToArray();
int a;
Profile("Raw performance", 100000, () => { a = nums[nums.Length - 1]; });
Profile("With Last", 100000, () => { a = nums.Last(); });
Console.ReadKey();
}
}
}
Вывод:
Raw performance Time Elapsed 1 ms
With Last Time Elapsed 31 ms
Таким образом, он только в 30 раз медленнее и поддерживает этот профиль производительности с любым списком длины, который у вас есть, что ничто в большой схеме вещей.
Ответ 4
Для List<T>
это O (1), но для других перечислений это может быть O (N).
Ответ 5
Короткий ответ:
О (1).
Объяснение
Очевидно, что Last() для List использует метод расширения Count().
Count() проверяет тип коллекции во время выполнения и использует свойство Count, если оно доступно.
Свойство Count для списка имеет сложность O (1), а также метод расширения Last().