【问题标题】:Is there any obvious reason why my loop is not working as it should be?是否有任何明显的原因导致我的循环无法正常工作?
【发布时间】:2021-02-02 18:55:18
【问题描述】:

我有一个网格,其 (x,y) 坐标对应于 SQL Server。 每个坐标没有一行;只有那些不是空白的。有一列(“填充”)存储这些颜色代码整数。

当应用程序加载时,我在下面有一个序列循环遍历网格高度/宽度内的每个 (X,Y) 坐标。在这个循环中,它询问所讨论的 (X,Y) 是否与行中的匹配,如果返回 TRUE,则根据保存在行中的整数更新 Fill 整数。如果这返回 FALSE,我将行增加一并再次查看,直到它查看了保存在表中的每一行(名为 SQL.RecordCount)。完成此操作后,我增加 X(向右移动到下一个单元格)并再次执行此过程;一旦我到达行的末尾(其中 X = MapWidth),我就会转到下一行的开头并再次前进。如此重复直到 Y = MapHeight,如下面的代码所示。

对我来说,这个顺序是有道理的,所以肯定有一些我不知道的东西丢失了。即使您没有确切的解决方案,也非常感谢任何能够推动这个僵局的东西。

基于 cmets 的编辑在双星号中:

Public Sub LoadMap()

        Dim Fill As Integer = 0
        Dim Row As Integer = 0
        Dim X As Integer = 0
        Dim Y As Integer = 0
              

        SQL.ExecQuery("SELECT * FROM Mapper_Table")

        Do While Y <= MapHeight
            'Ultimate exit statement: stop loading cells when Y becomes greater than the MapHeight...
            Do While X <= MapWidth
                'Row Exit Statement: When X reaches 100, its time to start on the next row...
                Do While Row < SQL.RecordCount '12
                    'I only want to search as many rows as there are saved locations, otherwise the cell must be empty and therefore blank...
                    If X = SQL.DBDT.Rows(Row).Item("X") And Y = SQL.DBDT.Rows(Row).Item("Y") Then
                        'If the current (X,Y) is a match to the database row, then we take the stored colour...
                        Fill = SQL.DBDT.Rows(Row).Item("Fill")
                    End If
                    'If Fill is taken, we can colour the square; otherwise, we should look in the next row...
                     If Fill <> 0 Then
                    Map(X, Y, 0) = Fill
                    End If
          **  Row = Row + 1 **
                    Loop
                'Once we have looked in all the rows for out cell, it is either coloured in or not; we can move onto the next cell in the row; a.k.a...
                X = X + 1
          **  Fill = 0  **
            Loop
            'Once we have looked for all the cells in our row, we can move onto the next one; a.k.a...
            X = 0
            Y = Y + 1
        Loop
        'Once we get to the end of the map, it should be loaded!

    End Sub

【问题讨论】:

  • 好的,谢谢。我是在做你说的吗?先查询所有数据,然后让序列运行它?
  • 这个SQL 对象是什么?它是如何工作的?为什么不直接使用 ADO.NET(SqlConnectionSqlCommandSqlDataReader?)
  • 所以 SQL 引用了我的 SQL 控件类。我会在上面更新...
  • 为什么要为网格上的每个 X 和 Y 坐标探测结果集?为什么不只遍历结果集并使用它包含的 X、Y、Fill 值?
  • 首先 - 循环有什么问题?它永远消失了吗?我不是 100%,但如果 FILL 有一个值,那么 Do While Row &lt; SQL.RecordCount 循环将永远存在?

标签: sql-server vb.net visual-studio


【解决方案1】:

这就是我的做法,尽管在 C# 中(将其转换为 VB.NET 应该很简单——我真的讨厌 VB.NET 的数组语法)。

  • 下面的代码分为两部分:

    • 第一部分 (LoadMapperTableAsync)涉及将数据从您的 Mapper_table 加载到程序内存中(放入 List&lt;T&gt;,它使用 ValueTuples 来表示您的表数据)。
    • 第二部分 (ConvertTo2DArray) 将加载的数据转换为二维颜色数组。
  • 它通过 2 个连续循环来执行此操作(O(n) 运行时复杂度):

    • 第一个循环位于 LoadMapperTableAsync 中,因为它遍历来自 SqlDataReader 的行。
    • 第二个循环在ConvertTo2DArray 中,只关心填充数组。
      • 在 .NET 中创建新数组时,CLR 将使用预置零内存,因此所有 T[] 数组元素自动具有值 default(T)。在这种情况下,FillColor 的默认值是 None,我认为这是您想要的。
      • 请记住,按索引设置数组元素是O(1) 操作。
  • 这两个循环可以结合起来,但我强烈建议不要这样做,因为这样你就将 dumb 和简单的数据加载代码与应用程序逻辑(又名业务-逻辑或域逻辑)这通常是一件坏事,因为它使程序维护变得更加困难。我很欣赏你是新手,但没有硬性规定,但我会说,随着经验的增加,你会“感觉”到什么行为属于程序的哪些部分以及哪些部分应与其他部分分开存放。

public enum FillColor {
    None = 0, // It's important that zero represents "empty" or "not set" due to .NET's rules on default values for value-types like enums.
    Red = 1,
    Blue = 2,
    // etc
}

