I have made a solution using LibraryLink that appears to be even faster than Ajasja's function that uses Compile
. Here is the C code.
#include <stdio.h>
#include <stdlib.h>
#include "WolframLibrary.h"
/* 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 unitS_T_T(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res)
{
int err = 0;
MTensor result;
MTensor input;
register mint *inputAndResultDataPtr;
mint resDim[2];
mint inputDataLen;
_Bool isEven;
input = MArgument_getMTensor(Args[0]);
inputAndResultDataPtr = libData->MTensor_getIntegerData(input);
inputDataLen = libData->MTensor_getFlattenedLength(input);
//mAr stands for mallocArray, as this is not an array in the purest sense.
const mint* mArHP = malloc(sizeof(mint)*2*inputDataLen);
register mint* mArrayPtr = mArHP;
register mint count = 0;
isEven = (_Bool)((inputDataLen+1) % 2);
//the n in nEvenIterations stands for "number of".
//nEvenIterations tells us how often to repeat the code in the loop.
//To see that it is correct, consider how often you can increment
//inputAndResultDataPtr
//by 2, without making it point beyond the end of the integerData.
register mint nEvenIterations = (isEven? inputDataLen-2 : inputDataLen-1)/2;
//we need count2 to decide how long the result tensor will be.
register mint count2;
register mint a = *inputAndResultDataPtr;
//From here on, we consistently increment this pointer before assigning from
//its target value, rather than after.
register mint b;
*mArrayPtr = a;
mArrayPtr++;
count2 = 1;
//We make sure that in the loop below, whenever we store an upper bound, we
//also can
//store a lower bound. This is what safe in evenSafeLength refers to.
//The variable that the maximum between a or b, was set using an element
//of the array beyond the (candidate for) the upper bound anyway. We do not
//need to store the lower bound
//in a variable, as we store it in the array right away.
while(count < nEvenIterations)
{
inputAndResultDataPtr++;
b = *inputAndResultDataPtr;
if( b != a+1)
{
*mArrayPtr = a;
mArrayPtr++;
*mArrayPtr = b;
mArrayPtr++;
count2 += 2;
}
//if !isEven, the line of code below will make inputAndResultDataPtr
//eventually point to the last value.
inputAndResultDataPtr++;
a = *inputAndResultDataPtr;
if( a != b+1)
{
*mArrayPtr = b;
mArrayPtr++;
*mArrayPtr = a;
mArrayPtr++;
count2 += 2;
}
count++;
}
//a and b alternating roles. We can predict which one is higher.
if(isEven)
{
inputAndResultDataPtr++;
b = *inputAndResultDataPtr;
//inputAndResultDataPtr now points to the last value
if( b != a+1)
{
*mArrayPtr = a;
mArrayPtr++;
*mArrayPtr = b;
mArrayPtr++;
count2 += 2;
}
//count++;
}
*mArrayPtr = *inputAndResultDataPtr;
count2++;
//count2 should be even
resDim[0] = count2/2;
resDim[1] = 2;
err = libData->MTensor_new(MType_Integer, 2, resDim, &result);
inputAndResultDataPtr = libData->MTensor_getIntegerData(result);
count = 0;
mArrayPtr = mArHP;
while(count < count2)
{
*inputAndResultDataPtr = *mArrayPtr;
mArrayPtr++;
inputAndResultDataPtr++;
count++;
}
free(mArHP);
libData->MTensor_disown(input);
MArgument_setMTensor(Res, result);
return err;
}
About the code
The idea is very similar to that of other answers.
There are some pretentious register keywords that don't change anything. I did a nice little trick with the variables a and b in the code to save on assignments. Also my intuition is that inlining the code twice in the loop only makes things faster (fewer tests count < nEvenIterations
). It makes the code longer tough :-/. The code involves copying an array entry for entry. The problem is that the size of the result tensor is not known in advance and that you can only set the size of a tensor once.
Making the Mathematica function
To let Mathematica use the C code, we will make a string of the code and store it in the variable cCodeString
. To do this, copy the code above, paste it between quotes ""
and click "Yes" in the dialog (to escape the characters).
cCodeString = (*Insert C code here*);
Now we process the code/string and make a function out of it.
<< CCompilerDriver`;
lib = CreateLibrary[cCodeString, "unitS"];
unitS = LibraryFunctionLoad["unitS",
"unitS_T_T", {{Integer, 1, "Shared"}},
{Integer, 2}];
Timing comparisons
This uses definitions from other answers.
(r1=intervals[a])//Timing//First
(r2={Min[#],Max[#]}&/@Split[a,#2-#1==1&])//Timing//First
(r3=compiledGetContigIntervals[a])//Timing//First
(r4=unitS[a])//Timing//First
0.329104
10.1459
0.023
0.008142
r1 === r2 === r3 === r4
True
Notes
I made the code with the scenario that there are many "jumps" in mind. When there are very few jumps, like in the case for which we compare timings, it is probably much faster to determine the positions of the gaps using a binary search.
For a constant number of jumps, I feel such a binary search would require only $O(log(n))$ time and that should be the main work in the algorithm.