R Solution for Excel Puzzles

[This article was first published on Numbers around us - Medium, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Puzzles no. 379–388

Puzzles

Author: ExcelBI

All files (xlsx with puzzle and R with solution) for each and every puzzle are available on my Github. Enjoy.

Last week I were on my winter holiday and with refreshed mind, it is time to get back to work, to everyday routines, to our puzzles and challenges. I owe you my solutions for challenges for two weeks now. So If you are asking me, if I’m back, look below …

Puzzle #379

In this puzzle we have string with sequence of numbers comma separated. Just a series of numbers but our host doesn’t really like them, because they are not really tidy. To make more order in them we have to get only those parts of string that present only numbers followed by larger one. We are checking if for each number next number is greater, and if not drop the number. Let’s code it.

Loading libraries and data

library(tidyverse)
library(readxl)

input = read_excel("Excel/379 All Elements Larger than Preceding one.xlsx", range = "A1:A10")
test  = read_excel("Excel/379 All Elements Larger than Preceding one.xlsx", range = "B1:B10")

Transformation

check_succeeding <- function(numbers, index) {
  current <- numbers[index]
  succeeding <- numbers[(index + 1):length(numbers)]
  all(succeeding > current)
}

process_string <- function(string) {
  numbers <- str_split(string, ",\\s*")[[1]] %>% 
    as.numeric()

  result <- map_lgl(seq_along(numbers), ~check_succeeding(numbers, .)) %>%
    which() %>%
    map_chr(~ as.character(numbers[.])) %>%
    paste(collapse = ", ")
  
  result = ifelse(result == "", NA_character_, result)
  
  return(result)
}

result = input %>%
  mutate(`Answer Expected` = map_chr(Numbers, ~process_string(.x)))

Validation

identical(result$`Answer Expected`, test$`Answer Expected`)
#> [1] TRUE

Puzzle #380

In this puzzle we need to create matrix made from “x’s” instead of numbers, but only sides and diagonals should be filled with “x’s” and the rest should be empty. And all we have to use for this generations have to be side length. So let’s do it.

Load libraries and data

library(tidyverse)
library(readxl)

test1 = read_excel("Excel/380 Draw NxN Squares.xlsx", range = "A2:H9",   col_names = FALSE) %>%
  as.matrix() %>% {attr(., "dimnames") <- NULL; .}
test2 = read_excel("Excel/380 Draw NxN Squares.xlsx", range = "A11:G17", col_names = FALSE) %>%
  as.matrix() %>% {attr(., "dimnames") <- NULL; .}
test3 = read_excel("Excel/380 Draw NxN Squares.xlsx", range = "A19:E23", col_names = FALSE) %>%
  as.matrix() %>% {attr(., "dimnames") <- NULL; .}
test4 = read_excel("Excel/380 Draw NxN Squares.xlsx", range = "A25:D28", col_names = FALSE) %>%
  as.matrix() %>% {attr(., "dimnames") <- NULL; .}

Transformation

draw_sides_and_diag = function(matrix_size) {
  mat = matrix(NA, nrow = matrix_size, ncol = matrix_size)
  mat[1,] = "x"
  mat[matrix_size,] = "x"
  mat[,1] = "x"
  mat[,matrix_size] = "x"
  diag(mat) = "x"
  diag(mat[,ncol(mat):1]) = "x"

  return(mat)
}

draw_sides_and_diag(8)

     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,] "x"  "x"  "x"  "x"  "x"  "x"  "x"  "x" 
[2,] "x"  "x"  NA   NA   NA   NA   "x"  "x" 
[3,] "x"  NA   "x"  NA   NA   "x"  NA   "x" 
[4,] "x"  NA   NA   "x"  "x"  NA   NA   "x" 
[5,] "x"  NA   NA   "x"  "x"  NA   NA   "x" 
[6,] "x"  NA   "x"  NA   NA   "x"  NA   "x" 
[7,] "x"  "x"  NA   NA   NA   NA   "x"  "x" 
[8,] "x"  "x"  "x"  "x"  "x"  "x"  "x"  "x" 

