Excel,根据布尔值计算列表中下一个值的条件函数

Excel, conditional function to calculate next value within a list based on boolean

抱歉,如果标题不够清楚。我没有找到一种很好的方式来表达我在这里想要实现的目标。我是 excel 新手,所以不知道从哪里开始寻找我正在寻找的逻辑。

我有一个电子表格,我想用它来安排学生走廊的清洁工作。问题是房间并不总是有人住,所以它需要能够反应下一个房间是否有人,如果没有,select 列表中的下一个。

在这张图片中我们可以看到第 22 周是如何在 1902 年和 1903 年打扫的,下周轮到 [1901,1902,1903,1904,1905,1906,1907,1908,1909,1910] 中的下两个房间,1911],只要他们被预订(右侧列中的值 1)。 1904 和 1905 已预订(绿色)所以下周将轮到他们。

如果我们继续相同的逻辑,下一轮将是 1906 和 1907,下周(第 25 周)应该是 1908 和 1909,问题是 1908 房间现在是空的,所以它不应该被算作符合条件的房间,而是选择1909&1910作为下一个打扫房间

如何使用 excel 电子表格实现此逻辑?

这是我的电子表格副本的 link,因此您可以看到我正在努力实现的目标,并在需要时使用原始数据。

方法

  1. 确定上次清洁的房间
  2. 创建下一个已占用房间的列表
  3. 确定接下来要清洁的两个已占用房间
  4. 预订少于两个房间的处理情况

第 1 步:确定上次清洁的房间

我创建了一个名为“设置”的新选项卡,用户可以在其中从下拉列表中select 最后一个已打扫过的房间。单元格 $C$3 可通过范围名称 setup_lastcleaned_room.

访问

主选项卡上的单元格 $F$2 引用此 =setup_lastcleaned_room

步骤 02:创建下一个已占用房间的列表

U栏(黄色区域)指的是最后打扫过的房间,由上一行F栏确定。我们在步骤 04 中研究这个公式。

V(橙色区域)确定“最后清理的房间”在房间列表 =MATCH(U3,list_rooms,0)[=143 中的位置=] 是“=Schedule!$I$1:$S$1”的范围名称(选项卡的名称是“Schedule”)。

W 到 AG(浅绿色区域)利用 OFFSET-公式列出接下来必须清洁的房间,如果他们被占用。

=IF($V3+W<=11,IF(OFFSET($H,ROW()-1,$V3+W)=1,OFFSET($H,0,$V3+W),""),IF(OFFSET($H,ROW()-1,$V3+W-11)=1,OFFSET($H,0,$V3+W-11),"")).

看起来很复杂?让我们把它分成几部分:

=IF(last_room_on_list_not_reached, handle_end_of_list, handle_beginning_of_list).

  • last_room_on_list_not_reached = $V3+W<=11:我们检查“最后打扫过的房间”加上“第x个房间”会超出我们的房间列表。

  • handle_end_of_list=IF(OFFSET($H,ROW()-1,$V3+W)=1,OFFSET($H,0,$V3+W),"")分别为:IF(is_room_booked,show_room_name,"")

  • is_room_booked = OFFSET($H,ROW()-1,$V3+W)=1:我们移动anchor单元格($H $1) 由当前行数减一(锚点的行号)和由“最后一个房间清洁的位置”+“第 x 个房间”确定的列数。接下来,我们检查此房间是否已预订(单元格内容 = 1)。

  • show_room_name = OFFSET($H,0,$V3+W):这次我们将 anchor 单元格移动仅用于获取房间名称的列数(见上文)(与我们的 anchor 在同一行)。

  • handle_beginning_of_list:类似于“handle_end_of_list”,但这次列的确定略有不同。

AH(深绿色区域)将所有房间名称连接在一起,以实现以下房间名称的字符串:=W3&X3&Y3&Z3&AA3&AB3&AC3&AD3&AE3&AF3&AG3&"NONENONE"。我们还在字符串中添加了两个占位符 ("NONE") 以获得结果,即使预订的房间少于两个也是如此。 请注意此解决方案考虑到所有房间名称(和占位符)都由固定数量的字符(四个)组成。如果不是这种情况,我们将不得不包括一个分隔符,并且下面的公式会稍微复杂一些。

第 3 步:确定接下来要清洁的两个已占用房间。

所有的四字房间名都按要求的顺序排列后,就很容易确定接下来需要打扫的两个房间了。

E(浅蓝色区域)和F(深蓝色区域)检索第一个resp。本周应该打扫的第二个房间:=MID($AH3,1,4) resp。 =MID($AH3,5,4).

D(紫色区域)用破折号连接两个房间以重新创建提问者的样本:=E3&"-"&F3.

