Copy tuple with modification - julia

I have a tuple:
my_tup=(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9)
I would like to modify a value in this tuple. Because tuples are immutable, the obvious route doesn't work:
my_tup[:a]=50 #Raises an error, as expected
So to perform the modification, I'd like to copy the tuple and its elements while changing the target element.
My current solution is as follows:
my_tup=(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9)
args = keys(my_tup)
NamedTuple{args}(i!=:a ? getfield(my_tup, i) : 50 for i in args)
But this seems verbose or as though there should already be a function for it in the standard library.
Is there a better way?

You can merge two named tuples:
julia> xs = (a = 1, b = 2, c = 3)
(a = 1, b = 2, c = 3)
julia> ys = merge(xs, (; a = 50))
(a = 50, b = 2, c = 3)

David Varela has the correct answer, "use merge". It is an important technique.
To make this solution more easily available to others, here are some examples.
on creating plain Tuples (unnamed Tuples) and NamedTuples
Both Tuples and NamedTuples are constructed using parentheses:
julia> a_tuple = (1, 2)
(1, 2)
julia> a_namedtuple = (a = 1, b = 2)
(a = 1, b = 2)
If we try construct a Tuple or a NamedTuple with exactly one entry that same way, it does not work; instead the values are assigned named variables directly.
julia> not_a_tuple = ("xyz");
julia> not_a_tuple, typeof(not_a_tuple)
("xyz", String)
julia> not_a_namedtuple = (abc = "xyz");
julia> not_a_namedtuple, typeof(not_a_namedtuple)
("xyz", String)
To construct Tuples and NamedTuples that have a single value, we let Julia know that we are working with [Named]Tuples. An easy way to do this, and one that works for both kinds of Tuples, is to add a comma ',' before the closing parenthesis. For visual emphasis, I separate the commas from the values; it is not necessary to do that in your source code.
julia> a_tuple = (0.5 ,)
(0.5,)
julia> a_namedtuple = (onehalf = 0.5 ,)
(onehalf = 0.5,)
on substituting values in NamedTuples
julia> namedtuple = (a = 1, b = 2, c = 3); # the initial NamedTuple
julia> changes_to_make = (b = 0 ,); # the modifications intended
julia> changed_namedtuple = merge(namedtuple, changes_to_make)
(a = 1, b = 0, c = 3)
julia> namedtuple = (a = 1, b = 2, c = 3); # the initial NamedTuple
julia> changes_to_make = (b = 0, c = 7); # the modifications intended
julia> changed_namedtuple = merge(namedtuple, changes_to_make)
(a = 1, b = 0, c = 7)

julia> my_tup=(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9)
(a = 1, b = 2, c = 3,d = 4, e = 5, f = 6, g = 7, h = 8, i = 9)
julia> new_one = (my_tup..., a=50)
(a = 50, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8, i = 9)

Related

Transform an atomic vector to list (inverse of purrr::simplify())

TLDR:
I need a simple way to transform c(a = 1, a = 3, a = 6) into list(c(a = 1), c(a = 3), c(a = 6)).
Longer version:
I am using the function purrr::accumulate(), where the output of each element is an atomic vector of length greater or equal to one. When the length is one, purrr::accumulate() simplifies the whole output to an atomic vector, instead of a list.
Is there a simple way to undo or avoid this? Unfortunately, as.list() does not give me what I want.
Simple example to illustrate:
purrr::accumulate(2:3, `+`, .init = c(a=1, b=2))
gives me
list(c(a = 1, b = 2), c(a = 3, b = 4), c(a = 6, b = 7))
as expected. However,
purrr::accumulate(2:3, `+`, .init = c(a=1))
gives me
c(a = 1, a = 3, a = 6)
when I instead want
list(c(a = 1), c(a = 3), c(a = 6))
You could try
c(a = 1, a = 3, a = 6) %>% map(~setNames(.x, nm = "a"))
$a
a
1
$a
a
3
$a
a
6
or you can also remove the list names with set_names()
c(a = 1, a = 3, a = 6) %>% map(~setNames(.x, nm = "a")) %>%
set_names("")
[[1]]
a
1
[[2]]
a
3
[[3]]
a
6

Unlist LAST level of a list in R

