Recently I was thinking about how Google, Microsoft Outlook on Windows Phone (and now many others) method of giving you a coloured icon with a letter in the centre of it works.
Essentially, given some sort of name, they generate a colour based on that name. The kicker is that the same name always generates the same colour, the icon then generates with the 'initials' (dependent upon implementation) of the sender of the email within it. A sample is below.
(Sorry for the size, it was enormous at native resolution, feel free to click to view original.)
In the image you can see that both "WO" icons are the same colour, but the two "G" icons are a different colour, this is because the "G" icons come from different email addresses and the "WO" icons come from the same one.
So of course I wanted to write a system that could generate a similar thing, based upon "some string", it could generate a colour that would be mostly unique to that string. (I don't particularly care if multiple strings return the same colour, but a similar string should be substantially different.)
This should be entirely predictable. Every single string should result in the exact same output every time. There should be no deviation (unless the underlying implementation of Random
changes).
For the solution of this I went purely LINQ / method calls, and I want to keep it that way.
new string[]
{
"EBrown"
}
.Select(name =>
new Random(
Encoding
.UTF8
.GetBytes(name)
.Select((b, i) =>
b << (i % 4) * 8
)
.Aggregate(0, (acc, i) =>
acc + i
)
)
)
.Select(r =>
new double[3]
{
r.NextDouble(),
r.NextDouble(),
r.NextDouble()
}
)
I did not return a Color
mostly because I'm not referencing any libraries that have a Color
(this was built in the C# Interactive window), but also because it should be generic enough to just return a list of three values for the color.
To address one of the comments:
What purpose of this:
.Aggregate(0, (acc, i) => acc + i)
? It is just the same asSum()
...
As the two of us discovered (thanks Maxim for starting this discussion) the Sum
method performs it's operation within a checked
context, and will throw an OverflowException
on the last test-case I provided in this case. Wrapping the entire thing in an unchecked
context does not result in a solution, as the local checked
context in Sum
takes precedence.
We could define a SumOverflow
method that would do the summation inside an unchecked
context, but that takes us out of vanilla-LINQ mode, and requires an additional dependency on a new method. (I literally want zero additional dependencies, I only want vanilla / naked LINQ.)
Regarding:
I also think this
b << (i % 4) * 8
requires encapsulation too. It's a magic formula. Perhaps you could explain what you are calculating here?
Shout out to t3chb0t: you're not wrong; however, that, once again, goes against my intent. I'm trying to design this purely LINQ-only, as I want to be able to copy/pasta it and modify as necessary for the specific implementation (if I am even doing so), but I don't want to use any additional dependencies. That said, if someone comes up with a way to encapsulate it and keep it simple, I'm more than happy to use it.
The formula itself is rather simple though: the goal is to take the [i]ndex
of the [b]yte
and move the [b]yte
left 8
bits times the rotational [i]ndex
across a rotation of 4
. So if the [b]yte
is 4
, and it's the 7
th byte, then it gets shifted so that it's the highest section of the int
. (Since the next step is to add all the integers together, I shift beforehand to make sure that we're not just adding the lowest byte of integers together.)
As an example:
String: EBrown
Byte Array: 0x45, 0x42, 0x72, 0x6f, 0x77, 0x6e
Shifted Int Array: 0x45, 0x4200, 0x720000, 0x6f000000, 0x77, 0x6e00
Sum: 6f72b0bc
Test cases:
EBrown: double[3] { 0.93380133292349121, 0.61282262793407893, 0.99123903596365781 }
Elliott: double[3] { 0.46952278049174828, 0.71652967376472876, 0.041705311295439168 }
Brown: double[3] { 0.45409023782894492, 0.70977234454349258, 0.61635322292165518 }
Elliott Brown: double[3] { 0.033125448521750721, 0.84549407793464793, 0.41680685589872618 }
(int)(maxColorVal * randomValue)
, so that if your colour system runs0-255
, you just(int)(255 * value)
, if it's 0-100 you(int)(100 * randomValue)
. It's not dependent on any one color scale. \$\endgroup\$ – Der Kommissar Aug 1 '17 at 15:25HashCode
? \$\endgroup\$ – Vogel612♦ Aug 1 '17 at 15:33#if FEATURE_RANDOMIZED_STRING_HASHING
block, I need to find out if that feature is enabled or disabled for my current build. \$\endgroup\$ – Der Kommissar Aug 1 '17 at 15:37FEATURE_RANDOMIZED_STRING_HASHING
is enabled, that could be problematic. That would mean that if two strings are in different application domains they would have a different hash-code result. I'd prefer not to have that become an issue, so I'm not going to useGetHashCode
for it (unless someone can prove that it's a non-issue). \$\endgroup\$ – Der Kommissar Aug 1 '17 at 15:42