Fix image URLs to relative paths, add code block fencing
This commit is contained in:
@@ -6,16 +6,19 @@ published: true
|
|||||||
---
|
---
|
||||||
|
|
||||||
Hi, I’m James Kolpack, and I’m a code-aholic.
|
Hi, I’m James Kolpack, and I’m a code-aholic.
|
||||||
|
|
||||||
#### Where I’m from
|
#### Where I’m from
|
||||||
|
|
||||||
|
|
||||||
Ever since I was a kid I’ve been programming at various degrees of sophistication. Being a child of the 80’s, I started with Basic (of the [GW variety](http://en.wikipedia.org/wiki/GW-BASIC) on a [8086 based IBM](http://en.wikipedia.org/wiki/IBM_Personal_System/2)) with a dash of [Logo](http://en.wikipedia.org/wiki/Logo_%28programming_language%29) and [batch scripting](http://en.wikipedia.org/wiki/Batch_file). Later in college I became learned in [Pascal](http://en.wikipedia.org/wiki/Pascal) and then [C](http://en.wikipedia.org/wiki/C_%28programming_language%29) and the trials and tribulations of [memory management](http://en.wikipedia.org/wiki/Malloc) and [complex data structures](http://en.wikipedia.org/wiki/Tree_%28data_structure%29). I loved the ability to solve interesting problems by the clever assembly of instructions to be executed on a computer. It was a bit of a power trip, really - “Pow, I just made a program that can find the optimal combination of items to fill a [knapsack in psuedo-polynomial time](http://en.wikipedia.org/wiki/Knapsack_problem#Dynamic_programming_solution).” This capability does not, I discovered, transcend one-to-one to “real-world” power – but you’ve got to [](http://popcyclical.com/content/binary/WindowsLiveWriter/Introducing_90B9/LegoMan_bigger_2.png)do what you can with what you’ve got.
|
Ever since I was a kid I’ve been programming at various degrees of sophistication. Being a child of the 80’s, I started with Basic (of the [GW variety](http://en.wikipedia.org/wiki/GW-BASIC) on a [8086 based IBM](http://en.wikipedia.org/wiki/IBM_Personal_System/2)) with a dash of [Logo](http://en.wikipedia.org/wiki/Logo_%28programming_language%29) and [batch scripting](http://en.wikipedia.org/wiki/Batch_file). Later in college I became learned in [Pascal](http://en.wikipedia.org/wiki/Pascal) and then [C](http://en.wikipedia.org/wiki/C_%28programming_language%29) and the trials and tribulations of [memory management](http://en.wikipedia.org/wiki/Malloc) and [complex data structures](http://en.wikipedia.org/wiki/Tree_%28data_structure%29). I loved the ability to solve interesting problems by the clever assembly of instructions to be executed on a computer. It was a bit of a power trip, really - “Pow, I just made a program that can find the optimal combination of items to fill a [knapsack in psuedo-polynomial time](http://en.wikipedia.org/wiki/Knapsack_problem#Dynamic_programming_solution).” This capability does not, I discovered, transcend one-to-one to “real-world” power – but you’ve got to [](../media/WindowsLiveWriter/Introducing_90B9/LegoMan_bigger_2.png)do what you can with what you’ve got.
|
||||||
|
|
||||||
As for the name of the blog, “popcyclical”, it’s a dual homage to my interest in cyclical relationships and, of course, popsicles. It also references my moniker “poprhythm” which I was given many years ago for my ever-loving devotion to excellent music.
|
As for the name of the blog, “popcyclical”, it’s a dual homage to my interest in cyclical relationships and, of course, popsicles. It also references my moniker “poprhythm” which I was given many years ago for my ever-loving devotion to excellent music.
|
||||||
|
|
||||||
#### Where I’m going
|
#### Where I’m going
|
||||||
|
|
||||||
|
|
||||||
On this blog I will be exploring ideas and issues that are relevant to me at the given time. As a developer employed at a small shop, [](http://popcyclical.com/content/binary/WindowsLiveWriter/Introducing_90B9/100px-Directed_graph_with_back_edge.svg_4.png)the focus is guaranteed to wander over time. For any given week, I may be highly invested in any number of topics. This may include : [upcoming features](http://msdn.microsoft.com/en-us/library/dd264736%28VS.100%29.aspx) of programming languages, [graph algorithm design and application](http://www.codeplex.com/quickgraph/), [development tooling](http://www.jetbrains.com/resharper/), [data mining](http://en.wikipedia.org/wiki/Data_mining), [system architecture](http://martinfowler.com/books.html), [user experience](http://mitpress.mit.edu/catalog/item/default.asp?tid=5393&ttype=2), [natural language processing](http://opennlp.sourceforge.net/), [semantic web](http://en.wikipedia.org/wiki/Semantic_Web), [test driven development](http://en.wikipedia.org/wiki/Test-driven_development), [statistics](http://www.r-project.org/), [data management](http://en.wikipedia.org/wiki/Database), … these and many others are all fair game. As of today, the technologies I’m working in from day to day are [C#](http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx) on the [.NET](http://www.microsoft.com/NET/) framework, [ASP.NET MVC](http://www.asp.net/%28S%28d35rmemuuono1wvm1gsp2n45%29%29/mvc/) and [jQuery](http://jquery.com/), [WCF](http://msdn.microsoft.com/en-us/netframework/aa663324.aspx), and a [variety](http://www.microsoft.com/sqlserver/2008/en/us/default.aspx) of [database](http://www.oracle.com/database/berkeley-db/xml/index.html) [engines](http://www.mysql.com/).
|
On this blog I will be exploring ideas and issues that are relevant to me at the given time. As a developer employed at a small shop, [](../media/WindowsLiveWriter/Introducing_90B9/100px-Directed_graph_with_back_edge.svg_4.png)the focus is guaranteed to wander over time. For any given week, I may be highly invested in any number of topics. This may include : [upcoming features](http://msdn.microsoft.com/en-us/library/dd264736%28VS.100%29.aspx) of programming languages, [graph algorithm design and application](http://www.codeplex.com/quickgraph/), [development tooling](http://www.jetbrains.com/resharper/), [data mining](http://en.wikipedia.org/wiki/Data_mining), [system architecture](http://martinfowler.com/books.html), [user experience](http://mitpress.mit.edu/catalog/item/default.asp?tid=5393&ttype=2), [natural language processing](http://opennlp.sourceforge.net/), [semantic web](http://en.wikipedia.org/wiki/Semantic_Web), [test driven development](http://en.wikipedia.org/wiki/Test-driven_development), [statistics](http://www.r-project.org/), [data management](http://en.wikipedia.org/wiki/Database), … these and many others are all fair game. As of today, the technologies I’m working in from day to day are [C#](http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx) on the [.NET](http://www.microsoft.com/NET/) framework, [ASP.NET MVC](http://www.asp.net/%28S%28d35rmemuuono1wvm1gsp2n45%29%29/mvc/) and [jQuery](http://jquery.com/), [WCF](http://msdn.microsoft.com/en-us/netframework/aa663324.aspx), and a [variety](http://www.microsoft.com/sqlserver/2008/en/us/default.aspx) of [database](http://www.oracle.com/database/berkeley-db/xml/index.html) [engines](http://www.mysql.com/).
|
||||||
|
|
||||||
#### I can use your help
|
#### I can use your help
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ Interacting with your computer using a mouse-driven GUI makes many tasks quick a
|
|||||||
|
|
||||||
**It’s a matter of keeping up with your thoughts**. Suppose some series of tasks takes 20 seconds with a mouse but you can accomplish the same tasks in 12 seconds using the keyboard. The big deal isn’t that you’ve saved 8 seconds; the big deal is that you’re more likely to finish your tasks before you lose the thought that motivated them.
|
**It’s a matter of keeping up with your thoughts**. Suppose some series of tasks takes 20 seconds with a mouse but you can accomplish the same tasks in 12 seconds using the keyboard. The big deal isn’t that you’ve saved 8 seconds; the big deal is that you’re more likely to finish your tasks before you lose the thought that motivated them.
|
||||||
|
|
||||||
I interact with the[](http://popcyclical.com/content/binary/images/ConsoleLauncherHotKeyTilda_66F3/tilda_console.png) file system on a very regular basis. I haven’t measured this, but I bet I’ll open Windows Explorer (or [xplorer²](http://www.zabkat.com/)) at least two or three times for every hour I’m at the computer. I’m forever poking around for documents and restructuring directories so that they’ll be easier to find next time. Many times I’ll want to execute a command line statement in the path that I’m browsing. There are [several](http://www.burgaud.com/open-command-window-here/) [different](http://www.microsoft.com/windowsxp/downloads/powertoys/xppowertoys.mspx) [strategies](http://code.kliu.org/cmdopen/) for getting a “Open Command Prompt Here” to the explorer context menu, but they all require a right-click (or [simulating one with the keyboard](http://www.ehow.com/how_8219_click-using-keyboard.html)). There’s an easier way.
|
I interact with the[](../media/images/ConsoleLauncherHotKeyTilda_66F3/tilda_console.png) file system on a very regular basis. I haven’t measured this, but I bet I’ll open Windows Explorer (or [xplorer²](http://www.zabkat.com/)) at least two or three times for every hour I’m at the computer. I’m forever poking around for documents and restructuring directories so that they’ll be easier to find next time. Many times I’ll want to execute a command line statement in the path that I’m browsing. There are [several](http://www.burgaud.com/open-command-window-here/) [different](http://www.microsoft.com/windowsxp/downloads/powertoys/xppowertoys.mspx) [strategies](http://code.kliu.org/cmdopen/) for getting a “Open Command Prompt Here” to the explorer context menu, but they all require a right-click (or [simulating one with the keyboard](http://www.ehow.com/how_8219_click-using-keyboard.html)). There’s an easier way.
|
||||||
|
|
||||||
A couple years ago I read an article on [instructables](http://www.instructables.com) about a [“Drop Down”, Quake-style command prompt for Windows](http://www.instructables.com/id/%22Drop-Down%22%2c-Quake-style-command-prompt-for-Window/). The project uses [AutoHotKey](http://www.autohotkey.com/) to launch and hide a console window using a keyboard shortcut. Neat! It works great, except that it always dumps you in the your %HOMEPATH%. I’ve taken the script and upgraded it to navigate directly to the currently open path in Windows Explorer – it’s name is **Tilda**.
|
A couple years ago I read an article on [instructables](http://www.instructables.com) about a [“Drop Down”, Quake-style command prompt for Windows](http://www.instructables.com/id/%22Drop-Down%22%2c-Quake-style-command-prompt-for-Window/). The project uses [AutoHotKey](http://www.autohotkey.com/) to launch and hide a console window using a keyboard shortcut. Neat! It works great, except that it always dumps you in the your %HOMEPATH%. I’ve taken the script and upgraded it to navigate directly to the currently open path in Windows Explorer – it’s name is **Tilda**.
|
||||||
|
|
||||||
For example – I’ve got an explorer window open in C:\console\UnxUtils\usr\local\wbin and I want to string some of those juicy unix command line utilities together. I can now simply do **Win**+**~**, and I’ve got a new console instance in the right place. As with the original, it will minimize/maximize the console window on subsequent usage of the key combination.
|
For example – I’ve got an explorer window open in `C:\console\UnxUtils\usr\local\wbin` and I want to string some of those juicy unix command line utilities together. I can now simply do **Win**+**~**, and I’ve got a new console instance in the right place. As with the original, it will minimize/maximize the console window on subsequent usage of the key combination.
|
||||||
|
|
||||||
I have this nasty habit of navigating around the file system when I’m using Windows Explorer. After running some unix commands, I might have a hankering to run MSBuild.exe (ok, probably not, but who knows?). I could tediously type the path, or, I could have Tilda automatically enter it for me. From the new path in Windows Explorer, I type a combination **Win**+**Shift**+**~** and the chdir command gets sent to the current console window.[](http://popcyclical.com/content/binary/images/ConsoleLauncherHotKeyTilda_66F3/tilda_chdir.png)
|
I have this nasty habit of navigating around the file system when I’m using Windows Explorer. After running some unix commands, I might have a hankering to run MSBuild.exe (ok, probably not, but who knows?). I could tediously type the path, or, I could have Tilda automatically enter it for me. From the new path in Windows Explorer, I type a combination **Win**+**Shift**+**~** and the `chdir` command gets sent to the current console window.[](../media/images/ConsoleLauncherHotKeyTilda_66F3/tilda_chdir.png)
|
||||||
|
|
||||||
Please note that I’m using (and recommend) [Console](http://sourceforge.net/projects/console/) as a command line window host. **Tilda** is currently set to use this, but the script can be easily modified to use cmd.exe or whichever console window host you prefer.
|
Please note that I’m using (and recommend) [Console](http://sourceforge.net/projects/console/) as a command line window host. **Tilda** is currently set to use this, but the script can be easily modified to use `cmd.exe` or whichever console window host you prefer.
|
||||||
|
|
||||||
**Tilda **- open and close a console window using a hot key. Automatically navigates to the currently open explorer window. Save some trips to the mouse and make getting to the command line easier.
|
**Tilda **- open and close a console window using a hot key. Automatically navigates to the currently open explorer window. Save some trips to the mouse and make getting to the command line easier.
|
||||||
|
|
||||||
|
|||||||
@@ -7,24 +7,35 @@ published: true
|
|||||||
|
|
||||||
I was recently tasked to add server-and-client-side form validation for an ASP.NET MVC site - which already uses in-place editing courtesy [Jeditable](http://www.appelsiini.net/projects/jeditable). I really like the field editing experience that Jeditable provides – it makes form entry in the browser interactive, is fairly straightforward to integrate, and it’s adaptable to many scenarios. It does not, however, have any validation mechanism built in.
|
I was recently tasked to add server-and-client-side form validation for an ASP.NET MVC site - which already uses in-place editing courtesy [Jeditable](http://www.appelsiini.net/projects/jeditable). I really like the field editing experience that Jeditable provides – it makes form entry in the browser interactive, is fairly straightforward to integrate, and it’s adaptable to many scenarios. It does not, however, have any validation mechanism built in.
|
||||||
|
|
||||||
Our project already used [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) for a few forms by using the HTML class definitions – like adding class=”required phone” to an INPUT element. This works great, but doesn’t provide any server-side validation tie-in.
|
Our project already used [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) for a few forms by using the HTML class definitions – like adding `class=”required phone”` to an `INPUT` element. This works great, but doesn’t provide any server-side validation tie-in.
|
||||||
|
|
||||||
Earlier this year, I remembered having seen a [presentation](http://speakerrate.com/talks/1218-useful-jquery-tips-tricks-and-plugins-with-asp-net-mvc) by [Elijah Manor](http://elijahmanor.com/) who mentioned using [xVal](http://xval.codeplex.com/) for robust server and client side validation. And with the news that [MVC 2 will have a built in validation technique similar to xVal](http://haacked.com/archive/2009/10/01/asp.net-mvc-preview-2-released.aspx), it was an easy decision to start investigating this library.
|
Earlier this year, I remembered having seen a [presentation](http://speakerrate.com/talks/1218-useful-jquery-tips-tricks-and-plugins-with-asp-net-mvc) by [Elijah Manor](http://elijahmanor.com/) who mentioned using [xVal](http://xval.codeplex.com/) for robust server and client side validation. And with the news that [MVC 2 will have a built in validation technique similar to xVal](http://haacked.com/archive/2009/10/01/asp.net-mvc-preview-2-released.aspx), it was an easy decision to start investigating this library.
|
||||||
|
|
||||||
[xVal](http://xval.codeplex.com/) is a pretty easy to get integrated. The first step is to decorate the model with validation rules – I’ve decided to use .NET framework’s [DataAnnotations](http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx), which ends up looking like:public class ValidateViewModel
|
[xVal](http://xval.codeplex.com/) is a pretty easy to get integrated. The first step is to decorate the model with validation rules – I’ve decided to use .NET framework’s [DataAnnotations](http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx), which ends up looking like:
|
||||||
|
|
||||||
|
```
|
||||||
|
public class ValidateViewModel
|
||||||
{
|
{
|
||||||
[Required(ErrorMessage = "This string is required")]
|
[Required(ErrorMessage = "This string is required")]
|
||||||
public string StringRequired { get; set; }
|
public string StringRequired { get; set; }
|
||||||
|
|
||||||
[Range(13, 100, ErrorMessage = "Must be between 13 and 100")]
|
[Range(13, 100, ErrorMessage = "Must be between 13 and 100")]
|
||||||
public double DoubleRange13_100 { get; set; }
|
public double DoubleRange13_100 { get; set; }
|
||||||
}public class ValidatedController
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
public class ValidatedController
|
||||||
{
|
{
|
||||||
public ViewResult Index()
|
public ViewResult Index()
|
||||||
{
|
{
|
||||||
return View(new ValidateViewModel());
|
return View(new ValidateViewModel());
|
||||||
}
|
}
|
||||||
}<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
|
||||||
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
|
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
|
||||||
<% using(Html.BeginForm()) {%>
|
<% using(Html.BeginForm()) {%>
|
||||||
<%= Html.ValidationSummary() %>
|
<%= Html.ValidationSummary() %>
|
||||||
@@ -38,7 +49,12 @@ Earlier this year, I remembered having seen a [presentation](http://speakerrate.
|
|||||||
<%} %>
|
<%} %>
|
||||||
</asp:Content>
|
</asp:Content>
|
||||||
|
|
||||||
To validate this ViewModel server side, we use a [DataAnnotationsValidationRunner](http://blog.codeville.net/2009/01/10/xval-a-validation-framework-for-aspnet-mvc/) like the one in xVal’s documentation:[AcceptVerbs(HttpVerbs.Post)]
|
```
|
||||||
|
|
||||||
|
To validate this ViewModel server side, we use a [DataAnnotationsValidationRunner](http://blog.codeville.net/2009/01/10/xval-a-validation-framework-for-aspnet-mvc/) like the one in xVal’s documentation:
|
||||||
|
|
||||||
|
```
|
||||||
|
[AcceptVerbs(HttpVerbs.Post)]
|
||||||
public ActionResult Update(ValidateViewModel model)
|
public ActionResult Update(ValidateViewModel model)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -54,7 +70,11 @@ public ActionResult Update(ValidateViewModel model)
|
|||||||
|
|
||||||
return ModelState.IsValid ? RedirectToAction("Completed")
|
return ModelState.IsValid ? RedirectToAction("Completed")
|
||||||
: (ActionResult) View();
|
: (ActionResult) View();
|
||||||
}public static class DataAnnotationsValidationRunner
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
public static class DataAnnotationsValidationRunner
|
||||||
{
|
{
|
||||||
private static IEnumerable<ErrorInfo> GetErrors(object instance)
|
private static IEnumerable<ErrorInfo> GetErrors(object instance)
|
||||||
{
|
{
|
||||||
@@ -74,9 +94,16 @@ public ActionResult Update(ValidateViewModel model)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
*Aside: the project uses the *[*Active record pattern*](http://en.wikipedia.org/wiki/Active_record_pattern)* via *[*Castle ActiveRecord*](http://www.castleproject.org/activerecord/index.html)* for data access without an intermediate business-logic layer. For this case, the rules are defined on the ViewModel and are validated in the controller. This does add some noise in the actions – I’m definitely interested in other methods for handling this. Such as - perhaps the validation could be placed inside the ViewModel?*
|
*Aside: the project uses the *[*Active record pattern*](http://en.wikipedia.org/wiki/Active_record_pattern)* via *[*Castle ActiveRecord*](http://www.castleproject.org/activerecord/index.html)* for data access without an intermediate business-logic layer. For this case, the rules are defined on the ViewModel and are validated in the controller. This does add some noise in the actions – I’m definitely interested in other methods for handling this. Such as - perhaps the validation could be placed inside the ViewModel?*
|
||||||
|
|
||||||
The next step is to add client-side validation. [xVal](http://xval.codeplex.com/)’s built-in [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) rule generator makes this ridiculously simple – just reference jquery.validate.js and xVal.jquery.validate.js in the view, and then this single line:<%= Html.ClientSideValidation<ValidateViewModel>() %>
|
The next step is to add client-side validation. [xVal](http://xval.codeplex.com/)’s built-in [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) rule generator makes this ridiculously simple – just reference `jquery.validate.js` and `xVal.jquery.validate.js` in the view, and then this single line:
|
||||||
|
|
||||||
|
```
|
||||||
|
<%= Html.ClientSideValidation<ValidateViewModel>() %>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
The rules defined in the ViewModel will now be validated client side and enforced for server side actions. This works great for a statically defined HTML form, but I learned that integrating with [Jeditable](http://www.appelsiini.net/projects/jeditable)’s dynamic inline forms to be not so straight forward.
|
The rules defined in the ViewModel will now be validated client side and enforced for server side actions. This works great for a statically defined HTML form, but I learned that integrating with [Jeditable](http://www.appelsiini.net/projects/jeditable)’s dynamic inline forms to be not so straight forward.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ slug: jquery-validate-and-jeditable-part-2
|
|||||||
published: true
|
published: true
|
||||||
---
|
---
|
||||||
|
|
||||||
When using [Jeditable](http://www.appelsiini.net/projects/jeditable), there is no form element to bind [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) rules with. Instead, when an editable element is clicked or activated, it dynamically creates a new form and input element and destroys them after the user is done editing. For the ViewModel from [Part 1](/2010/01/01/jQueryValidateAndJeditablePart1.aspx), the View might be rendered like so for [Jeditable](http://www.appelsiini.net/projects/jeditable):<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
|
When using [Jeditable](http://www.appelsiini.net/projects/jeditable), there is no form element to bind [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) rules with. Instead, when an editable element is clicked or activated, it dynamically creates a new form and input element and destroys them after the user is done editing. For the ViewModel from [Part 1](/2010/01/01/jQueryValidateAndJeditablePart1.aspx), the View might be rendered like so for [Jeditable](http://www.appelsiini.net/projects/jeditable):
|
||||||
|
|
||||||
|
```
|
||||||
|
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
|
||||||
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
|
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
|
||||||
<% using(Html.BeginForm()) {%>
|
<% using(Html.BeginForm()) {%>
|
||||||
<%= Html.ValidationSummary() %>
|
<%= Html.ValidationSummary() %>
|
||||||
@@ -20,8 +23,12 @@ When using [Jeditable](http://www.appelsiini.net/projects/jeditable), there is n
|
|||||||
</div>
|
</div>
|
||||||
<%} %>
|
<%} %>
|
||||||
</asp:Content>
|
</asp:Content>
|
||||||
|
```
|
||||||
|
|
||||||
[xVal](http://xval.codeplex.com/)’s ClientSideValidation<TViewModel>() used in [Part 1](/2010/01/01/jQueryValidateAndJeditablePart1.aspx) won’t work to validate this. The reason? It generates a script that binds validation directly to the form elements on page load. The rendered script looks for the ViewModel looks like:<script type="text/javascript">
|
[xVal](http://xval.codeplex.com/)’s `ClientSideValidation<TViewModel>()` used in [Part 1](/2010/01/01/jQueryValidateAndJeditablePart1.aspx) won’t work to validate this. The reason? It generates a script that binds validation directly to the form elements on page load. The rendered script looks for the ViewModel looks like:
|
||||||
|
|
||||||
|
```
|
||||||
|
<script type="text/javascript">
|
||||||
xVal.AttachValidator(null,
|
xVal.AttachValidator(null,
|
||||||
{"Fields":[
|
{"Fields":[
|
||||||
{
|
{
|
||||||
@@ -52,8 +59,12 @@ xVal.AttachValidator(null,
|
|||||||
}
|
}
|
||||||
]}, {})
|
]}, {})
|
||||||
</script>
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
The rules are in the xVal’s StandardJSON format and the AttachValidator function (in xVal.jquery.validate.js) scans the DOM and attaches [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) rules as attributes to the matched input elements. Since [Jeditable](http://www.appelsiini.net/projects/jeditable) doesn’t create these elements until they’re actively being edited, the rules have nothing to attach to since they don’t exist yet. Fortunately, [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) provides several strategies for defining the rules. In addition to being able to attach attributes to the input elements, the rules can be placed in a separate data structure. [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) refers to these as “static rules”. Instead of attaching the xVal rule set directly to the elements, it can be adapted to the static rule set that [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) can use directly. The structure for the ViewModel rules will look like: {
|
The rules are in the xVal’s StandardJSON format and the `AttachValidator` function (in `xVal.jquery.validate.js`) scans the DOM and attaches [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) rules as attributes to the matched input elements. Since [Jeditable](http://www.appelsiini.net/projects/jeditable) doesn’t create these elements until they’re actively being edited, the rules have nothing to attach to since they don’t exist yet. Fortunately, [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) provides several strategies for defining the rules. In addition to being able to attach attributes to the input elements, the rules can be placed in a separate data structure. [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) refers to these as “static rules”. Instead of attaching the xVal rule set directly to the elements, it can be adapted to the static rule set that [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) can use directly. The structure for the ViewModel rules will look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
rules: {
|
rules: {
|
||||||
StringRequired: {
|
StringRequired: {
|
||||||
required: true
|
required: true
|
||||||
@@ -73,16 +84,26 @@ The rules are in the xVal’s StandardJSON format and the AttachValidator functi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
I've adapted some javascript to do this conversion - it's available [here](http://popcyclical.com/content/binary/images/jQueryValidateandJeditable_E71E/xValRuleAdapter.js). To get the ViewModel’s rules into this format for javascript consumption, this line is added:<script type="text/javascript">
|
```
|
||||||
|
|
||||||
|
I've adapted some javascript to do this conversion - it's available [here](../media/images/jQueryValidateandJeditable_E71E/xValRuleAdapter.js). To get the ViewModel’s rules into this format for javascript consumption, this line is added:
|
||||||
|
|
||||||
|
```
|
||||||
|
<script type="text/javascript">
|
||||||
var validateOptions
|
var validateOptions
|
||||||
= convertXvalToValidateOptions(
|
= convertXvalToValidateOptions(
|
||||||
<%= Html.ClientSideValidationRules<ValidateViewModel>()%>
|
<%= Html.ClientSideValidationRules<ValidateViewModel>()%>
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
To get these attached to form elements as soon as the user activates them, [Jeditable](http://www.appelsiini.net/projects/jeditable)’s “plugin” feature is utilized:$(function() { // <- on document ready
|
To get these attached to form elements as soon as the user activates them, [Jeditable](http://www.appelsiini.net/projects/jeditable)’s “plugin” feature is utilized:
|
||||||
|
|
||||||
|
```
|
||||||
|
$(function() { // <- on document ready
|
||||||
// register plugin with Jeditable to tie in jQuery Validate
|
// register plugin with Jeditable to tie in jQuery Validate
|
||||||
$.editable.types['text'].plugin = bindValidate;
|
$.editable.types['text'].plugin = bindValidate;
|
||||||
|
|
||||||
@@ -122,18 +143,20 @@ function jeditableValidate(settings, self) {
|
|||||||
return $('form', self).valid();
|
return $('form', self).valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
With this glue in place, the form elements will now be validated with the rules defined in the ViewModel. All fields valid:
|
With this glue in place, the form elements will now be validated with the rules defined in the ViewModel. All fields valid:
|
||||||
|
|
||||||
[](http://popcyclical.com/content/binary/images/jQueryValidateandJeditable_E71E/jQueryValidateJeditable1.png)
|
[](../media/images/jQueryValidateandJeditable_E71E/jQueryValidateJeditable1.png)
|
||||||
|
|
||||||
…and here after both have invalid values:
|
…and here after both have invalid values:
|
||||||
|
|
||||||
[](http://popcyclical.com/content/binary/images/jQueryValidateandJeditable_E71E/jQueryValidateJeditable2.png)
|
[](../media/images/jQueryValidateandJeditable_E71E/jQueryValidateJeditable2.png)
|
||||||
|
|
||||||
A few notes:
|
A few notes:
|
||||||
|
|
||||||
- Any additional options to be sent to [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) can be attached to the validateOptions object. I’ve used this to place all error messages into a separate errorLabelContainer (like [here](http://stackoverflow.com/questions/61456/mvc-net-jquery-validation)).
|
- Any additional options to be sent to [jQuery Validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) can be attached to the `validateOptions` object. I’ve used this to place all error messages into a separate `errorLabelContainer` (like [here](http://stackoverflow.com/questions/61456/mvc-net-jquery-validation)).
|
||||||
|
|
||||||
- I feel that AttachValidator function in xVal.jquery.validate.js from could become more loosely coupled by separating the rule conversion from the DOM element attachment.
|
- I feel that AttachValidator function in `xVal.jquery.validate.js` from could become more loosely coupled by separating the rule conversion from the DOM element attachment.
|
||||||
|
|
||||||
I think both of these jQuery libraries provide a great benefit when creating interactive and helpful forms. Kudos to [Jörn Zaefferer](http://bassistance.de/) and [Mika Tuupola](http://www.appelsiini.net/) for the good work. xVal is likewise an excellent library – thanks to [Steve Sanderson](http://www.codeplex.com/site/users/view/SteveSanderson).
|
I think both of these jQuery libraries provide a great benefit when creating interactive and helpful forms. Kudos to [Jörn Zaefferer](http://bassistance.de/) and [Mika Tuupola](http://www.appelsiini.net/) for the good work. xVal is likewise an excellent library – thanks to [Steve Sanderson](http://www.codeplex.com/site/users/view/SteveSanderson).
|
||||||
@@ -10,30 +10,45 @@ From [ASP.NET MVC in Action](http://www.manning.com/palermo/) section 4.4.1:
|
|||||||
|
|
||||||
*Views are difficult to unit test, so we want to keep them as thin as possible. … Notice in [the View Model] that all of the properties are strings. We’ll have the [properties] properly formatted before this view model object is placed in view data. This way, the view need not consider the object, and it can format the information properly.*
|
*Views are difficult to unit test, so we want to keep them as thin as possible. … Notice in [the View Model] that all of the properties are strings. We’ll have the [properties] properly formatted before this view model object is placed in view data. This way, the view need not consider the object, and it can format the information properly.*
|
||||||
|
|
||||||
To facilitate the formatting between the Domain Model and the View Model, a few of [AutoMapper](http://www.codeplex.com/AutoMapper)’s features may be utilized. Here’s a DomainModel containing a CurrencyProperty which will needed to formatted for human consumption:public class DomainModel
|
To facilitate the formatting between the Domain Model and the View Model, a few of [AutoMapper](http://www.codeplex.com/AutoMapper)’s features may be utilized. Here’s a `DomainModel` containing a `CurrencyProperty` which will needed to formatted for human consumption:
|
||||||
|
|
||||||
|
```
|
||||||
|
public class DomainModel
|
||||||
{
|
{
|
||||||
public decimal CurrencyProperty { get; set; }
|
public decimal CurrencyProperty { get; set; }
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Now, here is a ViewModel which will be used to transport the formatted value to the View:public class ViewModel
|
Now, here is a `ViewModel` which will be used to transport the formatted value to the View:
|
||||||
|
|
||||||
|
```
|
||||||
|
public class ViewModel
|
||||||
{
|
{
|
||||||
///<summary>Currency Property - formatted as $#,###.##</summary>
|
///<summary>Currency Property - formatted as $#,###.##</summary>
|
||||||
public string CurrencyProperty { get; set; }
|
public string CurrencyProperty { get; set; }
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
####
|
####
|
||||||
|
|
||||||
#### Mapping from Domain Model to View Model
|
#### Mapping from Domain Model to View Model
|
||||||
|
|
||||||
[AutoMapper](http://www.codeplex.com/AutoMapper) provides an easy way to create a mapping between two object types. For particular tweaks for individual property mappings, the ForMember method can be used like:///<summary>Setup mapping between domain and view model</summary>
|
[AutoMapper](http://www.codeplex.com/AutoMapper) provides an easy way to create a mapping between two object types. For particular tweaks for individual property mappings, the `ForMember` method can be used like:
|
||||||
|
|
||||||
|
```
|
||||||
|
///<summary>Setup mapping between domain and view model</summary>
|
||||||
static ViewModel()
|
static ViewModel()
|
||||||
{
|
{
|
||||||
// map dm to vm
|
// map dm to vm
|
||||||
Mapper.CreateMap<DomainModel, ViewModel>()
|
Mapper.CreateMap<DomainModel, ViewModel>()
|
||||||
.ForMember(vm => vm.CurrencyProperty, mc => mc.AddFormatter<CurrencyFormatter>());
|
.ForMember(vm => vm.CurrencyProperty, mc => mc.AddFormatter<CurrencyFormatter>());
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
This sets up a mapping between the DomainModel and ViewModel and additionally applies a custom formatter for CurrencyProperty. The formatter must implement the IValueFormatter interface like so:public class CurrencyFormatter : IValueFormatter
|
This sets up a mapping between the DomainModel and `ViewModel` and additionally applies a custom formatter for CurrencyProperty. The formatter must implement the IValueFormatter interface like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
public class CurrencyFormatter : IValueFormatter
|
||||||
{
|
{
|
||||||
///<summary>Formats source value as currency</summary>
|
///<summary>Formats source value as currency</summary>
|
||||||
public string FormatValue(ResolutionContext context)
|
public string FormatValue(ResolutionContext context)
|
||||||
@@ -41,32 +56,49 @@ This sets up a mapping between the DomainModel and ViewModel and additionally ap
|
|||||||
return string.Format(CultureInfo.CurrentCulture, "{0:c}", context.SourceValue);
|
return string.Format(CultureInfo.CurrentCulture, "{0:c}", context.SourceValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
…and a simple conversion constructor on the ViewModel:/// <summary> Creates the view model from the domain model.</summary>
|
…and a simple conversion constructor on the `ViewModel`:
|
||||||
|
|
||||||
|
```
|
||||||
|
/// <summary> Creates the view model from the domain model.</summary>
|
||||||
public ViewModel(DomainModel domainModel)
|
public ViewModel(DomainModel domainModel)
|
||||||
{
|
{
|
||||||
Mapper.Map(domainModel, this);
|
Mapper.Map(domainModel, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Now, neither the Controller or View need concern about any formatting and can stay focused on orchestrating and layout:public ViewResult Index()
|
```
|
||||||
|
|
||||||
|
Now, neither the Controller or View need concern about any formatting and can stay focused on orchestrating and layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
public ViewResult Index()
|
||||||
{
|
{
|
||||||
var model = new DomainModel{CurrencyProperty = 19.95m};
|
var model = new DomainModel{CurrencyProperty = 19.95m};
|
||||||
|
|
||||||
var viewModel = new ViewModel(model);
|
var viewModel = new ViewModel(model);
|
||||||
|
|
||||||
return View(viewModel);
|
return View(viewModel);
|
||||||
}<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ViewModel>" %>
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ViewModel>" %>
|
||||||
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
|
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
|
||||||
<% using(Html.BeginForm()) {%>
|
<% using(Html.BeginForm()) {%>
|
||||||
<%= Html.TextBoxFor(m=>m.CurrencyProperty)%>
|
<%= Html.TextBoxFor(m=>m.CurrencyProperty)%>
|
||||||
<%} %>
|
<%} %>
|
||||||
</asp:Content>
|
</asp:Content>
|
||||||
|
```
|
||||||
|
|
||||||
*Aside: TextBoxFor is an upcoming ASP.NET MVC 2 feature that’s available today in MVC Futures or the RC. Check out *[*Matt’s post*](http://www.trycatchfail.com/blog/post/2009/12/11/My-best-%28or-worst%29-MVC-hack-to-datehellip3b.aspx)* for some neat stuff.*
|
*Aside: `TextBoxFor` is an upcoming ASP.NET MVC 2 feature that’s available today in MVC Futures or the RC. Check out *[*Matt’s post*](http://www.trycatchfail.com/blog/post/2009/12/11/My-best-%28or-worst%29-MVC-hack-to-datehellip3b.aspx)* for some neat stuff.*
|
||||||
|
|
||||||
#### Mapping from View Model back to Domain Model
|
#### Mapping from View Model back to Domain Model
|
||||||
|
|
||||||
So now the formatted value is being rendered – but how do we go about the reverse trip back to the server? First, to define an action:[AcceptVerbs(HttpVerbs.Post)]
|
So now the formatted value is being rendered – but how do we go about the reverse trip back to the server? First, to define an action:
|
||||||
|
|
||||||
|
```
|
||||||
|
[AcceptVerbs(HttpVerbs.Post)]
|
||||||
public ActionResult Index(ViewModel viewModel)
|
public ActionResult Index(ViewModel viewModel)
|
||||||
{
|
{
|
||||||
var model = new DomainModel();
|
var model = new DomainModel();
|
||||||
@@ -75,22 +107,35 @@ public ActionResult Index(ViewModel viewModel)
|
|||||||
|
|
||||||
// ... return a view or action result
|
// ... return a view or action result
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
*Aside: for validating that what the user enters is in the correct format, see *[*another post about jQuery Validate here*](/2010/01/01/jQueryValidateAndJeditablePart1.aspx)*.*
|
*Aside: for validating that what the user enters is in the correct format, see *[*another post about jQuery Validate here*](/2010/01/01/jQueryValidateAndJeditablePart1.aspx)*.*
|
||||||
|
|
||||||
… and a Map method on the ViewModel:public void MapTo(DomainModel domainModel)
|
… and a `Map` method on the `ViewModel`:
|
||||||
|
|
||||||
|
```
|
||||||
|
public void MapTo(DomainModel domainModel)
|
||||||
{
|
{
|
||||||
Mapper.Map(this, domainModel);
|
Mapper.Map(this, domainModel);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
But this mapping will fail without first creating a definition back from the ViewModel to the Model. In the ViewModel’s static constructor: // from vm to dm
|
But this mapping will fail without first creating a definition back from the `ViewModel` to the Model. In the `ViewModel`’s static constructor:
|
||||||
|
|
||||||
|
```
|
||||||
|
// from vm to dm
|
||||||
Mapper.CreateMap<ViewModel, DomainModel>()
|
Mapper.CreateMap<ViewModel, DomainModel>()
|
||||||
.ForMember(dm => dm.CurrencyProperty,
|
.ForMember(dm => dm.CurrencyProperty,
|
||||||
mc => mc
|
mc => mc
|
||||||
.ResolveUsing<CurrencyResolver>()
|
.ResolveUsing<CurrencyResolver>()
|
||||||
.FromMember(vm => vm.CurrencyProperty));
|
.FromMember(vm => vm.CurrencyProperty));
|
||||||
|
|
||||||
This utilizes another [AutoMapper](http://www.codeplex.com/AutoMapper) method - ResolveUsing - which can be used to get the string property back to a decimal. The ValueResolver<TSource,TDestination> is defined like so:public class CurrencyResolver : ValueResolver<string, decimal>
|
```
|
||||||
|
|
||||||
|
This utilizes another [AutoMapper](http://www.codeplex.com/AutoMapper) method - ResolveUsing - which can be used to get the string property back to a decimal. The `ValueResolver<TSource,TDestination>` is defined like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
public class CurrencyResolver : ValueResolver<string, decimal>
|
||||||
{
|
{
|
||||||
///<summary>Parses source value as currency</summary>
|
///<summary>Parses source value as currency</summary>
|
||||||
protected override decimal ResolveCore(string source)
|
protected override decimal ResolveCore(string source)
|
||||||
@@ -99,6 +144,8 @@ This utilizes another [AutoMapper](http://www.codeplex.com/AutoMapper) method -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
#### Conclusions
|
#### Conclusions
|
||||||
|
|
||||||
There may be more elegant ways to accomplish formatting for MVC Views, but this method is quite workable. In particular, I can imagine utilizing [DataAnnotations’s DisplayFormatAttribute](http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayformatattribute.aspx) to decorate the Model or ViewModel and the framework automagically applying the formatting while rendering the View.
|
There may be more elegant ways to accomplish formatting for MVC Views, but this method is quite workable. In particular, I can imagine utilizing [DataAnnotations’s DisplayFormatAttribute](http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayformatattribute.aspx) to decorate the Model or ViewModel and the framework automagically applying the formatting while rendering the View.
|
||||||
@@ -6,67 +6,107 @@ published: true
|
|||||||
---
|
---
|
||||||
|
|
||||||
Being a [Resharper](http://www.jetbrains.com/resharper/) user for the past 5 years, I had to jump on opportunity to [try out the publicly released beta for the new 5.0 version](http://www.jetbrains.com/resharper/beta/beta.html?utm_source=jetbrains-dotnet-outlook4&utm_medium=resharper5beta-page&utm_campaign=resharper5beta). I’m currently using Visual Studio 2008, but I’ll be glad to have the updated VS2010 support from Resharper once it’s released. As for the changes in this major revision, I’m excited to try out new code inspections, LINQ integration improvements, and native NUnit integration.
|
Being a [Resharper](http://www.jetbrains.com/resharper/) user for the past 5 years, I had to jump on opportunity to [try out the publicly released beta for the new 5.0 version](http://www.jetbrains.com/resharper/beta/beta.html?utm_source=jetbrains-dotnet-outlook4&utm_medium=resharper5beta-page&utm_campaign=resharper5beta). I’m currently using Visual Studio 2008, but I’ll be glad to have the updated VS2010 support from Resharper once it’s released. As for the changes in this major revision, I’m excited to try out new code inspections, LINQ integration improvements, and native NUnit integration.
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
- [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/Resharper5BetaInstallComplete.png)Install was quick and easy. It uninstalled the version 4.5 and questioned me about killing a task that was getting in the way.
|
|
||||||
- Starting Resharper, I’m greeted with a “License to version 4.0 is not acceptable”. This is troubling in two ways:
|
- [](../media/images/Resharper5BetaImpressions_7EC3/Resharper5BetaInstallComplete.png)Install was quick and easy. It uninstalled the version 4.5 and questioned me about killing a task that was getting in the way.
|
||||||
|
- Starting Resharper, I’m greeted with a “License to version 4.0 is not acceptable”. This is troubling in two ways:
|
||||||
|
|
||||||
- The license that I bought is for 4.5 C# Edition.
|
- The license that I bought is for 4.5 C# Edition.
|
||||||
- Why does a beta need a license?
|
- Why does a beta need a license?
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/Resharper5BetaLicense.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/Resharper5BetaLicense.png)
|
||||||
- For now, using “free evaluation” – this seems to do the trick.
|
- For now, using “free evaluation” – this seems to do the trick.
|
||||||
- As expected, the [AgentSmith plugin](http://www.agentsmithplugin.com/) is no longer installed (duh), but an updated version is available on their site.
|
|
||||||
|
- As expected, the [AgentSmith plugin](http://www.agentsmithplugin.com/) is no longer installed (duh), but an updated version is available on their site.
|
||||||
|
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
|
|
||||||
I’d like to be pretty thorough in acquainting myself with the enhancements, so I’ll touch on each of them from the [list here](http://blogs.jetbrains.com/dotnet/2009/10/resharper-50-overview/).
|
I’d like to be pretty thorough in acquainting myself with the enhancements, so I’ll touch on each of them from the [list here](http://blogs.jetbrains.com/dotnet/2009/10/resharper-50-overview/).
|
||||||
- **Structure Patterns** [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image.png)
|
|
||||||
- Custom built code refactorings. This could be a godsend for [brownfield development](http://en.wikipedia.org/wiki/Brownfield_%28software_development%29) – enabling project-wide cleanup for stinky “[code smells](http://en.wikipedia.org/wiki/Code_smell)”. The real power is in the “Placeholder” templating – it’s much like the Live Templates but for refactoring. The image onthe right has a pattern that I made to change from timeSpan to happyHour. Needless to say, this is trivial (and useless!), but I’m readily awaiting the next time I find a [code smell](http://en.wikipedia.org/wiki/Code_smell) I can’t live with.
|
- **Structure Patterns** [](../media/images/Resharper5BetaImpressions_7EC3/image.png)
|
||||||
- **Project Refactoring and Dependencies View**
|
|
||||||
|
- Custom built code refactorings. This could be a godsend for [brownfield development](http://en.wikipedia.org/wiki/Brownfield_%28software_development%29) – enabling project-wide cleanup for stinky “[code smells](http://en.wikipedia.org/wiki/Code_smell)”. The real power is in the “Placeholder” templating – it’s much like the Live Templates but for refactoring. The image onthe right has a pattern that I made to change from `timeSpan` to `happyHour`. Needless to say, this is trivial (and useless!), but I’m readily awaiting the next time I find a [code smell](http://en.wikipedia.org/wiki/Code_smell) I can’t live with.
|
||||||
|
|
||||||
|
- **Project Refactoring and Dependencies View**
|
||||||
|
|
||||||
- I’ve been waiting for the ability to mass-rename namespaces. Resharper5 : check.
|
- I’ve been waiting for the ability to mass-rename namespaces. Resharper5 : check.
|
||||||
- So what does “Project Refactoring” mean? Does a project have a bunch of types declared in the same files? After a few clicks, they can all moved into their own:[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_3.png)
|
- So what does “Project Refactoring” mean? Does a project have a bunch of types declared in the same files? After a few clicks, they can all moved into their own:[](../media/images/Resharper5BetaImpressions_7EC3/image_3.png)
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_4.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_4.png)
|
||||||
- Dependency View is basically “find usages by project” – which could certainly be useful for larger solutions.
|
- Dependency View is basically “find usages by project” – which could certainly be useful for larger solutions.
|
||||||
- **Call Tracking, Value Tracking**
|
|
||||||
|
- **Call Tracking, Value Tracking**
|
||||||
|
|
||||||
- Examines method, variable, field or property usage through the solution and finds where it’s being generated or called from, as well as the opposite - where it’s being used. It’s the static-analysis version of the call stack.
|
- Examines method, variable, field or property usage through the solution and finds where it’s being generated or called from, as well as the opposite - where it’s being used. It’s the static-analysis version of the call stack.
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_5.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_5.png)
|
||||||
- **Internationalization**
|
|
||||||
- I’ve never worked on a project using [Internationalization](http://en.wikipedia.org/wiki/Internationalization_and_localization), but it’s bound to happen sooner than later. Resharper 5 adds the ability to move string to resource files as well as refactoring and inspection to support multiple languages.
|
- **Internationalization**
|
||||||
- **ASP.NET**
|
|
||||||
- [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_6.png)Syntax highlighting! Check it out - unused namespaces inside ASP.NET markup will now appear grayed out, just as they do in source code.
|
- I’ve never worked on a project using [Internationalization](http://en.wikipedia.org/wiki/Internationalization_and_localization), but it’s bound to happen sooner than later. Resharper 5 adds the ability to move string to resource files as well as refactoring and inspection to support multiple languages.
|
||||||
|
|
||||||
|
- **ASP.NET**
|
||||||
|
|
||||||
|
- [](../media/images/Resharper5BetaImpressions_7EC3/image_6.png)Syntax highlighting! Check it out - unused namespaces inside ASP.NET markup will now appear grayed out, just as they do in source code.
|
||||||
- Templates for ASP.NET:
|
- Templates for ASP.NET:
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_7.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_7.png)
|
||||||
- **ASP.NET MVC**
|
|
||||||
- [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_8.png)View name autocomplete from the controller, as well as navigation to and from actions.
|
- **ASP.NET MVC**
|
||||||
|
|
||||||
|
- [](../media/images/Resharper5BetaImpressions_7EC3/image_8.png)View name autocomplete from the controller, as well as navigation to and from actions.
|
||||||
- … and navigation to Views. **Shift+Click** on a view name to jump there:
|
- … and navigation to Views. **Shift+Click** on a view name to jump there:
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_9.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_9.png)
|
||||||
- *Aside: Our project has some calls to HtmlHelper.RenderPartial(“<ViewName>”) called inside of a class instantiated with an instance of HtmlHelper (it’s a Helper for HtmlHelper). Resharper can’t resolve these names… but I wouldn’t expect it to.*
|
- *Aside: Our project has some calls to HtmlHelper.RenderPartial(“<ViewName>”) called inside of a class instantiated with an instance of HtmlHelper (it’s a Helper for HtmlHelper). Resharper can’t resolve these names… but I wouldn’t expect it to.*
|
||||||
- **IntelliSense Changes**
|
|
||||||
|
- **IntelliSense Changes**
|
||||||
|
|
||||||
- In addition to performance improvements, completion can now done using abbreviated names based on [CamelHumps](http://animals.howstuffworks.com/mammals/question104.htm):
|
- In addition to performance improvements, completion can now done using abbreviated names based on [CamelHumps](http://animals.howstuffworks.com/mammals/question104.htm):
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_10.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_10.png)
|
||||||
- [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_11.png)**Bookmarks**
|
|
||||||
|
- [](../media/images/Resharper5BetaImpressions_7EC3/image_11.png)**Bookmarks**
|
||||||
|
|
||||||
- Set and jump to bookmarks with quick keystrokes. **Ctrl+Shift+[0-9]** to set, and **Ctrl+[0-9]** jump back. **Ctr+~** to see which bookmarks are available:
|
- Set and jump to bookmarks with quick keystrokes. **Ctrl+Shift+[0-9]** to set, and **Ctrl+[0-9]** jump back. **Ctr+~** to see which bookmarks are available:
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_12.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_12.png)
|
||||||
- **Upgrade to LINQ**
|
|
||||||
|
- **Upgrade to LINQ**
|
||||||
|
|
||||||
- Now this is pretty slick – parses a foreach loop and its innards and [translates it to a single LINQ statement](http://blogs.jetbrains.com/dotnet/2009/12/resharper-50-preview-loops-2-linq/):
|
- Now this is pretty slick – parses a foreach loop and its innards and [translates it to a single LINQ statement](http://blogs.jetbrains.com/dotnet/2009/12/resharper-50-preview-loops-2-linq/):
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_19.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_19.png)
|
||||||
- **New and Improved Code Inspections**
|
|
||||||
- So, JetBrain’s says they’ve added a bunch of new code inspections – I’m counting a little over 100 C# Context Actions in 5.0, where as 4.5 had closer to 80. There appear to be some LINQ related ones in there. They’ve also called out that it can now highlight errors in XML comments (something which the [AgentSmith](http://www.agentsmithplugin.com/) plugin already did quite well).
|
- **New and Improved Code Inspections**
|
||||||
- [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_14.png)**Native NUnit Support**
|
|
||||||
- To be honest, I’ve been using the previous Resharper version’s NUnit support without complaint. I’m thinking that the that the improvements are “under the hood” – it works just as well now as it did before.
|
- So, JetBrain’s says they’ve added a bunch of new code inspections – I’m counting a little over 100 C# Context Actions in 5.0, where as 4.5 had closer to 80. There appear to be some LINQ related ones in there. They’ve also called out that it can now highlight errors in XML comments (something which the [AgentSmith](http://www.agentsmithplugin.com/) plugin already did quite well).
|
||||||
- **XML Formatting**
|
|
||||||
|
- [](../media/images/Resharper5BetaImpressions_7EC3/image_14.png)**Native NUnit Support**
|
||||||
|
|
||||||
|
- To be honest, I’ve been using the previous Resharper version’s NUnit support without complaint. I’m thinking that the that the improvements are “under the hood” – it works just as well now as it did before.
|
||||||
|
|
||||||
|
- **XML Formatting**
|
||||||
|
|
||||||
- Inspection and refactoring support - “Reorder attributes” and “Collapse Empty Tag”, for instance.
|
- Inspection and refactoring support - “Reorder attributes” and “Collapse Empty Tag”, for instance.
|
||||||
[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_15.png)[](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_16.png)
|
[](../media/images/Resharper5BetaImpressions_7EC3/image_15.png)[](../media/images/Resharper5BetaImpressions_7EC3/image_16.png)
|
||||||
- [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_17.png)**External Sources**
|
|
||||||
- This promises to add navigation to referenced libraries that before could only be accessed at the higher namespace-class-method level via Visual Studio’s Object Browser. I poked around a bit on JetBrain’s site looking how to configure the symbol locations, but it doesn’t seem to be documented yet. Perhaps it might need to have the symbols locations populated in VS->Options->Debugging-Symbols, but perhaps not.
|
- [](../media/images/Resharper5BetaImpressions_7EC3/image_17.png)**External Sources**
|
||||||
|
|
||||||
|
- This promises to add navigation to referenced libraries that before could only be accessed at the higher namespace-class-method level via Visual Studio’s Object Browser. I poked around a bit on JetBrain’s site looking how to configure the symbol locations, but it doesn’t seem to be documented yet. Perhaps it might need to have the symbols locations populated in VS->Options->Debugging-Symbols, but perhaps not.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Surprises
|
### Surprises
|
||||||
|
|
||||||
|
|
||||||
- Transparently imported settings from 4.5 – horray! Appears to be able to use the 4.5.resharper shared solution settings
|
- Transparently imported settings from 4.5 – horray! Appears to be able to use the 4.5.resharper shared solution settings
|
||||||
- [](http://popcyclical.com/content/binary/images/Resharper5BetaImpressions_7EC3/image_18.png)Running “Find Usages” on a class is taking much longer – in previous version it was instantaneous, now it appears to be scrounging through files instead of an index (could be External sources feature?)
|
- [](../media/images/Resharper5BetaImpressions_7EC3/image_18.png)Running “Find Usages” on a class is taking much longer – in previous version it was instantaneous, now it appears to be scrounging through files instead of an index (could be External sources feature?)
|
||||||
- Quick navigate to Type/Filename/Symbol now match partial names – no more needing to put in a “*” to match wildcards.
|
- Quick navigate to Type/Filename/Symbol now match partial names – no more needing to put in a “*” to match wildcards.
|
||||||
- In the day or two I’ve been using it, I don’t think I’ve encountered any crashes. This is a good thing – the [multitude of errors being automatically reported](http://www.jetbrains.net/jira/browse/RSRP) seem to be going to good use.
|
- In the day or two I’ve been using it, I don’t think I’ve encountered any crashes. This is a good thing – the [multitude of errors being automatically reported](http://www.jetbrains.net/jira/browse/RSRP) seem to be going to good use.
|
||||||
|
|
||||||
|
|
||||||
### Wish List
|
### Wish List
|
||||||
|
|
||||||
|
|
||||||
- Javascript. Being a dynamic language and all, it’d be pretty difficult to implement the full set of navigation and refactoring helpers Resharper provides for C# and VB. But oh, wouldn’t it be slick if it could.
|
- Javascript. Being a dynamic language and all, it’d be pretty difficult to implement the full set of navigation and refactoring helpers Resharper provides for C# and VB. But oh, wouldn’t it be slick if it could.
|
||||||
- Community sharing for code-style and and structure patterns. There’s already preference and template sharing with team members via a shared settings file. The next evolution is to extend this to the cloud and create a public library to exchange ideas with all Resharper users.
|
- Community sharing for code-style and and structure patterns. There’s already preference and template sharing with team members via a shared settings file. The next evolution is to extend this to the cloud and create a public library to exchange ideas with all Resharper users.
|
||||||
|
|
||||||
|
|
||||||
For every one of the new features I’ve encountered in the past couple of days, I’m sure there are two or three that I haven’t stumbled over yet. That’s a great thing about this product – utilizing a small subset of it’s features can greatly [streamline development and increase productivity](http://blog.briandicroce.com/2008/05/12/the-case-for-resharper-in-the-enterprise/). Even after years of use, I am still happily surprised to discover new facets of the tool I hadn’t noticed or investigated before.
|
For every one of the new features I’ve encountered in the past couple of days, I’m sure there are two or three that I haven’t stumbled over yet. That’s a great thing about this product – utilizing a small subset of it’s features can greatly [streamline development and increase productivity](http://blog.briandicroce.com/2008/05/12/the-case-for-resharper-in-the-enterprise/). Even after years of use, I am still happily surprised to discover new facets of the tool I hadn’t noticed or investigated before.
|
||||||
@@ -7,21 +7,31 @@ published: true
|
|||||||
|
|
||||||
As software developers we are forever scheming on ways to increase the quality of our software. It’s a great feeling when customers are enjoying some bit of code that you wrote, so making it even better next time is a worthwhile goal. In contrast, I’ve observed at a number of [SaaS](http://en.wikipedia.org/wiki/Software_as_a_service) development shops have a **lack of quality** when it comes to the **delivery mechanics**. It’s as if once the software is written, the development effort is over and it’s time to tediously prepare for the deployment ceremony. What I want to write about is the deployment process itself – there’s a whole other topic relating to deployment project management which includes tracking changes, scheduling – perhaps another day on that.
|
As software developers we are forever scheming on ways to increase the quality of our software. It’s a great feeling when customers are enjoying some bit of code that you wrote, so making it even better next time is a worthwhile goal. In contrast, I’ve observed at a number of [SaaS](http://en.wikipedia.org/wiki/Software_as_a_service) development shops have a **lack of quality** when it comes to the **delivery mechanics**. It’s as if once the software is written, the development effort is over and it’s time to tediously prepare for the deployment ceremony. What I want to write about is the deployment process itself – there’s a whole other topic relating to deployment project management which includes tracking changes, scheduling – perhaps another day on that.
|
||||||
|
|
||||||
The fact is that many software shops will deploy their services manually. This may (hopefully) include **multiple environments** for development, QA, staging, and finally production. Deployments will have checklists looking something like:
|
The fact is that many software shops will deploy their services manually. This may (hopefully) include **multiple environments** for development, QA, staging, and finally production. Deployments will have checklists looking something like:
|
||||||
|
|
||||||
- Log into target machine.
|
- Log into target machine.
|
||||||
- Shut down service.
|
- Shut down service.
|
||||||
- Copy updated binaries.
|
- Copy updated binaries.
|
||||||
- Make any configuration changes.
|
- Make any configuration changes.
|
||||||
- Restart service.
|
- Restart service.
|
||||||
- Repeat for every target.
|
- Repeat for every target.
|
||||||
|
|
||||||
|
|
||||||
If the application has data/schema updates, it could be added as another step in the deployment process. Likewise, in a multi-target environment there may be preliminary steps for switching out the targets from an application pool. Finally, in the case of a software emergency, a working roll-back strategy is essential. A few points:
|
If the application has data/schema updates, it could be added as another step in the deployment process. Likewise, in a multi-target environment there may be preliminary steps for switching out the targets from an application pool. Finally, in the case of a software emergency, a working roll-back strategy is essential. A few points:
|
||||||
- **Deployment is time consuming**
|
|
||||||
- While each step may only take a few minutes, together it can add up to a significant chunk of a work day. Multiply that by the target count for the environment - lather, rinse, repeat.
|
- **Deployment is time consuming**
|
||||||
- **Steps are error prone**
|
|
||||||
- A missed or botched step may not be noticed until it’s time to turn on the service – or worse, afterwards. There’s a lot of click-click-typedy-typedy-clicking with not a lot of feedback.
|
- While each step may only take a few minutes, together it can add up to a significant chunk of a work day. Multiply that by the target count for the environment - lather, rinse, repeat.
|
||||||
- **None of the steps actually require human interaction**
|
|
||||||
- I have never seen a deployment plan where all of the steps and details were not known before hand. If the plan is not 100% deterministic, it’s probably more of a deployment *idea*** **and should be re-thunk’d.
|
- **Steps are error prone**
|
||||||
|
|
||||||
|
- A missed or botched step may not be noticed until it’s time to turn on the service – or worse, afterwards. There’s a lot of click-click-typedy-typedy-clicking with not a lot of feedback.
|
||||||
|
|
||||||
|
- **None of the steps actually require human interaction**
|
||||||
|
|
||||||
|
- I have never seen a deployment plan where all of the steps and details were not known before hand. If the plan is not 100% deterministic, it’s probably more of a deployment *idea*** **and should be re-thunk’d.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Automating the deployment seems like a no-brainer. [Ayende Rahien makes his views clear](http://ayende.com/Blog/archive/2008/09/08/Requirements-101-Have-an-automated-deployment.aspx) -
|
Automating the deployment seems like a no-brainer. [Ayende Rahien makes his views clear](http://ayende.com/Blog/archive/2008/09/08/Requirements-101-Have-an-automated-deployment.aspx) -
|
||||||
>
|
>
|
||||||
@@ -30,15 +40,25 @@ Automating the deployment seems like a no-brainer. [Ayende Rahien makes his vi
|
|||||||
|
|
||||||
But how to get from source code to having it automatically deployed? It takes a bit of setup, but it’s well worth the effort for a project in active development. Here’s one potential dataflow:
|
But how to get from source code to having it automatically deployed? It takes a bit of setup, but it’s well worth the effort for a project in active development. Here’s one potential dataflow:
|
||||||
|
|
||||||
[](http://popcyclical.com/content/binary/images/AutomatedServiceDeploymentWorkflow_1325C/ServiceDevelopmentWorkflow.png)
|
[](../media/images/AutomatedServiceDeploymentWorkflow_1325C/ServiceDevelopmentWorkflow.png)
|
||||||
- **Code gets written for the project and placed in source control**
|
|
||||||
- All code needs to be in source control – no exceptions!
|
- **Code gets written for the project and placed in source control**
|
||||||
|
|
||||||
|
- All code needs to be in source control – no exceptions!
|
||||||
|
|
||||||
- **Continuous integration triggers a build**
|
- **Continuous integration triggers a build**
|
||||||
- **Builds artifacts are created by the build server**
|
- **Builds artifacts are created by the build server**
|
||||||
- For reliable builds, it’s important to have a [dedicated build machine](http://blog.ianuy.com/2009/06/07/the-importance-of-a-dedicated-build-machine/) - [magical or not](http://www.codinghorror.com/blog/2004/12/the-magical-build-machine.html)
|
|
||||||
- **Project configurations for each potential target**
|
- For reliable builds, it’s important to have a [dedicated build machine](http://blog.ianuy.com/2009/06/07/the-importance-of-a-dedicated-build-machine/) - [magical or not](http://www.codinghorror.com/blog/2004/12/the-magical-build-machine.html)
|
||||||
- This includes setting environmental variables and injecting any content needed to make the code run correctly once it is deployed
|
|
||||||
- **Automated Deployment**
|
- **Project configurations for each potential target**
|
||||||
- This is essentially a scripted equivalent of the manual deployment process
|
|
||||||
|
- This includes setting environmental variables and injecting any content needed to make the code run correctly once it is deployed
|
||||||
|
|
||||||
|
- **Automated Deployment**
|
||||||
|
|
||||||
|
- This is essentially a scripted equivalent of the manual deployment process
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
There are many open source and commercially available technologies catering to each of these functions – I’ll dig into a few of them in some future posts. In broad terms, it’s a worthwhile endeavor to have an **end-to-end software delivery process**. It’s a guarantee that your time and energy are kept focused on doing what’s important – developing software!
|
There are many open source and commercially available technologies catering to each of these functions – I’ll dig into a few of them in some future posts. In broad terms, it’s a worthwhile endeavor to have an **end-to-end software delivery process**. It’s a guarantee that your time and energy are kept focused on doing what’s important – developing software!
|
||||||
@@ -7,4 +7,4 @@ published: true
|
|||||||
|
|
||||||
I just posted an article on [CodeProject](http://www.codeproject.com) about a generalized code block which can be used in a common scenario for simple performance benchmarking. Check out ["Simplified Performance Counters for Timed Operations" here](http://www.codeproject.com/KB/dotnet/simpleperfcounters.aspx)…
|
I just posted an article on [CodeProject](http://www.codeproject.com) about a generalized code block which can be used in a common scenario for simple performance benchmarking. Check out ["Simplified Performance Counters for Timed Operations" here](http://www.codeproject.com/KB/dotnet/simpleperfcounters.aspx)…
|
||||||
|
|
||||||
[](http://popcyclical.com/content/binary/images/SimplifiedPerformanceCountersin.NET_E293/OperationPerformanceCounter_AddCounters.png) [](http://popcyclical.com/content/binary/images/SimplifiedPerformanceCountersin.NET_E293/OperationPerformanceCounter_OperationsPerSecond.png) [](http://popcyclical.com/content/binary/images/SimplifiedPerformanceCountersin.NET_E293/OperationPerformanceCounter_AverageDuration.png)
|
[](../media/images/SimplifiedPerformanceCountersin.NET_E293/OperationPerformanceCounter_AddCounters.png) [](../media/images/SimplifiedPerformanceCountersin.NET_E293/OperationPerformanceCounter_OperationsPerSecond.png) [](../media/images/SimplifiedPerformanceCountersin.NET_E293/OperationPerformanceCounter_AverageDuration.png)
|
||||||
@@ -5,17 +5,24 @@ slug: programming-language-misuse
|
|||||||
published: true
|
published: true
|
||||||
---
|
---
|
||||||
|
|
||||||
I’m feeling a bit guilty about some code I wrote:using (new OperationTimer("MyOperation", this))
|
I’m feeling a bit guilty about some code I wrote:
|
||||||
|
|
||||||
|
```
|
||||||
|
using (new OperationTimer("MyOperation", this))
|
||||||
{
|
{
|
||||||
// ... complete operation
|
// ... complete operation
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
This innocent looking C# snippet is hiding a tricky secret - the using statement is being misused (no pun intended). The [documentation defines the intended usage clearly](http://msdn.microsoft.com/en-us/library/yh598w02%28VS.80%29.aspx):
|
This innocent looking C# snippet is hiding a tricky secret - the `using` statement is being misused (no pun intended). The [documentation defines the intended usage clearly](http://msdn.microsoft.com/en-us/library/yh598w02%28VS.80%29.aspx):
|
||||||
|
|
||||||
using Statement
|
using Statement
|
||||||
Defines a scope, outside of which an object or objects will be disposed.
|
Defines a scope, outside of which an object or objects will be disposed.
|
||||||
|
|
||||||
The problem? The notion of “object disposal” is being hijacked! In your garden variety [IDisposable](http://msdn.microsoft.com/en-us/library/system.idisposable.aspx) implementation, you’d be dealing with an [external resource that needs to be released](http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx) before the object can be removed from memory. Instead, I’m using it to time a block of code like so:class OperationTimer : IDisposable
|
The problem? The notion of “object disposal” is being hijacked! In your garden variety [`IDisposable`](http://msdn.microsoft.com/en-us/library/system.idisposable.aspx) implementation, you’d be dealing with an [external resource that needs to be released](http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx) before the object can be removed from memory. Instead, I’m using it to time a block of code like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
class OperationTimer : IDisposable
|
||||||
{
|
{
|
||||||
private readonly string _operationName;
|
private readonly string _operationName;
|
||||||
private readonly ITimable _obj;
|
private readonly ITimable _obj;
|
||||||
@@ -35,20 +42,29 @@ The problem? The notion of “object disposal” is being hijacked! In your
|
|||||||
_obj.OnOperationCompleted(_operationName, _stopwatch.Elapsed);
|
_obj.OnOperationCompleted(_operationName, _stopwatch.Elapsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
The constructor starts a timer and the Dispose() method stops it and reports the elapsed time. (*aside: if you’re interested in how I’m using the timer, check out my previous article *[*Simplified Performance Counters*](http://popcyclical.com/2010/03/21/SimplifiedPerformanceCountersInNET.aspx)) There are certainly other ways to accomplish this same behavior, but they lack the elegance of a neatly scoped code block. It’s arguably an acceptable way to repurpose the language. In fact, the ASP.NET MVC authors saw fit to use it in a similar fashion with the [BeginForm helper](http://msdn.microsoft.com/en-us/library/dd410596.aspx). The only “resource” it disposes of is to render a closing </form> tag.
|
The constructor starts a timer and the `Dispose()` method stops it and reports the elapsed time. (*aside: if you’re interested in how I’m using the timer, check out my previous article *[*Simplified Performance Counters*](http://popcyclical.com/2010/03/21/SimplifiedPerformanceCountersInNET.aspx)) There are certainly other ways to accomplish this same behavior, but they lack the elegance of a neatly scoped code block. It’s arguably an acceptable way to repurpose the language. In fact, the ASP.NET MVC authors saw fit to use it in a similar fashion with the [BeginForm helper](http://msdn.microsoft.com/en-us/library/dd410596.aspx). The only “resource” it disposes of is to render a closing `</form>` tag.
|
||||||
|
|
||||||
My question is: **When does repurposing language constructs turn from “acceptable language use” to a “dirty trick”, or worse, “illegible line noise”**?
|
My question is: **When does repurposing language constructs turn from “acceptable language use” to a “dirty trick”, or worse, “illegible line noise”**?
|
||||||
|
|
||||||
It seems like a slippery slope. One instance that I don’t care for is controlling execution flow by-way-of logical operator precedence in most C-like languages:expression1 && expression2 || expression3
|
It seems like a slippery slope. One instance that I don’t care for is controlling execution flow by-way-of logical operator precedence in most C-like languages:
|
||||||
|
|
||||||
Which is equivalent to:if (expression1)
|
```
|
||||||
|
expression1 && expression2 || expression3
|
||||||
|
```
|
||||||
|
|
||||||
|
Which is equivalent to:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (expression1)
|
||||||
expression2
|
expression2
|
||||||
else
|
else
|
||||||
expression3
|
expression3
|
||||||
|
```
|
||||||
|
|
||||||
This takes advantage of the order of evaluation in a logical statement – it is assumed (correctly) that expression2 will never be evaluated if expression1 is evaluated as false, and instead, expression3 will get to run. Likewise, if the first two evaluate to true, the truth value is known for the statement and expression3 is never evaluated. This is clearly not the intended usage which the language designers had in mind, but it works, and it saves any keywords from being written.
|
This takes advantage of the order of evaluation in a logical statement – it is assumed (correctly) that `expression2` will never be evaluated if `expression1` is evaluated as false, and instead, `expression3` will get to run. Likewise, if the first two evaluate to true, the truth value is known for the statement and `expression3` is never evaluated. This is clearly not the intended usage which the language designers had in mind, but it works, and it saves any keywords from being written.
|
||||||
|
|
||||||
Some truly beautiful code has been written by way of hijacking the language. For instance, [here’s a program that will calculate the value of pi using an ascii circle](http://www.cise.ufl.edu/~manuel/obfuscate/pi.c). Truly neat - but also completely useless from a software development standpoint.
|
Some truly beautiful code has been written by way of hijacking the language. For instance, [here’s a program that will calculate the value of pi using an ascii circle](http://www.cise.ufl.edu/~manuel/obfuscate/pi.c). Truly neat - but also completely useless from a software development standpoint.
|
||||||
|
|
||||||
What do you think? Should I just get over my guilt about repurposing IDisposable? Or, should I be true to the original intent of the language and find another way?
|
What do you think? Should I just get over my guilt about repurposing `IDisposable`? Or, should I be true to the original intent of the language and find another way?
|
||||||
@@ -11,10 +11,12 @@ Digging around in some code circa 6 months ago I discovered a method that I had
|
|||||||
|
|
||||||
Luckily, that behavior wasn’t being used anywhere in my project (yet!), but still, it was essentially a land mine waiting for someone to trip it. My first reaction was “shame on them for posting that without testing it!” Of course, this code didn’t end up in my project because of the author. It was I who blindly accepted and given it the “it’s from the internet!”-stamp-of-approval.
|
Luckily, that behavior wasn’t being used anywhere in my project (yet!), but still, it was essentially a land mine waiting for someone to trip it. My first reaction was “shame on them for posting that without testing it!” Of course, this code didn’t end up in my project because of the author. It was I who blindly accepted and given it the “it’s from the internet!”-stamp-of-approval.
|
||||||
|
|
||||||
Lessons learned today:[](http://popcyclical.com/content/binary/images/fd79eac90e05_13202/itsfromtheinternet.png)
|
Lessons learned today:[](../media/images/fd79eac90e05_13202/itsfromtheinternet.png)
|
||||||
|
|
||||||
- Trust is earned, not given.
|
- Trust is earned, not given.
|
||||||
- Source code becomes trusted by-way-of thorough unit and functional testing.
|
- Source code becomes trusted by-way-of thorough unit and functional testing.
|
||||||
- Do not trust untested code from the internet.
|
- Do not trust untested code from the internet.
|
||||||
- Do not trust untested code from your own keyboard even more so – at least on the internet it’s likely that someone else has reviewed it.
|
- Do not trust untested code from your own keyboard even more so – at least on the internet it’s likely that someone else has reviewed it.
|
||||||
|
|
||||||
|
|
||||||
I’ve written the author a friendly note with a simple fix – it’s better to diffuse that bomb than let it get somebody else!
|
I’ve written the author a friendly note with a simple fix – it’s better to diffuse that bomb than let it get somebody else!
|
||||||
@@ -7,10 +7,10 @@ published: true
|
|||||||
|
|
||||||
This last week I gave two presentations at [CodeStock 2010](http://codestock.org/). Thanks to everyone attended my sessions, hope you got something out of them, and thanks to the [CodeStock organizers](http://www.vinull.com/) for all your hard work. Here’s some content from the presentations:
|
This last week I gave two presentations at [CodeStock 2010](http://codestock.org/). Thanks to everyone attended my sessions, hope you got something out of them, and thanks to the [CodeStock organizers](http://www.vinull.com/) for all your hard work. Here’s some content from the presentations:
|
||||||
|
|
||||||
[Parallel Computing in .NET 4 and VS 2010 - Presentation](http://popcyclical.com/content/binary/images/CodeStock2010_A334/ParallelComputing_VS2010.pptx)
|
[Parallel Computing in .NET 4 and VS 2010 - Presentation](../media/images/CodeStock2010_A334/ParallelComputing_VS2010.pptx)
|
||||||
[Parallel Computing in .NET 4 and VS 2010 - Code](http://popcyclical.com/content/binary/images/CodeStock2010_A334/ParallelComputing_VS2010_code.zip)
|
[Parallel Computing in .NET 4 and VS 2010 - Code](../media/images/CodeStock2010_A334/ParallelComputing_VS2010_code.zip)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Introduction to Windows PowerShell - Presentation](http://popcyclical.com/content/binary/images/CodeStock2010_A334/Intro_To_PowerShell.pptx)
|
[Introduction to Windows PowerShell - Presentation](../media/images/CodeStock2010_A334/Intro_To_PowerShell.pptx)
|
||||||
[Introduction to Windows PowerShell - Code](http://popcyclical.com/content/binary/images/CodeStock2010_A334/Intro_To_PowerShell_code.zip)
|
[Introduction to Windows PowerShell - Code](../media/images/CodeStock2010_A334/Intro_To_PowerShell_code.zip)
|
||||||
@@ -5,7 +5,7 @@ slug: poem-for-resharper
|
|||||||
published: true
|
published: true
|
||||||
---
|
---
|
||||||
|
|
||||||
*With tongue implementation injected dynamically into cheek object*[](http://www.jetbrains.com/resharper/)…
|
*With tongue implementation injected dynamically into cheek object*[](http://www.jetbrains.com/resharper/)…
|
||||||
|
|
||||||
ReSharper is my shepherd,
|
ReSharper is my shepherd,
|
||||||
I shall not want;
|
I shall not want;
|
||||||
|
|||||||
@@ -5,18 +5,42 @@ slug: splitting-pascalcamel-case-with-regex-enhancements
|
|||||||
published: true
|
published: true
|
||||||
---
|
---
|
||||||
|
|
||||||
In [Jon Galloway’s](http://weblogs.asp.net/jgalloway/) [Splitting Camel Case with RegEx](http://weblogs.asp.net/jgalloway/archive/2005/09/27/426087.aspx) blog post, he introduced a simple regular expression replacement which can split “ThisIsInPascalCase” into “This Is In Pascal Case”. Here’s the original code:output = System.Text.RegularExpressions.Regex.Replace(
|
In [Jon Galloway’s](http://weblogs.asp.net/jgalloway/) [Splitting Camel Case with RegEx](http://weblogs.asp.net/jgalloway/archive/2005/09/27/426087.aspx) blog post, he introduced a simple regular expression replacement which can split “ThisIsInPascalCase” into “This Is In Pascal Case”. Here’s the original code:
|
||||||
|
|
||||||
|
```
|
||||||
|
output = System.Text.RegularExpressions.Regex.Replace(
|
||||||
input,
|
input,
|
||||||
"([A-Z])",
|
"([A-Z])",
|
||||||
" $1",
|
" $1",
|
||||||
System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
|
System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
|
||||||
|
|
||||||
Simple and effective. Matches any capital letters and inserts a space before them. But there’s room for improvement. First, the call to String.Trim() to remove any spaces potentially added if the first letter is uppercase – this can be handled with a [“Match if prefix is absent” group](http://msdn.microsoft.com/en-us/library/az24scfc.aspx#grouping_constructs) containing the “beginning of line” character ^. This prevents any matches from occurring on the first character, which eliminates the need for the String.Trim() call. The formal name for this grouping construct is “Zero-width negative lookbehind assertion”, but just think of it as “if you see what’s in here, don’t match the next thing”. (?<!^)([A-Z])
|
```
|
||||||
|
|
||||||
Next - there’s a potential issue with how acronyms get handled with this. Given this fictional book title: “WCFForNoobs” – the split will occur on each uppercase letter resulting in “W C F For Noobs”. The fix is simple, though – require that uppercase letters be followed by a lowercase: (?<!^)([A-Z][a-z])
|
Simple and effective. Matches any capital letters and inserts a space before them. But there’s room for improvement. First, the call to `String.Trim()` to remove any spaces potentially added if the first letter is uppercase – this can be handled with a [“Match if prefix is absent” group](http://msdn.microsoft.com/en-us/library/az24scfc.aspx#grouping_constructs) containing the “beginning of line” character `^`. This prevents any matches from occurring on the first character, which eliminates the need for the `String.Trim()` call. The formal name for this grouping construct is “Zero-width negative lookbehind assertion”, but just think of it as “if you see what’s in here, don’t match the next thing”.
|
||||||
|
|
||||||
…Now it’ll result in “WCF For Noobs” (aren’t we all!). But now it won’t add a space before the acronym – for “LearnWCFInSixEasyMonths”, the result will be “LearnWCF In Six Easy Months”. No problem – add an alternate match for a lowercase letter coming before the uppercase letter. The replace pattern makes this more difficult – we don’t want the space to go before the lowercase letter, we want it between the lowercase and the first capital letter of the acronym. RegEx can handle this with another lookbehind match group – “Match prefix but exclude it” - (?<=). This allows the match to occur on the lowercase-uppercase pair, but only the uppercase portion will get matched, so when it comes time to run the replacement, the space will get inserted between the two letters. By itself, that’ll look like this: ((?<=[a-z])[A-Z])
|
```
|
||||||
|
(?<!^)([A-Z])
|
||||||
|
```
|
||||||
|
|
||||||
Great! But this needs to be combined with previous expression. Easy accomplished with an either/or match using the vertical bar “or” construct: (?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])
|
Next - there’s a potential issue with how acronyms get handled with this. Given this fictional book title: “WCFForNoobs” – the split will occur on each uppercase letter resulting in “W C F For Noobs”. The fix is simple, though – require that uppercase letters be followed by a lowercase:
|
||||||
|
|
||||||
|
```
|
||||||
|
(?<!^)([A-Z][a-z])
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
…Now it’ll result in “WCF For Noobs” (aren’t we all!). But now it won’t add a space before the acronym – for “LearnWCFInSixEasyMonths”, the result will be “LearnWCF In Six Easy Months”. No problem – add an alternate match for a lowercase letter coming before the uppercase letter. The replace pattern makes this more difficult – we don’t want the space to go before the lowercase letter, we want it between the lowercase and the first capital letter of the acronym. RegEx can handle this with another lookbehind match group – “Match prefix but exclude it” - `(?<=)`. This allows the match to occur on the lowercase-uppercase pair, but only the uppercase portion will get matched, so when it comes time to run the replacement, the space will get inserted between the two letters. By itself, that’ll look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
((?<=[a-z])[A-Z])
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Great! But this needs to be combined with previous expression. Easy accomplished with an either/or match using the vertical bar “or” construct:
|
||||||
|
|
||||||
|
```
|
||||||
|
(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
The example “LearnWCFInSixEasyMonths” will now be split into “Learn WCF In Six Easy Months”. These same techniques can be used for additional splits – perhaps on numbers or underscores. More generally, [lookbehind and lookahead are great tools](http://www.regular-expressions.info/lookaround.html) to have in your RegEx toolbelt.
|
The example “LearnWCFInSixEasyMonths” will now be split into “Learn WCF In Six Easy Months”. These same techniques can be used for additional splits – perhaps on numbers or underscores. More generally, [lookbehind and lookahead are great tools](http://www.regular-expressions.info/lookaround.html) to have in your RegEx toolbelt.
|
||||||
@@ -18,32 +18,51 @@ What is the largest square number formed by any member of such a pair?
|
|||||||
|
|
||||||
This is what I’d consider a pretty good puzzle – the elements (a word list and square numbers) are easy enough to grasp, the way they’re related together is unique for this scenario, and an effective solution does not immediately pop into mind. Well, it didn’t pop into *my *mind, at least. So – where to begin?
|
This is what I’d consider a pretty good puzzle – the elements (a word list and square numbers) are easy enough to grasp, the way they’re related together is unique for this scenario, and an effective solution does not immediately pop into mind. Well, it didn’t pop into *my *mind, at least. So – where to begin?
|
||||||
|
|
||||||
Many of the problems (at least the ones I’ve solved so far) on Project Euler involve a few common steps to reach a solution:
|
Many of the problems (at least the ones I’ve solved so far) on Project Euler involve a few common steps to reach a solution:
|
||||||
|
|
||||||
- **Generate a set of input values for the problem** – usually this will be a large set of potential values, such as all the prime numbers below 10,000,000, or in this case, 16K of words and a bunch of square numbers.
|
- **Generate a set of input values for the problem** – usually this will be a large set of potential values, such as all the prime numbers below 10,000,000, or in this case, 16K of words and a bunch of square numbers.
|
||||||
- Given these inputs, **build an algorithm that can test for the solution**.
|
- Given these inputs, **build an algorithm that can test for the solution**.
|
||||||
- **Add constraints to reduce the size of the input set** so that the solution can quickly be found. On modern hardware, this is usually under 1 second – but anything under a minute is [considered kosher](http://projecteuler.net/about).
|
- **Add constraints to reduce the size of the input set** so that the solution can quickly be found. On modern hardware, this is usually under 1 second – but anything under a minute is [considered kosher](http://projecteuler.net/about).
|
||||||
|
|
||||||
|
|
||||||
### Input Values
|
### Input Values
|
||||||
|
|
||||||
|
|
||||||
The input values are easy here – they’ve provided the word list, and a sequence of square numbers should be trivial. First, reading the word list. Instead of placing the words each on a new line, the file instead contains a single line formatted like:
|
The input values are easy here – they’ve provided the word list, and a sequence of square numbers should be trivial. First, reading the word list. Instead of placing the words each on a new line, the file instead contains a single line formatted like:
|
||||||
> "A","ABILITY","ABLE","ABOUT","ABOVE","ABSENCE"...
|
>
|
||||||
|
|
||||||
Ok – so it’ll take a little parsing. No problem.let words =
|
```
|
||||||
|
"A","ABILITY","ABLE","ABOUT","ABOVE","ABSENCE"...
|
||||||
|
```
|
||||||
|
|
||||||
|
Ok – so it’ll take a little parsing. No problem.
|
||||||
|
|
||||||
|
```
|
||||||
|
let words =
|
||||||
System.IO.File.ReadAllLines(@"words.txt")
|
System.IO.File.ReadAllLines(@"words.txt")
|
||||||
|> Array.collect (fun line -> line.Split(','))
|
|> Array.collect (fun line -> line.Split(','))
|
||||||
|> Array.map (fun w -> w.Replace("\"", "").ToLower())
|
|> Array.map (fun w -> w.Replace("\"", "").ToLower())
|
||||||
|
```
|
||||||
|
|
||||||
This loads the line from the file, splits the words by commas, and removes the quotations… and I threw in making the words lowercase – since I hate having my puzzles constantly shouting at me. A little aside for the language particulars: the |> is the magical-looking-but-actually-quite-simple [pipeline operator](http://msdn.microsoft.com/en-us/magazine/cc164244.aspx#S6) – it passes the output from the function on the left to be input for the function on the right. And the fun –> is [F# syntax for lambda expressions](http://msdn.microsoft.com/en-us/library/dd233201.aspx). At the end of the pipeline comes a simple array containing the parsed words.
|
This loads the line from the file, splits the words by commas, and removes the quotations… and I threw in making the words lowercase – since I hate having my puzzles constantly shouting at me. A little aside for the language particulars: the `|>` is the magical-looking-but-actually-quite-simple [pipeline operator](http://msdn.microsoft.com/en-us/magazine/cc164244.aspx#S6) – it passes the output from the function on the left to be input for the function on the right. And the `fun –>` is [F# syntax for lambda expressions](http://msdn.microsoft.com/en-us/library/dd233201.aspx). At the end of the pipeline comes a simple array containing the parsed words.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
So for the rest of the input - the square numbers. F# has the means to generate an infinite sequence which can then be sliced and diced to get at the bits that are needed. The perfect squares can be generated with:let allSquares =
|
So for the rest of the input - the square numbers. F# has the means to generate an infinite sequence which can then be sliced and diced to get at the bits that are needed. The perfect squares can be generated with:
|
||||||
|
|
||||||
|
```
|
||||||
|
let allSquares =
|
||||||
Seq.unfold(fun (square,odd) -> Some(square, (square+odd, odd+2))) (0,1)
|
Seq.unfold(fun (square,odd) -> Some(square, (square+odd, odd+2))) (0,1)
|
||||||
|
```
|
||||||
|
|
||||||
The square numbers are “[unfolded](http://msdn.microsoft.com/en-us/library/ee340363.aspx)” – that is, each element is calculated based on the result from the previous element, like you’d do with. Square numbers have a [property where they can be generated by as the sum of a list of successive odd numbers](http://en.wikipedia.org/wiki/Square_number#Properties). The [tuple](http://msdn.microsoft.com/en-us/library/dd233200.aspx) of (0,1) on the far right is fed in as an initial value, with 0 being the first “square” number and 1 being the first odd number. At each step of the unfold, the odd number is increased by 2, and the square number is calculated by the adding the previous odd number. So, the (square,odd) tuples being computed will look like:(0,1),(1,3),(4,5),(9,7),(16,9), with only the squares being captured as output. Works for me!
|
The square numbers are “[unfolded](http://msdn.microsoft.com/en-us/library/ee340363.aspx)” – that is, each element is calculated based on the result from the previous element, like you’d do with. Square numbers have a [property where they can be generated by as the sum of a list of successive odd numbers](http://en.wikipedia.org/wiki/Square_number#Properties). The [tuple](http://msdn.microsoft.com/en-us/library/dd233200.aspx) of `(0,1)` on the far right is fed in as an initial value, with `0` being the first “square” number and `1` being the first odd number. At each step of the unfold, the odd number is increased by 2, and the square number is calculated by the adding the previous odd number. So, the (square,odd) tuples being computed will look like:`(0,1),(1,3),(4,5),(9,7),(16,9)`, with only the squares being captured as output. Works for me!
|
||||||
|
|
||||||
As with most simple-yet-effective bits of code, this one went through a couple of refinements before nailing it. The first pass looked like this:Seq.unfold(fun i -> Some(i, i + 1)) 0
|
As with most simple-yet-effective bits of code, this one went through a couple of refinements before nailing it. The first pass looked like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Seq.unfold(fun i -> Some(i, i + 1)) 0
|
||||||
|> Seq.map (fun i -> i*i)
|
|> Seq.map (fun i -> i*i)
|
||||||
|
```
|
||||||
|
|
||||||
…where the squares are generated in two steps instead of one. The first bit unfolds all the natural numbers from 0 to infinity (or more likely, maxint), and the second part maps these each by squaring them. It produces the same result, but is less elegant because it uses more moving pieces.
|
…where the squares are generated in two steps instead of one. The first bit unfolds all the natural numbers from 0 to infinity (or more likely, maxint), and the second part maps these each by squaring them. It produces the same result, but is less elegant because it uses more moving pieces.
|
||||||
|
|
||||||
@@ -51,14 +70,21 @@ That’s all the input this problem should require – next, to design the…
|
|||||||
|
|
||||||
### Success Algorithm
|
### Success Algorithm
|
||||||
|
|
||||||
A useful bit of the Project Euler problem descriptions is that they usually include an example which can be used as a test case for a solution algorithm. Here they’ve provided:care, race // <- anagrams map to
|
A useful bit of the Project Euler problem descriptions is that they usually include an example which can be used as a test case for a solution algorithm. Here they’ve provided:
|
||||||
1296, 9216 // <- squares
|
|
||||||
|
|
||||||
…with a replacement dictionary of (c,1),(a,2),(r,9),(e,6). It’s important to note that the anagrams and squares might be symmetric, where the numbers can be swapped (i.e. (c,9),(a,2),(r,1),(e,6) works as well), but there are cases (such as the final answer) where this doesn’t hold.
|
```
|
||||||
|
care, race // <- anagrams map to
|
||||||
|
1296, 9216 // <- squares
|
||||||
|
```
|
||||||
|
|
||||||
|
…with a replacement dictionary of `(c,1),(a,2),(r,9),(e,6)`. It’s important to note that the anagrams and squares might be symmetric, where the numbers can be swapped (i.e. `(c,9),(a,2),(r,1),(e,6)` works as well), but there are cases (such as the final answer) where this doesn’t hold.
|
||||||
|
|
||||||
To devise an algorithm to test for an answer, the first step is to think it over before doing any typing. It’s better to have some notion of a strategy than to rush in with the codes. I struggle with adhering to this – it’s just too easy to think “I’m so good, I’ll just figure it out as I program.” I’ve found it’s more productive to get away - get a pen and some paper and start sketching ideas, take a walk, etc. There’ll be plenty of time to work through all the details in code, but it’s best to go in with a plan – a vague or even an incorrect strategy is better than none at all.
|
To devise an algorithm to test for an answer, the first step is to think it over before doing any typing. It’s better to have some notion of a strategy than to rush in with the codes. I struggle with adhering to this – it’s just too easy to think “I’m so good, I’ll just figure it out as I program.” I’ve found it’s more productive to get away - get a pen and some paper and start sketching ideas, take a walk, etc. There’ll be plenty of time to work through all the details in code, but it’s best to go in with a plan – a vague or even an incorrect strategy is better than none at all.
|
||||||
|
|
||||||
For this problem, I started with the idea of comparing each letter of the first word with the first square number, building a replacement dictionary while iterating through them. If a valid replacement dictionary for the first set is found, it would then be tested against the second word-square-number pair. Writing in algorithm in F# meant I could treat the words as lists and iterate them using a common combination of recursive function calls and pattern matching.let rec getReplacementDictionary (sToT, tToS) (source, target) =
|
For this problem, I started with the idea of comparing each letter of the first word with the first square number, building a replacement dictionary while iterating through them. If a valid replacement dictionary for the first set is found, it would then be tested against the second word-square-number pair. Writing in algorithm in F# meant I could treat the words as lists and iterate them using a common combination of recursive function calls and pattern matching.
|
||||||
|
|
||||||
|
```
|
||||||
|
let rec getReplacementDictionary (sToT, tToS) (source, target) =
|
||||||
match source, target with
|
match source, target with
|
||||||
// have already seen this exact mapping -> skip it
|
// have already seen this exact mapping -> skip it
|
||||||
| s::ss, t::tt when Map.containsKey s sToT && (Map.find s sToT) = t
|
| s::ss, t::tt when Map.containsKey s sToT && (Map.find s sToT) = t
|
||||||
@@ -76,28 +102,45 @@ For this problem, I started with the idea of comparing each letter of the first
|
|||||||
| [], [] -> Some(sToT, tToS)
|
| [], [] -> Some(sToT, tToS)
|
||||||
| _ -> raise(System.ArgumentException("words not equal length"))
|
| _ -> raise(System.ArgumentException("words not equal length"))
|
||||||
|
|
||||||
I’ve named the variables “source” and “target” – they’ll get passed the word and the number, respectively. They are “matched” against conditions using the patterns seen on each line following the pipe “|” character. The easiest of these to grasp is near the end – “| [], [] –> Some(sToT, tToS)“ which matches two empty lists, indicating that the words have been completely checked and that there is a valid dictionary. For the dictionary itself, I found that it was necessary to keep tabs on both the source-to-target values (sToT) as well as the target-to-source (tToS) values. A bi-directional mapping structure would be ideal, but it would have been more effort to construct that than to fudge it with an extra variable. If there is a better way to handle this, I’d be interested to hear it…
|
```
|
||||||
|
|
||||||
At any rate, the result of this function will either be a failure - with the None value - or with the completed dictionary(s) – the Some(sToT, tToS). Testing it against “care” and “1296” produces the expect result:> getReplacementDictionary (Map.empty, Map.empty) ("care" |> List.ofSeq, "1296" |> List.ofSeq);;
|
I’ve named the variables “source” and “target” – they’ll get passed the word and the number, respectively. They are “matched” against conditions using the patterns seen on each line following the pipe “`|`” character. The easiest of these to grasp is near the end – “`| [], [] –> Some(sToT, tToS)`“ which matches two empty lists, indicating that the words have been completely checked and that there is a valid dictionary. For the dictionary itself, I found that it was necessary to keep tabs on both the source-to-target values (sToT) as well as the target-to-source (tToS) values. A bi-directional mapping structure would be ideal, but it would have been more effort to construct that than to fudge it with an extra variable. If there is a better way to handle this, I’d be interested to hear it…
|
||||||
|
|
||||||
|
At any rate, the result of this function will either be a failure - with the None value - or with the completed dictionary(s) – the Some(sToT, tToS). Testing it against “care” and “1296” produces the expect result:
|
||||||
|
|
||||||
|
```
|
||||||
|
> getReplacementDictionary (Map.empty, Map.empty) ("care" |> List.ofSeq, "1296" |> List.ofSeq);;
|
||||||
val it : (Map<char,char> * Map<char,char>) option =
|
val it : (Map<char,char> * Map<char,char>) option =
|
||||||
Some
|
Some
|
||||||
(map [('a', '2'); ('c', '1'); ('e', '6'); ('r', '9')],
|
(map [('a', '2'); ('c', '1'); ('e', '6'); ('r', '9')],
|
||||||
map [('1', 'c'); ('2', 'a'); ('6', 'e'); ('9', 'r')])
|
map [('1', 'c'); ('2', 'a'); ('6', 'e'); ('9', 'r')])
|
||||||
|
```
|
||||||
|
|
||||||
…and changing a digit in the number to one of the already mapped digits results (I’m using 1292 which repeats the 2) in a failure:> getReplacementDictionary (Map.empty, Map.empty) ("care" |> List.ofSeq, "1292" |> List.ofSeq);;
|
…and changing a digit in the number to one of the already mapped digits results (I’m using 1292 which repeats the 2) in a failure:
|
||||||
|
|
||||||
|
```
|
||||||
|
> getReplacementDictionary (Map.empty, Map.empty) ("care" |> List.ofSeq, "1292" |> List.ofSeq);;
|
||||||
val it : (Map<char,char> * Map<char,char>) option = None
|
val it : (Map<char,char> * Map<char,char>) option = None
|
||||||
|
```
|
||||||
|
|
||||||
To test this dictionary against the second group, “race” and “9216”, the original function may be reused because all of the character mappings will have been seen in the first group and they will simply be verified via the first pattern. let dict = getReplacementDictionary (Map.empty, Map.empty) ("care" |> List.ofSeq, "1296" |> List.ofSeq)
|
To test this dictionary against the second group, “race” and “9216”, the original function may be reused because all of the character mappings will have been seen in the first group and they will simply be verified via the first pattern.
|
||||||
|
|
||||||
|
```
|
||||||
|
let dict = getReplacementDictionary (Map.empty, Map.empty) ("care" |> List.ofSeq, "1296" |> List.ofSeq)
|
||||||
getReplacementDictionary dict.Value ("race" |> List.ofSeq, "9216" |> List.ofSeq);;
|
getReplacementDictionary dict.Value ("race" |> List.ofSeq, "9216" |> List.ofSeq);;
|
||||||
|
|
||||||
val dict : (Map<char,char> * Map<char,char>) option =
|
val dict : (Map<char,char> * Map<char,char>) option =
|
||||||
Some
|
Some
|
||||||
(map [('a', '2'); ('c', '1'); ('e', '6'); ('r', '9')],
|
(map [('a', '2'); ('c', '1'); ('e', '6'); ('r', '9')],
|
||||||
map [('1', 'c'); ('2', 'a'); ('6', 'e'); ('9', 'r')])
|
map [('1', 'c'); ('2', 'a'); ('6', 'e'); ('9', 'r')])
|
||||||
|
```
|
||||||
|
|
||||||
This is making an assumption that the input words are anagrams of each other – this is an OK assumption to make for now. Indeed, it can be even assumed that the input data may be filtered for anagrams – I’ll cover that in the next section.
|
This is making an assumption that the input words are anagrams of each other – this is an OK assumption to make for now. Indeed, it can be even assumed that the input data may be filtered for anagrams – I’ll cover that in the next section.
|
||||||
|
|
||||||
I noticed that this could be further streamlined by simply appending the words and the numbers together, since that is more-or-less what is occurring when calling it twice. And in that case, the function no longer needs to return the mapping dictionary – just a simple boolean indicating if a valid dictionary can be applied to the input. So, the function can be modified as follows, making sure to rename it:let rec hasReplacementDictionary (sToT, tToS) (source, target) =
|
I noticed that this could be further streamlined by simply appending the words and the numbers together, since that is more-or-less what is occurring when calling it twice. And in that case, the function no longer needs to return the mapping dictionary – just a simple boolean indicating if a valid dictionary can be applied to the input. So, the function can be modified as follows, making sure to rename it:
|
||||||
|
|
||||||
|
```
|
||||||
|
let rec hasReplacementDictionary (sToT, tToS) (source, target) =
|
||||||
match source, target with
|
match source, target with
|
||||||
| s::ss, t::tt when Map.containsKey s sToT && (Map.find s sToT) = t
|
| s::ss, t::tt when Map.containsKey s sToT && (Map.find s sToT) = t
|
||||||
-> hasReplacementDictionary (sToT, tToS) (ss, tt)
|
-> hasReplacementDictionary (sToT, tToS) (ss, tt)
|
||||||
@@ -109,13 +152,18 @@ I noticed that this could be further streamlined by simply appending the words a
|
|||||||
-> hasReplacementDictionary (Map.add s t sToT, Map.add t s tToS) (ss, tt)
|
-> hasReplacementDictionary (Map.add s t sToT, Map.add t s tToS) (ss, tt)
|
||||||
| [], [] -> true
|
| [], [] -> true
|
||||||
| _ -> raise(System.ArgumentException("words not equal length"))
|
| _ -> raise(System.ArgumentException("words not equal length"))
|
||||||
|
```
|
||||||
|
|
||||||
and run a test:> let los = List.ofSeq
|
and run a test:
|
||||||
|
|
||||||
|
```
|
||||||
|
> let los = List.ofSeq
|
||||||
let s = List.append (List.ofSeq "care") (List.ofSeq "race")
|
let s = List.append (List.ofSeq "care") (List.ofSeq "race")
|
||||||
let t = List.append (List.ofSeq "1296") (List.ofSeq "9216");;
|
let t = List.append (List.ofSeq "1296") (List.ofSeq "9216");;
|
||||||
|
|
||||||
> hasReplacementDictionary (Map.empty, Map.empty) (s, t);;
|
> hasReplacementDictionary (Map.empty, Map.empty) (s, t);;
|
||||||
val it : bool = true
|
val it : bool = true
|
||||||
|
```
|
||||||
|
|
||||||
Success! Now, having all the necessary input and a valid algorithm, it would be possible to test every possibly combination of inputs to find the correct answer.
|
Success! Now, having all the necessary input and a valid algorithm, it would be possible to test every possibly combination of inputs to find the correct answer.
|
||||||
|
|
||||||
@@ -125,15 +173,22 @@ Project Euler problems are usually infeasible to answer without a computer. (T
|
|||||||
|
|
||||||
How many combinations of the input data could there be, anyway? There’s a total of 1,786 words ranging in length from 1 to 14 characters, which leads to ~3.2 million combinations of just the words. My script to calculate the count of all square numbers under 14 digits ran out of memory, but it turns out only up to 9 digit squares are needed. There are 31,623 of those. The number of combinations would be 1,7862 × 31,6232 ≈ 3.2E15. That’s several thousand billion – got to keep filtering or it’ll take a year!
|
How many combinations of the input data could there be, anyway? There’s a total of 1,786 words ranging in length from 1 to 14 characters, which leads to ~3.2 million combinations of just the words. My script to calculate the count of all square numbers under 14 digits ran out of memory, but it turns out only up to 9 digit squares are needed. There are 31,623 of those. The number of combinations would be 1,7862 × 31,6232 ≈ 3.2E15. That’s several thousand billion – got to keep filtering or it’ll take a year!
|
||||||
|
|
||||||
The next obvious step is to pair up the anagrams and discard the rest of the words. A simple way to do this is to alphabetically sort each character in each word and group together the ones that are exactly the same. For example “care” and “race” will both map to “acer” – neat trick! Here’s the codelet groupAnagrams ws =
|
The next obvious step is to pair up the anagrams and discard the rest of the words. A simple way to do this is to alphabetically sort each character in each word and group together the ones that are exactly the same. For example “care” and “race” will both map to “acer” – neat trick! Here’s the code
|
||||||
|
|
||||||
|
```
|
||||||
|
let groupAnagrams ws =
|
||||||
ws
|
ws
|
||||||
// sort by characters in each string and then glue them back together
|
// sort by characters in each string and then glue them back together
|
||||||
|> Seq.groupBy (Array.ofSeq >> Array.sort >> (fun cs -> new string(cs)))
|
|> Seq.groupBy (Array.ofSeq >> Array.sort >> (fun cs -> new string(cs)))
|
||||||
// take only the anagrams - where the number of words is greater than one
|
// take only the anagrams - where the number of words is greater than one
|
||||||
|> Seq.map snd
|
|> Seq.map snd
|
||||||
|> Seq.filter (Seq.length >> ((<) 1))
|
|> Seq.filter (Seq.length >> ((<) 1))
|
||||||
|
```
|
||||||
|
|
||||||
This is a great first step – but it’s not quite enough. I want all the pairs of anagrams and there are cases where there are more than 2 words that all have the same letters. For example, “post”, “spot” and “stop” will all group together. What I need is combinations of all the pairs – so I’ll end up with (“post”, “spot”), (“spot”, stop”), and (“post”, “stop”). Fortunately, I had already borrowed/stolen a generic combination function that [Tomas had posted on Stack Overflow](http://stackoverflow.com/a/4495708/99492).let combinations size set =
|
This is a great first step – but it’s not quite enough. I want all the pairs of anagrams and there are cases where there are more than 2 words that all have the same letters. For example, “post”, “spot” and “stop” will all group together. What I need is combinations of all the pairs – so I’ll end up with (“post”, “spot”), (“spot”, stop”), and (“post”, “stop”). Fortunately, I had already borrowed/stolen a generic combination function that [Tomas had posted on Stack Overflow](http://stackoverflow.com/a/4495708/99492).
|
||||||
|
|
||||||
|
```
|
||||||
|
let combinations size set =
|
||||||
let rec combinations acc size set = seq {
|
let rec combinations acc size set = seq {
|
||||||
match size, set with
|
match size, set with
|
||||||
| n, x::xs ->
|
| n, x::xs ->
|
||||||
@@ -142,17 +197,27 @@ This is a great first step – but it’s not quite enough. I want all the pai
|
|||||||
| 0, [] -> yield acc
|
| 0, [] -> yield acc
|
||||||
| _, [] -> () }
|
| _, [] -> () }
|
||||||
combinations [] size (set |> List.ofSeq)
|
combinations [] size (set |> List.ofSeq)
|
||||||
|
```
|
||||||
|
|
||||||
Now I can run all the whole list of anagram groups through the combinations algorithm and produce a single list of anagram pairs:let pairwiseTuples =
|
Now I can run all the whole list of anagram groups through the combinations algorithm and produce a single list of anagram pairs:
|
||||||
|
|
||||||
|
```
|
||||||
|
let pairwiseTuples =
|
||||||
Seq.collect (
|
Seq.collect (
|
||||||
combinations 2
|
combinations 2
|
||||||
>> Seq.map (fun l -> l.[0], l.[1])
|
>> Seq.map (fun l -> l.[0], l.[1])
|
||||||
)
|
)
|
||||||
|
|
||||||
And indeed – it does its job:[|("cat", "act"); ... ("race", "care");
|
```
|
||||||
|
|
||||||
|
And indeed – it does its job:
|
||||||
|
|
||||||
|
```
|
||||||
|
[|("cat", "act"); ... ("race", "care");
|
||||||
...
|
...
|
||||||
("spot", "post"); ("stop", "post"); ("stop", "spot");
|
("spot", "post"); ("stop", "post"); ("stop", "spot");
|
||||||
...|]
|
...|]
|
||||||
|
```
|
||||||
|
|
||||||
Running the words through this results in 44 pairs of anagrams with the lengthiest pair being 9 letters long ("reduction", "introduce"). This is starting to sound reasonable to process – and it’s where I got stuck for a bit. Looping through all of the squares for each pair still seems computationally prohibitive.
|
Running the words through this results in 44 pairs of anagrams with the lengthiest pair being 9 letters long ("reduction", "introduce"). This is starting to sound reasonable to process – and it’s where I got stuck for a bit. Looping through all of the squares for each pair still seems computationally prohibitive.
|
||||||
|
|
||||||
|
|||||||
@@ -5,19 +5,23 @@ slug: full-on-rainbow-spirograph-using-html5-and-coffeescript
|
|||||||
published: true
|
published: true
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
So intense. We've heard how HTML5's canvas element will provide device-independent in-browser graphics – and now I want a taste! After seeing some [magnificent](http://www.spielzeugz.de/html5/liquid-particles.html) [demos](http://peterned.home.xs4all.nl/demooo/), I started wondering what the plumbing looks like. I haven't touched any graphical code since maybe 1994 in [QBasic](http://en.wikipedia.org/wiki/QBASIC), so come along with me as I learn.
|
So intense. We've heard how HTML5's canvas element will provide device-independent in-browser graphics – and now I want a taste! After seeing some [magnificent](http://www.spielzeugz.de/html5/liquid-particles.html) [demos](http://peterned.home.xs4all.nl/demooo/), I started wondering what the plumbing looks like. I haven't touched any graphical code since maybe 1994 in [QBasic](http://en.wikipedia.org/wiki/QBASIC), so come along with me as I learn.
|
||||||
|
|
||||||
Also, I'm giving [CoffeeScript](http://coffeescript.org/) a spin. I saw a presentation by [Daniel Mohl](https://twitter.com/#!/dmohl) at [Codestock](http://codestock.org/) a few weeks ago and thought it was fantastic. You write in a terse-clean-"modern" language and get the functionally equivalent JavaScript out the other end. So long to writing function(){...} every three lines! Much of the syntax is based on Ruby, so it's an easy language to read - I'll comment on any syntax that isn't immediately obvious.
|
Also, I'm giving [CoffeeScript](http://coffeescript.org/) a spin. I saw a presentation by [Daniel Mohl](https://twitter.com/#!/dmohl) at [Codestock](http://codestock.org/) a few weeks ago and thought it was fantastic. You write in a terse-clean-"modern" language and get the functionally equivalent JavaScript out the other end. So long to writing `function(){...}` every three lines! Much of the syntax is based on Ruby, so it's an easy language to read - I'll comment on any syntax that isn't immediately obvious.
|
||||||
|
|
||||||
But the important question is - what to draw? I follow a few graphic design blogs and remember seeing some neat [geometric patterns](http://veerle.duoh.com/design/article/illustrator_full_spectrum_spirograph) on [veerle's blog](http://veerle.duoh.com) done in Adobe Illustrator. These were inspired by the kaleidoscopic artwork of [Andy Gilmore](http://crowquills.com/) – check them out, some of the pieces are kind of hypnotic. The specific creation that motivated Veerle's tutorial can be [seen here](http://crowquills.com/1061691/06-30-2008). It has a beautiful simplicity. As Veerle mentions - we don't want to make an exact replica, but instead use some of the patterns as a starting point of something original.
|
But the important question is - what to draw? I follow a few graphic design blogs and remember seeing some neat [geometric patterns](http://veerle.duoh.com/design/article/illustrator_full_spectrum_spirograph) on [veerle's blog](http://veerle.duoh.com) done in Adobe Illustrator. These were inspired by the kaleidoscopic artwork of [Andy Gilmore](http://crowquills.com/) – check them out, some of the pieces are kind of hypnotic. The specific creation that motivated Veerle's tutorial can be [seen here](http://crowquills.com/1061691/06-30-2008). It has a beautiful simplicity. As Veerle mentions - we don't want to make an exact replica, but instead use some of the patterns as a starting point of something original.
|
||||||
|
|
||||||
|
The absolute first step is to draw a circle, just like it was in the Illustrator tutorial by Veerle. Well, part of a circle. I found it easiest to get the desired results using an "arc". This is a term that simply means "a specific section along the side of the circle". What this entails for the HTML5 canvas is specifying the center point of the circle, the radius, and the angles at the start and end the arc. My trigonometry knowledge has deteriorated over the years (if it ever existed to begin with) – but this is pretty simple. The right-most point on the circle has an angle of 0π, travelling clockwise to the bottom-most point is .5π, the left-most has 1π, up to the top at 1.5π, and a full circle is (of course) 2π. To draw a full circle, you'd type in `context.arc(0,0,radius,0,Math.PI * 2)`. *Note: the angles I learned in school were oriented counterclockwise, yet most of the examples I've seen for HTML5 canvas are clockwise – most likely because the y-axis increases in a downward direction instead of up. I'll keep it clockwise here.*
|
||||||
|
|
||||||
The absolute first step is to draw a circle, just like it was in the Illustrator tutorial by Veerle. Well, part of a circle. I found it easiest to get the desired results using an "arc". This is a term that simply means "a specific section along the side of the circle". What this entails for the HTML5 canvas is specifying the center point of the circle, the radius, and the angles at the start and end the arc. My trigonometry knowledge has deteriorated over the years (if it ever existed to begin with) – but this is pretty simple. The right-most point on the circle has an angle of 0π, travelling clockwise to the bottom-most point is .5π, the left-most has 1π, up to the top at 1.5π, and a full circle is (of course) 2π. To draw a full circle, you'd type in context.arc(0,0,radius,0,Math.PI * 2). *Note: the angles I learned in school were oriented counterclockwise, yet most of the examples I've seen for HTML5 canvas are clockwise – most likely because the y-axis increases in a downward direction instead of up. I'll keep it clockwise here.*
|
|
||||||
### Access the HTML5 Canvas using CoffeeScript
|
### Access the HTML5 Canvas using CoffeeScript
|
||||||
|
|
||||||
|
|
||||||
But what's this context, you ask? I might have lied about the first step – the super-absolute first step will be to set up the HTML5 canvas object so that we can draw on it. Here's a complete example of a using CoffeeScript in a script tag with the in-browser compiler to accomplish this.<!DOCTYPE html>
|
But what's this context, you ask? I might have lied about the first step – the super-absolute first step will be to set up the HTML5 canvas object so that we can draw on it. Here's a complete example of a using CoffeeScript in a script tag with the in-browser compiler to accomplish this.
|
||||||
|
|
||||||
|
```
|
||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Full on rainbow Spirograph</title>
|
<title>Full on rainbow Spirograph</title>
|
||||||
@@ -50,9 +54,14 @@ But what's this context, you ask? I might have lied about the first step – the
|
|||||||
<!-- https://github.com/jashkenas/coffee-script/raw/master/extras/coffee-script.js -->
|
<!-- https://github.com/jashkenas/coffee-script/raw/master/extras/coffee-script.js -->
|
||||||
<!-- coffee-script.js goes AFTER all the CoffeeScript. -->
|
<!-- coffee-script.js goes AFTER all the CoffeeScript. -->
|
||||||
<script type="text/javascript" src="scripts/coffee-script.js"></script>
|
<script type="text/javascript" src="scripts/coffee-script.js"></script>
|
||||||
</html>
|
</html>
|
||||||
|
```
|
||||||
|

|
||||||
|
|
||||||
This generates a page containing a dark 300x300 rectangle with "#myCanvas" written in white. This is great for kicking the tires, but the script is getting compiled down to JavaScript each time the page loads. It's preferable to compile it ahead of time. With a little tooling this process becomes nearly transparent. In Visual Studio a great option is the [Web Workbench extension by Mindscape](http://www.mindscapehq.com/products/web-workbench). It automatically compiles your CoffeeScript (and [Sass](http://sass-lang.com/) and [LESS](http://lesscss.org/)) scripts each time the file is saved. After making this change, the code now looks like...<!DOCTYPE html>
|
This generates a page containing a dark 300x300 rectangle with "#myCanvas" written in white. This is great for kicking the tires, but the script is getting compiled down to JavaScript each time the page loads. It's preferable to compile it ahead of time. With a little tooling this process becomes nearly transparent. In Visual Studio a great option is the [Web Workbench extension by Mindscape](http://www.mindscapehq.com/products/web-workbench). It automatically compiles your CoffeeScript (and [Sass](http://sass-lang.com/) and [LESS](http://lesscss.org/)) scripts each time the file is saved. After making this change, the code now looks like...
|
||||||
|
|
||||||
|
```
|
||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Full on rainbow Spirograph</title>
|
<title>Full on rainbow Spirograph</title>
|
||||||
@@ -63,6 +72,9 @@ This generates a page containing a dark 300x300 rectangle with "#myCanvas" writt
|
|||||||
<canvas id="myCanvas"></canvas>
|
<canvas id="myCanvas"></canvas>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
# CoffeeScript_Compiled.coffee
|
# CoffeeScript_Compiled.coffee
|
||||||
canvasSize = 300
|
canvasSize = 300
|
||||||
|
|
||||||
@@ -78,6 +90,9 @@ $ ->
|
|||||||
ctx.fillStyle = 'white'
|
ctx.fillStyle = 'white'
|
||||||
ctx.fillText("#myCanvas", 80, 150)
|
ctx.fillText("#myCanvas", 80, 150)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
// CoffeeScript_Compiled.js
|
// CoffeeScript_Compiled.js
|
||||||
(function() {
|
(function() {
|
||||||
var canvasSize;
|
var canvasSize;
|
||||||
@@ -97,10 +112,14 @@ $ ->
|
|||||||
});
|
});
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
```
|
||||||
|
|
||||||
### Circular
|
### Circular
|
||||||
|
|
||||||
Now that the canvas is ready to draw on – let's get back to that arc. Like from the Illustrator tutorial, I'm aiming to make a leaf-like object that can be duplicated around a central axis. First I'll create the right side of the leaf.# cache the math stuff
|
Now that the canvas is ready to draw on – let's get back to that arc. Like from the Illustrator tutorial, I'm aiming to make a leaf-like object that can be duplicated around a central axis. First I'll create the right side of the leaf.
|
||||||
|
|
||||||
|
```
|
||||||
|
# cache the math stuff
|
||||||
pi = Math.PI
|
pi = Math.PI
|
||||||
cos = Math.cos
|
cos = Math.cos
|
||||||
sin = Math.sin
|
sin = Math.sin
|
||||||
@@ -123,43 +142,62 @@ $ ->
|
|||||||
# fill it in with a color
|
# fill it in with a color
|
||||||
ctx.fillStyle = '#fdf6e3'
|
ctx.fillStyle = '#fdf6e3'
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
|
```
|
||||||
|
|
||||||
The first bits are again to set up the canvas context – I'll omit those from now on. The first line of interest contains the [translate](http://www.html5canvastutorials.com/advanced/html5-canvas-transform-translate-tutorial/) function – this reorients the context to the specified point. Anything drawn on the canvas afterwards will treat this point as the center. Here I pass in size/2, size/2 - which makes the center of the canvas the 0,0 coordinate.
|
The first bits are again to set up the canvas context – I'll omit those from now on. The first line of interest contains the `[translate](http://www.html5canvastutorials.com/advanced/html5-canvas-transform-translate-tutorial/)` function – this reorients the context to the specified point. Anything drawn on the canvas afterwards will treat this point as the center. Here I pass in `size/2, size/2` - which makes the center of the canvas the `0,0` coordinate.
|
||||||
|
|
||||||
The next line computes a radius based off the size of the canvas – I simply did a fourth of the canvas dimensions so that it would fit with room to spare.
|
The next line computes a radius based off the size of the canvas – I simply did a fourth of the canvas dimensions so that it would fit with room to spare.
|
||||||
|
|
||||||
Next comes the definition of the arc's shape – or the path that the arc will make. The first step is a call to [beginPath](http://www.html5canvastutorials.com/tutorials/html5-canvas-paths/)(). This is done so that the first point of the arc it is drawn as the beginning of the shape. If I had drawn anything else on the canvas before this (which I actually have – more on that later), it would continue the previous path, drawing a line between that last piece and the arc. I've found it's easier to explicitly begin and close the path rather than to assume it's a fresh canvas – especially when you start composing more complex drawings.
|
Next comes the definition of the arc's shape – or the path that the arc will make. The first step is a call to `[beginPath](http://www.html5canvastutorials.com/tutorials/html5-canvas-paths/)()`. This is done so that the first point of the arc it is drawn as the beginning of the shape. If I had drawn anything else on the canvas before this (which I actually have – more on that later), it would continue the previous path, drawing a line between that last piece and the arc. I've found it's easier to explicitly begin and close the path rather than to assume it's a fresh canvas – especially when you start composing more complex drawings.
|
||||||
|
|
||||||
Finally – the actual call to [arc](http://www.html5canvastutorials.com/tutorials/html5-canvas-arcs/)! Since we've translated the canvas to center point we're passing in the 0,0 coordinate so that the circle gets drawn in the exact middle. Next comes the radius variable which we've already defined. Now comes the tricky bit – the beginning and end angles of the arc. Going back to the circle schematic – I'm defining the beginning point slightly past the top (to the right), and the end point slightly before the bottom (to the left). By "slightly", I mean by 0.2 for each side. Multiply these values by π, and it's ready to be drawn! Oh – and the final parameter indicates that it should be drawn counter-clockwise. Completely assembled, the call looks like arc(0, 0, radius, 1.7 * pi, .3 * pi, false)
|
Finally – the actual call to `[arc](http://www.html5canvastutorials.com/tutorials/html5-canvas-arcs/)`! Since we've translated the canvas to center point we're passing in the `0,0` coordinate so that the circle gets drawn in the exact middle. Next comes the radius variable which we've already defined. Now comes the tricky bit – the beginning and end angles of the arc. Going back to the circle schematic – I'm defining the beginning point slightly past the top (to the right), and the end point slightly before the bottom (to the left). By "slightly", I mean by `0.2` for each side. Multiply these values by π, and it's ready to be drawn! Oh – and the final parameter indicates that it should be drawn counter-clockwise. Completely assembled, the call looks like `arc(0, 0, radius, 1.7 * pi, .3 * pi, false)`
|
||||||
|
|
||||||
Finally, a fillStyle is specified as a color using hex RGB and the arc is drawn by calling [fill](http://www.html5canvastutorials.com/tutorials/html5-canvas-shape-fill/)(). Voila!Arc
|
Finally, a `fillStyle` is specified as a color using hex RGB and the arc is drawn by calling `[fill](http://www.html5canvastutorials.com/tutorials/html5-canvas-shape-fill/)()`. Voila!Arc
|
||||||
|
|
||||||
Well... that seems like a lot of work to draw something so trivial! It'll get more interesting in a little bit...
|
Well... that seems like a lot of work to draw something so trivial! It'll get more interesting in a little bit...
|
||||||
|
|
||||||
### Leafy
|
### Leafy
|
||||||
|
|
||||||
The first half of the leaf has been drawn – now for the other half. There are a number of ways to go about this – I'll go over a few that I've found. In this first pass, we'll simply draw another arc on the opposite side of the circle.ctx.beginPath()
|
The first half of the leaf has been drawn – now for the other half. There are a number of ways to go about this – I'll go over a few that I've found. In this first pass, we'll simply draw another arc on the opposite side of the circle.
|
||||||
|
|
||||||
|
```
|
||||||
|
ctx.beginPath()
|
||||||
ctx.arc(0, 0, radius, 1.7 * pi, .3 * pi, false)
|
ctx.arc(0, 0, radius, 1.7 * pi, .3 * pi, false)
|
||||||
ctx.arc(0, 0, radius, .7 * pi, 1.3 * pi, false)
|
ctx.arc(0, 0, radius, .7 * pi, 1.3 * pi, false)
|
||||||
ctx.closePath()Double Arc
|
ctx.closePath()
|
||||||
|
```
|
||||||
|
Double Arc
|
||||||
|
|
||||||
Ok – not a bad start. This time I draw the same arc on the right as before and then continue on with a second call to arc on the left side of the circle – arc(0, 0, radius, .7 * pi, 1.3 * pi, false). The only differences are the start and end angle. Going clockwise, the first arc goes from 1.7π to .3π, and then the second arc continues at .7π and ends at 1.3π. Filling in this path produces the shape seen above.
|
Ok – not a bad start. This time I draw the same arc on the right as before and then continue on with a second call to `arc` on the left side of the circle – `arc(0, 0, radius, .7 * pi, 1.3 * pi, false)`. The only differences are the start and end angle. Going clockwise, the first arc goes from 1.7π to .3π, and then the second arc continues at .7π and ends at 1.3π. Filling in this path produces the shape seen above.
|
||||||
|
|
||||||
The obvious problem is all that extra space in the middle of the circle – it's definitely not leafy so it's got to go! I'll start over with just the arc on the right side. To allow us to easily figure out where the leaf will go, it'll be good to have an easy-to-spot anchor point. For this, I chose the bottom bottom-most point of the leaf which which will be located at 0,0. All that's needed is a bit of maths.
|
The obvious problem is all that extra space in the middle of the circle – it's definitely not leafy so it's got to go! I'll start over with just the arc on the right side. To allow us to easily figure out where the leaf will go, it'll be good to have an easy-to-spot anchor point. For this, I chose the bottom bottom-most point of the leaf which which will be located at `0,0`. All that's needed is a bit of maths.
|
||||||
|
|
||||||
It's clear to see that the bottom-most point of the arc is below-and-right to the canvas center point. To do this, we'll move the coordinates for the center of the circle up and to the left. But how much? To calculate, we'll use a little trig. The coordinates on a circle can be calculated, given the angle θ, with x = cos(θ) and y = sin(θ). So, the offset for x will be -cos(.3π) * radius and for y will be -sin(.3π) * radius...ctx.arc(
|
It's clear to see that the bottom-most point of the arc is below-and-right to the canvas center point. To do this, we'll move the coordinates for the center of the circle up and to the left. But how much? To calculate, we'll use a little trig. The coordinates on a circle can be calculated, given the angle θ, with `x = cos(θ)` and `y = sin(θ)`. So, the offset for x will be `-cos(.3π) * radius` and for y will be `-sin(.3π) * radius`...
|
||||||
|
|
||||||
|
```
|
||||||
|
ctx.arc(
|
||||||
-cos(.3 * pi) * radius,
|
-cos(.3 * pi) * radius,
|
||||||
-sin(.3 * pi) * radius,
|
-sin(.3 * pi) * radius,
|
||||||
radius, 1.7 * pi, .3 * pi, false)Arc with Bottom at Origin
|
radius, 1.7 * pi, .3 * pi, false)
|
||||||
|
```
|
||||||
|
Arc with Bottom at Origin
|
||||||
|
|
||||||
Success! Now the same process for the left-side...ctx.arc(
|
Success! Now the same process for the left-side...
|
||||||
|
|
||||||
|
```
|
||||||
|
ctx.arc(
|
||||||
-cos(.7 * pi) * radius,
|
-cos(.7 * pi) * radius,
|
||||||
-sin(.7 * pi) * radius,
|
-sin(.7 * pi) * radius,
|
||||||
radius, .7 * pi, 1.3 * pi, false)Leaf
|
radius, .7 * pi, 1.3 * pi, false)
|
||||||
|
```
|
||||||
|
Leaf
|
||||||
|
|
||||||
### Rotate
|
### Rotate
|
||||||
|
|
||||||
Another way to skin this cat is to use the rotate() function to spin the canvas around. Now I'll try drawing the first arc, flipping the context, and then drawing the exact same arc.drawArc = ->
|
Another way to skin this cat is to use the `rotate()` function to spin the canvas around. Now I'll try drawing the first arc, flipping the context, and then drawing the exact same arc.
|
||||||
|
|
||||||
|
```
|
||||||
|
drawArc = ->
|
||||||
ctx.arc(
|
ctx.arc(
|
||||||
-cos(.3 * pi) * radius,
|
-cos(.3 * pi) * radius,
|
||||||
-sin(.3 * pi) * radius,
|
-sin(.3 * pi) * radius,
|
||||||
@@ -169,9 +207,14 @@ ctx.beginPath()
|
|||||||
drawArc()
|
drawArc()
|
||||||
ctx.rotate(pi) # rotate by half a circle
|
ctx.rotate(pi) # rotate by half a circle
|
||||||
drawArc()
|
drawArc()
|
||||||
ctx.closePath()Rotated Arc
|
ctx.closePath()
|
||||||
|
```
|
||||||
|
Rotated Arc
|
||||||
|
|
||||||
Kind of neat looking – but it obviously didn't turn out quite right. It rotated around the center and ended up both vertically and horizontally on the opposite side. This can be fixed with some additional translation...startAngle = 1.7 * pi
|
Kind of neat looking – but it obviously didn't turn out quite right. It rotated around the center and ended up both vertically and horizontally on the opposite side. This can be fixed with some additional translation...
|
||||||
|
|
||||||
|
```
|
||||||
|
startAngle = 1.7 * pi
|
||||||
endAngle = .3 * pi
|
endAngle = .3 * pi
|
||||||
|
|
||||||
drawArc = -> ctx.arc(0, 0, radius, startAngle, endAngle, false)
|
drawArc = -> ctx.arc(0, 0, radius, startAngle, endAngle, false)
|
||||||
@@ -182,11 +225,16 @@ drawArc()
|
|||||||
ctx.rotate(pi)
|
ctx.rotate(pi)
|
||||||
ctx.translate(-cos(endAngle) * radius * 2, 0)
|
ctx.translate(-cos(endAngle) * radius * 2, 0)
|
||||||
drawArc()
|
drawArc()
|
||||||
ctx.closePath()Rotated Arc for Leaf
|
ctx.closePath()
|
||||||
|
```
|
||||||
|
Rotated Arc for Leaf
|
||||||
|
|
||||||
This time I'm drawing the arc at 0,0 and doing a translate before the first arc, and a rotate and translate before the second arc. It turns out to look the same the leaf before, but the code gets a bit more muddy. I prefer the previous implementation if this is what it takes. Also notice that I've added a few variables to capture the start and end angles – this will be important in just a little bit...
|
This time I'm drawing the arc at `0,0` and doing a `translate` before the first arc, and a `rotate` and `translate` before the second arc. It turns out to look the same the leaf before, but the code gets a bit more muddy. I prefer the previous implementation if this is what it takes. Also notice that I've added a few variables to capture the start and end angles – this will be important in just a little bit...
|
||||||
|
|
||||||
But first, you may have noticed that the height of the leaf, from bottom to top, is *some* length, but *what* exact length is not immediately obvious. It'd be helpful to be able to define the leaf by a given height...height = size/2
|
But first, you may have noticed that the height of the leaf, from bottom to top, is *some* length, but *what* exact length is not immediately obvious. It'd be helpful to be able to define the leaf by a given height...
|
||||||
|
|
||||||
|
```
|
||||||
|
height = size/2
|
||||||
arcDelta = .2
|
arcDelta = .2
|
||||||
arcAngles =
|
arcAngles =
|
||||||
start: (1.5 + arcDelta) * pi,
|
start: (1.5 + arcDelta) * pi,
|
||||||
@@ -208,9 +256,11 @@ ctx.arc(
|
|||||||
-sin(arcAngles.start) * radius,
|
-sin(arcAngles.start) * radius,
|
||||||
radius, arcAngles.start, arcAngles.end, false)
|
radius, arcAngles.start, arcAngles.end, false)
|
||||||
|
|
||||||
ctx.closePath()Leaf using height
|
ctx.closePath()
|
||||||
|
```
|
||||||
|
Leaf using height
|
||||||
|
|
||||||
So, to compute the desired radius given for a given height, you can divide that height by the ratio between the center point and the leaf's bottom, sin(endPoint), multiplied by 2 to account for the top half: radius = height / (sin(arcAngles.end) * 2)
|
So, to compute the desired radius given for a given height, you can divide that height by the ratio between the center point and the leaf's bottom, sin(endPoint), multiplied by 2 to account for the top half: `radius = height / (sin(arcAngles.end) * 2)`
|
||||||
|
|
||||||
Also note that I'm trying the rotate again, except I'm just repositioning the center point of the second arc instead doing that messy translate. This allows the leaf to be defined by only one set of start and end angles which get reused in the second arc.
|
Also note that I'm trying the rotate again, except I'm just repositioning the center point of the second arc instead doing that messy translate. This allows the leaf to be defined by only one set of start and end angles which get reused in the second arc.
|
||||||
|
|
||||||
@@ -218,7 +268,10 @@ Finally, I've added another variable, arcDelta, to capture the offset between th
|
|||||||
|
|
||||||
### Composing
|
### Composing
|
||||||
|
|
||||||
Now to the fun stuff – the process to draw a leaf can now be encapsulated and we can start drawing neat patterns. Let's make a class for the leaf.class Leaf
|
Now to the fun stuff – the process to draw a leaf can now be encapsulated and we can start drawing neat patterns. Let's make a class for the leaf.
|
||||||
|
|
||||||
|
```
|
||||||
|
class Leaf
|
||||||
constructor: (height, @fillStyle = '#fdf6e3', arcDelta = .2) ->
|
constructor: (height, @fillStyle = '#fdf6e3', arcDelta = .2) ->
|
||||||
@arcAngles =
|
@arcAngles =
|
||||||
start: (1.5 + arcDelta) * pi,
|
start: (1.5 + arcDelta) * pi,
|
||||||
@@ -247,7 +300,11 @@ Now to the fun stuff – the process to draw a leaf can now be encapsulated and
|
|||||||
ctx.fillStyle = @fillStyle
|
ctx.fillStyle = @fillStyle
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
|
|
||||||
ctx.restore() var Leaf;
|
ctx.restore()
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
var Leaf;
|
||||||
Leaf = (function() {
|
Leaf = (function() {
|
||||||
|
|
||||||
Leaf.name = 'Leaf';
|
Leaf.name = 'Leaf';
|
||||||
@@ -279,17 +336,26 @@ Now to the fun stuff – the process to draw a leaf can now be encapsulated and
|
|||||||
return Leaf;
|
return Leaf;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
I've added the compiled JavaScript for comparison to the right. This is where [CoffeeScript](http://coffeescript.org) starts to spread its wings. The [class definition](http://coffeescript.org/#classes) is much neater – the prototype functions are taken care of, the this repetitive-keystroke-injury-waiting-to-happen is replaced with @, you've got default values for arguments – and this is just scratching the surface. So, now that we can easily draw some leaves...ctx.translate(size/2, size) #bottom center
|
I've added the compiled JavaScript for comparison to the right. This is where [CoffeeScript](http://coffeescript.org) starts to spread its wings. The [class definition](http://coffeescript.org/#classes) is much neater – the prototype functions are taken care of, the `this` repetitive-keystroke-injury-waiting-to-happen is replaced with `@`, you've got default values for arguments – and this is just scratching the surface. So, now that we can easily draw some leaves...
|
||||||
|
|
||||||
|
```
|
||||||
|
ctx.translate(size/2, size) #bottom center
|
||||||
leaves = [
|
leaves = [
|
||||||
new Leaf(size, '#c3d5eb')
|
new Leaf(size, '#c3d5eb')
|
||||||
new Leaf(size/9 * 8, '#648dcf', .25)
|
new Leaf(size/9 * 8, '#648dcf', .25)
|
||||||
new Leaf(size/5 * 4, '#12204d', .3)
|
new Leaf(size/5 * 4, '#12204d', .3)
|
||||||
new Leaf(size/2, 'white', .35)
|
new Leaf(size/2, 'white', .35)
|
||||||
]
|
]
|
||||||
leaf.draw() for leaf in leavesLeaf Object
|
leaf.draw() for leaf in leaves
|
||||||
|
```
|
||||||
|
Leaf Object
|
||||||
|
|
||||||
Now we're cooking with gas! (badum-ching - I'll be here all week) The next step is to draw the spirograph from the Illustrator tutorial...class SpiroLeaves
|
Now we're cooking with gas! (badum-ching - I'll be here all week) The next step is to draw the spirograph from the Illustrator tutorial...
|
||||||
|
|
||||||
|
```
|
||||||
|
class SpiroLeaves
|
||||||
constructor: (leafCount, radius, arcDelta = 1/10) ->
|
constructor: (leafCount, radius, arcDelta = 1/10) ->
|
||||||
@rotateAngle = (pi*2)/leafCount
|
@rotateAngle = (pi*2)/leafCount
|
||||||
hsla = (i) -> "hsla(#{i/leafCount*360}, 100%, 50%, .2)"
|
hsla = (i) -> "hsla(#{i/leafCount*360}, 100%, 50%, .2)"
|
||||||
@@ -306,23 +372,28 @@ Now we're cooking with gas! (badum-ching - I'll be here all week) The next ste
|
|||||||
|
|
||||||
ctx.globalCompositeOperation = "lighter"
|
ctx.globalCompositeOperation = "lighter"
|
||||||
spiroLeaves = new SpiroLeaves(18, size / 2)
|
spiroLeaves = new SpiroLeaves(18, size / 2)
|
||||||
spiroLeaves.draw(ctx)Full on rainbow spirograph
|
spiroLeaves.draw(ctx)
|
||||||
|
```
|
||||||
|
Full on rainbow spirograph
|
||||||
|
|
||||||
A few notes...
|
A few notes...
|
||||||
|
|
||||||
- The SpiroLeaves constructor takes...
|
- The SpiroLeaves constructor takes...
|
||||||
|
|
||||||
- leafCount – which is how many leaves you want to be drawn... it is used to calculate the angle to rotate between each leaf - @rotateAngle = (pi*2)/leafCount
|
- leafCount – which is how many leaves you want to be drawn... it is used to calculate the angle to rotate between each leaf - `@rotateAngle = (pi*2)/leafCount`
|
||||||
|
|
||||||
- radius – which is simply passed as the height for each leaf
|
- radius – which is simply passed as the height for each leaf
|
||||||
|
|
||||||
- arcDelta – which how "skinny" or "squat" the leaves will be. Can be values between 0 and .5, with the larger the value, the skinnier.
|
- arcDelta – which how "skinny" or "squat" the leaves will be. Can be values between 0 and .5, with the larger the value, the skinnier.
|
||||||
|
|
||||||
- The hue of each leaf is calculated this function - hsla = (i) -> "hsla(#{i/leafCount*360}, 100%, 50%, .2)". This produces a gradient hue between 0 and 360, with 100% saturation, 50% lightness, and an alpha value of .2. Read more [here at w3.org](http://www.w3.org/wiki/CSS/Properties/color/HSLA).
|
- The hue of each leaf is calculated this function - `hsla = (i) -> "hsla(#{i/leafCount*360}, 100%, 50%, .2)"`. This produces a gradient hue between 0 and 360, with 100% saturation, 50% lightness, and an alpha value of .2. Read more [here at w3.org](http://www.w3.org/wiki/CSS/Properties/color/HSLA).
|
||||||
|
|
||||||
- Before drawing, the context is set to [globalCompositeOperation](http://mudcu.be/journal/2011/04/globalcompositeoperation/) = "lighter" so that the colors in overlapping leaves will reinforce each other.
|
- Before drawing, the context is set to `[globalCompositeOperation](http://mudcu.be/journal/2011/04/globalcompositeoperation/) = "lighter"` so that the colors in overlapping leaves will reinforce each other.
|
||||||
|
|
||||||
Finally – let's do something really cool and animate this. I'll draw two overlapping copies of the SpiroLeaves slowly rotating in opposite directions.spiroLeaves = new SpiroLeaves(18, size)
|
Finally – let's do something really cool and animate this. I'll draw two overlapping copies of the SpiroLeaves slowly rotating in opposite directions.
|
||||||
|
|
||||||
|
```
|
||||||
|
spiroLeaves = new SpiroLeaves(18, size)
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
drawFrame = ->
|
drawFrame = ->
|
||||||
@@ -343,17 +414,26 @@ drawFrame = ->
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
setInterval drawFrame, 25Spin It
|
setInterval drawFrame, 25
|
||||||
|
```
|
||||||
|
Spin It
|
||||||
|
|
||||||
Nice! I've made a [jsfiddle](http://jsfiddle.net) with this code setup – feel free to tinker and make something of your own. [Here's the link](http://jsfiddle.net/poprhythm/g2WMu/). Also, if you'd like to bring your CPU to its knees, make the canvas full screen.
|
Nice! I've made a [jsfiddle](http://jsfiddle.net) with this code setup – feel free to tinker and make something of your own. [Here's the link](http://jsfiddle.net/poprhythm/g2WMu/). Also, if you'd like to bring your CPU to its knees, make the canvas full screen.
|
||||||
|
|
||||||
This is my first foray into both HTM5 Canvas and CoffeeScript and I'm simply sharing what I've learned. So, please feel free to make any corrections and/or suggestions.
|
This is my first foray into both HTM5 Canvas and CoffeeScript and I'm simply sharing what I've learned. So, please feel free to make any corrections and/or suggestions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
HTML5 Canvas resources: [HTML5 Canvas Tutorials](http://www.html5canvastutorials.com/), [HTML5 Canvas Cheat Sheet](http://blog.nihilogic.dk/2009/02/html5-canvas-cheat-sheet.html)
|
HTML5 Canvas resources: [HTML5 Canvas Tutorials](http://www.html5canvastutorials.com/), [HTML5 Canvas Cheat Sheet](http://blog.nihilogic.dk/2009/02/html5-canvas-cheat-sheet.html)
|
||||||
|
|
||||||
CoffeeScript resources: [CoffeeScript.org](http://coffeescript.org/), [CoffeeScript Cookbook](http://coffeescriptcookbook.com/), [The Little Book of CoffeeScript](http://arcturo.github.com/library/coffeescript/)
|
CoffeeScript resources: [CoffeeScript.org](http://coffeescript.org/), [CoffeeScript Cookbook](http://coffeescriptcookbook.com/), [The Little Book of CoffeeScript](http://arcturo.github.com/library/coffeescript/)
|
||||||
|
|
||||||
P.S. – I had mentioned that I had already drawn something on the canvas before the leaves. This would be the grid pattern – here's the code:# draws a nice grid on the canvas
|
---
|
||||||
|
|
||||||
|
P.S. – I had mentioned that I had already drawn something on the canvas before the leaves. This would be the grid pattern – here's the code:
|
||||||
|
|
||||||
|
```
|
||||||
|
# draws a nice grid on the canvas
|
||||||
drawGrid = (ctx, styles) ->
|
drawGrid = (ctx, styles) ->
|
||||||
line = (x1,y1,x2,y2) ->
|
line = (x1,y1,x2,y2) ->
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
@@ -385,4 +465,5 @@ $ ->
|
|||||||
ctx = this.getContext '2d'
|
ctx = this.getContext '2d'
|
||||||
ctx.fillStyle = '#002b36'
|
ctx.fillStyle = '#002b36'
|
||||||
ctx.fillRect(0,0,size,size)
|
ctx.fillRect(0,0,size,size)
|
||||||
drawGrid(ctx, ['#657b83','#29434C','#073642','#073642'])
|
drawGrid(ctx, ['#657b83','#29434C','#073642','#073642'])
|
||||||
|
```
|
||||||
@@ -5,24 +5,35 @@ slug: traveling-salesman-problem-visualization
|
|||||||
published: true
|
published: true
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Over the last 9 weeks I've had the pleasure of being enrolled in the [Discrete Optimization](http://www.coursera.org/course/optimization) course at [Coursera](http://www.coursera.org) taught by [Pascal Van Hentenryck](https://www.coursera.org/instructor/~256). I had previously taken several of other massive open online courses (MOOCs), but this was the most challenging and rewarding. The key ingredients of this course were the unquestionable enthusiasm by its instructor and assistants, who created an excellent series of lectures and were personally involved at every step, as well as a game-like grading system, where the better your algorithm, the better your grade. It was rather intense! None of the programming assignments are issued with a well-defined strategy for creating a solution. Instead, the lectures cover various types of techniques and tools to be implemented and tinkered with by the students. Another interesting feature of the course is that all of the lectures and assignments are available from the first day. Most courses impose a schedule of lectures and assignments to be watched and completed on a week-by-week basis. The open structure of Discrete Optimization had me feeling a bit bewildered at first. They do provide a suggested study plan, so I (mostly) stuck with that and was able to make steady progress throughout the course.
|
Over the last 9 weeks I've had the pleasure of being enrolled in the [Discrete Optimization](http://www.coursera.org/course/optimization) course at [Coursera](http://www.coursera.org) taught by [Pascal Van Hentenryck](https://www.coursera.org/instructor/~256). I had previously taken several of other massive open online courses (MOOCs), but this was the most challenging and rewarding. The key ingredients of this course were the unquestionable enthusiasm by its instructor and assistants, who created an excellent series of lectures and were personally involved at every step, as well as a game-like grading system, where the better your algorithm, the better your grade. It was rather intense! None of the programming assignments are issued with a well-defined strategy for creating a solution. Instead, the lectures cover various types of techniques and tools to be implemented and tinkered with by the students. Another interesting feature of the course is that all of the lectures and assignments are available from the first day. Most courses impose a schedule of lectures and assignments to be watched and completed on a week-by-week basis. The open structure of Discrete Optimization had me feeling a bit bewildered at first. They do provide a suggested study plan, so I (mostly) stuck with that and was able to make steady progress throughout the course.
|
||||||
|
|
||||||
One of the topics that resonated with many of the students, myself included, was that of [Local Search](http://en.wikipedia.org/wiki/Local_search_(optimization)). It's an idea that's easy to conceptualize and program, yet very powerful. I implemented a Local Search solution for the Traveling Salesmen Problem and, along the way, added some code to visualize some of the larger solutions. It looked pretty cool to see so many points connected together by a continuous route. I began experimenting with animating the algorithm as it finds a solution. Later, the professor's assistant (AKA graciously-answering-forum-questions-[Carleton Coffrin](http://www.coffr.in/)) put out a call for the students to create visualizations of various heuristics that can be used to solve TSP. Here is my contribution – it displays [Greedy algorithm](http://en.wikipedia.org/wiki/Greedy_algorithm), [Local Search](http://en.wikipedia.org/wiki/Local_search_(optimization)), and [Simulated annealing](http://en.wikipedia.org/wiki/Simulated_annealing) strategies for a group of US cities. Traveling Salesman Problem Visualization
|
One of the topics that resonated with many of the students, myself included, was that of [Local Search](http://en.wikipedia.org/wiki/Local_search_(optimization)). It's an idea that's easy to conceptualize and program, yet very powerful. I implemented a Local Search solution for the Traveling Salesmen Problem and, along the way, added some code to visualize some of the larger solutions. It looked pretty cool to see so many points connected together by a continuous route. I began experimenting with animating the algorithm as it finds a solution. Later, the professor's assistant (AKA graciously-answering-forum-questions-[Carleton Coffrin](http://www.coffr.in/)) put out a call for the students to create visualizations of various heuristics that can be used to solve TSP. Here is my contribution – it displays [Greedy algorithm](http://en.wikipedia.org/wiki/Greedy_algorithm), [Local Search](http://en.wikipedia.org/wiki/Local_search_(optimization)), and [Simulated annealing](http://en.wikipedia.org/wiki/Simulated_annealing) strategies for a group of US cities. Traveling Salesman Problem Visualization
|
||||||
|
|
||||||
I’ve seen some interest in knowing how this was created. Here are the steps:
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
I’ve seen some interest in knowing how this was created. Here are the steps:
|
||||||
|
|
||||||
- I wanted to use some "real" map data to help illustrate the problem. From a list of cities from [geonames.org](http://www.geonames.org/export/), filtered by country and population to generate data sets
|
- I wanted to use some "real" map data to help illustrate the problem. From a list of cities from [geonames.org](http://www.geonames.org/export/), filtered by country and population to generate data sets
|
||||||
- Ran these through my TSP solver from the Discrete Optimization course assignment, collecting the intermediate routes as improvements are made
|
- Ran these through my TSP solver from the Discrete Optimization course assignment, collecting the intermediate routes as improvements are made
|
||||||
- Generated a metric ton of still images to illustrate the transitions between the routes - this involves:
|
- Generated a metric ton of still images to illustrate the transitions between the routes - this involves:
|
||||||
|
|
||||||
- translating the points and lines onto a bitmap
|
- translating the points and lines onto a bitmap
|
||||||
- "tweening" many frames between each route to provide smooth transitions. I tried several different "easing functions" but ended up with something like "easeInOutQuad" shown on this [Easing Functions](http://easings.net/) page
|
- "tweening" many frames between each route to provide smooth transitions. I tried several different "easing functions" but ended up with something like "easeInOutQuad" shown on this [Easing Functions](http://easings.net/) page
|
||||||
- … also generate images for the distance and temperature
|
- … also generate images for the distance and temperature
|
||||||
|
|
||||||
- Imported these as numbered frames into Adobe Premiere Elements - I don't use anything fancy, just the text, some cross-dissolve transitions, and alter the speed of the frames - the same could likely be done with open source editors ( [VLMC](http://www.videolan.org/vlmc/)?)
|
- Imported these as numbered frames into Adobe Premiere Elements - I don't use anything fancy, just the text, some cross-dissolve transitions, and alter the speed of the frames - the same could likely be done with open source editors ( [VLMC](http://www.videolan.org/vlmc/)?)
|
||||||
- The map of the US is a "flat" Mercator projection so that the latitude and longitude coordinates from the city list will show up in approximately the correct location - it's from here: [Mercator Projection.svg](http://commons.wikimedia.org/wiki/File:Mercator_Projection.svg)
|
- The map of the US is a "flat" Mercator projection so that the latitude and longitude coordinates from the city list will show up in approximately the correct location - it's from here: [Mercator Projection.svg](http://commons.wikimedia.org/wiki/File:Mercator_Projection.svg)
|
||||||
- ... and then a bunch of slicing and dicing and mixing and matching inside the video editor to "tell the story"
|
- ... and then a bunch of slicing and dicing and mixing and matching inside the video editor to "tell the story"
|
||||||
|
|
||||||
|
|
||||||
The code was done in .NET – here’s some pseudo-code used to generate the animation:minX, minY = points.Minimum(X), points.Minimum(Y)
|
The code was done in .NET – here’s some pseudo-code used to generate the animation:
|
||||||
|
|
||||||
|
```
|
||||||
|
minX, minY = points.Minimum(X), points.Minimum(Y)
|
||||||
maxX, maxY = points.Maximum(X), points.Maximum(Y)
|
maxX, maxY = points.Maximum(X), points.Maximum(Y)
|
||||||
w = maxX - minX
|
w = maxX - minX
|
||||||
h = maxY - minY
|
h = maxY - minY
|
||||||
@@ -66,4 +77,5 @@ Translate(point)
|
|||||||
Y = (point.Y - minY) / h * _imgH)
|
Y = (point.Y - minY) / h * _imgH)
|
||||||
|
|
||||||
EaseInOut(x)
|
EaseInOut(x)
|
||||||
(x*x)/(x*x + (1 - x)*(1 - x))
|
(x*x)/(x*x + (1 - x)*(1 - x))
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user