鉴于
IList<int> indexes;
ICollection<T> collection;
根据索引中提供的索引,集合中提取所有 T 的最优雅方法是什么?
例如,如果集合包含
"Brian", "Cleveland", "Joe", "Glenn", "Mort"
索引包含
1, 3
回报将是
"Cleveland," "Glenn"
编辑:假设索引始终按升序排序。
答案 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;
}
}
}
}
}
此解决方案优于其他解决方案:
缺点:
答案 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]))