In C#, can multiplications inside of an array declaration/initialization cause mayhem? - c#

I am going a bit crazy here with the following section of code
public static readonly float f = 1.25f;
public static void init(){
Debug.Log(f); // output: 1.25f
FLIPPER_CENTERS = new float[,] {
{ (20*f), (27*f) }, { FLIPPER_WIDTH - (20*f), (27*f)},
{ (6*f), (25*f) }, { MH_FLIPPER_WIDTH- (6*f), (25*f) },
{ (8), (15)}, { (SMALL_FLIPPER_WIDTH - 8), (15)},
{ (8), (20)}, { (67 - 8), (20)},
};
Debug.Log(FLIPPER_CENTERS[0,0]); // output: 0, expected 25;
}
If I print the values of the first element of that array, I get [0, 0]. The last two elements are [59, 20], as expected.
The first value is supposed to be [25, 33,75]. Which I can get if I substitute (20*f) for (20*1.25f).
{ (20*1.25f), (27*1.25f) }, { FLIPPER_WIDTH - (20*f), (27*f)},
So here is the problem: if I leave the multiplication by f in the array initialization, the values are 0. However, if I change f to 1.25f all is good.
I have tried to figure out what is going on, but to no avail. I am certain the value of f is 1.25f and not 0. Can anyone shed some light on this for me please?
Edit:
To prove that f is not 0, I've tried 20/f for the first element. That didn't throw an exception and the output was still 0.
Partial Solution
Changing f from readonly to const solves the problem. However, I would really much like to know why this is happening.
public const float f = 1.25f;
All this is running in Unity, it may have something to do with it.

I see this question is tagged Unity3D.
Is it possible that f is a public variable exposing it to be modified in the Unity Editor? If so, any value set in the editor will override a value set in the code during variable declaration. The value set during declaration is simply used as a default value for the Unity Editor.
If this is the case then someone could have set f to 0 in the editor causing it to be 0 when you do your math.
Edited Answer After Some Testing: 8/22/2013
I was able to repeat your problem when using Unity. It seems there is a bug in Unity that doesn't allow variables to be used during initialization of an array at declaration. Everything works properly if it is a standard C# project, however, so this is related to Unity only.
The problem has nothing to do with f being static or readonly, only that it is a variable. The following code does not work:
public void init()
{
float f = 1.25f;
float[,] FLIPPER_CENTERS = new float[,] {
{ (5+f), (27*f) }, { 30 - (20*f), (27*f)},
{ (6*f), (25*f) }, { 20 - (6*f), (25*f) },
{ (8), (15)}, { (10 - 8), (15)},
{ (8), (20)}, { (67 - 8), (20)},
};
Debug.Log(FLIPPER_CENTERS[0,0]); // Outputs 0 | Expected 6.25f
}
Anywhere the variable f is used in the array initialization will result in a 0. More specifically, it doesn't just set f = 0 it sets the whole expression to 0. For example, the first element in the array above, 5+f, would result in 0 - not 5. The elements with constants, however, evaluate normally (such as the last few elements). It seems if Unity is bailing out of the evaluation when it encounters a variable.
If, however, I define the array in one line and then set the elements later everything works normally. For example:
public void init()
{
float f = 1.25f;
float[,] FLIPPER_CENTERS = new float[8, 2];
FLIPPER_CENTERS[0, 0] = 20*f;
FLIPPER_CENTERS[0, 1] = 27*f;
FLIPPER_CENTERS[1, 0] = 30 - (20*f);
FLIPPER_CENTERS[1, 1] = 27*f;
FLIPPER_CENTERS[2, 0] = 6*f;
FLIPPER_CENTERS[2, 1] = 25*f;
FLIPPER_CENTERS[3, 0] = 20 - (6*f);
FLIPPER_CENTERS[3, 1] = 25*f;
FLIPPER_CENTERS[4, 0] = 8;
FLIPPER_CENTERS[4, 1] = 15;
FLIPPER_CENTERS[5, 0] = 10 - 8;
FLIPPER_CENTERS[5, 1] = 15;
FLIPPER_CENTERS[6, 0] = 8;
FLIPPER_CENTERS[6, 1] = 20;
FLIPPER_CENTERS[7, 0] = 67 - 8;
FLIPPER_CENTERS[7, 1] = 20;
Debug.Log(FLIPPER_CENTERS[0,0]); // Outputs 25.0f | Expected 25.0f
}
Just did some further testing and it seems that the problem is only present with multidimensional arrays. The following gives expected output:
public void init()
{
float f = 1.25f;
float[] FLIPPER_CENTERS = new float[] {
(f), (f * 2), (f * 3), (f * 4),
(f + 1), (f + 2), (f + 3), (f + 5)
};
Debug.Log(FLIPPER_CENTERS[0]); // Outputs 1.25f | Expected 1.25f
}
This certainly seems like a bug in Unity to me. It is generally recommended, however, that const be used over static readonly. Using const provides a performance benefit (albeit slight) since it is converted into a literal at compile time. In addition, using const will avoid this problem altogether.
I know you already figured out const works, just wanted to give you a little information that I discovered.

Related

Why multiply Random.Next() by a Constant?

I recently read an article explaining how to generate a weighted random number, and there's a piece of the code that I don't understand:
int r = ((int)(rand.Next() * (323567)) % prefix[n - 1]) + 1;
Why is rand.Next being multiplied by a constant 323567? Would the code work without this constant?
Below is the full code for reference, and you can find the full article here: https://www.geeksforgeeks.org/random-number-generator-in-arbitrary-probability-distribution-fashion/
Any help is appreciated, thank you!!
// C# program to generate random numbers
// according to given frequency distribution
using System;
class GFG{
// Utility function to find ceiling
// of r in arr[l..h]
static int findCeil(int[] arr, int r,
int l, int h)
{
int mid;
while (l < h)
{
// Same as mid = (l+h)/2
mid = l + ((h - l) >> 1);
if (r > arr[mid])
l = mid + 1;
else
h = mid;
}
return (arr[l] >= r) ? l : -1;
}
// The main function that returns a random number
// from arr[] according to distribution array
// defined by freq[]. n is size of arrays.
static int myRand(int[] arr, int[] freq, int n)
{
// Create and fill prefix array
int[] prefix = new int[n];
int i;
prefix[0] = freq[0];
for(i = 1; i < n; ++i)
prefix[i] = prefix[i - 1] + freq[i];
// prefix[n-1] is sum of all frequencies.
// Generate a random number with
// value from 1 to this sum
Random rand = new Random();
int r = ((int)(rand.Next() * (323567)) % prefix[n - 1]) + 1; // <--- RNG * Constant
// Find index of ceiling of r in prefix array
int indexc = findCeil(prefix, r, 0, n - 1);
return arr[indexc];
}
// Driver Code
static void Main()
{
int[] arr = { 1, 2, 3, 4 };
int[] freq = { 10, 5, 20, 100 };
int i, n = arr.Length;
// Let us generate 10 random numbers
// according to given distribution
for(i = 0; i < 5; i++)
Console.WriteLine(myRand(arr, freq, n));
}
}
UPDATE:
I ran this code to check it:
int[] intArray = new int[] { 1, 2, 3, 4, 5 };
int[] weights = new int[] { 5, 20, 20, 40, 15 };
List<int> results = new List<int>();
for (int i = 0; i < 100000; i++)
{
results.Add(WeightedRNG.GetRand(intArray, weights, intArray.Length));
}
for (int i = 0; i < intArray.Length; i++)
{
int itemsFound = results.Where(x => x == intArray[i]).Count();
Console.WriteLine($"{intArray[i]} was returned {itemsFound} times.");
}
And here are the results with the constant:
1 was returned 5096 times.
2 was returned 19902 times.
3 was returned 20086 times.
4 was returned 40012 times.
5 was returned 14904 times.
And without the constant...
1 was returned 100000 times.
2 was returned 0 times.
3 was returned 0 times.
4 was returned 0 times.
5 was returned 0 times.
It completely breaks without it.
The constant does serve a purpose in some environments, but I don't believe this code is correct for C#.
To explain, let's look at the arguments to the function. The first sign something is off is passing n as an argument instead of inferring it from the arrays. The second sign is it's poor practice in C# to deal with paired arrays rather than something like a 2D array or sequence of single objects (such as a Tuple). But those are just indicators something is odd, and not evidence of any bugs.
So let's put that on hold for a moment and explain why a constant might matter by looking a small example.
Say you have three numbers (1, 2, and 3) with weights 3, 2, and 2. This function first builds up a prefix array, where each item includes the chances of finding the number for that index and all previous numbers.
We end up with a result like this: (3, 5, 7). Now we can use the last value and take a random number from 1 to 7. Values 1-3 result in 1, values 4 and 5 result in 2, and values 6 and 7 result in 3.
To find this random number the code now calls rand.Next(), and this is where I think the error comes in. In many platforms, the Next() function returns a double between 0 and 1. That's too small to use to lookup your weighted value, so you then multiply by a prime constant related the platform's epsilon value to ensure you have a reasonably large result that will cover the entire possible range of desired weights (in this case, 1-7) and then some. Now you take the remainder (%) vs your max weight (7), and map it via the prefix array to get the final result.
So the first error is, in C#, .Next() does not return a double. It is documented to return a non-negative random integer between 0 and int.MaxValue. Multiply that by 323567 and you're asking for integer overflow exceptions. Another sign of this mistake is the cast to int: the result of this function is already an int. And let's not even talk the meaningless extra parentheses around (323567).
There is also another, more subtle, error.
Let's the say the result of the (int)(rand.Next() * 323567) expression is 10. Now we take this value and get the remainder when dividing by our max value (%7). The problem here is we have two chances to roll a 1, 2, or 3 (the extra chance is if the original was 8, 9, or 10), and only once chance for the remaining weights (4-7). So we have introduced unintended bias into the system, completely throwing off the expected weights. (This is a simplification. The actual number space is not 1-10; it's 0 * 323567 - 0.999999 * 323567. But the potential for bias still exists as long that max value is not evenly divisible by the max weight value).
It's possible the constant was chosen because it has properties to minimize this effect, but again: it was based on a completely different kind of .Next() function.
Therefore the rand.Next() line should probably be changed to look like this:
int r = rand.Next(prefix[n - 1]) +1;
or this:
int r = ((int)(rand.NextDouble() * (323567 * prefix[n - 1])) % prefix[n - 1]) + 1;
But, given the other errors, I'd be wary of using this code at all.
For fun, here's an example running several different options:
https://dotnetfiddle.net/Y5qhRm
The original random method (using NextDouble() and a bare constant) doesn't fare as badly as I'd expect.

