Before we make any changes, let's add some tests to the end, so we have confidence that we don't break anything that worked:
#include <stdio.h>
static int test(unsigned int value, unsigned int expected)
{
unsigned int actual = findsaw(value);
if (actual == expected)
return 0;
printf("findsaw(%#x) = %u\n", value, actual);
return 1;
}
int main()
{
return test(0x0, 0)
+ test(0xAA, 0)
+ test(0x585, 0)
+ test(0xA0F, 8)
+ test(0xF0A, 0)
+ test(0xFA0, 4)
+ test(0xEAEA476Bu, 23);
}
This all passes (the program returns 0). I made test
take the value as an unsigned int, because 0xEAEA476B
is too big for a plain int on my system.
Now let's look at your code.
#include <stdio.h>
#define YES 1 /* inside a sequence */
#define NO 0 /* outside a sequence */
We're not using anything from <stdio.h>
, so that's not required. Also, instead of defining YES
and NO
in the preprocessor, we could include <stdbool.h>
, and then use true
and false
instead.
unsigned int findsaw(int value) {
unsigned int sequence = value;
Are you required to accept input as a signed integer? If not, then declare findsaw()
to take an unsigned int
. As it is, the last test case (from the question's code) produced a compilation warning, as 0xEAEA476BL
is larger than INT_MAX
on my system.
unsigned char idx = 0;
unsigned char sequenceIdx = 0;
unsigned char currentIdx = 0;
unsigned char size = 0;
unsigned char sequenceSize = 0;
unsigned char bit = value & 1;
unsigned char nextBit;
None of these really need to be char
. It's probably better to let them be int
(or uint_fast8_t
if you're really keen).
These names aren't immediately obvious to me (or to you in six months' time). It might be worth grouping them so that idx
and size
, which represent the best match so far, are placed together, with a comment explaining that. Similarly, sequenceIdx
and sequenceSize
seem to represent the current in-progress match, and should go together.
Some of the naming was alien to me: I'd have considered what you call the "next" bit to be "current", and what you call just "bit" to be "last_bit". That might be just me, though.
if (bit ^ nextBit) {
A comment would be nice here: this is true when we're matching the sawtooth pattern. Also, the else
branch contains actions that are only needed immediately at the end of a sequence, and do nothing for extended runs such as 0x00 or 0xFF, so that can be else if (insideSequence)
.
We can save a bit of work here:
/* matched a transition */
if (insideSequence) { /* just add to the size */
sequenceSize++;
} else {
/* initialize for inside a sequence */
sequenceSize = 2;
sequenceIdx = currentIdx;
insideSequence = true;
}
If we initialise sequenceSize
to 1
instead of 0
, then we can simply increment it in both cases (i.e. outside the if
). We can use sequenceSize == 1
to determine whether we're in a sequence, and we no longer need the insideSequence
variable (nor <stdbool.h>
):
unsigned int findsaw(unsigned int value) {
unsigned int sequence = value;
/* best match so far */
unsigned int best_start = 0; /* this is what we're looking for */
unsigned int best_size = 0;
unsigned int sequenceIdx = 0;
unsigned int currentIdx = 0;
unsigned int sequenceSize = 1;
while (sequence) {
const int last_bit = sequence & 1;
sequence >>= 1;
const int current_bit = sequence & 1;
if (last_bit ^ current_bit) {
/* matched a transition */
if (sequenceSize++ == 1) {
/* the start of a new sequence */
sequenceIdx = currentIdx;
}
} else if (sequenceSize != 1) {
/* not matched - did we just finish a sequence? */
if (best_size < sequenceSize) {
best_size = sequenceSize;
best_start = sequenceIdx;
}
sequenceSize = 1;
}
++currentIdx;
}
if (best_size < sequenceSize) {
best_start = sequenceIdx;
}
return best_start;
}
Let's see if we can do anything about the duplication of if (best_size < sequenceSize)
when we reach the last bit. I'll add a couple of extra tests:
+ test(0x50000005u, 27)
+ test(0xA0000005u, 0)
The last one fails (returns 28), because we are zero-extending the left of the value, but not the right. If we simply take out the test, then other test cases fail (0xa0f
and 0x50000005
). It seems that the while (sequence)
returns too early in those cases.
At this point, I give up fixing this and look at a different approach. We don't need to check last_bit ^ current_bit
every time around the loop - we can compute them all at the same time like this:
unsigned int sequence = value;
unsigned int mask = sequence ^ (sequence >> 1);
This produces a 1 everywhere we have a bit preceded by its complement. We still have the problem that the high bit might be set, but we can fix this by writing mask = sequence ^ (sequence << 1)
to make the low bit the stray one, and we can just shift that out. Now, all we have to do is find the longest run of 1s:
unsigned int findsaw(unsigned int value)
{
unsigned int sequence = value;
unsigned int mask = sequence ^ (sequence << 1);
/* best match so far */
unsigned int best_start = 0; /* this is what we're looking for */
unsigned int best_size = 0;
/* current run */
unsigned int current_start = 0;
unsigned int current_size = 0;
/* current position */
for (unsigned int i = 0; mask >>= 1; ++i) {
if (mask & 1) {
if (!current_size++)
/* the start of a new run */
current_start = i;
} else if (current_size) {
current_size = 0;
}
if (current_size > best_size) {
best_size = current_size;
best_start = current_start;
}
}
return best_start;
}
#include <stdio.h>
static int test(unsigned int value, unsigned int expected)
{
unsigned int actual = findsaw(value);
if (actual == expected)
return 0;
printf("findsaw(%#x) = %u\n", value, actual);
return 1;
}
int main()
{
return test(0x0, 0)
+ test(0xAA, 0)
+ test(0x585, 0)
+ test(0xA0F, 8)
+ test(0xF0A, 0)
+ test(0xFA0, 4)
+ test(0xEAEA476Bu, 23)
+ test(0x50000005u, 27)
+ test(0xA0000005u, 0);
}
All the tests pass; time to go home.
0xF
is not 'all 1's' because it actually is0x0000000F
for 32-bitint
, hence 3 is a correct answer: the longest chain of alternating bits in the given number is a two-bit sequence01
at positions 4 and 3. \$\endgroup\$