Вопрос

I was wondering if it is possible to write an expression for a Linq extension (or a custom extension) to filter a collection using a lambda expression that compares two elements of the collection.

In other words, if I have a List<DateTime> and some value, var v = DateTime.Today, then I am wondering if it is possible to write create a method that will return the first element of the collection that is less than or equal to the value, current <= v, with the next element of the collection being greater than or equal to the value, next >= v.

Please note that the above is just an example, and may or may not be the final implementation.


The following would be a valid solution, were the .First() method to accept Func<DateTime, DateTime, bool> with the two DateTime parameters being consecutive elements of the sequence:

dateCollection.First((current, next) => current <= v && next >= v);

Please also note that with the example given, a valid workaround could be to use .OrderBy and then find the first index that is greater than d and subtract 1. However, this type of comparison is not the only one that I am looking for. I may have a situation in which I am checking a List<string> for the first situation where the current element starts with the first letter of my value, v, and the next element ends with the last letter of my value, v.


I am looking for something that would be just a few of code. My goal is to find the simplest solution to this possible, so brevity carries a lot of weight.

What I am looking for is something of the form:

public static T First (...)
{
    ...
}

I believe that this will also require two or more lambda expressions as parameters. One thing that may also provide a good solution is to be able to select into all possible, consecutive pairs of elements of the sequence, and call the .First() method on that.

For example:

//value 
var v = 5;

//if my collection is the following
List<int> stuff = { a, b, c, d };

//select into consecutive pairs, giving: 
var pairs = ... // { { a, b }, { b, c }, { c, d } };

//then run comparison
pairs.First(p => p[0] <= v && p[1] >= v).Select(p => p[0]);


Thanks and happy coding! :)

Это было полезно?

Решение

What we can create is a Pairwise method that can map a sequence of values into a sequence of pairs representing each value and the value that comes before it.

public static IEnumerable<Tuple<T, T>> Pairwise<T>(this IEnumerable<T> source)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            yield break;

        T prev = iterator.Current;

        while (iterator.MoveNext())
        {
            yield return Tuple.Create(prev, iterator.Current);
            prev = iterator.Current;
        }
    }
}

Now we can write out:

var item = data.Pairwise()
    .First(pair => pair.Item1 <= v && pair.Item2 >= v)
    .Item1;

If this is something you're going to use a fair bit, it may be worth creating a new custom type to replace Tuple, so that you can have Current and Next properties, instead of Item1 and Item2.

Другие советы

    List<int> list = new List<int>();
    list.Add(3);
    list.Add(2);
    list.Add(8);
    list.Add(1);
    list.Add(4);

    var element = list
         .Where((elem, idx) => idx < list.Count-1 && elem<=list[idx+1])
         .FirstOrDefault();

    Console.WriteLine(element);

rsult: 2

where 'elem' is the current element, and 'idx' is an index of the current element

Not really sure what you want to return here is my main question, but to take your example and put it into a LINQ statement, it would be like this:

DateTime? Firstmatch = dateCollection.DefaultIfEmpty(null).FirstOrDefault(a => a <= d && ((dateCollection.IndexOf(a) + 1) < (dateCollection.Count) && dateCollection[dateCollection.IndexOf(a) + 1] >= d));

Strictly following the description, you could combine linq and list indexes to find the first index that matches your criterium, and returning its element:

DateTime d= DateTime.Today;
var res = dateCollection[Enumerable.Range(0, dateCollection.Count - 1).First(i => dateCollection[i] <= d && dateCollection[i + 1] >= d)];

Servy's answer can be handled without an extension method:

var first = items
  .Select((current, index) => index > 0 ? new { Prev = items.ElementAt(index-1), Current = current } : null)
  .First(pair => pair != null && pair.Prev <= v && pair.Current >= v)
  .Prev;      
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top