Index outside the bounds of the array error

I am coding an algorithm and I am using arrays of custom data and if it looks a bit strange is that I am coding the algorithm for a futures trading platform and so they may have functions that don't look standard C#.
I'm trying to Resize my arrays because I need them resized every time a new value is found to be added and then I use the SetValue sometimes to replace the last value found when a better one is found again within the next 5 values after the last value was set.
Trouble is, when I debug it in Visual Studio, it stops at line after ArrayResize and when I hover over the LastLSwDMIpriceBar[k], it shows the k = 0, just as I expected it to be, as it would be the first element in the array of one, so what Index is then outside the bounds of the array?
The way I understand and hoped the code to work is this: when the conditions met are by setting LSwDMIbool to true, the array is resized from 0 to 1 element and the element with index [0] is then set as LastLSwDMIpriceBar[k]. Am I wrong about this?
Any help would be greatly appreciated.
private int k, l;
private int[] LastLSwDMIpriceBar;
LastLSwDMIpriceBar = new int [1];
.....
if (LSwDMIbool)
{
lastLSwDMIbar = CurrentBar - 2 - LowestBar(LSwDMI, 5);
LastLSwDMI[0] = DMI(Closes[2], Convert.ToInt32(DmiPeriod)).Values[0].GetValueAt(lastLSwDMIbar);
Array.Resize(ref LastLSwDMIpriceBar, l++);
LastLSwDMIpriceBar[k] = CurrentBar - LowestBar(Lows[2], 5);
k++;
LSwDMIprice[0] = Lows[2].GetValueAt(LastLSwDMIpriceBar[k]);
}
......
if(!LSwDMIbool)
{
for (int LastBar = CurrentBar - 1; IsFirstTickOfBar && LastBar <= lastLSwDMIbar + 5; LastBar++)
LastLSwDMIpriceBar.SetValue((CurrentBar - LowestBar(Lows[2], 5)), k);
}
You may find this helpful:
https://stackoverflow.com/a/24858/12343726
I believe you want ++l instead of l++ so that the new value of l is used when resizing the array. I suspect that l is zero the first time Array.Resize is called so the array is being resized to zero.
I'm trying to Resize my arrays because I need them resized every time a new value is found to be added
Don't use an array for this!
Instead, use a List<T>.
private int k, l;
private List<int> LastLSwDMIpriceBar = new List<int>();
.....
if (LSwDMIbool)
{
lastLSwDMIbar = CurrentBar - 2 - LowestBar(LSwDMI, 5);
LastLSwDMI[0] = DMI(Closes[2], Convert.ToInt32(DmiPeriod)).Values[0][lastLSwDMIbar];
var newValue = CurrentBar - LowestBar(Lows[2], 5);
LastLSwDMIpriceBar.Add(newValue);
k++;
LSwDMIprice[0] = Lows[2][newValue];
}
......
if(!LSwDMIbool)
{
for (int LastBar = CurrentBar - 1; IsFirstTickOfBar && LastBar <= lastLSwDMIbar + 5; LastBar++)
LastLSwDMIpriceBar[k] = CurrentBar - LowestBar(Lows[2], 5));
}

NumPy implementation of the Nearest Neighbor classification algorithm classifies everything the exact same way

My assignment is to use a K-Nearest Neighbor algorithm to determine what kind of flower something is based on various features of it (e.g. stem length, petal length, etc.) using NumPy. (For the record, I've worked with Python in the past, although it's not my "best" language; however, I'm completely new to NumPy).
Both my training and my testing data are in CSVs that look like this:
4.6,3.6,1.0,0.2,Iris-setosa
5.1,3.3,1.7,0.5,Iris-setosa
4.8,3.4,1.9,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,1.5,Iris-versicolor
5.5,2.3,4.0,1.3,Iris-versicolor
I know how to do the basic algorithm. Here's the C# I created for it:
namespace Project_3_Prototype
{
public class FourD
{
public double f1, f2, f3, f4;
public string name;
public static double Distance(FourD a, FourD b)
{
double squared = Math.Pow(a.f1 - b.f1, 2) + Math.Pow(a.f2 - b.f2, 2) + Math.Pow(a.f3 - b.f3, 2) + Math.Pow(a.f4 - b.f4, 2);
return Math.Sqrt(squared);
}
}
class Program
{
static void Main(string[] args)
{
List<FourD> distances = new List<FourD>();
using (var parser = new TextFieldParser("iris-training-data.csv"))
{
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields();
var curr = new FourD
{
f1 = double.Parse(fields[0]),
f2 = double.Parse(fields[1]),
f3 = double.Parse(fields[2]),
f4 = double.Parse(fields[3]),
name = fields[4]
};
distances.Add(curr);
}
}
double correct = 0, total = 0;
using (var parser = new TextFieldParser("iris-testing-data.csv"))
{
parser.SetDelimiters(",");
int i = 1;
while (!parser.EndOfData)
{
total++;
string[] fields = parser.ReadFields();
var curr = new FourD
{
f1 = double.Parse(fields[0]),
f2 = double.Parse(fields[1]),
f3 = double.Parse(fields[2]),
f4 = double.Parse(fields[3]),
name = fields[4]
};
FourD min = distances[0];
foreach (FourD comp in distances)
{
if (FourD.Distance(comp, curr) < FourD.Distance(min, curr))
{
min = comp;
}
}
if (min.name == curr.name)
{
correct++;
}
Console.WriteLine(string.Format("{0},{1},{2}", i, curr.name, min.name));
i++;
}
}
Console.WriteLine("Accuracy: " + correct / total);
Console.ReadLine();
}
}
}
This is working exactly as expected, with the following output:
# The format is Number,Correct label,Predicted Label
1,Iris-setosa,Iris-setosa
2,Iris-setosa,Iris-setosa
3,Iris-setosa,Iris-setosa
4,Iris-setosa,Iris-setosa
5,Iris-setosa,Iris-setosa
6,Iris-setosa,Iris-setosa
7,Iris-setosa,Iris-setosa
8,Iris-setosa,Iris-setosa
9,Iris-setosa,Iris-setosa
10,Iris-setosa,Iris-setosa
11,Iris-setosa,Iris-setosa
12,Iris-setosa,Iris-setosa
...
Accuracy: 0.946666666666667
I'm trying to do the same thing in NumPy. However, the assignment does not permit me to use for loops, only vectorized functions.
So, basically what I want to do is: for every row in the testing data, get the index of the row in the training data that's closest to it (i.e. has the minimum Euclidean distance).
Here's what I tried in Python:
import numpy as np
def main():
# Split each line of the CSV into a list of attributes and labels
data = [x.split(',') for x in open("iris-training-data.csv")]
# The last item is the label
labels = np.array([x[-1].rstrip() for x in data])
# Convert the first 3 items to a 2D array of floats
floats = np.array([x[0:3] for x in data]).astype(float)
classifyTrainingExamples(labels, floats)
def classifyTrainingExamples(labels, floats):
# We're basically doing the same thing to the testing data that we did to the training data
testingData = [x.split(',') for x in open("iris-testing-data.csv")]
testingLabels = np.array([x[-1].rstrip() for x in testingData])
testingFloats = np.array([x[0:3] for x in testingData]).astype(float)
res = np.apply_along_axis(lambda x: closest(floats, x), 1, testingFloats)
correct = 0
for number, index in enumerate(res):
if labels[index] == testingLabels[number]:
correct += 1
print("{},{},{}".format(number + 1, testingLabels[number], labels[index]))
number += 1
print(correct / len(list(res)))
def closest(otherArray, item):
res = np.apply_along_axis(lambda x: distance(x, item), 1, otherArray)
i = np.argmin(res)
return i
# Get the Euclidean distance between two "flat" lists (i.e. one particular row
def distance(a, b):
# Subtract one from the other elementwise, then raise each one to the power of 2
lst = (a - b) ** 2
# Sum all of the elements together, and take the square root
result = np.sqrt(lst.sum())
return result
main()
Unfortunately, the output looks like
1,Iris-setosa,Iris-setosa
2,Iris-setosa,Iris-setosa
3,Iris-setosa,Iris-setosa
4,Iris-setosa,Iris-setosa
....
74,Iris-setosa,Iris-setosa
75,Iris-setosa,Iris-setosa
0.93333333
Every single line has nothing but Iris-setosa for the labels, and the accuracy is 0.9333333.
I tried stepping through this with a debugger, and every item is being counted as being correct by the if statement (but the correctness percentage is still shown as 0.93333333).
So basically:
It is showing that every result is "correct" (when it clearly isn't).
It's showing Iris-setosa for every value
My percentage showing as 93%. The correct value is actually approximately 94%, but I'd expect this to show 100% given that every result is supposedly "correct."
Can someone help me see what I'm missing here?
And before anyone asks, for the record, yes, I did try stepping through this with a debugger :) Also for the record, yes, this is homework.
If you really want to do it in one line, here is what you can do (I downloaded the dataset from scikit-learn):
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
# Load dataset
iris = datasets.load_iris()
X = iris.data
y = iris.target
# Split training and test set
Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, test_size=0.2)
# 1-neareast neighbour
ypred = np.array([ytrain[np.argmin(np.sum((x-Xtrain)**2,axis=1))] for x in Xtest])
# Compute classification error
sum(ypred != ytest)/ len(ytest)
Now, this is 1-nearest neighbour, it only looks at the closest point from training set. For k-nearest neighbour, you have to change it to this:
# k-neareast neighbour
k = 3
ypredk = np.array([np.argmax(np.bincount(ytrain[np.argsort(np.sum((x-Xtrain)**2,axis=1))[0:k]])) for x in Xtest])
sum(ypredk != ytest)/ len(ytest)
To put it in words, you sort the distances, you find the indices of the k lowest values (that's the np.argsort part) and the corresponding labels, then you look for the most common label among the k ones (that's the np.argmax(np.bincount(x)) part).
Finally, if you want to make sure, you can compare with scikit-learn:
# scikit-learn NN
from sklearn import neighbors
knn = neighbors.KNeighborsClassifier(n_neighbors=k, algorithm='ball_tree')
knn.fit(Xtrain,ytrain)
ypred_sklearn = knn.predict(Xtest)
sum(ypred_sklearn != ytest)/ len(ytest)

