Increment Guid in C#
Asked Answered
S

4

12

I have an application that has a guid variable which needs to be unique (of course). I know that statistically any guid should just be assumed to be unique, but due to dev/test environment reasons, the same value may be seen multiple times. So when that happens, I want to "increment" the value of the Guid, rather than just creating a whole new one. There does not seem to be a simple way to do this. I found a hack, which I will post as a possible answer, but want a cleaner solution.

Spleenwort answered 22/5, 2015 at 19:58 Comment(9)
Why can't it be a new Guid?Adamsite
That's not how GUIDs are supposed to be handled. What is wrong with calling Guid.NewGuid() when you need a new one?Mayne
The way you are using this, you might want to consider calling it a UUID instead.Gotten
Aren't you looking for a sequential GUID: #1752504 ?Tetrahedron
@EBrown the main problem with non-sequential GUID's is that they don't work well with b-trees used in databases. More exact: they drain the performance from the system. Also, IIRC sequential GUID's are perfectly valid GUID's if you use the correct implementation... but to be honest I'm not 100% sure (can't remember the details from the spec).Tetrahedron
Yes, looked it up. Sequential GUID's are normally implemented as type 1 UUID's from the spec: tools.ietf.org/rfc/rfc4122.txt .Tetrahedron
@EBrown As I explained in the question, I want to increment when there is a collision. This log is for humans to see, so the human could see that the guids were nearly identical, and the database is satisfied that they are unique.Spleenwort
@Tetrahedron No, the original guid is generated randomly, and the point is address when a collision occurs, by creating a nearly identical value that is also unique.Spleenwort
@Spleenwort so basically you want a Global unique sequential IDs. Well it is possible, since Google does that using timestamps in Spanner -- but they have dedicated hardware for that (including stuff like geo locators and atom clocks and so on, it's quite a fancy paper to read :-). For normal people like me and probably you, it's just not going to happen with the "Global Unique" constraint. If you drop that constraint, might as well use a sequencer, since they are designed to do this.Tetrahedron
I
14

You can get the byte components of the guid, so you can just work on that:

static class GuidExtensions
{
    private static readonly int[] _guidByteOrder =
        new[] { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };
    public static Guid Increment(this Guid guid)
    {
        var bytes = guid.ToByteArray();
        bool carry = true;
        for (int i = 0; i < _guidByteOrder.Length && carry; i++)
        {
            int index = _guidByteOrder[i];
            byte oldValue = bytes[index]++;
            carry = oldValue > bytes[index];
        }
        return new Guid(bytes);
    }
}

EDIT: now with correct byte order

Invariable answered 22/5, 2015 at 20:2 Comment(4)
Why the downvotes? care to explain what's wrong with this answer?Invariable
This code throws an OverflowException with the ++ when the byte's value is 255. So would need to have unchecked{} around that statement.Spleenwort
@Abacus, is your project compiled with the /checked option? Integer arithmetic is unchecked with the default options.Invariable
Yes, it is checked in my project. Or rather, it is not unchecked. Using VS2010 Premium, and it's under project properties, Compile, Advanced, and I have "Remove integer overflow checks" left unchecked. This seems the sensible default -- it seems very strange to me that the default behavior would be unchecked.Spleenwort
B
10

Thanks to Thomas Levesque's byte order, here's a nifty LINQ implementation:

static int[] byteOrder = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };

static Guid NextGuid(Guid guid)
{
    var bytes = guid.ToByteArray();
    var canIncrement = byteOrder.Any(i => ++bytes[i] != 0);
    return new Guid(canIncrement ? bytes : new byte[16]);
}

Note it wraps around to Guid.Empty if you manage to increment it that far.

It would be more efficient if you were to keep incrementing a single copy of bytes rather than calling ToByteArray on each GUID in turn.

