Каков самый элегантный способ получить набор элементов по индексу из коллекции?

Учитывая

IList<int> indexes;
ICollection<T> collection;

Каков самый элегантный способ извлечения всех T в коллекции на основе индексов, представленных в индексах?

Например, если коллекция содержит

"Brian", "Cleveland", "Joe", "Glenn", "Mort"

И индексы содержат

1, 3

Возврат будет

"Cleveland," "Glenn"

Изменить: предположим, что индексы всегда сортируются по возрастанию.

Ответ 1

Это предполагает, что индексная последовательность является монотонной восходящей последовательностью неотрицательных индексов. Стратегия проста: для каждого индекса набросьте счетчик на сборку до этой точки и дайте элемент.

public static IEnumerable<T> GetIndexedItems<T>(this IEnumerable<T> collection, IEnumerable<int> indices)
{
    int currentIndex = -1;
    using (var collectionEnum = collection.GetEnumerator())
    {
        foreach(int index in indices)
        {
            while (collectionEnum.MoveNext()) 
            {
                currentIndex += 1;
                if (currentIndex == index)
                {
                    yield return collectionEnum.Current;
                    break;
                }
            }
        }    
    }
}

Преимущества этого решения по сравнению с другими опубликованными решениями:

  • O (1) в дополнительном хранилище - некоторые из этих решений - O (n) в пространстве
  • O (n) во времени - некоторые из этих решений квадратичны по времени
  • работает над любыми двумя последовательностями; не требует ICollection или IList.
  • только повторяет сборку один раз; некоторые решения повторяют сбор нескольких раз (например, для создания списка из него).

Недостатки:

  • труднее читать

Ответ 2

Здесь более быстрая версия:

IEnumerable<T> ByIndices<T>(ICollection<T> data, IList<int> indices)
{
    int current = 0;
    foreach(var datum in data.Select((x, i) => new { Value = x, Index = i }))
    {
        if(datum.Index == indices[current])
        {
            yield return datum.Value;
            if(++current == indices.Count)
                yield break;
        }
    }
}

Ответ 3

Не уверен, насколько это элегантно, но здесь вы идете.

Так как ICollection<> не дает вам индексацию, я просто использовал IEnumerable<>, и так как мне не нужен индекс в IList<>, я использовал IEnumerable<> тоже.

public static IEnumerable<T> IndexedLookup<T>(
    IEnumerable<int> indexes, IEnumerable<T> items)
{
    using (var indexesEnum = indexes.GetEnumerator())
    using (var itemsEnum = items.GetEnumerator())
    {
        int currentIndex = -1;
        while (indexesEnum.MoveNext())
        {
            while (currentIndex != indexesEnum.Current)
            {
                if (!itemsEnum.MoveNext())
                    yield break;
                currentIndex++;
            }

            yield return itemsEnum.Current;
        }
    }
}

EDIT: Я заметил, что мое решение похоже на Erics.

Ответ 4

Я бы использовал метод расширения

public static IEnumerable<T> Filter<T>(this IEnumerable<T> pSeq, 
                                       params int [] pIndexes)
{
      return pSeq.Where((pArg, pId) => pIndexes.Contains(pId));
}

Ответ 5

Вы можете сделать это в методе расширения:

static IEnumerable<T> Extract<T>(this ICollection<T> collection, IList<int> indexes)
{
   int index = 0;
   foreach(var item in collection)
   {
     if (indexes.Contains(index))
       yield item;
     index++;
   }
}

Ответ 6

Не элегантный, но эффективный - убедитесь, что индексы отсортированы...

ICollection<T> selected = new Collection<T>();
var indexesIndex = 0;
var collectionIndex = 0;
foreach( var item in collection )
{
    if( indexes[indexesIndex] != collectionIndex++ )
    {
        continue;
    }
    selected.Add( item );
    if( ++indexesIndex == indexes.Count )
    {
        break;
    }
}

Ответ 7

Как правильный ответ:

var col = new []{"a","b","c"};
var ints = new []{0,2};
var set = new HashSet<int>(ints);

var result = col.Where((item,index) => set.Contains(index));

Обычный с IList.Contains или Enumerable.Contains, не выполняйте поиск в списках, если вы не знаете, сколько индексов будет в коллекции. Или вы пойдете по пути O (n ^ 2). Если вы хотите быть в безопасности, вам следует использовать посредник Lookup/Dictionary/Hashset и тест в этой коллекции, а не в списке ванили (линейный поиск не подходит для вас)

Ответ 8

Несколько хороших предложений здесь уже, я просто брошу свои два цента.

int counter = 0;
var x = collection
    .Where((item, index) => 
        counter < indices.Length && 
        index == indices[counter] && 
        ++counter != 0);

edit: yah, не думал об этом в первый раз. приращение должно происходить только тогда, когда выполняются два других условия.

Ответ 9

Я нахожу это решение особенно элегантным и немного легче следовать.