I have a list of list like ll:
ll <- list(a = list(data.frame(c = 1, d = 2), data.frame(h = 3, j = 4)), b = list(data.frame(c = 5, d = 6), data.frame(h = 7, j = 9)))
I want to unnest/unlist the last level of the structure (the interior list). Note that every list contains the same structure. I want to obtain lj:
lj <- list(a = (data.frame(c = 1, d = 2, h = 3, j = 4)), b = data.frame(c = 5, d = 6, h = 7, j = 9))
I have tried the following code without any success:
lj_not_success <- unlist(ll, recursive = F)
However, this code unlists the FIRST level, not the LAST one.
Any clue?
We may need to cbind the inner list elements instead of unlisting as the expected output is a also a list of data.frames
ll_new <- lapply(ll, function(x) do.call(cbind, x))
-checking
> identical(lj, ll_new)
[1] TRUE

The right way to pass a list of list to a function, with missing and/or irrelevant elements

What is a robust way of passing a list of list of arguments to a function, when not all arguments are used by the function?
Parameters are stored in a named list of named lists
params <- list("1" = list(a = 1, b = 2),
"2" = list(a = 3, b = 4, c = 5), # not all parameters will be used
"3" = list(a = 6, b = 7),
"4" = list(a = 8)) # some parameters will be missing
The function uses some of these parameters, but not necessarily all of them (list "2"). Also it has default values, so I'd like it to work even if only some of its values are passed (list "4").
f <- function(a = 0, b = 0)
print(paste0("a = ", a, ", b = ", b))
I don't know how to pass an excessively populated list:
do.call(f, params[["2"]])
## Error in (function (a = 0, b = 0) : unused argument (c = 5)
I would like to select and pass several lists at once. The expected output is something like what this loop produces:
selected <- c("1", "3")
for (i in selected)
do.call(f, params[[i]])
## [1] "a = 1, b = 2"
## [1] "a = 6, b = 7"
If you can allow optional arguments to your function
f <- function(a = 0, b = 0, ...) print(paste0("a = ", a, ", b = ", b))
for (i in seq_along(params)) {
do.call(f, params[[i]])
}
#[1] "a = 1, b = 2"
#[1] "a = 3, b = 4"
#[1] "a = 6, b = 7"
#[1] "a = 8, b = 0"

Passing in all arguments

Consider the following:
foo <- function(a = 1, b = 2, c = 3, d = 1, e = 2, f = 3, g = 4, h = 1) {
print(h)
bar(a = a, b = b, c = c, d = d, e = e, f = f, g = g)
foobar(c = c, e = e, g = g)
}
bar <- function(a, b, c, d, e, f, g) {
a + b + c + d + e + f * g
}
foobar <- function(c, e, g) {
if (c) {
print(g + e)
}
}
foo(1, 2, 3, 4,5, 6, 7, 8)
I have something similar to the above where I have subfunction calls that require a lot of the variables passed down from the level above it. Is there an easier way of doing lines 3 and 4 of this code, rather than manually listing out each variable each time it calls it?
Well, we can create a helper function to make this easier
call_match_args <- function(fun, data) {
fun <- match.fun(fun)
seeking <- names(formals(fun))
stopifnot(all(seeking %in% names(data)))
do.call(fun, data[seeking], envir=parent.frame())
}
this will take a function and a list and will pass all the values of the list that match the function parameters names as arguments. The do.call function takes care of turning the list into parameters.
Then we can change your foo function to look something like this
foo <- function(a = 1, b = 2, c = 3, d = 1, e = 2, f = 3, g = 4, h = 1) {
vars <- mget(ls())
print(vars$h)
call_match_args(bar, vars)
call_match_args(foobar, vars)
}
foo(1, 2, 3, 4,5, 6, 7, 8)
# [1] 8
# [1] 12
while bar and foobar can stay the same. The first step in the function it to take all the parameter values and put them in a list. Then you access them from that list and can pass that list to the call_match_args helper function.

Why named splats is not working in Julia?

How to make code below to work?
function configure(; options...) println(options) end
configure(a = 1, b = 2, c = 3)
options = (c = 2, b = 3)
configure(options...)
options = (c = 2, b = 3)
configure(a = 1, options...)
Because the semicolon matters here.
configure(;options...)
configure(;a = 1, options...)

Resources