【问题标题】:Efficiently sort an IList<T> without copying the source list在不复制源列表的情况下有效地对 IList<T> 进行排序
【发布时间】:2011-10-03 09:22:03
【问题描述】:

鉴于下面的测试用例,我该怎么做:

  1. 根据匹配的Id 的索引对IList&lt;TestObject&gt; 进行排序 在IList&lt;int&gt; 列表中。
  2. 不匹配的值被移到列表的末尾并按其原始索引排序。在这种情况下,由于索引列表中不存在 3 和 4,我们希望看到 list[3] == 3 和 list[4] == 4。
  3. 虽然我知道这可以通过 linq 实现,但我需要使用 原始 列表而不是创建新列表(由于列表的存储方式)。
  4. 源列表必须是IList(我不能使用List&lt;T&gt;)

这是测试:

    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));
    }

更新:

根据要求,这是我尝试过的,但 1) 它仅适用于 List&lt;T&gt; 和 2) 我不确定这是最有效的方法:

       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);
        });

更新 2:

感谢@leppie、@jamiec、@mitch wheat - 这是工作代码:

    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));
    }

【问题讨论】:

  • @Mitch,我已经更新了我的问题。我让它工作了,但只有List&lt;T&gt;
  • @Ben - 查看我对 IList 的就地排序的答案,以及可能更有效的比较方法......尽管我认为两者都不会特别慢!

标签: c# .net collections


【解决方案1】:

对此进行了一段时间的研究,确实如前所述,您将需要ArrayList.Adapter,但是您会注意到它需要一个非泛型 IList,因此需要进行一些转换:

ArrayList.Adapter((IList)list)

您还需要编写一个比较器,其中将包含进行排序的逻辑。对不起,但是:

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);
        }
    }
}

编辑:在上面的 comparerr 中添加了实现

那么用法如下:

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

顺便说一句,this thread 解释了一种将其转换为扩展方法的好方法,这将使您的代码更可重用且更易于阅读 IMO。

【讨论】:

  • +1 - 被 ArrayList.Adapter 的非通用要求所吸引,然后看到你的答案:)
  • 除了假设容器实现IList(不一定正确)之外,ArrayList.Adapter 的Sort 将列表中的项目复制到一个新数组中,对其进行排序,然后复制它们返回列表。当问题标题要求避免复制源列表时,不确定为什么会接受这一点。在每一种可行的情况下,只需自己复制到一个普通数组中同样有效,将保持所有内容的强类型化,从而避免大量无意义的装箱。
【解决方案2】:

您可以尝试以下方法:

ArrayList.Adapter(yourilist).Sort();

更新:

通用比较器:

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));
  }
}

用法:

ArrayList.Adapter(list).Sort<int>((x,y) => x - y);

【讨论】:

  • "ArrayList 类提供通用的 Reverse、BinarySearch 和 Sort 方法。此包装器可以作为在 IList 上使用这些方法的一种方式;但是,通过包装器执行这些通用操作可能比应用的操作效率低直接在 IList 上。”:msdn.microsoft.com/en-us/library/…
  • @leppie,无论如何要使用它并通过委托比较器?我可以创建一个IComparer,但我需要访问源列表才能读取当前索引。我已经用我所做的尝试更新了我的问题。
  • @Mitch Wheat:如果没有解释,说“可能比直接应用于 IList 的操作效率低”是没有用的。唯一效率较低的是额外的间接级别。
  • @Ben:创建一个以委托为参数的“通用”比较器类。
  • @leppie :这不是我的话:这就是 ArrayList.Adapter 的 MSDN 页面所说的!建议您提出连接问题!
【解决方案3】:

这是比较器的通用版本。 IEntity&lt;TId&gt; 只是一个简单的接口,具有TId 类型的属性“Id”:

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);
    }
}

工作测试:

[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));
    }
}

这符合我最初的问题中概述的要求。不匹配的元素被移到列表的末尾,然后按其原始索引排序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-25
    • 1970-01-01
    • 1970-01-01
    • 2020-09-13
    • 2013-10-10
    • 2021-03-14
    相关资源
    最近更新 更多