Validation

all.equal(draw_sides_and_diag(8), test1)
#> [1] TRUE
all.equal(draw_sides_and_diag(7), test2)
#> [1] TRUE
all.equal(draw_sides_and_diag(5), test3)
#> [1] TRUE
all.equal(draw_sides_and_diag(4), test4)
#> [1] TRUE

Puzzle #381

In IPv6 addressing system there are some nice tricks that allows to write it shorter in some cases. For example four zeroes can be written as one etc. Let cite those rules from the task:

IPv6 address is represented as x:x:x:x:x:x:x:x (total 8 x) where x consists of 1 to 4 Hexadecimal digits. Following rules to be followed to shorted an IPv6 address -
1. Leading 0s should be omitted. Hence, 00A6 should be written as A6.
2. Double colons (::) should be used in place of a series of contiguous zeros. For example, IPv6 address CD34:0000:0000:0000:0000:0000:0000:A4 can be written as CD34::A4.
3. Double colons should be used only once in an IP address. Since, we are looking at shortest possible IPv6, hence double colon in this case should be used where more number of series of contiguous 0s are there.
CD34:0000:0000:2AB6:0000:0000:0000:A4 can be written as CD34:0:0:2AB6::A4 not as CD34::2AB6:0:0:0:A4.

And what we have to do? Of course shorten given addresses in all possible way.

Load libraries and data

library(tidyverse)
library(readxl)

input = read_excel("Excel/381 IPv6 Shortening.xlsx", range = "A1:A11")
test  = read_excel("Excel/381 IPv6 Shortening.xlsx", range = "B1:B11")

Transformation

shorten_ipv6 = function(ipv6) {
  blocks = str_split(ipv6, ":")[[1]] %>%
  str_replace( "^0+", "") %>%
  str_replace("^$", "0") 

  zeros = blocks %>% 
    str_detect("^0+$") %>%
    which() %>%
    unlist()  %>%
    data.frame(x = .) %>%
    mutate(group = cumsum(x - lag(x, default = 0) > 1)) %>%
    group_by(group) %>%
    mutate(n = n(),
           min_index = min(x),
           max_index = max(x)) %>%
    ungroup() %>%
    arrange(desc(n), group) %>%
    slice(1)

  first_zero = zeros$min_index
  last_zero = zeros$max_index
  
  block_df = data.frame(blocks = blocks,
                        index = 1:length(blocks),
                        stringsAsFactors = FALSE)
  
  block_df$blocks[block_df$index >= first_zero & block_df$index <= last_zero] = ""
  
  result = block_df$blocks %>%
    str_c(collapse = ":")  %>%
    str_replace(., ":{2,}", "::")
  
  return(result)
}

result = input %>% 
  mutate(short = map_chr(`IPv6 Addresses`, shorten_ipv6))

Validation

identical(result$short, test$`Expected Answer`)
#> [1] TRUE

Puzzle #382

This is one of two puzzles concerning “Kamasutra Cipher” in today’s episode. This kind of cipher is based on randomly mixing set of characters used in cipher, dividing into two equally long groups and then stack it like sandwich. Sandwich -> Kamasutra, your imagination will know what to do with it. 🙂 First puzzle are coding only English alphabet letters. We are not validating anything, because every shuffling is unique.

Load libraries and data

library(tidyverse)
library(readxl)

input = read_excel("Excel/382 Kamasutra Cipher.xlsx", range = "A1:A10")

Transformation

generate_code = function() {
shuffled_alph = sample(letters)
sh_p1 = shuffled_alph[1:(length(shuffled_alph)/2)] 
sh_p2 = shuffled_alph[(length(shuffled_alph)/2 + 1):length(shuffled_alph)]
sh_p1 = setNames(sh_p1, sh_p2)
sh_p2 = setNames(sh_p2, sh_p1)
code = c(sh_p1, sh_p2)
return(code)
}