第 4 步:预订少于两个房间的情况处理

每周,我们都会确定最后一个打扫过的房间。但是,如果 none 或仅预订了一个怎么办?我们无法确定有效的房间,因此不知道要继续使用哪个房间。因此,我们检查上面一排已经打扫过的第二个房间,如果它是“none”,我们寻找第一个房间。如果它也是“none”,我们将使用在 setup_lastcleaned_room 中定义的房间: =IF(F2="NONE",IF(E2="NONE",setup_lastcleaned_room,VALUE(E2)),VALUE(F2))注意事项:我们使用“VALUE”,因为房间名称是数字,但在连接时变成了字符串。

简答

我很确定是这样的:

=IF(NOT(ISNA(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-1)), INDEX($G:$Q,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-1) & "-" & IF(NOT(ISNA(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-1,COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-1,COLUMNS($G:$Q)))), INDEX($G:$Q,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-1,COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-1,COLUMNS($G:$Q))), INDEX($G:$Q,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-1,COLUMNS($G:$Q))+1-2)),0))), INDEX($G:$Q,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-2)),0)) & "-" & IF(NOT(ISNA(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-2)),0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-2)),0),COLUMNS($G:$Q)))), INDEX($G:$Q,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-2)),0),COLUMNS($G:$Q))+1)):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q))),0)+MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-2)),0),COLUMNS($G:$Q))), INDEX($G:$Q,1,MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(1,INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q))):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q) + MOD(MATCH(INT(RIGHT(D5, LEN(D5)-FIND("-", D5))),$G:$Q,0),COLUMNS($G:$Q))+1-2)),0),COLUMNS($G:$Q))+1-2)),0))))

将其粘贴到已经有转弯的行下方,例如D6(第 27 周轮到)。此公式使用上一回合和房间名称范围 ($G:$Q) 来确定下一回合应该是什么。

长答案

等等,这是什么怪物!?这也是我见过的最令人生畏的公式!

好吧,里面有很多重复,所以它是独立工作的。它还做了很多繁重的工作,例如灵活地考虑可能有多少个房间,当前行是什么以及任何正在预订的房间。可能有更多的逻辑来处理边缘情况,例如,如果所有房间都被预订了怎么办? (目前是returns#N/A),但这解决了根本问题。

将其分解为以下步骤:

查找最后打扫过的房间

以单元格D6为起点,最后一个要打扫的房间是1910。所以,我们要在单元格D5中找到这个值:

=INT(RIGHT(D5, LEN(D5)-FIND("-", D5)))

这只是从 D5 中获取文本并提取最后一个数字。

范围内定位

范围 $G:$Q 是第一行指定房间号的单元格列表($ 表示这是固定的,因此如果将此公式向下拖动,例如,范围仍将引用 G1:Q1 而不是它下面的任何内容)。

此范围的第 1 列 (G) 是电子表格中的第 7 列。这个(7)可以用下面的公式求得:

=COLUMN($G:$Q)

范围 (11) 中的列数是:

=COLUMNS($G:$Q)

要找到范围内最后打扫过的房间,我们使用 MATCHMatch_type = 0:

MATCH finds the first value that is exactly equal to lookup_value.

=MATCH([最后的房间号],$G$1:$Q$1,0)

=MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0)

此 returns 匹配单元格的相对列号,从 1 到 11(1910 在第 10 列)。

确定目标范围

我们现在要开始查看之后的下一专栏。我们可以简单地加 1,这很好用,除非最后一个要打扫的房间是 1911。在那种情况下,我们不能只加 1,因为没有第 12 个房间。在那里,我们想回到第一个房间。为此,我们首先使用 MOD 并除以列数:

=MOD([相对列号],$G$1:$Q$1,0),[列数])+1

=MOD( MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) ) +1

由此返回的数字是开始的列。所以,如果最后一个房间是 1910,那么结果就是 11。结束的列是最后一列,11(列数)。

但是,如果在这个范围内没有找到未预订的房间,那么我们就得从第一个到开始前的那个再绕一遍。这意味着我们总是需要做两次检查来维护房间的顺序。

在我们的示例中:

Check Start End Range
1 11 11 Q6:Q6
2 1 10 G6:P6

作为伪公式:

Check Start End Range
1 MOD( [Relative column number] ,$G:$Q,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] ))

如公式:

Check Start End Range
1 =MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) ) +1 =COLUMNS($G:$Q) =INDIRECT(ADDRESS(ROW(), COLUMN($G:$Q)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) ) +1 )):INDIRECT(ADDRESS(ROW(), COLUMN($G:$Q)-1 + COLUMNS($G:$Q) ))
2 1 =MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) ) =INDIRECT(ADDRESS(ROW(), COLUMN($G:$Q) )):INDIRECT(ADDRESS(ROW(), COLUMN($G:$Q)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) ) ))

