admin管理员组文章数量:1336732
This is hard to search for as a non-native English speaker, as any search terms I come up with results in lots of questions related to OrderBy(Descending), and not what I am after.
Imagine I have a Thing, and this thing has a collection of ThingEvent objects. Now I want to query for Thing objects that have events occurring in a certain order.
var result = things.Where(t =>
t.ThingEvents
.Has( te => te.EventType == 1 )
.FollowedBy( te => te.EventType == 2 )
)
.ToList();
This is simple psuedo-code only to illustrate a point. Thing.ThingEvents is a collection ala IEnumerable, so Has() and FollowedBy() are two imaginary LINQ methods that help me do the following:
- Verify that the given predicate is true - in this respect they are the same as the Where() LINQ method.
- In the case of FollowedBy, that this happens AFTER a previous "match" in the sequence.
The last part is what trips me up - it means we need the ThingEvents collection to contain ThingEvents in a certain order (this collection is sorted correctly by the time we get to this query).
A collection containing eventtypes [1,2] would match. So would [1,1,1,1,2,2,2,2] and [1,2,1,2,3,1,2,1,2]. But [2,1,1,1,1,1] would not match - because at no point does an event type of 2 happen after an event type of 1.
Is it possible to query like this given the built-in LINQ methods?
If not, would it even be possible to build something like this by extending LINQ itself? Where my understanding breaks down with regards to solving this is that a method like Where() returns a simple boolean stating if the queried item meets the given predicate or not, my imagined Has() and FollowedBy() would do the same, but still pass on the "context" of where they are in the list to the following methods. Of course there could be other similar methods as well (PrecededBy() for instance).
Am I trying to do something that LINQ was not designed to do here? Or am I going about it all wrong? Or perhaps missing something pretty basic?
Thanks for any insights!
EDIT: I tried to be precise, but there are always some details escaping and adding confusion. :)
As some have pointed out, in my example it is not clear if "FollowedBy" means "is followed immediately by" (i.e. the very next item is...), or if it just means "any later item is...". My original intention was the latter, but your questions have enlightened me to see that there could probably be a need for both.
To add some context to perhaps make it easier to see the usefulness of what I am searching for: There can be many of these "Thing" objects, for a normal query there would be a couple of hundred, but it could be several thousand if aggregating more data. And each Thing object has between 0 and 20 ThingEvent objects.
Thing etc. lives in library code, and the use for the above is for users trying to read and create statistical information from the database of Thing objects. A certain subset of these will be in memory, and the user will be looking for information like "Does it EVER happen that event type 2 happens after event type 1 for a Thing?" - which is what my feeble attempt at psuedo code above represents.
There could of course be any number of ways they want to query this, and LINQ makes MOST of them easy. What I didn't find an easy solution for is the bit about also querying about their "spatial" position relative to each other in the collection, list, whatever.
As some have pointed out, multiple enumerations is a good thing to avoid. Is IEnumerable the wrong abstraction for this? Would I need something similar that also adds awareness of position of last and current item? I love the flexibility of LINQ, but of course I don't want to have to recreate it in order to give users ultimate flexibility. Or do I?
SECOND EDIT: Besides the point, but a shower thought this morning was that this could probably be solved easily by... regular expressions! (Yes, this abandons the dream of an elegant LINQ solution).
Say you have only 10 event types, so we assign them the character 0 through 9. The Thing object would have a conversion from its event collection to a string of these event types. Then we could easily construct a regex to match quite complex variants, such as "1.*2" for FollowedBy(), or just "12" for FollowedImmediatelyBy(). I might actually explore this as an easier way to attain the flexibility I want, even if it would be slightly more complex than this trivial example. If this incidentally helps prove that a RegEx can actually make solving a problem easier and I win a Nobel price, the beer is on me!
This is hard to search for as a non-native English speaker, as any search terms I come up with results in lots of questions related to OrderBy(Descending), and not what I am after.
Imagine I have a Thing, and this thing has a collection of ThingEvent objects. Now I want to query for Thing objects that have events occurring in a certain order.
var result = things.Where(t =>
t.ThingEvents
.Has( te => te.EventType == 1 )
.FollowedBy( te => te.EventType == 2 )
)
.ToList();
This is simple psuedo-code only to illustrate a point. Thing.ThingEvents is a collection ala IEnumerable, so Has() and FollowedBy() are two imaginary LINQ methods that help me do the following:
- Verify that the given predicate is true - in this respect they are the same as the Where() LINQ method.
- In the case of FollowedBy, that this happens AFTER a previous "match" in the sequence.
The last part is what trips me up - it means we need the ThingEvents collection to contain ThingEvents in a certain order (this collection is sorted correctly by the time we get to this query).
A collection containing eventtypes [1,2] would match. So would [1,1,1,1,2,2,2,2] and [1,2,1,2,3,1,2,1,2]. But [2,1,1,1,1,1] would not match - because at no point does an event type of 2 happen after an event type of 1.
Is it possible to query like this given the built-in LINQ methods?
If not, would it even be possible to build something like this by extending LINQ itself? Where my understanding breaks down with regards to solving this is that a method like Where() returns a simple boolean stating if the queried item meets the given predicate or not, my imagined Has() and FollowedBy() would do the same, but still pass on the "context" of where they are in the list to the following methods. Of course there could be other similar methods as well (PrecededBy() for instance).
Am I trying to do something that LINQ was not designed to do here? Or am I going about it all wrong? Or perhaps missing something pretty basic?
Thanks for any insights!
EDIT: I tried to be precise, but there are always some details escaping and adding confusion. :)
As some have pointed out, in my example it is not clear if "FollowedBy" means "is followed immediately by" (i.e. the very next item is...), or if it just means "any later item is...". My original intention was the latter, but your questions have enlightened me to see that there could probably be a need for both.
To add some context to perhaps make it easier to see the usefulness of what I am searching for: There can be many of these "Thing" objects, for a normal query there would be a couple of hundred, but it could be several thousand if aggregating more data. And each Thing object has between 0 and 20 ThingEvent objects.
Thing etc. lives in library code, and the use for the above is for users trying to read and create statistical information from the database of Thing objects. A certain subset of these will be in memory, and the user will be looking for information like "Does it EVER happen that event type 2 happens after event type 1 for a Thing?" - which is what my feeble attempt at psuedo code above represents.
There could of course be any number of ways they want to query this, and LINQ makes MOST of them easy. What I didn't find an easy solution for is the bit about also querying about their "spatial" position relative to each other in the collection, list, whatever.
As some have pointed out, multiple enumerations is a good thing to avoid. Is IEnumerable the wrong abstraction for this? Would I need something similar that also adds awareness of position of last and current item? I love the flexibility of LINQ, but of course I don't want to have to recreate it in order to give users ultimate flexibility. Or do I?
SECOND EDIT: Besides the point, but a shower thought this morning was that this could probably be solved easily by... regular expressions! (Yes, this abandons the dream of an elegant LINQ solution).
Say you have only 10 event types, so we assign them the character 0 through 9. The Thing object would have a conversion from its event collection to a string of these event types. Then we could easily construct a regex to match quite complex variants, such as "1.*2" for FollowedBy(), or just "12" for FollowedImmediatelyBy(). I might actually explore this as an easier way to attain the flexibility I want, even if it would be slightly more complex than this trivial example. If this incidentally helps prove that a RegEx can actually make solving a problem easier and I win a Nobel price, the beer is on me!
Share Improve this question edited Nov 21, 2024 at 21:04 Rune Jacobsen asked Nov 20, 2024 at 12:44 Rune JacobsenRune Jacobsen 10.1k12 gold badges60 silver badges76 bronze badges 11 | Show 6 more comments4 Answers
Reset to default 2(Update: Adding predicate support was easy.)
I created two LINQ extension methods: Has and FollowedBy. For example:
var rslt = sequence
.Has(t => t == 5)
.FollowedBy(t => t >= 8)
.FollowedBy(t => t < 3)
.Any();
The return value is a single-item sequence on success: an empty sequence (Enumerable.Empty
, essentially) on failure.
This is a little less than ideal. I'd like it to return a Boolean result, but the function chaining expects Has
to return an IEnumerable
so that FollowedBy
can chain. I don't see how to resolve that. But the call to Any()
fixes the problem.
Code below. This is just a proof of concept.
using System.Collections;
namespace HasLinqExtension
{
public static class HasLinqExtension
{
public static IHasEnumerable<TSource> Has<TSource>(this IEnumerable<TSource> source, Predicate<TSource> target)
{
return new HasEnumerable<TSource>(source, target);
}
public static IHasEnumerable<TSource> FollowedBy<TSource>(this IHasEnumerable<TSource> source, Predicate<TSource> target)
{
source.AddPredicate(target);
return source;
}
public interface IHasEnumerable<TSource>: IEnumerable<TSource>
{
public HasEnumerable<TSource> AddPredicate(Predicate<TSource> patternItem);
}
public class HasEnumerable<TSource> : IHasEnumerable<TSource>
{
private readonly IEnumerable<TSource> _source;
private readonly List<Predicate<TSource>> _predicates = [];
public HasEnumerable(IEnumerable<TSource> source, Predicate<TSource> target)
{
_source = source;
_predicates.Add(target);
}
public HasEnumerable<TSource> AddPredicate(Predicate<TSource> target)
{
_predicates.Add(target);
return this;
}
public IEnumerator<TSource> GetEnumerator()
{
var ip = 0;
foreach (var t in _source)
{
if (_predicates[ip](t))
{
ip++;
if (ip == _predicates.Count)
{
yield return default;
yield break;
}
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}
Note: I'd initially interpreted "following" as "immediately following", i.e. you wanted a pairwise condition. It looks like that isn't actually the case, but I'll leave this answer so that anyone finding the question actually looking for a pairwise condition is helped.
Assuming the collection can be safely iterated multiple times, you can use a combination of Zip/Skip to look at pairs. So for example:
var result = things.Where(t =>
t.ThingEvents
// This produces a sequence of 2-element tuples
.Zip(t.ThingEvents.Skip(1))
.Any(pair => pair.Item1.EventType == 1 && pair.Item2.EventType == 2));
I think that does what you want. It gets more fiddly if you need more than two elements, of course.
If you need this frequently, you probably could build Has
/FollowedBy
, but designing that would at least be non-trivial. (Ideally you'd do so in a way which only iterated the collection once.)
It seems that you are looking for Lag
, which is not implemented (at least in .Net 9) in the standard Linq yet.
We can do it manually as
public static class MyExtensions {
public static IEnumerable<(T Current, T? Prior)> Lag<T>(
this IEnumerable<T> source, T? skip = default) {
ArgumentNullException.ThrowIfNull(source);
var prior = skip;
foreach (var current in source) {
yield return (current, prior);
prior = current;
}
}
}
and then use it like this:
var result = things
.Where(t => t
.ThingEvents
.Lag()
.Any(pair => pair.Prior?.EventType == 1 && pair.Current.EventType == 2));
If we want standard Linq and just one pass (multiple iterations are not allowed), I vote for Aggregate
;
var result = things.Where(t => t.ThingEvents
.Aggregate(0, (state, item) => state = // state 0 - no 1 has found
state == 0 && item == 1 ? 1 // state 1 - has 1, but no 2
: state == 1 && item == 2 ? 2 // state 2 - 1 is followed by 2
: state) == 2);
Assuming gaps are allowed between matching items, you could use SkipWhile
to scan ahead to the next possible matching value and then test, skip, and move on to the next element:
public static class LinqExtensions {
public static bool HasSequentialElements<T>(this IEnumerable<T> source, params Predicate<T>[] predicates)
where T : IEquatable<T> {
try {
foreach(var predicate in predicates){
source = source.SkipWhile(item => !predicate(item));
if (!predicate(source.First())) return false;
source = source.Skip(1);
}
return true;
}
catch {
return false;
}
}
}
Then pass the appropriate predicates as arguments to HasSequentialElements
:
bool satisfied = t.ThingEvents.HasSequentialElements(
te => te.EventType == 1,
te => te.EventType == 2
);
本文标签: cIs it possible to use LINQ to query for elements occuring in a certain sequenceStack Overflow
版权声明:本文标题:c# - Is it possible to use LINQ to query for elements occuring in a certain sequence? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742356090a2459441.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
[1, 2, 1]
(extra1
),[1, 1, 2, 2, 2]
(two many2
),[1, 1, 1, 2, 2]
(too many1
) match? – Dmitrii Bychenko Commented Nov 20, 2024 at 12:52