Решение 1

   public static IEnumerable<T> GetIndexedItems2<T>(this IEnumerable<T> collection,    IEnumerable<int> indices) {

        int skipped = 0;
        foreach (int index in indices) {
            int offset = index - skipped;
            collection = collection.Skip(offset);
            skipped += offset;
            yield return collection.First();
        }
    }

Это может быть реорганизовано дальше до реальной простой реализации:

Решение 2

   public static IEnumerable<T> GetIndexedItems3<T>(this IEnumerable<T> collection, IEnumerable<int> indices) {
        foreach (int offset in indices.Distances()) {
            collection = collection.Skip(offset);
            yield return collection.First();
        }
    }

    public static IEnumerable<int> Distances(this IEnumerable<int> numbers) {
        int offset = 0;
        foreach (var number in numbers) {
            yield return number - offset;
            offset = number;
        }
    }

Но мы не делаем

Из-за отложенного выполнения LINQs Skip слишком медленный.

   public static IEnumerable<T> GetIndexedItems4<T>(this IEnumerable<T> collection, IEnumerable<int> indices) {
        var rest = collection.GetEnumerator();
        foreach (int offset in indices.Distances()) {
            Skip(rest, offset);
            yield return rest.Current;
        }
    }

    static void Skip<T>(IEnumerator<T> enumerator, int skip) {
        while (skip > 0) {
            enumerator.MoveNext();
            skip--;
        }
        return;
    }

    static IEnumerable<int> Distances(this IEnumerable<int> numbers) {
        int offset = 0;
        foreach (var number in numbers) {
            yield return number - offset;
            offset = number;
        }
    }

Бенчмаркинг дает нам аналогичную производительность решению Эрика.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication21 {

    static class LinqExtensions {

        public static IEnumerable<T> GetIndexedItemsEric<T>(this IEnumerable<T> collection, IEnumerable<int> indices) {
            int currentIndex = -1;
            using (var collectionEnum = collection.GetEnumerator()) {
                foreach (int index in indices) {
                    while (collectionEnum.MoveNext()) {
                        currentIndex += 1;
                        if (currentIndex == index) {
                            yield return collectionEnum.Current;
                            break;
                        }
                    }
                }
            }
        }

        public static IEnumerable<T> GetIndexedItemsSam<T>(this IEnumerable<T> collection, IEnumerable<int> indices) {
            var rest = collection.GetEnumerator();
            foreach (int offset in indices.Distances()) {
                Skip(rest, offset);
                yield return rest.Current;
            }
        }

        static void Skip<T>(this IEnumerator<T> enumerator, int skip) {
            while (skip > 0) {
                enumerator.MoveNext();
                skip--;
            }
            return;
        }

        static IEnumerable<int> Distances(this IEnumerable<int> numbers) {
            int offset = 0;
            foreach (var number in numbers) {
                yield return number - offset;
                offset = number;
            }
        }
    } 

    class Program {

        static void TimeAction(string description, int iterations, Action func) {
            var watch = new Stopwatch();
            watch.Start();
            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 max = 100000;
            int lookupCount = 1000;
            int iterations = 500;
            var rand = new Random();
            var array = Enumerable.Range(0, max).ToArray();
            var lookups = Enumerable.Range(0, lookupCount).Select(i => rand.Next(max - 1)).Distinct().OrderBy(_ => _).ToArray();

            // warmup 
            array.GetIndexedItemsEric(lookups).ToArray();
            array.GetIndexedItemsSam(lookups).ToArray();

            TimeAction("Eric Solution", iterations, () => {
                array.GetIndexedItemsEric(lookups).ToArray();
            });

            TimeAction("Sam Solution", iterations, () =>
            {
                array.GetIndexedItemsEric(lookups).ToArray();
            });

            Console.ReadKey();
        }
    }
}
 
Eric Solution Time Elapsed 770 ms
Sam Solution Time Elapsed 768 ms

Ответ 10

Мне нравится linq.

    IList<T> list = collection.ToList<T>();

    var result = from i in indexes
                 select list[i];

    return result.ToList<T>();

Ответ 11

Как я понимаю, ICollection может не иметь никакого порядка, поэтому нет чрезвычайно элегантного решения для доступа к вещам по индексу. Вы хотите рассмотреть использование словаря или списка для хранения данных в коллекции.

Лучший способ, который я могу придумать, - перебирать коллекцию, сохраняя при этом отслеживание того, на каком индексе вы находитесь. Затем проверьте, содержит ли список индексов этот индекс. Если это так, верните этот элемент.

Ответ 12

    public static IEnumerable<T> WhereIndexes<T>(this IEnumerable<T> collection, IEnumerable<int> indexes)
    {
        IList<T> l = new List<T>(collection);
        foreach (var index in indexes)
        {
            yield return l[index]; 
        }
    }

Ответ 13

Кажется, что наиболее эффективным способом было бы использовать Dictionary<int,T> вместо Collection<T>. Вы все равно можете сохранить список индексов, которые вы хотите использовать в IList<int>.

Ответ 14

Может быть, я что-то упустил, но что не так:

indexes.Select( (index => values[index]))