vba needed

HorseRacing

New Member
Joined
Jun 2, 2021
Messages
9
Office Version
  1. 2019
Platform
  1. Windows
Want a Vba that allows user to highlight cells to form a table. Need exel to randomly assign '1' in empty cells in this table such that:
The sum of each row is equal to the value of the adjacent column after the table. AT THE SAME TIME.
The sum of each column of the table equals to the value of the adjacent row after the table.
 

Excel Facts

Excel Can Read to You
Customize Quick Access Toolbar. From All Commands, add Speak Cells or Speak Cells on Enter to QAT. Select cells. Press Speak Cells.
Does: "highlight cells to form a table ..." mean that user selects a range of cells which is to the left of a column with numbers and above a row with numbers (does it touch the column/row and VBA code fills cells?

What if no matching fill combination exists?
It's quite possible situation, especially if number of rows selected is lower than any of numbers in row below, and if number of columns selected is smaller than any of the numbers in column to the right.
 
Upvote 0
Hi Kaper
Thank you for replying.
The data will be adjusted such that there will always be a matching fill that enables a match with the sum of rows and sum of columns. Is there anyway for me to send you the excel file so you can have a clearer idea?
Here is a picture up of what i need to do.

Colum A contains names of invigilators this can vary in terms of numbers. Row A has the dates of exams, the number of exam can vary too. Hence the need for user to higlight cells to form the table.....in this case the table would start at B2 to L10.
In the table B2 to L10, some cells will contain 1 and some 0 and some blank.
I need a VBA that will randomly insert 1s in blank cells such that for e.g the sum of column B of the table must be equal to B11, which is fixed by me. While at the same time the sum of each row of the table is given by the cell value C18 in this case is 8.
Cell value in C18 will always be adjusted by me to ensure that it is possible to randomly assign 1 in the empty cells so that the sum of the row corresponds to value C18 and the sum of the column corresponds to row 11.
It will be great if the vba can also count the sum of the row and place it in the column adjacent to the table.....in this case Column M.

I tried several VBAs but can only manage to get it to randomly insert 1s and meet the sum of row or sum of column criteria but NOT BOTH. In the picture, this VBA inserted 1 randomly and was able to meet the criteria for the sum of each column.....but can't meet the criteria where the sum of the rows, in this case should be 8.

I hope you can help.
Thanks.
 

Attachments

  • Picture4.jpg
    Picture4.jpg
    110.5 KB · Views: 10
Upvote 0
OK, so the range to be filled is rather constatnt (it can be adjusted if number of proctors or exam dates change.

I changed one of the limits from your original screenshot (in L11) because sum of all proctors in all dates must be the same if counted by every date and by every proctor (cells O11 vs. M13)
Sheet layout (after solving - before there were only 0's in some cells) including formulas. Formulas are important, as I decided to rely on in-sheet calculation in the macro:

random_assignment_in_table.xlsm
ABCDEFGHIJKLMNOP
11234567891011sums
2a111110111199
3b1111011177
4c11111111111010
5d1111111188
6e11011111188
7f11111111199
8g1111111188
9h111111177
10j111111177
1176786687774730
127678668777473
137373
1400
Sheet1
Cell Formulas
RangeFormula
N2:N10N2=SUM(B2:L2)
P11P11=SUMXMY2(B11:L11,B12:L12)
B12:L12B12=SUM(B2:B10)
O11:O12O11=SUM(B11:L11)
M13:N13N13=SUM(N2:N10)
M14M14=SUMXMY2(M2:M10,N2:N10)
P14P14=M14+P11


Note that if some columns or rows are to be added or deleted - do it always in the table center like column F or row 5. Not first or last column/row as this may hurt the formulas on right/bottom.
Of course after adding rows/columns copy column/row summing formulas from neighbouring cells and add new requirements.

Also after removing/adding rows/cols change constants declaration in the code below.

And here is the code:
VBA Code:
Const my_rows = 9
Const my_cols = 11

Sub random_insertion()
Dim required As Long, i As Long, j As Long, counter As Long
' check initial setup
If Cells(my_rows + 4, my_cols + 2) <> Cells(my_rows + 2, my_cols + 4) Then
  MsgBox "Sorry, cant do that, as rows sums is different than columns sum", vbCritical, "! Please correct data !"
  Exit Sub
End If
required = Cells(my_rows + 4, my_cols + 2)
' clear working space
For i = 1 To my_rows
  For j = 1 To my_cols
    If Cells(i + 1, j + 1) = 1 Then Cells(i + 1, j + 1).ClearContents
Next j, i
'initial random assignment
counter = 0
Randomize
While counter < required
  i = WorksheetFunction.RandBetween(1, my_rows)
  j = WorksheetFunction.RandBetween(1, my_cols)
  If Cells(i + 1, j + 1) = "" Then
    Cells(i + 1, j + 1) = 1
    counter = counter + 1
  End If
Wend
Call optimization
End Sub

Sub optimization()
Dim required As Long, i As Long, j As Long, x As Long, y As Long, counter As Long, sq_sum As Long, tmp As Variant, swaps As Long
' initialize
required = Cells(my_rows + 4, my_cols + 2)
Randomize
counter = 0
swaps = 0
sq_sum = Cells(my_rows + 5, my_cols + 5)
' loop trying to swap empty cells and 1's
Application.ScreenUpdating = False
While sq_sum > 0 And counter < 25000 'counter limit may be increased or replaced with time limit
  i = WorksheetFunction.RandBetween(1, my_rows)
  j = WorksheetFunction.RandBetween(1, my_cols)
  y = WorksheetFunction.RandBetween(1, my_rows)
  x = WorksheetFunction.RandBetween(1, my_cols)
  If Cells(i + 1, j + 1).Text <> "0" And Cells(y + 1, x + 1).Text <> "0" Then
    If Cells(i + 1, j + 1) <> Cells(y + 1, x + 1) Then
      tmp = Cells(i + 1, j + 1)
      Cells(i + 1, j + 1) = Cells(y + 1, x + 1)
      Cells(y + 1, x + 1) = tmp
      If Cells(my_rows + 5, my_cols + 5) < sq_sum Then
        sq_sum = Cells(my_rows + 5, my_cols + 5)
        swaps = swaps + 1
      Else
        tmp = Cells(i + 1, j + 1)
        Cells(i + 1, j + 1) = Cells(y + 1, x + 1)
        Cells(y + 1, x + 1) = tmp
      End If
    End If
  End If
  counter = counter + 1
  If counter Mod 100 = 0 Or sq_sum = 0 Then 'show progress each 100 rounds in status bar
    Application.StatusBar = counter & " attempts and " & swaps & " swaps done so far, result = " & sq_sum
  End If
Wend
Application.ScreenUpdating = True
tmp = " after: " & counter & " attempts and " & swaps & " swaps."
If sq_sum = 0 Then
  MsgBox "Fisished" & tmp, vbInformation
Else
  If MsgBox("Gave up" & tmp & vbLf & vbLf & "Shall I continue with the same initial values?", _
    vbCritical + vbYesNo) = vbYes Then Call optimization
End If
Application.StatusBar = ""
End Sub

The code is divided ito 2 procedures. The second is by default called from the first one, but after combo of first and second stops without perfect match, the second could be run again (possibly some times).
While optimization is performed current status is displayed in status bar.

Hope this fits your needs 🙃
 
Upvote 0
OK, so the range to be filled is rather constatnt (it can be adjusted if number of proctors or exam dates change.

I changed one of the limits from your original screenshot (in L11) because sum of all proctors in all dates must be the same if counted by every date and by every proctor (cells O11 vs. M13)
Sheet layout (after solving - before there were only 0's in some cells) including formulas. Formulas are important, as I decided to rely on in-sheet calculation in the macro:

random_assignment_in_table.xlsm
ABCDEFGHIJKLMNOP
11234567891011sums
2a111110111199
3b1111011177
4c11111111111010
5d1111111188
6e11011111188
7f11111111199
8g1111111188
9h111111177
10j111111177
1176786687774730
127678668777473
137373
1400
Sheet1
Cell Formulas
RangeFormula
N2:N10N2=SUM(B2:L2)
P11P11=SUMXMY2(B11:L11,B12:L12)
B12:L12B12=SUM(B2:B10)
O11:O12O11=SUM(B11:L11)
M13:N13N13=SUM(N2:N10)
M14M14=SUMXMY2(M2:M10,N2:N10)
P14P14=M14+P11


Note that if some columns or rows are to be added or deleted - do it always in the table center like column F or row 5. Not first or last column/row as this may hurt the formulas on right/bottom.
Of course after adding rows/columns copy column/row summing formulas from neighbouring cells and add new requirements.

Also after removing/adding rows/cols change constants declaration in the code below.

And here is the code:
VBA Code:
Const my_rows = 9
Const my_cols = 11

Sub random_insertion()
Dim required As Long, i As Long, j As Long, counter As Long
' check initial setup
If Cells(my_rows + 4, my_cols + 2) <> Cells(my_rows + 2, my_cols + 4) Then
  MsgBox "Sorry, cant do that, as rows sums is different than columns sum", vbCritical, "! Please correct data !"
  Exit Sub
End If
required = Cells(my_rows + 4, my_cols + 2)
' clear working space
For i = 1 To my_rows
  For j = 1 To my_cols
    If Cells(i + 1, j + 1) = 1 Then Cells(i + 1, j + 1).ClearContents
Next j, i
'initial random assignment
counter = 0
Randomize
While counter < required
  i = WorksheetFunction.RandBetween(1, my_rows)
  j = WorksheetFunction.RandBetween(1, my_cols)
  If Cells(i + 1, j + 1) = "" Then
    Cells(i + 1, j + 1) = 1
    counter = counter + 1
  End If
Wend
Call optimization
End Sub

Sub optimization()
Dim required As Long, i As Long, j As Long, x As Long, y As Long, counter As Long, sq_sum As Long, tmp As Variant, swaps As Long
' initialize
required = Cells(my_rows + 4, my_cols + 2)
Randomize
counter = 0
swaps = 0
sq_sum = Cells(my_rows + 5, my_cols + 5)
' loop trying to swap empty cells and 1's
Application.ScreenUpdating = False
While sq_sum > 0 And counter < 25000 'counter limit may be increased or replaced with time limit
  i = WorksheetFunction.RandBetween(1, my_rows)
  j = WorksheetFunction.RandBetween(1, my_cols)
  y = WorksheetFunction.RandBetween(1, my_rows)
  x = WorksheetFunction.RandBetween(1, my_cols)
  If Cells(i + 1, j + 1).Text <> "0" And Cells(y + 1, x + 1).Text <> "0" Then
    If Cells(i + 1, j + 1) <> Cells(y + 1, x + 1) Then
      tmp = Cells(i + 1, j + 1)
      Cells(i + 1, j + 1) = Cells(y + 1, x + 1)
      Cells(y + 1, x + 1) = tmp
      If Cells(my_rows + 5, my_cols + 5) < sq_sum Then
        sq_sum = Cells(my_rows + 5, my_cols + 5)
        swaps = swaps + 1
      Else
        tmp = Cells(i + 1, j + 1)
        Cells(i + 1, j + 1) = Cells(y + 1, x + 1)
        Cells(y + 1, x + 1) = tmp
      End If
    End If
  End If
  counter = counter + 1
  If counter Mod 100 = 0 Or sq_sum = 0 Then 'show progress each 100 rounds in status bar
    Application.StatusBar = counter & " attempts and " & swaps & " swaps done so far, result = " & sq_sum
  End If
Wend
Application.ScreenUpdating = True
tmp = " after: " & counter & " attempts and " & swaps & " swaps."
If sq_sum = 0 Then
  MsgBox "Fisished" & tmp, vbInformation
Else
  If MsgBox("Gave up" & tmp & vbLf & vbLf & "Shall I continue with the same initial values?", _
    vbCritical + vbYesNo) = vbYes Then Call optimization
End If
Application.StatusBar = ""
End Sub

The code is divided ito 2 procedures. The second is by default called from the first one, but after combo of first and second stops without perfect match, the second could be run again (possibly some times).
While optimization is performed current status is displayed in status bar.

Hope this fits your needs 🙃
Hi Kaper
Thanks for the effort and the code. I really appreciate it.

Perhaps i should explain exactly what i am trying to do.
I am trying to create an exam invigilation schedule where each invigilator(a,b,c,d,e,f,g,h,j)gets the same number of invigilation..in this case 8. Row 11 shows how many invigilators i need each date(1,2,3,4,5,6,7,...11). The '0' that is preinserted in the table is to show that the invigilator is not free to invigilate and the preinserted '1' is to ensure that the invigilator is invigilating on the given date

The problem still persists....The sum (row sum) for all invigilators...i.e a,b,c,d,e,f,g,h,j has to be 8. The random assigning of 1s in empty cells must ensure that column sum is equal to row 11 ( which you were able to ensure that).....but at the same time the row sum must be the same...in this case 8.

With regards to your data (screeshot you sent)....there was a mismatch....coz there were 73 invigilation slots but only 9 invigilators......hence each invigilator will not be able to do 8 duty each (9x8=72). I would ensure that before running the macro, i will adjust row 11 so that the row sum will be 72.....or even adjust row 11 to have 81 slots....so that now the row sum of each invigilator (a,b,c,...) is 9. Please refer to the pic to see one possible way to assign the 1s if the macro worked correctly.

.Once again, thanks for your time and effort.
Cheers.
 

Attachments

  • reply.jpg
    reply.jpg
    106.2 KB · Views: 5
Upvote 0
So insert 8's in column M (as required values), change back L11 to 3 and ... run random_insertion macro.
Your result will be surely different one than mine (see first screenshot) but the macro will deal with it.
also if you want a more "equilibrium" distribution between dates just insert it in row 11 (see second screenshot)

and on third screenshot two examples of tight and loose cases.

Each of these generated just by filling required values in column M and row 11 (as mentioned earlier - can be changed for more/less people or/and more/less dates.

Just remember to use macro called random_insertion. The next one (optimization) is only for rare (but possible) cases when after random_insertion finishes in cell P14 is still non-zero value. This non-zero means that row 12 (computed) does not fit perfectly row 11 (required) and/or column N (computed) does not fit perfectly column M (required). If it is so, you can run optimization macro (may be few times?). Just remember to not ask impossible. If all column M is 11 and all row 11 is filled with 9 and there are any zeros in B2:L10 then macro will hang up (has no built in "fuse" against such errors).

Hope I understood you well, and that above clarification is also understandable.
If not, write again (having in mind that for me English is 3rd language. Used to be 4th, but now I'm using English more often).


1730922711529.png

1730922954492.png


1730923305509.png
 
Upvote 0
Kaper!!!! WOW!! It works perfectly!!
Thank YOU!!Thank YOU!!Thank YOU!!Thank YOU!!
Really sorry....but I need to make one more request.....the user of the file will not know how to adjust the macros.....hence they will not be able to adjust the row and column number.
I will provide many rows and many columns for them to delete so they will not add any column or rows....just delete what they will not use.
Is there any way that the user can highlight the table and the row and column number will be adjusted automatically in the macros?
 
Upvote 0
Kaper!!!! WOW!! It works perfectly!!
Thank YOU!!Thank YOU!!Thank YOU!!Thank YOU!!
Really sorry....but I need to make one more request.....the user of the file will not know how to adjust the macros.....hence they will not be able to adjust the row and column number.
I will provide many rows and many columns for them to delete so they will not add any column or rows....just delete what they will not use.
Is there any way that the user can highlight the table and the row and column number will be adjusted automatically in the macros?
Opps.....found a bug. If i preassigned 0 in the table......it prevents the assigning of 1. BUt if i preassign '1' in the table, it gets cleared. I need to be able to preassign 1 in the table as well. Can you help with that too?
Really sorry.
 
Upvote 0
Kaper!!!! WOW!! It works perfectly!!
Thank YOU!!Thank YOU!!Thank YOU!!Thank YOU!!
Really sorry....but I need to make one more request.....the user of the file will not know how to adjust the macros.....hence they will not be able to adjust the row and column number.
I will provide many rows and many columns for them to delete so they will not add any column or rows....just delete what they will not use.
Is there any way that the user can highlight the table and the row and column number will be adjusted automatically in the macros?
Hi Kaper
How do i adjust the macros if the table starts for e.g at Column E instead of Column B?
Also what adjustments need to be made if the table starts at row 9 instead of row 2?

What would be great is if the user can identify the start and end of the table by highlighting the table so that there would be no need to modify the macros.
Once again...sorry for the trouble
 
Last edited:
Upvote 0
Hmmm, the needs are growning.

1) The range to be used in simulation has to be selected
2) if columns/rows were deleted/added it has to be done in the migddle of the range and in case of adding formulas in second row/column below/right to working range have to be copied from neighbouring cells.
3) 0 for non not available combination of day/person, 1 for preassigned combination. If you want to see (differentiate) these preassigned 1s from randomly assigned use for instance cell color.
4) additional runs of optimization can be obtained only answering yes if the result was not satisfactory after first attempt(s). This can happen mostly in situation when there is a lot of preassigned values.
5) formulas in sums were adjusted. See the mini sheet.

See screenshot which is an example of such "tough" situation as described in 4). And note what is selected - just simulation area, no headers, no required values rows/colums.

The code:

VBA Code:
Sub random_insertion()
'main variables
Dim start_row As Long, start_col As Long, my_rows As Long, my_cols As Long, required As Long
' helper variables
Dim i As Long, j As Long, counter As Long, tmp_rng As Range
' define random fill location
If Selection.Cells.Count < 2 Then
  MsgBox "Please select whole range to be filled before running macro", vbCritical, "! Please select proper range !"
  Exit Sub
End If
start_col = Selection.Column
start_row = Selection.Row
my_cols = Selection.Columns.Count
my_rows = Selection.rows.Count
' check initial setup
required = Cells(my_rows + 2 + start_row, my_cols + start_col) - Cells(my_rows + 2 + start_row, my_cols + start_col + 1)
If required <> Cells(my_rows + start_row, my_cols + 2 + start_col) - Cells(my_rows + start_row + 1, my_cols + 2 + start_col) Then
  MsgBox "Sorry, cant do that, as required rows sums is different than columns sum", vbCritical, "! Please correct data !"
  Exit Sub
End If
' no clearing working space, just change pre-inserted 1's
For i = 0 To my_rows - 1
  For j = 0 To my_cols - 1
    If Cells(i + start_row, j + start_row) = 1 Then Cells(i + start_row, j + start_row) = "x"
Next j, i
'initial random assignment
counter = 0
Randomize
While counter < required
  i = WorksheetFunction.RandBetween(0, my_rows - 1)
  j = WorksheetFunction.RandBetween(0, my_cols - 1)
  If Cells(i + start_row, j + start_col) = "" Then
    Cells(i + start_row, j + start_col) = 1
    counter = counter + 1
  End If
Wend
optimization start_row, start_col, my_rows, my_cols
End Sub

Private Sub optimization(start_row As Long, start_col As Long, my_rows As Long, my_cols As Long)
Dim i As Long, j As Long, x As Long, y As Long, counter As Long, sq_sum As Long, tmp As Variant, swaps As Long
' initialize
Randomize
counter = 0
swaps = 0
sq_sum = Cells(my_rows + 3 + start_row, my_cols + 3 + start_col)
' loop trying to swap empty cells and 1's
Application.ScreenUpdating = False
While sq_sum > 0 And counter < 25000 'counter limit may be increased or replaced with time limit
  i = WorksheetFunction.RandBetween(0, my_rows - 1)
  j = WorksheetFunction.RandBetween(0, my_cols - 1)
  y = WorksheetFunction.RandBetween(0, my_rows - 1)
  x = WorksheetFunction.RandBetween(0, my_cols - 1)
  If Cells(i + start_row, j + start_col).Text <> "0" And Cells(y + start_row, x + start_col).Text <> "0" _
  And Cells(i + start_row, j + start_col).Text <> "x" And Cells(y + start_row, x + start_col).Text <> "x" Then
    If Cells(i + start_row, j + start_col) <> Cells(y + start_row, x + start_col) Then
      tmp = Cells(i + start_row, j + start_col)
      Cells(i + start_row, j + start_col) = Cells(y + start_row, x + start_col)
      Cells(y + start_row, x + start_col) = tmp
      If Cells(my_rows + 3 + start_row, my_cols + 3 + start_col) <= sq_sum Then
        sq_sum = Cells(my_rows + 3 + start_row, my_cols + 3 + start_col)
        swaps = swaps + 1
      Else
        tmp = Cells(i + start_row, j + start_col)
        Cells(i + start_row, j + start_col) = Cells(y + start_row, x + start_col)
        Cells(y + start_row, x + start_col) = tmp
      End If
    End If
  End If
  counter = counter + 1
  If counter Mod 100 = 0 Or sq_sum = 0 Then 'show progress each 100 rounds in status bar
    Application.StatusBar = counter & " attempts and " & swaps & " swaps done so far, result = " & sq_sum
  End If
Wend
Application.ScreenUpdating = True
tmp = " after: " & counter & " attempts and " & swaps & " swaps."
If sq_sum = 0 Then
  MsgBox "Fisished" & tmp, vbInformation
Else
  If MsgBox("Stopped with not final fit achieved" & tmp & vbLf & vbLf & "Shall I continue with the same initial values?", _
    vbCritical + vbYesNo) = vbYes Then optimization start_row, start_col, my_rows, my_cols
End If
For i = 0 To my_rows - 1
  For j = 0 To my_cols - 1
    If Cells(i + start_row, j + start_row) = "x" Then Cells(i + start_row, j + start_row) = 1
Next j, i
Application.StatusBar = ""
End Sub


The mini sheet NOTE SUM FORMULAS:

random_assignment_in_table.xlsm
ABCDEFGHIJKL
1
2
3
4
508.11.202409.11.202410.11.2024requiredobtained
6Name11122
7Name21122
8Name311022
9Name411022
10Name51122
11required343100
12obtained34310
131010
1400
15
Sheet1
Cell Formulas
RangeFormula
I6:I10I6=SUM(E6:G6)+COUNTIF(E6:G6,"x")
K11K11=SUMXMY2(E11:G11,E12:G12)
E12:G12E12=SUM(E6:E10)+COUNTIF(E6:E10,"x")
J11:J12J11=SUM(E11:G11)
H13:I13I13=SUM(I6:I10)
H14H14=SUMXMY2(H6:H10,I6:I10)
K14K14=H14+K11


1731057293788.png
 
Upvote 0
Solution

Forum statistics

Threads
1,224,823
Messages
6,181,175
Members
453,021
Latest member
Justyna P

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top