从集合中通过索引获取一组项目的最优雅方法是什么?

时间:2009-06-19 14:52:52

标签: c#

鉴于

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

根据索引中提供的索引,集合中提取所有 T 的最优雅方法是什么?

例如,如果集合包含

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

索引包含

1, 3

回报将是

"Cleveland," "Glenn"

编辑:假设索引始终按升序排序。

14 个答案:

答案 0 :(得分:24)

这假设索引序列是非负索引的单调递增序列。策略很简单:对于每个索引,将集合上的枚举器提升到该点并生成元素。

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。
  • 只迭代集合一次;一些解决方案多次迭代集合(例如,从中构建一个列表。)

缺点:

  • 更难阅读

答案 1 :(得分:6)

这是一个更快的版本:

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;
        }
    }
}

答案 2 :(得分:4)

不确定这是多么优雅,但是你走了。

由于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;
        }
    }
}
编辑:注意我的解决方案类似于Erics。

答案 3 :(得分:3)

我会使用扩展方法

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

答案 4 :(得分:3)

您可以使用扩展方法执行此操作:

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++;
   }
}

答案 5 :(得分:3)

不优雅,但效率高 - 确保索引排序......

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;
    }
}

答案 6 :(得分:2)

作为一个正确的答案:

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并测试这个集合而不是在vanilla列表上(线性搜索对你不好)

答案 7 :(得分:2)

这里已经提出了一些很好的建议,我只会投入两分钱。

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

编辑:是的,第一次没想到它。只有在满足其他两个条件时才会发生增量。

答案 8 :(得分:2)

我觉得这个解决方案特别优雅,更容易理解。

解决方案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;
        }
    }

基准测试,使我们与Eric的解决方案具有相似的性能。

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's Solution", iterations, () => {
                array.GetIndexedItemsEric(lookups).ToArray();
            });

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

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

答案 9 :(得分:1)

我喜欢linq。

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

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

    return result.ToList<T>();

答案 10 :(得分:0)

据我了解,ICollection可能不一定有任何顺序,这就是为什么没有一个非常优雅的解决方案来访问索引的东西。许多人想要考虑使用字典或列表来存储集合中的数据。

我能想到的最好的方法是迭代整个集合,同时跟踪你所处的索引。然后检查索引列表是否包含该索引。如果是这样,请返回该元素。

答案 11 :(得分:0)

    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]; 
        }
    }

答案 12 :(得分:0)

似乎最有效的方法是使用Dictionary<int,T>代替Collection<T>。您仍然可以在IList<int>中保留要使用的索引列表。

答案 13 :(得分:0)

也许我错过了一些东西,但仅仅是:

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