【问题标题】:C# Given a desired order need a space efficient list reordering or sortingC# 给定所需的顺序需要一个节省空间的列表重新排序或排序
【发布时间】:2010-12-29 08:35:08
【问题描述】:

我的目标是:给定条目列表和所需的顺序,根据此顺序重新排列条目列表。列表会很大,因此空间效率很重要。

例如:

List<Entry> data = ReadDataFromSomeWhere(); // data => [a, b, c];
List<int> ordering = RandomPermutation(data.Count); // ordering => [2, 1, 3];
data.ReOrderBy(ordering);  // data => [b, a, c];

我可能弄错了,但似乎最直接且节省空间的解决方案是按 orderingdata 进行排序/排序.或更笼统地说:

给定两个列表:A,B 有没有办法按 B 对 A 进行排序?该功能将与以下内容基本相同: Array.Sort&lt;(Of &lt;(TKey, TValue&gt;)&gt;)(array&lt;TKey&gt;[]()[], array&lt;TValue&gt;[]()[])

想到的一种方法是创建一个由 A 和 B 组成的新数据类型,即。配对,然后按 B 值排序:

List<T> A;
List<T> B;
Assert(A.Count == B.Count);
var C = A.Select( (a,idx) => new Pair<T,T>(B[idx],a)).OrderBy(c => c.First);
A = C.Select(x => x.Second).ToList();

但是,我希望这尽可能节省空间(我猜 select 和 tolist() 调用都很昂贵),因此需要在很大程度上进行就地排序。为此,有没有办法为 A.Sort() 编写一个比较器,它引用 B?

【问题讨论】:

  • Reflector, profiler 是你的朋友。

标签: c# algorithm list sorting


【解决方案1】:

有两种解释顺序数组的方法,一种是列出每个元素的源索引,另一种是列出每个元素的目标索引。我不确定你指的是哪一个。

根据目的地列表重新排序列表非常简单:

// Sets list[destination[i]] = list[i] for all i.  Clobbers destination list.
void ReorderByDestination(List<T> list, List<int> destination) {
  for (int i = 0; i < list.Count; i++) {
    while (destination[i] != i) {
      int d = destination[i];
      T t = list[d];            // save element in destination slot
      int t_d = destination[d]; // and its own destination
      list[d] = list[i];        // move element to destination
      destination[d] = d;       // and mark it as moved
      list[i] = t;              // store saved element in slot i
      destination[i] = t_d;     // ... and its destination
    }
  }
}

给定一个源列表(我认为你想要的)重新排序一个列表有点困难,你只需要先反转排列。

// Sets list[i] = list[source[i]] for all i.  Clobbers source list.
void ReorderBySource(List<T> list, List<int> source) {
  InvertPermutation(source);
  ReorderByDestination(list, source);
}

有已知的就地置换反转例程,我发现的第一个是SUBSET中的perm_inv。

通常,您不需要反转排列,而是修改生成源列表的任何内容以生成目标列表。

【讨论】:

    【解决方案2】:

    最有效的重新排序算法可能是某种形式的惰性求值。您可以创建一个 IList 实现,它使用数据列表和排序列表从“虚拟重新排序”的序列中动态返回值,而不是实际重新计算序列。

    惰性模型的好处是执行双重查找在性能方面可以忽略不计,并且不需要重新排序整个列表即可开始返回值。它还可以潜在地允许以多个顺序呈现相同的值列表,而无需复制基础列表。当然,如果您想确保对原始列表(或排序)的更改不会影响动态排序的列表,您可以更改实现以复制列表。

    这是一个代码示例(未经测试)。

    注意:为了简单起见,我已经从您的示例中稍微更改了实现,以使用基于 0 的索引(而不是基于 1)。但如有必要,将代码转换为使用基于 1 的索引会很容易。

    public DynamicallyOrderedList<T> : IList<T>
    {
       private readonly IList<T> m_Values;
       private readonly IList<int> m_Order;
    
       public DynamicallyOrderedList( IList<T> valueList, IList<int> ordering )
       {
           if( valueList == null || ordering == null ) 
               throw new ArgumentNullException();
           if( valueList.Count != ordering.Count )
               throw new InvalidArgumentException("Lists are not of same size.");
           // assumes ordering list has distinct values ranging from 0 to Count-1
    
           m_Values = valueList;
           m_Order  = ordering;           
       }
    
       // IList<T> Implementation
    
       // for simplicity, don't allow addition, removal or clearing of items
       // these could, however be implemented to add items to the end of the list 
       // and remove them by collapsing the ordering list. 
       // Left as an exercise for the reader :-)
       public void Add( T item ) { throw new NotSupportedException(); }
    
       public void Insert(int index, T item) { throw new NotSupportedException(); }
    
       public void Clear() { throw new NotSupportedException(); }
    
       public void Remove( T item ) { throw new NotSupportedException(); }
    
       public void RemoveAt( int index ) { throw new NotSupportedException(); }
    
       public T this[int index]
       {
           get 
           {
               if( index > m_Values.Count ) 
                   throw new ArgumentOutOfRangeException("index");
               return m_Values[m_Order[index]];
           }
           set
           {
               if( index > m_Values.Count ) 
                   throw new ArgumentOutOfRangeException("index");
               m_Values[m_Order[index]] = value;
           }
        }
    
       public int Count { get { return m_Values.Count; } }
    
       public bool Contains( T item ) { return m_Values.Contains( item ); }
    
       public bool IndexOf( T item ) { return m_Order[m_Values.IndexOf( item )]; }
    
       // Enumerator that returns items in the order defined by m_Order
       public IEnumerator<T> GetEnumerator()
       {
           // use generator syntax to simplify enumerator implementation
           foreach( var index in m_Order )
               yield return m_Values[index];
       }
    
       public void CopyTo( T[] array, int arrayIndex )
       {
            foreach( var item in this )
                array[arrayIndex++] = item;
       }
    

    }

    你的ReorderBy()函数就变成了:

    public static class ReorderByExt
    {
        public static IList<T> ReorderBy<T>( this IList<T> list, IList<int> order )
        {
            return new DynamicallyOrderedList( list, order );
        }
    }
    

    如果您随后需要将动态排序的列表具体化为常规列表,您可以使用以下方法(它将在 O(n) 时间内运行并且不会创建不必要的数据副本):

    var staticallyOrderedList = originalList.ReorderBy( ordering ).ToList();
    

    【讨论】:

    • 没有。 OrderBy 将在 IOrderedEnumerable 第一次迭代后立即创建一个临时存储排序。
    • @Jason:我打错了,我的意思是在最后一个代码 sn-p 中使用 ReorderBy( ordering ) 而不是 OrderBy( ordering ),这甚至无法编译。
    • @LBushkin:正如我的旁注。我喜欢你博客上的两篇文章。我也是一个非常喜欢拼图的人。您应该认真考虑发布更多内容。 :-)
    • @Jason:不知道为什么 SO 不允许你投票。至于我的博客,很快就会有更多的东西,我最近工作很忙,所以必须给一些东西。
    • @LBushkin:它只是说“投票太旧,无法更改,除非编辑此答案。”但我对你的帖子没有投票权。重新工作:我知道这种感觉。
    猜你喜欢
    • 2021-12-26
    • 1970-01-01
    • 2021-08-23
    • 1970-01-01
    • 2013-05-10
    • 2012-07-23
    • 2021-10-10
    • 2016-12-17
    • 1970-01-01
    相关资源
    最近更新 更多