解析以二进制序列编码的日期

Parsing days coded in binary sequence

我继承了一个以我不熟悉的方式安排事情的数据库。我发现了以下内容:

星期一 = 1,星期二 = 2,星期三 = 4,星期四 = 8,星期五 = 16,星期六 = 32,星期日 = 64

很简单。但是,如果事件安排在星期一、星期三和星期五,则该字段显示 21(即 M + W + F)。这看起来很聪明,但我很难弄清楚如何从该系统返回 "English"。给定数字 21,我如何以编程方式确定活动安排在哪几天?

在我的脑海中,我会这样处理: 找到小于或等于我的数字的最大二进制数,然后减去它(= 第一天),然后减去下一个最大的,等等。所以,给定 21,最大的二进制数小于或等于 16(星期五),剩下我 5。下一个最大的是 4,它是星期三,剩下我 1,它是星期一。

这种方法正确吗?如果是这样,我看到自己构建了一个极其复杂的 case_when 开关,或者可能是一个复杂的 for 循环,但我觉得可能有更简单的方法。

我正在混合使用 SQL 服务器(用于提取数据)和 R(用于分析数据),因此我可以在任何一个中执行此操作。但是,此时即使是伪代码也会有所帮助。

有人试图保存 space 并在单个字节中使用位域编码来存储工作日。显然,他们想证明自己很聪明,或者用 CPU 周期来换取存储。

我们可以使用intToBits()函数获取数值并将其转换为位数组。

例如:

intToBits(1)
##  [1] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
## [24] 00 00 00 00 00 00 00 00 00

intToBits(4)
##  [1] 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
## [24] 00 00 00 00 00 00 00 00 00

intToBits(5)
##  [1] 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
## [24] 00 00 00 00 00 00 00 00 00

出于某种原因,Powers That Be™ 选择将最低有效数字放在首位(可能是因为服用了 LSD)。它也有 way 对我们来说太多了,因为我们只需要 7.

所以,我们只需要在编码和解码时重新排列和压缩一些东西:

decode_days <- function(x) {
  days <- c("Sunday", "Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday")
  lapply(x, function(y) {
    rev(days[as.logical(rev(intToBits(y)[1:7]))])
  })
}

encode_days <- function(x) {
  c(
    "sunday" = 64, "saturday" = 32, "friday" = 16, "thursday" = 8,
    "wednesday" = 4, "tuesday" = 2, "monday" = 1
  ) -> days
  sapply(x, function(y) {
    y <- unique(tolower(trimws(y)))
    y <- y[y %in% names(days)]
    sum(days[y])
  })
}

正在解码:

decode_days(c(1,2,4,8,16,32,64,127,21))
## [[1]]
## [1] "Monday"
## 
## [[2]]
## [1] "Tuesday"
## 
## [[3]]
## [1] "Wednesday"
## 
## [[4]]
## [1] "Thursday"
## 
## [[5]]
## [1] "Friday"
## 
## [[6]]
## [1] "Saturday"
## 
## [[7]]
## [1] "Sunday"
## 
## [[8]]
## [1] "Monday"    "Tuesday"   "Wednesday" "Thursday"  "Friday"    "Saturday" 
## [7] "Sunday"   
## 
## [[9]]
## [1] "Monday"    "Wednesday" "Friday"

实际编码:

encode_days(decode_days(c(1,2,4,8,16,32,64,127,21)))
## [1]   1   2   4   8  16  32  64 127  21

编码器可以稍微优化一下,但这是留给 OP 的练习,因为我试图实现 "in order" 以使翻译更加明显。

FWIW table 查找 encoding/decoding(如您所建议)比此方法快得多(仅显示部分解码示例):

list(
  "1" = "Monday",
  "2" = "Tuesday",
  "3" = c("Monday", "Tuesday"),
  "4" = "Wednesday",
  "5" = c("Monday", "Wednesday"),
  "6" = c("Tuesday", "Wednesday"),
  "7" = c("Monday", "Tuesday", "Wedneday"),
  "8" = "Thursday"
  # you can do the rest
) -> decode_lkp

# moved this outside to make it a fair comparison
days_dec <- rev(c("Sunday", "Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday"))

decode_days <- function(x) { # optimized version
  lapply(x, function(y) {
    days_dec[as.logical(intToBits(y)[1:7])]
  })
}

microbenchmark::microbenchmark(
  lookup = unname(decode_lkp[c(1:8)]),
  `ƒ()` = decode_days(1:8)
)
## Unit: microseconds
##    expr    min      lq     mean median     uq      max neval
##  lookup  1.599  1.7635  2.13525  1.843  1.944   25.302   100
##     ƒ() 12.126 12.8310 40.92872 13.084 13.447 2741.986   100

但我认为这将有助于显示 "logic" 你的前辈们的聪明尝试背后的原因,并且编码中有一些防弹措施。

对于"How" w/r/t bits/ints,一个字节是8位,但他们在这里只使用7位,所以我们坚持使用7位。

64 32 16 08 04 02 01

如果我们将除 01 之外的所有位都设置为 0:

64 32 16 08 04 02 01
 0  0  0  0  0  0  1

我们有星期几。如果我们设置 0401 我们

64 32 16 08 04 02 01
 0  0  0  0  1  0  1

我们有这两个。只要有 1,我们就会添加 header #。

在其他语言中,可以使用二元运算符来测试和设置位。这在 R 中有点可能,但对于大多数用例来说这更直接。

一种lookup-ish方式:

library(rlist)  
decode_days_setup<- function(){
  l <- c(1,2,4,8,16,32,64)
  l_name <- c("Monday", "Tuesday" ,"Wednesday", "Thursday","Friday", "Saturday","Sunday")

  c_sum<- list()
  value_list<- list()

  for (i in 1:7){
    c<-combn(l,i)
    c_sum <- list.append(c_sum, colSums(c))
    unlist(apply(c, 2, list), recursive =FALSE) -> t
    value_list<- list.append(value_list, t)
  }

  f_list <<- lapply(unlist(value_list, recursive = FALSE), function(e) as.character(factor(e, level=l, labels =l_name)))
  c_list <<- unlist(c_sum)

}

decode_days<-function(d){
  unlist(f_list[which(c_list==d)])
}

> decode_days(21)
[1] "Monday"    "Wednesday" "Friday"  

hrbrmstr函数方法与hash方法比较:

days_dec <- rev(c("Sunday", "Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday"))

decode_days_2 <- function(x) { # optimized version
  lapply(x, function(y) {
    days_dec[as.logical(intToBits(y)[1:7])]
  })
}



library(hashmap)
f_list_c <- unlist(lapply(f_list, function(e) paste(e, collapse = " ")))

H <- hashmap(c_list, f_list_c)

hash<-function(x){
  H[[x]]
}

decode_days<- function(d){
  f_list[which(c_list==d)]
}
microbenchmark::microbenchmark(
  lookup_list = lapply(1:100, decode_days),
  lookup_hash = lapply(1:100, hash),
  `ƒ()` = lapply(1:100, decode_days_2)
)

Unit: microseconds
        expr      min        lq      mean    median        uq      max neval
 lookup_list  136.214  146.9980  163.9146  158.0440  165.3305  336.688   100
 lookup_hash 1236.040 1304.5370 1386.7976 1373.1710 1444.3965 1900.020   100
         ƒ()  267.834  289.7065  353.9536  313.6065  343.5070 3594.135   100

令人惊讶的是哈希方法慢了一个数量级。我认为我可能没有正确使用 hashmap 函数。