开发者

linq way to insert element in order

i have a collection of elements sorted by the elements' Name property. i need to insert a new element into the collection while maintaining the order. i am looking for a concise LINQ way to do this. my code is below. "this.Children" is the collection, and "d" is the new element that i need to insert. it takes two passes over the collection to find the insertion point. is there a way to get the index from the First() extension method? (please do not suggest using foreach, i know that :), i am learning LINQ).

than开发者_StackOverflow社区ks! konstantin


var v = this.Children.FirstOrDefault(x => string.Compare(x.Name, d.Name) > 0);
int index = this.Children.IndexOf(v);

if (index < 0)
{
    this.children.Add(d);
}
else
{
    this.Children.Insert(index, d);
}


Yes, using the overload of Select which includes the index as well as the value:

var pair = this.Children
               .Select((value, index) => new { value, index })
               .FirstOrDefault(x => string.Compare(x.value.Name, d.Name) > 0);

if (pair == null)
{
    Children.Add(d);
}
else
{
    Children.Insert(pair.index, d);
}

Note that this is still inefficient though - if you already know the values are sorted, you can use a binary chop to find out the insertion index. It's hard to give sample code for that without knowing the type of Children though... there's already List<T>.BinarySearch and Array.BinarySearch.

Learning LINQ is admirable - but it's also important to learn when using LINQ isn't the best way to go :)


Assuming that this.Children is a List<T>, you can use List<T>.BinarySearch with a custom comparer to efficiently find the position to insert the new element at:

IComparer<Foo> comparer = AnonymousComparer.Create<Foo>(
    (x, y) => string.Compare(x.Name, y.Name));

int index = this.Children.BinarySearch(d, comparer);
if (index < 0) index = ~index;
this.Children.Insert(index, d);

with

static class AnonymousComparer
{
    public static IComparer<T> Create<T>(Func<T, T, int> comparer)
    {
        if (comparer == null) { throw new ArgumentNullException("comparer"); }
        return new TheComparer<T>(comparer);
    }
    private class TheComparer<T> : IComparer<T>
    {
        private readonly Func<T, T, int> c;
        public TheComparer(Func<T, T, int> c) { this.c = c; }
        int IComparer<T>.Compare(T x, T y) { return this.c(x, y); }
    }
}


I created my own Extension method for adding a new item in the correct order:

public static class ListExtension
{
    public static void InsertOrderedBy<TSource, TKey>(this IList<TSource> source, TSource item, Func<TSource, TKey> keySelector) where TKey : IComparable<TKey>
    {
        var i = source.Select((Value, Index) => new { Value, Index }).FirstOrDefault(x => keySelector(x.Value).CompareTo(keySelector(item)) > 0);

        if (i == null)
        {
            source.Add(item);
        }
        else
        {
            source.Insert(i.Index, item);
        }
    }
}

I use it like this:

List<Item> ItemList = new List<Item>();
ItemList.InsertOrderedBy(item, x => x.Duration);

It's almost the same like the answer from Jon Skeet, but I can pass the sort argument as second parameter, e.g. a Duration (type TimeSpan).


Well, you could always just use OrderBy after adding the new element...

var v = this.Children.Union(new List<TypeOfChildren>() { d }).OrderBy<TypeOfChildren, string>(x => x.Name).ToList<TypeOfChildren>();
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