How to stop enum from going out of bounds?

I'm still learning the ropes with C# programming. I am trying to write an application to solve Knight's Tour but have run into some grief when trying to get possible future positions for the knight.
For positions I am using a very simple struct:
public struct Position
{
public enum BoardRank { A = 1, B, C, D, E, F, G, H };
public BoardRank X;
public int Y;
}
In the knight class, my PossibleMoves() method begins like this:
public List<Position> PossibleMoves(Position position)
{
List<Position> positions = new List<Position>();
int[] multiply = new int[]{1, -1};
foreach (int times in multiply)
{
try{
Position possible = new Position();
possible.X = position.X + (Behaviour.X * times);
possible.Y = position.Y + (Behaviour.Y * times);
positions.Add(possible);
}
...
For position = A1 and times = -1, you can see how Behaviour.X could quickly fall out of bounds, but I assumed this would have been picked up by the try block.
I tried adding a {get; set;} on the enum declaration but that threw some useless syntax errors in VS2010.
Is there anything else I can try here to stop my enum from going out of bounds?
I assumed this would have been picked up by the try block.
Nope. Enums in C# are "named numbers" effectively. They're not a complete set of values for the type.
Is there anything else I can try here to stop my enum from going out of bounds?
You can use Enum.IsDefined to check whether a value exists in the original enum. I would personally stop using public fields, and instead make Position immutable - then validate the value in the constructor. You could also have methods such as WithX which returned a new value based on the current value with just X changing. While you've got public fields, you're never going to be able to trust that any particular value is valid though.
It may be useful to use modulo to keep the values within a specific range:
possible.X = (position.X + (Behaviour.X * times)) % ((int)BoardRank.H + 1);
This way I am not sure whether an enum is the best solution here, as we're working with integers anyway. The numbers must be a sequence with no gaps and you have to make sure you take the highest defined enum value plus one. Thus, if you add a I to your enum, you need to change the modul.
Here I have a very simple program to illustrate how it works:
enum Foo { A, B, C }
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i % ((int)Foo.C + 1));
}
}
As you see we take i modulo C + 1 which makes C's integer value the actual range maximum. This is the output:
0, 1, 2, 0, 1, 2, 0, 1, 2, 0

Generate distinctly different RGB colors in graphs

