我有一个自定义类型的IEnumerable。 (我是从SelectMany那里得到的)
我在IEnumerable中也有一个项目(myItem),我想要IEnumerable中的上一个和下一个项目。
目前,我正在做这样的事情:
var previousItem = myIEnumerable.Reverse().SkipWhile(
i => i.UniqueObjectID != myItem.UniqueObjectID).Skip(1).FirstOrDefault();
我只需省略.Reverse
即可获得下一个项目。
或,我可以:
int index = myIEnumerable.ToList().FindIndex(
i => i.UniqueObjectID == myItem.UniqueObjectID)
然后使用.ElementAt(index +/- 1)
获取上一个或下一个项目。
“更好”包括性能(内存和速度)和可读性的组合;可读性是我最关心的问题。
答案 0 :(得分:39)
首先关闭
“更好”包括性能(记忆和速度)的组合
一般来说,你不能同时拥有这两者,经验法则是,如果你优化速度,它会花费内存,如果你优化内存,它会花费你的速度。
有一个更好的选择,在内存和速度前端都表现良好,并且可以以可读的方式使用(我对功能名称不满意,但是,FindItemReturningPreviousItemFoundItemAndNextItem
有点像一口)。
所以,它看起来像是自定义查找扩展方法的时候了。 。
public static IEnumerable<T> FindSandwichedItem<T>(this IEnumerable<T> items, Predicate<T> matchFilling)
{
if (items == null)
throw new ArgumentNullException("items");
if (matchFilling == null)
throw new ArgumentNullException("matchFilling");
return FindSandwichedItemImpl(items, matchFilling);
}
private static IEnumerable<T> FindSandwichedItemImpl<T>(IEnumerable<T> items, Predicate<T> matchFilling)
{
using(var iter = items.GetEnumerator())
{
T previous = default(T);
while(iter.MoveNext())
{
if(matchFilling(iter.Current))
{
yield return previous;
yield return iter.Current;
if (iter.MoveNext())
yield return iter.Current;
else
yield return default(T);
yield break;
}
previous = iter.Current;
}
}
// If we get here nothing has been found so return three default values
yield return default(T); // Previous
yield return default(T); // Current
yield return default(T); // Next
}
如果您需要多次引用这些项目,可以将结果缓存到列表中,但它会返回找到的项目,前面跟上一项,然后是下面的项目。 e.g。
var sandwichedItems = myIEnumerable.FindSandwichedItem(item => item.objectId == "MyObjectId").ToList();
var previousItem = sandwichedItems[0];
var myItem = sandwichedItems[1];
var nextItem = sandwichedItems[2];
如果第一个或最后一个项目可能需要根据您的要求进行更改,则返回默认值。
希望这有帮助。
答案 1 :(得分:20)
为了便于阅读,我将IEnumerable
加载到链接列表中:
var e = Enumerable.Range(0,100);
var itemIKnow = 50;
var linkedList = new LinkedList<int>(e);
var listNode = linkedList.Find(itemIKnow);
var next = listNode.Next.Value; //probably a good idea to check for null
var prev = listNode.Previous.Value; //ditto
答案 2 :(得分:13)
通过创建用于为当前元素建立上下文的扩展方法,您可以使用Linq查询,如下所示:
var result = myIEnumerable.WithContext()
.Single(i => i.Current.UniqueObjectID == myItem.UniqueObjectID);
var previous = result.Previous;
var next = result.Next;
扩展名是这样的:
public class ElementWithContext<T>
{
public T Previous { get; private set; }
public T Next { get; private set; }
public T Current { get; private set; }
public ElementWithContext(T current, T previous, T next)
{
Current = current;
Previous = previous;
Next = next;
}
}
public static class LinqExtensions
{
public static IEnumerable<ElementWithContext<T>>
WithContext<T>(this IEnumerable<T> source)
{
T previous = default(T);
T current = source.FirstOrDefault();
foreach (T next in source.Union(new[] { default(T) }).Skip(1))
{
yield return new ElementWithContext<T>(current, previous, next);
previous = current;
current = next;
}
}
}
答案 3 :(得分:4)
您可以将可枚举缓存在列表中
var myList = myIEnumerable.ToList()
通过索引
迭代它for (int i = 0; i < myList.Count; i++)
然后当前元素为myList[i]
,前一个元素为myList[i-1]
,下一个元素为myList[i+1]
(不要忘记列表中第一个和最后一个元素的特殊情况。)
答案 4 :(得分:2)
<强> CPU 强>
完全取决于对象在序列中的位置。如果它位于最后,我会期望第二个更快,超过因子2(但只是一个常数因子)。如果它位于开头,则第一个会更快,因为您不会遍历整个列表。
<强>内存强>
第一个是迭代序列而不保存序列,因此内存命中率非常小。第二个解决方案将占用与列表长度*引用+对象+开销一样多的内存。
答案 5 :(得分:2)
你真的过于复杂化了:
有时候只需要一个for
循环就可以做得更好,而且我认为可以更清楚地实现你想做的事情/
var myList = myIEnumerable.ToList();
for(i = 0; i < myList.Length; i++)
{
if(myList[i].UniqueObjectID == myItem.UniqueObjectID)
{
previousItem = myList[(i - 1) % (myList.Length - 1)];
nextItem = myList[(i + 1) % (myList.Length - 1)];
}
}
答案 6 :(得分:2)
我想我会尝试使用Linq的Zip来解答这个问题。
string[] items = {"nought","one","two","three","four"};
var item = items[2];
var sandwiched =
items
.Zip( items.Skip(1), (previous,current) => new { previous, current } )
.Zip( items.Skip(2), (pair,next) => new { pair.previous, pair.current, next } )
.FirstOrDefault( triplet => triplet.current == item );
这将返回匿名类型{previous,current,next}。 不幸的是,这只适用于索引1,2和3.
string[] items = {"nought","one","two","three","four"};
var item = items[4];
var pad1 = Enumerable.Repeat( "", 1 );
var pad2 = Enumerable.Repeat( "", 2 );
var padded = pad1.Concat( items );
var next1 = items.Concat( pad1 );
var next2 = items.Skip(1).Concat( pad2 );
var sandwiched =
padded
.Zip( next1, (previous,current) => new { previous, current } )
.Zip( next2, (pair,next) => new { pair.previous, pair.current, next } )
.FirstOrDefault( triplet => triplet.current == item );
此版本适用于所有索引。 这两个版本都使用Linq的惰性评估。
答案 7 :(得分:1)
这里是LINQ扩展方法,它返回当前项目以及上一项和下一项。它产生ValueTuple
类型以避免分配。来源被枚举一次。
public static IEnumerable<(T Previous, T Current, T Next)> WithPreviousAndNext<T>(
this IEnumerable<T> source, T firstPrevious = default, T lastNext = default)
{
Queue<T> queue = new Queue<T>(2);
queue.Enqueue(firstPrevious);
foreach (var item in source)
{
if (queue.Count > 1)
{
yield return (queue.Dequeue(), queue.Peek(), item);
}
queue.Enqueue(item);
}
if (queue.Count > 1) yield return (queue.Dequeue(), queue.Peek(), lastNext);
}
用法示例:
var source = Enumerable.Range(1, 5);
Console.WriteLine($"Source: {String.Join(", ", source)}");
var result = source.WithPreviousAndNext(firstPrevious: -1, lastNext: -1);
Console.WriteLine($"Result: {String.Join(", ", result)}");
输出:
资料来源:1、2、3、4、5
结果:(-1,1,2),(1,2,3),(2,3,4),(3,4,5),(4,5,-1)
要获取特定项目的上一个和下一个(使用tuple deconstruction):
var (previous, current, next) = myIEnumerable
.WithPreviousAndNext()
.First(e => e.Current.UniqueObjectID == myItem.UniqueObjectID);
答案 8 :(得分:0)
如果你需要myIEnumerable中的每个元素,我只需要遍历它,保持对前两个元素的引用。在循环体中,我将对第二个前一个元素进行处理,当前将是它的后代,并且第一个先前是它的祖先。
如果你只需要一个元素,我会选择你的第一个方法。
答案 9 :(得分:0)
这里有一些扩展方法。名称是通用名称,可与任何简单类型重复使用,并且存在查找重载以获取获取下一个或上一个项目所需的项目。我将对解决方案进行基准测试,然后看看您可以在哪里压缩周期。
public static class ExtensionMethods
{
public static T Previous<T>(this List<T> list, T item) {
var index = list.IndexOf(item) - 1;
return index > -1 ? list[index] : default(T);
}
public static T Next<T>(this List<T> list, T item) {
var index = list.IndexOf(item) + 1;
return index < list.Count() ? list[index] : default(T);
}
public static T Previous<T>(this List<T> list, Func<T, Boolean> lookup) {
var item = list.SingleOrDefault(lookup);
var index = list.IndexOf(item) - 1;
return index > -1 ? list[index] : default(T);
}
public static T Next<T>(this List<T> list, Func<T,Boolean> lookup) {
var item = list.SingleOrDefault(lookup);
var index = list.IndexOf(item) + 1;
return index < list.Count() ? list[index] : default(T);
}
public static T PreviousOrFirst<T>(this List<T> list, T item) {
if(list.Count() < 1)
throw new Exception("No array items!");
var previous = list.Previous(item);
return previous == null ? list.First() : previous;
}
public static T NextOrLast<T>(this List<T> list, T item) {
if(list.Count() < 1)
throw new Exception("No array items!");
var next = list.Next(item);
return next == null ? list.Last() : next;
}
public static T PreviousOrFirst<T>(this List<T> list, Func<T,Boolean> lookup) {
if(list.Count() < 1)
throw new Exception("No array items!");
var previous = list.Previous(lookup);
return previous == null ? list.First() : previous;
}
public static T NextOrLast<T>(this List<T> list, Func<T,Boolean> lookup) {
if(list.Count() < 1)
throw new Exception("No array items!");
var next = list.Next(lookup);
return next == null ? list.Last() : next;
}
}
您可以像这样使用它们。
var previous = list.Previous(obj);
var next = list.Next(obj);
var previousWithLookup = list.Previous((o) => o.LookupProperty == otherObj.LookupProperty);
var nextWithLookup = list.Next((o) => o.LookupProperty == otherObj.LookupProperty);
var previousOrFirst = list.PreviousOrFirst(obj);
var nextOrLast = list.NextOrLast(ob);
var previousOrFirstWithLookup = list.PreviousOrFirst((o) => o.LookupProperty == otherObj.LookupProperty);
var nextOrLastWithLookup = list.NextOrLast((o) => o.LookupProperty == otherObj.LookupProperty);
答案 10 :(得分:0)
我使用以下技术:
var items = new[] { "Bob", "Jon", "Zac" };
var sandwiches = items
.Sandwich()
.ToList();
哪个会产生以下结果:
请注意,第一个Previous
值和最后一个Next
值都为空。
它使用以下扩展方法:
public static IEnumerable<(T Previous, T Current, T Next)> Sandwich<T>(this IEnumerable<T> source, T beforeFirst = default, T afterLast = default)
{
var sourceList = source.ToList();
T previous = beforeFirst;
T current = sourceList.FirstOrDefault();
foreach (var next in sourceList.Skip(1))
{
yield return (previous, current, next);
previous = current;
current = next;
}
yield return (previous, current, afterLast);
}