在位掩码中设置下一个工作日的算法

Posted

技术标签:

【中文标题】在位掩码中设置下一个工作日的算法【英文标题】:An algorithm to get the next weekday set in a bitmask 【发布时间】:2008-12-08 23:50:12 【问题描述】:

我有一个小问题 - 给定工作日的位掩码(例如,Sunday = 0x01, Monday = 0x02, Tuesday = 0x04 等...)和今天(以Sunday = 1, Monday = 2, Tuesday = 3 等的形式) - 什么是最优雅的从今天开始找出第二天的方法,这是在位掩码中设置的?优雅的意思是,有没有办法在没有 if/switch/etc... 的情况下做到这一点,因为我知道非优雅的方式?

编辑我可能应该提到(为了更清楚地说明)持有位掩码的变量可以设置好几天,例如(大致):

uDay = Sunday | Monday;
today = Tuesday;

我需要得到“星期天”

【问题讨论】:

顺便说一句,“下一个”是指“今天或更晚”,还是“严格迟于今天”? (例如“uDay = Sunday | Tuesday; today = Tuesday;”,它应该说什么?) 错过了这条评论 :) next = 今天或更晚 【参考方案1】:
int getNextDay(int days_mask, int today) 
   if (!days_mask) return -1; // no days set
   days_mask |= days_mask << 7; // duplicate days into next week
   mask = 1 << (today % 7); // keep track of the day
   while (!(mask & days_mask)) 
      mask <<= 1;
      ++today;
   
   return today % 7;

所以这只是一个 if 开头和 while 循环。怎么样?

编辑:我刚刚意识到有一种退化的情况,如果今天使用超过>=14(或大于设置的最高位),while 循环将变为无限。第 4 行的 (today % 7) 修复了这种情况。

如果我可能会(轻松地)抱怨另一个版本获得复选标记,我的版本只有 2 个模数调用,而选中的解决方案将有最少 1 个和最多 6 个模数调用。

另外,如果设置了今天,函数是否返回“今天”的评论很有趣。如果函数今天不应该返回,除非今天是集合中唯一的一天,则需要您在我的解决方案的第 3 行预增加今天。

【讨论】:

复制位然后继续的方式非常聪明:) 我打勾是因为他首先提出了解决方案 - 抱歉 :) 当我使用编译器时,我会检查所有代码,然后再决定最终标记。感谢您为此付出的时间和精力。 嗯,实际上我认为它不起作用(或者我搞砸了)-例如,如果 days_mask = 0x14 和 today = 3,则函数返回 4,而我希望它返回 3 (或 5 个)。 4 是从 0 开始的一周中的第 5 天。 0x14 设置了基于 0 的位 2 和 4。重读要求,我发现我的回报需要 +1:return today%7 + 1。 是的,就我而言,星期日是从 1 开始的。【参考方案2】:

您根本不需要任何额外的变量。最简单的想法——从“明天”开始,查看连续的日子,直到找到面具中的一天——也是最优雅的实现方式。做到这一点的诀窍是将日子想象为 Sunday=0、Monday=1 等等(仅在此函数内部)。那么,“今天”实际上是t-1(其中t是函数的输入,所以它从1到7),而“明天”是(t-1+1)%7,即t%7

这很简单,并且已经针对 litb 的代码进行了详尽的测试,以确保 :-)

int getNextDay(int m, int t) 
  if((m&127)==0) return t;          //If no day is set, return today
  t=t%7;                            //Start with tomorrow
  while((m&(1<<t))==0) t = (t+1)%7; //Try successive days
  return t+1;                       //Change back to Sunday=1, etc.

编辑:如果您希望“下一个”表示“今天或以后”,则应将“t=t%7”行更改为t=t-1--t

【讨论】:

【参考方案3】:

我是这样理解你的问题的:

// returns t (today) if no weekday is set in the mask.
int getNextDay(int m, int t) 
    int i, idx;
    for(i = 0, idx=t%7; i<7 && !((1<<idx)&m); i++, idx=(idx+1)%7)
        /* body empty */ ;
    return (i == 7) ? t : (idx + 1);


// getNextDay(8|2, 2) == 4, getNextDay(64, 2) == 7
// getNextDay(128, 2) == 2

【讨论】:

谢谢 - 我需要计算一下它的作用,然后看看它是否能按照我需要的方式工作:) 我现在只是在纸上做这个:) 但是 getNextDay(128, 2) 不应该返回 5 吗? 128 是 10000000 ,也就是说,对于所有工作日,我们都有零位。那就不可能有第二天了。那我今天就回来【参考方案4】:

我的意思是优雅,有没有办法在没有 if/switch/etc 的情况下做到这一点...

你打赌!这是否意味着任何通常意义上的“优雅”,嗯:

static unsigned next_day_set (unsigned today, unsigned set) 
  unsigned arev = bitreverse (highest_bit_set (bitreverse ((set << 7) | set)
                                               & (bitreverse (today) - 1)));
  return ((arev >> 7) | arev) & 0x7f;

假设您有“优雅”的功能来反转单词中的位并找到最左边的位集——请参阅Hacker's Delight。如果你以相反的顺序表示工作日位,假设我没有搞砸,它会更简单,实际上甚至有点优雅:

enum 
  Sunday  = 1 << 6
  Monday  = 1 << 5
  Tuesday = 1 << 4,
  /* etc */
  Saturday = 1 << 0
;

static unsigned next_day_set (unsigned today, unsigned set) 
  unsigned a = highest_bit_set (((set << 7) | set) & ((today << 7) - 1));
  return ((a >> 7) | a) & 0x7f;

【讨论】:

以上是关于在位掩码中设置下一个工作日的算法的主要内容,如果未能解决你的问题,请参考以下文章

在 FSCalendar 中设置下一个上一个按钮

如何在 Java 循环链表中设置下一个值?

星号掩码算法

在位图上绘制对角线文本

有效地找到第 k 个集合位在位集中的位置

引导输入掩码无法工作,控制台中未显示错误