When generating graphs and showing different sets of data it usually a good idea to difference the sets by color. So one line is red and the next is green and so on. The problem is then that when the number of datasets is unknown one needs to randomly generate these colors and often they end up very close to each other (green, light green for example).
Any ideas on how this could be solved and how it would be possibler to generate distinctly different colors?
I'd be great if any examples (feel free to just discuss the problem and solution without examples if you find that easier) were in C# and RGB based colors.
You have three colour channels 0 to 255 R, G and B.
First go through
0, 0, 255
0, 255, 0
255, 0, 0
Then go through
0, 255, 255
255, 0, 255
255, 255, 0
Then divide by 2 => 128 and start again:
0, 0, 128
0, 128, 0
128, 0, 0
0, 128, 128
128, 0, 128
128, 128, 0
Divide by 2 => 64
Next time add 64 to 128 => 192
follow the pattern.
Straightforward to program and gives you fairly distinct colours.
EDIT: Request for code sample
Also - adding in the additional pattern as below if gray is an acceptable colour:
255, 255, 255
128, 128, 128
There are a number of ways you can handle generating these in code.
The Easy Way
If you can guarantee that you will never need more than a fixed number of colours, just generate an array of colours following this pattern and use those:
static string[] ColourValues = new string[] {
"FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", "000000",
"800000", "008000", "000080", "808000", "800080", "008080", "808080",
"C00000", "00C000", "0000C0", "C0C000", "C000C0", "00C0C0", "C0C0C0",
"400000", "004000", "000040", "404000", "400040", "004040", "404040",
"200000", "002000", "000020", "202000", "200020", "002020", "202020",
"600000", "006000", "000060", "606000", "600060", "006060", "606060",
"A00000", "00A000", "0000A0", "A0A000", "A000A0", "00A0A0", "A0A0A0",
"E00000", "00E000", "0000E0", "E0E000", "E000E0", "00E0E0", "E0E0E0",
};
The Hard Way
If you don't know how many colours you are going to need, the code below will generate up to 896 colours using this pattern. (896 = 256 * 7 / 2) 256 is the colour space per channel, we have 7 patterns and we stop before we get to colours separated by only 1 colour value.
I've probably made harder work of this code than I needed to. First, there is an intensity generator which starts at 255, then generates the values as per the pattern described above. The pattern generator just loops through the seven colour patterns.
using System;
class Program {
static void Main(string[] args) {
ColourGenerator generator = new ColourGenerator();
for (int i = 0; i < 896; i++) {
Console.WriteLine(string.Format("{0}: {1}", i, generator.NextColour()));
}
}
}
public class ColourGenerator {
private int index = 0;
private IntensityGenerator intensityGenerator = new IntensityGenerator();
public string NextColour() {
string colour = string.Format(PatternGenerator.NextPattern(index),
intensityGenerator.NextIntensity(index));
index++;
return colour;
}
}
public class PatternGenerator {
public static string NextPattern(int index) {
switch (index % 7) {
case 0: return "{0}0000";
case 1: return "00{0}00";
case 2: return "0000{0}";
case 3: return "{0}{0}00";
case 4: return "{0}00{0}";
case 5: return "00{0}{0}";
case 6: return "{0}{0}{0}";
default: throw new Exception("Math error");
}
}
}
public class IntensityGenerator {
private IntensityValueWalker walker;
private int current;
public string NextIntensity(int index) {
if (index == 0) {
current = 255;
}
else if (index % 7 == 0) {
if (walker == null) {
walker = new IntensityValueWalker();
}
else {
walker.MoveNext();
}
current = walker.Current.Value;
}
string currentText = current.ToString("X");
if (currentText.Length == 1) currentText = "0" + currentText;
return currentText;
}
}
public class IntensityValue {
private IntensityValue mChildA;
private IntensityValue mChildB;
public IntensityValue(IntensityValue parent, int value, int level) {
if (level > 7) throw new Exception("There are no more colours left");
Value = value;
Parent = parent;
Level = level;
}
public int Level { get; set; }
public int Value { get; set; }
public IntensityValue Parent { get; set; }
public IntensityValue ChildA {
get {
return mChildA ?? (mChildA = new IntensityValue(this, this.Value - (1<<(7-Level)), Level+1));
}
}
public IntensityValue ChildB {
get {
return mChildB ?? (mChildB = new IntensityValue(this, Value + (1<<(7-Level)), Level+1));
}
}
}
public class IntensityValueWalker {
public IntensityValueWalker() {
Current = new IntensityValue(null, 1<<7, 1);
}
public IntensityValue Current { get; set; }
public void MoveNext() {
if (Current.Parent == null) {
Current = Current.ChildA;
}
else if (Current.Parent.ChildA == Current) {
Current = Current.Parent.ChildB;
}
else {
int levelsUp = 1;
Current = Current.Parent;
while (Current.Parent != null && Current == Current.Parent.ChildB) {
Current = Current.Parent;
levelsUp++;
}
if (Current.Parent != null) {
Current = Current.Parent.ChildB;
}
else {
levelsUp++;
}
for (int i = 0; i < levelsUp; i++) {
Current = Current.ChildA;
}
}
}
}
To implement a variation list where by your colors go, 255 then use all possibilities of that up, then add 0 and all RGB patterns with those two values. Then add 128 and all RGB combinations with those. Then 64. Then 192. Etc.
In Java,
public Color getColor(int i) {
return new Color(getRGB(i));
}
public int getRGB(int index) {
int[] p = getPattern(index);
return getElement(p[0]) << 16 | getElement(p[1]) << 8 | getElement(p[2]);
}
public int getElement(int index) {
int value = index - 1;
int v = 0;
for (int i = 0; i < 8; i++) {
v = v | (value & 1);
v <<= 1;
value >>= 1;
}
v >>= 1;
return v & 0xFF;
}
public int[] getPattern(int index) {
int n = (int)Math.cbrt(index);
index -= (n*n*n);
int[] p = new int[3];
Arrays.fill(p,n);
if (index == 0) {
return p;
}
index--;
int v = index % 3;
index = index / 3;
if (index < n) {
p[v] = index % n;
return p;
}
index -= n;
p[v ] = index / n;
p[++v % 3] = index % n;
return p;
}
This will produce patterns of that type infinitely (2^24) into the future. However, after a hundred or so spots you likely won't see much of a difference between a color with 0 or 32 in the blue's place.
You might be better off normalizing this into a different color space. LAB color space for example with the L,A,B values normalized and converted. So the distinctness of the color is pushed through something more akin to the human eye.
getElement() reverses the endian of an 8 bit number, and starts counting from -1 rather than 0 (masking with 255). So it goes 255,0,127,192,64,... as the number grows it moves less and less significant bits, subdividing the number.
getPattern() determines what the most significant element in the pattern should be (it's the cube root). Then proceeds to break down the 3N²+3N+1 different patterns that involve that most significant element.
This algorithm will produce (first 128 values):
#FFFFFF
#000000
#FF0000
#00FF00
#0000FF
#FFFF00
#00FFFF
#FF00FF
#808080
#FF8080
#80FF80
#8080FF
#008080
#800080
#808000
#FFFF80
#80FFFF
#FF80FF
#FF0080
#80FF00
#0080FF
#00FF80
#8000FF
#FF8000
#000080
#800000
#008000
#404040
#FF4040
#40FF40
#4040FF
#004040
#400040
#404000
#804040
#408040
#404080
#FFFF40
#40FFFF
#FF40FF
#FF0040
#40FF00
#0040FF
#FF8040
#40FF80
#8040FF
#00FF40
#4000FF
#FF4000
#000040
#400000
#004000
#008040
#400080
#804000
#80FF40
#4080FF
#FF4080
#800040
#408000
#004080
#808040
#408080
#804080
#C0C0C0
#FFC0C0
#C0FFC0
#C0C0FF
#00C0C0
#C000C0
#C0C000
#80C0C0
#C080C0
#C0C080
#40C0C0
#C040C0
#C0C040
#FFFFC0
#C0FFFF
#FFC0FF
#FF00C0
#C0FF00
#00C0FF
#FF80C0
#C0FF80
#80C0FF
#FF40C0
#C0FF40
#40C0FF
#00FFC0
#C000FF
#FFC000
#0000C0
#C00000
#00C000
#0080C0
#C00080
#80C000
#0040C0
#C00040
#40C000
#80FFC0
#C080FF
#FFC080
#8000C0
#C08000
#00C080
#8080C0
#C08080
#80C080
#8040C0
#C08040
#40C080
#40FFC0
#C040FF
#FFC040
#4000C0
#C04000
#00C040
#4080C0
#C04080
#80C040
#4040C0
#C04040
#40C040
#202020
#FF2020
#20FF20
Read left to right, top to bottom. 729 colors (9³). So all the patterns up to n = 9. You'll notice the speed at which they start to clash. There's only so many WRGBCYMK variations. And this solution, while clever basically only does different shades of primary colors.
Much of the clashing is due to green and how similar most greens look to most people. The demand that each be maximally different at start rather than just different enough to not be the same color. And basic flaws in the idea resulting in primary colors patterns, and identical hues.
Using CIELab2000 Color Space and Distance Routine to randomly select and try 10k different colors and find the maximally-distant minimum-distance from previous colors, (pretty much the definition of the request) avoids clashing longer than the above solution:
Which could be just called a static list for the Easy Way. It took an hour and a half to generate 729 entries:
#9BC4E5
#310106
#04640D
#FEFB0A
#FB5514
#E115C0
#00587F
#0BC582
#FEB8C8
#9E8317
#01190F
#847D81
#58018B
#B70639
#703B01
#F7F1DF
#118B8A
#4AFEFA
#FCB164
#796EE6
#000D2C
#53495F
#F95475
#61FC03
#5D9608
#DE98FD
#98A088
#4F584E
#248AD0
#5C5300
#9F6551
#BCFEC6
#932C70
#2B1B04
#B5AFC4
#D4C67A
#AE7AA1
#C2A393
#0232FD
#6A3A35
#BA6801
#168E5C
#16C0D0
#C62100
#014347
#233809
#42083B
#82785D
#023087
#B7DAD2
#196956
#8C41BB
#ECEDFE
#2B2D32
#94C661
#F8907D
#895E6B
#788E95
#FB6AB8
#576094
#DB1474
#8489AE
#860E04
#FBC206
#6EAB9B
#F2CDFE
#645341
#760035
#647A41
#496E76
#E3F894
#F9D7CD
#876128
#A1A711
#01FB92
#FD0F31
#BE8485
#C660FB
#120104
#D48958
#05AEE8
#C3C1BE
#9F98F8
#1167D9
#D19012
#B7D802
#826392
#5E7A6A
#B29869
#1D0051
#8BE7FC
#76E0C1
#BACFA7
#11BA09
#462C36
#65407D
#491803
#F5D2A8
#03422C
#72A46E
#128EAC
#47545E
#B95C69
#A14D12
#C4C8FA
#372A55
#3F3610
#D3A2C6
#719FFA
#0D841A
#4C5B32
#9DB3B7
#B14F8F
#747103
#9F816D
#D26A5B
#8B934B
#F98500
#002935
#D7F3FE
#FCB899
#1C0720
#6B5F61
#F98A9D
#9B72C2
#A6919D
#2C3729
#D7C70B
#9F9992
#EFFBD0
#FDE2F1
#923A52
#5140A7
#BC14FD
#6D706C
#0007C4
#C6A62F
#000C14
#904431
#600013
#1C1B08
#693955
#5E7C99
#6C6E82
#D0AFB3
#493B36
#AC93CE
#C4BA9C
#09C4B8
#69A5B8
#374869
#F868ED
#E70850
#C04841
#C36333
#700366
#8A7A93
#52351D
#B503A2
#D17190
#A0F086
#7B41FC
#0EA64F
#017499
#08A882
#7300CD
#A9B074
#4E6301
#AB7E41
#547FF4
#134DAC
#FDEC87
#056164
#FE12A0
#C264BA
#939DAD
#0BCDFA
#277442
#1BDE4A
#826958
#977678
#BAFCE8
#7D8475
#8CCF95
#726638
#FEA8EB
#EAFEF0
#6B9279
#C2FE4B
#304041
#1EA6A7
#022403
#062A47
#054B17
#F4C673
#02FEC7
#9DBAA8
#775551
#835536
#565BCC
#80D7D2
#7AD607
#696F54
#87089A
#664B19
#242235
#7DB00D
#BFC7D6
#D5A97E
#433F31
#311A18
#FDB2AB
#D586C9
#7A5FB1
#32544A
#EFE3AF
#859D96
#2B8570
#8B282D
#E16A07
#4B0125
#021083
#114558
#F707F9
#C78571
#7FB9BC
#FC7F4B
#8D4A92
#6B3119
#884F74
#994E4F
#9DA9D3
#867B40
#CED5C4
#1CA2FE
#D9C5B4
#FEAA00
#507B01
#A7D0DB
#53858D
#588F4A
#FBEEEC
#FC93C1
#D7CCD4
#3E4A02
#C8B1E2
#7A8B62
#9A5AE2
#896C04
#B1121C
#402D7D
#858701
#D498A6
#B484EF
#5C474C
#067881
#C0F9FC
#726075
#8D3101
#6C93B2
#A26B3F
#AA6582
#4F4C4F
#5A563D
#E83005
#32492D
#FC7272
#B9C457
#552A5B
#B50464
#616E79
#DCE2E4
#CF8028
#0AE2F0
#4F1E24
#FD5E46
#4B694E
#C5DEFC
#5DC262
#022D26
#7776B8
#FD9F66
#B049B8
#988F73
#BE385A
#2B2126
#54805A
#141B55
#67C09B
#456989
#DDC1D9
#166175
#C1E29C
#A397B5
#2E2922
#ABDBBE
#B4A6A8
#A06B07
#A99949
#0A0618
#B14E2E
#60557D
#D4A556
#82A752
#4A005B
#3C404F
#6E6657
#7E8BD5
#1275B8
#D79E92
#230735
#661849
#7A8391
#FE0F7B
#B0B6A9
#629591
#D05591
#97B68A
#97939A
#035E38
#53E19E
#DFD7F9
#02436C
#525A72
#059A0E
#3E736C
#AC8E87
#D10C92
#B9906E
#66BDFD
#C0ABFD
#0734BC
#341224
#8AAAC1
#0E0B03
#414522
#6A2F3E
#2D9A8A
#4568FD
#FDE6D2
#FEE007
#9A003C
#AC8190
#DCDD58
#B7903D
#1F2927
#9B02E6
#827A71
#878B8A
#8F724F
#AC4B70
#37233B
#385559
#F347C7
#9DB4FE
#D57179
#DE505A
#37F7DD
#503500
#1C2401
#DD0323
#00A4BA
#955602
#FA5B94
#AA766C
#B8E067
#6A807E
#4D2E27
#73BED7
#D7BC8A
#614539
#526861
#716D96
#829A17
#210109
#436C2D
#784955
#987BAB
#8F0152
#0452FA
#B67757
#A1659F
#D4F8D8
#48416F
#DEBAAF
#A5A9AA
#8C6B83
#403740
#70872B
#D9744D
#151E2C
#5C5E5E
#B47C02
#F4CBD0
#E49D7D
#DD9954
#B0A18B
#2B5308
#EDFD64
#9D72FC
#2A3351
#68496C
#C94801
#EED05E
#826F6D
#E0D6BB
#5B6DB4
#662F98
#0C97CA
#C1CA89
#755A03
#DFA619
#CD70A8
#BBC9C7
#F6BCE3
#A16462
#01D0AA
#87C6B3
#E7B2FA
#D85379
#643AD5
#D18AAE
#13FD5E
#B3E3FD
#C977DB
#C1A7BB
#9286CB
#A19B6A
#8FFED7
#6B1F17
#DF503A
#10DDD7
#9A8457
#60672F
#7D327D
#DD8782
#59AC42
#82FDB8
#FC8AE7
#909F6F
#B691AE
#B811CD
#BCB24E
#CB4BD9
#2B2304
#AA9501
#5D5096
#403221
#F9FAB4
#3990FC
#70DE7F
#95857F
#84A385
#50996F
#797B53
#7B6142
#81D5FE
#9CC428
#0B0438
#3E2005
#4B7C91
#523854
#005EA9
#F0C7AD
#ACB799
#FAC08E
#502239
#BFAB6A
#2B3C48
#0EB5D8
#8A5647
#49AF74
#067AE9
#F19509
#554628
#4426A4
#7352C9
#3F4287
#8B655E
#B480BF
#9BA74C
#5F514C
#CC9BDC
#BA7942
#1C4138
#3C3C3A
#29B09C
#02923F
#701D2B
#36577C
#3F00EA
#3D959E
#440601
#8AEFF3
#6D442A
#BEB1A8
#A11C02
#8383FE
#A73839
#DBDE8A
#0283B3
#888597
#32592E
#F5FDFA
#01191B
#AC707A
#B6BD03
#027B59
#7B4F08
#957737
#83727D
#035543
#6F7E64
#C39999
#52847A
#925AAC
#77CEDA
#516369
#E0D7D0
#FCDD97
#555424
#96E6B6
#85BB74
#5E2074
#BD5E48
#9BEE53
#1A351E
#3148CD
#71575F
#69A6D0
#391A62
#E79EA0
#1C0F03
#1B1636
#D20C39
#765396
#7402FE
#447F3E
#CFD0A8
#3A2600
#685AFC
#A4B3C6
#534302
#9AA097
#FD5154
#9B0085
#403956
#80A1A7
#6E7A9A
#605E6A
#86F0E2
#5A2B01
#7E3D43
#ED823B
#32331B
#424837
#40755E
#524F48
#B75807
#B40080
#5B8CA1
#FDCFE5
#CCFEAC
#755847
#CAB296
#C0D6E3
#2D7100
#D5E4DE
#362823
#69C63C
#AC3801
#163132
#4750A6
#61B8B2
#FCC4B5
#DEBA2E
#FE0449
#737930
#8470AB
#687D87
#D7B760
#6AAB86
#8398B8
#B7B6BF
#92C4A1
#B6084F
#853B5E
#D0BCBA
#92826D
#C6DDC6
#BE5F5A
#280021
#435743
#874514
#63675A
#E97963
#8F9C9E
#985262
#909081
#023508
#DDADBF
#D78493
#363900
#5B0120
#603C47
#C3955D
#AC61CB
#FD7BA7
#716C74
#8D895B
#071001
#82B4F2
#B6BBD8
#71887A
#8B9FE3
#997158
#65A6AB
#2E3067
#321301
#FEECCB
#3B5E72
#C8FE85
#A1DCDF
#CB49A6
#B1C5E4
#3E5EB0
#88AEA7
#04504C
#975232
#6786B9
#068797
#9A98C4
#A1C3C2
#1C3967
#DBEA07
#789658
#E7E7C6
#A6C886
#957F89
#752E62
#171518
#A75648
#01D26F
#0F535D
#047E76
#C54754
#5D6E88
#AB9483
#803B99
#FA9C48
#4A8A22
#654A5C
#965F86
#9D0CBB
#A0E8A0
#D3DBFA
#FD908F
#AEAB85
#A13B89
#F1B350
#066898
#948A42
#C8BEDE
#19252C
#7046AA
#E1EEFC
#3E6557
#CD3F26
#2B1925
#DDAD94
#C0B109
#37DFFE
#039676
#907468
#9E86A5
#3A1B49
#BEE5B7
#C29501
#9E3645
#DC580A
#645631
#444B4B
#FD1A63
#DDE5AE
#887800
#36006F
#3A6260
#784637
#FEA0B7
#A3E0D2
#6D6316
#5F7172
#B99EC7
#777A7E
#E0FEFD
#E16DC5
#01344B
#F8F8FC
#9F9FB5
#182617
#FE3D21
#7D0017
#822F21
#EFD9DC
#6E68C4
#35473E
#007523
#767667
#A6825D
#83DC5F
#227285
#A95E34
#526172
#979730
#756F6D
#716259
#E8B2B5
#B6C9BB
#9078DA
#4F326E
#B2387B
#888C6F
#314B5F
#E5B678
#38A3C6
#586148
#5C515B
#CDCCE1
#C8977F
Using brute force to (testing all 16,777,216 RGB colors through CIELab Delta2000 / Starting with black) produces a series. Which starts to clash at around 26 but could make it to 30 or 40 with visual inspection and manual dropping (which can't be done with a computer). So doing the absolute maximum one can programmatically only makes a couple dozen distinct colors. A discrete list is your best bet. You will get more discrete colors with a list than you would programmatically. The easy way is the best solution, start mixing and matching with other ways to alter your data than color.
#000000
#00FF00
#0000FF
#FF0000
#01FFFE
#FFA6FE
#FFDB66
#006401
#010067
#95003A
#007DB5
#FF00F6
#FFEEE8
#774D00
#90FB92
#0076FF
#D5FF00
#FF937E
#6A826C
#FF029D
#FE8900
#7A4782
#7E2DD2
#85A900
#FF0056
#A42400
#00AE7E
#683D3B
#BDC6FF
#263400
#BDD393
#00B917
#9E008E
#001544
#C28C9F
#FF74A3
#01D0FF
#004754
#E56FFE
#788231
#0E4CA1
#91D0CB
#BE9970
#968AE8
#BB8800
#43002C
#DEFF74
#00FFC6
#FFE502
#620E00
#008F9C
#98FF52
#7544B1
#B500FF
#00FF78
#FF6E41
#005F39
#6B6882
#5FAD4E
#A75740
#A5FFD2
#FFB167
#009BFF
#E85EBE
Update:
I continued this for about a month so, at 1024 brute force.
public static final String[] indexcolors = new String[]{
"#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059",
"#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87",
"#5A0007", "#809693", "#FEFFE6", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80",
"#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100",
"#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F",
"#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09",
"#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66",
"#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C",
"#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81",
"#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00",
"#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700",
"#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329",
"#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C",
"#83AB58", "#001C1E", "#D1F7CE", "#004B28", "#C8D0F6", "#A3A489", "#806C66", "#222800",
"#BF5650", "#E83000", "#66796D", "#DA007C", "#FF1A59", "#8ADBB4", "#1E0200", "#5B4E51",
"#C895C5", "#320033", "#FF6832", "#66E1D3", "#CFCDAC", "#D0AC94", "#7ED379", "#012C58",
"#7A7BFF", "#D68E01", "#353339", "#78AFA1", "#FEB2C6", "#75797C", "#837393", "#943A4D",
"#B5F4FF", "#D2DCD5", "#9556BD", "#6A714A", "#001325", "#02525F", "#0AA3F7", "#E98176",
"#DBD5DD", "#5EBCD1", "#3D4F44", "#7E6405", "#02684E", "#962B75", "#8D8546", "#9695C5",
"#E773CE", "#D86A78", "#3E89BE", "#CA834E", "#518A87", "#5B113C", "#55813B", "#E704C4",
"#00005F", "#A97399", "#4B8160", "#59738A", "#FF5DA7", "#F7C9BF", "#643127", "#513A01",
"#6B94AA", "#51A058", "#A45B02", "#1D1702", "#E20027", "#E7AB63", "#4C6001", "#9C6966",
"#64547B", "#97979E", "#006A66", "#391406", "#F4D749", "#0045D2", "#006C31", "#DDB6D0",
"#7C6571", "#9FB2A4", "#00D891", "#15A08A", "#BC65E9", "#FFFFFE", "#C6DC99", "#203B3C",
"#671190", "#6B3A64", "#F5E1FF", "#FFA0F2", "#CCAA35", "#374527", "#8BB400", "#797868",
"#C6005A", "#3B000A", "#C86240", "#29607C", "#402334", "#7D5A44", "#CCB87C", "#B88183",
"#AA5199", "#B5D6C3", "#A38469", "#9F94F0", "#A74571", "#B894A6", "#71BB8C", "#00B433",
"#789EC9", "#6D80BA", "#953F00", "#5EFF03", "#E4FFFC", "#1BE177", "#BCB1E5", "#76912F",
"#003109", "#0060CD", "#D20096", "#895563", "#29201D", "#5B3213", "#A76F42", "#89412E",
"#1A3A2A", "#494B5A", "#A88C85", "#F4ABAA", "#A3F3AB", "#00C6C8", "#EA8B66", "#958A9F",
"#BDC9D2", "#9FA064", "#BE4700", "#658188", "#83A485", "#453C23", "#47675D", "#3A3F00",
"#061203", "#DFFB71", "#868E7E", "#98D058", "#6C8F7D", "#D7BFC2", "#3C3E6E", "#D83D66",
"#2F5D9B", "#6C5E46", "#D25B88", "#5B656C", "#00B57F", "#545C46", "#866097", "#365D25",
"#252F99", "#00CCFF", "#674E60", "#FC009C", "#92896B", "#1E2324", "#DEC9B2", "#9D4948",
"#85ABB4", "#342142", "#D09685", "#A4ACAC", "#00FFFF", "#AE9C86", "#742A33", "#0E72C5",
"#AFD8EC", "#C064B9", "#91028C", "#FEEDBF", "#FFB789", "#9CB8E4", "#AFFFD1", "#2A364C",
"#4F4A43", "#647095", "#34BBFF", "#807781", "#920003", "#B3A5A7", "#018615", "#F1FFC8",
"#976F5C", "#FF3BC1", "#FF5F6B", "#077D84", "#F56D93", "#5771DA", "#4E1E2A", "#830055",
"#02D346", "#BE452D", "#00905E", "#BE0028", "#6E96E3", "#007699", "#FEC96D", "#9C6A7D",
"#3FA1B8", "#893DE3", "#79B4D6", "#7FD4D9", "#6751BB", "#B28D2D", "#E27A05", "#DD9CB8",
"#AABC7A", "#980034", "#561A02", "#8F7F00", "#635000", "#CD7DAE", "#8A5E2D", "#FFB3E1",
"#6B6466", "#C6D300", "#0100E2", "#88EC69", "#8FCCBE", "#21001C", "#511F4D", "#E3F6E3",
"#FF8EB1", "#6B4F29", "#A37F46", "#6A5950", "#1F2A1A", "#04784D", "#101835", "#E6E0D0",
"#FF74FE", "#00A45F", "#8F5DF8", "#4B0059", "#412F23", "#D8939E", "#DB9D72", "#604143",
"#B5BACE", "#989EB7", "#D2C4DB", "#A587AF", "#77D796", "#7F8C94", "#FF9B03", "#555196",
"#31DDAE", "#74B671", "#802647", "#2A373F", "#014A68", "#696628", "#4C7B6D", "#002C27",
"#7A4522", "#3B5859", "#E5D381", "#FFF3FF", "#679FA0", "#261300", "#2C5742", "#9131AF",
"#AF5D88", "#C7706A", "#61AB1F", "#8CF2D4", "#C5D9B8", "#9FFFFB", "#BF45CC", "#493941",
"#863B60", "#B90076", "#003177", "#C582D2", "#C1B394", "#602B70", "#887868", "#BABFB0",
"#030012", "#D1ACFE", "#7FDEFE", "#4B5C71", "#A3A097", "#E66D53", "#637B5D", "#92BEA5",
"#00F8B3", "#BEDDFF", "#3DB5A7", "#DD3248", "#B6E4DE", "#427745", "#598C5A", "#B94C59",
"#8181D5", "#94888B", "#FED6BD", "#536D31", "#6EFF92", "#E4E8FF", "#20E200", "#FFD0F2",
"#4C83A1", "#BD7322", "#915C4E", "#8C4787", "#025117", "#A2AA45", "#2D1B21", "#A9DDB0",
"#FF4F78", "#528500", "#009A2E", "#17FCE4", "#71555A", "#525D82", "#00195A", "#967874",
"#555558", "#0B212C", "#1E202B", "#EFBFC4", "#6F9755", "#6F7586", "#501D1D", "#372D00",
"#741D16", "#5EB393", "#B5B400", "#DD4A38", "#363DFF", "#AD6552", "#6635AF", "#836BBA",
"#98AA7F", "#464836", "#322C3E", "#7CB9BA", "#5B6965", "#707D3D", "#7A001D", "#6E4636",
"#443A38", "#AE81FF", "#489079", "#897334", "#009087", "#DA713C", "#361618", "#FF6F01",
"#006679", "#370E77", "#4B3A83", "#C9E2E6", "#C44170", "#FF4526", "#73BE54", "#C4DF72",
"#ADFF60", "#00447D", "#DCCEC9", "#BD9479", "#656E5B", "#EC5200", "#FF6EC2", "#7A617E",
"#DDAEA2", "#77837F", "#A53327", "#608EFF", "#B599D7", "#A50149", "#4E0025", "#C9B1A9",
"#03919A", "#1B2A25", "#E500F1", "#982E0B", "#B67180", "#E05859", "#006039", "#578F9B",
"#305230", "#CE934C", "#B3C2BE", "#C0BAC0", "#B506D3", "#170C10", "#4C534F", "#224451",
"#3E4141", "#78726D", "#B6602B", "#200441", "#DDB588", "#497200", "#C5AAB6", "#033C61",
"#71B2F5", "#A9E088", "#4979B0", "#A2C3DF", "#784149", "#2D2B17", "#3E0E2F", "#57344C",
"#0091BE", "#E451D1", "#4B4B6A", "#5C011A", "#7C8060", "#FF9491", "#4C325D", "#005C8B",
"#E5FDA4", "#68D1B6", "#032641", "#140023", "#8683A9", "#CFFF00", "#A72C3E", "#34475A",
"#B1BB9A", "#B4A04F", "#8D918E", "#A168A6", "#813D3A", "#425218", "#DA8386", "#776133",
"#563930", "#8498AE", "#90C1D3", "#B5666B", "#9B585E", "#856465", "#AD7C90", "#E2BC00",
"#E3AAE0", "#B2C2FE", "#FD0039", "#009B75", "#FFF46D", "#E87EAC", "#DFE3E6", "#848590",
"#AA9297", "#83A193", "#577977", "#3E7158", "#C64289", "#EA0072", "#C4A8CB", "#55C899",
"#E78FCF", "#004547", "#F6E2E3", "#966716", "#378FDB", "#435E6A", "#DA0004", "#1B000F",
"#5B9C8F", "#6E2B52", "#011115", "#E3E8C4", "#AE3B85", "#EA1CA9", "#FF9E6B", "#457D8B",
"#92678B", "#00CDBB", "#9CCC04", "#002E38", "#96C57F", "#CFF6B4", "#492818", "#766E52",
"#20370E", "#E3D19F", "#2E3C30", "#B2EACE", "#F3BDA4", "#A24E3D", "#976FD9", "#8C9FA8",
"#7C2B73", "#4E5F37", "#5D5462", "#90956F", "#6AA776", "#DBCBF6", "#DA71FF", "#987C95",
"#52323C", "#BB3C42", "#584D39", "#4FC15F", "#A2B9C1", "#79DB21", "#1D5958", "#BD744E",
"#160B00", "#20221A", "#6B8295", "#00E0E4", "#102401", "#1B782A", "#DAA9B5", "#B0415D",
"#859253", "#97A094", "#06E3C4", "#47688C", "#7C6755", "#075C00", "#7560D5", "#7D9F00",
"#C36D96", "#4D913E", "#5F4276", "#FCE4C8", "#303052", "#4F381B", "#E5A532", "#706690",
"#AA9A92", "#237363", "#73013E", "#FF9079", "#A79A74", "#029BDB", "#FF0169", "#C7D2E7",
"#CA8869", "#80FFCD", "#BB1F69", "#90B0AB", "#7D74A9", "#FCC7DB", "#99375B", "#00AB4D",
"#ABAED1", "#BE9D91", "#E6E5A7", "#332C22", "#DD587B", "#F5FFF7", "#5D3033", "#6D3800",
"#FF0020", "#B57BB3", "#D7FFE6", "#C535A9", "#260009", "#6A8781", "#A8ABB4", "#D45262",
"#794B61", "#4621B2", "#8DA4DB", "#C7C890", "#6FE9AD", "#A243A7", "#B2B081", "#181B00",
"#286154", "#4CA43B", "#6A9573", "#A8441D", "#5C727B", "#738671", "#D0CFCB", "#897B77",
"#1F3F22", "#4145A7", "#DA9894", "#A1757A", "#63243C", "#ADAAFF", "#00CDE2", "#DDBC62",
"#698EB1", "#208462", "#00B7E0", "#614A44", "#9BBB57", "#7A5C54", "#857A50", "#766B7E",
"#014833", "#FF8347", "#7A8EBA", "#274740", "#946444", "#EBD8E6", "#646241", "#373917",
"#6AD450", "#81817B", "#D499E3", "#979440", "#011A12", "#526554", "#B5885C", "#A499A5",
"#03AD89", "#B3008B", "#E3C4B5", "#96531F", "#867175", "#74569E", "#617D9F", "#E70452",
"#067EAF", "#A697B6", "#B787A8", "#9CFF93", "#311D19", "#3A9459", "#6E746E", "#B0C5AE",
"#84EDF7", "#ED3488", "#754C78", "#384644", "#C7847B", "#00B6C5", "#7FA670", "#C1AF9E",
"#2A7FFF", "#72A58C", "#FFC07F", "#9DEBDD", "#D97C8E", "#7E7C93", "#62E674", "#B5639E",
"#FFA861", "#C2A580", "#8D9C83", "#B70546", "#372B2E", "#0098FF", "#985975", "#20204C",
"#FF6C60", "#445083", "#8502AA", "#72361F", "#9676A3", "#484449", "#CED6C2", "#3B164A",
"#CCA763", "#2C7F77", "#02227B", "#A37E6F", "#CDE6DC", "#CDFFFB", "#BE811A", "#F77183",
"#EDE6E2", "#CDC6B4", "#FFE09E", "#3A7271", "#FF7B59", "#4E4E01", "#4AC684", "#8BC891",
"#BC8A96", "#CF6353", "#DCDE5C", "#5EAADD", "#F6A0AD", "#E269AA", "#A3DAE4", "#436E83",
"#002E17", "#ECFBFF", "#A1C2B6", "#50003F", "#71695B", "#67C4BB", "#536EFF", "#5D5A48",
"#890039", "#969381", "#371521", "#5E4665", "#AA62C3", "#8D6F81", "#2C6135", "#410601",
"#564620", "#E69034", "#6DA6BD", "#E58E56", "#E3A68B", "#48B176", "#D27D67", "#B5B268",
"#7F8427", "#FF84E6", "#435740", "#EAE408", "#F4F5FF", "#325800", "#4B6BA5", "#ADCEFF",
"#9B8ACC", "#885138", "#5875C1", "#7E7311", "#FEA5CA", "#9F8B5B", "#A55B54", "#89006A",
"#AF756F", "#2A2000", "#576E4A", "#7F9EFF", "#7499A1", "#FFB550", "#00011E", "#D1511C",
"#688151", "#BC908A", "#78C8EB", "#8502FF", "#483D30", "#C42221", "#5EA7FF", "#785715",
"#0CEA91", "#FFFAED", "#B3AF9D", "#3E3D52", "#5A9BC2", "#9C2F90", "#8D5700", "#ADD79C",
"#00768B", "#337D00", "#C59700", "#3156DC", "#944575", "#ECFFDC", "#D24CB2", "#97703C",
"#4C257F", "#9E0366", "#88FFEC", "#B56481", "#396D2B", "#56735F", "#988376", "#9BB195",
"#A9795C", "#E4C5D3", "#9F4F67", "#1E2B39", "#664327", "#AFCE78", "#322EDF", "#86B487",
"#C23000", "#ABE86B", "#96656D", "#250E35", "#A60019", "#0080CF", "#CAEFFF", "#323F61",
"#A449DC", "#6A9D3B", "#FF5AE4", "#636A01", "#D16CDA", "#736060", "#FFBAAD", "#D369B4",
"#FFDED6", "#6C6D74", "#927D5E", "#845D70", "#5B62C1", "#2F4A36", "#E45F35", "#FF3B53",
"#AC84DD", "#762988", "#70EC98", "#408543", "#2C3533", "#2E182D", "#323925", "#19181B",
"#2F2E2C", "#023C32", "#9B9EE2", "#58AFAD", "#5C424D", "#7AC5A6", "#685D75", "#B9BCBD",
"#834357", "#1A7B42", "#2E57AA", "#E55199", "#316E47", "#CD00C5", "#6A004D", "#7FBBEC",
"#F35691", "#D7C54A", "#62ACB7", "#CBA1BC", "#A28A9A", "#6C3F3B", "#FFE47D", "#DCBAE3",
"#5F816D", "#3A404A", "#7DBF32", "#E6ECDC", "#852C19", "#285366", "#B8CB9C", "#0E0D00",
"#4B5D56", "#6B543F", "#E27172", "#0568EC", "#2EB500", "#D21656", "#EFAFFF", "#682021",
"#2D2011", "#DA4CFF", "#70968E", "#FF7B7D", "#4A1930", "#E8C282", "#E7DBBC", "#A68486",
"#1F263C", "#36574E", "#52CE79", "#ADAAA9", "#8A9F45", "#6542D2", "#00FB8C", "#5D697B",
"#CCD27F", "#94A5A1", "#790229", "#E383E6", "#7EA4C1", "#4E4452", "#4B2C00", "#620B70",
"#314C1E", "#874AA6", "#E30091", "#66460A", "#EB9A8B", "#EAC3A3", "#98EAB3", "#AB9180",
"#B8552F", "#1A2B2F", "#94DDC5", "#9D8C76", "#9C8333", "#94A9C9", "#392935", "#8C675E",
"#CCE93A", "#917100", "#01400B", "#449896", "#1CA370", "#E08DA7", "#8B4A4E", "#667776",
"#4692AD", "#67BDA8", "#69255C", "#D3BFFF", "#4A5132", "#7E9285", "#77733C", "#E7A0CC",
"#51A288", "#2C656A", "#4D5C5E", "#C9403A", "#DDD7F3", "#005844", "#B4A200", "#488F69",
"#858182", "#D4E9B9", "#3D7397", "#CAE8CE", "#D60034", "#AA6746", "#9E5585", "#BA6200"
};
I have put up a page online for procedurally generating visually distinct colors:
http://phrogz.net/css/distinct-colors.html
Unlike other answers here that evenly step across RGB or HSV space (where there is a nonlinear relationship between the axis values and the perceptual differences), my page uses the standard CMI(I:c) color distance algorithm to prevent two colors from being too visually close.
The final tab of the page allows you to sort the values in several ways, and then interleave them (ordered shuffle) so that you get very distinct colors placed next to one another.
As of this writing, it only works well in Chrome and Safari, with a shim for Firefox; it uses HTML5 range input sliders in the interface, which IE9 and Firefox do not yet support natively.
I think the HSV (or HSL) space has more opportunities here. If you don't mind the extra conversion, it's pretty easy to go through all the colors by just rotating the Hue value. If that's not enough, you can change the Saturation/Value/Lightness values and go through the rotation again. Or, you can always shift the Hue values or change your "stepping" angle and rotate more times.
There's a flaw in the previous RGB solutions. They don't take advantage of the whole color space since they use a color value and 0 for the channels:
#006600
#330000
#FF00FF
Instead they should be using all the possible color values to generate mixed colors that can have up to 3 different values across the color channels:
#336600
#FF0066
#33FF66
Using the full color space you can generate more distinct colors. For example, if you have 4 values per channel, then 4*4*4=64 colors can be generated. With the other scheme, only 4*7+1=29 colors can be generated.
If you want N colors, then the number of values per channel required is: ceil(cube_root(N))
With that, you can then determine the possible (0-255 range) values (python):
max = 255
segs = int(num**(Decimal("1.0")/3))
step = int(max/segs)
p = [(i*step) for i in xrange(segs)]
values = [max]
values.extend(p)
Then you can iterate over the RGB colors (this is not recommended):
total = 0
for red in values:
for green in values:
for blue in values:
if total <= N:
print color(red, green, blue)
total += 1
Nested loops will work, but are not recommended since it will favor the blue channel and the resulting colors will not have enough red (N will most likely be less than the number of all possible color values).
You can create a better algorithm for the loops where each channel is treated equally and more distinct color values are favored over small ones.
I have a solution, but didn't want to post it since it isn't the easiest to understand or efficient. But, you can view the solution if you really want to.
Here is a sample of 64 generated colors: 64 colors
I needed the same functionality, in a simple form.
What I needed was to generate as unique as possible colors from an an increasing index value.
Here is the code, in C# (Any other language implementation should be very similar)
The mechanism is very simple
A pattern of color_writers get generated from indexA values from 0 to 7.
For indices < 8, those colors are = color_writer[indexA] * 255.
For indices between 8 and 15, those colors are = color_writer[indexA] * 255 + (color_writer[indexA+1]) * 127
For indices between 16 and 23, those colors are = color_writer[indexA] * 255 + (color_writer[indexA+1]) * 127 + (color_writer[indexA+2]) * 63
And so on:
private System.Drawing.Color GetRandColor(int index)
{
byte red = 0;
byte green = 0;
byte blue = 0;
for (int t = 0; t <= index / 8; t++)
{
int index_a = (index+t) % 8;
int index_b = index_a / 2;
//Color writers, take on values of 0 and 1
int color_red = index_a % 2;
int color_blue = index_b % 2;
int color_green = ((index_b + 1) % 3) % 2;
int add = 255 / (t + 1);
red = (byte)(red+color_red * add);
green = (byte)(green + color_green * add);
blue = (byte)(blue + color_blue * add);
}
Color color = Color.FromArgb(red, green, blue);
return color;
}
Note: To avoid generating bright and hard to see colors (in this example: yellow on white background) you can modify it with a recursive loop:
int skip_index = 0;
private System.Drawing.Color GetRandColor(int index)
{
index += skip_index;
byte red = 0;
byte green = 0;
byte blue = 0;
for (int t = 0; t <= index / 8; t++)
{
int index_a = (index+t) % 8;
int index_b = index_a / 2;
//Color writers, take on values of 0 and 1
int color_red = index_a % 2;
int color_blue = index_b % 2;
int color_green = ((index_b + 1) % 3) % 2;
int add = 255 / (t + 1);
red = (byte)(red + color_red * add);
green = (byte)(green + color_green * add);
blue = (byte)(blue + color_blue * add);
}
if(red > 200 && green > 200)
{
skip_index++;
return GetRandColor(index);
}
Color color = Color.FromArgb(red, green, blue);
return color;
}
In case someone needs to generate random medium to high dark color for white foreground in C#, here is the code.
[DllImport("shlwapi.dll")]
public static extern int ColorHLSToRGB(int H, int L, int S);
public static string GetRandomDarkColor()
{
int h = 0, s = 0, l = 0;
h = (RandomObject.Next(1, 2) % 2 == 0) ? RandomObject.Next(0, 180) : iApp.RandomObject.Next(181, 360);
s = RandomObject.Next(90, 160);
l = RandomObject.Next(80, 130);
return System.Drawing.ColorTranslator.FromWin32(ColorHLSToRGB(h, l, s)).ToHex();
}
private static string ToHex(this System.Drawing.Color c)
{
return "#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
}
You can replace RandomObject with your own Random class object.
I would start with a set brightness 100% and go around primary colors first:
FF0000, 00FF00, 0000FF
then the combinations
FFFF00, FF00FF, 00FFFF
next for example halve the brightness and do same round. There's not too many really clearly distinct colors, after these I would start to vary the line width and do dotted/dashed lines etc.
I implemented this algorithm in a shorter way
void ColorValue::SetColorValue( double r, double g, double b, ColorType myType )
{
this->c[0] = r;
this->c[1] = g;
this->c[2] = b;
this->type = myType;
}
DistinctColorGenerator::DistinctColorGenerator()
{
mFactor = 255;
mColorsGenerated = 0;
mpColorCycle = new ColorValue[6];
mpColorCycle[0].SetColorValue( 1.0, 0.0, 0.0, TYPE_RGB);
mpColorCycle[1].SetColorValue( 0.0, 1.0, 0.0, TYPE_RGB);
mpColorCycle[2].SetColorValue( 0.0, 0.0, 1.0, TYPE_RGB);
mpColorCycle[3].SetColorValue( 1.0, 1.0, 0.0, TYPE_RGB);
mpColorCycle[4].SetColorValue( 1.0, 0.0, 1.0, TYPE_RGB);
mpColorCycle[5].SetColorValue( 0.0, 1.0, 1.0, TYPE_RGB);
}
//----------------------------------------------------------
ColorValue DistinctColorGenerator::GenerateNewColor()
{
int innerCycleNr = mColorsGenerated % 6;
int outerCycleNr = mColorsGenerated / 6;
int cycleSize = pow( 2, (int)(log((double)(outerCycleNr)) / log( 2.0 ) ) );
int insideCycleCounter = outerCycleNr % cyclesize;
if ( outerCycleNr == 0)
{
mFactor = 255;
}
else
{
mFactor = ( 256 / ( 2 * cycleSize ) ) + ( insideCycleCounter * ( 256 / cycleSize ) );
}
ColorValue newColor = mpColorCycle[innerCycleNr] * mFactor;
mColorsGenerated++;
return newColor;
}
You could also think of the color space as all combinations of three numbers from 0 to 255, inclusive. That's the base-255 representation of a number between 0 and 255^3, forced to have three decimal places (add zeros on to the end if need be.)
So to generate x number of colors, you'd calculate x evenly spaced percentages, 0 to 100. Get numbers by multiplying those percentages by 255^3, convert those numbers to base 255, and add zeros as previously mentioned.
Base conversion algorithm, for reference (in pseudocode that's quite close to C#):
int num = (number to convert);
int baseConvert = (desired base, 255 in this case);
(array of ints) nums = new (array of ints);
int x = num;
double digits = Math.Log(num, baseConvert); //or ln(num) / ln(baseConvert)
int numDigits = (digits - Math.Ceiling(digits) == 0 ? (int)(digits + 1) : (int)Math.Ceiling(digits)); //go up one if it turns out even
for (int i = 0; i < numDigits; i++)
{
int toAdd = ((int)Math.Floor(x / Math.Pow((double)convertBase, (double)(numDigits - i - 1))));
//Formula for 0th digit: d = num / (convertBase^(numDigits - 1))
//Then subtract (d * convertBase^(numDigits - 1)) from the num and continue
nums.Add(toAdd);
x -= toAdd * (int)Math.Pow((double)convertBase, (double)(numDigits - i - 1));
}
return nums;
You might also have to do something to bring the range in a little bit, to avoid having white and black, if you want. Those numbers aren't actually a smooth color scale, but they'll generate separate colors if you don't have too many.
This question has more on base conversion in .NET.
for getting nth colour. Just this kind of code would be enough. This i have use in my opencv clustering problem. This will create different colours as col changes.
for(int col=1;col<CLUSTER_COUNT+1;col++){
switch(col%6)
{
case 1:cout<<Scalar(0,0,(int)(255/(int)(col/6+1)))<<endl;break;
case 2:cout<<Scalar(0,(int)(255/(int)(col/6+1)),0)<<endl;break;
case 3:cout<<Scalar((int)(255/(int)(col/6+1)),0,0)<<endl;break;
case 4:cout<<Scalar(0,(int)(255/(int)(col/6+1)),(int)(255/(int)(col/6+1)))<<endl;break;
case 5:cout<<Scalar((int)(255/(int)(col/6+1)),0,(int)(255/(int)(col/6+1)))<<endl;break;
case 0:cout<<Scalar((int)(255/(int)(col/6)),(int)(255/(int)(col/6)),0)<<endl;break;
}
}
You could get a random set of your 3 255 values and check it against the last set of 3 values, making sure they are each at least X away from the old values before using them.
OLD: 190, 120, 100
NEW: 180, 200, 30
If X = 20, then the new set would be regenerated again.

Categories