public static async Task< List<( Int32 x, Int32 y, FillColor fill )> > LoadMapperTableAsync()
{
    const String connectionString = "...";

    using( SqlConnection c = new SqlConnection( connectionString ) )
    using( SqlCommand cmd = c.CreateCommand() )
    {
        await c.OpenAsync().ConfigureAwait(false);

        cmd.CommandText = "SELECT x, y, fill FROM Mapper_Table"; // NEVER EVER USE `SELECT * FROM` in application code!!!!!! Always explicitly name your columns!
        
        using( SqlDataReader rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false) )
        {
            List<( Int32 x, Int32 y, FillColor fill )> list = new List<( Int32 x, Int32 y, FillColor fill )>();
            
            while( await rdr.ReadAsync().ConfigureAwait(false) )
            {
                Int32 x    = rdr.GetInt32( 0 ); // Add `rdr.IsDBNull()` guards, if necessary!
                Int32 y    = rdr.GetInt32( 1 );
                Int32 fill = rdr.GetInt32( 2 );

                list.Add( ( x, y, (FillColor)fill ) );
            }

            return list;
        }
    }
}

public static FillColor[,] ConvertTo2DArray( IEnumerable< ( Int32 x, Int32 y, FillColor fill ) > values )
{
    const Int32 MAP_WIDTH  = 100;
    const Int32 MAP_HEIGHT = 100;

    FillColor[,] map = new FillColor[ MAP_HEIGHT, MAP_WIDTH ];

    // Do note that 2D arrays in .NET are slooooow: https://stackoverflow.com/questions/468832/why-are-multi-dimensional-arrays-in-net-slower-than-normal-arrays

    foreach( ( Int32 x, Int32 y, FillColor fill ) in values )
    {
        // Skip-over invalid data:
        if( x < 0 || x > MAP_WIDTH  ) continue;
        if( y < 0 || y > MAP_HEIGHT ) continue;

        map[ y, x ] = fill;
    }

    return map;
}

public static async Task<FillColor[,]> LoadAsync()
{
    List<( Int32 x, Int32 y, FillColor fill )> list = await LoadMapperTableAsync().ConfigureAwait(false);

    return ConvertTo2DArray( list );
}

【讨论】:

  • 感谢您抽出宝贵时间输入此内容@Dai,我会处理并回复您!
  • 为什么在这里.ConfigureAwait(false)?它看起来像一个 WinForms 项目。
  • @Jimi 正确。我特别添加了它 因为 它是 WinForms:这是因为 LoadMapperTableAsync 有一个 tight loop 在其中(特别是 while( await rdr.ReadAsync ) 循环)你真的不想继续在 UI 线程上恢复:您希望它在 第一个 可用线程池线程上恢复,因此 ConfigureAwait(false)。方法内部没有使用 UI 控件或对象,因此 ConfigureAwait(false) 将产生最佳性能。
  • 可靠。 OP 如何调用LoadAsync()ConfigureAwait(false) 在这里可能很有用,因为 ConvertTo2DArray(list);:如果这是在线程池线程中运行的,你就不需要它(我实际上并没有提出不同的方法,这是 OP 需要考虑/测试的东西)。当数据准备好时,绘制该网格可能需要 20-30 毫秒。
【解决方案2】:

Public Sub LoadMap()

    Dim Fill As Integer = 0
    Dim Row As Integer = 0
    Dim X As Integer = 0
    Dim Y As Integer = 0

    'Experiment: Sets X and Y to CurrentLocation with Record that has Fill. Works
    'Experiment: If Row(0) is not correct, try other rows. 
    'Experiment: Loop the loop for all X and Y's


    SQL.ExecQuery("SELECT X,Y,Fill FROM Mapper_Table")

    Do While Y <= MapHeight
        'Ultimate exit statement: stop loading cells when Y becomes greater than the MapHeight...
        Do While X <= MapWidth
            'Row Exit Statement: When X reaches 100, its time to start on the next row...
            Do While Row < SQL.RecordCount
                'I only want to search as many rows as there are saved locations, otherwise the cell must be empty and therefore blank...
                If X = SQL.DBDT.Rows(Row).Item("X") And Y = SQL.DBDT.Rows(Row).Item("Y") Then
                    'If the current (X,Y) is a match to the database row, then we take the stored colour...
                    Fill = SQL.DBDT.Rows(Row).Item("Fill")
                    'And paint the cell...
                    Map(X, Y, 0) = Fill
                    Row = SQL.RecordCount
                    'Otherwise, we look at the next row
                Else Row = Row + 1
                End If
            Loop
            'Once we have looked in all the rows for out cell, it is either coloured in or not; we can move onto the next cell in the row; a.k.a...
            X = X + 1
            Row = 0
            Fill = 0
        Loop
        'Once we have looked for all the cells in our row, we can move onto the next one; a.k.a...
        X = 0
        Y = Y + 1
    Loop
    'Once we get to the end of the map, it should be loaded!

End Sub

【讨论】:

    猜你喜欢
    • 2023-04-05
    • 1970-01-01
    • 2017-04-29
    • 1970-01-01
    • 1970-01-01
    • 2023-02-24
    • 1970-01-01
    • 1970-01-01
    • 2019-08-10
    相关资源
    最近更新 更多