Barbellate answered 22/5, 2015 at 19:58 Comment(8)
Very elegant. Are you sure about the byte order, though?Invariable
@ThomasLevesque It matches up with a few other places I've seen it.Barbellate
I observed this order: 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3Invariable
@ThomasLevesque You're right, mine was all over the place and I didn't actually check any of the higher bytes... ThanksBarbellate
@ThomasLevesque I've now checked and I'm pretty sure you're right, so thanks again.Barbellate
This code throws an OverflowException with the ++ when the byte's value is 255. So would need to have unchecked{} around that statement.Spleenwort
@Spleenwort Well, that depends whether you're running in a checked context or not. The VS default is not, so I didn't bother.Barbellate
My pair-coding partner and I just want to acknowledge how awesome that .Any() is. Very cool.Joleen
S
1

Possible solution -- I think this works (not really tested), but want a better solution.

public static Guid Increment(this Guid value)
{
    var bytes = value.ToByteArray();
    // Note that the order of bytes in the returned byte array is different from the string representation of a Guid value.
    //  Guid:       00112233-4455-6677-8899-aabbccddeeff
    //  byte array: 33 22 11 00 55 44 77 66 88 99 AA BB CC DD EE FF
    // So the byte order of the following indexes indicates the true low-to-high sequence
    if (++bytes[15] == 0) if (++bytes[14] == 0) if (++bytes[13] == 0) if (++bytes[12] == 0) if (++bytes[11] == 0) if (++bytes[10] == 0) // normal order
     if (++bytes[9] == 0) if (++bytes[8] == 0) // normal order
      if (++bytes[6] == 0) if (++bytes[7] == 0) // reverse order
       if (++bytes[5] == 0) if (++bytes[4] == 0) // reverse order
        if (++bytes[3] == 0) if (++bytes[2] == 0) if (++bytes[1] == 0) { ++bytes[0]; } // reverse order
    return new Guid(bytes);
}

Edit: here is the code I ended up using; props to the answers above for the general technique, although without the "unchecked" clause they both would throw exceptions in some cases. But I also tried to make the below as readable as possible.

private static int[] _guidByteOrder = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };
public static Guid NextGuid(this Guid guid)
{
    var bytes = guid.ToByteArray();
    for (int i = 0; i < 16; i++)
    {
        var iByte = _guidByteOrder[i];
        unchecked { bytes[iByte] += 1; }
        if (bytes[iByte] != 0)
            return new Guid(bytes);
    }
    return Guid.Empty;
}
Spleenwort answered 22/5, 2015 at 20:2 Comment(2)
That if chain is magnificent.Barbellate
Using BigInteger to increment will lead to obviously correct and likely nice looking code.Bedesman
D
1

Verified Solution for Ordered Strings:

    private static Guid Increment(Guid guid)
    {

        byte[] bytes = guid.ToByteArray();

        byte[] order = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };

        for (int i = 0; i < 16; i++)
        {
            if (bytes[order[i]] == byte.MaxValue)
            {
                bytes[order[i]] = 0;
            }
            else
            {
                bytes[order[i]]++;
                return new Guid(bytes);
            }
        }

        throw new OverflowException("Congratulations you are one in a billion billion billion billion etc...");

    }

Verification:

    private static Guid IncrementProof(Guid guid, int start, int end)
    {

        byte[] bytes = guid.ToByteArray();

        byte[] order = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };

        for (int i = start; i < end; i++)
        {
            if (bytes[order[i]] == byte.MaxValue)
            {
                bytes[order[i]] = 0;
            }
            else
            {
                bytes[order[i]]++;
                return new Guid(bytes);
            }
        }

        throw new OverflowException("Congratulations you are one in a billion billion billion billion etc...");

    }

    static void Main(string[] args)
    {

        Guid temp = new Guid();

        for (int j = 0; j < 16; j++)
        {
            for (int i = 0; i < 255; i++)
            {
                Console.WriteLine(temp.ToString());
                temp = IncrementProof(temp, j, j + 1);
            }
        }

    }
Digestive answered 20/11, 2015 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.