7

Given the test case below how can I:

  1. Sort the IList<TestObject> based on the index of a matching Id in the IList<int> list.
  2. Unmatched values are moved to the end of the list and sorted by their original index. In this case, since 3 and 4 do not exist in the index list, we expect to see list[3] == 3 and list[4] == 4.
  3. Whilst I know this can be achieved with linq, I need to resort the original list rather than creating a new one (due to how the list is stored).
  4. The source list must be an IList (I can't use List<T>)

Here's the test:

    public class TestObject
    {
        public int Id { get; set; }
    }

    [Test]
    public void Can_reorder_using_index_list()
    {
        IList<TestObject> list = new List<TestObject>
        {
            new TestObject { Id = 1 },
            new TestObject { Id = 2 },
            new TestObject { Id = 3 },
            new TestObject { Id = 4 },
            new TestObject { Id = 5 }
        };

        IList<int> indexList = new[] { 10, 5, 1, 9, 2 };

        // TODO sort

        Assert.That(list[0].Id, Is.EqualTo(5));
        Assert.That(list[1].Id, Is.EqualTo(1));
        Assert.That(list[2].Id, Is.EqualTo(2));
        Assert.That(list[3].Id, Is.EqualTo(3));
        Assert.That(list[4].Id, Is.EqualTo(4));
    }

Update:

As requested, this is what I did try, but 1) it only works with List<T> and 2) I'm not sure it's the most efficient way:

       var clone = list.ToList();
        list.Sort((x, y) =>
        {
            var xIndex = indexList.IndexOf(x.Id);
            var yIndex = indexList.IndexOf(y.Id);

            if (xIndex == -1)
            {
                xIndex = list.Count + clone.IndexOf(x);
            }
            if (yIndex == -1)
            {
                yIndex = list.Count + clone.IndexOf(y);
            }

            return xIndex.CompareTo(yIndex);
        });

Update 2:

Thanks to @leppie, @jamiec, @mitch wheat - this is the working code:

    public class TestObjectComparer : Comparer<TestObject>
    {
        private readonly IList<int> indexList;
        private readonly Func<TestObject, int> currentIndexFunc;
        private readonly int listCount;

        public TestObjectComparer(IList<int> indexList, Func<TestObject, int> currentIndexFunc, int listCount)
        {
            this.indexList = indexList;
            this.currentIndexFunc = currentIndexFunc;
            this.listCount = listCount;
        }

        public override int Compare(TestObject x, TestObject y)
        {
            var xIndex = indexList.IndexOf(x.Id);
            var yIndex = indexList.IndexOf(y.Id);

            if (xIndex == -1)
            {
                xIndex = listCount + currentIndexFunc(x);
            }
            if (yIndex == -1)
            {
                yIndex = listCount + currentIndexFunc(y);
            }

            return xIndex.CompareTo(yIndex);
        }
    }

    [Test]
    public void Can_reorder_using_index_list()
    {
        IList<TestObject> list = new List<TestObject>
        {
            new TestObject { Id = 1 },
            new TestObject { Id = 2 },
            new TestObject { Id = 3 },
            new TestObject { Id = 4 },
            new TestObject { Id = 5 }
        };

        IList<int> indexList = new[] { 10, 5, 1, 9, 2, 4 };

        ArrayList.Adapter((IList)list).Sort(new TestObjectComparer(indexList, x => list.IndexOf(x), list.Count));

        Assert.That(list[0].Id, Is.EqualTo(5));
        Assert.That(list[1].Id, Is.EqualTo(1));
        Assert.That(list[2].Id, Is.EqualTo(2));
        Assert.That(list[3].Id, Is.EqualTo(3));
        Assert.That(list[4].Id, Is.EqualTo(4));
    }
Ben Foster
  • 34,340
  • 40
  • 176
  • 285
  • @Mitch, I've updated my question. I got it working but only with `List` – Ben Foster Jul 12 '11 at 07:45
  • @Ben - see my answer for in-place sorting of your IList, and potentially more efficient comparison method...although neither will be particularly slow i dont think! – Jamiec Jul 12 '11 at 07:50

3 Answers3

2

You can try the following:

ArrayList.Adapter(yourilist).Sort();

Update:

A generic comparer:

class Comparer<T> : IComparer<T>, IComparer
{
  internal Func<T, T, int> pred;

  public int Compare(T x, T y)
  {
    return pred(x, y);  
  }

  public int Compare(object x, object y)
  {
    return Compare((T)x, (T)y);
  }
}

static class ComparerExtensions
{
  static IComparer Create<T>(Func<T, T, int> pred)
  {
    return new Comparer<T> { pred = pred };
  }

  public static void Sort<T>(this ArrayList l, Func<T, T, int> pred)
  {
    l.Sort(Create(pred));
  }
}

Usage:

