Efficiently sort an IList<T> without copying the source list
Given the test case below how can I:
IList<TestObject>
based on the index of a matching Id
in the IList<int>
list. list[3] == 3
and list[4] == 4
. 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));
}
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.
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);
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.
链接地址: http://www.djcxy.com/p/53610.html上一篇: 使用笔尖创建自定义视图并在窗口中使用它