admin管理员组文章数量:1135111
Very simple case: I have a list of items (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
. I need to filter it and drop a few first elements and a few last elements and to get a few items from the middle (for example I need to get (5, 6, 7, 8)
).
I wrote this method:
IEnumerable<int> GetCapturedTimeLapsesWithRatios(IEnumerable<int> allTimeLapsesProfitabilitiesResult, int from, int to)
{
foreach (var item in allTimeLapsesProfitabilitiesResult)
{
if (item <= from)
{
Console.WriteLine(item);
continue;
}
else
{
if (item >= to)
{
Console.WriteLine("if (item >= to) " + item);
yield return item;
yield break;
}
else
{
Console.WriteLine("else " + item);
yield return item;
break;
}
}
}
foreach (var item in allTimeLapsesProfitabilitiesResult)
{
if (item < to)
{
Console.WriteLine("if (item < to) " + item);
yield return item;
}
else
{
Console.WriteLine("else " + item);
yield return item;
yield break;
}
}
}
GetCapturedTimeLapsesWithRatios(new List<int>() {0,1,2,3,4,5,6,7,8,9,10 }, 4, 8);
And I get completely wrong result:
{ 5, 0, 1, 2, 3, 4, 5, 6, 7, 8 }
and the console displays more lines than I expected
0
1
2
3
4
else 5
if (item < to) 0 // wrong line
if (item < to) 1 // wrong line
if (item < to) 2 // wrong line
if (item < to) 3 // wrong line
if (item < to) 4 // wrong line
if (item < to) 5 // wrong line
if (item < to) 6
if (item < to) 7
else 8
I see that second 'foreach' started iterating from '0' element, but I expected it starts iterating from '6' element because first 'foreach' processed first 5.
So is there a way to use 'foreach' multiple times (every 'foreach' with different filtering) on the same IEnumerable
inside a method with 'yield'? Or maybe I do it wrong way?
PS: a code above is a dummy because my actual code is more complex, so I can't use just standard LINQ methods like
new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
.SkipWhile(i => i <= 4)
.TakeWhile(i => i <= 8)
UPDATE: I rewrote my code using GetEnumerator()
and now it works as I expect, but readability now is much worse, So I want to know is there a way to make it more readable?
IEnumerable<int> GetCapturedTimeLapsesWithRatios(IEnumerable<int> allTimeLapsesProfitabilitiesResult, int from, int to)
{
using (var enumerator = allTimeLapsesProfitabilitiesResult.GetEnumerator())
{
do
{
if (!enumerator.MoveNext())
yield break;
var item = enumerator.Current;
if (item <= from)
{
Console.WriteLine(item);
continue;
}
else
{
if (item >= to)
{
Console.WriteLine("if (item >= to) " + item);
yield return item;
yield break;
}
else
{
Console.WriteLine("else " + item);
yield return item;
break;
}
}
} while (true);
do
{
if (!enumerator.MoveNext())
yield break;
var item = enumerator.Current;
if (item < to)
{
Console.WriteLine("if (item < to) " + item);
yield return item;
}
else
{
Console.WriteLine("else " + item);
yield return item;
yield break;
}
} while (true);
}
}
GetCapturedTimeLapsesWithRatios(new List<int>() {0,1,2,3,4,5,6,7,8,9,10 }, 4, 8);
UPDATE: mjwills asked:
Then this is a XY problem, and you are hiding from us the information needed to help you fully. Tell us more about the nature of this complex code.
OK. I have ascending ordered list of DateOnly
(final point of interval), a previous DateOnly
is a start point for current DateOnly
, so I have a ascending ordered sequence of intervals. And I have input interval (from
, to
), and I need to return a list of only those intervals that 'wrap' input interval.
For example, given this list:
2024-10-01
2024-10-10
2024-10-15
2024-10-25
2024-10-28
2024-10-30
So I have list of intervals:
1970-01-01 to 2024-10-01
2024-10-01 to 2024-10-10
2024-10-10 to 2024-10-15
2024-10-15 to 2024-10-25
2024-10-25 to 2024-10-28
2024-10-28 to 2024-10-30
Input interval:
2024-10-12 to 2024-10-27
I need to find intervals in ascending ordered list that 'wrap' input interval and I need return DateOnly
-s.
So, for this example, I need to return:
2024-10-15
2024-10-25
2024-10-28
UPDATE: solution of @Enigmativity below works, but makes 3 condition evaluating in every iteration. My idea was not make unnesessary condition evaluations if possible. For example, once you reach 'if (e.Current > @from)' returning true then no need to check it again because it will return true everytime until the end of loop because input list is ascending ordered. So in order not no make unnesessary condition evaluations I do different condition evaluations in 2 different foreach, not in 1 foreach.
IEnumerable<DateOnly> GetCapturedTimeLapsesWithRatios(IEnumerable<DateOnly> input, DateOnly @from, DateOnly @to)
{
bool yielding = false;
IEnumerator<DateOnly> e = input.GetEnumerator();
while (e.MoveNext())
{
if (e.Current > @from) yielding = true;
if (yielding) yield return e.Current;
if (e.Current > @to) yield break;
}
}
Very simple case: I have a list of items (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
. I need to filter it and drop a few first elements and a few last elements and to get a few items from the middle (for example I need to get (5, 6, 7, 8)
).
I wrote this method:
IEnumerable<int> GetCapturedTimeLapsesWithRatios(IEnumerable<int> allTimeLapsesProfitabilitiesResult, int from, int to)
{
foreach (var item in allTimeLapsesProfitabilitiesResult)
{
if (item <= from)
{
Console.WriteLine(item);
continue;
}
else
{
if (item >= to)
{
Console.WriteLine("if (item >= to) " + item);
yield return item;
yield break;
}
else
{
Console.WriteLine("else " + item);
yield return item;
break;
}
}
}
foreach (var item in allTimeLapsesProfitabilitiesResult)
{
if (item < to)
{
Console.WriteLine("if (item < to) " + item);
yield return item;
}
else
{
Console.WriteLine("else " + item);
yield return item;
yield break;
}
}
}
GetCapturedTimeLapsesWithRatios(new List<int>() {0,1,2,3,4,5,6,7,8,9,10 }, 4, 8);
And I get completely wrong result:
{ 5, 0, 1, 2, 3, 4, 5, 6, 7, 8 }
and the console displays more lines than I expected
0
1
2
3
4
else 5
if (item < to) 0 // wrong line
if (item < to) 1 // wrong line
if (item < to) 2 // wrong line
if (item < to) 3 // wrong line
if (item < to) 4 // wrong line
if (item < to) 5 // wrong line
if (item < to) 6
if (item < to) 7
else 8
I see that second 'foreach' started iterating from '0' element, but I expected it starts iterating from '6' element because first 'foreach' processed first 5.
So is there a way to use 'foreach' multiple times (every 'foreach' with different filtering) on the same IEnumerable
inside a method with 'yield'? Or maybe I do it wrong way?
PS: a code above is a dummy because my actual code is more complex, so I can't use just standard LINQ methods like
new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
.SkipWhile(i => i <= 4)
.TakeWhile(i => i <= 8)
UPDATE: I rewrote my code using GetEnumerator()
and now it works as I expect, but readability now is much worse, So I want to know is there a way to make it more readable?
IEnumerable<int> GetCapturedTimeLapsesWithRatios(IEnumerable<int> allTimeLapsesProfitabilitiesResult, int from, int to)
{
using (var enumerator = allTimeLapsesProfitabilitiesResult.GetEnumerator())
{
do
{
if (!enumerator.MoveNext())
yield break;
var item = enumerator.Current;
if (item <= from)
{
Console.WriteLine(item);
continue;
}
else
{
if (item >= to)
{
Console.WriteLine("if (item >= to) " + item);
yield return item;
yield break;
}
else
{
Console.WriteLine("else " + item);
yield return item;
break;
}
}
} while (true);
do
{
if (!enumerator.MoveNext())
yield break;
var item = enumerator.Current;
if (item < to)
{
Console.WriteLine("if (item < to) " + item);
yield return item;
}
else
{
Console.WriteLine("else " + item);
yield return item;
yield break;
}
} while (true);
}
}
GetCapturedTimeLapsesWithRatios(new List<int>() {0,1,2,3,4,5,6,7,8,9,10 }, 4, 8);
UPDATE: mjwills asked:
Then this is a XY problem, and you are hiding from us the information needed to help you fully. Tell us more about the nature of this complex code.
OK. I have ascending ordered list of DateOnly
(final point of interval), a previous DateOnly
is a start point for current DateOnly
, so I have a ascending ordered sequence of intervals. And I have input interval (from
, to
), and I need to return a list of only those intervals that 'wrap' input interval.
For example, given this list:
2024-10-01
2024-10-10
2024-10-15
2024-10-25
2024-10-28
2024-10-30
So I have list of intervals:
1970-01-01 to 2024-10-01
2024-10-01 to 2024-10-10
2024-10-10 to 2024-10-15
2024-10-15 to 2024-10-25
2024-10-25 to 2024-10-28
2024-10-28 to 2024-10-30
Input interval:
2024-10-12 to 2024-10-27
I need to find intervals in ascending ordered list that 'wrap' input interval and I need return DateOnly
-s.
So, for this example, I need to return:
2024-10-15
2024-10-25
2024-10-28
UPDATE: solution of @Enigmativity below works, but makes 3 condition evaluating in every iteration. My idea was not make unnesessary condition evaluations if possible. For example, once you reach 'if (e.Current > @from)' returning true then no need to check it again because it will return true everytime until the end of loop because input list is ascending ordered. So in order not no make unnesessary condition evaluations I do different condition evaluations in 2 different foreach, not in 1 foreach.
IEnumerable<DateOnly> GetCapturedTimeLapsesWithRatios(IEnumerable<DateOnly> input, DateOnly @from, DateOnly @to)
{
bool yielding = false;
IEnumerator<DateOnly> e = input.GetEnumerator();
while (e.MoveNext())
{
if (e.Current > @from) yielding = true;
if (yielding) yield return e.Current;
if (e.Current > @to) yield break;
}
}
Share
Improve this question
edited Jan 8 at 8:20
Andrew
asked Jan 7 at 23:01
AndrewAndrew
352 bronze badges
10
|
Show 5 more comments
3 Answers
Reset to default 1Add MoreLinq
to your project then use code like:
using System.Collections.Generic;
using System.Linq;
using MoreLinq;
public class Program
{
public static void Main()
{
var data1 = DateTime.Parse("2024-10-01");
var data2 = DateTime.Parse("2024-10-10");
var data3 = DateTime.Parse("2024-10-15");
var data4 = DateTime.Parse("2024-10-25");
var data5 = DateTime.Parse("2024-10-28");
var data6 = DateTime.Parse("2024-10-30");
var dates = new List<DateTime>() { data1, data2, data3, data4, data5, data6 };
var fromDate = DateTime.Parse("2024-10-12");
var toDate = DateTime.Parse("2024-10-27");
var result = Process(dates, fromDate, toDate);
Console.WriteLine(string.Join(",", result));
}
public static IEnumerable<DateTime> Process(IEnumerable<DateTime> dates, DateTime from, DateTime to)
{
var data = dates.SkipWhile(z => z < from).TakeUntil(z => z > to);
return data;
}
}
This will give you what you are looking for I suspect.
It seems to me that this works:
IEnumerable<DateOnly> GetCapturedTimeLapsesWithRatios(IEnumerable<DateOnly> input, DateOnly @from, DateOnly @to)
{
bool yielding = false;
IEnumerator<DateOnly> e = input.GetEnumerator();
while (e.MoveNext())
{
if (e.Current > @from) yielding = true;
if (yielding) yield return e.Current;
if (e.Current > @to) yield break;
}
}
When I run with this data:
var list = new List<DateOnly>()
{
new DateOnly(2024, 10, 1),
new DateOnly(2024, 10, 10),
new DateOnly(2024, 10, 15),
new DateOnly(2024, 10, 25),
new DateOnly(2024, 10, 28),
new DateOnly(2024, 10, 30),
};
var results = GetCapturedTimeLapsesWithRatios(list, new DateOnly(2024, 10, 12), new DateOnly(2024, 10, 27));
I get:
PS: ... my actual code is more complex, so I can't use just standard LINQ methods like [SkipWhile()/TakeWhile()]
You can still do this as long as you can constrain your data to something that implements IComparable
. Then you can even solve this in a generic way that works for any compatible type. The naïve initial implementation looks like this:
IEnumerable<T> GetCapturedTimeLapsesWithRatios<T>(IEnumerable<T> allTimeLapsesProfitabilitiesResult, T from, T to) where T : IComparable
{
return allTimeLapsesProfitabilitiesResult.
SkipWhile(a => a.CompareTo(from) < 0).
TakeWhile(a => a.CompareTo(to) <= 0);
}
Technically, that's still a one-liner. Check here to see it work with both int
and DateOnly
values:
https://dotnetfiddle.net/0B8a0i
I know we only really need DateOnly
now, and don't need the generic support anymore, but the work is done so it costs nothing, and it might be useful in the event this someday changes to need to handle full DateTime
values.
But I'm not done yet! This still fails your requirements because it doesn't wrap the final range. We can solve for that by flagging when we enter and exit the range using that as part of the predicate criteria. This lets us "buffer" the final value, so it's included.
That looks like this:
public static IEnumerable<T> GetCapturedTimeLapsesWithRatios<T>(IEnumerable<T> allTimeLapsesProfitabilitiesResult, T from, T to) where T : IComparable
{
bool inRange = false;
return allTimeLapsesProfitabilitiesResult.
SkipWhile(a => a.CompareTo(from)< 0).
TakeWhile(a => {
bool result = a.CompareTo(to) <= 0 || inRange;
inRange = a.CompareTo(to) < 0;
return result;
});
}
And you can see the result here:
https://dotnetfiddle.net/JdW1Qs
It's up to you if you find this to be cleaner or simpler than manual iteration.
Additionally, it's not clear to me how you want this to behave if the to
value of the input range matches a value in the data. For example, if the end value were 2024-10-25
instead of 2024-10-27
, it's not clear if we should still include 2024-10-28
in the result.
Right now, you will NOT get the extra value, and it would stop at 2024-10-25
. I suspect (but am not sure) that is correct, because the original int
sample works differently. If I'm wrong, you fix it by adding one character to change the final < 0
comparison to <= 0
:
public static IEnumerable<T> GetCapturedTimeLapsesWithRatios<T>(IEnumerable<T> allTimeLapsesProfitabilitiesResult, T from, T to) where T : IComparable
{
bool inRange = false;
return allTimeLapsesProfitabilitiesResult.
SkipWhile(a => a.CompareTo(from)< 0).
TakeWhile(a => {
bool result = a.CompareTo(to) <= 0 || inRange;
inRange = a.CompareTo(to) <= 0;
return result;
});
}
本文标签:
版权声明:本文标题:c# - Is it possible to use 'foreach' on the same IEnumerable twice or more inside a method with 'yield&a 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736769942a1952025.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
GetCapturedTimeLapsesWithRatios(new List<int>() { 0, 1, 2, 3, 4, 3, 2, 1, 5, 9, 10 }, 4, 8)
? – Enigmativity Commented Jan 7 at 23:26but I expected it starts iterating from '6' element because first 'foreach' processed first 5.
The short answer is "no - it doesn't work that way". And logically you don't want it to. If I passed a List to two different methods and asked them both to write the values to the console I wouldn't expect one method to impact what the other does. Which is what you are essentially asking for. – mjwills Commented Jan 7 at 23:47