My main goal here is to provide an example for other people who want to experiment with LibraryLink and strings, as well as to test how fast all of this is (and to become the "bottom line" in the fancy plot of course >:D ).
Anyway I made the following functions in C. Note that you have to have a C-compiler set up in such a way that Mathematica knows about it, before you try to use this code.
The C code
typedef int bool;
#define true 1
#define false 0
#include "WolframLibrary.h"
char* instring;
/* Return the version of Library Link */
DLLEXPORT mint WolframLibrary_getVersion( ) {
return WolframLibraryVersion;
}
/* Initialize Library */
DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData) {
return LIBRARY_NO_ERROR;
}
/* Uninitialize Library */
DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData) {
return;
}
DLLEXPORT int sortDigitsInString(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res)
{
instring = MArgument_getUTF8String(Args[0]);
int counts[10];
for(mint j=0; j<10; j++){
counts[j]=0;
}
mint slen = strlen(instring);
mint* intPos = malloc(slen*sizeof(mint)); //cast if you are using C++
char* charPtr = instring;
char cChar;
mint* intPosPtr = intPos;
mint intPosLen = 0;
for (mint i = 0; i < slen; i++) {
cChar = *charPtr;
if (48 <= cChar && cChar <= 57) {
counts[cChar - 48]++;
*intPosPtr = i;
intPosPtr++;
intPosLen++;
}
charPtr++;
}
intPosPtr = intPos;
mint c;
mint cCount;
for (mint i=0; i<10; i++) {
cCount = counts[i];
c = i + 48;
for(mint j=0; j < cCount; j++){
instring[*intPosPtr] = c;
intPosPtr++;
}
}
MArgument_setUTF8String(Res, instring);
return LIBRARY_NO_ERROR;
}
DLLEXPORT int sortDigitsInString2(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res)
{
instring = MArgument_getUTF8String(Args[0]);
int counts[10];
for(mint j=0; j<10; j++){
counts[j]=0;
}
mint slen = strlen(instring);
mint* pickAr = malloc(slen*sizeof(mint)); //cast if you are using C++
char* charPtr = instring;
char cChar;
mint* pickPtr = pickAr;
for (mint i = 0; i < slen; i++) {
cChar = *charPtr;
if (48 <= cChar && cChar <= 57) {
counts[cChar - 48]++;
*pickPtr = 1;
}
else{
*pickPtr = 0;
}
pickPtr++;
charPtr++;
}
mint c = 48;
mint cCount = counts[0];
mint cUsedCount = 0;
mint k = 0;
pickPtr = pickAr;
charPtr = instring;
bool zeroCountFlag = true;
for (mint i = 0; i < slen; i++) {
if (*pickPtr) {
*charPtr = c;
cUsedCount++;
}
charPtr++;
pickPtr++;
if (cUsedCount == cCount) {
while (zeroCountFlag) {
k++;
c++;
cCount = counts[k];
zeroCountFlag = cCount == 0;
}
cUsedCount = 0;
zeroCountFlag = true;
}
}
MArgument_setUTF8String(Res, instring);
return LIBRARY_NO_ERROR;
}
DLLEXPORT int digitCounts(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res)
{
int err = 0;
instring = MArgument_getUTF8String(Args[0]);
int counts[10];
for(mint j=0; j<10; j++){
counts[j]=0;
}
mint* data;
MTensor countsTensor;
mint dims[1];
dims[0] = 10;
err = libData->MTensor_new(MType_Integer, 1, dims, &countsTensor);
data = libData->MTensor_getIntegerData(countsTensor);
mint c =0;
mint slen = strlen(instring);
char* charPtr = instring;
char cChar;
for (mint i = 0; i < slen; i++) {
cChar = *charPtr;
if (48 <= cChar && cChar <= 57) {
counts[cChar - 48]++;
}
charPtr++;
}
for(mint j=0; j<10; j++){
data[j] = counts[j];
}
MArgument_setMTensor(Res, countsTensor);
return err;
}
DLLEXPORT int freeString(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res)
{
libData->UTF8String_disown( instring);
return LIBRARY_NO_ERROR;
}
In order to use the C code, no file has to be made. We can simply use the code as a Mathematica string. In my setup, we have to store the string in the variable cCodeString
.
One way to achieve this is by pasting the contents of the code block above between quotes (cCodeStr = "<paste here>"
) and pressing Yes in the dialogue.
Alternatively, the following code makes a nice cell to paste the c code in.
(*NotebookDelete[Cells[CellTags -> "cCodeCell"]];*)
Cell["", "Code", CellLabel -> "Paste Here", CellTags -> "cCodeCell",
InitializationCell -> False, Evaluatable -> False ,
CellAutoOverwrite -> True] // CellPrint
After pasting, we can set cCodeString
to the right value as follows
(*evaluate after pasting*)
cCodeStr = First@NotebookRead@First@Cells[CellTags -> "cCodeCell"];
Setting up the functions
The following then sets up all the functions. Note that we really only care about sDSFS
and sDSFS2
. The function dCFS
is is just an added bonus and the other functions are there just to help these functions.
<< CCompilerDriver`
CreateLibrary[cCodeStr, "libStrDigStr"]
sDS = LibraryFunctionLoad["libStrDigStr",
"sortDigitsInString", {"UTF8String"}, "UTF8String"];
sDS2 = LibraryFunctionLoad["libStrDigStr",
"sortDigitsInString2", {"UTF8String"}, "UTF8String"];
dC = LibraryFunctionLoad["libStrDigStr",
"digitCounts", {"UTF8String"}, {Integer, 1}];
freeString =
LibraryFunctionLoad["libStrDigStr", "freeString", {}, "Void"];
sDSFS =
PreemptProtect@AbortProtect@First@{sDS@#, freeString[]} &;
sDSFS2 =
PreemptProtect@AbortProtect@First@{sDS2@#, freeString[]} &;
dCFS =
PreemptProtect@AbortProtect@First@{sDS@#, freeString[]} &;
Comparison
Now with the definitions in Mr.Wizards answer, we get
Needs["GeneralUtilities`"]
BenchmarkPlot[{f1, subSort, strnumsrt, sDSFS, sDSFS2}, g]

Considerations
Somehow I got into my head that working with strings in LibraryLink was impossible. At the same time I thought using FromCharacterCode
was inefficient somehow, but perhaps that is because I used it as FromCharacterCode/@list
, rather than FromCharacterCode@list
. I think if we use FromCharacterCode
we can use a CompiledFunction
most of the time, which is always a nice solution in between "normal MMA" solutions and LibraryLink solutions. I think that a quite straightforward translation of my LibraryLink function to a CompiledFunction
will probably beat Rasher's function.