需要做一些公式杂技来将其描述为 Excel 的范围。这些范围指定当前行,并给出绝对列引用(请记住,范围的第 1 列是电子表格的第 7 列,因此我们需要添加 6,或 =COLUMN($G:$Q) -1 到这些)

查找下一个未预订的房间

现在我们可以将其与 MATCH 结合使用,在每种情况下查找下一个未预订的房间:

=MATCH(1, [范围] ,0) + [开始] -1

=MATCH(1, INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) ) +1 )):INDIRECT(ADDRESS(ROW(),COLUMN($G:$Q)-1 + COLUMNS($G:$Q) )) ,0)+ MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) )

(Returns11)

=匹配(1, [范围] ,0)

=MATCH(1, INDIRECT(ADDRESS(ROW(), COLUMN($G:$Q) )):INDIRECT(ADDRESS(ROW(), COLUMN($G:$Q)-1 + MOD(MATCH( INT(RIGHT(D5, LEN(D5)-FIND("-", D5))) ,$G:$Q,0), COLUMNS($G:$Q) ) )) ,0)

(这稍微简单一些,因为开始总是 1)

(Returns 1)

这意味着在第 11 和第 1 列中找到了 1 的值。

查找房间号

要从这些列号中找到实际房间号,我们使用 INDEX:

=INDEX($G$1:$Q$1,1, [匹配列号]) Returns 以上为 1901 和 1911。

条件逻辑

如果第一次检查找到一个数字(否则返回 #N/A),我们不需要第二次。换句话说,如果在上一个房间之后找到了下一个未预订的房间,那么我们就不需要绕到1901房间继续检查了。在 Excel 中,这是一个 IF 函数:

=IF(NOT(ISNA( [Check 1] )), INDEX($G$1:$Q$1,1, [Check 1]), INDEX($G$1:$Q $1,1, [检查 2]))

但是,我们正在搜索两个房间,可能在第一次和第二次检查中,或者都在第一次检查中,或者都在第二次检查中,或者没有找到。由于 MATCH 只找到范围内的第一个匹配,我们需要做一次,根据结果调整范围,然后再做一遍。整个公式变成了IF个函数的组合,像这样:

=IF(NOT(ISNA( [检查 1] )),

INDEX($G$1:$Q$1,1, [检查 1]) & "-" & IF(NOT(ISNA( [检查 3] )), INDEX($G$1 :$Q$1,1, [检查 3]), INDEX($G$1:$Q$1,1, [检查 4])),

INDEX($G$1:$Q$1,1, [Check 2]) & "-" & IF(NOT(ISNA( [Check 3] )), INDEX($G$1 :$Q$1,1, [检查 3]), INDEX($G$1:$Q$1,1, [检查 4]))

)

将所有这些结合起来,结果就是从一开始就形成的大公式。

备选答案

这实际上在 VBA 代码中看起来简单得多。 Here 是关于如何在 Excel 中进行设置的指南。使用以下功能:

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

然后,在您的工作表中,您可以将其用作这样的公式(例如在 D6 中):

=choose_turns(D5, $G:$Q)

如果您有支持动态数组的 Excel 版本,请考虑(在 D2 中并向下复制)

=LET(Rooms,$G:$Q,
Use,$G2:$Q2,
Used,FILTER(Rooms,Use=1),
Avail,COUNT(Used),
Cols,SEQUENCE(1,Avail*2, 1, 1),
Roster,INDEX(Used, 1, IF(Cols<=Avail, Cols, Cols-Avail)),
Prev,IF(D1="Turn", INDEX(Used, 1, Avail), --MID(D1,6,4)),
First,INDEX(Roster, 1, XMATCH(Prev, Roster, -1)+1),
Second,INDEX(Roster, 1, XMATCH(First, Roster, 0)+1),
First&"-"&Second)

说明

  • Rooms - 范围参考房间名称
  • Occupancy - 相关周的房间入住率范围
  • Used - 已过滤的本周已入住房间列表
  • Avail - 已入住房间数
  • Cols - 序列 1Avail * 2. Used to index the Used` 列表
  • Roster - 使用过的房间的数组,重复(所以我们不必担心包装)
  • Prev - 上一行结果中的第二个房间
  • First - 第一个结果。从 Prev 房间
  • 进入花名册的索引
  • Second - 第二个结果。从 First 房间
  • 进入花名册的索引

注意:如果任何一周只有 01 个房间被占用,这将 return 错误。这是否有可能,如果有,您的预期结果是什么?

演示