Sorry if the title isn't clear enough. I didn't find a nice way to phrase what I'm trying to achieve here. I'm an excel newbie so don't know where to start with the logic for what I'm looking for.
I have a spreadsheet I want to use to schedule cleaning in a student corridor. Thing is that rooms are not always occupied, so it needs to be able to react whether the next room is occupied or not and if not, select next one on the list.
In this image we can see how week 22 was cleaned by 1902 and 1903, next week turn should be next two rooms in [1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911] as long as they are booked (value 1 in the columns of the right). 1904 and 1905 are booked (green) so next week will be their turn.
If we continue with the same logic next turn will be 1906&1907 and next week (weeknumber 25) should be 1908&1909, problem is that room 1908 is empty right now so it shouldn't be counted as an eligible room and instead choose 1909&1910 as next cleaning rooms.
How could I achieve this logic with an excel spreadsheet?
Here is a link for a copy of my spreadsheet so you can see what I'm trying to achieve and play with original data if needed.
I'm fairly sure it's this:
=IF(NOT(ISNA(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-1)), INDEX($G$1:$Q$1,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-1) & "-" & IF(NOT(ISNA(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-1,COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-1,COLUMNS($G$1:$Q$1)))), INDEX($G$1:$Q$1,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-1,COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-1,COLUMNS($G$1:$Q$1))), INDEX($G$1:$Q$1,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-1,COLUMNS($G$1:$Q$1))+1-2)),0))), INDEX($G$1:$Q$1,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-2)),0)) & "-" & IF(NOT(ISNA(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-2)),0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-2)),0),COLUMNS($G$1:$Q$1)))), INDEX($G$1:$Q$1,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-2)),0),COLUMNS($G$1:$Q$1))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-2)),0),COLUMNS($G$1:$Q$1))), INDEX($G$1:$Q$1,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1))):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G$1:$Q$1,0),COLUMNS($G$1:$Q$1))+1-2)),0),COLUMNS($G$1:$Q$1))+1-2)),0))))
Paste this in below a row which already has a turn in it, e.g. D6
(the turn for week 27). This formula uses the previous turn and the range of room names ($G$1:$Q$1
) to determine what the next turn should be.
Wait, what is this monstrosity!? It's the most daunting formula I've ever seen too!
Well, there's a lot of repetition in there so that it works independently. It's also doing a lot of heavy lifting so that it's e.g. flexible to how many rooms there might be, what the present row is and to any rooms being booked. There could be even more logic to handle edge cases such as, what if all rooms are booked? (currently it returns #N/A
), but this solves the fundamental problem.
Breaking it down into steps:
Find which room was cleaned last
Using cell D6
as a starting point, the last room to be cleaned is 1910. So, we want to find this value in cell D5
:
=INT(RIGHT(D5, LEN(D5)-FIND("-", D5)))
This simply takes the text from D5
and extracts the last number.
Locate that within the range
The range $G$1:$Q$1
is the list of cells on the first row that specify the room numbers ($
means that this is fixed, so if you drag this formula down, for example, the range will still refer to G1:Q1 and not anything below it).
The 1st column of this range (G
) is the 7th column in the spreadsheet. This (7) can be found with the following formula:
=COLUMN($G$1:$Q$1)
The number of columns in the range (11) is:
=COLUMNS($G$1:$Q$1)
To find the last cleaned room in the range, we use MATCH with Match_type = 0:
MATCH finds the first value that is exactly equal to lookup_value.
=MATCH( [Last room number] ,$G$1:$Q$1,0)
=MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0)
This returns the relative column number, from 1 to 11, of the the matching cell (1910 is on the 10th column).
Identify target range
We now want to start looking at the next column after that. We could simply add 1, which works fine unless the last room to be cleaned was 1911. In that case, we can't just add 1, because there's no 12th room. There, we instead want to go back round to the 1st room. To do this, we first use MOD and divide by the number of columns:
=MOD( [Relative column number] ,$G$1:$Q$1,0), [Column count] ) +1
=MOD( MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) ) +1
The number returned by this is the column to start with. So, if the last room was 1910, then we get 11 as a result. The column to end with is the last one, 11 (the column count).
However, if no unbooked rooms are found in this range, then we have to go around and check again from the first to the one before the start. This means we always need to do two checks to maintain the sequence of rooms.
In our example:
Check | Start | End | Range |
---|---|---|---|
1 | 11 | 11 | Q6:Q6 |
2 | 1 | 10 | G6:P6 |
As pseudo-formulae:
Check | Start | End | Range |
---|---|---|---|
1 | MOD( [Relative column number] ,$G$1:$Q$1,0), [Column count] ) +1 | [Column count] | =INDIRECT(ADDRESS(ROW(), [Column count before first] + [Start] )):INDIRECT(ADDRESS(ROW(), [Column count before first] + [End] )) |
2 | 1 | [Last room column] | =INDIRECT(ADDRESS(ROW(), [Column count before first] + [Start] )):INDIRECT(ADDRESS(ROW(), [Column count before first] + [End] )) |
As formulae:
Check | Start | End | Range |
---|---|---|---|
1 | =MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) ) +1 |
=COLUMNS($G$1:$Q$1) |
=INDIRECT(ADDRESS(ROW(), COLUMN($G$1:$Q$1)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) ) +1 )):INDIRECT(ADDRESS(ROW(), COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1) )) |
2 | 1 |
=MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) ) |
=INDIRECT(ADDRESS(ROW(), COLUMN($G$1:$Q$1) )):INDIRECT(ADDRESS(ROW(), COLUMN($G$1:$Q$1)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) ) )) |
Some formula acrobatics needs to be done to describe this as a range to Excel. These ranges are specifying the present row, and giving the absolute column reference (remember, the 1st column of the range is the 7th column of the spreadsheet, so we need to add 6, or =COLUMN($G$1:$Q$1) -1
to these)
Find next unbooked room
Now we can use that with MATCH
to find the next unbooked room in each case:
=MATCH(1, [Range] ,0) + [Start] -1
=MATCH(1, INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) ) +1 )):INDIRECT(ADDRESS(ROW(),COLUMN($G$1:$Q$1)-1 + COLUMNS($G$1:$Q$1) )) ,0)+ MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) )
(Returns 11)
=MATCH(1, [Range] ,0)
=MATCH(1, INDIRECT(ADDRESS(ROW(), COLUMN($G$1:$Q$1) )):INDIRECT(ADDRESS(ROW(), COLUMN($G$1:$Q$1)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G$1:$Q$1,0), COLUMNS($G$1:$Q$1) ) )) ,0)
(This is slightly simpler as start is always 1)
(Returns 1)
This means that a value of 1
has been found on the 11th and 1st columns.
Finding room numbers
To find the actual room numbers from these column numbers, we use INDEX:
=INDEX($G$1:$Q$1,1, [Matching column number]) Returns 1901 and 1911 for the above.
Conditional logic
If the first check found a number (returning #N/A
otherwise), we don't need the second. In other words, if the next unbooked room was found after the last one, then we don't need to go round to room 1901 to continue checking. In Excel, this is an IF function:
=IF(NOT(ISNA( [Check 1] )), INDEX($G$1:$Q$1,1, [Check 1]), INDEX($G$1:$Q$1,1, [Check 2]))
However, we are searching for two rooms, which may be in the first and second check, or both be within the first check, or both within the second, or otherwise not found. As MATCH
only finds the first match in the range, we need to do this once, adjust the range based on the result, then do this all again. The overall formula becomes a combination of IF
functions, like this:
=IF(NOT(ISNA( [Check 1] )),
INDEX($G$1:$Q$1,1, [Check 1]) & "-" & IF(NOT(ISNA( [Check 3] )), INDEX($G$1:$Q$1,1, [Check 3]), INDEX($G$1:$Q$1,1, [Check 4])),
INDEX($G$1:$Q$1,1, [Check 2]) & "-" & IF(NOT(ISNA( [Check 3] )), INDEX($G$1:$Q$1,1, [Check 3]), INDEX($G$1:$Q$1,1, [Check 4]))
)
When you combine all of that, the result is the big formula from the beginning.
This actually looks much simpler in VBA code. Here is a guide on how to set this up in Excel. Use the function below:
Public Function choose_turns(last As Range, rooms As Range) As String
Dim row As Integer
Dim rooms_first_column As Integer
Dim rooms_count As Integer
Dim last_room_number As Integer
Dim last_room_column As Integer
Dim count_rooms_found As Integer
Dim start_column As Integer
Dim end_column As Integer
Dim found_column As Integer
Dim target As Range
Dim result As String
'Find which room was cleaned last
last_room_number = Int(Mid(last.Value, InStr(last.Value, "-") + 1))
'Locate that within the range
rooms_first_column = rooms.Column
rooms_count = rooms.Columns.Count
last_room_column = Application.Match(last_room_number, rooms, 0)
'Conditional logic
row = last.row + 1
count_rooms_found = 0
start_column = last_room_column
Do While count_rooms_found < 2
found_column = 0
'Check 1
'Identify target range
start_column = (start_column Mod rooms_count) + 1
end_column = rooms_count
Set target = Range(Cells(row, rooms_first_column - 1 + start_column), Cells(row, rooms_first_column - 1 + end_column))
'Find next unbooked room
If Not IsError(Application.Match(1, target, 0)) Then
found_column = Application.Match(1, target, 0) + start_column - 1
Else
'Check 2
'Identify target range
start_column = 1
end_column = last_room_column
Set target = Range(Cells(row, rooms_first_column - 1 + start_column), Cells(row, rooms_first_column - 1 + end_column))
'Find next unbooked room
If Not IsError(Application.Match(1, target, 0)) Then
found_column = Application.Match(1, target, 0) + start_column - 1
End If
End If
If found_column = 0 Then
Exit Do
End If
If 0 < Len(result) Then
result = result + "-"
End If
'Finding room numbers
result = result + CStr(Application.WorksheetFunction.Index(rooms, 1, found_column))
count_rooms_found = count_rooms_found + 1
Loop
choose_turns = result
End Function
Then, in your worksheet, you can use it as a formula like this (e.g. in D6
):
=choose_turns(D5, $G$1:$Q$1)