The Challenge

For a particular time represented by three indistinguishable hands on a clock-face, we are given a cyclic triplet of angles including (but not necessarily in this order) the angle from the 0:00:00 to the first hand arrived at clockwise, the angle to the next hand clockwise from the first, and then the angle from the second to the third hand. 12:30:00 (or 00:30:00) would have a cyclic angle representation of \(15^\circ, 165^\circ, 180^\circ\). Since 06:30:00 also has this same cyclic angle representation (starting with 180, though, and then 15 and 165) it is indistinguishable from 12:30:00.

Now, 03:00:00 has the cyclic representation \(90^\circ, 270^\circ, 0^\circ\), while 09:00:00 has the cyclic representation \(270^\circ, 90^\circ, 0^\circ\), which are different cyclic orientations and therefore, distinguishable.

How many of the 43,200 seconds in a day cannot be deduced?

The Solution

Let’s construct all of the angles for each of the 43,200 seconds.

##  Build the seconds, minutes, and hour vectors, as well as the minute mark
## (or second mark) on the clock. 
seconds <- 1:43200
secMM <- seconds %% 60
minutes <- seconds/60
minuteMM <- minutes %% 60
hours <- seconds/3600
hourMM <- hours*5

## Build the angle matrix. 
angleMat <- matrix(ncol =3)
for(i in 1:43200){
  # Sort so the minute markers are in order from least to greatest. 
  A <- sort(c(hourMM[i], minuteMM[i], secMM[i]))
  ## When there is 1 zero, this hand will not be arrived at until the last
  ## rotation angle. Let's assume it is 60 and place it at the end. 
  if(sum(A==0)==1){
    A <- c(A[2],A[3]-A[2],60-A[3]) * 6
    angleMat <- rbind(angleMat, round(A,10)) ## round for fuzzy equality
    next
  }
  else{
    ## when there are two zeroes, then we spin around to the 0 mark on the 
    ## second rotation and then stay there. 
    if(sum(A==0)==2){
      A <- c(A[3],60-A[3],0)*6
      angleMat <- rbind(angleMat, round(A,10))
      next
    }
    A <- c(A[1],A[2]-A[1],A[3]-A[2])*6
    angleMat <- rbind(angleMat, round(A,10))
  }
}

Now, I need something that determines whether there are duplicates (or triplicates, etc) where if rows are cyclically equivalent than they are duplicates.

## Input a vector, and compute a small matrix of rows that are all cyclically 
## equal to the input vector. Output the one that starts with the min value.
canon <- function(v){
  rots <- rbind(
    v, 
    v[c(2,3,1)],
    v[c(3,1,2)]
  )
  rots[which.min(rots[,1])[1], ]
}

# apply to the matrix
canonMat <- t(apply(angleMat, 1, canon))

How many of the rows in this matrix are duplicates?

dupIndex <- duplicated(canonMat) | duplicated(canonMat, fromLast = TRUE)
# canonMat[dupIndex, ]
sum(dupIndex)
## [1] 108

Thus, there are 108 times that cannot be deduced because their cyclical angle triplet matches another. An interesting time in this list is 12:06:00, which goes with \(3^\circ, 33^\circ, 324^\circ\) which shares the same cyclical pattern with 10:54:00 (ordered \(324^\circ, 3^\circ, 33^\circ\)).

Bonus

For the bonus section, I’ll need to check the above for \(1<H\leq M\leq S\) such that \(H*M*S=43200\). First, a function is created to generally build a matrix for a selection of H, M, and S.

## This function avoids row-wise sorting within the Matrix build function
row_sort <- function(A) {
  mins <- pmin(A[,1], A[,2], A[,3])
  maxs <- pmax(A[,1], A[,2], A[,3])
  meds <- A[,1] + A[,2] + A[,3] - mins - maxs
  cbind(mins, meds, maxs)
}

## seconds is best in the global environment
SECONDS <- 1:43200

# function to build the matrix with a selected H, M, and S. 
MatrixOfAngles <- function(H,M,S, seconds = SECONDS){
  secMM <- seconds %% S
  minutes <- (seconds/S) %% M
  minuteMM <- minutes * S/M
  hours <- seconds/S/M
  hourMM <- hours*S/H

  A <- row_sort(cbind(hourMM, minuteMM, secMM))

  zero_count <- rowSums(A == 0)

  angleMat <- matrix(0, nrow = nrow(A), ncol = 3)

  # exactly one zero
  idx1 <- zero_count == 1
  angleMat[idx1, ] <- cbind(A[idx1, 2],
                            A[idx1, 3] - A[idx1, 2],
                            S - A[idx1, 3]) * 360/S
                        

  # exactly two zeros
  idx2 <- zero_count == 2
  angleMat[idx2, ] <- cbind(A[idx2, 3],
                            S - A[idx2, 3],
                            0) * 360/S
                        
                      

  # no zeros
  idx3 <- zero_count == 0
  angleMat[idx3, ] <- cbind(A[idx3, 1],
                            A[idx3, 2] - A[idx3, 1],
                            A[idx3, 3] - A[idx3, 2]) * 360/S
  # to avoid 1e-14 differences
  return(round(angleMat,10))
}

Next, a function that will a vector (or matrix row) of H, M and S values and output the number of duplicates this selection produces.

checkMat <- function(v){
  checkM <- MatrixOfAngles(v[1],v[2],v[3])
  checkM <- t(apply(checkM, 1, canon))
  dupIdx <- duplicated(checkM) | duplicated(checkM, fromLast = TRUE)
  return(sum(dupIdx))
}

## See if we get 108
checkMat(c(12,60,60))
## [1] 108

Next, a function to take a number (in this case 43,200) and produce a matrix that exhausts all H, M, S combinations for which \(1<H\leq M\leq S\) and \(H*M*S=43,200\).

three_factorizations <- function(n) {
  # must have at least 3 prime factors (with multiplicity)
  pf <- primeFactors(n)  # from numbers package
  if (length(pf) < 3) {
    stop("Number must have at least 3 prime factors (with multiplicity).")
  }
  
  # using the numbers package, we get divisors of n
  divs <- divisors(n)
  divs <- divs[divs > 1]  # exclude 1
  
  results <- list()
  
  for (i in 1:length(divs)) {
    #
    a <- divs[i]
    for (j in i:length(divs)) {
      b <- divs[j]
      if (n %% (a * b) != 0) next
      
      c <- n / (a * b)
      
      if (c < b) next  # enforce a ≤ b ≤ c
      
      if (c > 1) {
        results[[length(results) + 1]] <- c(a, b, c)
      }
    }
  }
  
  do.call(rbind, results)
}

Finally, we build the possible (H,M,S) triplets and cycle through them to find the arrangement with the most duplicates.

PossibleHMS <- three_factorizations(43200)
LargestDupSoFar <- 0
rowWithLargest <- 0
for(r in 1:nrow(PossibleHMS)){
  N <- checkMat(PossibleHMS[r,])
  rowWithLargest <- ifelse(N>LargestDupSoFar, r, rowWithLargest)
  LargestDupSoFar <- ifelse(N>LargestDupSoFar, N, LargestDupSoFar)
}
PossibleHMS[rowWithLargest,]
LargestDupSoFar

When running the code above, it takes a while to produce \(H=30\), \(M=32\), and \(S=45\), where there are 628 duplicate times.