Sine Wave (plotting with digits)
Excel BI’s Excel Challenge #305 — solved in R
Defining the Puzzle
Today task from ExcelBI which is under this link has another approach. We are not manipulate numbers or string to check their properties, but we are painting with them. That is why I will not upload exercise file today. Here it is:
Print Sine Wave for Amplitudes given in B2
Example for Amplitudes 2, 3 and 4 are given.
Always 5 columns only need to be filled in.
And it looks like this when ready:
So let’s start it in R. Our pictures (sine waves) will be printed in console.
library(tibble)
library(purrr)
library(data.table)
Approach 1: Tidyverse with purrr
generate_wave <- function(amp) {
n_rows <- 2 * amp — 1
generate_palindrome <- function(base) {
sequence <- base:1
number <- c(sequence, rev(sequence[-length(sequence)]))
return(paste0(number, collapse = “”))
}
single_column <- rep(NA, n_rows)
seq = c(1:amp, (amp-1):1)
table = tibble(row = seq) %>%
mutate(row_number = row_number(),
col1 = map_chr(row, ~generate_palindrome(.x)),
col2 = col1,
col3 = col1,
col4 = col1,
col5 = col1) %>%
mutate_at(vars(col1, col3, col5),
~ifelse(row_number > amp, “ “, .)) %>%
mutate_at(vars(col2, col4),
~ifelse(row_number < amp, “ “, .)) %>%
mutate_at(vars(col1, col2, col3, col4, col5),
~str_pad(., max(nchar(.), na.rm = TRUE), side = “both”)) %>%
select(-c(row_number, row))
return(table)
}
x <- generate_wave(6)
print(x, n = Inf)
Approach 2: Base R
generate_wave_base <- function(amp) {
n_rows <- 2 * amp — 1
generate_palindrome <- function(base) {
sequence <- base:1
number <- c(sequence, rev(sequence[-length(sequence)]))
return(paste0(number, collapse = “”))
}
seq <- c(1:amp, (amp-1):1)
table <- data.frame(row = seq)
table$row_number <- seq_along(table$row)
table$col1 <- sapply(table$row, generate_palindrome)
table$col2 <- table$col1
table$col3 <- table$col1
table$col4 <- table$col1
table$col5 <- table$col1
condition_space <- table$row_number > amp
condition_empty <- table$row_number < amp
table$col1[condition_space] <- “ “
table$col3[condition_space] <- “ “
table$col5[condition_space] <- “ “
table$col2[condition_empty] <- “ “
table$col4[condition_empty] <- “ “
max_length <- max(nchar(c(table$col1, table$col2, table$col3, table$col4, table$col5)), na.rm = TRUE)
pad_string <- function(string, max_length) {
current_length <- nchar(string)
side_length <- (max_length — current_length) %/% 2
return(paste0(paste(rep(“ “, side_length), collapse = “”), string))
}
table$col1 <- sapply(table$col1, pad_string, max_length = max_length)
table$col2 <- sapply(table$col2, pad_string, max_length = max_length)
table$col3 <- sapply(table$col3, pad_string, max_length = max_length)
table$col4 <- sapply(table$col4, pad_string, max_length = max_length)
table$col5 <- sapply(table$col5, pad_string, max_length = max_length)
table$row_number <- NULL
table$row <- NULL
table = as_tibble(table)
return(table)
}
x_base = generate_wave_base(6)
print(x_base)
Approach 3: Data.table
generate_wave_dt <- function(amp) {
n_rows <- 2 * amp — 1
generate_palindrome <- function(base) {
sequence <- base:1
number <- c(sequence, rev(sequence[-length(sequence)]))
return(paste0(number, collapse = “”))
}
seq <- c(1:amp, (amp-1):1)
table <- data.table(row = seq)
table[, row_number := .I]
table[, col1 := sapply(row, generate_palindrome)]
table[, `:=` (col2 = col1, col3 = col1, col4 = col1, col5 = col1)]
table[row_number > amp, `:=` (col1 = “ “, col3 = “ “, col5 = “ “)]
table[row_number < amp, `:=` (col2 = “ “, col4 = “ “)]
max_length <- max(nchar(c(table$col1, table$col2, table$col3, table$col4, table$col5)), na.rm = TRUE
pad_string <- function(strings, max_length) {
padded_strings <- vapply(strings, function(string) {
current_length <- nchar(string)
total_padding <- max_length — current_length
if (total_padding <= 0) return(string)
side_padding <- floor(total_padding / 2)
padded_string <- paste0(
strrep(“ “, side_padding),
string,
strrep(“ “, total_padding — side_padding)
)
return(padded_string)
}, FUN.VALUE = character(1))
return(padded_strings)
}
table[, (c(“col1”, “col2”, “col3”, “col4”, “col5”)) := lapply(.SD, function(x) pad_string(x, max_length)), .SDcols = c(“col1”, “col2”, “col3”, “col4”, “col5”)]
table[, c(“row_number”, “row”) := NULL]
return(table)
}
x_dt = generate_wave_dt(6)
print(x_dt)
Validation
As we don’t have to validate it another way than looking at them and confirm that it looks like sine wave, I can say, it works well. Let check how it will look with not 6 but 9 digits.
However I make little bechmark as well to check which is best in time consumption:
library(microbenchmark)
microbenchmark(generate_wave(6), generate_wave_base(6), generate_wave_dt(6), times
= 100)
And what we see? Easiest to do are ussually not fast enough. Fastest one is base R approach, but it has very important cause: base approach is basing on simple, but computationally powerful data structure: matrix.
Do not hesitate to show your approaches in R or even Python. Comment, share and come back for next puzzles.