Bad result method

I have an inefficient code. I need some help. Here’s the explanation:

On a project, I build an html file and an index at the beginning of the file (using a <TABLE…).

That TABLE have a max of 4 columns. I wrote code to achieve that goal, but the code is reliably… “wrong” !

I found a variant, but the “wrong” change …

What’s wrong ? In the result, case of the project example, if I have a number of lines to place in the table is a multiple of 4, I have a 5th column (and empty) that is created.

In the other version, if the number of lines in NOT divisible by 4, the files are sorted correctly in the Colums… ELSE: the Last column is not completely filled…

I take the time to create an example, believing it will help me to found what is wrong, but no, I failed.

Did you have ideas of how to correct the code ?

The used code (from the example’s button):


Dim Loc_Col    As Integer = 1
Dim Total_Cnt  As Integer = Val(TF.Text)
Dim Row_Cnt    As Integer = (Total_Cnt \ 4) + 1 // Remove " + 1 " for the worst result !
Dim html_Col   As Integer = 1
Dim EOL        As String  = EndOfLine
Dim Sommaire_List As String = "<table><tr><td>Column " + Str(html_Col) + EOL
Dim Loop_Idx As Integer

For Loop_Idx = 1 To Total_Cnt
  // Display the file entries
  Sommaire_List = Sommaire_List + Str(Loop_Idx) + ". This is a file entry.<br>" + EOL
  
  // If the Column is full, add a brand new one…
  If Loc_Col = Row_Cnt Then
    html_Col = html_Col + 1
    // Place the end of Column / Start of column tags "</td><td>"
    Sommaire_List = Sommaire_List + "</td>" + EOL + "<td width=25%>Column " + Str(html_Col) + EOL
    
    // Reset the Column Count
    Loc_Col = 1
  Else
    // Increase the # of columns
    Loc_Col = Loc_Col + 1
  End If
Next

// Report the result
TA.Text = Sommaire_List + "</td></tr>" + EOL + "</table>" + EOL

The test project is here:

In case it help to understand, here’s a screen shot f the real project:

84 MOD 4 = 0: this is the important computation to understand the screen shot above.

So, the code had to display 21 lines in each Column, but you can see it places 22 lines / Column excepted in the last Column where there is room for 4 columns.

Somewhere in my code, if I remove “+ 1” (where I cound the number of Rows), and in this specific case, the result is different (21 lines in each Column), but I get a 5th Column (empty).

In HTML tables you don’t fill up columns and pass to another one, you fill up rows, containing all columns, so you must compute every row and fill up all the 4 columns of it at once, including empty cells.

That is not what I’ve done.

I have one Row, with some lines, and 4 Columns.

As you can see in the screen shot, in the original project, they are links to the issues description (it’s a magazine description document).
I do not need a …/… for each “line” (group of 4 links/entries); one is enough.

The main problem is that you decide how many things to put in each row using

(Total_Cnt \ 4)

If this is divisible by 4, it works.
eg 24 gets you 6 things in each column.

27 still suggests that each column needs 6 things, but you have 3 left over.
When you hit 25, your code adds an unwanted column.

Without rewriting your code for you, here is one approach…

Consider 27
Total_Cnt \ 4 gets you 6
Total_Cnt mod 4 gets you 3… the extra items

Add a variable called Pseudo_Cnt

Pseudo_cnt =  (Total_Cnt \ 4) *4      //eg for 27,  this gets you 24
if (Total_Cnt mod 4) <> 0 then 
Pseudo_cnt = Pseudo_cnt = +4   // eg for 27, this gets you 28
end if
//for 27, you will get 7 items per column, but one column needs only 6

now, loop from 1 to Pseudo_cnt instead of 1 to Total_Cnt
and only add an item if Loop_Idx <= Total_Cnt

Hi Jeff,

Thank you for your answer.

The actual code works fine only if the total count is NOT divisible by 4.
I use this project since… two years ago many times a week, and in most of the cases Total_Cnt MOD 4 <> 0… but yesterday, Total_Cnt was 84… And I wanted to squash the bug when I saw an empty 5th column (this reminds me something).

When Total_Cnt MOD 4 <> 0 the index is correct.

I confess, I have to implement your advice (as is, I do not understand how it can work), I will do it after a nap (for max brain possible).

