Example #1
0
        internal static IEnumerable <List <TColor> > KmeansPlusPlus <TColor>(IReadOnlyList <TColor> colors, ISpace <TColor> space, int count, int?seed = null)
        {
            var random = seed.HasValue ? new Random(seed.Value) : new Random();

            var centroids = new List <TColor> {
                colors[random.Next(colors.Count)]
            };

            foreach (var c in SelectCentroids().Take(count - 1))
            {
                centroids.Add(c);
            }

            var preClusters = new List <TColor> [count];

            while (true)
            {
                var clusters = Enumerable.Range(0, count).Select(_ => new List <TColor>()).ToArray();
                foreach (var color in colors)
                {
                    clusters[GetNearestCentroidIndex(color)].Add(color);
                }

                if (Enumerable.Range(0, count).All(i => preClusters[i] != null && clusters[i].SequenceEqual(preClusters[i])))
                {
                    return(clusters.OrderByDescending(x => x.Count));
                }

                for (var i = 0; i < count; i++)
                {
                    centroids[i]   = space.GetCentroid(clusters[i]);
                    preClusters[i] = clusters[i];
                }
            }

            int GetNearestCentroidIndex(TColor color)
            {
                var minDistance = double.MaxValue;
                var minIndex    = -1;

                for (var i = 0; i < centroids.Count; i++)
                {
                    var d = space.GetDistance(centroids[i], color);
                    if (d < minDistance)
                    {
                        minDistance = d;
                        minIndex    = i;
                    }
                }

                return(minIndex);
            }

            IEnumerable <TColor> SelectCentroids()
            {
                var distances = colors.Select(x => centroids.Min(c => Math.Pow(space.GetDistance(x, c), 2))).ToArray();
                var sum       = distances.Sum();

                while (true)
                {
                    var init = random.NextDouble();
                    var d    = 0.0;
                    var i    = 0;
                    for (; i < colors.Count; i++)
                    {
                        d += distances[i] / sum;
                        if (d > init)
                        {
                            break;
                        }
                    }

                    yield return(colors[i]);
                }
            }
        }