ArrayList.Adapter(list).Sort<int>((x,y) => x - y);
leppie
  • 115,091
  • 17
  • 196
  • 297
  • "The ArrayList class provides generic Reverse, BinarySearch and Sort methods. This wrapper can be a means to use those methods on IList; however, performing these generic operations through the wrapper might be less efficient than operations applied directly on the IList.": http://msdn.microsoft.com/en-us/library/system.collections.arraylist.adapter.aspx – Mitch Wheat Jul 12 '11 at 07:37
  • @leppie, anyway to use this and pass a delegate comparer? I could create an `IComparer` but I need access to the source list in order to read the current index. I've updated my question just with what I did try. – Ben Foster Jul 12 '11 at 07:38
  • @Mitch Wheat: Saying 'might be less efficient than operations applied directly on the IList' is useless without an explanation. The only thing less efficient is an extra level of indirection. – leppie Jul 12 '11 at 07:40
  • @Ben: Create a 'generic' Comparer class taking a delegate as an argument. – leppie Jul 12 '11 at 07:40
  • 1
    @leppie : Those are not my words: That's what the MSDN page for ArrayList.Adapter states! Suggest you lodge a connect issue! – Mitch Wheat Jul 12 '11 at 07:52
  • @Ben - see the thread i linked in my answer - has the code for just the `ComparisonComparer` taking a delegate that @leppie describes. – Jamiec Jul 12 '11 at 07:53
  • @leppie, nice! think you've clinched it. – Ben Foster Jul 12 '11 at 08:26
1

Been looking at this for a bit, and indeed as previously said, your going to need ArrayList.Adapter, however you'll note it takes a non-generic IList so some casting will be required:

ArrayList.Adapter((IList)list)

You'll also need to write a comparer, of which the logic to do your sorting willl be contained. Excuse the name but:

public class WeirdComparer : IComparer,IComparer<TestObject>
{
    private IList<int> order;
    public WeirdComparer(IList<int> order)
    {
        this.order = order;
    }
    public int Compare(object x, object y)
    {
        return Compare((TestObject) x, (TestObject) y);
    }

    public int Compare(TestObject x, TestObject y)
    {
        if(order.Contains(x.Id))
        {
            if(order.Contains(y.Id))
            {
                return order.IndexOf(x.Id).CompareTo(order.IndexOf(y.Id));    
            }
            return -1;
        }
        else
        {
            if (order.Contains(y.Id))
            {
                return 1;
            }
            return x.Id.CompareTo(y.Id);
        }
    }
}

EDIT: Added implementation to above comparerr

Then the usage would be as follows:

IList<int> indexList = new[] { 10, 5, 1, 9, 2 };
ArrayList.Adapter((IList)list).Sort(new WeirdComparer(indexList));

By the way, this thread explains a nice way to turn this into an extension method which will make your code more reusable and easier to read IMO.

Community
  • 1
  • 1
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • +1 - Got caught out by the non generic requirement of ArrayList.Adapter then saw your answer :) – Ben Foster Jul 12 '11 at 07:58
  • 2
    Aside from the assumption that the container implements `IList` (not necessarily true), `ArrayList.Adapter`'s `Sort` copies the items in the list into a new array, sorts that, then copies them back to the list. Not sure why this was accepted when the question title asks to avoid copying the source list. In every scenario where this would work, just copying into a plain array yourself would equally work, would keep everything strongly typed, and would thereby avoid a whole lot of pointless boxing. –  Jun 10 '17 at 10:18
0

Here's a generic version of the comparer. IEntity<TId> is just a simple interface that has a property "Id" of type TId:

public class IndexComparer<T, TId> : Comparer<T> where T : IEntity<TId> where TId : IComparable
{
    private readonly IList<TId> order;
    private readonly int listCount;
    private readonly Func<T, int> currentIndexFunc;

    public IndexComparer(Func<T, int> currentIndexFunc, IList<TId> order, int listCount) {
        this.order = order;
        this.listCount = listCount;
        this.currentIndexFunc = currentIndexFunc;
    }

    public override int Compare(T x, T y)
    {
        var xIndex = order.IndexOf(x.Id);
        var yIndex = order.IndexOf(y.Id);

        if (xIndex == -1)
        {
            xIndex = listCount + currentIndexFunc(x);
        }
        if (yIndex == -1)
        {
            yIndex = listCount + currentIndexFunc(y);
        }

        return xIndex.CompareTo(yIndex);
    }
}

Working test:

[TestFixture]
public class OrderingTests
{
    public class TestObject : IEntity<int>
    {
        public int Id { get; set; }
    }

    [Test]
    public void Can_reorder_using_index_list()
    {
        IList<TestObject> list = new List<TestObject>
        {
            new TestObject { Id = 1 },
            new TestObject { Id = 2 },
            new TestObject { Id = 3 },
            new TestObject { Id = 4 },
            new TestObject { Id = 5 }
        };

        IList<int> indexList = new[] { 10, 5, 1, 9, 2, 4 };
        ArrayList.Adapter((IList)list)
            .Sort(new IndexComparer<TestObject, int>(x => list.IndexOf(x), indexList, list.Count));

        Assert.That(list[0].Id, Is.EqualTo(5));
        Assert.That(list[1].Id, Is.EqualTo(1));
        Assert.That(list[2].Id, Is.EqualTo(2));
        Assert.That(list[3].Id, Is.EqualTo(4));
        Assert.That(list[4].Id, Is.EqualTo(3));
    }
}

This meets the requirements as outlined in my original question. Unmatched elements are moved to the end of the list and then ordered by their original index.

Ben Foster
  • 34,340
  • 40
  • 176
  • 285