Now, maybe if i use a different code design to populate a Row at a time (using a step in the Loop… I think I have the source FolderItems in an Array.

What’s important is the final result; one Row/Many line in it vs Many Rows, I will take the one that always show the correct solution.

Maybe extracting that code from the main Method allows me to better “see” (or feel) the code…

BTW: my example show variations I have not seen yesterday…

Now, I understand… I already falled into that trap (with Tab Character added at the end of a String Concatenation). Usually, I remove the unwanted last Tab (last char of the string) after the loop.

I remember (how is it possible ?) that I tried => and <= variations, but that failed… when I added the html index to my html code generation.

This works better; I simplified it a bit. You had too many variables doing the counting. You would need some CSS to get the content of the last column to be aligned properly. It’s too long since I did html so I’ll leave that to you.

Var  i, ColNum, RowNum, RowLim, TotalCnt  As Integer, Sommaire_List As String

ColNum   = 1
RowNum   = 1
TotalCnt = 86
RowLim   = (TotalCnt \ 4) + 1

Sommaire_List = "<!doctype html><head></head><body><table><tr><td>Column " + ColNum.ToString + "<br>"

For i = 1 To TotalCnt
  
  // Display the file entries
  Sommaire_List = Sommaire_List + i.ToString + ". This is a file entry.<br>"
  RowNum = RowNum + 1
  
  // If the Column is full, add a brand new one (unless at the end)
  If  (RowNum=RowLim and i<TotalCnt)  Then
    
    // Place the end of Column / Start of column tags "</td><td>"
    ColNum = ColNum + 1
    Sommaire_List = Sommaire_List + "</td><td>Column " + ColNum.ToString + "<br>"
    
    // Reset the Row Count
    RowNum = 1
    
  end if
  
Next

// Report the result
Sommaire_List = Sommaire_List + "</td></tr></table></body></html>"

HTMLViewer1.LoadPage (Sommaire_List, Nil)

In any case, as @Rick_Araujo said, you should be filling as many <ROW>s as you need, not using <BR>. I’ll try to change my example to do that.

Here it is done with many rows. This does mean that the entries are numbered across rather than down, which may not be what you want.

Note that with both methods, you avoid the blank column (or row) with the extra test in the if.

Var  i, ColNum, ColLim, TotalCnt  As Integer, Sommaire_List As String

ColNum   = 1
ColLim   = 4
TotalCnt = 83

Sommaire_List = "<!doctype html><head></head><body><table><tr>"

for  i = 1 to ColLim
  Sommaire_List = Sommaire_List + "<td>Column " + i.ToString + "</td>"
next

Sommaire_List = Sommaire_List + "</tr>"

For i = 1 To TotalCnt
  
  // Display the file entries
  Sommaire_List = Sommaire_List + "<td>" + i.ToString + ". This is a file entry.</td>"
  ColNum = ColNum + 1
  
  // If the Row is full, add a brand new one (unless at the end)
  If  (ColNum>ColLim and i<TotalCnt)  Then
    
    // Start a new row
    Sommaire_List = Sommaire_List + "</tr><tr>"
    
    // Reset the Row Count
    ColNum = 1
    
  end if
  
Next

// Report the result
Sommaire_List = Sommaire_List + "</td></tr></table></body></html>"

HTMLViewer1.LoadPage (Sommaire_List, Nil)

Here it is with the numbers increasing down each column. Slightly more complex.

Var  i, j, ColNum, ColLim, RowNum, RowCnt, TotalCnt, DodCols  As Integer, Sommaire_List As String

ColNum   = 1
RowNum   = 1
ColLim   = 4
TotalCnt = 88
RowCnt   = (TotalCnt \ ColLim) + 1
DodCols  = TotalCnt mod ColLim

Sommaire_List = "<!doctype html><head></head><body><table width=100%><tr>"

for  i = 1 to ColLim
  Sommaire_List = Sommaire_List + "<td>Column " + i.ToString + "</td>"
next

Sommaire_List = Sommaire_List + "</tr>"

For i = 1 To TotalCnt
  
  j = RowNum + ((ColNum-1) * (RowCnt-1)) + (if  (ColNum>DodCols, DodCols, (ColNum-1)))
  
  // Display the file entries
  Sommaire_List = Sommaire_List + "<td>" + j.ToString + ". This is a file entry.</td>"
  ColNum = ColNum + 1
  
  // If the Row is full, add a brand new one (unless at the end)
  If  (ColNum>ColLim and i<TotalCnt)  Then
    
    RowNum = RowNum + 1
    Sommaire_List = Sommaire_List + "</tr><tr>"
    
    // Reset the Column Count
    ColNum = 1
    
  end if
  
Next

// Report the result
Sommaire_List = Sommaire_List + "</td></tr></table></body></html>"

HTMLViewer1.LoadPage (Sommaire_List, Nil)

Public Function GetItemsToPopulateTable() As String()

  // Just simulate some content for tests

  Var itemsRead() As String
  
  Var lastItem As Integer = TF.Text.Trim.Val() -1 // get the mumber of items from UI
  
  For i As Integer = 0 to lastItem
    itemsRead.Add "Subject " + i.ToString
  Next
  
  Return itemsRead
  
End Function
Public Function GetHTMLTableFromArray(items() As String, numColumns As Integer = 4, tableWidth As String = "90%") As String

  // Create a HTML table string using the parameters and return it
  
  Var numItems As Integer = items.Count
  
  Var numRows As Integer = numItems \ numColumns + If(numItems mod numColumns > 0, 1, 0)
  
  Var numItemsGrid As Integer = numRows * numColumns // includes empty cells at end
  
  Var  htmlTable As String = "<table style=""width:"+tableWidth+""">" + EndOfLine // Start the html table
  
  For row As Integer = 0 to numRows - 1
    htmlTable = htmlTable + "<tr>"
    For Col As Integer = 0 to numColumns - 1
      Var mappedItem As Integer = row + col * numRows // transpose into columns
      htmlTable = htmlTable + "<td>" + If(mappedItem >= numItems, "&nbsp;", items(mappedItem)) + "</td>"
    Next
    htmlTable = htmlTable + "</tr>" + EndOfLine 
  Next
  
  Return (htmlTable + "</table>" + EndOfLine)  // end the html table and return it
  
End Function
Sub Action() Handles Action

  // Run
  
  Var html As String = GetHTMLTableFromArray( GetItemsToPopulateTable() )
  
  TA.Text = html
  
  HTMLViewer1.LoadPage(html, FolderItem.TemporaryFile)
  
End Sub

You can remove this line safely. It’s useless, it is a forgot remain of the tests while designing the function.