Sine Wave (plotting with digits)

Numbers around us
Numbers around us
Published in
4 min readOct 17, 2023

--

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.

--

--

Numbers around us
Numbers around us

Self developed analyst. BI Developer, R programmer. Delivers what you need, not what you asked for.