<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Software Programming</title>
	<atom:link href="http://kunuk.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://kunuk.wordpress.com</link>
	<description>Kunuk Nykjaer</description>
	<lastBuildDate>Thu, 23 Feb 2012 16:34:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='kunuk.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://1.gravatar.com/blavatar/135ee3f33df989c081ac8c2cfc026100?s=96&#038;d=http%3A%2F%2Fs2.wp.com%2Fi%2Fbuttonw-com.png</url>
		<title>Software Programming</title>
		<link>http://kunuk.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://kunuk.wordpress.com/osd.xml" title="Software Programming" />
	<atom:link rel='hub' href='http://kunuk.wordpress.com/?pushpress=hub'/>
		<item>
		<title>Q-learning Framework example with C#</title>
		<link>http://kunuk.wordpress.com/2012/01/14/q-learning-framework-example-with-csharp/</link>
		<comments>http://kunuk.wordpress.com/2012/01/14/q-learning-framework-example-with-csharp/#comments</comments>
		<pubDate>Sat, 14 Jan 2012 17:53:25 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Csharp]]></category>
		<category><![CDATA[Framework]]></category>
		<category><![CDATA[artificial intelligence]]></category>
		<category><![CDATA[Bender]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[example]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[path finding]]></category>
		<category><![CDATA[Q Learning]]></category>
		<category><![CDATA[rock paper scissors]]></category>
		<category><![CDATA[temporal difference]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=565</guid>
		<description><![CDATA[This is a follow up of my previous post &#8211; Q-learning example with Java http://kunuk.wordpress.com/2010/09/24/q-learning/ Imagine you had a robot, just like Bender here and you want it to learn to navigate correctly to the destination (the beer). Your possible moves are move up, right, down or left from any room. And you want some [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=565&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>This is a follow up of my previous post &#8211; Q-learning example with Java<br />
<a href="http://kunuk.wordpress.com/2010/09/24/q-learning/" title="Q-learning example with Java" target="_blank">http://kunuk.wordpress.com/2010/09/24/q-learning/</a></p>
<p><img src="http://kunuk.files.wordpress.com/2012/01/qlearnbender.png?w=700" alt="q-learn-bender" /><br />
Imagine you had a robot, just like Bender here and you want it to learn to navigate correctly to the destination (the beer). Your possible moves are move up, right, down or left from any room. And you want some kind of automatic system that gives you the solution. This is what this framework can do for you. You configure the framework with possible states, possible actions and the action outcomes. E.g. when you move right from the bedroom the action result is Bender has moved to the Hallway and if you move left from the bedroom then Bender hits the wall and stays in the bedroom. You will need to set up a reward value when Bender reaches the beer e.g. 100 points. That&#8217;s it!<br />
No logic programming from your part, just some configuration.</p>
<p>In my previous post I made a quick example in Java and this time I will use C#.<br />
This version will support non-deterministic action outcomes and I will include examples of how to use this framework. The code examples are included at the bottom. You need QLearning.cs to run these examples.</p>
<ul>
<li>Path finding Bender</li>
<li>Path finding demo (same example as in my previous post)</li>
<li>Rock paper scissors demo</li>
<li>Path finding advanced demo</li>
</ul>
<p><strong>Path finding Bender example</strong><br />
Path finding Bender result:<br />
<pre class="brush: plain; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
** Q-Learning structure **
State Bedroom
  Action up
     ActionResult State Bedroom, Prob. 1, Reward 0, PrevState Bedroom, QE 72.9
  Action right
     ActionResult State Hallway, Prob. 1, Reward 0, PrevState Bedroom, QE 81
  Action down
     ActionResult State Bedroom, Prob. 1, Reward 0, PrevState Bedroom, QE 72.9
  Action left
     ActionResult State Bedroom, Prob. 1, Reward 0, PrevState Bedroom, QE 72.9
State Hallway
  Action up
     ActionResult State Hallway, Prob. 1, Reward 0, PrevState Hallway, QE 81
  Action right
     ActionResult State Hallway, Prob. 1, Reward 0, PrevState Hallway, QE 81
  Action down
     ActionResult State Stairwell, Prob. 1, Reward 0, PrevState Hallway, QE 90
  Action left
     ActionResult State Bedroom, Prob. 1, Reward 0, PrevState Hallway, QE 72.9
State Stairwell
  Action up
     ActionResult State Hallway, Prob. 1, Reward 0, PrevState Stairwell, QE 81
  Action right
     ActionResult State Stairwell, Prob. 1, Reward 0, PrevState Stairwell, QE 90
  Action down
     ActionResult State Stairwell, Prob. 1, Reward 0, PrevState Stairwell, QE 90
  Action left
     ActionResult State Kitchen, Prob. 1, Reward 100, PrevState Stairwell, QE 100

** Show Policy **
From state Bedroom do action right, max QEstimated is 81
From state Hallway do action down, max QEstimated is 90
From state Stairwell do action left, max QEstimated is 100
</pre><br />
Bender has learned the fastest way to get some beer. He knows which direction to take from any room.</p>
<p><strong>Path finding example</strong><br />
<img src="http://kunuk.files.wordpress.com/2010/09/q-learn1.png?w=700" alt="q-learn1" /></p>
<p>Path finding result:<br />
<pre class="brush: plain; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
** Q-Learning structure **
State A
  Action from_A_to_B
     ActionResult State B, Prob. 1, Reward 0, PrevState A, QE 90
  Action from_A_to_D
     ActionResult State D, Prob. 1, Reward 0, PrevState A, QE 72.9
State B
  Action from_B_to_A
     ActionResult State A, Prob. 1, Reward 0, PrevState B, QE 81
  Action from_B_to_C
     ActionResult State C, Prob. 1, Reward 100, PrevState B, QE 100
  Action from_B_to_E
     ActionResult State E, Prob. 1, Reward 0, PrevState B, QE 81
State C
  Action from_C_to_C
     ActionResult State C, Prob. 1, Reward 0, PrevState C, QE 0
State D
  Action from_D_to_A
     ActionResult State A, Prob. 1, Reward 0, PrevState D, QE 81
  Action from_D_to_E
     ActionResult State E, Prob. 1, Reward 0, PrevState D, QE 81
State E
  Action from_E_to_B
     ActionResult State B, Prob. 1, Reward 0, PrevState E, QE 90
  Action from_E_to_D
     ActionResult State D, Prob. 1, Reward 0, PrevState E, QE 72.9
  Action from_E_to_F
     ActionResult State F, Prob. 1, Reward 0, PrevState E, QE 90
State F
  Action from_F_to_C
     ActionResult State C, Prob. 1, Reward 100, PrevState F, QE 100
  Action from_F_to_E
     ActionResult State E, Prob. 1, Reward 0, PrevState F, QE 81

** Show Policy **
From state A do action from_A_to_B, max QEstimated is 90
From state B do action from_B_to_C, max QEstimated is 100
From state C do action from_C_to_C, max QEstimated is 0
From state D do action from_D_to_A, max QEstimated is 81
From state E do action from_E_to_B, max QEstimated is 90
From state F do action from_F_to_C, max QEstimated is 100
</pre></p>
<p>The results are exactly the same as in my previous post. </p>
<p><img src="http://kunuk.files.wordpress.com/2012/01/rockpaperscissors.png?w=700" alt="rock-paper-scissors" /></p>
<p>Rock paper scissors result:<br />
<pre class="brush: plain; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
** Q-Learning structure **
State Begin
  Action from_Begin_to_Rock
     ActionResult State Rock, Prob. 0.2, Reward 0, PrevState Begin, QE 0
     ActionResult State Paper, Prob. 0.5, Reward -10, PrevState Begin, QE -0.91
     ActionResult State Scissor, Prob. 0.3, Reward 100, PrevState Begin, QE 4.11

  Action from_Begin_to_Paper
     ActionResult State Rock, Prob. 0.2, Reward 100, PrevState Begin, QE 2.44
     ActionResult State Paper, Prob. 0.5, Reward 0, PrevState Begin, QE 0
     ActionResult State Scissor, Prob. 0.3, Reward -10, PrevState Begin, QE -0.41
  Action from_Begin_to_Scissor
     ActionResult State Rock, Prob. 0.2, Reward -10, PrevState Begin, QE -0.24
     ActionResult State Paper, Prob. 0.5, Reward 100, PrevState Begin, QE 9.09
     ActionResult State Scissor, Prob. 0.3, Reward 0, PrevState Begin, QE 0

** Show Policy **
From state Begin do action from_Begin_to_Scissor, max QEstimated is 9.09

** Opponent style **
style is rock 0.2 paper 0.5 scissor 0.3
</pre></p>
<p>Here the opponents style is to pick paper 50% of the time. The robot learns correctly to pick scissors as the action policy for maximizing the reward outcome.</p>
<p><strong>Path finding advanced example</strong><br />
<img src="http://kunuk.files.wordpress.com/2012/01/q-learn-adv.png?w=700" alt="q-learn-adv" /></p>
<p>In the advanced version of path finding, there is a hill from state B to C.<br />
whenever the robot wants to go climb the hill from state B to C, there is a 10% of success and 90% of chance it will slide back to state A. </p>
<p>Path finding advanced example result:<br />
<pre class="brush: plain; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
** Q-Learning structure **
State A
  Action from_A_to_B
     ActionResult State B, Prob. 1, Reward 0, PrevState A, QE 72.9
  Action from_A_to_D
     ActionResult State D, Prob. 1, Reward 0, PrevState A, QE 72.9
State B
  Action from_B_to_A
     ActionResult State A, Prob. 1, Reward 0, PrevState B, QE 65.61
  Action from_B_to_C
     ActionResult State C, Prob. 0.1, Reward 100, PrevState B, QE 1.1
     ActionResult State A, Prob. 0.9, Reward 0, PrevState B, QE 31.08
  Action from_B_to_E
     ActionResult State E, Prob. 1, Reward 0, PrevState B, QE 81
State C
  Action from_C_to_C
     ActionResult State C, Prob. 1, Reward 0, PrevState C, QE 0
State D
  Action from_D_to_A
     ActionResult State A, Prob. 1, Reward 0, PrevState D, QE 65.61
  Action from_D_to_E
     ActionResult State E, Prob. 1, Reward 0, PrevState D, QE 81
State E
  Action from_E_to_B
     ActionResult State B, Prob. 1, Reward 0, PrevState E, QE 72.9
  Action from_E_to_D
     ActionResult State D, Prob. 1, Reward 0, PrevState E, QE 72.9
  Action from_E_to_F
     ActionResult State F, Prob. 1, Reward 0, PrevState E, QE 90
State F
  Action from_F_to_C
     ActionResult State C, Prob. 1, Reward 100, PrevState F, QE 100
  Action from_F_to_E
     ActionResult State E, Prob. 1, Reward 0, PrevState F, QE 81

** Show Policy **
From state A do action from_A_to_B, max QEstimated is 72.9
From state B do action from_B_to_E, max QEstimated is 81
From state C do action from_C_to_C, max QEstimated is 0
From state D do action from_D_to_E, max QEstimated is 81
From state E do action from_E_to_F, max QEstimated is 90
From state F do action from_F_to_C, max QEstimated is 100
</pre></p>
<p>The robot has learned to take the action &#8216;go to state E&#8217; instead of &#8216;go to state C&#8217; when it is in state B.<br />
10% of success is not good enough and the robot wisely chooses a longer but more rewarding path.</p>
<p>The data structure is as follows:<br />
The QLearning algorithm has multiple states, for every state there are multiple possible actions and for each action there are multiple possible action outcomes.<br />
<img src="http://kunuk.files.wordpress.com/2012/01/qlearningframework.png?w=700" alt="q-learn" /></p>
<p><strong>PathFindingBenderDemo.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;
using QLearningFramework;

namespace ConsoleQLearning
{
    class PathFindingBenderDemo
    {
        // ----------- Insert the state names here -----------
        internal enum StateNameEnum
        {
            Bedroom, Hallway, Stairwell, Kitchen
        }
        // ----------- End Insert the state names here -------

        static void Main(string[] args)
        {
            DateTime starttime = DateTime.Now;

            PathFinding();            

            double timespend = DateTime.Now.Subtract(starttime).TotalSeconds;
            Console.WriteLine(&quot;\n{0} sec. press a key ...&quot;, timespend.Pretty()); Console.ReadKey();
        }         

        static void PathFinding()
        {
            QLearning q = new QLearning();
            QAction fromTo;
            QState state;
            string stateName;
            string stateNameNext;

            // ----------- Begin Insert the path setup here -----------
            // insert the end states here, e.g. goal state
            q.EndStates.Add(StateNameEnum.Kitchen.EnumToString()); 

            // State Bedroom           
            stateName = StateNameEnum.Bedroom.EnumToString();
            q.AddState(state = new QState(stateName, q));
            // action up
            stateNameNext = StateNameEnum.Bedroom.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;up&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action right
            stateNameNext = StateNameEnum.Hallway.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;right&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action down
            stateNameNext = StateNameEnum.Bedroom.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;down&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action left
            stateNameNext = StateNameEnum.Bedroom.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;left&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State Hallway           
            stateName = StateNameEnum.Hallway.EnumToString();
            q.AddState(state = new QState(stateName, q));
            // action up
            stateNameNext = StateNameEnum.Hallway.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;up&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action right
            stateNameNext = StateNameEnum.Hallway.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;right&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action down
            stateNameNext = StateNameEnum.Stairwell.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;down&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action left
            stateNameNext = StateNameEnum.Bedroom.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;left&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State Stairwell           
            stateName = StateNameEnum.Stairwell.EnumToString();
            q.AddState(state = new QState(stateName, q));
            // action up
            stateNameNext = StateNameEnum.Hallway.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;up&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action right
            stateNameNext = StateNameEnum.Stairwell.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;right&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action down
            stateNameNext = StateNameEnum.Stairwell.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;down&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action left
            stateNameNext = StateNameEnum.Kitchen.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(&quot;left&quot;)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0){Reward = 100});            
            // ----------- End Insert the path setup here -----------

            q.RunTraining();
            q.PrintQLearningStructure();
            q.ShowPolicy();
        }
    }
}
</pre></p>
<p><strong>PathFindingDemo.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;
using QLearningFramework;

namespace ConsoleQLearning
{
    class PathFindingDemo
    {
        // ----------- Insert the state names here -----------
        internal enum StateNameEnum
        {
            A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, 
            V, W, X, Y, Z
        }
        // ----------- End Insert the state names here -------

        static void Main(string[] args)
        {
            DateTime starttime = DateTime.Now;

            PathFinding();            

            double timespend = DateTime.Now.Subtract(starttime).TotalSeconds;
            Console.WriteLine(&quot;\n{0} sec. press a key ...&quot;, timespend.Pretty()); Console.ReadKey();
        }         

        static void PathFinding()
        {
            QLearning q = new QLearning { Episodes = 1000, Alpha = 0.1, Gamma = 0.9, 
                MaxExploreStepsWithinOneEpisode = 1000 };

            QAction fromTo;
            QState state;
            string stateName;
            string stateNameNext;

            // ----------- Begin Insert the path setup here -----------
            // insert the end states here, e.g. goal state
            q.EndStates.Add(StateNameEnum.C.EnumToString()); 


            // State A           
            stateName = StateNameEnum.A.EnumToString();
            q.AddState(state = new QState(stateName, q));
            // action A -&gt; B
            stateNameNext = StateNameEnum.B.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext) ));
            // action outcome probability
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action A -&gt; D
            stateNameNext = StateNameEnum.D.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            // action outcome probability
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State B
            stateName = StateNameEnum.B.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // B -&gt; A
            stateNameNext = StateNameEnum.A.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // B -&gt; C
            stateNameNext = StateNameEnum.C.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            // action outcome probability
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0) { Reward = 100 });
            // B -&gt; E
            stateNameNext = StateNameEnum.E.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State C
            stateName = StateNameEnum.C.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // C -&gt; C
            stateNameNext = stateName;
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State D
            stateName = StateNameEnum.D.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // D -&gt; A
            stateNameNext = StateNameEnum.A.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // D -&gt; E
            stateNameNext = StateNameEnum.E.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State E
            stateName = StateNameEnum.E.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // E -&gt; B
            stateNameNext = StateNameEnum.B.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // E -&gt; D
            stateNameNext = StateNameEnum.D.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // E -&gt; F
            stateNameNext = StateNameEnum.F.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State F
            stateName = StateNameEnum.F.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // F -&gt; C
            stateNameNext = StateNameEnum.C.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0) { Reward = 100 });
            // F -&gt; E
            stateNameNext = StateNameEnum.E.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // ----------- End Insert the path setup here -----------

            q.RunTraining();
            q.PrintQLearningStructure();
            q.ShowPolicy();
        }
    }
}
</pre></p>
<p><strong>PathFindingAdvDemo.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;
using QLearningFramework;

namespace ConsoleQLearning
{
    class PathFindingAdvDemo
    {
        // ----------- Insert the state names here -----------
        internal enum StateNameEnum
        {
            A, B, C, D, E, F
        }
        // ----------- End Insert the state names here -------

        static void Main(string[] args)
        {
            DateTime starttime = DateTime.Now;

            PathFinding();            

            double timespend = DateTime.Now.Subtract(starttime).TotalSeconds;
            Console.WriteLine(&quot;\n{0} sec. press a key ...&quot;, timespend.Pretty()); Console.ReadKey();
        }         

        static void PathFinding()
        {
            QLearning q = new QLearning { Episodes = 1000, Alpha = 0.1, Gamma = 0.9, 
                MaxExploreStepsWithinOneEpisode = 1000 };

            QAction fromTo;
            QState state;
            string stateName;
            string stateNameNext;

            // ----------- Begin Insert the path setup here -----------
            // insert the end states here, e.g. goal state
            q.EndStates.Add(StateNameEnum.C.EnumToString()); 


            // State A           
            stateName = StateNameEnum.A.EnumToString();
            q.AddState(state = new QState(stateName, q));
            // action A -&gt; B
            stateNameNext = StateNameEnum.B.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext) ));
            // action outcome probability
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // action A -&gt; D
            stateNameNext = StateNameEnum.D.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            // action outcome probability
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State B
            stateName = StateNameEnum.B.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // B -&gt; A
            stateNameNext = StateNameEnum.A.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // B -&gt; C
            stateNameNext = StateNameEnum.C.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            // action outcome probability
            fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.C.EnumToString(), 0.1) { Reward = 100 });
            fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.A.EnumToString(), 0.9));
            // B -&gt; E
            stateNameNext = StateNameEnum.E.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State C
            stateName = StateNameEnum.C.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // C -&gt; C
            stateNameNext = stateName;
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State D
            stateName = StateNameEnum.D.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // D -&gt; A
            stateNameNext = StateNameEnum.A.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // D -&gt; E
            stateNameNext = StateNameEnum.E.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State E
            stateName = StateNameEnum.E.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // E -&gt; B
            stateNameNext = StateNameEnum.B.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // E -&gt; D
            stateNameNext = StateNameEnum.D.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));
            // E -&gt; F
            stateNameNext = StateNameEnum.F.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // State F
            stateName = StateNameEnum.F.EnumToString();
            q.States.Add(state = new QState(stateName, q));
            // F -&gt; C
            stateNameNext = StateNameEnum.C.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0) { Reward = 100 });
            // F -&gt; E
            stateNameNext = StateNameEnum.E.EnumToString();
            state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
            fromTo.AddActionResult(new QActionResult(fromTo, stateNameNext, 1.0));

            // ----------- End Insert the path setup here -----------

            q.RunTraining();
            q.PrintQLearningStructure();
            q.ShowPolicy();
        }
    }
}
</pre></p>
<p><strong>RockPaperScissorsDemo.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;
using System.Collections.Generic;
using QLearningFramework;

namespace ConsoleQLearning
{
    class RockPaperScissorsDemo
    {
        // ----------- Insert the state names here -----------
        internal enum StateNameEnum
        {
            Begin, Rock, Paper, Scissor
        }
        // ----------- End Insert the state names here -------

        static void Main(string[] args)
        {
            DateTime starttime = DateTime.Now;
            
            RockPaperScissors();

            double timespend = DateTime.Now.Subtract(starttime).TotalSeconds;
            Console.WriteLine(&quot;\n{0} sec. press a key ...&quot;, timespend.Pretty()); Console.ReadKey();
        }

