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

特定

IList indexes; ICollection collection; 

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

例如,如果包含集合

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

并包含索引

 1, 3 

回报将是

 "Cleveland," "Glenn" 

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

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

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

缺点:

  • 更难读

这是一个更快的版本:

 IEnumerable ByIndices(ICollection data, IList 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; } } } 

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

由于ICollection<>没有给你索引我只使用IEnumerable<> ,因为我不需要IList<>上的索引我也使用了IEnumerable<>

 public static IEnumerable IndexedLookup( IEnumerable indexes, IEnumerable 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。

我会使用扩展方法

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

您可以在扩展方法中执行此操作:

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

不优雅,但效率高 – 确保索引排序……

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

作为一个正确的答案:

 var col = new []{"a","b","c"}; var ints = new []{0,2}; var set = new HashSet(ints); var result = col.Where((item,index) => set.Contains(index)); 

通常使用IList.Contains或Enumerable.Contains,如果您不知道集合中将有多少索引,则不要在列表中进行查找。 或者你将以艰难的方式走O(n ^ 2)方式。 如果你想要安全起见,你应该使用中间的Lookup / Dictionary / Hashset并测试这个集合而不是在vanilla列表上(线性搜索不适合你)

这里有几个很好的建议,我只想投入两分钱。

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

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

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

解决方案1

  public static IEnumerable GetIndexedItems2(this IEnumerable collection, IEnumerable 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 GetIndexedItems3(this IEnumerable collection, IEnumerable indices) { foreach (int offset in indices.Distances()) { collection = collection.Skip(offset); yield return collection.First(); } } public static IEnumerable Distances(this IEnumerable numbers) { int offset = 0; foreach (var number in numbers) { yield return number - offset; offset = number; } } 

但我们还没有完成

由于延迟执行LINQs Skip太慢了。

  public static IEnumerable GetIndexedItems4(this IEnumerable collection, IEnumerable indices) { var rest = collection.GetEnumerator(); foreach (int offset in indices.Distances()) { Skip(rest, offset); yield return rest.Current; } } static void Skip(IEnumerator enumerator, int skip) { while (skip > 0) { enumerator.MoveNext(); skip--; } return; } static IEnumerable Distances(this IEnumerable 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 GetIndexedItemsEric(this IEnumerable collection, IEnumerable 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 GetIndexedItemsSam(this IEnumerable collection, IEnumerable indices) { var rest = collection.GetEnumerator(); foreach (int offset in indices.Distances()) { Skip(rest, offset); yield return rest.Current; } } static void Skip(this IEnumerator enumerator, int skip) { while (skip > 0) { enumerator.MoveNext(); skip--; } return; } static IEnumerable Distances(this IEnumerable 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的解决方案时间耗时770毫秒
 Sam的解决方案时间耗时768毫秒

我喜欢linq。

  IList list = collection.ToList(); var result = from i in indexes select list[i]; return result.ToList(); 

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

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

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

似乎最有效的方法是使用Dictionary而不是Collection 。 您仍然可以在IList保留要使用的索引列表。

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

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