code = function(string){
  code = generate_code()
  string = tolower(string)
  words = str_split(string, " ")[[1]]
  chars = map(words, str_split, "") %>%
    map(unlist)
  coded_chars = map(chars, function(x) code[x])
  coded_words = map(coded_chars, paste, collapse = "") 
  coded_string = paste(coded_words, collapse = " ")
  return(coded_string)
}

result = input %>%
  mutate(coded = map_chr(`Plain Text`, code))

Puzzle #383

In this puzzle we need to extract numbers from given string and divide them into positive and negative numbers. Looks like piece of cake. Let’s check it.

Load libraries and data

library(tidyverse)
library(readxl)

input = read_excel("Excel/383 Extract Positive and Negative Numbers.xlsx", range = "A2:A10")
test  = read_excel("Excel/383 Extract Positive and Negative Numbers.xlsx", range = "B2:C10")

Transformation

extract = function(input, sign) {
  numbers = input %>%
    str_extract_all(paste0(sign, "(\\d+)")) %>%
    unlist() %>%
    as.numeric() %>%
    abs() %>%
    unique() %>%
    str_c(collapse = ", ") 
  
  if (numbers == "") {
    numbers = NA_character_
  } else {
    numbers = numbers
  }
}

result = input %>%
  mutate(positive = map_chr(Strings, extract, "\\+"),
         negative = map_chr(Strings, extract, "\\-")) 

Validation

identical(result$positive, test$`Positive Numbers`)
# [1] TRUE

identical(result$negative, test$`Negative Numbers`)
# [1] TRUE

Puzzle #384

This puzzle is looking for increasing numbers as well, but different way. We have long numbers and what we need to do is:
- get first digit and,
- check if second digit is greater than first
- if not check if two following are greater (and do it unless we find one)
- if yes slice off first digit and get the number we found above.

It is rather difficult to explain it in simple words. But it is much more difficult to code it. We are using recursive function here.

Load data and libraries

library(tidyverse)
library(readxl)

input = read_excel("Excel/384 Extract Increasing Numbers.xlsx", range = "A1:A12")
test  = read_excel("Excel/384 Extract Increasing Numbers.xlsx", range = "B1:B12")

Transformation

recursive_append <- function(n, p = 0, c = 1, a = 0) {
  if (p + c > nchar(n)) {
    return("")
  } else {
    v <- as.numeric(substr(n, p + 1, p + c))
    if (!is.na(v) && v > a) {
      b <- paste(", ", v, recursive_append(n, p + c, 1, v), sep = "")
    } else {
      b <- recursive_append(n, p, c + 1, a)
    }
    return(b)
  }
}

result = input %>%
  rowwise() %>%
  mutate(R = substring(recursive_append(as.character(Numbers)), 1)) %>%
  mutate(R = str_sub(R, 3, -1))

Validation

identical(result$R, test$`Answer Expected`)
# [1] TRUE

Puzzle #385

Matrices again. And job can be divided into two parts: populate first matrix (harder one) and create larger matrix by transforming and binding matrices (much easier one).
First matrix has size 5x5 in which we have first row from 0 to 4 and in others this row is shifted once at the time. Then we need to make matrix with elements bigger by 5 in each cell, and then combine it to matrix given in puzzle.

Load libraries and data

library(tidyverse)
library(readxl)

test = read_excel("Excel/385 Generate the Grid.xlsx", range = "C3:L12", col_names = FALSE) %>%
  as.matrix() %>%
  {attr(., "dimnames") <- NULL; .}

Transformation

generate = function(n){
  grid_df <- expand.grid(i = 1:n, j = 1:n) %>% 
    mutate(value = (i + j - 2) %% n) %>%
    pull(value)
  
  matrix(grid_df, nrow = n, ncol = n)
}

a = generate(5)

b = a + 5

c = cbind(a,b)
d = cbind(b,a)

result = rbind(c,d) %>% {attr(., "dimnames") <- NULL; .}

Validation

identical(result, test)
# [1] TRUE

Puzzle #386