         static void RockPaperScissors()
         {
             QLearning q = new QLearning { Episodes = 1000, Alpha = 0.1, Gamma = 0.9, 
                 MaxExploreStepsWithinOneEpisode = 1000 };            

             var opponentStyles = new List&lt;double[]&gt;();
             // rock paper scissor probability styles
             opponentStyles.Add(new []{ 0.33,0.33,0.33} );
             opponentStyles.Add(new [] { 0.5, 0.3, 0.2 });
             opponentStyles.Add(new [] { 0.2, 0.5, 0.3 });
             opponentStyles.Add(new[] { 0.1, 0.1, 0.8 });
             int index = new Random().Next(opponentStyles.Count);
             var opponent = opponentStyles[index];

             // opponent probability pick 
             double rockOpponent = opponent[0];
             double paperOpponent = opponent[1];
             double scissorOpponent = opponent[2];

             QAction fromTo;
             QState state;
             string stateName;
             string stateNameNext;

             // ----------- Begin Insert the path setup here -----------

             // insert the end states here
             q.EndStates.Add(StateNameEnum.Rock.EnumToString());
             q.EndStates.Add(StateNameEnum.Paper.EnumToString());
             q.EndStates.Add(StateNameEnum.Scissor.EnumToString());

             // State Begin
             stateName = StateNameEnum.Begin.EnumToString();
             q.AddState(state = new QState(stateName, q));             
             
             // action Rock
             stateNameNext = StateNameEnum.Rock.EnumToString();
             state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));             
             // action outcome probability
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Rock.EnumToString(), 
                 rockOpponent) { Reward = 0 });
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Paper.EnumToString(), 
                 paperOpponent) { Reward = -10 });
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Scissor.EnumToString(), 
                 scissorOpponent) { Reward = 100 });             
             
             // action paper
             stateNameNext = StateNameEnum.Paper.EnumToString();
             state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
             // action outcome probability
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Rock.EnumToString(), 
                 rockOpponent) { Reward = 100 });
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Paper.EnumToString(), 
                 paperOpponent) { Reward = 0 });
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Scissor.EnumToString(), 
                 scissorOpponent) { Reward = -10 });

             // action scissor
             stateNameNext = StateNameEnum.Scissor.EnumToString();
             state.AddAction(fromTo = new QAction(stateName, new QActionName(stateName, stateNameNext)));
             // action outcome probability
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Rock.EnumToString(), 
                 rockOpponent) { Reward = -10 });
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Paper.EnumToString(), 
                 paperOpponent) { Reward = 100 });
             fromTo.AddActionResult(new QActionResult(fromTo, StateNameEnum.Scissor.EnumToString(), 
                 scissorOpponent) { Reward = 0 });
             // ----------- End Insert the path setup here -----------

             q.RunTraining();
             q.PrintQLearningStructure();
             q.ShowPolicy();

             Console.WriteLine(&quot;\n** Opponent style **&quot;);
             Console.WriteLine(string.Format(&quot;style is rock {0} paper {1} scissor {2}&quot;, 
                 opponent[0].Pretty(), opponent[1].Pretty(), opponent[2].Pretty()));
         }        
    }
}
</pre></p>
<p><strong>QLearning.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace QLearningFramework
{  
    /// &lt;summary&gt;
    /// Version 0.1
    /// Author: Kunuk Nykjaer
    /// one to many relationships data structure
    /// QLearning [1 -- *] State [1 -- *] Action [1 -- *] ActionResult
    /// &lt;/summary&gt;
    class QLearning
    {
        public List&lt;QState&gt; States { get; private set; }
        public Dictionary&lt;string, QState&gt; StateLookup { get; private set; }

        public double Alpha { get; internal set; }
        public double Gamma { get; internal set; }

        public HashSet&lt;string&gt; EndStates { get; private set; }
        public int MaxExploreStepsWithinOneEpisode { get; internal set; } //avoid infinite loop
        public bool ShowWarning { get; internal set; } // show runtime warnings regarding q-learning
        public int Episodes { get; internal set; }

        public QLearning()
        {
            States = new List&lt;QState&gt;();
            StateLookup = new Dictionary&lt;string, QState&gt;();
            EndStates = new HashSet&lt;string&gt;();

            // Default when not set
            MaxExploreStepsWithinOneEpisode = 1000;
            Episodes = 1000;
            Alpha = 0.1;
            Gamma = 0.9;
            ShowWarning = true;
        }

        public void AddState(QState state)
        {
            States.Add(state);
        }

        public void RunTraining()
        {
            QMethod.Validate(this);

            /*		 
		    For each episode: Select random initial state 
			Do while not reach goal state
				Select one among all possible actions for the current state 
				Using this possible action, consider to go to the next state 
				Get maximum Q value of this next state based on all possible actions 				
                Set the next state as the current state
		    */

            // For each episode
            var rand = new Random();
            long maxloopEventCount = 0;

            // Train episodes
            for (long i = 0; i &lt; Episodes; i++)
            {
                long maxloop = 0;
                // Select random initial state			
                int stateIndex = rand.Next(States.Count);
                QState state = States[stateIndex];
                QAction action = null;
                do
                {
                    if (++maxloop &gt; MaxExploreStepsWithinOneEpisode)
                    {
                        if(ShowWarning)
                        {
                            string msg = string.Format(
                            &quot;{0} !! MAXLOOP state: {1} action: {2}, {3} endstate is to difficult to reach?&quot;,
                            ++maxloopEventCount, state, action, &quot;maybe your path setup is wrong or the &quot;);
                            QMethod.Log(msg);
                        }
                        
                        break;
                    }

                    // no actions, skip this state
                    if(state.Actions.Count==0)
                        break;

                    // Selection strategy is random based on probability
                    int index = rand.Next(state.Actions.Count);
                    action = state.Actions[index];

                    // Using this possible action, consider to go to the next state
                    // Pick random Action outcome
                    QActionResult nextStateResult = action.PickActionByProbability();
                    string nextStateName = nextStateResult.StateName;

                    double q = nextStateResult.QEstimated;
                    double r = nextStateResult.Reward;
                    double maxQ = MaxQ(nextStateName);

                    // Q(s,a)= Q(s,a) + alpha * (R(s,a) + gamma * Max(next state, all actions) - Q(s,a))
                    double value = q + Alpha * (r + Gamma * maxQ - q); // q-learning                  
                    nextStateResult.QValue = value; // update

                    // is end state go to next episode
                    if (EndStates.Contains(nextStateResult.StateName))
                        break;

                    // Set the next state as the current state                    
                    state = StateLookup[nextStateResult.StateName];

                } while (true);
            }
        }


        double MaxQ(string stateName)
        {
            const double defaultValue = 0;

            if(!StateLookup.ContainsKey(stateName))            
                return defaultValue;                            

            QState state = StateLookup[stateName];
            var actionsFromState = state.Actions;
            double? maxValue = null;
            foreach (var nextState in actionsFromState)
            {
                foreach (var actionResult in nextState.ActionsResult)
                {
                    double value = actionResult.QEstimated;
                    if (value &gt; maxValue || !maxValue.HasValue)
                        maxValue = value;
                }
            }

            // no update
            if (!maxValue.HasValue &amp;&amp; ShowWarning) 
                QMethod.Log(string.Format(&quot;Warning: No MaxQ value for stateName {0}&quot;,
                    stateName) );

            return maxValue.HasValue ? maxValue.Value : defaultValue;
        }

        public void PrintQLearningStructure()
        {
            Console.WriteLine(&quot;** Q-Learning structure **&quot;);
            foreach (QState state in States)
            {
                Console.WriteLine(&quot;State {0}&quot;, state.StateName);
                foreach (QAction action in state.Actions)
                {
                    Console.WriteLine(&quot;  Action &quot; + action.ActionName);
                    Console.Write(action.GetActionResults());
                }
            }
            Console.WriteLine();
        }

        public void ShowPolicy()
        {
            Console.WriteLine(&quot;** Show Policy **&quot;);
            foreach (QState state in States)
            {
                double max = Double.MinValue;
                string actionName = &quot;nothing&quot;;
                foreach (QAction action in state.Actions)
                {
                    foreach (QActionResult actionResult in action.ActionsResult)
                    {
                        if (actionResult.QEstimated &gt; max)
                        {
                            max = actionResult.QEstimated;
                            actionName = action.ActionName.ToString();
                        }
                    }
                }

                Console.WriteLine(string.Format(&quot;From state {0} do action {1}, max QEstimated is {2}&quot;,
                    state.StateName, actionName, max.Pretty()));
            }
        }
    }

    class QState
    {        
        public string StateName { get; private set; }
        public List&lt;QAction&gt; Actions { get; private set; }

        public void AddAction(QAction action)
        {
            Actions.Add(action);
        }

        public QState(string stateName, QLearning q)
        {            
            q.StateLookup.Add(stateName, this);
            StateName = stateName;
            Actions = new List&lt;QAction&gt;();
        }

        public override string ToString()
        {
            return string.Format(&quot;StateName {0}&quot;, StateName);
        }
    }

    class QAction
    {
        private static readonly Random Rand = new Random();
        public QActionName ActionName { get; internal set; }
        public string CurrentState { get; private set; }
        public List&lt;QActionResult&gt; ActionsResult { get; private set; }

        public void AddActionResult(QActionResult actionResult)
        {
            ActionsResult.Add(actionResult);
        }

        public string GetActionResults()
        {
            var sb = new StringBuilder();
            foreach (QActionResult actionResult in ActionsResult)
                sb.AppendLine(&quot;     ActionResult &quot; + actionResult);

            return sb.ToString();
        }

        public QAction(string currentState, QActionName actionName = null)
        {
            CurrentState = currentState;
            ActionsResult = new List&lt;QActionResult&gt;();
            ActionName = actionName;
        }

        // The sum of action outcomes must be close to 1
        public void ValidateActionsResultProbability()
        {
            const double epsilon = 0.1;

            if (ActionsResult.Count == 0)
                throw new ApplicationException(string.Format(
                    &quot;ValidateActionsResultProbability is invalid, no action results:\n {0}&quot;, 
                    this));

            double sum = ActionsResult.Sum(a =&gt; a.Probability);
            if (Math.Abs(1 - sum) &gt; epsilon)
                throw new ApplicationException(string.Format(
                    &quot;ValidateActionsResultProbability is invalid:\n {0}&quot;, this));
        }

        public QActionResult PickActionByProbability()
        {
            double d = Rand.NextDouble();
            double sum = 0;
            foreach (QActionResult actionResult in ActionsResult)
            {
                sum += actionResult.Probability;
                if (d &lt;= sum)
                    return actionResult;
            }
            
            // we might get here if sum probability is below 1.0 e.g. 0.99 
            // and the d random value is 0.999
            if (ActionsResult.Count &gt; 0) 
                return ActionsResult.Last();

            throw new ApplicationException(string.Format(&quot;No PickAction result: {0}&quot;, this));
        }

        public override string ToString()
        {
            double sum = ActionsResult.Sum(a =&gt; a.Probability);
            return string.Format(&quot;ActionName {0} probability sum: {1} actionResultCount {2}&quot;,
                ActionName, sum, ActionsResult.Count);
        }
    }

    class QActionResult
    {
        public string StateName { get; internal set; }
        public string PrevStateName { get; internal set; }
        public double QValue { get; internal set; } // Q value is stored here        
        public double Probability { get; internal set; }
        public double Reward { get; internal set; }

        public double QEstimated
        {
            get { return QValue * Probability; }
        }

        public QActionResult(QAction action, string stateNameNext = null, 
            double probability = 1, double reward = 0)
        {
            PrevStateName = action.CurrentState;
            StateName = stateNameNext;
            Probability = probability;
            Reward = reward;
        }

        public override string ToString()
        {
            return string.Format(&quot;State {0}, Prob. {1}, Reward {2}, PrevState {3}, QE {4}&quot;,
                StateName, Probability.Pretty(), Reward, PrevStateName, QEstimated.Pretty());
        }
    }

    public class QActionName
    {
        public string From { get; private set; }
        public string To { get; private set; }

        public QActionName(string from, string to = null)
        {
            From = from;
            To = to;
        }

        public override string ToString()
        {
            return GetActionName();
        }

        public string GetActionName()
        {
            if (To == null) 
                return From;
            return QMethod.ActionNameFromTo(From,To);
        }
    }

    static class QMethod
    {
        public static void Log(string s)
        {
            Console.WriteLine(s);
        }

        public static readonly CultureInfo CultureEnUs = new CultureInfo(&quot;en-US&quot;);

        public static string ToStringEnUs(this double d)
        {
            return d.ToString(&quot;G&quot;, CultureEnUs);
        }
        public static string Pretty(this double d)
        {
            return ToStringEnUs(Math.Round(d, 2));
        }

        public static string ActionNameFromTo(string a, string b)
        {
            return string.Format(&quot;from_{0}_to_{1}&quot;, a, b);
        }

        public static string EnumToString&lt;T&gt;(this T type)
        {
            return Enum.GetName(typeof(T), type);
        }

        public static void ValidateRange(double d, string origin = null)
        {
            if (d &lt; 0 || d &gt; 1)
            {
                string s = origin ?? string.Empty;
                throw new ApplicationException(string.Format(&quot;ValidateRange error: {0} {1}&quot;, d, s));
            }
        }

        public static void Validate(QLearning q)
        {
            foreach (var state in q.States)
            {
                foreach (var action in state.Actions)
                {
                    action.ValidateActionsResultProbability();
                }
            }
        }
    }
}
</pre></p>
<p>Currently you have to know all the probability values in advance for the action outcomes, which are used for the Q-learning structure setup. The source code can easily be adapted for if you need runtime knowledge of probability outcomes. Just add a probability function lookup and look at the Q-learning formula calculation area. Maybe I will look more into that at a later time.</p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/artificial-intelligence/'>Artificial Intelligence</a>, <a href='http://kunuk.wordpress.com/category/csharp/'>Csharp</a>, <a href='http://kunuk.wordpress.com/category/framework/'>Framework</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/565/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/565/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/565/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/565/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/565/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/565/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/565/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/565/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/565/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/565/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/565/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/565/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/565/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/565/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=565&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2012/01/14/q-learning-framework-example-with-csharp/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2012/01/qlearnbender.png" medium="image">
			<media:title type="html">q-learn-bender</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2010/09/q-learn1.png" medium="image">
			<media:title type="html">q-learn1</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2012/01/rockpaperscissors.png" medium="image">
			<media:title type="html">rock-paper-scissors</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2012/01/q-learn-adv.png" medium="image">
			<media:title type="html">q-learn-adv</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2012/01/qlearningframework.png" medium="image">
			<media:title type="html">q-learn</media:title>
		</media:content>
	</item>
		<item>
		<title>Facebook Friends Tag Cloud</title>
		<link>http://kunuk.wordpress.com/2011/12/04/facebook-friends-tag-cloud/</link>
		<comments>http://kunuk.wordpress.com/2011/12/04/facebook-friends-tag-cloud/#comments</comments>
		<pubDate>Sun, 04 Dec 2011 20:31:40 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Visualization]]></category>
		<category><![CDATA[Facebook]]></category>
		<category><![CDATA[tag cloud]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=547</guid>
		<description><![CDATA[I thought it would be fun to make some kind of visualization using your Facebook friends. I decided to make a tag cloud of the facebook friends&#8217; names. You can try it yourself here. The styling part was made by using this tutorial: How to Style a Tag Cloud. Filed under: Visualization<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=547&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I thought it would be fun to make some kind of visualization using your Facebook friends. I decided to make a tag cloud of the facebook friends&#8217; names. </p>
<p>You can try it yourself <a href="http://jory.dk/AreaSocial/TagCloud.aspx" title="tag cloud" target="_blank">here</a>. The styling part was made by using this tutorial: <a href="http://webdesign.about.com/od/csstutorials/a/aa011407.htm" title="how to make a tag cloud" target="_blank">How to Style a Tag Cloud</a>.</p>
<p><a href="http://jory.dk/AreaSocial/TagCloud.aspx" style="text-decoration:none;" title="tag cloud" target="_blank"><br />
<img src="http://kunuk.files.wordpress.com/2011/12/tagcloud.gif?w=700" style="border-style:none;" alt="tag cloud example" /></a></p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/visualization/'>Visualization</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/547/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/547/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/547/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/547/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/547/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/547/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/547/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/547/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/547/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/547/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/547/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/547/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/547/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/547/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=547&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/12/04/facebook-friends-tag-cloud/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2011/12/tagcloud.gif" medium="image">
			<media:title type="html">tag cloud example</media:title>
		</media:content>
	</item>
		<item>
		<title>Facebook login to 3rd party, how much data do you give away?</title>
		<link>http://kunuk.wordpress.com/2011/12/04/facebook-login-to-3rd-party-how-much-data-do-you-give-away/</link>
		<comments>http://kunuk.wordpress.com/2011/12/04/facebook-login-to-3rd-party-how-much-data-do-you-give-away/#comments</comments>
		<pubDate>Sun, 04 Dec 2011 20:19:17 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Visualization]]></category>
		<category><![CDATA[data]]></category>
		<category><![CDATA[Facebook]]></category>
		<category><![CDATA[privacy]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=542</guid>
		<description><![CDATA[Do you have a Facebook account and have you used it to login to other sites than Facebook? Do you know how much data you give away about yourself by using the Facebook login? You can test by using this page. This page requires you to accept logging in to the Jory web-application using your [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=542&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Do you have a Facebook account and have you used it to login to other sites than Facebook?<br />
Do you know how much data you give away about yourself by using the Facebook login?</p>
<p>You can test by using this <a href="http://jory.dk/AreaSocial/Details.aspx" title="Facebook data" target="_blank">page</a>. This page requires you to accept logging in to the Jory web-application using your Facebook account. The application doesn&#8217;t require any extra permission, just the permission to accept the login.</p>
<p>The page will examine all the data that is available and display them in a table format. It will also extract information about your friends and display your friends in an image grid view format. You can mouse-over your friends and it will show you the names of your friends.</p>
<p>You can adjust your Facebook privacy settings and test again to see if you want to know whether you show too much private data about yourself.</p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/visualization/'>Visualization</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/542/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/542/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/542/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/542/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/542/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/542/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/542/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/542/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/542/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/542/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/542/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/542/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/542/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/542/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=542&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/12/04/facebook-login-to-3rd-party-how-much-data-do-you-give-away/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>
	</item>
		<item>
		<title>Social data exploration</title>
		<link>http://kunuk.wordpress.com/2011/12/04/social-data-exploration/</link>
		<comments>http://kunuk.wordpress.com/2011/12/04/social-data-exploration/#comments</comments>
		<pubDate>Sun, 04 Dec 2011 19:56:36 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Csharp]]></category>
		<category><![CDATA[Data Mining]]></category>
		<category><![CDATA[Visualization]]></category>
		<category><![CDATA[social data]]></category>
		<category><![CDATA[socialauth-net]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=533</guid>
		<description><![CDATA[I will have fun playing with Social data. The idea is to make a site where users can login with a social profile such as Facebook, Google or Twitter account. After the users login the user data will be extracted using the social API. The purpose is to explore Social sites API and use them [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=533&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I will have fun playing with Social data. The idea is to make a site where users can login with a social profile such as Facebook, Google or Twitter account. After the users login the user data will be extracted using the social API. The purpose is to explore Social sites API and use them to make web-tools for easy data exploration, make data visualizations or maybe even knowledge discovery using Data mining techniques.</p>
<p>I will put the content on this <a href="http://jory.dk/AreaSocial/" title="social data" target="_blank">site</a>.</p>
<p>I will use the <a href="http://code.google.com/p/socialauth-net/" title="socialauth-net" target="_blank">Socialauth-net login</a> framework.</p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/csharp/'>Csharp</a>, <a href='http://kunuk.wordpress.com/category/data-mining/'>Data Mining</a>, <a href='http://kunuk.wordpress.com/category/visualization/'>Visualization</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/533/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/533/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/533/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/533/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/533/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/533/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/533/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/533/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/533/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/533/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/533/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/533/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/533/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/533/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=533&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/12/04/social-data-exploration/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>
	</item>
		<item>
		<title>Certified Web Applications Development with Microsoft .NET Framework 4</title>
		<link>http://kunuk.wordpress.com/2011/12/04/certified-web-applications-development-with-microsoft-net-framework-4/</link>
		<comments>http://kunuk.wordpress.com/2011/12/04/certified-web-applications-development-with-microsoft-net-framework-4/#comments</comments>
		<pubDate>Sun, 04 Dec 2011 19:38:26 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Certification]]></category>
		<category><![CDATA[Csharp]]></category>
		<category><![CDATA[mcpd]]></category>
		<category><![CDATA[microsoft]]></category>
		<category><![CDATA[web developer]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=527</guid>
		<description><![CDATA[I am now certified as Web Applications Development with Microsoft .NET Framework 4. If you are going to take this certification I can recommend this book: MCTS Self-Paced Training Kit (Exam 70-515): Web Applications Development with Microsoft .NET Framework 4. But it lacks JQuery materials and you only learn MVC briefly. The certification exam contained [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=527&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I am now certified as <a href="http://www.microsoft.com/learning/en/us/Exam.aspx?ID=70-515&amp;Locale=en-us" title="Web Applications Development with Microsoft .NET Framework 4" target="_blank">Web Applications Development with Microsoft .NET Framework 4</a>.</p>
<p>If you are going to take this certification I can recommend this book: <a href="http://www.amazon.com/MCTS-Self-Paced-Training-Exam-70-515/dp/0735627401" title="MCTS Self-Paced Training Kit (Exam 70-515): Web Applications Development with Microsoft .NET Framework 4" target="_blank">MCTS Self-Paced Training Kit (Exam 70-515): Web Applications Development with Microsoft .NET Framework 4</a>. But it lacks JQuery materials and you only learn MVC briefly. The certification exam contained a high amount on jQuery related questions imho (jQuery is not a MS-technology). For the exam preparation I recommend reading the book, learn jQuery and practice Asp.net MVC.  </p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/certification-2/'>Certification</a>, <a href='http://kunuk.wordpress.com/category/csharp/'>Csharp</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/527/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=527&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/12/04/certified-web-applications-development-with-microsoft-net-framework-4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>
	</item>
		<item>
		<title>Probability of getting certified by guessing</title>
		<link>http://kunuk.wordpress.com/2011/11/12/probability-of-getting-certified-by-guessing/</link>
		<comments>http://kunuk.wordpress.com/2011/11/12/probability-of-getting-certified-by-guessing/#comments</comments>
		<pubDate>Sat, 12 Nov 2011 16:08:36 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Certification]]></category>
		<category><![CDATA[binomial]]></category>
		<category><![CDATA[guessing]]></category>
		<category><![CDATA[probability]]></category>
		<category><![CDATA[sitecore]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=467</guid>
		<description><![CDATA[I am curious about the probability of passing a certification exam by guessing. Therefore I made a quick implementation to calculate some probabilities. In this example I will use Website .NET Developer Certification for Sitecore CMS as the test experiment. The exam is in multiple-choice format with 3 possible answers. In total there are 40 [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=467&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I am curious about the probability of passing a certification exam by guessing.<br />
Therefore I made a quick implementation to calculate some probabilities. </p>
<p>In this example I will use<br />
<a href="http://www.sitecore.net/Support/Training/Course-Overview/" title="sitecore certification" target="_blank">Website .NET Developer Certification for Sitecore CMS</a> as the test experiment. </p>
<p>The exam is in multiple-choice format with 3 possible answers.<br />
In total there are 40 questions and you have to get at least 28 to pass the exam.<br />
You have 55 minutes to answer all the questions (about 1 minute per answer).<br />
Almost always two possible answers look very similar and one is usually easier to spot as a wrong candidate from the other two. </p>
<p>The math example of how to calculate this kind of probabilities can be read <a href="http://www.regentsprep.org/Regents/math/algtrig/ATS7/BLesson2.htm" title="binomial probability" target="_blank">here</a>. </p>
<p><pre class="brush: plain; collapse: false; pad-line-numbers: false; wrap-lines: false;">
The probability of passing the exam by only guessing
AtLeastProbability(0.33, 40, 28) ~ 0%;

The probability of passing the exam by knowing 10 answers and guessing the rest minimum 18 correct
AtLeastProbability(0.33, 30, 18) ~ 0%;

The probability of passing the exam by knowing 20 answers and guessing the rest minimum 8 correct
AtLeastProbability(0.33, 20, 8) ~ 34%;

The probability of passing the exam by knowing 22 answers and guessing the rest minimum 6 correct
AtLeastProbability(0.33, 18, 6) ~ 59%;
</pre></p>
<p>As you can see, even if you know 22 correct answers out of 40 then the chance of passing is only about 59% if you guess the rest of the questions where you need to guess 6 correct. That is not promising if your strategy is to pass by guessing.</p>
<p>Now let’s assume you will be able to spot one wrong candidate and are left with 50% of guessing correct. The calculations are now:</p>
<p><pre class="brush: plain; collapse: false; pad-line-numbers: false; wrap-lines: false;">
The probability of passing the exam by only guessing
AtLeastProbability(0.5, 40, 28) ~ 1%;

The probability of passing the exam by knowing 10 answers and guessing the rest minimum 18 correct
AtLeastProbability(0.5, 30, 18) ~ 18%;

The probability of passing the exam by knowing 20 answers and guessing the rest minimum 8 correct
AtLeastProbability(0.5, 20, 8) ~ 87%;

The probability of passing the exam by knowing 22 answers and guessing the rest minimum 6 correct
AtLeastProbability(0.5, 18, 6) ~ 95%;
</pre></p>
<p>If you are able to spot 1 wrong candidate, then you should at least know 22 correct answers out of 28 to pass the exam.</p>
<p>The strategy of passing a certification exam by guessing seems like a failing strategy.</p>
<p>Out of the box this program will calculate the probability of having at least 3 successes out of 5 where the success probability is 33%.<br />
AtLeastProbability(0.33, 5, 3) ~ 21%;</p>
<p><strong>Program.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;
using System.Collections.Generic;
using System.Diagnostics;

/// &lt;summary&gt;
/// Author: Kunuk Nykjaer
/// Binomial probability
/// At least x success out of n tries with probability p.
/// &lt;/summary&gt;
public class Program
{
    public static void Main(string[] args)
    {
        const double T = 1.0 / 3; // odds of success        
        const int knownCorrect = 0;
        long n = 5; // total of chances
        long atleast = 3; // must have at least this number of success        
        n -= knownCorrect;
        atleast -= knownCorrect;
        AtLeastProbability(T, n, atleast);
    }

    // http://www.regentsprep.org/Regents/math/algtrig/ATS7/BLesson2.htm
    static void AtLeastProbability(double T, long n, long atleast)
    {
        if (T &lt; 0 || T &gt; 1 || n &lt; 0 || atleast &lt; 0 || atleast &gt; n)
            throw new ApplicationException(&quot;AtLeastProbability invalid&quot;);

        double F = 1.0 - T; // odds of failure
        Console.WriteLine(string.Format(&quot;AtLeastProbability({0}, {1}, {2}, {3})&quot;, 
            Math.Round(T,2), Math.Round(F,2), n, atleast));
        var stopwatch = new System.Diagnostics.Stopwatch();
        stopwatch.Start();

        double odds = 0;
        for (long r = atleast; r &lt;= n; r++)
            odds += C(n, r) * Math.Pow(T, r) * Math.Pow(F, n - r);

        stopwatch.Stop();
        odds = Math.Round(odds * 100, 3); // %
        Console.WriteLine(string.Format(
            &quot;odds: {0}%\nTime elapsed msec: {1}&quot;, odds, stopwatch.ElapsedMilliseconds));
        Console.WriteLine(&quot;press a key to exit&quot;); Console.ReadKey();
    }


    // Binomial    
    // using tricks to avoid too big numbers in factorisation, 
    // runtime might be slow for really big numbers
    // nCr see http://en.wikipedia.org/wiki/Binomial_probability
    static long C(long n, long r)
    {
        if (r &lt; 0 || n &lt; r)
            throw new ApplicationException(&quot;C(n,r) invalid n,r&quot;);
        if (n &lt;= 1) return 1;

        long bigr = n - r;
        if (bigr &lt; r)
            bigr = r;

        var uppers = new System.Collections.Generic.List&lt;long&gt;();
        var lowers = new System.Collections.Generic.List&lt;long&gt;();

        for (long i = n; i &gt; bigr; i--)
            uppers.Add(i);

        for (long i = n - bigr; i &gt; 1; i--)
            lowers.Add(i);

        // O(n^2)
        // trick to reduce big numbers, datatype long has a limit
        for (int i = 0; i &lt; uppers.Count; i++)
        {
            for (int j = 0; j &lt; lowers.Count; j++)
            {
                long u = uppers[i];
                long l = lowers[j];
                long gcd = Gcd(u, l);
                if (gcd &lt;= 1) continue;

                // reduce number value
                uppers[i] = u / gcd;
                lowers[j] = l / gcd;
            }
        }

        long upper = 1;
        long lower = 1;
        foreach (var i in lowers) lower *= i;
        foreach (var i in uppers) upper *= i;        

        if (upper &lt; 0 || lower &lt; 0)
            //long datatype has a limit
            throw new ApplicationException(&quot;C(n,r) negative, datatype limit issue&quot;); 

        long result = upper / lower;
        return result;
    }

    // greatest common divisor
    public static long Gcd(long a, long b)
    {
        return b == 0 ? a : Gcd(b, a % b);
    }
}
</pre></p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/algorithm/'>Algorithm</a>, <a href='http://kunuk.wordpress.com/category/certification-2/'>Certification</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/467/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/467/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/467/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/467/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/467/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/467/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/467/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/467/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/467/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/467/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/467/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/467/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/467/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/467/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=467&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/11/12/probability-of-getting-certified-by-guessing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>
	</item>
		<item>
		<title>Google Maps Server-side Clustering with Asp.net C#</title>
		<link>http://kunuk.wordpress.com/2011/11/05/google-map-server-side-clustering-with-asp-net/</link>
		<comments>http://kunuk.wordpress.com/2011/11/05/google-map-server-side-clustering-with-asp-net/#comments</comments>
		<pubDate>Sat, 05 Nov 2011 16:46:48 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Csharp]]></category>
		<category><![CDATA[Framework]]></category>
		<category><![CDATA[Visualization]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[clustering]]></category>
		<category><![CDATA[crunch panorama]]></category>
		<category><![CDATA[google maps]]></category>
		<category><![CDATA[maptimize]]></category>
		<category><![CDATA[server-side]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=435</guid>
		<description><![CDATA[For a project in my work I made a server-side clustering of data to be displayed in Google Maps. The result can be seen here. The clustering is a grid-based clustering using geo-stationary grid-placement. I was impressed by the performance of the solution from Maptimize They have this example webside Crunch Panorama I wanted to [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=435&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>For a project in my work I made a server-side clustering of data to be displayed in Google Maps.<br />
The result can be seen <a href="http://www.hjertestarter.dk/Find_Hjertestarter.aspx" title="find hjertestarter" target="_blank">here</a>.</p>
<p>The clustering is a <a href="http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster/" title="grid-based clustering" target="_blank">grid-based clustering</a> using geo-stationary grid-placement.<br />
I was impressed by the performance of the solution from <a href="http://www.maptimize.com" title="http://www.maptimize.com" target="_blank">Maptimize</a><br />
They have this example webside <a href="http://www.crunchpanorama.com" title="http://www.crunchpanorama.com" target="_blank">Crunch Panorama</a></p>
<p>I wanted to make something similar and have made an example project of server-side clustering. The demo can be seen here <a href="http://jory.dk/AreaGMC/MapClustering.aspx" title="Google Maps Clustering" target="_blank">Google Maps Clustering</a>. The grid has been included in the view for demonstration. Clusters that are close to each other are merged.</p>
<p>The search options is included by example code from <a href="http://tech.cibul.net/geocode-with-google-maps-api-v3/" title="Cibul tech blog" target="_blank">Cibul Tech Blog</a>.  </p>
<p><a href="http://jory.dk/AreaGMC/MapClustering.aspx" title="Google Maps Clustering" target="_blank"><br />
<img src="http://kunuk.files.wordpress.com/2011/11/googlemapclustering.gif?w=700" alt="googlemapclustering" /><br />
</a></p>
<h3>Google Maps Clustering Viewport:</h3>
<p>Here is the overview of how the viewport works.<br />
<img src="http://kunuk.files.wordpress.com/2011/11/googlemaps-clustering-viewport2.png?w=700" alt="googlemaps-clustering-viewport" /></p>
<h3>Client-server Ajax communication demonstration:</h3>
<p>This Google Maps clustering project uses two webservice methods: GetMarkers and GetMarkerDetail.<br />
The GetMarkers method returns the marker and cluster information and GetMarkerDetail method returns specific information for a selected marker.</p>
<p><img src="http://kunuk.files.wordpress.com/2011/11/googlemapclusteringsd.png?w=700" alt="googlemapclustering SD" /></p>
<p><strong>GetMarkers method:</strong><br />
The client sends the boundary of the screen using Norht-east and South-west latlon point and the zoom level.<br />
Client json get markers request:<br />
<pre class="brush: xml; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;"> 
{&quot;nelat&quot;:&quot;62.511244050310154&quot;,&quot;nelon&quot;:&quot;30.008460312500006&quot;,
  &quot;swlat&quot;:&quot;47.574956142655&quot;,&quot;swlon&quot;:&quot;-5.235680312499994&quot;,&quot;zoomlevel&quot;:&quot;5&quot;}
</pre></p>
<p>The server does the server-side clustering based on the information from the client and returns only the data that is to be displayed to the client. Here it is two markers with latlon information. When the count (the C) is larger than 1, then it is a cluster marker, else it is a details marker. The details marker has the I and T information set. The I is the id and the T is type information.<br />
Server json show markers reply:<br />
<pre class="brush: xml; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;"> 
{&quot;Points&quot;:[
  {&quot;Y&quot;:&quot;56.05777&quot;,&quot;X&quot;:&quot;9.30294&quot;,&quot;C&quot;:1624,&quot;I&quot;:&quot;&quot;,&quot;T&quot;:&quot;&quot;},
  {&quot;Y&quot;:&quot;55.09446&quot;,&quot;X&quot;:&quot;15.11401&quot;,&quot;C&quot;:&quot;1&quot;,&quot;I&quot;:&quot;aa005c3b-9301-4040-bac3-3dee8cb29b48&quot;,&quot;T&quot;:&quot;2&quot;}
]}
</pre></p>
<p>The client examines the reply and removes markers that should no longer be visible and only adds new marker to the screen. The latlon and count information are used to build a key for identifying unique marker objects.</p>
<p><strong>GetMarkerDetail method:</strong><br />
The client sends a details request for a specific marker using an id-information.<br />
Client json get marker detail: request<br />
<pre class="brush: xml; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;"> 
  {&quot;id&quot;:&quot;aa005c3b-9301-4040-bac3-3dee8cb29b48&quot;}
</pre></p>
<p>The server replies by giving content for the specific marker.<br />
Server json show marker detail reply:<br />
<pre class="brush: xml; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;"> 
  {&quot;Content&quot;:&quot;CONTENT INFORMATION HERE&quot;}
</pre></p>
<h3>Math information</h3>
<p>The <a href="http://en.wikipedia.org/wiki/Centroid" title="centroid" target="_blank">centroid</a> calculation is based on <a href="http://en.wikipedia.org/wiki/Circular_mean" title="Circular mean">circular mean</a>. This is because the longitude overlaps at -180 and 180 degrees. There are no overlapping using latitude with Google Maps (haven&#8217;t seen or noticed it). </p>
<p>The distances are calculated using Euclidean distance. This calculation is fast and it is good enough for clustering the points into a cluster.</p>
<p>You usually only need as a maximum 6 decimal precision when using latlon and Google Maps. <a href="http://en.wikipedia.org/wiki/Decimal_degrees" title="latlon precision" target="_blank">This gives within 1 meter precision</a>. </p>
<p>The grid-size is fixed and adapts to the zoom level.<br />
By doing one step zooming in Google Maps the latlon window range is doubled or halved.<br />
The geo-stationary grid-placement indexes are archived by using divide and modulus calculation.</p>
<p>Time complexity is on average(m*n) and space complexity is O(m*n)<br />
where n is the number of points used in total and m is the number of grids returned to the client.<br />
You are welcome to analyse it yourself and correct me <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  I use hashset and hashmap to minimize the time complexity. For fast enough response time (below 1 sec) the number of markers should be max 300.000.<br />
Time complexity is ~ O(n^2) on worst case but extremely unlikely,<br />
happens if most centroids are merged with neighbor-centroids.</p>
<p>To reduce the running time, the range search could be used. Here is an example of <a href="http://www.emilstefanov.net/Projects/RangeSearchTree.aspx" title="range search C#" target="_blank">range search in C#</a>.</p>
<p>I might look into that some day and try to increase the number of markers to millions while keeping fast enough response time.</p>
<p>The project is available at <a href="http://code.google.com/p/google-maps-server-side-clustering-dotnet/" title="project url" target="_blank">google code</a></p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/algorithm/'>Algorithm</a>, <a href='http://kunuk.wordpress.com/category/csharp/'>Csharp</a>, <a href='http://kunuk.wordpress.com/category/framework/'>Framework</a>, <a href='http://kunuk.wordpress.com/category/visualization/'>Visualization</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/435/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=435&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/11/05/google-map-server-side-clustering-with-asp-net/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2011/11/googlemapclustering.gif" medium="image">
			<media:title type="html">googlemapclustering</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2011/11/googlemaps-clustering-viewport2.png" medium="image">
			<media:title type="html">googlemaps-clustering-viewport</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2011/11/googlemapclusteringsd.png" medium="image">
			<media:title type="html">googlemapclustering SD</media:title>
		</media:content>
	</item>
		<item>
		<title>MarkerClusterer with C# example and html-canvas  (part 3)</title>
		<link>http://kunuk.wordpress.com/2011/09/20/markerclusterer-with-c-example-and-html-canvas-part-3/</link>
		<comments>http://kunuk.wordpress.com/2011/09/20/markerclusterer-with-c-example-and-html-canvas-part-3/#comments</comments>
		<pubDate>Tue, 20 Sep 2011 20:49:10 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Csharp]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Visualization]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[clustering]]></category>
		<category><![CDATA[markerclusterer]]></category>
		<category><![CDATA[points overlapping]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=386</guid>
		<description><![CDATA[This is part 3 of my topic on point clustering. I will look at the marker clustering algorithm. This is a follow up of my previous post. http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2/ The MarkerClusterer algorithm is simple. It iterates the point and put them in boxes. The first item is put in a box. The following items are only [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=386&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>This is part 3 of my topic on point clustering.<br />
I will look at the marker clustering algorithm. </p>
<p>This is a follow up of my previous post.<br />
<a href="http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2/" title="clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2/" target="_blank">http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2/</a></p>
<p>The MarkerClusterer algorithm is simple. It iterates the point and put them in boxes.<br />
The first item is put in a box. The following items are only put in a box if they are within a specified distance from any existing box. If not then a new box is created and the item is put in the new box.</p>
<p>The algorithm is also explained here:<br />
<a href="http://code.google.com/intl/en-US/apis/maps/articles/toomanymarkers.html#markerclusterer" title="http://code.google.com/intl/en-US/apis/maps/articles/toomanymarkers.html#markerclusterer" target="_blank">http://code.google.com/intl/en-US/apis/maps/articles/toomanymarkers.html#markerclusterer</a></p>
<p>My implementation running time is O(k*n) where k is number of boxes and n is number of points.<br />
(Please make a comment if I am wrong).<br />
It is possible to manipulate the running time by setting the box-size. If the box-size is big the k will be smaller and the running time faster.</p>
<p>Imagine you have a set of point like this.<br />
And you are interested in clustering to avoid overlapping points and for reducing the visual overload.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/markercluster1.gif?w=700" /></p>
<p>Here&#8217;s and example of how marker clustering works.<br />
Every point has been put in box by running the defined algorithm above. the boxsize is user-defined.<br />
The distance of the cluster point can minimum be boxsize / 2.<br />
Thus we don&#8217;t need to consider merging clusters if they should be to close to each other and overlap.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/markercluster2.gif?w=700" /></p>
<p>Here is the box-area removed for a more clean look.<br />
Notice that the points are not all inside the nearest box. This usually happens but there is a simple fix explained later if needed.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/markercluster3.gif?w=700" /></p>
<p>And the points are removed for an even more clean look. Now there are only cluster points with numbers that tells how many points they contain.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/markercluster4.gif?w=700" /></p>
<p>As the last step in the algorithm all the points are iterated and compared to the existing cluster points. They are assigned to the nearest cluster. This takes O(k*n). The cluster points are located at the center of the boxes.<br />
Here you can see an example how it looks like but this image is for another dataset.<br />
Notice at top left corner there is one red point inside the green box which is not inside it&#8217;s &#8216;correct&#8217; box. This is not an error but because the nearest cluster point for the red point is the red box, the distances are calculated by comparing the center of the boxes to the points. If needed it can also be &#8216;fixed&#8217; but that is usually not needed.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/markercluster5a.gif?w=700" /></p>
<p><img src="http://kunuk.files.wordpress.com/2011/09/markercluster6a.gif?w=700" /></p>
<p>Alternatively I could also have used a distance-based approach, where you use circles instead of boxes. This is explained in the google code reference link at the top and I will skip the description of the algorithm because it is very similar to the marker clusterer. But I have included the source code for the distance clustering at the bottom. Here&#8217;s an image of how the clustering would like like.</p>
<p><img src="http://kunuk.files.wordpress.com/2011/09/distanceclusterer.gif?w=700" /></p>
<p>Here are some performance test for the marker clusterer algorithm executed from my laptop.<br />
about 0.15 sec for 10.000 points.<br />
about 7.5 sec for 1.000.000 points where the k = 4.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/markercluster7-5sec1000000p.gif?w=700" /></p>
<p>With a bigger box area which can contain all the points, i.e. k = 1, the performance is about 4.8 sec for 1.000.000 points and 0.4 sec. for 100.000 points.</p>
<p><strong>Summary</strong><br />
For the task of clustering overlapping points.<br />
In summary of the 4 clusters: grid, k-means, marker and distance I would usually prefer grid, marker or distance clustering as they have fast running time O(k*n) and you can easily manipulate the running time by adjusting the k-value by defining the grid-size, boxsize or distance-size. K-means can have poor running time depending on your dataset and max_error level. K-means will run slower than the other 3 algorithms for large n.</p>
<p>This is my bundle file that contains the 4 clustering algorithms (grid, k-means, marker, distance) in one package.<br />
The other two clustering algorithms (grid and k-means) are explained in my previous post.<br />
Out of the box, the C# file runs marker clustering with similar result as the images above. It generates a draw.js<br />
file with Canvas drawing information used by the canvas.html file.<br />
All the classes are put in one file for simple distribution. Refactor as you like.</p>
<p><strong>AlgorithmClusterBundle.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;"> 
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace ClusterBundle
{
    // By Kunuk Nykjaer
    // Clustering package, Grid clustering, MarkerClusterer and auto increasing K-means clustering
    // should split all class to each file and refactor all global const away
    // quick source code bundle version
    // Version 0.4
    //
    // for visualization this generates a draw.js file which is used by the canvas.html file
    // the canvas.html is distributed elsewhere, e.g. at my blog at http://kunuk.wordpress.com
    public class AlgorithmClusterBundle
    {
        public static DateTime Starttime;
        static void Main(string[] args)
        {
            Starttime = DateTime.Now;
            var algo = new AlgorithmClusterBundle();

            // run clustertype
            algo.Run(clusterType);
            algo.GenerateJavascriptDrawFile(clusterType); //create draw.js

            var timespend = DateTime.Now.Subtract(Starttime).TotalSeconds;
            Console.WriteLine(timespend + &quot; sec. press a key ...&quot;);
            Console.ReadKey();
        }

        //------------------------------
        // USER CONFIG, CUSTOMIZE BELOW
        public const ClusterType clusterType = ClusterType.MarkerClusterer;
        public static readonly int N = 100; //number of points

        // use centroid or semi-centroid cluster point placement visualization?
        public const bool DoUpdateAllCentroidsToNearestContainingPoint = false;
        public const bool UseProfiling = false; //debug, output time spend

        // window frame boundary, aka viewport
        public const double MinX = 10;
        public const double MinY = 10;
        public const double MaxX = 400;
        public const double MaxY = 300;
        // where the files are saved or loaded
        public const string FolderPath = @&quot;c:\temp\&quot;;

        // K-MEANS config
        // heuristic, set tolerance for cluster density, has effect on running time.
        // Set high for many points in dataset, can be lower for fewer points
        public const double MAX_ERROR = 50;

        // MARKERCLUSTERER config
        // box area i.e. cluster size
        public const int MARKERCLUSTERER_SIZE = 100;

        // DISTANCECLUSTERER config
        // radius size i.e. cluster size
        public const int DISTANCECLUSTERER_SIZE = 62;

        // GRIDCLUSTER config
        // grid size
        public const int GridX = 7;
        public const int GridY = 6;        
        public const bool DoMergeGridIfCentroidsAreCloseToEachOther = true;
        // USER CONFIG, CUSTOMIZE ABOVE
        //------------------------------



        public enum ClusterType { Unknown = -1, Grid, KMeans, MarkerClusterer, DistanceClusterer } ;
        static readonly CultureInfo Culture_enUS = new CultureInfo(&quot;en-US&quot;);
        const string S = &quot;G&quot;;

        readonly List&lt;XY&gt; _points = new List&lt;XY&gt;();
        List&lt;XY&gt; _clusters = new List&lt;XY&gt;();

        public static readonly Random Rand = new Random();

        const double AbsSizeX = MaxX - MinX;
        const double AbsSizeY = MaxY - MinY;

        // for bucket placement calc, grid cluster algo
        const double DeltaX = AbsSizeX / GridX;
        const double DeltaY = AbsSizeY / GridY;

        static private readonly string NL = Environment.NewLine;
        private const string ctx = &quot;ctx&quot;; // javascript canvas
        public bool DisplayGridInCanvas = false; //autoset by used cluster type

        public AlgorithmClusterBundle()
        {
            CreateTestDataSet(N); // create points in memory
            //SaveDataSetToFile(_points); // save test points to file
            //_points = LoadDataSetFromFile(); // load test points from file            
        }

        // dictionary lookup key used by grid cluster algo
        public static string GetId(int idx, int idy) //O(1)
        {
            return idx + &quot;;&quot; + idy;
        }

        // save points from mem to file
        private const string DatasetSerializeName = &quot;dataset.ser&quot;;
        static void SaveDataSetToFile(List&lt;XY&gt; dataset)
        {
            var objectToSerialize = new DatasetToSerialize { Dataset = dataset };
            new Serializer().SerializeObject(FolderPath + DatasetSerializeName, objectToSerialize);
        }
        // load points from file to mem
        static List&lt;XY&gt; LoadDataSetFromFile()
        {
            var objectToSerialize = (DatasetToSerialize)
                (new Serializer().DeSerializeObject(FolderPath + DatasetSerializeName));
            return objectToSerialize.Dataset;
        }

        void CreateTestDataSet(int n) //O(n)
        {
            // CREATE DATA SET

            // Create random scattered points
            //for (int i = 0; i &lt; n; i++)
            //{
            //    var x = MinX + Rand.NextDouble() * (AbsSizeX);
            //    var y = MinY + Rand.NextDouble() * (AbsSizeY);
            //    _points.Add(new XY(x, y));
            //}


            // Create random region of clusters
            for (int i = 0; i &lt; n / 3; i++)
            {
                var x = MinX + 50 + Rand.NextDouble() * 100;
                var y = MinY + 50 + Rand.NextDouble() * 100;
                _points.Add(new XY(x, y));
            }

            for (int i = 0; i &lt; n / 3; i++)
            {
                var x = MinX + 260 + Rand.NextDouble() * 100;
                var y = MinY + 110 + Rand.NextDouble() * 100;
                _points.Add(new XY(x, y));
            }

            for (int i = 0; i &lt; n / 3; i++)
            {
                var x = MinX + 100 + Rand.NextDouble() * 100;
                var y = MinY + 200 + Rand.NextDouble() * 100;
                _points.Add(new XY(x, y));
            }
        }

        public void Run(ClusterType clustertype)
        {
            switch (clustertype)
            {
                case ClusterType.Grid:
                    _clusters = new GridCluster(_points).GetCluster();
                    DisplayGridInCanvas = true;
                    break;
                case ClusterType.KMeans:
                    _clusters = new KMeans(_points).GetCluster();
                    break;
                case ClusterType.MarkerClusterer:
                    _clusters = new MarkerClusterer(_points).GetCluster();
                    break;
                case ClusterType.DistanceClusterer:
                    _clusters = new DistanceClusterer(_points).GetCluster();
                    break;
                case ClusterType.Unknown:
                    break;
            }
        }

        public static string GetRandomColor()
        {
            int r = Rand.Next(10, 250);
            int g = Rand.Next(10, 250);
            int b = Rand.Next(10, 250);
            return string.Format(&quot;'rgb({0},{1},{2})'&quot;, r, g, b);
        }

        static void CreateFile(string s)
        {
            var path = new FileInfo(FolderPath + &quot;draw.js&quot;);
            var isCreated = FileUtil.WriteFile(s, path);
            Console.WriteLine(isCreated + &quot; = File is Created&quot;);
        }
        public void GenerateJavascriptDrawFile(ClusterType clusterType)
        {
            var sb = new StringBuilder();

            // markers
            var head = &quot;function drawMarkers(ctx) {&quot; + NL;
            var tail = NL + &quot;}&quot; + NL;

            sb.Append(head);

            // grid
            if (DisplayGridInCanvas)
            {
                sb.Append(&quot;ctx.beginPath();&quot; + NL);
                for (int i = 0; i &lt; GridX + 1; i++)
                {
                    sb.Append(String.Format(&quot;ctx.moveTo({0}, {1});{2}&quot;,
                                            ToStringEN(MinX + i * DeltaX),
                                            ToStringEN(MinY), NL));
                    sb.Append(String.Format(&quot;ctx.lineTo({0}, {1});{2}&quot;,
                                            ToStringEN(MinX + i * DeltaX),
                                            ToStringEN(MaxY), NL));
                }
                for (int j = 0; j &lt; GridY + 1; j++)
                {
                    sb.Append(String.Format(&quot;ctx.moveTo({0}, {1});{2}&quot;,
                                            ToStringEN(MinX),
                                            ToStringEN(MinY + j * DeltaY), NL));
                    sb.Append(String.Format(&quot;ctx.lineTo({0}, {1});{2}&quot;,
                                            ToStringEN(MaxX),
                                            ToStringEN(MinY + j * DeltaY), NL));
                }
                sb.Append(&quot;ctx.stroke(); &quot; + NL);
            }

            // if to many points, the canvas can not be drawn or is slow, 
            // use max points and clusters for drawing
            const int max = 10000;

            // markers
            if (_points != null)
            {
                sb.Append(NL);
                for (int i = 0; i &lt; _points.Count; i++)
                {
                    var p = _points[i];
                    sb.Append(string.Format(&quot;drawMark({0}, {1}, {2}, {3});{4}&quot;,
                              ToStringEN(p.X), ToStringEN(p.Y), p.Color, ctx, NL));
                    if (i &gt; max)
                        break;
                }
            }
            string clusterInfo = &quot;0&quot;;
            if (_clusters != null)
            {
                sb.Append(NL);

                if (clusterType == ClusterType.Grid || clusterType == ClusterType.KMeans)
                {
                    for (int i = 0; i &lt; _clusters.Count; i++)
                    {
                        var c = _clusters[i];
                        sb.Append(string.Format(&quot;drawCluster({0}, {1}, {2}, {3}, {4});{5}&quot;,
                                                ToStringEN(c.X), ToStringEN(c.Y), c.Color,
                                                c.Size, ctx, NL));

                        if (i &gt; max)
                            break;
                    }
                }

                else if (clusterType == ClusterType.MarkerClusterer)
                {
                    for (int i = 0; i &lt; _clusters.Count; i++)
                    {
                        var c = _clusters[i];
                        sb.Append(string.Format(
                            &quot;drawMarkerCluster({0}, {1}, {2}, {3}, {4}, {5});{6}&quot;,
                                                ToStringEN(c.X), ToStringEN(c.Y), c.Color,
                                                c.Size, MARKERCLUSTERER_SIZE, ctx, NL));

                        if (i &gt; max)
                            break;
                    }
                }
                else if (clusterType == ClusterType.DistanceClusterer)
                {
                    for (int i = 0; i &lt; _clusters.Count; i++)
                    {
                        var c = _clusters[i];
                        sb.Append(string.Format(
                            &quot;drawDistanceCluster({0}, {1}, {2}, {3}, {4}, {5});{6}&quot;,
                                                ToStringEN(c.X), ToStringEN(c.Y), c.Color,
                                                c.Size, DISTANCECLUSTERER_SIZE, ctx, NL));

                        if (i &gt; max)
                            break;
                    }
                }

                clusterInfo = _clusters.Count + string.Empty;
            }

            // bottom text                
            sb.Append(&quot;ctx.fillStyle = 'rgb(0,0,0)';&quot; + NL);
            sb.Append(string.Format(&quot;ctx.fillText('Clusters = ' + {0}, {1}, {2});{3}&quot;,
                clusterInfo, ToStringEN(MinX + 10), ToStringEN(MaxY + 20), NL));

            sb.Append(tail);
            CreateFile(sb.ToString());
            //Console.WriteLine(sb.ToString());
        }
        public static string ToStringEN(double d)
        {
            double rounded = Math.Round(d, 3);
            return rounded.ToString(S, Culture_enUS);
        }
        public static void Profile(string s)//O(1)
        {
            if (!UseProfiling)
                return;
            var timespend = DateTime.Now.Subtract(Starttime).TotalSeconds;
            Console.WriteLine(timespend + &quot; sec. &quot; + s);
        }


        [Serializable()]
        public class XY : IComparable, ISerializable
        {
            public double X { get; set; }
            public double Y { get; set; }
            public string Color { get; set; }
            public int Size { get; set; }

            //public string XToString(){return X.ToString(S, Culture_enUS);}
            //public string YToString(){return Y.ToString(S, Culture_enUS);}

            public XY() { }
            public XY(double x, double y)
            {
                X = x;
                Y = y;
                Color = &quot;'rgb(0,0,0)'&quot;;//default            
            }
            public XY(XY p) //clone
            {
                this.X = p.X;
                this.Y = p.Y;
                this.Color = p.Color;
                this.Size = p.Size;
            }

            public int CompareTo(object o) // if used in sorted list
            {
                if (this.Equals(o))
                    return 0;

                var other = (XY)o;
                if (this.X &gt; other.X)
                    return -1;
                if (this.X &lt; other.X)
                    return 1;

                return 0;
            }

            // used by k-means random distinct selection of cluster point
            public override int GetHashCode()
            {
                var x = X * 10000; //make the decimals be important
                var y = Y * 10000;
                var r = x * 17 + y * 37;
                return (int)r;
            }
            private const int ROUND = 6;
            public override bool Equals(Object o)
            {
                if (o == null)
                    return false;
                var other = o as XY;
                if (other == null)
                    return false;

                // rounding could be skipped
                // depends on granularity of wanted decimal precision
                // note, 2 points with same x,y is regarded as being equal
                var x = Math.Round(this.X, ROUND) == Math.Round(other.X, ROUND);
                var y = Math.Round(this.Y, ROUND) == Math.Round(other.Y, ROUND);
                return x &amp;&amp; y;
            }


            public XY(SerializationInfo info, StreamingContext ctxt)
            {
                this.X = (double)info.GetValue(&quot;X&quot;, typeof(double));
                this.Y = (double)info.GetValue(&quot;Y&quot;, typeof(double));
                this.Color = (string)info.GetValue(&quot;Color&quot;, typeof(string));
                this.Size = (int)info.GetValue(&quot;Size&quot;, typeof(int));
            }

            public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
            {
                info.AddValue(&quot;X&quot;, this.X);
                info.AddValue(&quot;Y&quot;, this.Y);
                info.AddValue(&quot;Color&quot;, this.Color);
                info.AddValue(&quot;Size&quot;, this.Size);
            }
        }
        public class Bucket
        {
            public string Id { get; private set; }
            public List&lt;XY&gt; Points { get; private set; }
            public XY Centroid { get; set; }
            public int Idx { get; private set; }
            public int Idy { get; private set; }
            public double ErrorLevel { get; set; } // clusterpoint and points avg dist
            private bool _IsUsed;
            public bool IsUsed
            {
                get { return _IsUsed &amp;&amp; Centroid != null; }
                set { _IsUsed = value; }
            }
            public Bucket(string id)
            {
                IsUsed = true;
                Centroid = null;
                Points = new List&lt;XY&gt;();
                Id = id;
            }
            public Bucket(int idx, int idy)
            {
                IsUsed = true;
                Centroid = null;
                Points = new List&lt;XY&gt;();
                Idx = idx;
                Idy = idy;
                Id = GetId(idx, idy);
            }
        }


        public abstract class BaseClusterAlgorithm
        {
            public List&lt;XY&gt; BaseDataset; // all points
            //id, bucket
            public readonly Dictionary&lt;string, Bucket&gt; BaseBucketsLookup =
                new Dictionary&lt;string, Bucket&gt;();

            public BaseClusterAlgorithm(){}
            public BaseClusterAlgorithm(List&lt;XY&gt; dataset)
            {
                if (dataset == null || dataset.Count == 0)
                    throw new ApplicationException(
                        string.Format(&quot;dataset is null or empty&quot;));

                BaseDataset = dataset;
            }

            public abstract List&lt;XY&gt; GetCluster();
            //O(k?? random fn can be slow, but is not slow because atm the k is always 1)
            public static XY[] BaseGetRandomCentroids(List&lt;XY&gt; list, int k)
            {
                Profile(&quot;BaseGetRandomCentroids beg&quot;);
                var set = new HashSet&lt;XY&gt;();
                int i = 0;
                var kcentroids = new XY[k];

                int MAX = list.Count;
                while (MAX &gt;= k)
                {
                    int index = Rand.Next(0, MAX - 1);
                    var xy = list[index];
                    if (set.Contains(xy))
                        continue;

                    set.Add(xy);
                    kcentroids[i++] = new XY(xy.X, xy.Y) { Color = GetRandomColor() };

                    if (i &gt;= k)
                        break;
                }
                return kcentroids;
            }

            public List&lt;XY&gt; BaseGetClusterResult()
            {
                Profile(&quot;BaseGetClusterResult beg&quot;);                

                // collect used buckets and return the result
                var clusterPoints = new List&lt;XY&gt;();
                foreach (var item in BaseBucketsLookup)
                {
                    var bucket = item.Value;
                    if (bucket.IsUsed)
                    {
                        bucket.Centroid.Size = bucket.Points.Count;
                        clusterPoints.Add(bucket.Centroid);
                    }
                }

                return clusterPoints;
            }
            public static XY BaseGetCentroidFromCluster(List&lt;XY&gt; list) //O(n)
            {
                Profile(&quot;BaseGetCentroidFromCluster beg&quot;);
                int count = list.Count;
                if (list == null || count == 0)
                    return null;

                // color is set for the points and the cluster point here
                var color = GetRandomColor(); //O(1)
                XY centroid = new XY(0, 0) { Size = list.Count };//O(1)
                foreach (XY p in list)
                {
                    p.Color = color;
                    centroid.X += p.X;
                    centroid.Y += p.Y;
                }
                centroid.X /= count;
                centroid.Y /= count;
                var cp = new XY(centroid.X, centroid.Y) { Size = count, Color = color };

                return cp;
            }
            //O(k*n)
            public static void BaseSetCentroidForAllBuckets(IEnumerable&lt;Bucket&gt; buckets)
            {
                Profile(&quot;BaseSetCentroidForAllBuckets beg&quot;);
                foreach (var item in buckets)
                {
                    var bucketPoints = item.Points;
                    var cp = BaseGetCentroidFromCluster(bucketPoints);
                    item.Centroid = cp;
                }
            }
            public double BaseGetTotalError()//O(k)
            {
                int centroidsUsed = BaseBucketsLookup.Values.Count(b =&gt; b.IsUsed);
                double sum = BaseBucketsLookup.Values.
                    Where(b =&gt; b.IsUsed).Sum(b =&gt; b.ErrorLevel);
                return sum / centroidsUsed;
            }
            public string BaseGetMaxError() //O(k)
            {
                double maxError = -double.MaxValue;
                string id = string.Empty;
                foreach (var b in BaseBucketsLookup.Values)
                {
                    if (!b.IsUsed || b.ErrorLevel &lt;= maxError)
                        continue;

                    maxError = b.ErrorLevel;
                    id = b.Id;
                }
                return id;
            }
            public XY BaseGetClosestPoint(XY from, List&lt;XY&gt; list) //O(n)
            {
                double min = double.MaxValue;
                XY closests = null;
                foreach (var p in list)
                {
                    var d = MathTool.Distance(from, p);
                    if (d &gt;= min)
                        continue;

                    // update
                    min = d;
                    closests = p;
                }
                return closests;
            }
            public XY BaseGetLongestPoint(XY from, List&lt;XY&gt; list) //O(n)
            {
                double max = -double.MaxValue;
                XY longest = null;
                foreach (var p in list)
                {
                    var d = MathTool.Distance(from, p);
                    if (d &lt;= max)
                        continue;

                    // update
                    max = d;
                    longest = p;
                }
                return longest;
            }
            // assign all points to nearest cluster
            public void BaseUpdatePointsByCentroid()//O(n*k)
            {
                Profile(&quot;UpdatePointsByCentroid beg&quot;);
                int count = BaseBucketsLookup.Count();

                // clear points in the buckets, they will be re-inserted
                foreach (var bucket in BaseBucketsLookup.Values)
                    bucket.Points.Clear();

                foreach (XY p in BaseDataset)
                {
                    double minDist = Double.MaxValue;
                    string index = string.Empty;
                    //for (int i = 0; i &lt; count; i++)
                    foreach (var i in BaseBucketsLookup.Keys)
                    {
                        var bucket = BaseBucketsLookup[i];
                        if (bucket.IsUsed == false)
                            continue;

                        var centroid = bucket.Centroid;
                        var dist = MathTool.Distance(p, centroid);
                        if (dist &lt; minDist)
                        {
                            // update
                            minDist = dist;
                            index = i;
                        }
                    }
                    //update color for point to match centroid and re-insert
                    var closestBucket = BaseBucketsLookup[index];
                    p.Color = closestBucket.Centroid.Color;
                    closestBucket.Points.Add(p);
                }
            }

            // update centroid location to nearest point, 
            // e.g. if you want to show cluster point on a real existing point area
            //O(n)
            public void BaseUpdateCentroidToNearestContainingPoint(Bucket bucket)
            {
                Profile(&quot;BaseUpdateCentroidToNearestContainingPoint beg&quot;);
                if (bucket == null || bucket.Centroid == null ||
                    bucket.Points == null || bucket.Points.Count == 0)
                    return;

                var closest = BaseGetClosestPoint(bucket.Centroid, bucket.Points);
                bucket.Centroid.X = closest.X;
                bucket.Centroid.Y = closest.Y;
            }
            //O(k*n)
            public void BaseUpdateAllCentroidsToNearestContainingPoint()
            {
                Profile(&quot;BaseUpdateAllCentroidsToNearestContainingPoint beg&quot;);
                foreach (var bucket in BaseBucketsLookup.Values)
                    BaseUpdateCentroidToNearestContainingPoint(bucket);
            }
        }

        public class DistanceClusterer : BaseClusterAlgorithm
        {
            public DistanceClusterer(List&lt;XY&gt; dataset)
                : base(dataset)
            {                
            }

            public override List&lt;XY&gt; GetCluster()
            {
                var cluster = RunClusterAlgo();
                return cluster;
            }

            // O(k*n)
            List&lt;XY&gt; RunClusterAlgo()
            {
                // put points in buckets     
                int allPointsCount = BaseDataset.Count;
                var firstPoint = BaseDataset[0];
                firstPoint.Color = GetRandomColor();
                var firstId = 0.ToString();
                var firstBucket = new Bucket(firstId) { Centroid = firstPoint };
                BaseBucketsLookup.Add(firstId, firstBucket);

                for (int i = 1; i &lt; allPointsCount; i++)
                {
                    var set = new HashSet&lt;string&gt;(); //cluster candidate list
                    var p = BaseDataset[i];
                    // iterate clusters and collect candidates
                    foreach (var bucket in BaseBucketsLookup.Values)
                    {                        
                        var isInCluster = MathTool.DistWithin(p, bucket.Centroid, DISTANCECLUSTERER_SIZE);
                        if (!isInCluster)
                            continue;

                        set.Add(bucket.Id);
                        //use first, short dist will be calc at last step before returning data
                        break;
                    }

                    // if not within box area, then make new cluster   
                    if (set.Count == 0)
                    {
                        var pid = i.ToString();
                        p.Color = GetRandomColor();
                        var newbucket = new Bucket(pid) { Centroid = p };
                        BaseBucketsLookup.Add(pid, newbucket);
                    }
                }

                //important, align all points to closest cluster point
                BaseUpdatePointsByCentroid();

                return BaseGetClusterResult();
            }

        }

        public class MarkerClusterer : BaseClusterAlgorithm
        {
            public MarkerClusterer(List&lt;XY&gt; dataset) : base(dataset)
            {                
            }

            public override List&lt;XY&gt; GetCluster()
            {
                var cluster = RunClusterAlgo();
                return cluster;
            }

            // O(k*n)
            List&lt;XY&gt; RunClusterAlgo()
            {                
                // put points in buckets     
                int allPointsCount = BaseDataset.Count;
                var firstPoint = BaseDataset[0];
                firstPoint.Color = GetRandomColor();
                var firstId = 0.ToString();
                var firstBucket = new Bucket(firstId) { Centroid = firstPoint };                
                BaseBucketsLookup.Add(firstId, firstBucket);
                
                for (int i = 1; i &lt; allPointsCount; i++)
                {
                    var set = new HashSet&lt;string&gt;(); //cluster candidate list
                    var p = BaseDataset[i];
                    // iterate clusters and collect candidates
                    foreach (var bucket in BaseBucketsLookup.Values)
                    {
                        var isInCluster = MathTool.BoxWithin(p, bucket.Centroid, MARKERCLUSTERER_SIZE);
                        if (!isInCluster) 
                            continue;

                        set.Add(bucket.Id);
                        //use first, short dist will be calc at last step before returning data
                        break;
                    }

                    // if not within box area, then make new cluster   
                    if (set.Count == 0)
                    {
                        var pid = i.ToString();
                        p.Color = GetRandomColor();
                        var newbucket = new Bucket(pid) { Centroid = p };                        
                        BaseBucketsLookup.Add(pid, newbucket);
                    }
                }

                //important, align all points to closest cluster point
                BaseUpdatePointsByCentroid();
               
                return BaseGetClusterResult();
            }
        }

        // O(exponential) ~ can be slow when n or k is big
        public class KMeans : BaseClusterAlgorithm
        {
            private readonly int _InitClusterSize; // start from this cluster points
            // Rule of thumb k = sqrt(n/2)        

            // cluster point optimization iterations
            private const int _MaxIterations = 100; 
            private const int _MaxClusters = 100;

            public KMeans(List&lt;XY&gt; dataset) : base(dataset)
            {                
                _InitClusterSize = 1;
            }

            public override List&lt;XY&gt; GetCluster()
            {
                var cluster = RunClusterAlgo();
                return cluster;
            }

            List&lt;XY&gt; RunClusterAlgo()
            {
                /*
                ITERATIVE LINEAR ADDING CLUSTER UNTIL 
                REPEATE iteration of clusters convergence 
                   until the max error is small enough
                if not, insert a new cluster at worst place 
                (farthest in region of worst cluster area) and run again 
                keeping current cluster points              
             
                 // one iteration of clusters convergence is defined as ..
              1) Random k centroids
              2) Cluster data by euclidean distance to centroids
              3) Update centroids by clustered data,     
              4) Update cluster
              5) Continue last two steps until error is small, error is sum of diff
                  between current and updated centroid                          
               */

                RunAlgo();

                if (DoUpdateAllCentroidsToNearestContainingPoint)
                    BaseUpdateAllCentroidsToNearestContainingPoint();
                return BaseGetClusterResult();
            }

            void RunAlgo()
            {
                // Init clusters
                var centroids = BaseGetRandomCentroids(BaseDataset, _InitClusterSize);
                for (int i = 0; i &lt; centroids.Length; i++)
                {
                    var pid = i.ToString();
                    var newbucket = new Bucket(pid) { Centroid = centroids[i] };
                    BaseBucketsLookup.Add(pid, newbucket);
                }

                //
                double currentMaxError = double.MaxValue;
                while (currentMaxError &gt; MAX_ERROR &amp;&amp; BaseBucketsLookup.Count &lt; _MaxClusters)
                {
                    RunIterationsUntilKClusterPlacementAreDone();

                    var id = BaseGetMaxError();
                    var bucket = BaseBucketsLookup[id];
                    currentMaxError = bucket.ErrorLevel; //update
                    if (currentMaxError &gt; MAX_ERROR)
                    {
// Here it is linear speed when putting one new centroid at a time
// should be semi-fast because the new point is inserted at best area 
//from current centroids view.
// possible improvement exists by log2 search by inserting multiple centroids and 
// reducing centroid again if needed

// put new centroid in area where maxError but farthest away from current centroid in area
                        var longest = BaseGetLongestPoint(bucket.Centroid, bucket.Points);
                        var newcentroid = new XY(longest);
                        var newid = BaseBucketsLookup.Count.ToString();
                        var newbucket = new Bucket(newid) { Centroid = newcentroid };
                        BaseBucketsLookup.Add(newid, newbucket);
                    }
                }
            }

            void RunIterationsUntilKClusterPlacementAreDone()
            {
                Profile(&quot;RunIterationsUntilKClusterPlacementAreDone beg&quot;);
                double prevError = Double.MaxValue;
                double currError = Double.MaxValue;

                for (int i = 0; i &lt; _MaxIterations; i++)
                {
                    prevError = currError;
                    currError = RunOneIteration();
                    if (currError &gt;= prevError) // no improvement
                        break;
                }
            }

            double RunOneIteration() //O(k*n)
            {
                Profile(&quot;RunOneIteration beg&quot;);

                // update points, assign points to cluster
                BaseUpdatePointsByCentroid();
                // update centroid pos by its points
                BaseSetCentroidForAllBuckets(BaseBucketsLookup.Values);//O(k*n)
                var clustersCount = BaseBucketsLookup.Count;
                for (int i = 0; i &lt; clustersCount; i++)
                {
                    var currentBucket = BaseBucketsLookup[i.ToString()];
                    if (currentBucket.IsUsed == false)
                        continue;

                    //update centroid                
                    var newcontroid = BaseGetCentroidFromCluster(currentBucket.Points);
                    //no need to update color, autoset
                    currentBucket.Centroid = newcontroid;
                    currentBucket.ErrorLevel = 0;
                    //update error                
                    foreach (var p in currentBucket.Points)
                    {
                        var dist = MathTool.Distance(newcontroid, p);
                        currentBucket.ErrorLevel += dist;
                    }
                    var val = currentBucket.ErrorLevel / currentBucket.Points.Count;
                    currentBucket.ErrorLevel = val; //Math.Sqrt(val);
                }

                return BaseGetTotalError();
            }

        }

        public class GridCluster : BaseClusterAlgorithm
        {
            public GridCluster(List&lt;XY&gt; dataset) : base(dataset)
            {                
            }

            public override List&lt;XY&gt; GetCluster()
            {
                var cluster = RunClusterAlgo(GridX, GridY);
                return cluster;
            }

            // O(k*n)
            void MergeClustersGrid()
            {
                foreach (var key in BaseBucketsLookup.Keys)
                {
                    var bucket = BaseBucketsLookup[key];
                    if (bucket.IsUsed == false)
                        continue;

                    var x = bucket.Idx;
                    var y = bucket.Idy;

                    // get keys for neighbors
                    var N = GetId(x, y + 1);
                    var NE = GetId(x + 1, y + 1);
                    var E = GetId(x + 1, y);
                    var SE = GetId(x + 1, y - 1);
                    var S = GetId(x, y - 1);
                    var SW = GetId(x - 1, y - 1);
                    var W = GetId(x - 1, y);
                    var NW = GetId(x - 1, y - 1);
                    var neighbors = new[] { N, NE, E, SE, S, SW, W, NW };

                    MergeClustersGridHelper(key, neighbors);
                }
            }
            void MergeClustersGridHelper(string currentKey, string[] neighborKeys)
            {
                double minDistX = DeltaX / 2.0;//heuristic, higher value gives less merging
                double minDistY = DeltaY / 2.0;
                //if clusters in grid are too close to each other, merge them
                double minDist = MathTool.Max(minDistX, minDistY);

                foreach (var neighborKey in neighborKeys)
                {
                    if (!BaseBucketsLookup.ContainsKey(neighborKey))
                        continue;

                    var neighbor = BaseBucketsLookup[neighborKey];
                    if (neighbor.IsUsed == false)
                        continue;

                    var current = BaseBucketsLookup[currentKey];
                    var dist = MathTool.Distance(current.Centroid, neighbor.Centroid);
                    if (dist &gt; minDist)
                        continue;

                    // merge
                    var color = current.Centroid.Color;
                    foreach (var p in neighbor.Points)
                    {
                        //update color
                        p.Color = color;
                    }

                    current.Points.AddRange(neighbor.Points);//O(n)

                    // recalc centroid
                    var cp = BaseGetCentroidFromCluster(current.Points);
                    current.Centroid = cp;
                    neighbor.IsUsed = false; //merged, then not used anymore
                    neighbor.Points.Clear(); //clear mem
                }
            }

            public List&lt;XY&gt; RunClusterAlgo(int gridx, int gridy)
            {                
                // params invalid
                if (gridx &lt;= 0 || gridy &lt;= 0)
                    throw new ApplicationException(&quot;GridCluster.RunClusterAlgo gridx or gridy is &lt;= 0&quot;);

                // put points in buckets
                foreach (var p in BaseDataset)
                {
                    // find relative val
                    var relativeX = p.X - MinX;
                    var relativeY = p.Y - MinY;

                    int idx = (int)(relativeX / DeltaX);
                    int idy = (int)(relativeY / DeltaY);

                    // bucket id
                    string id = GetId(idx, idy);

                    // bucket exists, add point
                    if (BaseBucketsLookup.ContainsKey(id))
                        BaseBucketsLookup[id].Points.Add(p);

                    // new bucket, create and add point
                    else
                    {
                        Bucket bucket = new Bucket(idx, idy);
                        bucket.Points.Add(p);
                        BaseBucketsLookup.Add(id, bucket);
                    }
                }

                // calc centrod for all buckets
                BaseSetCentroidForAllBuckets(BaseBucketsLookup.Values);

                // merge if gridpoint is to close
                if (DoMergeGridIfCentroidsAreCloseToEachOther)
                    MergeClustersGrid();

                if (DoUpdateAllCentroidsToNearestContainingPoint)
                {
                    BaseUpdateAllCentroidsToNearestContainingPoint();                                        
                }

                // check again
                // merge if gridpoint is to close
                if (DoMergeGridIfCentroidsAreCloseToEachOther 
                    &amp;&amp; DoUpdateAllCentroidsToNearestContainingPoint)
                {
                    MergeClustersGrid();
                    // and again set centroid to closest point in bucket 
                    BaseUpdateAllCentroidsToNearestContainingPoint();
                }

                return BaseGetClusterResult();
            }
        }

        public class MathTool
        {
            private const double Exp = 2; // 2=euclid, 1=manhatten
            //Minkowski dist        
            public static double Distance(XY a, XY b)
            {
                return Math.Pow(Math.Pow(Math.Abs(a.X - b.X), Exp) +
                    Math.Pow(Math.Abs(a.Y - b.Y), Exp), 1.0 / Exp);
            }

            public static double Min(double a, double b)
            {
                return a &lt;= b ? a : b;
            }
            public static double Max(double a, double b)
            {
                return a &gt;= b ? a : b;
            }

            public static bool DistWithin(XY a, XY b, double d)
            {
                var dist = Distance(a, b);
                return dist &lt; d;
            }

            public static bool BoxWithin(XY a, XY b, double boxsize)
            {
                var d = boxsize / 2;
                var withinX = a.X - d &lt;= b.X &amp;&amp; a.X + d &gt;= b.X;
                var withinY = a.Y - d &lt;= b.Y &amp;&amp; a.Y + d &gt;= b.Y;
                return withinX &amp;&amp; withinY;
            }
        }

        public static class FileUtil
        {
            public static bool WriteFile(string data, FileInfo fileInfo)
            {
                bool isSuccess = false;
                try
                {
                    using (StreamWriter streamWriter =
                        File.CreateText(fileInfo.FullName))
                    {
                        streamWriter.Write(data);
                        isSuccess = true;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.StackTrace + &quot;\nPress a key ... &quot;);
                    Console.ReadKey();
                }
                return isSuccess;
            }
        }

        [Serializable()]
        public class DatasetToSerialize : ISerializable
        {
            private const string Name = &quot;Dataset&quot;;
            public List&lt;XY&gt; Dataset { get; set; }
            public DatasetToSerialize()
            {
                Dataset = new List&lt;XY&gt;();
            }
            public DatasetToSerialize(SerializationInfo info, StreamingContext ctxt)
            {
                this.Dataset = (List&lt;XY&gt;)info.GetValue(Name, typeof(List&lt;XY&gt;));
            }
            public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
            {
                info.AddValue(Name, this.Dataset);
            }
        }

        public class Serializer
        {
            public void SerializeObject(string filename, object objectToSerialize)
            {
                try
                {
                    using (Stream stream = File.Open(filename, FileMode.Create))
                    {
                        BinaryFormatter bFormatter = new BinaryFormatter();
                        bFormatter.Serialize(stream, objectToSerialize);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.StackTrace + &quot;\nPress a key ... &quot;);
                    Console.ReadKey();
                }
            }
            public object DeSerializeObject(string filename)
            {
                try
                {
                    using (Stream stream = File.Open(filename, FileMode.Open))
                    {
                        BinaryFormatter bFormatter = new BinaryFormatter();
                        var objectToSerialize = bFormatter.Deserialize(stream);
                        return objectToSerialize;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.StackTrace + &quot;\nPress a key ... &quot;);
                    Console.ReadKey();
                }
                return null;
            }
        }
    }
}
</pre></p>
<p>Put both the html and javascript file in the c:\temp folder.<br />
The C# program creates a new draw.js file in the c:\temp folder.</p>
<p>Open the html file with a modern browser that support canvas, like Firefox or Chrome.<br />
If you want to test with Internet Explorer on your local machine, you should download the <a href="http://code.google.com/p/explorercanvas/downloads/list" title="excanvas" target="_blank">excanvas javascript plugin</a><br />
<strong>canvas.html</strong><br />
<pre class="brush: xml; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Cluster Test&lt;/title&gt;
    &lt;!-- IE canvas not supported fallback --&gt;
    &lt;!--[if lt IE 10]&gt;
	&lt;script src=&quot;excanvas.js&quot;&gt;&lt;/script&gt;
    &lt;![endif]--&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;draw.js&quot;&gt;&lt;/script&gt;
    &lt;!-- default --&gt;
    &lt;script type=&quot;text/javascript&quot;&gt;
        var G = {
            yellow: &quot;#FF0&quot;,
            red: &quot;#F00&quot;,
            green: &quot;#0F0&quot;,
            blue: &quot;#00F&quot;,
            black: &quot;#000&quot;
        }

        function drawMark(cx, cy, color, ctx) {
            //draw a circle
            var radius = 4;
            ctx.beginPath();
            ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.fillStyle = color;
            ctx.fill();
            //ctx.stroke();
        }

        function drawCluster(cx, cy, color, n, ctx) {
            //draw a bigger circle
            var radius = 12;
            ctx.strokeStyle = color;
            ctx.beginPath();
            ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.stroke();
            ctx.fillStyle = color;
            ctx.fillText(&quot;&quot; + n, cx - 8, cy + 2);
        }

        function drawMarkerCluster(cx, cy, color, n, size, ctx) {
            drawCluster(cx, cy, color, n, ctx);
            // draw rect
            var a = cx - (size / 2);
            var b = cy - (size / 2);
            var len = size;
            ctx.lineWidth = 2;
            ctx.strokeStyle = color;
            ctx.strokeRect(a, b, len, len);
        }

        function drawDistanceCluster(cx, cy, color, n, size, ctx) {
            drawCluster(cx, cy, color, n, ctx);
            // draw radius circle
            var radius = size;
            ctx.lineWidth = 2;
            ctx.strokeStyle = color;
            ctx.beginPath();
            ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.stroke();
        }

        window.onload = function draw() {
            var canvas = document.getElementById('canvas');
            if (canvas != null &amp;&amp; canvas != undefined &amp;&amp; canvas.getContext) {
                var ctx = canvas.getContext('2d');
                ctx.strokeStyle = G.black;
                ctx.font = &quot;10pt Arial&quot;;
                drawMarkers(ctx);
            }
        }
    &lt;/script&gt;
    &lt;style type=&quot;text/css&quot;&gt;
        body
        {
            margin-left: 10px;
            margin-top: 10px;
        }
        canvas
        {
            border: 1px solid red;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;canvas id=&quot;canvas&quot; width=&quot;610&quot; height=&quot;430&quot;&gt;
        &lt;p&gt;Your browser doesn't support canvas. Try Firefox, Chrome or an another modern browser.&lt;/p&gt;
    &lt;/canvas&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre></p>
<p>The javascript file will be overwritten when you run the C# code.<br />
<strong>draw.js</strong><br />
<pre class="brush: jscript; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
function drawMarkers(ctx) {
    drawMark(156.434, 157.921, 'rgb(182,114,144)', ctx);
    drawMark(156.788, 139.939, 'rgb(182,114,144)', ctx);
    drawMark(150.712, 67.882, 'rgb(67,235,136)', ctx);
    drawMark(445.846, 165.556, 'rgb(153,85,202)', ctx);
    drawMark(382.982, 179.917, 'rgb(153,85,202)', ctx);
    drawMark(466.365, 172.098, 'rgb(153,85,202)', ctx);
    drawMark(302.81, 243.478, 'rgb(48,234,127)', ctx);
    drawMark(235.35, 265.31, 'rgb(48,234,127)', ctx);
    drawMark(262.092, 258.321, 'rgb(48,234,127)', ctx);

    drawMarkerCluster(156.434, 157.921, 'rgb(182,114,144)', 2, 160, ctx);
    drawMarkerCluster(150.712, 67.882, 'rgb(67,235,136)', 1, 160, ctx);
    drawMarkerCluster(445.846, 165.556, 'rgb(153,85,202)', 3, 160, ctx);
    drawMarkerCluster(302.81, 243.478, 'rgb(48,234,127)', 3, 160, ctx);
    ctx.fillStyle = 'rgb(0,0,0)';
    ctx.fillText('Clusters = ' + 4, 20, 320);
}
</pre></p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/algorithm/'>Algorithm</a>, <a href='http://kunuk.wordpress.com/category/csharp/'>Csharp</a>, <a href='http://kunuk.wordpress.com/category/javascript/'>Javascript</a>, <a href='http://kunuk.wordpress.com/category/visualization/'>Visualization</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/386/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/386/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/386/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/386/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/386/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/386/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/386/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/386/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/386/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/386/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/386/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/386/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/386/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/386/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=386&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/09/20/markerclusterer-with-c-example-and-html-canvas-part-3/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2011/09/markercluster1.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/markercluster2.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/markercluster3.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/markercluster4.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/markercluster5a.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/markercluster6a.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/distanceclusterer.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/markercluster7-5sec1000000p.gif" medium="image" />
	</item>
		<item>
		<title>Clustering K-means and Grid with C# example and html-canvas (part 2)</title>
		<link>http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2/</link>
		<comments>http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2/#comments</comments>
		<pubDate>Sat, 17 Sep 2011 07:35:34 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Csharp]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Visualization]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[clustering]]></category>
		<category><![CDATA[k-means]]></category>
		<category><![CDATA[points overlapping]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=349</guid>
		<description><![CDATA[This is a follow up to my previous post. http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster The next post with updated code can be seen here. http://kunuk.wordpress.com/2011/09/20/markerclusterer-with-c-example-and-html-canvas-part-3/ I have implemented K-means clustering algorithm which automatically increments the K value until the errorlevel is low enough to be accepted. The running time of the K-means runs reasonable fast. It&#8217;s best used in [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=349&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>This is a follow up to my previous post.<br />
<a href="http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster" title="http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster" target="_blank">http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster</a></p>
<p>The next post with updated code can be seen here.<br />
<a href="http://kunuk.wordpress.com/2011/09/20/markerclusterer-with-c-example-and-html-canvas-part-3/" title="http://kunuk.wordpress.com/2011/09/20/markerclusterer-with-c-example-and-html-canvas-part-3/" target="_blank">http://kunuk.wordpress.com/2011/09/20/markerclusterer-with-c-example-and-html-canvas-part-3/</a></p>
<p>I have implemented K-means clustering algorithm which automatically increments the K value until the errorlevel is low enough to be accepted. The running time of the K-means runs reasonable fast. It&#8217;s best used in combination of chosing the right amount of points and setting the max_error. If the max_error is small and the number of points are big then the running time will be very slow. There are max iterations set to avoid very long running time as I believe K-means is NP-hard.</p>
<p>The insertion of the extra cluster point is linear and works by inserting them at the best place and keeping the current cluster points. It finds the current bad performing cluster point regarding error value and insert a new cluster point in that region farthest away from the bad performing cluster point. By doing this the new points are inserted at place which seems logical while minimizing the overall error level.</p>
<p>The Grid clustering algorithm runs pretty fast. Even with 100.000 points the time is below 2 sec. For 10.000 points the time is below 1 sec.</p>
<p>There is added an optional functionality to move the cluster point position to the closest point in the region of the clustered area after the final centroid calculation. By using this your cluster point will be positioned visually on a true existing point in the region.</p>
<p>When used with maps and websites the grid clustering algorithm is a good candidate of chose because of it&#8217;s fast running time which is O(m*n) where m is the number of grids and n is the number of points, please comment if I have made a wrong statement <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /><br />
The K-means can also be used when you know the expected points and setting the max_error accordingly.</p>
<p>Below are some image examples and the C# source code bundled in one file to keep it simple.</p>
<p>K-means 0.7 sec 10.000 points, max_error 100<br />
<img src="http://kunuk.files.wordpress.com/2011/09/kmeans_0-7sec10000perr100.gif?w=700" /></p>
<p>K-means 2.2 sec 10.000 points, max_error 50<br />
<img src="http://kunuk.files.wordpress.com/2011/09/kmeans_2-2sec10000perr50.gif?w=700" /></p>
<p>K-means 0.2 sec 150 points, max_error 30<br />
<img src="http://kunuk.files.wordpress.com/2011/09/0-2sec150perr30.gif?w=700" /></p>
<p>K-means, an example of how K-means can&#8217;t &#8220;see&#8221; all the true clusters. Here the K is manually set to 3.<br />
This will not happen with the current code, but this is mainly to show some weakness of the K-means algorithm.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/kmeans_weak.gif?w=700" /></p>
<p>To many points view<br />
<img src="http://kunuk.files.wordpress.com/2011/09/gridcluster1.gif?w=700" /></p>
<p>Grid clustering algorithm with grid 7&#215;6 took 0.04 sec. with 160 points.<br />
The cluster points location are moved to the nearest point in the region after the centroid calculation in this example. Some grid merging has also occurred because the clusters were too close to each other.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/gridcluster21.gif?w=700" /></p>
<p>Final clustered result view<br />
<img src="http://kunuk.files.wordpress.com/2011/09/gridcluster31.gif?w=700" /></p>
<p>Performance test of the grid by using lots of points.<br />
Grid 8&#215;7, 1.3 sec, 100.000 points.<br />
With Grid 6&#215;5, it&#8217;s about 3 sec, without centroid calculation and without saving to file for 1.000.000 points.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/grid_1-3sec100000p8x7.gif?w=700" /></p>
<p><strong>Algorithm.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;"> 
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

// By Kunuk Nykjaer
// Clustering package, Grid clustering and auto increasing K-means clustering
// Version 0.2
public class Algorithm
{
    public static DateTime Starttime;
    static void Main(string[] args)
    {
        Starttime = DateTime.Now;
        var algo = new Algorithm();

        // run kmeans or grid
        algo.Run(ClusterType.KMeans); 
        //algo.Run(ClusterType.Grid);

        algo.GenerateJavascriptDrawFile(); //create draw.js

        var timespend = DateTime.Now.Subtract(Starttime).TotalSeconds;     
        Console.WriteLine(timespend+&quot; sec. press a key ...&quot;);
        Console.ReadKey();
    }

    // use centroid or semi-centroid cluster point placement visualization?
    public const bool DoUpdateAllCentroidsToNearestContainingPoint = false;
    public const bool UseProfiling = false; //debug, output time spend

    public enum ClusterType { Unknown = -1, Grid, KMeans } ;
    static readonly CultureInfo Culture_enUS = new CultureInfo(&quot;en-US&quot;);
    const string S = &quot;G&quot;;

    readonly List&lt;XY&gt; _points = new List&lt;XY&gt;();
    List&lt;XY&gt; _clusters = new List&lt;XY&gt;();

    public static readonly Random _rand = new Random();
    public const double MinX = 10;
    public const double MinY = 10;
    public const double MaxX = 400;
    public const double MaxY = 300;

    // used with Grid cluster algo
    public const int GridX = 8;
    public const int GridY = 7;

    const double AbsSizeX = MaxX - MinX;
    const double AbsSizeY = MaxY - MinY;

    // for bucket placement calc, grid cluster algo
    const double DeltaX = AbsSizeX / GridX;
    const double DeltaY = AbsSizeY / GridY;

    static private readonly string NL = Environment.NewLine;
    private const string ctx = &quot;ctx&quot;; // javascript canvas
    public bool DisplayGridInCanvas; //autoset by used cluster type

    public Algorithm()
    {
        CreateDataSet();
    }

    public static string GetId(int idx, int idy) //O(1)
    {
        return idx + &quot;;&quot; + idy;
    }

    void CreateDataSet() //O(1)
    {
        // CREATE DATA SET

        // Create random scattered points
        //for (int i = 0; i &lt; 150; i++)
        //{
        //    var x = MinX + _rand.NextDouble() * (AbsSizeX);
        //    var y = MinY + _rand.NextDouble() * (AbsSizeY);
        //    _points.Add(new XY(x, y));
        //}
        
        // Create random region of clusters
        for (int i = 0; i &lt; 50; i++)
        {
            var x = MinX + _rand.NextDouble() * 100;
            var y = MinY + _rand.NextDouble() * 100;
            _points.Add(new XY(x, y));
        }

        for (int i = 0; i &lt; 50; i++)
        {
            var x = MinX + 200 + _rand.NextDouble() * 100;
            var y = MinY + 10 + _rand.NextDouble() * 100;
            _points.Add(new XY(x, y));
        }

        for (int i = 0; i &lt; 50; i++)
        {
            var x = MinX + 50 + _rand.NextDouble() * 100;
            var y = MinY + 150 + _rand.NextDouble() * 100;
            _points.Add(new XY(x, y));
        }         
        
    }

    public void Run(ClusterType clustertype)
    {
        switch (clustertype)
        {
            case ClusterType.Grid:
                _clusters = new GridBaseCluster(_points).GetCluster();
                DisplayGridInCanvas = true;
                break;
            case ClusterType.KMeans:
                _clusters = new KMeans(_points).GetCluster();
                DisplayGridInCanvas = false;
                break;
        }
    }

    public static string GetRandomColor()
    {
        int r = _rand.Next(10, 240);
        int g = _rand.Next(10, 240);
        int b = _rand.Next(10, 240);
        return string.Format(&quot;'rgb({0},{1},{2})'&quot;, r, g, b);
    }

    static void CreateFile(string s)
    {
        var path = new FileInfo(@&quot;C:\temp\draw.js&quot;);
        var isCreated = FileUtil.WriteFile(s, path);
        Console.WriteLine(isCreated + &quot; = File is Created&quot;);
    }
    public void GenerateJavascriptDrawFile()
    {
        var sb = new StringBuilder();

        // markers
        var head = NL + &quot;function drawMarkers(ctx) {&quot; + NL;
        var tail = NL + &quot;}&quot; + NL;

        sb.Append(head);

        // grid
        if (DisplayGridInCanvas)
        {
            sb.Append(&quot;ctx.beginPath();&quot; + NL);
            for (int i = 0; i &lt; GridX + 1; i++)
            {
                sb.Append(String.Format(&quot;ctx.moveTo({0}, {1});{2}&quot;,
                                        ToStringEN(MinX + i * DeltaX), 
                                        ToStringEN(MinY), NL));
                sb.Append(String.Format(&quot;ctx.lineTo({0}, {1});{2}&quot;,
                                        ToStringEN(MinX + i * DeltaX), 
                                        ToStringEN(MaxY), NL));
            }
            for (int j = 0; j &lt; GridY + 1; j++)
            {
                sb.Append(String.Format(&quot;ctx.moveTo({0}, {1});{2}&quot;,
                                        ToStringEN(MinX), 
                                        ToStringEN(MinY + j * DeltaY), NL));
                sb.Append(String.Format(&quot;ctx.lineTo({0}, {1});{2}&quot;,
                                        ToStringEN(MaxX), 
                                        ToStringEN(MinY + j * DeltaY), NL));
            }
            sb.Append(&quot;ctx.stroke(); &quot; + NL);
        }

        // markers
        if (_points != null)
            foreach (var p in _points)
                sb.Append(string.Format(&quot;drawMark({0}, {1}, {2}, {3});{4}&quot;,
                    ToStringEN(p.X), ToStringEN(p.Y), p.Color, ctx, NL));

        string clusterInfo = &quot;0&quot;;
        if (_clusters != null)
        {
            foreach (var c in _clusters)
                sb.Append(string.Format(&quot;drawCluster({0}, {1}, {2}, {3}, {4});{5}&quot;,
                    ToStringEN(c.X), ToStringEN(c.Y), c.Color,
                                        c.Size, ctx, NL));

            clusterInfo = _clusters.Count + string.Empty;
        }

        // bottom text                
        sb.Append(&quot;ctx.fillStyle = 'rgb(0,0,0)';&quot; + NL);
        sb.Append(string.Format(&quot;ctx.fillText('Clusters = ' + {0}, {1}, {2});{3}&quot;,
            clusterInfo, ToStringEN(MinX + 10), ToStringEN(MaxY + 20), NL));

        sb.Append(tail);
        CreateFile(sb.ToString());
        //Console.WriteLine(sb.ToString());
    }
    public static string ToStringEN(double d)
    {
        double rounded = Math.Round(d, 3);
        return rounded.ToString(S, Culture_enUS);
    }
    public static void Profile(string s)
    {
        if(!UseProfiling)
            return;        
        var timespend = DateTime.Now.Subtract(Starttime).TotalSeconds;
        Console.WriteLine(timespend + &quot; sec. &quot; + s);
    }//O(1)

    public class XY : IComparable
    {
        public double X { get; set; }
        public double Y { get; set; }
        public string Color { get; set; }
        public int Size { get; set; }
        public string XToString { get { return X.ToString(S, Culture_enUS); } }
        public string YToString { get { return Y.ToString(S, Culture_enUS); } }
        public XY(double x, double y)
        {
            X = x;
            Y = y;
            Color = &quot;'rgb(0,0,0)'&quot;;//default
            Size = 1;
        }
        public XY(XY p) //clone
        {
            this.X = p.X;
            this.Y = p.Y;
            this.Color = p.Color;
            this.Size = p.Size;
        }

        public int CompareTo(object o) // if used in sorted list
        {
            if (this.Equals(o))
                return 0;

            var other = (XY)o;
            if (this.X &gt; other.X)
                return -1;
            if (this.X &lt; other.X)
                return 1;

            return 0;
        }
        public override int GetHashCode()
        {
            var x = X * 10000;
            var y = Y * 10000;
            var r = x * 17 + y * 37;
            return (int)r;
        }
        private const int ROUND = 6;
        public override bool Equals(Object o)
        {
            if (o == null)
                return false;
            var other = o as XY;
            if (other == null)
                return false;

            var x = Math.Round(this.X, ROUND) == Math.Round(other.X, ROUND);
            var y = Math.Round(this.Y, ROUND) == Math.Round(other.Y, ROUND);
            return x &amp;&amp; y;
        }
    }
    public class Bucket
    {
        public string Id { get; private set; }
        public List&lt;XY&gt; Points { get; private set; }
        public XY Centroid { get; set; }
        public int Idx { get; private set; }
        public int Idy { get; private set; }
        public double ErrorLevel { get; set; } // clusterpoint and points avg dist
        private bool _IsUsed;
        public bool IsUsed
        {
            get { return _IsUsed &amp;&amp; Centroid != null; }
            set{_IsUsed = value; }
        }
        public Bucket(string id)
        {
            IsUsed = true;
            Centroid = null;
            Points = new List&lt;XY&gt;();
            Id = id;
        }
        public Bucket(int idx, int idy)
        {
            IsUsed = true;
            Centroid = null;
            Points = new List&lt;XY&gt;();
            Idx = idx;
            Idy = idy;
            Id = GetId(idx, idy);
        }
    }

    public abstract class BaseClusterAlgorithm
    {
        public List&lt;XY&gt; BaseDataset; // all points
        //id, bucket
        public readonly Dictionary&lt;string, Bucket&gt; BaseBucketsLookup = 
            new Dictionary&lt;string, Bucket&gt;();
        public abstract List&lt;XY&gt; GetCluster();
        //O(k?? random fn can be slow)
        public static XY[] BaseGetRandomCentroids(List&lt;XY&gt; list, int k)
        {
            var set = new HashSet&lt;XY&gt;();
            int i = 0;
            var kcentroids = new XY[k];

            int MAX = list.Count;
            while (MAX &gt;= k)
            {
                int index = _rand.Next(0, MAX - 1);
                var xy = list[index];
                if (set.Contains(xy))
                    continue;

                set.Add(xy);
                kcentroids[i++] = new XY(xy.X, xy.Y) { Color = GetRandomColor() };

                if (i &gt;= k)
                    break;
            }

            Profile(&quot;BaseGetRandomCentroids&quot;);
            return kcentroids;
        }

        public List&lt;XY&gt; BaseGetClusterResult()//O(k*n)
        {
            var clusterPoints = new List&lt;XY&gt;();
            // collect used buckets and return the result

            if (DoUpdateAllCentroidsToNearestContainingPoint)
                BaseUpdateAllCentroidsToNearestContainingPoint();

            foreach (var item in BaseBucketsLookup)
            {
                var bucket = item.Value;
                if (bucket.IsUsed)
                    clusterPoints.Add(bucket.Centroid);
            }

            Profile(&quot;BaseGetClusterResult&quot;);
            return clusterPoints;
        }
        public static XY BaseGetCentroidFromCluster(List&lt;XY&gt; list) //O(n)
        {
            int count = list.Count;
            if (list == null || count == 0)
                return null;

            // color is set for the points and the cluster point here
            var color = GetRandomColor(); //O(1)
            XY centroid = new XY(0, 0) { Size = list.Count };//O(1)
            foreach (XY p in list)
            {
                p.Color = color;
                centroid.X += p.X;
                centroid.Y += p.Y;
            }
            centroid.X /= count;
            centroid.Y /= count;
            var cp = new XY(centroid.X, centroid.Y) { Size = count, Color = color };

            Profile(&quot;BaseGetCentroidFromCluster&quot;);
            return cp;
        }
        //O(k*n)
        public static void BaseSetCentroidForAllBuckets(IEnumerable&lt;Bucket&gt; buckets)
        {
            foreach (var item in buckets)
            {
                var bucketPoints = item.Points;
                var cp = BaseGetCentroidFromCluster(bucketPoints);
                item.Centroid = cp;
            }
            Profile(&quot;BaseSetCentroidForAllBuckets&quot;);
        }
        public double BaseGetTotalError()//O(k)
        {
            int centroidsUsed = BaseBucketsLookup.Values.Count(b =&gt; b.IsUsed);
            double sum = BaseBucketsLookup.Values.Where(b =&gt; b.IsUsed).Sum(b =&gt; b.ErrorLevel);
            return sum / centroidsUsed;
        }
        public string BaseGetMaxError() //O(k)
        {
            double maxError = -double.MaxValue;
            string id = string.Empty;
            foreach (var b in BaseBucketsLookup.Values)
            {
                if (!b.IsUsed || b.ErrorLevel &lt;= maxError) 
                    continue;

                maxError = b.ErrorLevel;
                id = b.Id;
            }
            return id;
        }
         public XY BaseGetClosestPoint(XY from, List&lt;XY&gt; list) //O(n)
         {
             double min = double.MaxValue;
             XY closests = null;
             foreach (var p in list)
             {
                 var d = MathTool.Distance(from, p);
                 if (d &gt;= min) 
                     continue;

                 // update
                 min = d;
                 closests = p;
             }
             return closests;
         }        
         public XY BaseGetLongestPoint(XY from, List&lt;XY&gt; list) //O(n)
         {
             double max = -double.MaxValue;
             XY longest = null;
             foreach (var p in list)
             {
                 var d = MathTool.Distance(from, p);
                 if (d &lt;= max)
                     continue;

                 // update
                 max = d;
                 longest = p;
             }
             return longest;
         }

        // update centroid location to nearest point, 
        // e.g. if you want to show cluster point on a real existing point area
         //O(n)
         public void BaseUpdateCentroidToNearestContainingPoint(Bucket bucket) 
         {
             if(bucket==null || bucket.Centroid==null || 
                 bucket.Points==null||bucket.Points.Count==0)
                 return;

             var closest = BaseGetClosestPoint(bucket.Centroid, bucket.Points);
             bucket.Centroid.X = closest.X;
             bucket.Centroid.Y = closest.Y;
             Profile(&quot;BaseUpdateCentroidToNearestContainingPoint&quot;);
         }
         //O(k*n)
         public void BaseUpdateAllCentroidsToNearestContainingPoint() 
        {
             foreach (var bucket in BaseBucketsLookup.Values)             
                 BaseUpdateCentroidToNearestContainingPoint(bucket);
             Profile(&quot;BaseUpdateAllCentroidsToNearestContainingPoint&quot;);
        }
    }

    // O(exponential) ~ can be slow when n or k is big
    public class KMeans : BaseClusterAlgorithm
    {
        private const int _InitClusterSize = 1; // start from this cluster points

        // heuristic, set tolerance for cluster density, has effect on running time.
        // Set high for many points in dataset, can be lower for fewer points
        private const double MAX_ERROR = 30;             
            
        private const int _MaxIterations = 100; // cluster point optimization iterations
        private const int _MaxClusters = 100;

        public KMeans(List&lt;XY&gt; dataset)
        {
            if (dataset==null || dataset.Count==0)
                throw new ApplicationException(
                    string.Format(&quot;KMeans dataset is null or empty&quot;) );

            this.BaseDataset = dataset;            
        }

        public override List&lt;XY&gt; GetCluster()
        {
            var cluster = RunClusterAlgo();
            return cluster;
        }

        List&lt;XY&gt; RunClusterAlgo()
        {
            /*
            ITERATIVE LINEAR ADDING CLUSTER UNTIL 
            REPEATE iteration of clusters convergence 
             * until the max error is small enough
            if not, insert a new cluster at worst place 
            (farthest in region of worst cluster area) and run again 
            keeping current cluster points              
             
             // one iteration of clusters convergence is defined as ..
          1) Random k centroids
          2) Cluster data by euclidean distance to centroids
          3) Update centroids by clustered data,     
          4) Update cluster
          5) Continue last two steps until error is small, error is sum of diff
              between current and updated centroid                          
           */

            var clusterPoints = new List&lt;XY&gt;();
            // params invalid
            if (BaseDataset == null || BaseDataset.Count == 0)
                return clusterPoints;

            RunAlgo();         
            return BaseGetClusterResult();
        }

        void RunAlgo()
        {
            // Init clusters
            var centroids = BaseGetRandomCentroids(BaseDataset, _InitClusterSize);
            for (int i = 0; i &lt; centroids.Length; i++)
            {
                var newbucket = new Bucket(i.ToString()) { Centroid = centroids[i] };
                BaseBucketsLookup.Add(i.ToString(), newbucket);
            }            

            //
            double currentMaxError = double.MaxValue;
            while (currentMaxError &gt; MAX_ERROR &amp;&amp; BaseBucketsLookup.Count &lt; _MaxClusters)
            {
                RunIterationsUntilKClusterPlacementAreDone();                

                var id = BaseGetMaxError();
                var bucket = BaseBucketsLookup[id];
                currentMaxError = bucket.ErrorLevel; //update
                if (currentMaxError &gt; MAX_ERROR)
                {                                        
// Here it is linear speed when putting one new centroid at a time
// should be semi-fast because the new point is inserted at best area 
//from current centroids view.
// possible improvement exists by log2 search by inserting multiple centroids and 
// reducing centroid again by using closest pair algo which can be O(nlogn) to 
// find closest cluster dist and analysing the dist length, if its to small, 
//then reduce centroids.

// put new centroid in area where maxError but farthest away from current centroid in area
                    var longest = BaseGetLongestPoint(bucket.Centroid, bucket.Points);
                    var newcentroid = new XY(longest);
                    var newid = BaseBucketsLookup.Count.ToString();
                    var newbucket = new Bucket(newid) { Centroid = newcentroid };
                    BaseBucketsLookup.Add(newid, newbucket);
                }
            }                     
        }

        void RunIterationsUntilKClusterPlacementAreDone()
        {
            double prevError = Double.MaxValue;
            double currError = Double.MaxValue;

            for (int i = 0; i &lt; _MaxIterations; i++)
            {
                prevError = currError;
                currError = RunOneIteration();
                if (currError &gt;= prevError) // no improvement then stop
                    break;
            }
            Profile(&quot;RunIterationsUntilKClusterPlacementAreDone&quot;);
        }

        double RunOneIteration() //O(k*n)
        {
            // update points, assign points to cluster
            UpdatePointsByCentroid();
            // update centroid pos by its points
            BaseSetCentroidForAllBuckets(BaseBucketsLookup.Values);//O(k*n)
            var clustersCount = BaseBucketsLookup.Count;
            for (int i = 0; i &lt; clustersCount; i++)
            {
                var currentBucket = BaseBucketsLookup[i.ToString()];
                if (currentBucket.IsUsed == false)
                    continue;

                //update centroid                
                var newcontroid = BaseGetCentroidFromCluster(currentBucket.Points);
                //no need to update color, autoset
                currentBucket.Centroid = newcontroid; 
                currentBucket.ErrorLevel = 0;
                //update error                
                foreach (var p in currentBucket.Points)
                {
                    var dist = MathTool.Distance(newcontroid, p);
                    currentBucket.ErrorLevel += dist;
                }
                var val = currentBucket.ErrorLevel/currentBucket.Points.Count;
                currentBucket.ErrorLevel = val; //Math.Sqrt(val);
            }

            Profile(&quot;RunOneIteration&quot;);
            return BaseGetTotalError();
        }
        void UpdatePointsByCentroid()//O(n*k)
        {
            int count = BaseBucketsLookup.Count();
          
            // clear points in the buckets, they will be re-inserted
            foreach (var bucket in BaseBucketsLookup.Values)
                bucket.Points.Clear();

            foreach (XY p in BaseDataset)
            {
                double minDist = Double.MaxValue;
                int index = -1;
                for (int i = 0; i &lt; count; i++)
                {
                    var bucket = BaseBucketsLookup[i.ToString()];
                    if (bucket.IsUsed == false)
                        continue;

                    var centroid = bucket.Centroid;
                    var dist = MathTool.Distance(p, centroid);
                    if (dist &lt; minDist)
                    {
                        // update
                        minDist = dist;
                        index = i;
                    }
                }
                //update color for point to match centroid and re-insert
                var closestBucket = BaseBucketsLookup[index.ToString()];
                p.Color = closestBucket.Centroid.Color;
                closestBucket.Points.Add(p);
            }

            Profile(&quot;UpdatePointsByCentroid&quot;);
        }
    }

    public class GridBaseCluster : BaseClusterAlgorithm
    {
        public GridBaseCluster(List&lt;XY&gt; dataset)
        {
            BaseDataset = dataset;
        }

        public override List&lt;XY&gt; GetCluster()
        {
            var cluster = RunClusterAlgo(GridX, GridY);
            return cluster;
        }

        // O(k*n)
        void MergeClustersGrid()
        {
            foreach (var key in BaseBucketsLookup.Keys)
            {
                var bucket = BaseBucketsLookup[key];
                if (bucket.IsUsed == false)
                    continue;

                var x = bucket.Idx;
                var y = bucket.Idy;

                // get keys for neighbors
                var N = GetId(x, y + 1);
                var NE = GetId(x + 1, y + 1);
                var E = GetId(x + 1, y);
                var SE = GetId(x + 1, y - 1);
                var S = GetId(x, y - 1);
                var SW = GetId(x - 1, y - 1);
                var W = GetId(x - 1, y);
                var NW = GetId(x - 1, y - 1);
                var neighbors = new[] { N, NE, E, SE, S, SW, W, NW };

                MergeClustersGridHelper(key, neighbors);
            }
        }
        void MergeClustersGridHelper(string currentKey, string[] neighborKeys)
        {
            double minDistX = DeltaX / 2.0;//heuristic, higher value gives less merging
            double minDistY = DeltaY / 2.0;
            //if clusters in grid are too close to each other, merge them
            double minDist = MathTool.Max(minDistX, minDistY);

            foreach (var neighborKey in neighborKeys)
            {
                if (!BaseBucketsLookup.ContainsKey(neighborKey))
                    continue;
                
                var neighbor = BaseBucketsLookup[neighborKey];
                if (neighbor.IsUsed == false)
                    continue;

                var current = BaseBucketsLookup[currentKey];
                var dist = MathTool.Distance(current.Centroid, neighbor.Centroid);
                if (dist &gt; minDist)
                    continue;

                // merge
                var color = current.Centroid.Color;
                foreach (var p in neighbor.Points)
                {
                    //update color
                    p.Color = color;
                }

                current.Points.AddRange(neighbor.Points);//O(n)

                // recalc centroid
                var cp = BaseGetCentroidFromCluster(current.Points);
                current.Centroid = cp;
                neighbor.IsUsed = false; //merged, then not used anymore
            }
        }

        public List&lt;XY&gt; RunClusterAlgo(int gridx, int gridy)
        {
            var clusterPoints = new List&lt;XY&gt;();
            // params invalid
            if (BaseDataset == null || BaseDataset.Count == 0 || 
                gridx &lt;= 0 || gridy &lt;= 0)
                return clusterPoints;

            // put points in buckets
            foreach (var p in BaseDataset)
            {
                // find relative val
                var relativeX = p.X - MinX;
                var relativeY = p.Y - MinY;

                int idx = (int)(relativeX / DeltaX);
                int idy = (int)(relativeY / DeltaY);

                // bucket id
                string id = GetId(idx, idy);

                // bucket exists, add point
                if (BaseBucketsLookup.ContainsKey(id))
                    BaseBucketsLookup[id].Points.Add(p);

                // new bucket, create and add point
                else
                {
                    Bucket bucket = new Bucket(idx, idy);
                    bucket.Points.Add(p);
                    BaseBucketsLookup.Add(id, bucket);
                }
            }

            // calc centrod for all buckets
            BaseSetCentroidForAllBuckets(BaseBucketsLookup.Values);

            // merge if gridpoint is to close
            MergeClustersGrid();

            return BaseGetClusterResult();
        }
    }

    public class MathTool
    {
        private const double Exp = 2; // 2=euclid, 1=manhatten
        //Minkowski dist
        public static double Distance(Algorithm.XY a, Algorithm.XY b)
        {
            return Math.Pow(Math.Pow(Math.Abs(a.X - b.X), Exp) +
                Math.Pow(Math.Abs(a.Y - b.Y), Exp), 1.0 / Exp);
        }

        public static double Min(double a, double b)
        {
            return a &lt;= b ? a : b;
        }
        public static double Max(double a, double b)
        {
            return a &gt;= b ? a : b;
        }
    }

    public static class FileUtil
    {
        public static bool WriteFile(string data, FileInfo fileInfo)
        {
            bool isSuccess = false;
            try
            {
                using (StreamWriter streamWriter = 
                    File.CreateText(fileInfo.FullName))
                {
                    streamWriter.Write(data);
                    isSuccess = true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace + &quot;\nPress a key ... &quot;);
                Console.ReadKey();
            }
            return isSuccess;
        }
    }
}
</pre></p>
<p>Put both the html and javascript file in the c:\temp folder.<br />
The C# program creates a new draw.js file in the c:\temp folder.</p>
<p>Open the html file with a modern browser that support canvas, like Firefox or Chrome.</p>
<p><strong>canvas.html</strong><br />
<pre class="brush: xml; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Cluster test&lt;/title&gt;
    &lt;script type=&quot;text/javascript&quot;&gt;
        var G = {
            yellow: &quot;#FF0&quot;,
            red: &quot;#F00&quot;,
            green: &quot;#0F0&quot;,
            blue: &quot;#00F&quot;,
            black: &quot;#000&quot;
        }       

        window.onload = function draw() {
            var canvas = document.getElementById('canvas');
            if (canvas.getContext) {
                var ctx = canvas.getContext('2d');
                ctx.strokeStyle = G.black;
                ctx.font = &quot;10pt Arial&quot;;
                drawMarkers(ctx); //use fn defined in draw.js
            }
        }

        function drawMark(cx, cy, color, ctx) {
            //draw a circle
            var radius = 4;
            ctx.beginPath();
            ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.fillStyle = color;
            ctx.fill();
            ctx.stroke();
        }

        function drawCluster(cx, cy, color, n, ctx) {
            //draw a bigger circle
            var radius = 12;
            ctx.beginPath();
            ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.stroke();
            ctx.fillStyle = color;
            ctx.fillText(&quot;&quot; + n, cx-8, cy+2);
        }
    &lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;draw.js&quot;&gt;&lt;/script&gt; 

    &lt;style type=&quot;text/css&quot;&gt;
        body{ margin-left: 10px; margin-top:10px;}
        canvas  {  border: 1px solid red; }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;canvas id=&quot;canvas&quot; width=&quot;410&quot; height=&quot;330&quot;&gt;
        &lt;p&gt;Your browser doesn't support canvas.&lt;/p&gt;
    &lt;/canvas&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre></p>
<p>The javascript file will be overwritten when you run the C# code.<br />
<strong>draw.js</strong><br />
<pre class="brush: jscript; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
function drawMarkers(ctx) {
    drawMark(161.089, 215.198, 'rgb(82,128,16)', ctx);
    drawMark(283.556, 12.712, 'rgb(92,49,66)', ctx);
    drawMark(98.915, 37.67, 'rgb(181,136,150)', ctx);
    drawMark(318.432, 102.905, 'rgb(92,49,66)', ctx);
    drawMark(378.091, 238.713, 'rgb(182,83,25)', ctx);
    drawMark(318.736, 80.199, 'rgb(92,49,66)', ctx);
    drawMark(122.702, 252.349, 'rgb(82,128,16)', ctx);
    drawMark(173.836, 44.669, 'rgb(181,136,150)', ctx);
    drawMark(141.157, 172.273, 'rgb(82,128,16)', ctx);
    drawMark(102.749, 56.737, 'rgb(181,136,150)', ctx);
    drawCluster(306.908, 65.272, 'rgb(92,49,66)', 3, ctx);
    drawCluster(378.091, 238.713, 'rgb(182,83,25)', 1, ctx);
    drawCluster(141.649, 213.273, 'rgb(82,128,16)', 3, ctx);
    drawCluster(125.167, 46.359, 'rgb(181,136,150)', 3, ctx);
    ctx.fillStyle = 'rgb(0,0,0)';
    ctx.fillText('Clusters = ' + 4, 20, 320);
}
</pre></p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/algorithm/'>Algorithm</a>, <a href='http://kunuk.wordpress.com/category/csharp/'>Csharp</a>, <a href='http://kunuk.wordpress.com/category/javascript/'>Javascript</a>, <a href='http://kunuk.wordpress.com/category/visualization/'>Visualization</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/349/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/349/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/349/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/349/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/349/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/349/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/349/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/349/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/349/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/349/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/349/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/349/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/349/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/349/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=349&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2011/09/kmeans_0-7sec10000perr100.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/kmeans_2-2sec10000perr50.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/0-2sec150perr30.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/kmeans_weak.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/gridcluster1.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/gridcluster21.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/gridcluster31.gif" medium="image" />

		<media:content url="http://kunuk.files.wordpress.com/2011/09/grid_1-3sec100000p8x7.gif" medium="image" />
	</item>
		<item>
		<title>Clustering &#8211; Grid cluster with C# and html-canvas example (part 1)</title>
		<link>http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster/</link>
		<comments>http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster/#comments</comments>
		<pubDate>Thu, 15 Sep 2011 21:24:53 +0000</pubDate>
		<dc:creator>kunuk Nykjaer</dc:creator>
				<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Csharp]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Visualization]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[clustering]]></category>
		<category><![CDATA[points overlapping]]></category>

		<guid isPermaLink="false">http://kunuk.wordpress.com/?p=311</guid>
		<description><![CDATA[Grid clustering With merging supported if the centroid of the grid clusters are too close to each other. The second post with updated code can be seen here. http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2 Sometimes when you want to visualize points, there can be to many points and they overlap. To avoid this problem you can use different clustering methods [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=311&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Grid clustering With merging supported if the centroid of the grid clusters are too close to each other.<br />
The second post with updated code can be seen here.<br />
<a href="http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2" title="http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2" target="_blank">http://kunuk.wordpress.com/2011/09/17/clustering-k-means-and-grid-with-c-example-and-html-canvas-part-2</a></p>
<p>Sometimes when you want to visualize points, there can be to many points and they overlap.<br />
To avoid this problem you can use different clustering methods and group points which are close to each. A cluster contains information such as the number of points. The idea is to apply interaction method with a click-event for the cluster where you will zoom in. The view gets expanded and the points stop overlapping.</p>
<p>Here I look at a grid version of clustering. You divide the screen into grid sections and put the points in a grid cluster. Then you apply a centroid calculation to put the location of the cluster point.</p>
<p>In this example I will visualize both the points and the cluster point. Normally you would only visualize the cluster points.<br />
For the visualization I will use html and canvas. The clustering algorithm is implemented in C#. The C# program generates a draw.js file with point and cluster points drawing information.</p>
<p>The running time is something like O(m*n) where m is number of grids and n is number of points.<br />
This clustering visualization works good enough but is not always perfect. Perfect would be always putting points to the closest cluster point.<br />
I will compare this with K-means at a later time.</p>
<p>Here&#8217;s an example of how the points are clustered in grids.<br />
<img src="http://kunuk.files.wordpress.com/2011/09/clustergrid.gif?w=700" alt="" /></p>
<p>The cluster points has a number which tells how many points it contains. Notice the bottom cluster with size 8. It has merged with it&#8217;s neighbor cluster where you can see the points are spread in two grid area.</p>
<p>The code examples will generate an image similar to above when run out of the box.</p>
<p>
Here are the source codes.
</p>
<p><strong>Program.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;

// By Kunuk Nykjaer
class Program
{
    static void Main(string[] args)
    {
        var algo = new Algorithm();
        algo.Run(&quot;gridcluster&quot;);
        algo.GenerateDataString();

        Console.WriteLine(&quot;press a key ...&quot;);
        //Console.ReadKey();
    }
}
</pre></p>
<p><strong>Algorithm.cs</strong><br />
<pre class="brush: csharp; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

// By Kunuk Nykjaer
public class Algorithm
{
    static readonly CultureInfo Culture_enUS = new CultureInfo(&quot;en-US&quot;);
    const string S = &quot;G&quot;;

    readonly List&lt;XY&gt; _points = new List&lt;XY&gt;();
    List&lt;XY&gt; _clusters = new List&lt;XY&gt;();

    public static readonly Random _rand = new Random();
    public const double MinX = 10;
    public const double MinY = 10;
    public const double MaxX = 400;
    public const double MaxY = 300;
    public const int GridX = 4;
    public const int GridY = 3;

    const double AbsSizeX = MaxX - MinX;
    const double AbsSizeY = MaxY - MinY;

    // for bucket placement calc
    const double DeltaX = AbsSizeX / GridX;
    const double DeltaY = AbsSizeY / GridY;

    static private readonly string NL = Environment.NewLine;
    private const string ctx = &quot;ctx&quot;;

    public Algorithm()
    {
        CreateDataSet();
    }

    void CreateDataSet()
    {
        // CREATE DATA SET

        // Create random spread points
        //for (int i = 0; i &lt; 100; i++)
        //{        
        //    var x = MinX + rand.NextDouble() * (MaxX);
        //    var y = MinY + rand.NextDouble() * (MaxY);
        //    _points.Add(new XY(x, y));
        //}

        // Create random region of clusters
        for (int i = 0; i &lt; 10; i++)
        {
            var x = MinX + _rand.NextDouble() * 100;
            var y = MinY + _rand.NextDouble() * 100;
            _points.Add(new XY(x, y));
        }

        for (int i = 0; i &lt; 10; i++)
        {
            var x = MinX + 200 + _rand.NextDouble() * 100;
            var y = MinY + 10 + _rand.NextDouble() * 100;
            _points.Add(new XY(x, y));
        }

        for (int i = 0; i &lt; 10; i++)
        {
            var x = MinX + 50 + _rand.NextDouble() * 100;
            var y = MinY + 150 + _rand.NextDouble() * 100;
            _points.Add(new XY(x, y));
        }
    }

    public void Run(string cluster)
    {
        if (cluster == &quot;gridcluster&quot;)
        {
            GridCluster grid = new GridCluster(_points);
            _clusters = grid.GetCluster();
        }
    }

    public static string GetRandomColor()
    {
        int r = _rand.Next(10, 240);
        int g = _rand.Next(10, 240);
        int b = _rand.Next(10, 240);
        return string.Format(&quot;'rgb({0},{1},{2})'&quot;, r, g, b);
    }

    public static void SetCentroidForAllBuckets(IEnumerable&lt;Bucket&gt; buckets)
    {
        foreach (var item in buckets)
        {
            var bucketPoints = item.Points;
            var cp = GetCentroidFromCluster(bucketPoints);
            item.Centroid = cp;
        }
    }

    public static string GetId(int idx, int idy)
    {
        return idx + &quot;;&quot; + idy;
    }

    static void CreateFile(string s)
    {       
        var path = new FileInfo(@&quot;C:\temp\draw.js&quot;);
        var isCreated = FileUtil.WriteFile(s, path);
        Console.WriteLine(isCreated + &quot; = File is Created&quot;);
    }

    static XY GetCentroidFromCluster(List&lt;XY&gt; list)
    {
        var color = GetRandomColor();

        if (list.Count == 0)
            return null;

        XY centroid = new XY(0, 0);
        foreach (XY p in list)
        {
            p.Color = color;
            centroid.X += p.X;
            centroid.Y += p.Y;
        }

        int count = list.Count();
        centroid.X /= count;
        centroid.Y /= count;

        var cp = new XY(centroid.X, centroid.Y) { Size = count, Color = color };
        return cp;
    }

    public void GenerateDataString()
    {
        var sb = new StringBuilder();

        // markers
        var head = NL + &quot;function drawMarkers(ctx) {&quot; + NL;
        var tail = NL + &quot;}&quot; + NL;

        sb.Append(head);

        // grid
        sb.Append(&quot;ctx.beginPath();&quot; + NL);
        for (int i = 0; i &lt; GridX + 1; i++)
        {
            sb.Append(String.Format(&quot;ctx.moveTo({0}, {1});{2}&quot;, 
                ToStringEN(MinX + i * DeltaX), ToStringEN(MinY), NL));
            sb.Append(String.Format(&quot;ctx.lineTo({0}, {1});{2}&quot;, 
                ToStringEN(MinX + i * DeltaX), ToStringEN(MaxY), NL));
        }
        for (int j = 0; j &lt; GridY + 1; j++)
        {
            sb.Append(String.Format(&quot;ctx.moveTo({0}, {1});{2}&quot;, 
                ToStringEN(MinX), ToStringEN(MinY + j * DeltaY), NL));
            sb.Append(String.Format(&quot;ctx.lineTo({0}, {1});{2}&quot;, 
                ToStringEN(MaxX), ToStringEN(MinY + j * DeltaY), NL));
        }
        sb.Append(&quot;ctx.stroke(); &quot; + NL);


        // markers
        if (_points != null)
            foreach (var p in _points)
                sb.Append(string.Format(&quot;drawMark({0}, {1}, {2}, {3});{4}&quot;, 
                    ToStringEN(p.X), ToStringEN(p.Y), p.Color, ctx, NL));


        string clusterInfo = &quot;0&quot;;
        if (_clusters != null)
        {
            foreach (var c in _clusters)
                sb.Append(string.Format(&quot;drawCluster({0}, {1}, {2}, {3}, {4});{5}&quot;, 
                    ToStringEN(c.X), ToStringEN(c.Y), c.Color,
                                        c.Size, ctx, NL));

            clusterInfo = _clusters.Count + string.Empty;
        }
        // bottom text                


        sb.Append(&quot;ctx.fillStyle = 'rgb(0,0,0)';&quot; + NL);
        sb.Append(string.Format(&quot;ctx.fillText('Clusters = ' + {0}, {1}, {2});{3}&quot;, 
            clusterInfo, ToStringEN(MinX + 10), ToStringEN(MaxY + 16), NL));

        sb.Append(tail);

        CreateFile(sb.ToString());
        //Console.WriteLine(sb.ToString());
    }

    public static string ToStringEN(double d)
    {
        double rounded = Math.Round(d, 3);
         return rounded.ToString(S, Culture_enUS); 
    }

    public class XY
    {
        public double X { get; set; }
        public double Y { get; set; }
        public string Color { get; set; }
        public int Size { get; set; }
        public string XToString { get { return X.ToString(S, Culture_enUS); } }
        public string YToString { get { return Y.ToString(S, Culture_enUS); } }
        public XY(double x, double y)
        {
            X = x;
            Y = y;
            Color = &quot;'rgb(0,0,0)'&quot;;//default
            Size = 1;
        }
    }
    public class Bucket
    {
        public string Id { get; private set; }
        public List&lt;XY&gt; Points { get; private set; }
        public XY Centroid { get; set; }
        public int Idx { get; private set; }
        public int Idy { get; private set; }
        private bool _IsUsed;
        public bool IsUsed
        {
            get { return _IsUsed &amp;&amp; Centroid != null; }
            set { _IsUsed = value; }
        }

        public Bucket(int idx, int idy)
        {
            IsUsed = true;
            Centroid = null;
            Points = new List&lt;XY&gt;();
            Idx = idx;
            Idy = idy;
            Id = GetId(idx, idy);
        }
    }

    public abstract class ClusterAlgorithm
    {
        //id, bucket
        public readonly Dictionary&lt;string, Bucket&gt; bucketsLookup = new Dictionary&lt;string, Bucket&gt;(); 
        public abstract List&lt;XY&gt; GetCluster();
    }
    public class KMeans : ClusterAlgorithm
    {
        //NOT IMPLE YET
        public override List&lt;XY&gt; GetCluster()
        {
            return null;
        }
        /* 
        1) Random k centroids
        2) Cluster data by euclidean distance to centroids
        3) Update centroids by clustered data, 
             http://en.wikipedia.org/wiki/Centroid#Of_a_finite_set_of_points
        4) Update cluster
        5) Continue last two steps until error is small, error is sum of diff 
            between current and updated centroid         
         */
    }

    public class GridCluster : ClusterAlgorithm
    {
        private readonly List&lt;XY&gt; _dataset;
        public GridCluster(List&lt;XY&gt; dataset)
        {
            _dataset = dataset;
        }

        public override List&lt;XY&gt; GetCluster()
        {
            var cluster = GridClusterAlgo(GridX, GridY);
            return cluster;
        }


        void MergeClustersGrid()
        {
            foreach (var key in bucketsLookup.Keys)
            {
                var bucket = bucketsLookup[key];
                if (bucket.IsUsed == false)
                    continue;

                var x = bucket.Idx;
                var y = bucket.Idy;

                // get keys for neighbors
                var N = GetId(x, y + 1);
                var NE = GetId(x + 1, y + 1);
                var E = GetId(x + 1, y);
                var SE = GetId(x + 1, y - 1);
                var S = GetId(x, y - 1);
                var SW = GetId(x - 1, y - 1);
                var W = GetId(x - 1, y);
                var NW = GetId(x - 1, y - 1);
                var neighbors = new[] { N, NE, E, SE, S, SW, W, NW };

                MergeClustersGridHelper(key, neighbors);
            }
        }

        void MergeClustersGridHelper(string currentKey, string[] neighborKeys)
        {
            double minDistX = DeltaX / 2.0;//heuristic
            double minDistY = DeltaY / 2.0;
            //if clusters in grid are too close to each other, merge them
            double minDist = MathTool.Min(minDistX, minDistY); 

            foreach (var neighborKey in neighborKeys)
            {
                if (!bucketsLookup.ContainsKey(neighborKey))
                    continue;

                var current = bucketsLookup[currentKey];
                var neighbor = bucketsLookup[neighborKey];
                if (neighbor.IsUsed == false)
                    continue;

                var dist = MathTool.Distance(current.Centroid, neighbor.Centroid);
                if (dist &gt;= minDist)
                    continue;

                // merge
                var color = current.Centroid.Color;
                foreach (var p in neighbor.Points)
                {
                    //update color
                    p.Color = color;
                }

                current.Points.AddRange(neighbor.Points);

                // recalc centroid
                var cp = GetCentroidFromCluster(current.Points);
                current.Centroid = cp;
                neighbor.IsUsed = false; //merged, then not used anymore
            }
        }


        public List&lt;XY&gt; GridClusterAlgo(int gridx, int gridy)
        {
            var clusterPoints = new List&lt;XY&gt;();
            // params invalid
            if (_dataset == null || _dataset.Count == 0 || gridx &lt;= 0 || gridy &lt;= 0) 
                return clusterPoints;

            // put points in buckets
            foreach (var p in _dataset)
            {
                // find relative val
                var relativeX = p.X - MinX;
                var relativeY = p.Y - MinY;

                int idx = (int)(relativeX / DeltaX);
                int idy = (int)(relativeY / DeltaY);

                // bucket id
                string id = GetId(idx, idy);

                // bucket exists, add point
                if (bucketsLookup.ContainsKey(id))
                    bucketsLookup[id].Points.Add(p);

                // new bucket, create and add point
                else
                {
                    Bucket bucket = new Bucket(idx, idy);
                    bucket.Points.Add(p);
                    bucketsLookup.Add(id, bucket);
                }
            }

            // calc centrod for all buckets
            SetCentroidForAllBuckets(bucketsLookup.Values);

            // merge if gridpoint is to close
            MergeClustersGrid();

            // collect buckets non-empty
            foreach (var item in bucketsLookup)
            {
                var bucket = item.Value;
                if (bucket.IsUsed)
                    clusterPoints.Add(bucket.Centroid);
            }
            return clusterPoints;
        }
    }


    public class MathTool
    {
        private const double Exp = 2; // 2=euclid, 1=manhatten
        //Minkowski dist
        public static double Distance(Algorithm.XY a, Algorithm.XY b) 
        {
            return Math.Pow(Math.Pow(Math.Abs(a.X - b.X), Exp) + 
                Math.Pow(Math.Abs(a.Y - b.Y), Exp), 1.0 / Exp);
        }

        public static double Min(double a, double b)
        {
            return a &lt;= b ? a : b;
        }
    }

    public static class FileUtil
    {
        public static bool WriteFile(string data, FileInfo fileInfo)
        {
            bool isSuccess = false;
            try
            {
                using (StreamWriter streamWriter = File.CreateText(fileInfo.FullName))
                {
                    streamWriter.Write(data);
                    isSuccess = true;
                }
            }
            catch (Exception ex) { Console.WriteLine(ex.StackTrace+&quot;\nPress a key ... &quot;);
                Console.ReadKey(); }

            return isSuccess;
        }
    }
}
</pre></p>
<p>Put both the html and javascript file in the c:\temp folder.<br />
The C# program creates a new draw.js file in the c:\temp folder.</p>
<p>
Open the html file with a modern browser that support canvas, like Firefox or Chrome.<br />
The html file will display the same as the example image above out of the box.
</p>
<p><strong>canvas.html</strong><br />
<pre class="brush: xml; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Cluster test&lt;/title&gt;            
    &lt;script type=&quot;text/javascript&quot;&gt;
        var G = {
            yellow: &quot;#FF0&quot;,
            red: &quot;#F00&quot;,
            green: &quot;#0F0&quot;,
            blue: &quot;#00F&quot;,
            black: &quot;#000&quot;
        }       

        window.onload = function draw() {
            var canvas = document.getElementById('canvas');
            if (canvas.getContext) {
                var ctx = canvas.getContext('2d');
                ctx.strokeStyle = G.black;
                ctx.font = &quot;10pt Arial&quot;;
                drawMarkers(ctx); //use fn defined in draw.js
            }
        }

        function drawMark(cx, cy, color, ctx) {
            //draw a circle
            var radius = 4;
            ctx.beginPath();
            ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.fillStyle = color;
            ctx.fill();
            ctx.stroke();
        }

        function drawCluster(cx, cy, color, n, ctx) {
            //draw a bigger circle            
            var radius = 12;
            ctx.beginPath();
            ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
            ctx.closePath();            
            ctx.stroke();
            ctx.fillStyle = color;
            ctx.fillText(&quot;&quot; + n, cx-8, cy+2);
        }	  
    &lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;draw.js&quot;&gt;&lt;/script&gt; 

    &lt;style type=&quot;text/css&quot;&gt;
        body{ margin-left: 10px; margin-top:10px;}
        canvas  {  border: 1px solid red; }        
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;canvas id=&quot;canvas&quot; width=&quot;410&quot; height=&quot;330&quot;&gt;
        &lt;p&gt;Your browser doesn't support canvas.&lt;/p&gt;
    &lt;/canvas&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre></p>
<p>
The javascript file will be overwritten when you run the C# code.
</p>
<p><strong>draw.js</strong><br />
<pre class="brush: jscript; collapse: true; light: false; pad-line-numbers: false; toolbar: true; wrap-lines: false;">
function drawMarkers(ctx) {
    // By Kunuk Nykjaer
    ctx.beginPath();
    ctx.moveTo(10, 10);
    ctx.lineTo(10, 300);
    ctx.moveTo(107.5, 10);
    ctx.lineTo(107.5, 300);
    ctx.moveTo(205, 10);
    ctx.lineTo(205, 300);
    ctx.moveTo(302.5, 10);
    ctx.lineTo(302.5, 300);
    ctx.moveTo(400, 10);
    ctx.lineTo(400, 300);
    ctx.moveTo(10, 10);
    ctx.lineTo(400, 10);
    ctx.moveTo(10, 106.667);
    ctx.lineTo(400, 106.667);
    ctx.moveTo(10, 203.333);
    ctx.lineTo(400, 203.333);
    ctx.moveTo(10, 300);
    ctx.lineTo(400, 300);
    ctx.stroke();
    drawMark(24.566, 48.809, 'rgb(197,120,199)', ctx);
    drawMark(63.55, 22.318, 'rgb(197,120,199)', ctx);
    drawMark(36.17, 66.005, 'rgb(197,120,199)', ctx);
    drawMark(30.083, 23.226, 'rgb(197,120,199)', ctx);
    drawMark(96.758, 53.378, 'rgb(197,120,199)', ctx);
    drawMark(97.994, 13.421, 'rgb(197,120,199)', ctx);
    drawMark(91.573, 15.839, 'rgb(197,120,199)', ctx);
    drawMark(59.451, 23.006, 'rgb(197,120,199)', ctx);
    drawMark(63.699, 81.094, 'rgb(197,120,199)', ctx);
    drawMark(21.665, 15.711, 'rgb(197,120,199)', ctx);
    drawMark(298.417, 77.83, 'rgb(11,24,61)', ctx);
    drawMark(291.38, 86.759, 'rgb(11,24,61)', ctx);
    drawMark(281.345, 95.517, 'rgb(11,24,61)', ctx);
    drawMark(284.307, 92.13, 'rgb(11,24,61)', ctx);
    drawMark(240.746, 106.23, 'rgb(11,24,61)', ctx);
    drawMark(247.004, 85.546, 'rgb(11,24,61)', ctx);
    drawMark(291.715, 33.158, 'rgb(11,24,61)', ctx);
    drawMark(216.012, 33.45, 'rgb(11,24,61)', ctx);
    drawMark(260.559, 46.371, 'rgb(11,24,61)', ctx);
    drawMark(239.061, 103.01, 'rgb(11,24,61)', ctx);
    drawMark(67.791, 210.702, 'rgb(111,163,157)', ctx);
    drawMark(146.852, 211.327, 'rgb(111,163,157)', ctx);
    drawMark(82.629, 164.594, 'rgb(106,75,235)', ctx);
    drawMark(71.372, 244.351, 'rgb(111,163,157)', ctx);
    drawMark(110.755, 255.688, 'rgb(111,163,157)', ctx);
    drawMark(144.035, 185.499, 'rgb(96,173,79)', ctx);
    drawMark(116.13, 217.092, 'rgb(111,163,157)', ctx);
    drawMark(112.972, 216.736, 'rgb(111,163,157)', ctx);
    drawMark(117.428, 246.791, 'rgb(111,163,157)', ctx);
    drawMark(100.972, 210.653, 'rgb(111,163,157)', ctx);
    drawCluster(58.551, 36.281, 'rgb(197,120,199)', 10, ctx);
    drawCluster(265.055, 76, 'rgb(11,24,61)', 10, ctx);
    drawCluster(105.534, 226.668, 'rgb(111,163,157)', 8, ctx);
    drawCluster(82.629, 164.594, 'rgb(106,75,235)', 1, ctx);
    drawCluster(144.035, 185.499, 'rgb(96,173,79)', 1, ctx);
    ctx.fillStyle = 'rgb(0,0,0)';
    ctx.fillText('Clusters = ' + 5, 20, 316);
}

</pre></p>
<br />Filed under: <a href='http://kunuk.wordpress.com/category/algorithm/'>Algorithm</a>, <a href='http://kunuk.wordpress.com/category/csharp/'>Csharp</a>, <a href='http://kunuk.wordpress.com/category/javascript/'>Javascript</a>, <a href='http://kunuk.wordpress.com/category/visualization/'>Visualization</a>  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/kunuk.wordpress.com/311/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/kunuk.wordpress.com/311/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/kunuk.wordpress.com/311/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/kunuk.wordpress.com/311/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/kunuk.wordpress.com/311/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/kunuk.wordpress.com/311/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/kunuk.wordpress.com/311/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/kunuk.wordpress.com/311/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/kunuk.wordpress.com/311/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/kunuk.wordpress.com/311/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/kunuk.wordpress.com/311/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/kunuk.wordpress.com/311/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/kunuk.wordpress.com/311/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/kunuk.wordpress.com/311/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=kunuk.wordpress.com&amp;blog=8976625&amp;post=311&amp;subd=kunuk&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://kunuk.wordpress.com/2011/09/15/clustering-grid-cluster/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/591d5030a5d877ad29dd37673355bee0?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&#38;r=G" medium="image">
			<media:title type="html">kunuk</media:title>
		</media:content>

		<media:content url="http://kunuk.files.wordpress.com/2011/09/clustergrid.gif" medium="image" />
	</item>
	</channel>
</rss>
