基于项目权重合并多个列表的有效方法

时间:2014-02-25 13:22:26

标签: c# linq chess

我正在写一个国际象棋引擎,我需要一种有效的方法将多个列表合并到一个由最小值排序的单个列表中。

每个列表都是典型的一组棋子,这些棋子已经以完美的攻击顺序排序。例如,我在a2上有一个白色女王,在b3上有白色主教,在f1上有白色车,在f2上有白色车。现在说我在f7上有一个黑色棋子然后所有四个白色棋子从两个不同的离散方向聚集在f7广场上 - 东北(女王和主教)和北(鲁克斯)。

这两组将按如下方式订购:

A组第1名 - 主教(b3);第二名 - 女王(a2)
B组第1组 - 鲁克(f2);第二名 - 鲁克(f1)

现在使用下面的积分系统,我希望两个列表按以下顺序合并为一个列表(最低值到最高值):Bishop(b3),Rook(f2),Rook(f1),最后是Queen (A2)。

女王= 900分 鲁克= 500分 主教= 375分 骑士= 350分 典当= 100分

一些代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SO_22015528
{
  public enum eRayDirection
  {
    North,
    NorthEast,
    East,
    SouthEast,
    South,
    SouthWest,
    West,
    NorthWest
  }

  public enum ePieceType
  {
    Empty = 0,
    Pawn = 1,
    Knight = 2,
    Bishop = 3,
    Rook = 4,
    Queen = 5,
    King = 6
  }

  public struct BoardPosition : IComparable<BoardPosition>
  {
    public int File;
    public int Rank;
    public int Square;

    public BoardPosition( int square )
    {
      Square = square;
      File = square % 7;
      Rank = 8 - ( square >> 3 );
    }

    public static Boolean operator >( BoardPosition b1, BoardPosition b2 )
    {
      return ( b1.Rank > b2.Rank ) || ( b1.Rank == b2.Rank && b1.File < b2.File );
    }

    public static Boolean operator <( BoardPosition b1, BoardPosition b2 )
    {
      return ( b1.Rank < b2.Rank ) || ( b1.Rank == b2.Rank && b1.File > b2.File );
    }

    public int CompareTo( BoardPosition obj )
    {
      if ( this < obj ) return 1;
      else if ( this > obj ) return -1;
      else return 0;
    }
  }

  public class ChessPiece
  {
    public int Value { get; set; }
    public ePieceType Type { get; set; }
    public int Square { get; set; }
    public BoardPosition XY
    {
      get
      {
        return new BoardPosition( this.Square );
      }
    }
    public ChessPiece( ePieceType type, int value, int square )
    {
      Value = value;
      Type = type;
      Square = square;
    }
  }

  public class Constraint
  {
    public ChessPiece Piece { get; set; }
    public eRayDirection Ray { get; set; }
    public Constraint( ChessPiece piece, eRayDirection ray )
    {
      Piece = piece;
      Ray = ray;
    }
    public override string ToString()
    {
      return String.Format( "{0} {1} {2}", Piece.Square, Piece.Type, Ray );
    }
  }

}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SO_22015528
{
  class Program
  {
    static void Main( string[] args )
    {
      // test code
      ChessPiece a2 = new ChessPiece( ePieceType.Queen, 900, 48 );
      ChessPiece b3 = new ChessPiece( ePieceType.Bishop, 375, 41 );
      ChessPiece f1 = new ChessPiece( ePieceType.Rook, 500, 61 );
      ChessPiece f2 = new ChessPiece( ePieceType.Rook, 500, 53 );

      // This just simulates pieces that attack on the f7 square.
      List<Constraint> f7 = new List<Constraint>();
      f7.Add( new Constraint( b3, eRayDirection.NorthEast ) );
      f7.Add( new Constraint( a2, eRayDirection.NorthEast ) );
      f7.Add( new Constraint( f1, eRayDirection.North ) );
      f7.Add( new Constraint( f2, eRayDirection.North ) );

      // Get all positive ray directions ( use to simplify LINQ orderby )
      List<eRayDirection> positiveRayDirections = new List<eRayDirection>();
      positiveRayDirections.Add( eRayDirection.North );
      positiveRayDirections.Add( eRayDirection.NorthEast );
      positiveRayDirections.Add( eRayDirection.NorthWest );
      positiveRayDirections.Add( eRayDirection.West );

      var groups = f7
        .GroupBy( g => g.Ray )
        .Select( a =>
        new
        {
          Key = a.Key,
          Results = positiveRayDirections.Contains( a.Key ) ? a.OrderBy( x => x.Piece.XY ).ToList() : a.OrderByDescending( x => x.Piece.XY ).ToList()
        } ).ToList();

              // The groups object returns two discrete groups here; 
              // NorthEast containing 2 entries (Bishop & Queen) and North 
              // also containing to entries (Rook x 2).
      List<Constraint> attackOrder = new List<Constraint>();

      List<Int32> groupIndicies = new List<Int32>( groups.Count() );
      for ( int n = 0; n < groups.Count(); n++ )
        groupIndicies.Add( 0 );

      while ( true )
      {
        Int32 value = Int32.MaxValue;
        Int32 groupIndex = -1;

        for ( int n = 0; n < groups.Count(); n++ )
        {
          var g = groups[ n ];
          int gIndex = groupIndicies[ n ];

          if ( gIndex < g.Results.Count && g.Results[ gIndex ].Piece.Value < value )
          {
            value = g.Results[ gIndex ].Piece.Value;
            groupIndex = n;
          }
        }

        if ( groupIndex < 0 )
          break;

        attackOrder.Add( groups[ groupIndex ].Results[ groupIndicies[ groupIndex ] ] );

        groupIndicies[ groupIndex ]++;

      }

              foreach ( var ao in attackOrder )
                  Console.WriteLine( ao.ToString() );

      Console.ReadKey();
    }
  }
}