Today we have to extract all numbers that are comfortly hugged by parentheses from both sides. Numbers that has parenthesis only from one side are not important this time. Let’s find them.

Load libraries and data

library(tidyverse)
library(readxl)

input = read_excel("Excel/386 Extract Numbers in Parentheses.xlsx", range = "A1:A10")
test  = read_excel("Excel/386 Extract Numbers in Parentheses.xlsx", range = "B1:B10")

Transformation

extract = function(x) {
  x = str_extract_all(x, "\\((\\d+)\\)") %>%
    unlist() %>%
    str_remove_all("\\D") %>%
    str_c(collapse = ", ")
  if (x == "") x = NA_character_
  return(x)
}

result = input %>%
  rowwise() %>%
  mutate(result = map_chr(String, extract))

Validation

identical(result$result, test$`Answer Expected`)
# [1] TRUE

Puzzle #387

Now we have to construct some weird kind of calendar. Basing on given date we have to make month chart that has weekdays in normal order but weeks upside down so first days of month are at the bottom of months and last ones at the top.
Weird but not impossible. Let’s do it.

Load libraries and data

library(tidyverse)
library(readxl)

test = read_excel("Excel/387 Fill in the Last Dates.xlsx", range = "B1:H6") %>%
  mutate(across(everything(), as.Date))

date = read_excel("Excel/387 Fill in the Last Dates.xlsx", range = "A1", col_names = FALSE) %>%
  pull()

Transformation

df = data.frame(date = seq(floor_date(date, "month"), 
                           ceiling_date(date, "month") - days(1), 
                           by = "day") %>%
                  as.Date()) %>%
  mutate(week = week(date), 
         wday = wday(date, label = T, abbr = T,  week_start = 1, locale = "US_us")) %>%
  pivot_wider(names_from = wday, values_from = date) %>%
  select(week, Mon, Tue, Wed, Thu, Fri, Sat, Sun) %>%
  arrange(desc(week)) %>%
  select(-week)

Validation

identical(df, test)
# [1] TRUE

Puzzle #388

And as I said before second puzzle about Kamasutra Cipher comes here. This time not only letters, but also digits come to game.

Load libraries and data

library(tidyverse)
library(readxl)

input = read_excel("Excel/388 Kamasutra Cipher_2.xlsx", range = "A1:A10")

Transformation

generate_code = function() {
  shuffled_alph = sample(letters)
  sh_p1 = shuffled_alph[1:(length(shuffled_alph)/2)] 
  sh_p2 = shuffled_alph[(length(shuffled_alph)/2 + 1):length(shuffled_alph)]
  sh_p1 = setNames(sh_p1, sh_p2)
  sh_p2 = setNames(sh_p2, sh_p1)
  code = c(sh_p1, sh_p2)
  
  shuffled_digits = sample(0:9)
  sh_d1 = shuffled_digits[1:(length(shuffled_digits)/2)]
  sh_d2 = shuffled_digits[(length(shuffled_digits)/2 + 1):length(shuffled_digits)]
  sh_d1 = setNames(sh_d1, sh_d2)
  sh_d2 = setNames(sh_d2, sh_d1)
  code = c(code, sh_d1, sh_d2)
  return(code)
}

code = function(string){
  code = generate_code()
  string = tolower(string)
  words = str_split(string, " ")[[1]]
  chars = map(words, str_split, "") %>%
    map(unlist)
  coded_chars = map(chars, function(x) code[x])
  coded_words = map(coded_chars, paste, collapse = "") 
  coded_string = paste(coded_words, collapse = " ")
  return(coded_string)
}

result = input %>%
  mutate(coded = map_chr(`Plain Text`, code))

Feel free to comment, share and contact me with advices, questions and your ideas how to improve anything. Contact me on Linkedin if you wish as well.


R Solution for Excel Puzzles was originally published in Numbers around us on Medium, where people are continuing the conversation by highlighting and responding to this story.

To leave a comment for the author, please follow the link and comment on their blog: Numbers around us - Medium.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)