Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ordered and unordered C++ maps are converted to R lists #437

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cpp11test/DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ Suggests:
xml2
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.1
RoxygenNote: 7.3.2
12 changes: 12 additions & 0 deletions cpp11test/R/cpp11.R
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ cpp11_insert_ <- function(num_sxp) {
.Call(`_cpp11test_cpp11_insert_`, num_sxp)
}

ordered_map_to_list_ <- function(x) {
.Call(`_cpp11test_ordered_map_to_list_`, x)
}

ordered_map_to_list_2_ <- function(x) {
.Call(`_cpp11test_ordered_map_to_list_2_`, x)
}

unordered_map_to_list_ <- function(x) {
.Call(`_cpp11test_unordered_map_to_list_`, x)
}

gibbs_cpp <- function(N, thin) {
.Call(`_cpp11test_gibbs_cpp`, N, thin)
}
Expand Down
24 changes: 24 additions & 0 deletions cpp11test/src/cpp11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,27 @@ extern "C" SEXP _cpp11test_cpp11_insert_(SEXP num_sxp) {
return cpp11::as_sexp(cpp11_insert_(cpp11::as_cpp<cpp11::decay_t<SEXP>>(num_sxp)));
END_CPP11
}
// map.cpp
SEXP ordered_map_to_list_(cpp11::doubles x);
extern "C" SEXP _cpp11test_ordered_map_to_list_(SEXP x) {
BEGIN_CPP11
return cpp11::as_sexp(ordered_map_to_list_(cpp11::as_cpp<cpp11::decay_t<cpp11::doubles>>(x)));
END_CPP11
}
// map.cpp
SEXP ordered_map_to_list_2_(cpp11::doubles x);
extern "C" SEXP _cpp11test_ordered_map_to_list_2_(SEXP x) {
BEGIN_CPP11
return cpp11::as_sexp(ordered_map_to_list_2_(cpp11::as_cpp<cpp11::decay_t<cpp11::doubles>>(x)));
END_CPP11
}
// map.cpp
SEXP unordered_map_to_list_(cpp11::doubles x);
extern "C" SEXP _cpp11test_unordered_map_to_list_(SEXP x) {
BEGIN_CPP11
return cpp11::as_sexp(unordered_map_to_list_(cpp11::as_cpp<cpp11::decay_t<cpp11::doubles>>(x)));
END_CPP11
}
// matrix.cpp
SEXP gibbs_cpp(int N, int thin);
extern "C" SEXP _cpp11test_gibbs_cpp(SEXP N, SEXP thin) {
Expand Down Expand Up @@ -500,6 +521,8 @@ static const R_CallMethodDef CallEntries[] = {
{"_cpp11test_my_warning_n1", (DL_FUNC) &_cpp11test_my_warning_n1, 1},
{"_cpp11test_my_warning_n1fmt", (DL_FUNC) &_cpp11test_my_warning_n1fmt, 1},
{"_cpp11test_my_warning_n2fmt", (DL_FUNC) &_cpp11test_my_warning_n2fmt, 2},
{"_cpp11test_ordered_map_to_list_", (DL_FUNC) &_cpp11test_ordered_map_to_list_, 1},
{"_cpp11test_ordered_map_to_list_2_", (DL_FUNC) &_cpp11test_ordered_map_to_list_2_, 1},
{"_cpp11test_protect_many_", (DL_FUNC) &_cpp11test_protect_many_, 1},
{"_cpp11test_protect_many_cpp11_", (DL_FUNC) &_cpp11test_protect_many_cpp11_, 1},
{"_cpp11test_protect_many_preserve_", (DL_FUNC) &_cpp11test_protect_many_preserve_, 1},
Expand Down Expand Up @@ -533,6 +556,7 @@ static const R_CallMethodDef CallEntries[] = {
{"_cpp11test_sum_int_foreach_", (DL_FUNC) &_cpp11test_sum_int_foreach_, 1},
{"_cpp11test_test_destruction_inner", (DL_FUNC) &_cpp11test_test_destruction_inner, 0},
{"_cpp11test_test_destruction_outer", (DL_FUNC) &_cpp11test_test_destruction_outer, 0},
{"_cpp11test_unordered_map_to_list_", (DL_FUNC) &_cpp11test_unordered_map_to_list_, 1},
{"_cpp11test_upper_bound", (DL_FUNC) &_cpp11test_upper_bound, 2},
{"run_testthat_tests", (DL_FUNC) &run_testthat_tests, 1},
{NULL, NULL, 0}
Expand Down
29 changes: 29 additions & 0 deletions cpp11test/src/map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "cpp11/as.hpp"
#include "cpp11/doubles.hpp"

[[cpp11::register]] SEXP ordered_map_to_list_(cpp11::doubles x) {
std::map<double, int> counts;
int n = x.size();
for (int i = 0; i < n; i++) {
counts[x[i]]++;
}
return cpp11::as_sexp(counts);
}

[[cpp11::register]] SEXP ordered_map_to_list_2_(cpp11::doubles x) {
std::map<double, double> counts;
double n = x.size();
for (int i = 0; i < n; i++) {
counts[x[i]] += 1.0;
}
return cpp11::as_sexp(counts);
}

[[cpp11::register]] SEXP unordered_map_to_list_(cpp11::doubles x) {
std::unordered_map<double, int> counts;
int n = x.size();
for (int i = 0; i < n; i++) {
counts[x[i]]++;
}
return cpp11::as_sexp(counts);
}
21 changes: 21 additions & 0 deletions cpp11test/tests/testthat/test-map-to-list.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
test_that("ordered and unordered C++ maps are converted to R lists", {
set.seed(42L)
x <- rnorm(10L)
xprime <- c(x, x[1])

om <- ordered_map_to_list_(x)
expect_type(om, "list")

om2 <- ordered_map_to_list_2_(x)
expect_equal(om, om2)

om_doubles <- as.double(names(om))
expect_equal(om_doubles, sort(om_doubles))

omprime <- ordered_map_to_list_(xprime)
expect_equal(unlist(unique(omprime)), 1:2)

um <- unordered_map_to_list_(xprime)
expect_type(um, "list")
expect_equal(unlist(unique(um)), 1:2)
})
55 changes: 49 additions & 6 deletions inst/include/cpp11/as.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

#include <cmath> // for modf
#include <initializer_list> // for initializer_list
#include <map> // for std::map
#include <memory> // for std::shared_ptr, std::weak_ptr, std::unique_ptr
#include <stdexcept>
#include <string> // for string, basic_string
#include <type_traits> // for decay, enable_if, is_same, is_convertible
#include <string> // for string, basic_string
#include <type_traits> // for decay, enable_if, is_same, is_convertible
#include <unordered_map> // for std::unordered_map
#include <vector> // for std::vector

#include "cpp11/R.hpp" // for SEXP, SEXPREC, Rf_xlength, R_xlen_t
#include "cpp11/protect.hpp" // for stop, protect, safe, protect::function
Expand Down Expand Up @@ -243,7 +246,7 @@ enable_if_integral<T, SEXP> as_sexp(const Container& from) {
}

inline SEXP as_sexp(std::initializer_list<int> from) {
return as_sexp<std::initializer_list<int>>(from);
return as_sexp(std::vector<int>(from));
}

template <typename Container, typename T = typename Container::value_type,
Expand All @@ -261,7 +264,7 @@ enable_if_floating_point<T, SEXP> as_sexp(const Container& from) {
}

inline SEXP as_sexp(std::initializer_list<double> from) {
return as_sexp<std::initializer_list<double>>(from);
return as_sexp(std::vector<double>(from));
}

template <typename Container, typename T = typename Container::value_type,
Expand All @@ -279,7 +282,7 @@ enable_if_bool<T, SEXP> as_sexp(const Container& from) {
}

inline SEXP as_sexp(std::initializer_list<bool> from) {
return as_sexp<std::initializer_list<bool>>(from);
return as_sexp(std::vector<bool>(from));
}

namespace detail {
Expand Down Expand Up @@ -325,12 +328,52 @@ enable_if_c_string<T, SEXP> as_sexp(const Container& from) {
}

inline SEXP as_sexp(std::initializer_list<const char*> from) {
return as_sexp<std::initializer_list<const char*>>(from);
return as_sexp(std::vector<const char*>(from));
}

template <typename T, typename = disable_if_r_string<T>>
enable_if_convertible_to_sexp<T, SEXP> as_sexp(const T& from) {
return from;
}

// Templated specialization for std::map
template <typename Key, typename Value,
typename = enable_if_t<std::is_arithmetic<Key>::value &&
std::is_arithmetic<Value>::value>>
inline SEXP as_sexp(const std::map<Key, Value>& map) {
R_xlen_t size = map.size();
SEXP result = PROTECT(Rf_allocVector(VECSXP, size));
SEXP names = PROTECT(Rf_allocVector(REALSXP, size));

auto it = map.begin();
for (R_xlen_t i = 0; i < size; ++i, ++it) {
SET_VECTOR_ELT(result, i, as_sexp(it->second));
REAL(names)[i] = it->first;
}

Rf_setAttrib(result, R_NamesSymbol, names);
UNPROTECT(2);
return result;
}

// Templated specialization for std::unordered_map
template <typename Key, typename Value,
typename = enable_if_t<std::is_arithmetic<Key>::value &&
std::is_arithmetic<Value>::value>>
inline SEXP as_sexp(const std::unordered_map<Key, Value>& map) {
R_xlen_t size = map.size();
SEXP result = PROTECT(Rf_allocVector(VECSXP, size));
SEXP names = PROTECT(Rf_allocVector(REALSXP, size));

auto it = map.begin();
for (R_xlen_t i = 0; i < size; ++i, ++it) {
SET_VECTOR_ELT(result, i, as_sexp(it->second));
REAL(names)[i] = it->first;
}

Rf_setAttrib(result, R_NamesSymbol, names);
UNPROTECT(2);
return result;
}

} // namespace cpp11
4 changes: 0 additions & 4 deletions vignettes/cpp11.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,6 @@ logicals duplicated_cpp(integers x) {
}
```

````{=html}
<!--- TODO: Add `as_sexp()` support for maps
### Map

A map is similar to a set, but instead of storing presence or absence, it can store additional data.
Expand All @@ -903,8 +901,6 @@ SEXP table_cpp(doubles x) {
return as_sexp(counts);
}
```
!-->
````

### Exercises

Expand Down
47 changes: 46 additions & 1 deletion vignettes/motivations.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,52 @@ Doing this universally avoids many locale specific issues when dealing with Unic

Concretely cpp11 always uses `Rf_translateCharUTF8()` when obtaining `const char*` from `CHRSXP` objects and uses `Rf_mkCharCE(, CE_UTF8)` when creating new `CHRSXP` objects from `const char*` inputs.

<!--TODO: unicode examples?-->
Converting R Unicode Strings to C++ Strings:

```cpp
#include <cpp11.hpp>
#include <string>

[[cpp11::register]]
std::string convert_to_utf8(cpp11::strings input) {
std::string result;
for (auto str : input) {
result += cpp11::r_string(Rf_translateCharUTF8(str));
}
return result;
}
```

```r
# hello + how are you? in Japanese and Spanish
input <- c("\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf",
"\xc2\xbfC\xc3\xb3mo est\xc3\xa1s\x3f")

convert_to_utf8(input)

[1] "こんにちは¿Cómo estás?"
```

Returning Unicode Strings from C++ to R:

```cpp
#include <cpp11.hpp>
#include <string>

[[cpp11::register]]
cpp11::writable::strings return_utf8_string() {
cpp11::writable::strings result;
std::string utf8_str = "こんにちは"; // Hello in Japanese
result.push_back(Rf_mkCharCE(utf8_str.c_str(), CE_UTF8));
return result;
}
```

```r
return_utf8_string()

[1] "こんにちは"
```

## C++11 features {#c11-features}

Expand Down
Loading