我不认为最后一点是非常有效的,如果有人能找到一种更简单的方法,我会很感激。

4 个答案:

答案 0 :(得分:1)

使用快速排序单独订购每个列表,然后按插入排序到最终列表中。

Order the lists individually.
Create the empty Final list.

do
{
   consider the first item in each of the sorted lists and find the highest ranking candidate.
   remove the item from its sorted list and add it to the final list.
} 
until all of the sorted lists are empty.

答案 1 :(得分:1)

简单明了。只需调用SelectMany()上的groups,然后根据OrderByDescending()应用Constraint.Piece.Value。请参阅以下代码:

List<Constraint> attackOrder = groups.SelectMany(x => x.Results).OrderByDescending(x => x.Piece.Value).ToList();

所以上面的代码片段的更新版本如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SO_22015528
{
  class Program
  {
    static void Main( string[] args )
    {
      // test code
      ChessPiece a2 = new ChessPiece( ePieceType.Queen, 900, 48 );
      ChessPiece b3 = new ChessPiece( ePieceType.Bishop, 375, 41 );
      ChessPiece f1 = new ChessPiece( ePieceType.Rook, 500, 61 );
      ChessPiece f2 = new ChessPiece( ePieceType.Rook, 500, 53 );

      // This just simulates pieces that attack on the f7 square.
      List<Constraint> f7 = new List<Constraint>();
      f7.Add( new Constraint( b3, eRayDirection.NorthEast ) );
      f7.Add( new Constraint( a2, eRayDirection.NorthEast ) );
      f7.Add( new Constraint( f1, eRayDirection.North ) );
      f7.Add( new Constraint( f2, eRayDirection.North ) );

      // Get all positive ray directions ( use to simplify LINQ orderby )
      List<eRayDirection> positiveRayDirections = new List<eRayDirection>();
      positiveRayDirections.Add( eRayDirection.North );
      positiveRayDirections.Add( eRayDirection.NorthEast );
      positiveRayDirections.Add( eRayDirection.NorthWest );
      positiveRayDirections.Add( eRayDirection.West );

      var groups = f7
        .GroupBy( g => g.Ray )
        .Select( a =>
        new
        {
          Key = a.Key,
          Results = positiveRayDirections.Contains( a.Key ) ? a.OrderBy( x => x.Piece.XY ).ToList() : a.OrderByDescending( x => x.Piece.XY ).ToList()
        } ).ToList();

              // The groups object returns two discrete groups here; 
              // NorthEast containing 2 entries (Bishop & Queen) and North 
              // also containing to entries (Rook x 2).
      List<Constraint> attackOrder = groups.SelectMany(x => x.Results).OrderByDescending(x => x.Piece.Value).ToList();

      foreach ( var ao in attackOrder )
          Console.WriteLine( ao.ToString() );

      Console.ReadKey();
    }
  }
}

答案 2 :(得分:1)

考虑一个人如何实现这一点:他会查看所有传入的列表,然后选择最好的列表。然后他会冲洗并重复,直到所有传入的清单都耗尽。

/// <summary>
/// Merges the incoming sorted streams of items into a single sorted stream of items, using the provided comparison function
/// </summary>
public static IEnumerable<T> MergeMany<T>(Comparison<T> comparison, params IEnumerable<T>[] collections)
{
  var liveEnumerators = new PriorityQueue<IEnumerator<T>>((e1, e2) => comparison(e1.Current, e2.Current));

  // start each enumerator and add them to the queue, sorting by the current values.
  // Discard any enumerator that has no item
  foreach (var coll in collections)
  {
    var enumerator = coll.GetEnumerator();
    if (enumerator.MoveNext())
      liveEnumerators.Push(enumerator);
  }

  while (liveEnumerators.Any())
  {
    // pull an enumerator off the queue and yield its current item
    var enumerator = liveEnumerators.Pop();
    yield return enumerator.Current;

    // if it has more items, throw it back on the queue, sorting using its new current item.
    if (enumerator.MoveNext())
      liveEnumerators.Push(enumerator);
  }
}

此代码保留枚举器列表,按每个枚举器标识的当前项排序。要使用它,您将提供列表列表,以及描述哪个项目大于哪个项目的函数。所以,如果你开始使用两个集合,一个用于NorthEast包含[bishop,queen],另一个用于North包含[rook,rook],那么当你Merge这两个列表时,你会出现[bishop,rook,rook] ,女王]。请注意,此代码遵循传入列表的原始顺序。

另请注意,您需要找到自己的PriorityQueue类,它可以使用和使用比较函数。

答案 3 :(得分:0)

在发现LINQ对我的需求太慢之后,我决定坚持使用原始代码。用循环和迭代器替换所有LINQ代码后,我设法将引擎性能提高了12倍。