从集合中通过索引获取一组项目的最优雅方法是什么?
特定
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]))