├── .rvmrc ├── favicon.ico ├── images ├── git.png ├── me.png ├── cards.jpg ├── ditaa.png ├── grid.png ├── ideone.png ├── logo.png ├── jitouch2.png ├── osx-open.png ├── queenbee.gif ├── 11-12-28-Bait.png ├── 11-12-28-Legs.png ├── 12-08-14-maze.png ├── atreqfirebug.png ├── dotreqfirebug.png ├── uwangel-cli-1.png ├── 11-12-28-Scale.png ├── 12-07-11-hg-glog.png ├── 12-07-11-hg-lga.png ├── 12-07-11-hg-log.png ├── 12-08-14-pickers.png ├── 12-08-14-share.png ├── 12-08-22 │ ├── 2-axis.png │ ├── devshell.png │ ├── programs.png │ ├── discussions.png │ ├── prod-import.png │ ├── vim-arcanist.png │ ├── vim-mercenary.png │ ├── flask-jinja2-gae.png │ └── number-scrubber.png ├── JobmineImproved.png ├── regexr-300x111.png ├── 12-07-11-hg-color.png ├── 12-07-11-hg-prompt.png ├── 11-12-28-Incentives.png └── 11-12-28-Disincentives.png ├── .gitignore ├── README.md ├── 404.html ├── config.rb ├── robots.txt ├── _sass ├── print.scss ├── partials │ └── _base.scss ├── ie.scss ├── mixins │ └── _syntax.scss ├── pygments.scss └── screen.sass ├── index.html ├── _config.yml ├── atom.xml ├── sitemap.xml ├── _plugins └── markdown.rb ├── Rakefile ├── _layouts ├── post.html └── default.html └── _posts ├── 2010-07-16-jsonimal-elegant-dom-contruction-with-jquery.md ├── 2010-11-17-mechanize-and-uwace-downloader.md ├── 2010-03-09-learn-source-control-with-git.md ├── 2010-01-07-tools-ideone-regexr-jitouch-2-opengnome-open-ditaa.md ├── 2009-12-01-game-of-life.md ├── 2009-11-08-spoj-problem-1.md ├── 2010-07-07-flexactionscript-3-debug-output-to-console.md ├── 2009-12-27-uwangel-cli.md ├── 2009-11-14-omegle-voyeur-multiple-connections.md ├── 2010-02-05-jobmine-improved-greasemonkey-jquery.md ├── 2010-08-14-javascript-dependency-loading-jquery-atreq.md ├── 2009-12-12-manual-sorting.md ├── 2010-04-19-ajax-method-callbacks-and-omegle-voyeur-update.md ├── 2010-10-16-dfas-and-graphviz-dot.md ├── 2011-01-12-hacker-cup-qualification-round-solutions.md ├── 2010-06-29-commandline-tips-and-tricks.md ├── 2012-04-16-declarative-programming-and-autosubscribe.md ├── 2011-06-25-complex-asynchronous-queries-in-javascript.md ├── 2012-08-14-khan-academy-computer-science.md ├── 2011-10-16-fifteen-puzzle-algorithm.md ├── 2010-09-30-make-your-life-easier-with-gnu-make.md ├── 2010-05-11-google-codejam-2010-solutions-qualification-round.md ├── 2011-12-30-immersion-and-schadenfreude.md ├── 2012-02-01-stop-returning-void.md ├── 2012-05-25-an-argument-for-mutable-local-history.md ├── 2011-10-03-meet-doctor-jekyll.md └── 2009-12-06-lambda-functions.md /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use 1.9.2 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/favicon.ico -------------------------------------------------------------------------------- /images/git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/git.png -------------------------------------------------------------------------------- /images/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/me.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | _sass/.sass-cache 3 | .sass-cache 4 | _site/ 5 | stylesheets/* 6 | -------------------------------------------------------------------------------- /images/cards.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/cards.jpg -------------------------------------------------------------------------------- /images/ditaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/ditaa.png -------------------------------------------------------------------------------- /images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/grid.png -------------------------------------------------------------------------------- /images/ideone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/ideone.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/logo.png -------------------------------------------------------------------------------- /images/jitouch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/jitouch2.png -------------------------------------------------------------------------------- /images/osx-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/osx-open.png -------------------------------------------------------------------------------- /images/queenbee.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/queenbee.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Jekyll Blog: [jamie-wong.com] 2 | 3 | [jamie-wong.com]: http://jamie-wong.com 4 | -------------------------------------------------------------------------------- /images/11-12-28-Bait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/11-12-28-Bait.png -------------------------------------------------------------------------------- /images/11-12-28-Legs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/11-12-28-Legs.png -------------------------------------------------------------------------------- /images/12-08-14-maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-14-maze.png -------------------------------------------------------------------------------- /images/atreqfirebug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/atreqfirebug.png -------------------------------------------------------------------------------- /images/dotreqfirebug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/dotreqfirebug.png -------------------------------------------------------------------------------- /images/uwangel-cli-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/uwangel-cli-1.png -------------------------------------------------------------------------------- /images/11-12-28-Scale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/11-12-28-Scale.png -------------------------------------------------------------------------------- /images/12-07-11-hg-glog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-07-11-hg-glog.png -------------------------------------------------------------------------------- /images/12-07-11-hg-lga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-07-11-hg-lga.png -------------------------------------------------------------------------------- /images/12-07-11-hg-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-07-11-hg-log.png -------------------------------------------------------------------------------- /images/12-08-14-pickers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-14-pickers.png -------------------------------------------------------------------------------- /images/12-08-14-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-14-share.png -------------------------------------------------------------------------------- /images/12-08-22/2-axis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/2-axis.png -------------------------------------------------------------------------------- /images/JobmineImproved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/JobmineImproved.png -------------------------------------------------------------------------------- /images/regexr-300x111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/regexr-300x111.png -------------------------------------------------------------------------------- /images/12-07-11-hg-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-07-11-hg-color.png -------------------------------------------------------------------------------- /images/12-07-11-hg-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-07-11-hg-prompt.png -------------------------------------------------------------------------------- /images/12-08-22/devshell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/devshell.png -------------------------------------------------------------------------------- /images/12-08-22/programs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/programs.png -------------------------------------------------------------------------------- /images/11-12-28-Incentives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/11-12-28-Incentives.png -------------------------------------------------------------------------------- /images/12-08-22/discussions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/discussions.png -------------------------------------------------------------------------------- /images/12-08-22/prod-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/prod-import.png -------------------------------------------------------------------------------- /images/12-08-22/vim-arcanist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/vim-arcanist.png -------------------------------------------------------------------------------- /images/11-12-28-Disincentives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/11-12-28-Disincentives.png -------------------------------------------------------------------------------- /images/12-08-22/vim-mercenary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/vim-mercenary.png -------------------------------------------------------------------------------- /images/12-08-22/flask-jinja2-gae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/flask-jinja2-gae.png -------------------------------------------------------------------------------- /images/12-08-22/number-scrubber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelsonjchen/blog/master/images/12-08-22/number-scrubber.png -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | description: Page not found 4 | title: Uh Oh - Nothing Here 5 | --- 6 | 7 | Oh man I hope there isn't supposed to be something here. 8 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | http_path = "/" 2 | css_dir = "stylesheets" 3 | sass_dir = "_sass" 4 | images_dir = "images" 5 | javascripts_dir = "javascripts" 6 | output_style = :expanded 7 | line_comments = false 8 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /_sass/print.scss: -------------------------------------------------------------------------------- 1 | @import "blueprint"; 2 | 3 | // To generate css equivalent to the blueprint css but with your configuration applied, uncomment: 4 | // @include blueprint-print 5 | 6 | //Recommended Blueprint configuration with scoping and semantic layout: 7 | body.bp { 8 | @include blueprint-print(true); } 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | description: Personal website of Jamie Wong 4 | --- 5 | 6 |
7 | 15 |
16 | -------------------------------------------------------------------------------- /_sass/partials/_base.scss: -------------------------------------------------------------------------------- 1 | // Here is where you can define your constants for your application and to configure the blueprint framework. 2 | // Feel free to delete these if you want keep the defaults: 3 | 4 | $blueprint-grid-columns: 24; 5 | $blueprint-container-size: 950px; 6 | $blueprint-grid-margin: 10px; 7 | 8 | // Use this to calculate the width based on the total width. 9 | // Or you can set $blueprint-grid-width to a fixed value and unset $blueprint-container-size -- it will be calculated for you. 10 | $blueprint-grid-width: ($blueprint-container-size + $blueprint-grid-margin) / $blueprint-grid-columns - $blueprint-grid-margin; 11 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # standard jekyll configuration 2 | 3 | permalink: pretty 4 | pygments: false 5 | exclude: ['Rakefile', 'README.md', 'config.rb', 'Gemfile'] 6 | markdown: rdiscount 7 | 8 | # configuration required for some pages 9 | 10 | domain: jamie-wong.com 11 | title: "Zero Wind :: Jamie Wong" 12 | author: Jamie Wong 13 | email: jamie.lf.wong@gmail.com 14 | 15 | # configuration for ehancements, uncomment to enable 16 | 17 | rdiscount: 18 | extensions: ['smart', 'generate_toc'] 19 | toc_token: "{:toc}" 20 | 21 | feedburner: 22 | id: jamiewong 23 | 24 | google_analytics: 25 | account: UA-11869199-1 26 | 27 | github: 28 | username: phleet 29 | 30 | disqus: 31 | id: jamiewong 32 | -------------------------------------------------------------------------------- /_sass/ie.scss: -------------------------------------------------------------------------------- 1 | @import "blueprint"; 2 | 3 | // To generate css equivalent to the blueprint css but with your configuration applied, uncomment: 4 | // @include blueprint-ie 5 | 6 | //Recommended Blueprint configuration with scoping and semantic layout: 7 | body.bp { 8 | @include blueprint-ie(true); 9 | // Note: Blueprint centers text to fix IE6 container centering. 10 | // This means all your texts will be centered under all version of IE by default. 11 | // If your container does not have the .container class, don't forget to restore 12 | // the correct behavior to your main container (but not the body tag!) 13 | // Example: 14 | // .my-container 15 | // text-align: left 16 | } 17 | -------------------------------------------------------------------------------- /atom.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: nil 3 | --- 4 | 5 | 6 | 7 | {{ site.title | xml_escape }} 8 | 9 | 10 | {{ site.time | date_to_xmlschema }} 11 | http://{{ site.domain }}/ 12 | 13 | {{ site.author }} 14 | {{ site.email }} 15 | 16 | 17 | {% for post_num in (0...10) %} 18 | {% assign post = site.posts[post_num] %} 19 | 20 | {{ post.title | xml_escape }} 21 | 22 | {{ post.date | date_to_xmlschema }} 23 | http://{{ site.domain }}{{ post.id }} 24 | {{ post.content | xml_escape }} 25 | 26 | {% endfor %} 27 | 28 | 29 | -------------------------------------------------------------------------------- /sitemap.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: nil 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | http://{{ site.domain }}/ 10 | {{ site.time | date: "%Y-%m-%d" }} 11 | daily 12 | 1.0 13 | 14 | 15 | {% for page in site.posts %} 16 | 17 | http://{{ site.domain }}{{ page.url }} 18 | {% if page.last_updated %} 19 | {{ page.last_updated | date: "%Y-%m-%d" }} 20 | {% elsif page.date %} 21 | {{ page.date | date: "%Y-%m-%d" }} 22 | {% else %} 23 | {{ site.time | date: "%Y-%m-%d" }} 24 | {% endif %} 25 | 26 | {% endfor %} 27 | 28 | {% for page in site.html_pages %} 29 | 30 | http://{{ site.domain }}{{ page.url }} 31 | {% if page.last_updated %} 32 | {{ page.last_updated | date: "%Y-%m-%d" }} 33 | {% elsif page.date %} 34 | {{ page.date | date: "%Y-%m-%d" }} 35 | {% else %} 36 | {{ site.time | date: "%Y-%m-%d" }} 37 | {% endif %} 38 | {% if page.changefreq %}{{ page.changefreq }}{% endif %} 39 | {% if page.priority %}{{ page.priority }}{% endif %} 40 | 41 | {% endfor %} 42 | 43 | 44 | -------------------------------------------------------------------------------- /_plugins/markdown.rb: -------------------------------------------------------------------------------- 1 | require 'rdiscount' 2 | require 'nokogiri' 3 | require 'albino' 4 | 5 | module Jekyll 6 | class MarkdownConverter < Converter 7 | safe true 8 | 9 | priority :high 10 | 11 | def matches(ext) 12 | ext =~ /md/i 13 | end 14 | 15 | def output_ext(ext) 16 | ".html" 17 | end 18 | 19 | def convert(content) 20 | rdiscount_extensions = @config['rdiscount']['extensions'].map { |e| e.to_sym } 21 | rd = RDiscount.new(content, *rdiscount_extensions) 22 | html = rd.to_html 23 | toc_token = @config['rdiscount']['toc_token'] 24 | 25 | if html.include? "Table of Contents" 26 | puts "\n\n" 27 | puts "TOC TOKEN: #{toc_token}" 28 | puts "INCLUDED?: #{html.include?(toc_token)}" 29 | puts "HTML: #{html}" 30 | end 31 | 32 | if rd.generate_toc and html.include?(toc_token) 33 | toc_html = %( 34 |
35 | #{rd.toc_content} 36 |
37 | ) 38 | html.gsub!(@config['rdiscount']['toc_token'], toc_html) 39 | end 40 | 41 | doc = Nokogiri::HTML(html) 42 | doc.css('pre > code').each do |code_node| 43 | code_content = code_node.children[0].content 44 | 45 | lines = code_content.lines.to_a.collect(&:rstrip) 46 | 47 | if lines[0].start_with? 'lang:' 48 | lang = lines[0].split(':')[1].to_sym 49 | code = lines[2..-1].join "\n" 50 | colorized_html = Albino.colorize(code, lang) 51 | if colorized_html.length > 0 52 | code_node.parent.replace(colorized_html) 53 | end 54 | end 55 | end 56 | 57 | doc.to_html 58 | end 59 | end 60 | end 61 | 62 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => :server 2 | 3 | desc 'Clean up generated site' 4 | task :clean do 5 | cleanup 6 | end 7 | 8 | desc 'Build site with Jekyll' 9 | task :build => :clean do 10 | compass 11 | jekyll 12 | end 13 | 14 | desc 'Start server with --auto' 15 | task :server => :clean do 16 | compass 17 | jekyll('--server --auto') 18 | end 19 | 20 | desc 'Deploy' 21 | task :deploy do 22 | sh 'rsync -rtzh --progress --delete _site/ ec2:/var/www/jamie-wong.com/blog/' 23 | end 24 | 25 | desc 'Check links for site already running on localhost:4000' 26 | task :check_links do 27 | begin 28 | require 'anemone' 29 | root = 'http://localhost:4000/' 30 | Anemone.crawl(root, :discard_page_bodies => true) do |anemone| 31 | anemone.after_crawl do |pagestore| 32 | broken_links = Hash.new { |h, k| h[k] = [] } 33 | pagestore.each_value do |page| 34 | if page.code != 200 35 | referrers = pagestore.pages_linking_to(page.url) 36 | referrers.each do |referrer| 37 | broken_links[referrer] << page 38 | end 39 | end 40 | end 41 | broken_links.each do |referrer, pages| 42 | puts "#{referrer.url} contains the following broken links:" 43 | pages.each do |page| 44 | puts " HTTP #{page.code} #{page.url}" 45 | end 46 | end 47 | end 48 | end 49 | 50 | rescue LoadError 51 | abort 'Install anemone gem: gem install anemone' 52 | end 53 | end 54 | 55 | desc 'Watch and recompile sass' 56 | task :compass do 57 | sh 'compass watch -c config.rb --force' 58 | end 59 | 60 | def cleanup 61 | sh 'rm -rf _site' 62 | end 63 | 64 | def jekyll(opts = '') 65 | sh 'jekyll ' + opts 66 | end 67 | 68 | def compass(opts = '') 69 | sh 'compass compile -c config.rb --force ' + opts 70 | end 71 | -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | {% if page.date %} 5 |
6 | Posted on {{ page.date | date:"%B %e, %Y" }} 7 |
8 | {% endif %} 9 | 10 |
11 | {{ content }} 12 |
13 | 14 |
15 |

16 | If you enjoyed this post and want to read more, you should follow me on twitter, or subscribe to the feed. You can 19 | also browse the archives. 20 |

21 | 22 | Follow @jlfwong 23 | Tweet 24 | 25 |
26 | 27 |
28 |

Related Posts

29 | 37 |
38 | 39 | {% if site.disqus %} 40 |
41 |

Comments

42 |
43 | 51 | 52 | blog comments powered by Disqus 53 | {% endif %} 54 |
55 | -------------------------------------------------------------------------------- /_sass/mixins/_syntax.scss: -------------------------------------------------------------------------------- 1 | .highlight { 2 | background: #ffffff; 3 | .c { 4 | color: #999988; 5 | font-style: italic; } 6 | .err { 7 | color: #a61717; 8 | background-color: #e3d2d2; } 9 | .k, .o { 10 | font-weight: bold; } 11 | .cm { 12 | color: #999988; 13 | font-style: italic; } 14 | .cp { 15 | color: #999999; 16 | font-weight: bold; } 17 | .c1 { 18 | color: #999988; 19 | font-style: italic; } 20 | .cs { 21 | color: #999999; 22 | font-weight: bold; 23 | font-style: italic; } 24 | .gd { 25 | color: #000000; 26 | background-color: #ffdddd; 27 | .x { 28 | color: #000000; 29 | background-color: #ffaaaa; } } 30 | .ge { 31 | font-style: italic; } 32 | .gr { 33 | color: #aa0000; } 34 | .gh { 35 | color: #999999; } 36 | .gi { 37 | color: #000000; 38 | background-color: #ddffdd; 39 | .x { 40 | color: #000000; 41 | background-color: #aaffaa; } } 42 | .go { 43 | color: #888888; } 44 | .gp { 45 | color: #555555; } 46 | .gs { 47 | font-weight: bold; } 48 | .gu { 49 | color: #aaaaaa; } 50 | .gt { 51 | color: #aa0000; } 52 | .kc, .kd, .kp, .kr { 53 | font-weight: bold; } 54 | .kt { 55 | color: #445588; 56 | font-weight: bold; } 57 | .m { 58 | color: #009999; } 59 | .s { 60 | color: #d14; } 61 | .na { 62 | color: #008080; } 63 | .nb { 64 | color: #0086B3; } 65 | .nc { 66 | color: #445588; 67 | font-weight: bold; } 68 | .no { 69 | color: #008080; } 70 | .ni { 71 | color: #800080; } 72 | .ne, .nf { 73 | color: #990000; 74 | font-weight: bold; } 75 | .nn { 76 | color: #555555; } 77 | .nt { 78 | color: #000080; } 79 | .nv { 80 | color: #008080; } 81 | .ow { 82 | font-weight: bold; } 83 | .w { 84 | color: #bbbbbb; } 85 | .mf, .mh, .mi, .mo { 86 | color: #009999; } 87 | .sb, .sc, .sd, .s2, .se, .sh, .si, .sx { 88 | color: #d14; } 89 | .sr { 90 | color: #009926; } 91 | .s1 { 92 | color: #d14; } 93 | .ss { 94 | color: #990073; } 95 | .bp { 96 | color: #999999; } 97 | .vc, .vg, .vi { 98 | color: #008080; } 99 | .il { 100 | color: #009999; } } 101 | -------------------------------------------------------------------------------- /_posts/2010-07-16-jsonimal-elegant-dom-contruction-with-jquery.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: JSONimal - Elegant DOM Contruction with jQuery 4 | tags: 5 | - github 6 | - javascript 7 | - jquery 8 | - Projects 9 | status: publish 10 | type: post 11 | published: true 12 | meta: 13 | _edit_lock: "1285900399" 14 | _edit_last: "1" 15 | _wp_old_slug: "" 16 | --- 17 | **EDIT** There is a more complete project achieving exactly the goals I set out for called jquery-haml: [http://github.com/creationix/jquery-haml]() 18 | 19 | Occasionally for Javascript projects, I found myself building a lot of HTML programatically, and I wasn't satisfied with any of the techniques available, so I built JSONimal. I was originally going to just call it JSONML, but that was taken. 20 | 21 | What's it do? This example should demonstrate my goal fairly well. 22 | 23 | lang:js 24 | 25 | $(function() { 26 | $.mktag("#demo").jsonimal([ 27 | ["h1", {text: "JSONimal!"}], 28 | ["table",{style: 'border: 1px solid black'},[ 29 | ["thead",[ 30 | ["tr",{style: 'text-transform: uppercase'},[ 31 | ["th", {text: "one"}], 32 | ["th", {text: "two"}], 33 | ["th", {text: "three"}] 34 | ]] 35 | ]], 36 | ["tbody", [ 37 | ["tr",[ 38 | ["td", {html: "a"}], 39 | ["td", {text: "b"}], 40 | ["td", {text: "c"}] 41 | ]], 42 | ["tr",[ 43 | ["td",[ 44 | ["a", {href: "http://www.google.ca", text: "Google"}] 45 | ]], 46 | ["td", {text: "b"}], 47 | ["td", {text: "c"}] 48 | ]], 49 | ["tr",[ 50 | ["td", {text: "a"}], 51 | ["td", {text: "b"}], 52 | ["td", {text: "c"}] 53 | ]] 54 | ]] 55 | ]] 56 | ]).appendTo("body"); 57 | }); 58 | 59 | Which will add this to the body: 60 | 61 |

JSONimal!

onetwothree
abc
Googlebc
abc
62 | 63 | For more information and examples, check out the github page: [JSONimal @ github][github]. 64 | 65 | I also posted it as on the jQuery plugins page - but that just points to the github page anyway. [JSONimal @ plugins.jquery.com][plugin] 66 | 67 | [github]: http://github.com/phleet/JSONimal 68 | [plugin]: http://plugins.jquery.com/project/jsonimal 69 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% if page.description %} 12 | 13 | {% endif %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 | 26 |

27 | Zerø Wind 28 |

29 | 30 |
31 | 32 |

Jamie Wong

33 | 34 |
35 |

36 | I'm a uWaterloo Software Engineering student from Ottawa. 37 | I'm currently an intern at Khan Academy. 38 |

39 |
40 | 41 | 52 |
53 |
54 | 55 |
56 |
57 | {% if page.title %} 58 |

{{ page.title }}

59 | {% endif %} 60 | 61 | {{ content }} 62 |
63 |
64 |
65 | 66 | {% if site.google_analytics %} 67 | 68 | 78 | {% endif %} 79 | 80 | 81 | -------------------------------------------------------------------------------- /_posts/2010-11-17-mechanize-and-uwace-downloader.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Mechanize and UWAce Downloader 4 | tags: [] 5 | 6 | status: publish 7 | type: post 8 | published: true 9 | meta: 10 | _edit_last: "1" 11 | _edit_lock: "1290224986" 12 | _wp_old_slug: "" 13 | --- 14 | A while ago I made something in php that would let you download things form UW-Ace (the University of Waterloo's course management system) from the command-line. Michael Chang had the excellent idea of making something that would simply download everything available to you from Ace so you could have a local copy of everything. 15 | 16 | His solution was an extension of my php solution. 17 | 18 | Because I wanted to make the product easier to distribute and because I wanted to make use of Mechanize, I rewrote the solution in ruby and packaged it as a gem. 19 | 20 | UWAce Downloader 21 | ============ 22 | The new version looks like this: 23 | 24 | ![uwace gem screenshot](http://phleet.github.com/images/uwacegem.png) 25 | 26 | To get it running, you'll need a copy of RubyGems, which you can get here: [Download RubyGems][]. It's a simple package management and distribution system for ruby, which is used by more or less every ruby project you can think of. 27 | 28 | Once you have that set up, just run 29 | 30 | sudo gem install uwace 31 | 32 | Wait for it to finish installing, then run 33 | 34 | uwace 35 | 36 | Which will prompt you for a username and password. 37 | Of course, I wouldn't release anything that asks for your password without releasing source, so here it is: 38 | 39 | * [uwace gem @ github][] 40 | * [uwace gem @ RubyGems][] 41 | 42 | [uwace gem @ github]: https://github.com/phleet/UWAngel-CLI 43 | [uwace gem @ RubyGems]: https://rubygems.org/gems/uwace 44 | [Download RubyGems]: http://rubygems.org/pages/download 45 | 46 | Mechanize 47 | ======= 48 | 49 | This version of the downloader is written in ruby with the aid of Mechanize for ruby. 50 | Mechanize is a set of tools for automating webpage interactions and retrieving data. It can identify links and forms on a page, fill them in, submit them and grab any data you want. Perfect for this task. 51 | 52 | There's a simple guide on github here: [Getting Started with Mechanize][] 53 | 54 | To demonstrate how simple form interaction is, here's the method for login: 55 | 56 | lang:ruby 57 | 58 | def login 59 | @username ||= ask("Username: ") 60 | @password ||= ask("Password: ") {|q| q.echo = '*'} 61 | 62 | say "Logging in ..." 63 | 64 | login_page = angel_get 'home.asp' 65 | form = login_page.form_with(:name => "frmLogon") 66 | form.username = @username 67 | form.password = @password 68 | 69 | login_submit_page = @agent.submit form 70 | 71 | if login_submit_page.uri.to_s.include? 'authenticate.asp' 72 | raise InvalidLogin 73 | end 74 | rescue InvalidLogin 75 | say 'Invalid Username/Password' 76 | exit 77 | end 78 | 79 | While I've only used the Mechanize bindings for ruby, there bindings for many other languages: 80 | 81 | * [Python's Mechanize](http://wwwsearch.sourceforge.net/mechanize/) 82 | * [Perl's WWW::Mechanize](http://search.cpan.org/dist/WWW-Mechanize/lib/WWW/Mechanize/Cookbook.pod) - This was actually the original Mechanize 83 | 84 | [Getting Started with Mechanize]: https://github.com/tenderlove/mechanize/blob/master/GUIDE.rdoc 85 | -------------------------------------------------------------------------------- /_posts/2010-03-09-learn-source-control-with-git.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Learn Source Control with Git 4 | tags: 5 | - git 6 | - hg 7 | - mercurial 8 | - revision control 9 | - source control 10 | - svn 11 | - Tools 12 | status: publish 13 | type: post 14 | published: true 15 | meta: 16 | _edit_lock: "1274333057" 17 | _edit_last: "1" 18 | --- 19 | 20 |
21 | 22 |
23 | 24 | One of the gaps among my tech skills upon entering university was source/revision control. 25 | 26 | For those of you unfamiliar, revision control is a method of tracking and storing the changes made to files. This is particularly useful when keeping track of all the changes made to source code being worked on in a group. This allows you to all work on the same set of files at once and merge together the changes later. 27 | 28 | This doesn't mean it isn't useful for projects you're working on by yourself. If you've ever coded something up, then decided you have a better way of solving the problem, then finally realize your new solution doesn't work, you need to go back. Except the deleted code is one undo step beyond your history. Crap. 29 | 30 | And no, allowing for more undo steps is not the solution to this problem. If you want to look at older versions across multiple files from weeks ago, undo won't help you. Revision control will. 31 | 32 | After speaking with employers, it seems that the most commonly used source control system at the moment is git. I'd like to note that most of the people I interviewed with were small, fairly new, web or mobile based companies. Older companies may be using svn or possibly even cvs. Then there's the whole set of Microsoft source control systems such as Microsoft Visual Source Safe. 33 | 34 | You can see a summary of source control options here: Comparison of revision control software @ Wikipedia 35 | 36 | I'm posting about this now because I'm working on a collaborative project using git for the first time. Since this is a private project, I'm using Project Locker instead of Github. To be honest, I probably should have just set up my own private repository and I might look into that later. 37 | 38 | You don't actually need to have a remote repository. You can use a git repository to control your source locally if you're the only one working on it. You might consider doing this for school projects so you don't accidentally overwrite your working code in an attempt to appease Marmoset (automated testing in CS at U Waterloo). 39 | 40 | In order to learn how to use git, I can recommend two sources of information. 41 | 42 | 1. GitCasts: these are screen casts, going through git and explaining things along the way. I'm in the middle of the fourth cast right now, so I can't say for sure these are all of high quality, but I'm seeing things I didn't know before, and that's enough for me. 43 | 44 | 2. Visual Git Guide: Pictures are awesome, especially for those people of the tl;dr mindset. Or those attracted to colourful pictures. The picture at the top of the post is from the visual git guide. This is a fairly in depth explanation of some of the functionality of git and shows you what's actually happening behind the scenes. 45 | 46 | For those of you more interested in learning Mercurial (Hg), Zameer Manji has recommended the following 48 | guide: Hg Init: a Mercurial tutorial by 49 | Joel Spolsky. 50 | 51 | I would recommend you go grab an account of GitHub to help yourself learning. If you're in need of something to fool around with, fork one of my projects: phleet @ github. 52 | -------------------------------------------------------------------------------- /_posts/2010-01-07-tools-ideone-regexr-jitouch-2-opengnome-open-ditaa.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Tools: ideone, RegExr, jitouch 2, open, ditaa" 4 | tags: 5 | - ditaa 6 | - ideone 7 | - jitouch 8 | - mac os x 9 | - open 10 | - regexr 11 | - regular expressions 12 | - Tools 13 | status: publish 14 | type: post 15 | published: true 16 | meta: 17 | _edit_last: "1" 18 | _edit_lock: "1274332878" 19 | --- 20 | Through reading proggit and hearing about new technology from classmates, every 21 | once in a while, I build up a list of tools which I plan on checking out and see 22 | whether they're useful enough to add to my regular routine. The first four on 23 | this list fall into that category, and I may eventually find a use for the last. 24 | 25 | ideone 26 | ====== 27 | 28 | ideone is an in-browser, syntax-highlighted 30 | code editor complete with interpreters and compilers. Basically, if you've ever 31 | wanted to try out a language but really didn't feel like installing it on your 32 | system, this is the perfect place to start. The site even runs Brainf**k. 33 | 34 | There's another site which accomplishes the same task, but less elegantly: Codepad. This site is so much less elegant that I 36 | wasn't originally planning on posting it, but decided to once I saw there was a 37 | Codepad vim 38 | plugin. There's also emacs integrations. 39 | 40 | RegExr 41 | ====== 42 | 43 | Regexr is an online tool, implemented in 45 | Adobe Flex, to test out regular expressions in real time. If you haven't learned 46 | about regular expressions yet, go 47 | learn right now. They're just about the most powerful text matching, user 48 | verification and error correction tool in existence. They're also implemented in 49 | nearly all languages now in some form or another. Before I saw this site, I 50 | would test out all my regular expressions just using vim, but found it 51 | frustrating when the expressions needed to be changed to be compatible with php. 52 | So I'm likely going to start using RegExr instead. 53 | 54 | jitouch 2 55 | ========= 56 | 57 | Jitouch 2 is an application expanding on the 59 | multi-touch gestures available to MacBook Pro users who want to do more with 60 | just the touch pad. The two big things that this enables me to do that I love 61 | are opening links in new tabs using only taps on the keypad, and switching tabs 62 | using a gesture equivalent to ctrl-tab to switch tabs. I actually saw this 63 | reading Randal Munroe's blag. 64 | 65 | Mac OS X/Gnome open 66 | =================== 67 | 68 | open & gnome-open are terminal 69 | commands in Mac OS X and gnome respectively, but they do the same thing. 70 | Whenever you double click on a file in Finder or Nautilus, the operating system 71 | has a database of which extensions are opened by which applications. You can 72 | leverage that database by calling `open`. Examples: 73 | 74 | open "Office Space.avi" 75 | 76 | Open up Office Space in your default viewer. 77 | 78 | open http://www.jamie-wong.com 79 | 80 | Visit my website from the commandline, opening in your default browser 81 | 82 | open -a "Adobe Photoshop CS3" 83 | 84 | Launch Photoshop. `open -a` opens files with an application. 85 | 86 | ditaa 87 | ===== 88 | 89 | ditaa is a tool for converting ASCII 91 | art diagrams into graphical diagrams. This is pretty well illustrated in the 92 | picture to the left. I haven't actually found much of a use for this yet, but 93 | some of you might. 94 | 95 |
96 | -------------------------------------------------------------------------------- /_posts/2009-12-01-game-of-life.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Game of Life 4 | tags: 5 | - conway 6 | - gif 7 | - gifsicle 8 | - life 9 | - optparse 10 | - Projects 11 | - python 12 | status: publish 13 | type: post 14 | published: true 15 | meta: 16 | _edit_lock: "1265429990" 17 | _edit_last: "1" 18 | --- 19 | For my latest project, I'm implementing Conway's Game of Life in python into animated GIFs. 20 | 21 | Before I even explain what Conway's Game of Life is, be amused by the below, generated animation: 22 | 23 |
24 | Queenbee 25 |
26 | 27 | As always, the code I'm using here is open source: Game Of Life @ Github. 28 | In addition to the source code, there's also a few animation demos such as the one above. 29 | 30 | Conway's Game of Life is a cellular automaton following very simple rules, as outlined in the Wikipedia article. It is a zero player game played on a 2 dimensional grid of squares, each holding either a state of dead or alive. The state of any cell is dependent on the state of the 8 cells neighbouring it in the previous generation. There are only 4 rules. 31 | 32 | From the Wikipedia article Conway's Game of Life: 33 | 34 | > 1. Any live cell with fewer than two live neighbours dies, as if caused by 35 | > underpopulation. 36 | > 2. Any live cell with more than three live neighbours dies, as if by 37 | > overcrowding. 38 | > 3. Any live cell with two or three live neighbours lives on to the next 39 | > generation. 40 | > 4. Any dead cell with exactly three live neighbours becomes a live cell. 41 | 42 | The colours you see in the above animation represent the alive status of each cell and also how many neighbours that cell has if it's alive. 43 | 44 | This project is my first time making use of two utilities: optparse in python, and gifsicle. 45 | 46 | optparse is a python library designed to make the creation of command line application much simpler. Specifically it's targeted towards making application with many possible flags easy to maintain. lifeImage.py falls into this category. From the commandline, you can control the source of input, the colour scheme, the number of generations to output, the scaling of the image and various other things to produce the exact animation or image you want. 47 | 48 | You can take a look at optparse here: optparse @ docs.python.org. 49 | The tutorial included there was enough for me to create this application. 50 | 51 | gifsicle is a command line application for the creation and modification of animated gifs. It can create gifs out of a sequence of images, convert an image into a sequence of images, or even modify replace a single frame of an animated gif with an external image. 52 | 53 | You can see and download gifsicle here: Official Gifsicle Page 54 | 55 | Why did I use gifsicle instead of the much more universal convert in 56 | ImageMagick? Simply put: gifsicle is faster. If someone would like to do a 57 | benchmark to (dis)prove this, I'd be happy to post the results, but from simple 58 | experimentation, it seemed obvious to me that gifsicle took less time to make 59 | the animation. 60 | 61 | What's Next? 62 | ============ 63 | 64 | Now that I have a working command line utility, my next goal is to make an AJAX 65 | powered web interface for the thing. This might explain why I have `ProcMonitor` 66 | in the github for Game of Life. The web interface was another key motivator 67 | behind using gifs for the output medium. People may want to use these things for 68 | avatar, and they're simply easier to share and move around than a java applet, 69 | or a flash swf, or some database stored simulation. It also helps that gifs are 70 | designed for palette based images, which works out nicely for optimizing the 71 | file size of these animations. The first 1000 generations of acorn is 2.5 MB as 72 | it is. 73 | 74 | Another thing I want to do is make `lifeImage.py` read the `.cells` format from 75 | the Life Lexicon. This 76 | would save me a lot of time having to code all the states myself. This will be a 77 | very straightforward process, as the `.cells` files are simply plaintext with 2 78 | lines of header. 79 | 80 | Suggestions/bug fixes for my implementation of Game of Life are welcomed. 81 | 82 | I'm almost 100% sure that some combination of the command line flags of 83 | `lifeImage.py` don't work nicely together, and would like to know what they are. 84 | -------------------------------------------------------------------------------- /_posts/2009-11-08-spoj-problem-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: SPOJ Problem 1 4 | tags: 5 | - bash 6 | - c++ 7 | - c99 8 | - java 9 | - pascal 10 | - perl 11 | - php 12 | - prime 13 | - python 14 | - ruby 15 | - SPOJ 16 | status: publish 17 | type: post 18 | published: true 19 | meta: 20 | _edit_lock: "1258154416" 21 | _edit_last: "1" 22 | --- 23 | When I saw that Thor had done solutions to the first few problems of Project Euler in many many languages, I thought "Hey! That's a good idea!" but didn't want to copy exactly. 24 | 25 | So instead I'm doing solutions to some problems on SPOJ. 26 | SPOJ is an online judge system full of algorithmic problems. This is one of the things that Hanson Wang did to get as good as he is. The solutions I'll be providing here are going to be very simple problems, so don't expect any magic. The beautiful thing about SPOJ is the sheer number of languages it will judge. I figured this was the perfect playground to make sure my code worked in all the languages I try. 27 | 28 | Problem: Life, the Universe, and Everything 29 | 30 |
31 | Your program is to use the brute-force approach in order to find the Answer to Life, the Universe, and Everything. More precisely... rewrite small numbers from input to output. Stop processing input after reading in the number 42. All numbers at input are integers of one or two digits. 32 |
33 | Solutions - In order of frequency that I use the language 34 | 35 | C++ 36 | 37 | lang:cpp 38 | 39 | //TEST AC - CPP (g++) 40 | #include 41 | using namespace std; 42 | 43 | int main() { 44 | int n; 45 | while(1) { 46 | cin >> n; 47 | if (n == 42) break; 48 | cout << n << endl; 49 | } 50 | return 0; 51 | } 52 | 53 | C99 54 | 55 | lang:c 56 | 57 | //TEST AC - (gcc C99) 58 | #include 59 | int main() { 60 | int n; 61 | while(1) { 62 | scanf("%d",&n); 63 | if (n == 42) break; 64 | printf("%d\n",n); 65 | } 66 | return 0; 67 | } 68 | 69 | php 70 | 71 | lang:php 72 | 73 | 81 | 82 | Python 83 | 84 | lang:python 85 | 86 | #TEST AC - Python 87 | while 1: 88 | num = input() 89 | if (num == 42): 90 | break 91 | print num 92 | 93 | Bash 94 | 95 | lang:bash 96 | 97 | #!/bin/bash 98 | # TEST AC - BASH 99 | 100 | while true; do 101 | read n 102 | if [ $n -eq 42 ]; then 103 | break 104 | fi 105 | echo "$n" 106 | done 107 | 108 | Ruby 109 | 110 | lang:ruby 111 | 112 | #TEST AC - Ruby 113 | while 1 114 | n = gets.to_i 115 | if n == 42 116 | break 117 | end 118 | puts n 119 | end 120 | 121 | Java 122 | 123 | lang:java 124 | 125 | //TEST AC - Java 126 | import java.io.*; 127 | import java.util.*; 128 | 129 | public class Main { 130 | public static void main(String[] args) { 131 | Scanner in = new Scanner(System.in); 132 | 133 | while(true) { 134 | int n = in.nextInt(); 135 | if (n == 42) break; 136 | System.out.println(n); 137 | } 138 | } 139 | } 140 | 141 | Perl 142 | 143 | lang:perl 144 | 145 | #TEST AC - Perl 146 | while (1) 147 | { 148 | $n = ; 149 | if ($n == 42) { 150 | last; 151 | } 152 | print $n 153 | } 154 | 155 | C# 156 | 157 | lang:csharp 158 | 159 | //TEST AC - C# (gmcs + Mono) 160 | using System; 161 | class WelcomeCSS { 162 | static void Main() { 163 | while(true) { 164 | int n; 165 | n = int.Parse(Console.ReadLine()); 166 | if (n == 42) break; 167 | Console.WriteLine(n); 168 | } 169 | } 170 | } 171 | 172 | GNU Pascal 173 | 174 | lang:pascal 175 | 176 | {TEST AC - GPC Pascal} 177 | 178 | program TEST; 179 | 180 | var n:integer; 181 | 182 | begin 183 | while true do 184 | begin 185 | readln(n); 186 | if n = 42 then begin 187 | break; 188 | end; 189 | writeln(n); 190 | end; 191 | end. 192 | 193 | You can see Thor's Project Euler solutions here: Derek Thurn's Website - Project Euler 194 | -------------------------------------------------------------------------------- /_posts/2010-07-07-flexactionscript-3-debug-output-to-console.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Flex/Actionscript 3: Debug Output to Console" 4 | tags: 5 | - actionscript 6 | - flex 7 | - flixel 8 | - trace 9 | - Tutorials 10 | status: publish 11 | type: post 12 | published: true 13 | meta: 14 | _edit_last: "1" 15 | _edit_lock: "1278565881" 16 | _wp_old_slug: "" 17 | --- 18 | I recently started messing around with the [Flixel][] framework - something built on top of [Flex][] to make retro games. One of the first things I noticed was how difficult it is to debug things - especially complex objects. 19 | 20 | [Flixel]: http://flixel.org/ 21 | [FLex]: http://www.adobe.com/products/flex/ 22 | 23 | The first problem is capturing _any_ debug output. For whatever reason, I was unable to output traces to anywhere useful. 24 | My first success was with using [De MonsterDebugger][] to capture the output. While this was better than nothing, I needed very little of the functionality of De MonsterDebugger and it still didn't give me what I really wanted: formatted console output. 25 | 26 | [De MonsterDebugger]: http://demonsterdebugger.com/ 27 | 28 | print_r style output in actionscript 29 | ----------------------------- 30 | 31 | I found a good starting point [here][base86], but the output of this wasn't exactly what I wanted. I messed around with it until I got something more familiar to me. 32 | 33 | [base86]: http://dev.base86.com/solo/47/actionscript_3_equivalent_of_phps_printr.html 34 | 35 | lang:actionscript 36 | 37 | package learning { 38 | import org.flixel.*; 39 | 40 | public class Debugger { 41 | public static function pr(obj:*, level:int = 0, output:String = ""):* { 42 | var objtype:String = typeof(obj); 43 | if (objtype == "boolean" || objtype == "string" || objtype == "number") { 44 | return obj; 45 | } 46 | 47 | var tabs:String = ""; 48 | for(var i:int = 0; i < level; i++) { 49 | tabs += "\t" 50 | } 51 | 52 | output += "{\n"; 53 | for(var child:* in obj) { 54 | output += tabs +"\t["+ child +"] => "; 55 | 56 | var childOutput:String = pr(obj[child], level+1); 57 | if(childOutput != '') output += childOutput 58 | 59 | output += "\n"; 60 | } 61 | output += tabs + "}\n"; 62 | 63 | return output; 64 | } 65 | 66 | public static function log(obj:*):void { 67 | FlxG.log(pr(obj)); 68 | // This is a flixel thing. If you're not using flixel 69 | // Just use trace(pr(obj)); 70 | } 71 | } 72 | } 73 | 74 | Example output 75 | 76 | { 77 | [0] => { 78 | [tile] => 4 79 | [rule] => { 80 | [0] => xxx 81 | [1] => x1x 82 | [2] => x1x 83 | } 84 | } 85 | [1] => { 86 | [tile] => 8 87 | [rule] => { 88 | [0] => x1x 89 | [1] => x1x 90 | [2] => xxx 91 | } 92 | } 93 | } 94 | 95 | trace output to console 96 | -------------------- 97 | 98 | This, fortunately, was much less of a hassle for me to get working. 99 | 100 | Reference: [Configuring the debugger version of Flash Player][Flex 3 Reference] 101 | 102 | **Step 1** 103 | 104 | Locate/create your `mm.cfg` file. For me this was in `~/mm.cfg`. See [reference][Flex 3 Reference]. 105 | 106 | Stick this in it: `TraceOutputFileEnable=1` 107 | 108 | Or, in one command: 109 | 110 | lang:bash 111 | 112 | echo "TraceOutputFileEnable=1" > ~/mm.cfg` 113 | 114 | **Step 2** 115 | 116 | Locate the location of the log file. Mine is in 117 | 118 | ~/Library/Preferences/Macromedia/Flash Player/Logs/flashlog.txt` 119 | 120 | See [reference][Flex 3 Reference]. 121 | 122 | You don't want to type this in every time you want to view the log, so add a function to your `.bash_profile` 123 | 124 | This is what I have: 125 | 126 | lang:bash 127 | 128 | flashlog() { 129 | tail -f $* ~/Library/Preferences/Macromedia/Flash\ Player/Logs/flashlog.txt; 130 | } 131 | 132 | [Flex 3 Reference]: http://livedocs.adobe.com/flex/3/html/help.html?content=logging_04.html 133 | 134 | **Step 3** 135 | 136 | Start debugging! 137 | 138 | lang:bash 139 | 140 | $ flashlog -100 141 | Warning: 'flash' has no property 'prototype' 142 | Warning: 'flash' has no property 'prototype' 143 | flixel v2.35 [debug] 144 | ---------------------------------------------------- 145 | { 146 | [0] => { 147 | [tile] => 4 148 | [rule] => { 149 | [0] => xxx 150 | [1] => x1x 151 | [2] => x1x 152 | } 153 | } 154 | [1] => { 155 | [tile] => 8 156 | [rule] => { 157 | [0] => x1x 158 | [1] => x1x 159 | [2] => xxx 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /_posts/2009-12-27-uwangel-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: UWAngel-CLI 4 | tags: 5 | - CLI 6 | - curl 7 | - php 8 | - Projects 9 | - stty 10 | - UWAce 11 | - UWAngel 12 | status: publish 13 | type: post 14 | published: true 15 | meta: 16 | _edit_lock: "1265430002" 17 | _edit_last: "1" 18 | --- 19 |
20 | 21 |
22 | 23 | Near the end of the semester, I was getting kind of tired of navigating UW-ACE 24 | so frequently through my browsing and opening all the different tabs to grab all 25 | the files I wanted. While all the pretty graphics make for a decent user 26 | interface, it reduces the speed of the service. 27 | 28 | But aside from that, I like being able to do as much as I possibly can from the 29 | console. 30 | 31 | So I made a Command Line Interface (CLI) for UW-ACE in php using cUrl. I built 32 | in on my Macbook Pro in Snow Leopard, but it should work just fine on any *nix 33 | machine, and possibly in Cygwin or other emulators. 34 | 35 | As always, source is available on github: UWAngel-CLI @ Github. 37 | 38 | Since I always like to post snippets of code from my projects that may be 39 | universally useful, I'll do that here too. 40 | 41 | CLI Colour in PHP 42 | ----------------- 43 | 44 | `cli_colours.php` included in the UWAngel-CLI source is just a collection of 45 | constants which allow you to print out colours in your CLI scripts. 46 | 47 | lang:php 48 | 49 | 108 | 109 | As a complete side note, thanks to a boot-camped installation of Windows 7 (or 110 | at least I'm fairly sure that's the culprit,) my Macbook Pro is now stuck on 111 | Digital Out. This means I can't use the internal speakers on my computer under 112 | Mac OS X. Well... what I should say is that I can't use them without some 113 | annoying tricks. If I plug in my headphones, then tell my mac to use the input 114 | jack for audio input, then my internal speakers appear under the output options 115 | and let me use them. The speakers then work perfectly fine. They also work fine 116 | under Windows 7. A direct side effect of digital out being stuck on is a read 117 | light emanating from the audio jack. 118 | 119 | The problem is apparently fairly common, unfortunately the only confirmed fixes 120 | for it are sending it back to Apple or wiggling a toothpick 122 | around in the audio jack. I had absolutely no luck with the toothpick, or 123 | precision screwdriver, or pen cartridge, or paintbrush handle. If anyone knows 124 | how to fix this problem, I would love to know. Otherwise, I'm just going to take 125 | it into the Apple store in Rideau some time this week and hope they can fix the 126 | problem. I really don't want to have to send my Mac in during my first week at 127 | Velocity. 128 | -------------------------------------------------------------------------------- /_posts/2009-11-14-omegle-voyeur-multiple-connections.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Omegle Voyeur - Multiple Connections 4 | tags: 5 | - AJAX 6 | - javascript 7 | - omegle 8 | - php 9 | - Projects 10 | - prototype 11 | status: publish 12 | type: post 13 | published: true 14 | meta: 15 | _edit_lock: "1259218981" 16 | _edit_last: "1" 17 | --- 18 | In case you haven't read the post about all my projects, here's a description of what Omegle Voyeur is: 19 |
20 | Omegle is a website where you are connected to a stranger for a chat. It is dominated mostly by trolls whose primary purpose is to coerce you into a cyber session and then switch genders or to make you lose the game. Talking to these people is a rather tiresome endeavour, but seeing exactly what happens in these conversations is interesting. Omegle Voyeur is a way of watching a conversation which you aren't part of. What Voyeur does is form two simultaneous connections and then pass the input of one to the output of the other. This sets you up as a conversation proxy, allowing you to watch. Currently, this is exclusively a "sit and watch" program. Later I intend to add functionality to add more than 2 people into a conversation, automatically name the participants so it will be obvious that there are more than 2 people in the conversation, and allow the ability to interfere (mute participants/say things yourself) with a conversation. This concept was spawned during discussion (read: boredom) at CCC Stage 2, 2009. 21 |
22 | 23 | In terms of technology, Omegle Voyeur is primarily one big Javascript Prototype class. Prototype is a Javascript Framework which makes the creation and maintenance of classes, conversion of data into JSON for transfer, and sending AJAX requests much, much easier. 24 | 25 | There's also a very small bit of code in php which is able to be so short because it uses the incredible program cUrl. cUrl is a command line utility for grabbing data from websites using their URL. libcurl facilititates the use of curl in php without having to write your own wrapper. 26 | 27 | Below is some php code I use to make curl even easier than it already is. simple_get($url) will return the HTTP GET result from the url specified. simple_post works similarly, but delivers data using the payload. 28 | 29 | lang:php 30 | 31 | 54 | 55 | When I started working on this project today (well, I suppose that would be last night now... wonder if I'll see the sunrise) I figured it would be a good time to get used to using git, so I made a repository using github. 56 | So far I'm enjoying git. Everything seems to act pretty much the way you'd expect to, and I already had to do a revert once I realized my logic was wrong for the way I was structuring my code. 57 | 58 | You can see the github for Omegle Voyeur here: http://github.com/phleet/Omegle-Voyeur 59 | Feel free to design your own stuff with the all the code there - just be sure to link back here, or to the github page. 60 | 61 | In any case, the thing the majority of the people reading this are probably interested in are the result. 62 | Things I've updated since last time are primarily aesthetic and behind the scenes, but I did add the ability to connect one person to more than one other person, and the connections don't have to be mutual. In the first 3 way conversation example, 1 can only speak to 2, 2 can only speak to 3 and 3 can only speak to 1. It leads to some rather confused people. 63 | 64 | You can see the current running version here: Omegle Voyeur. 65 | EDIT: It seems that after being posted on reddit, omegle has (manually?) IP blocked me. The source should still work, so feel free to try it out yourself. 66 | You can go grab XAMPP to run it locally. 67 | For the time being, you can view it here: Omegle Voyeur 68 | 69 | A quick note on how I figured out the Omegle communication protocol. 70 | The entire code governing the process is conveniently kept here: http://omegle.com/static/omegle.js?27 71 | Unless you enjoy reading 1000s of characters on a single line, you can use the JS Beautifier to clean it up to a readable state. 72 | -------------------------------------------------------------------------------- /_posts/2010-02-05-jobmine-improved-greasemonkey-jquery.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Jobmine Improved (Greasemonkey & jQuery) 4 | tags: 5 | - greasemonkey 6 | - jobmine 7 | - jquery 8 | - Projects 9 | status: publish 10 | type: post 11 | published: true 12 | meta: 13 | _edit_lock: "1285900502" 14 | _edit_last: "1" 15 | --- 16 |
17 | 18 |
19 | 20 | _I will no longer be supporting this script, there's a much better version 21 | called [Jobmine Plus](http://userscripts.org/scripts/show/80771) maintained by 22 | Matthew Ng._ 23 | 24 | I, like many (most) Waterloo Co-op students, am forced to use Jobmine and am 25 | extremely dissatisfied with its functionality. So I decided to kill three birds 26 | with one stone: improve Jobmine, learn Greasemonkey and learn jQuery all at the 27 | same time. 28 | 29 | The result is, unsurprisingly, a Greasemonkey script written using jQuery that improves on some features of Jobmine. 30 | 31 | Features 32 |
    33 |
  • Table sorting - all major tables are now sortable (Interviews, Job Short List, Applications)
  • 34 |
  • Improved navigation - no more Student -> Use ridiculousness
  • 35 |
  • No more frames - you can refresh and it will stay on the same page!
  • 36 |
  • Colour highlighting for tables - pictured above, you see the applications page with various statuses highlighted. Selected is green, not selected is red.
  • 37 |
  • No more spacers - the Jobmine page is riddled with spacer images just sitting there, stealing screen real estate
  • 38 |
39 | 40 | How to Install 41 | You'll either need Firefox & Greasemonkey, or a recent build of Chrome (Windows only?). 42 | You can get Greasemonkey here: Greasemonkey @ addons.mozilla.org 43 | 44 | Once you've done that, navigate to the script and click install. 45 | You can get the script here: Jobmine Upgrade @ userscripts.org 46 | 47 | Now for the part where I explain the tech I used. 48 | 49 |

Greasemonkey

50 | Greasemonkey is a tool for customizing the way a web page displays and interacts using javascript. More or less, it overlays javascript you write on top of pages you specify by URLs with wildcards (*). It doesn't overlay it directly, but wraps it in some way as to prevent it from messing things up in the global scope. It also seems to run once the page is done loading, not when the page head is loaded. There are plenty of tutorials out there for doing cool stuff with Greasemonkey, but I started here: Dive into Greasemonkey. I know it says it's hideously outdated, but the metadata information it provides is still good enough. If you want more up to date information, go here: GreaseSpot (Greasemonkey Wiki). 51 | 52 |

jQuery

53 | jQuery is a javascript framework specifically designed for doing things involving the DOM tree absurdly quickly. Example: highlighting alternating rows of a table (zebra-striping). 54 | 55 | lang:js 56 | 57 | // Standard Javascript method: 58 | var tables = document.getElementsByTagName("table"); 59 | for (var i = 0; i < tables.length; i++) { 60 | var rows = tables[i].tBodies[0].rows; 61 | for (var j = 0; j < rows.length; j++) { 62 | var rowColor; 63 | if (j % 2 == 1) { 64 | rowColor = "#eef"; 65 | } else { 66 | rowColor = "#fff"; 67 | } 68 | 69 | var cells = rows[j].cells; 70 | for (var k = 0; k < cells.length; k++) { 71 | cells[k].style.backgroundColor = rowColor; 72 | cells[k].style.borderBottom = "1px solid #ccc"; 73 | } 74 | } 75 | } 76 | 77 | // jQuery way: 78 | $("td").css("border-bottom","1px solid #ccc"); 79 | $("tr:even > td").css("background-color","#fff"); 80 | $("tr:odd > td").css("background-color","#eef"); 81 | 82 | Now before someone says it, I know usually you can set the background-color for the whole row, and the cells will inherit it. But since, for some crazy reason, each cell is assigned a background colour on Jobmine, each cell needs to be set individually. In any case, you can see that things are made substantially easier with jQuery. I figured out jQuery mostly just using the API and looking at other people's code, but this is a decent place to start: Getting Started with jQuery. 83 | 84 | For the table sorting functionality, I decided to use a jQuery plugin as opposed to write my own (I'd rather be able to distribute this sooner). You can read all about it here: jQuery Plugin: Tablesorter 2.0 85 | 86 | What features do you want to see in this? By the way, the source is all available on the userscripts site, so feel free to tinker with it yourself. 87 | 88 | EDIT: As Trevor points out, the script in its current state won't work in Chrome due to the @require. You can grab his fix to make it work in chrome here: Jobmine Improved (Chrome) 89 | -------------------------------------------------------------------------------- /_posts/2010-08-14-javascript-dependency-loading-jquery-atreq.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Javascript Dependency Loading \xE2\x80\x93 jquery.atreq" 4 | tags: 5 | - javascript 6 | - jquery 7 | status: publish 8 | type: post 9 | published: true 10 | meta: 11 | _edit_last: "1" 12 | _edit_lock: "1287294623" 13 | _wp_old_slug: "" 14 | --- 15 | For a project I'm working on, I have a fairly complex dependency tree and was getting fed up with manually including every single one in my haml layout file. So I started looking up other solutions and eventually decided none met my needs, so, as usual, I wrote my own. 16 | 17 | [jquery.atreq @ github](http://github.com/phleet/jquery.atreq) 18 | 19 | Because there's not much to say beyond what I have in the readme, and because this blog is conveniently set up to format with markdown, I'm just going to display the current state of that here. 20 | 21 | jquery.atreq 22 | ============ 23 | 24 | jquery.atreq loads your javascript dependencies asynchronously. 25 | 26 | How to use 27 | ---------- 28 | 29 | First, include your main application file with `$.atreq` 30 | 31 | lang:html 32 | 33 | 34 | 35 | 38 | 39 | Then inside your application.js and any other required files, place a require statement in the comments. 40 | 41 | lang:js 42 | 43 | // application.js 44 | // @require 'lib/lib1.js' 45 | // @require 'lib/lib2.js' 46 | 47 | Library1Function(); 48 | Library2Function(); 49 | 50 | **NOTE**: While `//@require` "blocks", `$.atreq` does not. This means the following will not work: 51 | 52 | lang:js 53 | 54 | $.atreq('lib/lib1.js'); 55 | Library1Function(); 56 | 57 | Relative Require Paths 58 | ---------------------- 59 | 60 | Unless the require paths begin with a `/`, they're assumed to be relative to the location of the file they're in. 61 | 62 | This means, since `lib1.js` is in `lib/`, the require statement below will load `lib/deb/lib1dep.js`. This makes your code portable across locations. 63 | 64 | Each of the library files can also have their own dependencies, and `$.atreq` will make sure they're run in the correct order. 65 | 66 | lang:js 67 | 68 | // lib/lib1.js 69 | // @require `dep/libdep.js` 70 | 71 | function Library1Function() { 72 | alert('Awesome!"); 73 | } 74 | 75 | Redundant or Duplicate Requires 76 | ------------------------------- 77 | 78 | Duplicate requires of the same file will neither request nor run the same file twice. 79 | This is true even if the relative path is different from files. This means that: 80 | 81 | lang:js 82 | 83 | // lib/lib2.js 84 | // @require '../shared/shared.js' 85 | 86 | and 87 | 88 | lang:js 89 | 90 | // lib/lib3.js 91 | // @require '../shared/shared.js' 92 | 93 | will only make one request out to `shared/shared.js`. 94 | 95 | 96 | Alternatives and How They're Different 97 | -------------------------------------- 98 | 99 | ### $.include 100 | 101 | A jQuery plugin by Tobiasz Cudnik, this does load external files asynchronously, but these dependencies are non-blocking. He dodges this issue by delaying the document.ready event 102 | 103 | It even has the ability to load dependencies for included scripts like so (taken from his blog post): 104 | 105 | lang:js 106 | 107 | $.include( 108 | // URL 109 | 'js/my-script.js', 110 | // will be loaded after this script 111 | $.include(baseURL+'js/my-other-script.js') 112 | ); 113 | $.include('js/src/behaviors.js', 114 | // dependencies can also be an array 115 | [ 116 | $.include('js/src/jquery-metadata.js'), 117 | $.include('js/src/jquery.form.js') 118 | ] 119 | ); 120 | 121 | This is great, except that it requires all the dependencies to be loaded from the same file. 122 | This means you won't get useful behaviour if one of the files you're including has internal dependencies. 123 | 124 | See: [$.include() @ tobiasz123.wordpress.com](http://tobiasz123.wordpress.com/2007/08/01/include-script-inclusion-jquery-plugin/) 125 | 126 | ### $.require 127 | 128 | Another jQuery plugin, `$.require` doesn't have the problem of `$.include`: required files can require files of their own and scripts will still be parsed in the correct order. 129 | 130 | The way it does this is it forces the scripts to be included synchronously. Functionality wise, this has no effect. However, it will result in slower load times for complex dependency trees. 131 | 132 | The require paths in this script are relative to the inclusion point (the HTML file), not the script itself. 133 | 134 | Comparison of load times for the following dependency tree: 135 | 136 | application.js 137 | |--- 1.js 138 | | |--- 1a.js 139 | | |--- 1b.js 140 | | \--- shared.js 141 | \--- 2.js 142 | |--- 2a.js 143 | |--- 2b.js 144 | \--- shared.js 145 | 146 | Inside each file is the required include statements, a loop iterating 10000 times, and ~4kb of lorem ipsum in comments to bloat the file size. 147 | 148 | $.atreq: 149 | 150 | ![$.atreq firebug](/images/atreqfirebug.png) 151 | 152 | $.require: 153 | 154 | ![$.require firebug](/images/dotreqfirebug.png) 155 | 156 | Note the difference in the load order - there's no reason why 1.js and 2.js shouldn't load at the same time. 157 | 158 | 159 | See: [$.require @ plugins.jquery.com](http://plugins.jquery.com/project/require) 160 | 161 | **WARNING**: At time of writing, the script provided on that page does not work and makes some strange assumptions about the location of your javascript files on the server. Use with caution. 162 | -------------------------------------------------------------------------------- /_posts/2009-12-12-manual-sorting.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Manual Sorting 4 | tags: 5 | - algorithm 6 | - cards 7 | - sorting 8 | status: publish 9 | type: post 10 | published: true 11 | meta: 12 | _edit_lock: "1261898946" 13 | _edit_last: "1" 14 | --- 15 |
16 | 17 |
18 | 19 | The basis for this post is the following question: what is the fastest algorithm for sorting a deck of cards by hand? 20 | 21 | TL:DR advisor - skip to the end of the post for a challenge. 22 | 23 | In terms of algorithmic complexity, merge sort and quick sort are two of the fastest widely used sorting algorithms - both running at an average case of O(n log n). But if you've ever sorted a deck of cards, ordering primarily by suit and secondly by number, I doubt you used either of these algorithms. You might unknowingly be doing bucket sort, dividing the cards into 4 "buckets" - one for each suit, then sorting each suit using a different algorithm and joining all the suits together at the end. 24 | 25 | If I had a deck of cards in my room right now, I would be inclined to take videos of me sorting them using various different algorithms and comparing the time required. I have a feeling that the fastest algorithm would involve drawing out a 4x13 grid on a big piece of paper with each cell labeled with the exact card that fits there, then running through the deck, placing all the cards on their grid cell and just picking them up in order at the end. Of course, this could be accomplished without the grid but requires a spacial sense which I simply do not possess. 26 | 27 | As a followup, I have another question: what if the cards were labeled with a number system you've never seen before? Assume you have some visual reference allowing you to understand the system, but also assume that the number system is not intuitive. Does your approach change? Of course, the fastest approach here depends on what I'm really asking by "fastest". Given enough time and practice, you would be able to become familiar enough with the numeral system to use any approach you would with regular cards or regular decimal numbers. When I say "fastest", I'm asking how you would minimize the time between receiving the cards and the visual reference and the cards being sorted. In terms of computation, the introduction of this numeral system has drastically increased the time for a comparison or enumeration while not affecting the time for movement or swapping at all. The reverse would be to use the standard deck of cards but make them orders of magnitude larger - say 1 meter wide each. In this case a move or a swap is extremely costly, but a comparison is very cheap. 28 | 29 | A more realistic scenario in which the cost of comparison is drastically increased comes about when the criteria used for sorting is not an absolute. Example: sort these pictures by aesthetic appeal. Even by yourself this may be a long process, as you second guess your original decision about the relative appeal of a picture. In the world of web 2.0 though, it's a much much longer process. Sorting by crowd-sourced opinion is the basis of many websites, such as bash.org and reddit. Most of these systems work by providing users with the ability or increase or decrease the item's value by small amounts. Is there some better way of ensuring that the articles which will be the most valuable to the readers will show up at the top? 30 | 31 | Returning to the point of manual sorting, I have 2 more things to say. 32 | 33 | The first is an idea - competitive sorting. I'm sure this sounds nerdy as hell, but I think I'd still find it fairly entertaining. Groups of people would be given sets of objects and told to sort by some criteria, the fastest group being the victor, with penalties dished out for people falsely claiming their list is sorted. The criteria could be size, weight, volume, buoyancy, color saturation, retail price, alcohol content, power consumption or even something as obscure as average salary of a worker for the company manufacturing the product. Given that I'm at Waterloo, I feel that organizing such a competition isn't all that unlikely. Anyone feel like coming up with a plan for making this actually happen? 34 | 35 | The second is a challenge. I challenge everyone reading this to make a video of them sorting a deck of 52 cards (no jokers) as fast as they can, then post the video in the comment section. As soon as I actually get a deck of cards, I'll take part in this challenge myself. 36 | 37 | Rules. 38 |
    39 |
  1. The video must be all in one take
  2. 40 |
  3. The deck must be shuffled thoroughly on screen, then fanned towards the camera to demonstrate the randomized order of the cards.
  4. 41 |
  5. Once the cards have been fanned towards the camera, you can have a maximum of 10 seconds of review time looking through the cards before the sorting starts. All cards must stay in contact and in order during this review time.
  6. 42 |
  7. The timer starts the second a card is separated from the rest of the deck.
  8. 43 |
  9. The timer stops when the deck has been reassembled into a single sorted deck.
  10. 44 |
  11. A deck is considered sorted if the cards are primarily ordered by suit in the order diamonds, clubs, hearts, spades, and secondarily ascending numerically.
  12. 45 |
  13. You must fan the deck towards the camera after it is sorted.
  14. 46 |
  15. You are allowed any setup you want before starting to sort (e.g. the grid I talked about above,) provided that the setup does not contain moving parts - no robots.
  16. 47 |
  17. You must be the only one handling the cards - no team efforts.
  18. 48 |
49 | 50 | That's all. I fully expect to receive no responses to this challenge, but would love to see the things people come up with if someone does try it. 51 | 52 | EDIT: Woohoo! Someone attempted! 53 | Timothy Armstrong Sorting 54 | 55 | Also, the claimed record according to this page is 36.1 seconds. 56 | -------------------------------------------------------------------------------- /_posts/2010-04-19-ajax-method-callbacks-and-omegle-voyeur-update.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: AJAX Method Callbacks and Omegle Voyeur Update 4 | tags: 5 | - AJAX 6 | - callbacks 7 | - jquery 8 | - method 9 | - omegle 10 | - omegle voyeur 11 | - Projects 12 | status: publish 13 | type: post 14 | published: true 15 | meta: 16 | _edit_lock: "1274333018" 17 | _edit_last: "1" 18 | --- 19 | I finally got back around to updating Omegle Voyeur with the ability to 20 | interfere, and decided to re-implement the whole thing in jQuery while I'm at 21 | it. Since jQuery doesn't come with a built-in method of building classes, I used 22 | lowpro for 23 | jQuery. It's a port of a class building scheme from Prototype. It doesn't do 24 | everything I could have hoped for, but it served most of my needs. 25 | 26 | The other thing I implemented was a way of knowing when Omegle is blocking 27 | requests. They have a more robust form of detection now - it isn't just manual 28 | IP ban. Once you request too many things from them too fast, they start 29 | requesting a captcha. Locally, this isn't a problem - I simply embed an iframe 30 | with Omegle in it and provide instructions to the user. Hosted, this is a more 31 | troublesome problem, since the captcha is directed towards an IP, so it must be 32 | responded to from that IP. I have no solution to this problem at the moment, but 33 | I'm going to look into implementing the whole thing using Greasemonkey so this 34 | isn't an issue at all. 35 | 36 | For now, you can see the latest version here: Omegle Voyeur. 38 | Don't be surprised if it's down, and please go grab your own copy: Omegle-Voyeur @ github. 40 | 41 | Now on to the customary technical concept to go along with my own self promotion. 42 | 43 |

AJAX Method Callbacks

44 | While passing functions as arguments is a pretty standard thing among almost 45 | all languages, attempting to pass methods of specific instances as arguments in 46 | JavaScript presents an interesting problem. Consider the following: 47 | 48 | lang:js 49 | 50 | function car(price) { 51 | this.price = price; 52 | 53 | this.setPrice = function(price) { 54 | this.price = price; 55 | }; 56 | } 57 | 58 | function pass666(func) { 59 | func(666); 60 | } 61 | 62 | var redcar = new car(2000); 63 | alert(redcar.price); 64 | redcar.setPrice(123); 65 | alert(redcar.price); 66 | pass666(redcar.setPrice); 67 | alert(redcar.price); 68 | 69 | As you might expect, the first two alerts will say 2000 and 123 respectively. 70 | But the last one also says 123. Why? 71 | 72 | It all has to do with what `this` refers to. Both in the initialization of 73 | `redcar` and the modifier call `redcar.setPrice`, "this" refers to the instance 74 | of the function car given the identifier name `redcar`. In the `pass666` 75 | version, `this` refers to the function `pass666`. As a result, it does nothing 76 | to modify the properties of the car because it isn't told anything about 77 | `redcar`. 78 | 79 | One way to fix this is to use a placeholder variable. I used "self". Change the 80 | definition of car to the following yields the desired result. 81 | 82 | lang:js 83 | 84 | function car(price) { 85 | this.price = price; 86 | 87 | var self = this; 88 | this.setPrice = function(price) { 89 | self.price = price; 90 | }; 91 | } 92 | 93 | In this example, it's difficult to see why you would ever want to use this in 94 | the first place. The reason I encountered this problem is my need to use 95 | instance methods as callback functions for AJAX calls. Here's an excerpt of the 96 | jQuery version of Omegle Voyeur to see what I'm talking about. 97 | 98 | lang:js 99 | 100 | sendQuery: function(target,respFunc) { 101 | // Send a query to the omegle server 102 | //log('sending'); 103 | var self = this; 104 | if (respFunc == null) { 105 | respFunc = function(self,data) {} 106 | } 107 | $.ajax({ 108 | url: 'omegle.php?'+target, 109 | type: 'GET', 110 | dataType: 'json', 111 | success: function(data) { 112 | respFunc(self,data); 113 | } 114 | }); 115 | }, 116 | 117 | Sending a request to the Omegle server is a very common task in Omegle Voyeur, 118 | so I wanted all the AJAX requests leaving from the same method. This means I 119 | have to accept the callback function as a parameter. I've written all the 120 | callback methods to accept a parameter `self` which will refer to the instance 121 | of interest. 122 | 123 | This aspect is one of the many things that makes Prototype's class system 124 | superior to jQuery's. However, since jQuery makes a lot of other things nicer 125 | and the two libraries don't play together very well, I decided to port over to 126 | jQuery nonetheless. In Prototype, there's a function called bind (not to be confused 128 | with jQuery's bind which does 129 | something completely different,) which solves this problem elegantly. 130 | 131 | To fix the redcar problem with the aid of Prototype without having to use a 132 | placeholder variable, you can use bind like so: 133 | 134 | lang:js 135 | 136 | function car(price) { 137 | this.price = price; 138 | 139 | this.setPrice = function(price) { 140 | this.price = price; 141 | }; 142 | } 143 | 144 | function pass666(func) { 145 | func(666); 146 | } 147 | 148 | var redcar = new car(2000); 149 | alert(redcar.price); 150 | redcar.setPrice(123); 151 | alert(redcar.price); 152 | 153 | pass666(redcar.setPrice.bind(redcar)); 154 | 155 | alert(redcar.price); 156 | 157 | 158 | The line `pass666(redcar.setPrice.bind(redcar));` is what makes this work out. 159 | We're explicitly saying that we want `setPrice` executed from the scope of the 160 | instance. 161 | 162 | If I ever have to hire someone for a web development job, I'll be sure to ask 163 | something about this. 164 | -------------------------------------------------------------------------------- /_posts/2010-10-16-dfas-and-graphviz-dot.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: DFAs and Graphviz DOT 4 | tags: 5 | - cs241 6 | - dot 7 | - graphviz 8 | - ruby 9 | - school 10 | status: publish 11 | type: post 12 | published: true 13 | meta: 14 | _edit_lock: "1287296820" 15 | _edit_last: "1" 16 | _wp_old_slug: "" 17 | --- 18 | As is usually the case with CS assignments, I tend to focus on reducing debugging time as much as possible by spending time coding things to make my life (and hopefully yours) easier. 19 | 20 | On the latest assignment we are required to describe DFAs (Deterministic Finite Automatas) in a [format][] with very low human readability, I knew immediately that I was going to write something to allow me to write my DFAs in a more human readable format so that I could sanely debug them. So instead of this: 21 | 22 | 2 23 | 0 24 | 1 25 | 5 26 | start 27 | zero 28 | 0mod3 29 | 1mod3 30 | 2mod3 31 | start 32 | 2 33 | zero 34 | 0mod3 35 | 8 36 | start 0 zero 37 | start 1 1mod3 38 | 1mod3 0 2mod3 39 | 1mod3 1 0mod3 40 | 2mod3 0 1mod3 41 | 2mod3 1 2mod3 42 | 0mod3 0 0mod3 43 | 0mod3 1 1mod3 44 | 45 | I can do this in Ruby: 46 | 47 | lang:ruby 48 | 49 | dfa = { 50 | :alphabet => %w( 51 | 0 52 | 1 53 | ), 54 | :states => %w( 55 | start 56 | zero 57 | 0mod3 58 | 1mod3 59 | 2mod3 60 | ), 61 | :initial => 'start', 62 | :final => %w( 63 | zero 64 | 0mod3 65 | ), 66 | :transitions => { 67 | 'start' => [ 68 | ['0','zero'], 69 | ['1','1mod3'] 70 | ], 71 | '1mod3' => [ 72 | ['0','2mod3'], 73 | ['1','0mod3'] 74 | ], 75 | '2mod3' => [ 76 | ['0','1mod3'], 77 | ['1','2mod3'] 78 | ], 79 | '0mod3' => [ 80 | ['0','0mod3'], 81 | ['1','1mod3'] 82 | ] 83 | } 84 | } 85 | 86 | For those of you not familiar with ruby, `%w(one two three)` is equivalent to `['one','two','three']`. Just a bit of syntactic sugar. 87 | 88 | You can generate the DFA format required by Marmoset with the following function: 89 | 90 | lang:ruby 91 | 92 | def dfa_output(dfa) 93 | lines = [] 94 | lines << dfa[:alphabet].length.to_s 95 | dfa[:alphabet].each {|c| lines << c.to_s} 96 | 97 | lines << dfa[:states].length.to_s 98 | dfa[:states].each {|s| lines << s.to_s} 99 | 100 | lines << dfa[:initial].to_s 101 | 102 | lines << dfa[:final].length.to_s 103 | dfa[:final].each {|f| lines << f.to_s} 104 | 105 | transitions = [] 106 | dfa[:transitions].each do |start,pair_array| 107 | pair_array.each do |(sym,targ)| 108 | if sym.is_a? String 109 | transitions << "#{start} #{sym} #{targ}" 110 | else 111 | sym.each do |s| 112 | transitions << "#{start} #{s} #{targ}" 113 | end 114 | end 115 | end 116 | end 117 | 118 | lines << transitions.length.to_s 119 | lines += transitions 120 | 121 | return lines.join("\n") 122 | end 123 | 124 | And then invoking with 125 | 126 | lang:ruby 127 | 128 | puts dfa_output(dfa) 129 | 130 | I designed the function with ranges and multiple transition tokens in mind, so you can do stuff like this: 131 | 132 | lang:ruby 133 | 134 | 'start_state' => [ 135 | [%w(one that other),'end_state'], 136 | [1..9,'X'] 137 | ] 138 | 139 | Now, this makes fixing problems, once discovered, much much simpler than trying to locate the error in the DFA format, but it still doesn't help much by way of identifying problems in the DFA itself. Probably the easiest way to look at a DFA is as a picture. Thankfully, there exists a set of tools perfect for this task known as [Graphviz][]. 140 | 141 | Graphviz DOT 142 | ======== 143 | ![Sample Graph](http://imgur.com/FBFpb.png) 144 | 145 | Graphviz is a collection of utilities used for visualizing graphs. The tool of interest for this post is DOT. 146 | It produces the kind of thing you can see above - which is the same graph described by the ruby code and the DFA format above. 147 | 148 | The syntax of DOT is very simple, and is better explained by [the DOT language wiki entry][dotwiki] than by the official documentation. 149 | The DOT description of the above graph is as follows: 150 | 151 | digraph dfa { 152 | "" [shape=none] 153 | "start" [shape=circle] 154 | "zero" [shape=doublecircle] 155 | "0mod3" [shape=doublecircle] 156 | "1mod3" [shape=circle] 157 | "2mod3" [shape=circle] 158 | 159 | "" -> "start" 160 | "1mod3" -> "2mod3" [label="0"] 161 | "1mod3" -> "0mod3" [label="1"] 162 | "0mod3" -> "0mod3" [label="0"] 163 | "0mod3" -> "1mod3" [label="1"] 164 | "2mod3" -> "1mod3" [label="0"] 165 | "2mod3" -> "2mod3" [label="1"] 166 | "start" -> "zero" [label="0"] 167 | "start" -> "1mod3" [label="1"] 168 | } 169 | 170 | And this is the ruby code to generate that output using the `dfa = { ... }` format at the beginning of the post. 171 | 172 | lang:ruby 173 | 174 | def dot_output(dfa) 175 | lines = [] 176 | lines << "digraph dfa {" 177 | 178 | lines << %(\t"" [shape=none]) 179 | 180 | dfa[:states].each do |state| 181 | if (dfa[:final].include? state) 182 | lines << %(\t"#{state}" [shape=doublecircle]) 183 | else 184 | lines << %(\t"#{state}" [shape=circle]) 185 | end 186 | end 187 | 188 | lines << '' 189 | lines << %(\t"" -> "#{dfa[:initial]}") 190 | 191 | dfa[:transitions].each do |start,pair_array| 192 | pair_array.each do |(sym,targ)| 193 | if sym.is_a? String 194 | lines << %(\t"#{start}" -> "#{targ}" [label="#{sym}"]) 195 | else 196 | lines << %(\t"#{start}" -> "#{targ}" [label="[#{sym.collect(&:to_s).join(',')}]"]) 197 | end 198 | end 199 | end 200 | 201 | lines << "}" 202 | 203 | return lines.join("\n") 204 | end 205 | 206 | Once you've installed Graphviz, you can generate a PNG of the graph by running 207 | 208 | lang:bash 209 | 210 | dot -Tpng < graph.dot > graph.png 211 | 212 | Enjoy CS241 A5. 213 | 214 | [dotwiki]: http://en.wikipedia.org/wiki/DOT_language 215 | [Graphviz]: http://www.graphviz.org/ 216 | [format]: http://www.student.cs.uwaterloo.ca/~cs241/dfa/DFAfileformat.html 217 | -------------------------------------------------------------------------------- /_posts/2011-01-12-hacker-cup-qualification-round-solutions.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Hacker Cup Qualification Round - Solutions 4 | tags: [] 5 | 6 | status: publish 7 | type: post 8 | published: true 9 | meta: 10 | _edit_lock: "1294822476" 11 | _edit_last: "1" 12 | _wp_old_slug: "" 13 | --- 14 | Overall, I was fairly disappointed with the organization and general structure of hacker cup qualification round. Hopefully the next round will be better, but for now - here are the solutions to the three questions. Unfortunately it seems the questions themselves have been taken down. I did manage to score 3/3, so I can be fairly sure that these are correct to the spec of the questions Facebook wrote. 15 | 16 | Squares 17 | ===== 18 | 19 | **Problem**: For each given z, Find the # of pairs (x,y) such that x <= y and x^2 + y^2 = z. 20 | 21 | **Solution**: Pre-generate the set of perfect squares. Iterate through this set. Check to see if z - x^2 is also in this set, where x is the current perfect square. 22 | 23 | **Implementation**: 24 | 25 | lang:cpp 26 | 27 | #include 28 | #include 29 | #include 30 | using namespace std; 31 | 32 | typedef long long unsigned int llu; 33 | set squares; 34 | 35 | int main() { 36 | for (int i = 0; i < 50000; i++) { 37 | llu i2 = i*i; 38 | if (i2 > 2147483647L * 2L) { 39 | break; 40 | } else { 41 | squares.insert(i2); 42 | } 43 | } 44 | 45 | int N; 46 | cin >> N; 47 | 48 | for (int i = 0; i < N; i++) { 49 | int num; 50 | cin >> num; 51 | int ans = 0; 52 | for(set::iterator it = squares.begin(); it != squares.end(); ++it) { 53 | llu first = *it; 54 | if (2 * first > num) break; 55 | if (squares.count(num - first)) { 56 | ans++; 57 | } 58 | } 59 | cout << ans << endl; 60 | } 61 | } 62 | 63 | Students 64 | ====== 65 | 66 | **Problem**: For a given set of words (sequences of lowercase letters), find the the lexicographically lowest string result from the concatenation of these words in any order. 67 | 68 | **Solution**: I'm fairly sure simply sorting the words then concatenating them would suffice, but I was having issues string comparison yielding the same results at Facebook's output. Since the constraints were so low - maximum of 9 words per set, I simply did it exhaustively. This finishes easily within the 6 minute time limit since 9 factorial is relatively small. 69 | 70 | **Implementation**: 71 | 72 | lang:python 73 | 74 | from itertools import permutations 75 | 76 | def doit(): 77 | words = raw_input().split()[1:] 78 | 79 | best = "" 80 | 81 | for x in permutations(words): 82 | concatted = "".join(x) 83 | if best == "" or concatted < best: 84 | best = concatted 85 | 86 | print best 87 | 88 | n = input() 89 | 90 | for z in range(n): 91 | doit() 92 | 93 | Pegs 94 | === 95 | 96 | **Problem**: Given a peg board in the format like this: 97 | 98 | x.x.x 99 | x.x 100 | x.x.x 101 | 102 | defined by the number of rows and cols of pegs, except with a few pegs missing, like this: 103 | 104 | x.x.x.x.x 105 | x...x.x 106 | x...x.x.x 107 | x.x...x 108 | x.x.x.x.x 109 | 110 | Determine the optimal location to drop a ball in order to maximize the probability of the ball landing in a specific slot in the bottom row. The probability of the ball going to either side of a peg is 0.5. 111 | 112 | **Solution**: There's no real trick to this one - you just create an array of the probabilities that the ball reaches each available cell given which pegs are missing. This one is more of a coding problem than anything else. 113 | 114 | I'm sure I could have written a more elegant solution to this problem, but I just wanted to get it done. 115 | 116 | **Implementation**: 117 | 118 | lang:cpp 119 | 120 | #include 121 | #include 122 | #include 123 | #include 124 | using namespace std; 125 | 126 | void doit() { 127 | int R, C, K, M; 128 | cin >> R >> C >> K >> M; 129 | set > missing; 130 | for (int i = 0; i < M; i++) { 131 | int ri, ci; 132 | cin >> ri >> ci; 133 | missing.insert(make_pair(ri,ci)); 134 | } 135 | 136 | int best_pos = 0; 137 | long double best_prob = 0; 138 | for (int cur_pos = 0; cur_pos < C-1; cur_pos++) { 139 | long double probs[R][C]; 140 | for (int r = 0; r < R; r++) { 141 | for (int c = 0; c < C; c++) { 142 | probs[r][c] = 0.00; 143 | } 144 | } 145 | probs[0][cur_pos] = 1.00; 146 | 147 | for (int r = 1; r < R; r++) { 148 | if (r % 2 == 1) { // odd row 149 | for(int peg = 0; peg < C-1; peg++) { 150 | if (missing.count(make_pair(r,peg))) { 151 | if (r+1 < R) probs[r+1][peg] += probs[r-1][peg]; 152 | } else { 153 | if (peg == 0) { 154 | probs[r][peg] += probs[r-1][peg]; 155 | } else if (peg == C-2) { 156 | probs[r][peg-1] += probs[r-1][peg]; 157 | } else { 158 | probs[r][peg] += 0.5 * probs[r-1][peg]; 159 | probs[r][peg-1] += 0.5 * probs[r-1][peg]; 160 | } 161 | } 162 | } 163 | } else { // even row 164 | for(int peg = 1; peg < C-1; peg++) { 165 | if (missing.count(make_pair(r,peg))) { 166 | if (r+1 < R) probs[r+1][peg-1] += probs[r-1][peg-1]; 167 | } else { 168 | probs[r][peg] += 0.5 * probs[r-1][peg-1]; 169 | probs[r][peg-1] += 0.5 * probs[r-1][peg-1]; 170 | } 171 | } 172 | } 173 | } 174 | 175 | long double cur_prob = probs[R-1][K]; 176 | if (cur_prob > best_prob) { 177 | best_prob = cur_prob; 178 | best_pos = cur_pos; 179 | } 180 | } 181 | 182 | printf("%d %.6Lf\n", best_pos, best_prob); 183 | } 184 | 185 | int main() { 186 | int z; 187 | cin >> z; 188 | while(z--)doit(); 189 | } 190 | -------------------------------------------------------------------------------- /_posts/2010-06-29-commandline-tips-and-tricks.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Commandline Tips and Tricks 4 | tags: 5 | - bash 6 | - commandline 7 | - shell 8 | status: publish 9 | type: post 10 | published: true 11 | meta: 12 | _edit_last: "1" 13 | _edit_lock: "1287294635" 14 | --- 15 | Alas, I have been stricken with the plague of half-finished posts and projects. But, as I feel the need to have at least one post per month, here's some stuff you might find useful. 16 | 17 | I forget where I discovered this, but commandlinefu.com 19 | is an awesome site. 20 | 21 | It's so awesome in fact, that I think the productivity gains I've gotten from it almost outweigh the time I wasted looking through the site and testing it. 22 | 23 | Here are some of the more useful ones from the site and a few I learned at work and elsewhere. 24 | 25 |

Change to previous working directory

26 | 27 | From: Change to the previous working directory @ commandlinefu.com 28 | 29 | 30 | lang:bash 31 | 32 | $ pwd 33 | /Users/jamiewong/code/stackoverflow 34 | 35 | $ cd /usr/bin 36 | 37 | $ pwd 38 | /usr/bin 39 | 40 | $ cd - 41 | /Users/jamiewong/code/stackoverflow 42 | 43 | $ pwd 44 | /Users/jamiewong/code/stackoverflow 45 | 46 |

Rerun the last command

47 | From: Run the previous command with sudo @ commandlinefu.com 48 | 49 | lang:bash 50 | 51 | $ echo hello 52 | hello 53 | 54 | $ !! 55 | echo hello 56 | hello 57 | 58 | $ ln -s some_script.sh /usr/bin/some_script 59 | ln: /usr/bin/some_script: Permission denied 60 | 61 | $ sudo !! 62 | sudo ln -s some_script.sh /usr/bin/some_script 63 | 64 | $ ls -l /usr/bin/some_script 65 | lrwxr-xr-x 1 root wheel 14 30 Jun 00:44 /usr/bin/some_script -> some_script.sh 66 | 67 | $ cat /etc/apache2/passenger_pane_vhosts/searchgraph* 68 | 69 | ServerName searchgraph.local 70 | DocumentRoot "/Users/jamiewong/Code/searchgraph/public" 71 | RailsEnv development 72 | 73 | Order allow,deny 74 | Allow from all 75 | 76 | 77 | 78 | $ echo "!!" 79 | echo "cat /etc/apache2/passenger_pane_vhosts/searchgraph*"; 80 | cat /etc/apache2/passenger_pane_vhosts/searchgraph.local.vhost.conf 81 | 82 |

Modify the last command and run it

83 | From: Runs the previous command but replacing @ commandlinefu.com 84 | 85 | lang:bash 86 | 87 | $ echo hello world 88 | hello world 89 | 90 | $ ^world^friends 91 | echo hello friends 92 | hello friends 93 | 94 | $ ls *.php 95 | bolding.php shd.php stream.php 96 | 97 | $ ^php^rb 98 | ls *.rb 99 | hms.rb split_orderby.rb 100 |
101 |     $ cds ..
102 |     -bash: cds: command not found
103 |     
104 |     $ ^s
105 |     cd ..
106 | 
107 | 

List your last n commands

108 | 109 | lang:bash 110 | 111 | $ history 10 112 | 522 locate mysqld 113 | 523 sudo /Library/StartupItems/MySQLCOM/MySQLCOM start 114 | 524 sudo /Library/PreferencePanes/MySQL.prefPane/Contents/MacOS/MySQL 115 | 525 sudo mysqld_safe 116 | 526 sudo /usr/local/mysql/bin/mysqld_safe 117 | 527 mysql 118 | 528 history 119 | 529 history | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head 120 | 530 history 121 | 531 history 10 122 | 123 |

Edit and run a previous command

124 | From: Edit the last command line in an editor then execute @ commandlinefu.com 125 | 126 | This one requires a bit of explanation. The `fc` command will, by default, open 127 | your last command in the console editor you specify (emacs by default). 128 | If you want it to be vim, stick this in your `.bash_profile` to just run 129 | it when you want to use it. 130 | 131 | lang:bash 132 | 133 | export EDITOR=vim 134 | 135 | Then run `fc`, and it will open up your editor with your last command in it. 136 | Save and exit (`:wq`) to run that command. 137 | If you just want to execute it immediately, use `fc -s`. 138 | 139 | lang:bash 140 | 141 | $ history 5 142 | 86 cat /etc/apache2/passenger_pane_vhosts/searchgraph.local.vhost.conf 143 | 87 echo "cat /etc/apache2/passenger_pane_vhosts/searchgraph.local.vhost.conf " 144 | 88 history 10 145 | 89 echo "cat /etc/apache2/passenger_pane_vhosts/searchgraph.local.vhost.conf " 146 | 90 history 5 147 | 148 | $ fc -s 87 149 | echo "cat /etc/apache2/passenger_pane_vhosts/searchgraph.local.vhost.conf " 150 | cat /etc/apache2/passenger_pane_vhosts/searchgraph.local.vhost.conf 151 | 152 | If you don't care to look up the history number but happen to remember that it 153 | was the command you executed two lines ago, you can use negative history numbers 154 | 155 | $ echo hello 156 | hello 157 | 158 | $ echo world 159 | world 160 | 161 | $ fc -s -2 162 | echo hello 163 | hello 164 | 165 |

Follow new output to a log

166 | This one comes from my coworkers 167 | 168 | lang:bash 169 | 170 | $ tail -f ~/code/searchgraph/log/development.log 171 | 172 | 173 | Processing HomeController#bp (for 127.0.0.1 at 2010-06-23 22:19:27) [GET] 174 | Rendering home/bp 175 | Completed in 6ms (View: 5, DB: 0) | 200 OK [http://searchgraph.local/bp] 176 | 177 | 178 | Processing HomeController#bp (for 127.0.0.1 at 2010-06-23 22:20:46) [GET] 179 | Rendering home/bp 180 | Completed in 9ms (View: 7, DB: 0) | 200 OK [http://searchgraph.local/bp] 181 | 182 | Then if I go open up searchgraph.local, my console window will get updated on 183 | its own 184 | 185 |

Copying and pasting from files

186 | 187 | The pbcopy and pbpaste commands copy and paste from your clipboard. 188 | 189 | lang:bash 190 | 191 | $ echo test > test.txt 192 | 193 | $ pbcopy < test.txt 194 | 195 | $ pbpaste 196 | test 197 | 198 | And if you want to paste content you have in your clipboard to a file, just 199 | redirect, with 200 | 201 | lang:bash 202 | 203 | $ pbpaste > output.txt 204 | 205 | That's all for now - I'll write more down as I find them. 206 | -------------------------------------------------------------------------------- /_posts/2012-04-16-declarative-programming-and-autosubscribe.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Declarative Programming and Autosubscribe 4 | tags: 5 | - javascript 6 | - api 7 | --- 8 | 9 | While I was watching the [Meteor screencast]() (if you haven't already seen it, 10 | go watch it), I paused around 1:35. They had just said "There's no callbacks, no 11 | bindings, and no controllers. It just works." after showing off the code 12 | something like the following: 13 | 14 | lang:javascript 15 | 16 | Template.color_list.colors = function() { 17 | return Colors.find({}, {sort: {likes: -1, name: 1}}); 18 | } 19 | 20 | along with a [handlebars][] template kind of like this: 21 | 22 | {% raw %} 23 | 24 | lang:html 25 | 26 | 35 | 36 | {% endraw %} 37 | 38 | From an application developer's perspective, I thought "Wow. That is really 39 | amazing". This is a perfect example of how incredible working with declarative 40 | programming can be. Meteor itself focuses on reactive programming, but it 41 | facilitates declarative style in its live templates. 42 | 43 | From working on my own libraries and frameworks, I thought "Wow. How the hell 44 | did they get that to work?" More on this later. 45 | 46 | [Meteor screencast]: http://meteor.com/screencast 47 | [handlebars]: http://handlebarsjs.com/ 48 | 49 | Declarative Programming (The What) 50 | ================================== 51 | 52 | Whenever you're trying to make anything, you want to spend your time figuring 53 | out _what_ you want in your application, not _how_ to get it there. Declarative 54 | programming tries to shift the focus from control flow to logic and state. 55 | 56 | A prime example of this is the divide between the creation and updating of views 57 | in imperative style. For example, consider a simple view that wraps an `h1` 58 | that reflects some data stored in a model. 59 | 60 | Ultimately the goal here is to have an `h1` that says "Salutations, NAME", where 61 | NAME is always synchronized with the model. 62 | 63 | Imperative Creation and Update 64 | ------------------------------ 65 | 66 | If you were to write it in this style, you would probably do it with jQuery or 67 | some similar DOM manipulation library, so that's what we'll use here. 68 | 69 | lang:javascript 70 | 71 | var HelloView = function() { 72 | this.h1 = $('

'); 73 | 74 | var self = this; 75 | model.bind('update', function() { 76 | self.update(); 77 | }); 78 | self.update(); 79 | }; 80 | 81 | HelloView.prototype.update = function() { 82 | this.h1.text("Salutations, " + Model.get('name') + "!"); 83 | } 84 | 85 | Mentally, I had to translate the original task into "create an element, 86 | listen for any updates to the model, then update the view to match the model". 87 | This isn't so bad, and is definitely better than the model knowing about the 88 | view, but there's room for improvement. 89 | 90 | Namely, it's very apparent that the creation of the elements and the 91 | synchronization of the data exist as two distinct tasks, when they really don't 92 | need to. 93 | 94 | Declarative Creation and Update 95 | ------------------------------- 96 | 97 | Here's the same example written in Meteor style declarative programming. 98 | 99 | The template code: 100 | 101 | {% raw %} 102 | 103 | lang:html 104 | 105 | 108 | 109 | {% endraw %} 110 | 111 | And the JavaScript: 112 | 113 | lang:javascript 114 | 115 | Template.hello_view.name = function() { 116 | return Model.get('name'); 117 | } 118 | 119 | In this style, we aren't manually wiring up synchronization at all. We aren't 120 | thinking in a "when this, then that" kind of mindset. We just say "this is how 121 | it always should be". As a side effect, the mental separation between create and 122 | update becomes unnecessary. You don't need to consider what happens when the 123 | model changes - the view just _always_ reflects the model. 124 | 125 | While the amount of code isn't significantly different between these two 126 | examples, I believe that as applications become more complex, the savings in the 127 | declarative style will increase, and the logic will be easier to follow. 128 | 129 | Autosubscribe (The How) 130 | ======================= 131 | 132 | I've done a bunch of JS development by now, and this is one of the first things 133 | I've tripped over in a long time thinking "How is this possible?". I'm not 134 | talking about how the task was algorithmically complicated, I'm talking about 135 | how it looked like there was something essential _missing_ from the code. 136 | 137 | In particular, I couldn't understand how the views knew when to update, since 138 | there was no event binding anywhere. 139 | 140 | The solution comes from Meteor's concepts of "autosubscribe". 141 | 142 | The basic idea is to establish a global context that things are currently 143 | running in and check this whenever any mutable data is accessed. If the accessor 144 | notices that a global context is active, it will subscribe that context to be 145 | re-run when that data changes. 146 | 147 | A basic implementation would look something like this: 148 | 149 | lang:javascript 150 | 151 | var Magic = { 152 | context: null 153 | autosubscribe: function(cb) { 154 | Magic.context = cb; 155 | Magic.context(); 156 | } 157 | }; 158 | 159 | var Model = function() { 160 | this.props = {}; 161 | }; 162 | 163 | Model.prototype.get = function(prop) { 164 | if (Magic.context != null) { 165 | // An autosubscribe context is active, so subscribe it to future 166 | // changes to the property 167 | this.bind('change:' + prop, Magic.context); 168 | } 169 | return this.props[prop]; 170 | } 171 | 172 | Model.prototype.set = function(prop, val) { 173 | this.props[prop] = val; 174 | this.trigger('change: ' + prop); 175 | } 176 | 177 | // Implementations of bind and trigger omitted, but they would act exactly 178 | // like Backbone's 179 | 180 | Then it could be used like this - no manual event binding! 181 | 182 | lang:javascript 183 | 184 | var person = new Model(); 185 | person.set('name', 'Odeen'); 186 | 187 | Magic.autosubscribe(function() { 188 | $('h1').text('Salutations, ' + person.get('name')); 189 | }); 190 | 191 | person.set('name', 'Estwald'); 192 | 193 | While Meteor did not invent the concept of live templates, this method of 194 | dodging event binding seems the most powerful. While the idea of client-side 195 | access to the database seems a little scary, and it might end up hairier than 196 | normal client-server architecture if you want security, there are some ideas 197 | from Meteor that I think are a definite step in the right direction. 198 | -------------------------------------------------------------------------------- /_posts/2011-06-25-complex-asynchronous-queries-in-javascript.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Complex Asynchronous Queries in JavaScript 4 | tags: 5 | - AJAX 6 | - asynchronous 7 | - javascript 8 | - Karma 9 | status: publish 10 | type: post 11 | published: true 12 | meta: 13 | _edit_lock: "1309066702" 14 | _edit_last: "1" 15 | _wp_old_slug: "" 16 | --- 17 | Over the past couple of weeks, I finally got around to working on personal projects at an event I decided to create with the help of my housemates which we call [SEHackDay][]. The first and only project I worked on over the 3 nights was [Karma][], a Facebook App which gives you statistics about your posts and how they stack up against your friends. 18 | 19 | While it has not been remotely as successful as [The Wub Machine][] by one of my housemates, [Peter Sobot][], it did make me think about how to make complex asynchronous queries in JavaScript. 20 | 21 | While I was actually dealing with `FB.api` calls to the Facebook API, I'll use the example of jQuery AJAX queries for the purposes of this blog post, since they are more common. Before we delve into the complex examples, let's take a look at the basics. 22 | 23 | [Karma]: http://myfriendkarma.heroku.com/ 24 | [SEHackDay]: http://www.sehackday.com/ 25 | [The Wub Machine]: http://the.wubmachine.com/ 26 | [Peter Sobot]: http://www.petersobot.com/ 27 | 28 | Asynchronous Callbacks 29 | --------------------- 30 | *If you already understand AJAX, skip to the complex queries below*. 31 | 32 | JavaScript is, by nature, an asynchronous language. A few of the language's built-in constructs use callbacks, including `setTimeout` and `addEventListener`. The basic principle of a callback is this: 33 | 34 | **When something happens, do this, but keep doing other things while you're waiting.** 35 | 36 | Here's a simple example: 37 | 38 | lang:js 39 | 40 | window.setTimeout(function() { 41 | console.log("Three"); 42 | }, 1000); 43 | 44 | console.log("One"); 45 | console.log("Two"); 46 | 47 | Run that with Firebug, Chrome Developer Tools or whatever other console logging tools you work with and you'll see that the numbers come out "One", "Two" and then finally "Three" after a one second delay. 48 | 49 | A more useful example is retrieving data in jQuery through AJAX from the server. AJAX (for those unfamiliar) allows you to retrieve information on a web page from the server without necessitating a full page reload. A query typically looks something like this: 50 | 51 | lang:js 52 | 53 | $.ajax({ 54 | url: '/records.php', 55 | success: function(data) { 56 | console.log(data); 57 | } 58 | }); 59 | 60 | Which says roughly: **Send an HTTP GET request to the path `/date`. When the server responds, log the response body to the console.** 61 | 62 | *For those of you reading this and unfamiliar with complex JavaScript, you should probably stop here, fiddle around with asynchronous callbacks and AJAX, and then consider continuing.* 63 | 64 | Sequential Requests with a Callback 65 | ------------------------------ 66 | 67 | **Scenario**: I want to request a series of documents, one after another, then run a callback after they've all completed. A common example is running through paginated data when you don't know how many pages there are. 68 | 69 | **Solution**: Make the call recursive, keeping track of the data retrieved, then run the final callback when the last request completes. 70 | 71 | For example, let's assume a page `records.php` returns the latest 10 records in a database as JSON, with a parameter `offset`, specifying how many records from the front to skip. We'll assume if it returns fewer than 10 results, there are no more. The coded solution will look something like this: 72 | 73 | lang:js 74 | 75 | function allRecords(callback, offset, data) { 76 | if (typeof offset === 'undefined') { 77 | offset = 0; 78 | } 79 | if (typeof data === 'undefined') { 80 | data = []; 81 | } 82 | $.ajax({ 83 | url: 'records.php', 84 | data: {offset: offset}, 85 | success: function(dataChunk) { 86 | data = data.concat(dataChunk); 87 | if (dataChunk.length < 10) { 88 | callback(data); 89 | } else { 90 | allRecords(callback, offset + 10, data); 91 | } 92 | } 93 | }); 94 | } 95 | 96 | And since there's a check for the `undefined` state of the second two parameters, they can be omitted when the function is actually called, like so: 97 | 98 | lang:js 99 | 100 | allRecords(function(allData) { 101 | console.log(allData); 102 | }); 103 | 104 | And this callback will be called only after the last request finishes. Assuming there are 47 records, it would make the following requests in series, then run the callback with the concatenated arrays of data. 105 | 106 | records.php?offset=0 107 | records.php?offset=10 108 | records.php?offset=20 109 | records.php?offset=30 110 | records.php?offset=40 111 | 112 | Note that this solution ensures that the records are returned in order. 113 | 114 | Parallel Requests with a Callback 115 | ---------------------------- 116 | 117 | **Scenario**: I know exactly which documents I need, but I can't get them all at once, and I want to run a callback after all of them have been retrieved. A common use case is when you know exactly how many records there are, but for whatever reason (usually API limits), you can't get them all at once. 118 | 119 | While a simple modification of the above solution would work, it's inefficient. We can send more than one AJAX response at a time, so we might as well wait on more than one at a time. Chances are we can send at least the second request while we're waiting for the first to return. 120 | 121 | **Solution**: Send all the requests at once, noting how many were sent. Then count the responses as they come back. Once the count equals the number of requests sent, run the callback. Note that this does nothing to preserve order (and usually we don't care). 122 | 123 | Assuming the same general rules as above, let's say we want to retrieve the most recent n records. 124 | 125 | lang:js 126 | 127 | function nRecords(n, callback) { 128 | var nCallsNeeded = Math.ceil(n / 10); 129 | var nCallsFinished = 0; 130 | var data = []; 131 | for (var i = 0; i < nCallsNeeded; i++) { 132 | $.ajax({ 133 | url: 'records.php', 134 | data: {offset : i * 10}, 135 | success: function(dataChunk) { 136 | data = data.concat(dataChunk); 137 | nCallsFinished += 1; 138 | if (nCallsFinished == nCallsNeeded) { 139 | callback(data.slice(0,n)); 140 | } 141 | } 142 | }); 143 | } 144 | } 145 | 146 | So if I want the latest 23 records, I would get them like so: 147 | 148 | lang:js 149 | 150 | nRecords(23, function(allData) { 151 | console.log(allData); 152 | }); 153 | 154 | If you have suggestions on ways to improve these solutions, I'm all ears. 155 | 156 | Also, if you are interested in what your highest rated post or just generally want to see your own statistics on pretty graphs, please check out [Karma][]. 157 | -------------------------------------------------------------------------------- /_posts/2012-08-14-khan-academy-computer-science.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Khan Academy Computer Science: Instant Gratification and Bragging Rights" 4 | tags: 5 | - mercurial 6 | - arcanist 7 | - khan-academy 8 | --- 9 | 10 | ![Maze](/images/12-08-14-maze.png) 11 | 12 | When I was 6 years old, my mom asked me what kind of summer camp I wanted to go 13 | to. I excitedly exclaimed "LEGO!" She laughed it off a little then asked me to 14 | choose some other options, so I picked a couple sports camps and trampoline 15 | camp. 16 | 17 | A few weeks later, she found a Science and Technology camp called [Virtual 18 | Ventures][1] which actually did have playing with LEGO as part of the camp 19 | activities. But it was even better than that -- it was LEGO robotics! 20 | 21 | I went to that camp for the first time in 1998. In the 11 years that followed, I 22 | spent almost every full summer there, first as a camper, then as a volunteer, 23 | then 3 summers as an instructor. (I played with LEGO almost every week of it.) 24 | The life changing thing I got out of it was a fascination with programming. 25 | 26 | When I started there, I was saving HTML files to 3.5" floppies showing off my 27 | talent with ``, `
` and (probably) `` tags. By the time I 28 | left before going to the University of Waterloo, I had rewritten the camp 29 | registration system, taught kids about how the internet works, [ARP spoofing][2] 30 | and botnets, written my own (terrible) blog system from scratch, had a stint as 31 | a [teenage hacker][3], began my adventure into vim, and generally did my 32 | damnedest to get kids interested in programming. 33 | 34 | Through all of that, where I spent my computing time varied wildly, but the one 35 | element that was almost always present was web development. Two reasons I think 36 | that this persevered over all else: 37 | 38 | 1. **Instant gratification**. When I added an image to a page or changed the 39 | font color of my blaring neon header, I didn't need to wait for a recompile 40 | to see the changes -- I just refreshed the page. 41 | 2. **Bragging rights**. When I made something cool on the web, I didn't have to 42 | tell my friends to all go download it, unzip it, run the installer and make 43 | sure they have the right DLLs -- I just said "Look at this cool thing I made! 44 | Click this link!". 45 | 46 | And these are two aspects that we tried to amplify like crazy when building 47 | [Khan Academy Computer Science][4]. 48 | 49 | Khan Academy Computer Science 50 | ============================= 51 | 52 | I was at [CUSEC][5] when Bret Victor did his incredible talk [Inventing on 53 | Principle][6], so I was ecstatic when I found out I would be working with [John 54 | Resig][7] on making one of the editors in the video a reality. If you haven't 55 | watched Bret Victor's presentation yet, do it _right now_. 56 | 57 | This is something that really can't adequately be expressed with words, so I'll 58 | let the soothing voices of [Salman Khan][8] and John Resig do it for me in this 59 | introductory video: 60 | 61 | 63 | 64 | There's a whole bunch of stuff to be excited about with the introduction of 65 | Computer Science, but I'll just focus on how we facilitated the two aspects I 66 | mentioned before. 67 | 68 | For more depth, John Resig wrote an eloquent post explaining the motivation for 69 | the new platform: [Redefining the Introduction to Computer Science][12]. 70 | There's also an official announcement on the Khan Academy blog: [Khan Academy 71 | Computer Science][13]. 72 | 73 | Instant Gratification 74 | ===================== 75 | 76 | Here, instant gratification is all about seeing the effects of what you've done 77 | as quickly as possible. 78 | 79 | Instant Update 80 | -------------- 81 | Every time you make any change to the code in the editor, the changes will come 82 | into effect immediately. 83 | 84 | This means no edit-compile-run loop workflow that you see for compiled 85 | languages. There isn't even an edit-run/edit-refresh workflow you get using 86 | scripting languages. There's just edit-look-to-the-right. 87 | 88 | Number Scrubber, Color Picker, Image Picker 89 | ------------------------------------------- 90 | 91 | ![Pickers](/images/12-08-14-pickers.png) 92 | 93 | Since we have instant update, we can improve the experience further by reducing 94 | the friction on tweaking parameters. We made custom controls that let you tweak 95 | numbers, colors and images in the editor without having to type a thing. 96 | 97 | The cool thing about the pickers is that they're not only useful for tweaking 98 | parameters you know and understand -- they're useful for figuring out what a 99 | parameter _does_. If you're experimenting with someone else's code, you can play 100 | around with the number and see what effect changing them has in real time. 101 | 102 | This is really something that'll just be easier for you to try than for me to 103 | explain. I suggest playing around with the number scrubber and color picker on 104 | [Tree Generator][8] and playing around with the image picker on [Birds Flock 105 | Together][9]. 106 | 107 | Bragging Rights 108 | =============== 109 | 110 | I think most people who start learning how to code outside of academics do it to 111 | make cool stuff to show off to their friends. We wanted to make that process 112 | even easier by making it dead simple to get some code up and running on a 113 | webpage viewable by the world. 114 | 115 | Saving and Forking 116 | ------------------ 117 | 118 | There are two ways to get a Program with your name on it for the world to see. 119 | The first is saving a brand new program you made from scratch on the [New 120 | Program][10] page. 121 | 122 | The second is "forking" an existing program. In the user interface, the button 123 | is labelled "Save As...". If you play around with someone else's program and you 124 | want to save your modified version -- no need to copy and paste the code for 125 | into a blank program -- you can just "Save As..." and then you have your own 126 | saved copy. 127 | 128 | Regardless of which you do, after you save, you'll be brought to a page with a 129 | permalink for your new program, which you can share with the world! You can show 130 | off your work to your friends and family immediately. 131 | 132 | Social Integration 133 | ------------------ 134 | 135 | ![Share](/images/12-08-14-share.png) 136 | 137 | In the world of SoLoMo, listing "Social Integration" as a feature has started to 138 | feel a bit jokey, but this time it's not about "viral growth" or "lowering user 139 | acquisition cost", it's about giving our users a channel to brag about the 140 | awesome things they've made. It's about getting people to be proud of what 141 | they've made and trotting it through their social network saying "Look at this 142 | ball! I made it bounce!" 143 | 144 | To meet that end, we made it really easy to share Programs through facebook, 145 | twitter and email. 146 | 147 | I won't muddle up this post with descriptions of what I did during my internship 148 | -- that'll come in another post. For now, I'll just say that working on Computer 149 | Science has been a blast. If you're interested in joining an amazing team 150 | working on creating free, world-class education for the world, check out 151 | [Careers at Khan Academy][11] for job openings. 152 | 153 | 154 | [1]: http://www.virtualventures.ca/new/ 155 | [2]: http://en.wikipedia.org/wiki/ARP_spoofing 156 | [3]: http://www.codinghorror.com/blog/2012/08/i-was-a-teenage-hacker.html 157 | [4]: http://khanacademy.org/cs 158 | [5]: http://cusec.net/ 159 | [6]: https://vimeo.com/36579366 160 | [7]: http://ejohn.org/ 161 | [8]: http://www.khanacademy.org/cs/tree-generator/822944839 162 | [9]: http://www.khanacademy.org/cs/birds-flock-together/940061217 163 | [10]: http://khanacademy.org/cs/new 164 | [11]: http://www.khanacademy.org/careers 165 | [12]: http://ejohn.org/blog/introducing-khan-cs/ 166 | [13]: http://www.khanacademy.org/about/blog/post/29417655743/computer-science 167 | -------------------------------------------------------------------------------- /_posts/2011-10-16-fifteen-puzzle-algorithm.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: The Fifteen Puzzle - The Algorithm 4 | tags: 5 | - Fifteen Puzzle 6 | - algorithms 7 | - Intro to AI 8 | - coffeescript 9 | - Las Vegas 10 | - Monte Carlo 11 | status: publish 12 | type: post 13 | published: true 14 | --- 15 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | Before you read this, play with the above puzzle. You can move the blocks around 31 | yourself by clicking on one adjacent to the empty square. Click "shuffle" and 32 | the blocks will rearrange themselves using 25 randomly selected moves. Click 33 | "solve" from any configuration that isn't already ordered 1-15 and you'll see 34 | the blocks rearrange themselves. 35 | 36 | If you hit shuffle more than 2 times, it'll take some work to solve the puzzle, 37 | so you'll see it solving for a while before it actually does anything. The 38 | version you see above is actually throttled so it doesn't freeze your browser or 39 | use hundreds of MB of memory. 40 | 41 | As always, you can see source for the above: 42 | [github.com/phleet/fifteen-puzzle][]. 43 | 44 | Background 45 | ========== 46 | 47 | I've been going through Stanford's free online [Introduction to Artificial 48 | Intelligence][ai-class] with my housemates and have been enjoying them. My 49 | favourite thing from the first set of lectures was the example of heuristics 50 | being applied to the [Fifteen Puzzle][]. So I wrote up a solver and made the 51 | interactive demo you see above. Start watching at [Unit 2, Topic 31, Sliding 52 | Blocks Puzzle][sliding-puzzle-intro] to see a great explanation of what I'm 53 | doing. 54 | 55 | Algorithm 56 | ========= 57 | 58 | The algorithm I used is pretty much exactly what's described in the video. My 59 | solution in coffeescript is an optimized and throttled version of the following: 60 | 61 | lang:coffeescript 62 | 63 | solve = (startGrid) -> 64 | frontier = new PriorityQueue 65 | frontier.enqueue(new SolverState(startGrid, [])) 66 | 67 | while not frontier.empty() 68 | curState = frontier.dequeue() 69 | 70 | if curState.solved 71 | return curState.steps 72 | 73 | candidateMoves = grid.validMoves() 74 | 75 | for move in candidateMoves 76 | nextGrid = grid.applyMove(move) 77 | nextSteps = curState.steps.concat([move]) 78 | nextState = new SolverState(nextGrid, nextSteps) 79 | frontier.enqueue(nextState) 80 | 81 | `SolverState` stores the current position of all the numbers in the grid and the 82 | list of steps to get there from the starting grid. 83 | 84 | `PriorityQueue` is responsible for making sure we always explore the lowest cost 85 | state first. The cost of the state is the number of steps taken from the initial 86 | state plus the estimated number of steps remaining to get to the solution. This 87 | estimate (h2 from [Unit 2, Topic 31][sliding-puzzle-intro]) is admissible 88 | because each move can at best reduce that estimate by one. 89 | 90 | `grid.validMoves` returns a list of all valid moves to make the on the grid. If 91 | the empty square is in the middle of the grid, this is all four directions. If 92 | it's in a corner, there are only two valid directions. 93 | 94 | Optimizations 95 | ============= 96 | 97 | The algorithm described above will yield the optimal solution (fewest number of 98 | moves) for any valid fifteen-puzzle configuration. But it's pretty slow and can 99 | be much improved. 100 | 101 | Tree Pruning 102 | ------------ 103 | When looking at the search tree, there are some branches we can guarantee will 104 | not yield an optimal solution. The most obvious one here is to never go 105 | backwards. In the above algorithm, we can prevent going backwards like so: 106 | 107 | lang:coffeescript 108 | 109 | lastStep = _.last(steps) 110 | if lastStep? 111 | candidates = _(candidates).filter (x) -> 112 | not directionsAreOpposites x, lastStep 113 | 114 | I'm using [underscore.js][] for its functional goodness, so that's where 115 | `_.last` and `_.filter` come from. 116 | 117 | Las Vegas Randomization 118 | ----------------------- 119 | While the algorithm always takes the lowest cost state, there will frequently be 120 | ties. Because of of the nature of how `validMoves` works, this means that the 121 | algorithm will make a disproportionate number of moves in the same direction. 122 | To fix this, we shuffle the list of valid moves before we start adding states to 123 | the priority queue. To do this, we change the `candidates = ...` line to be: 124 | 125 | lang:coffeescript 126 | 127 | candidates = _.shuffle(grid.validMoves()) 128 | 129 | A [Las Vegas algorithm][] is always right and sometimes fast. This is in 130 | contrast to a [Monte Carlo algorithm][], which is always fast and sometimes 131 | right. 132 | 133 | Min-Heap Priority Queue 134 | ----------------------- 135 | Once I applied the above to make the algorithm terminate in fewer iterations, it 136 | was time to optimize each iteration. By using Chrome Developer Tools, I was able 137 | to identify that the JavaScript runtime was spending most of its time in 138 | `ProrityQueue::enqueue`. 139 | 140 | The first version I made of this used an `O(nlogn)` enqueue, `O(1)` dequeue 141 | method: I just appended and sorted after every enqueue and popped the element 142 | off the back of the array on dequeue. 143 | 144 | Once I saw it was a bottleneck, I reimplemented the `PriorityQueue` as a 145 | [min-heap][] to get `O(logn)` enqueues and dequeues, and this improved 146 | performance significantly. 147 | 148 | You can see my implementation of a heap in coffeescript in [solver.coffee][]. 149 | 150 | Leveraging Known Results 151 | ------------------------ 152 | After I switched to a min-heap, the next bottleneck was calculating the 153 | heuristic estimate. The observation here that made this faster is that the 154 | heuristic value can be determined for a state based on the previous state and 155 | the move used to get there. This prevents the need to recalculate the estimate 156 | for all the grids generated. 157 | 158 | You can see this update applied on the commit: [Leverage known results for 159 | lowerSolutionBound][lowerSolutionBound]. 160 | 161 | That's all for the algorithm. I'm planning to do two more posts about this project 162 | - one on using Chrome Developer Tools to optimize and how to not crash the 163 | browser, and another on the Jasmine test framework and literate programming with 164 | coffeescript. If you want to know when those come out, you should follow me on 165 | twitter [@jlfwong][twitter]. 166 | 167 | [Fifteen Puzzle]: http://en.wikipedia.org/wiki/Fifteen_puzzle 168 | [ai-class]: https://www.ai-class.com/ 169 | [sliding-puzzle-intro]: https://www.ai-class.com/course/video/quizquestion/15/1 170 | [underscore.js]: http://documentcloud.github.com/underscore/ 171 | [Las Vegas algorithm]: http://en.wikipedia.org/wiki/Las_vegas_algorithm 172 | [Monte Carlo algorithm]: http://en.wikipedia.org/wiki/Monte_Carlo_algorithm 173 | [github.com/phleet/fifteen-puzzle]: https://github.com/phleet/fifteen-puzzle 174 | [min-heap]: http://en.wikipedia.org/wiki/Binary_heap 175 | [solver.coffee]: https://github.com/phleet/fifteen-puzzle/blob/5ec9ffad6eab8309027e9fe19013b02c4b4f872a/src/solver.coffee 176 | [lowerSolutionBound]: https://github.com/phleet/fifteen-puzzle/commit/c6057dc1956cfbe89a119aa26ba0a65f50bc3824 177 | [twitter]: http://twitter.com/jlfwong 178 | -------------------------------------------------------------------------------- /_sass/pygments.scss: -------------------------------------------------------------------------------- 1 | $base03: #002b36 !default; //darkest blue 2 | $base02: #073642 !default; //dark blue 3 | $base01: #586e75 !default; //darkest gray 4 | $base00: #657b83 !default; //dark gray 5 | $base0: #839496 !default; //medium gray 6 | $base1: #93a1a1 !default; //medium light gray 7 | $base2: #f2f2f2 !default; //cream 8 | $base3: #ffffff !default; //white 9 | $solar-yellow: #b58900 !default; 10 | $solar-orange: #cb4b16 !default; 11 | $solar-red: #dc322f !default; 12 | $solar-magenta: #d33682 !default; 13 | $solar-violet: #6c71c4 !default; 14 | $solar-blue: #268bd2 !default; 15 | $solar-cyan: #2aa198 !default; 16 | $solar-green: #859900 !default; 17 | 18 | $solarized: dark !default; 19 | 20 | @if $solarized == light { 21 | 22 | $_base03: $base03; 23 | $_base02: $base02; 24 | $_base01: $base01; 25 | $_base00: $base00; 26 | $_base0: $base0; 27 | $_base1: $base1; 28 | $_base2: $base2; 29 | $_base3: $base3; 30 | 31 | $base03: $_base3; 32 | $base02: $_base2; 33 | $base01: $_base1; 34 | $base00: $_base0; 35 | $base0: $_base00; 36 | $base1: $_base01; 37 | $base2: $_base02; 38 | $base3: $_base03; 39 | } 40 | 41 | /* non highlighted code colors */ 42 | $pre-bg: $base03 !default; 43 | $pre-border: darken($base02, 5) !default; 44 | $pre-color: $base1 !default; 45 | 46 | .highlight { 47 | .c { color: $base01 !important; font-style: italic !important; } /* Comment */ 48 | .cm { color: $base01 !important; font-style: italic !important; } /* Comment.Multiline */ 49 | .cp { color: $base01 !important; font-style: italic !important; } /* Comment.Preproc */ 50 | .c1 { color: $base01 !important; font-style: italic !important; } /* Comment.Single */ 51 | .cs { color: $base01 !important; font-weight: bold !important; font-style: italic !important; } /* Comment.Special */ 52 | .err { color: $solar-red !important; background: none !important; } /* Error */ 53 | .k { color: $solar-orange !important; } /* Keyword */ 54 | .o { color: $base1 !important; font-weight: bold !important; } /* Operator */ 55 | .p { color: $base1 !important; } /* Operator */ 56 | .ow { color: $solar-cyan !important; font-weight: bold !important; } /* Operator.Word */ 57 | .gd { color: $base1 !important; background-color: mix($solar-red, $base03, 25%) !important; display: inline-block; } /* Generic.Deleted */ 58 | .gd .x { color: $base1 !important; background-color: mix($solar-red, $base03, 35%) !important; display: inline-block; } /* Generic.Deleted.Specific */ 59 | .ge { color: $base1 !important; font-style: italic !important; } /* Generic.Emph */ 60 | //.gr { color: #aa0000 } /* Generic.Error */ 61 | .gh { color: $base01 !important; } /* Generic.Heading */ 62 | .gi { color: $base1 !important; background-color: mix($solar-green, $base03, 20%) !important; display: inline-block; } /* Generic.Inserted */ 63 | .gi .x { color: $base1 !important; background-color: mix($solar-green, $base03, 40%) !important; display: inline-block; } /* Generic.Inserted.Specific */ 64 | //.go { color: #888888 } /* Generic.Output */ 65 | //.gp { color: #555555 } /* Generic.Prompt */ 66 | .gs { color: $base1 !important; font-weight: bold !important; } /* Generic.Strong */ 67 | .gu { color: $solar-violet !important; } /* Generic.Subheading */ 68 | //.gt { color: #aa0000 } /* Generic.Traceback */ 69 | .kc { color: $solar-green !important; font-weight: bold !important; } /* Keyword.Constant */ 70 | .kd { color: $solar-blue !important; } /* Keyword.Declaration */ 71 | .kp { color: $solar-orange !important; font-weight: bold !important; } /* Keyword.Pseudo */ 72 | .kr { color: $solar-magenta !important; font-weight: bold !important; } /* Keyword.Reserved */ 73 | .kt { color: $solar-cyan !important; } /* Keyword.Type */ 74 | .n { color: $solar-blue !important; } 75 | .na { color: $solar-blue !important; } /* Name.Attribute */ 76 | .nb { color: $solar-green !important; } /* Name.Builtin */ 77 | .nc { color: $solar-magenta !important;} /* Name.Class */ 78 | .no { color: $solar-yellow !important; } /* Name.Constant */ 79 | //.ni { color: #800080 } /* Name.Entity */ 80 | .nl { color: $solar-green !important; } 81 | .ne { color: $solar-blue !important; font-weight: bold !important; } /* Name.Exception */ 82 | .nf { color: $solar-blue !important; font-weight: bold !important; } /* Name.Function */ 83 | .nn { color: $solar-yellow !important; } /* Name.Namespace */ 84 | .nt { color: $solar-blue !important; font-weight: bold !important; } /* Name.Tag */ 85 | .nx { color: $solar-yellow !Important; } 86 | //.bp { color: #999999 } /* Name.Builtin.Pseudo */ 87 | //.vc { color: #008080 } /* Name.Variable.Class */ 88 | .vg { color: $solar-blue !important; } /* Name.Variable.Global */ 89 | .vi { color: $solar-blue !important; } /* Name.Variable.Instance */ 90 | .nv { color: $solar-blue !important; } /* Name.Variable */ 91 | //.w { color: #bbbbbb } /* Text.Whitespace */ 92 | .mf { color: $solar-cyan !important; } /* Literal.Number.Float */ 93 | .m { color: $solar-cyan !important; } /* Literal.Number */ 94 | .mh { color: $solar-cyan !important; } /* Literal.Number.Hex */ 95 | .mi { color: $solar-cyan !important; } /* Literal.Number.Integer */ 96 | //.mo { color: #009999 } /* Literal.Number.Oct */ 97 | .s { color: $solar-cyan !important; } /* Literal.String */ 98 | //.sb { color: #d14 } /* Literal.String.Backtick */ 99 | //.sc { color: #d14 } /* Literal.String.Char */ 100 | .sd { color: $solar-cyan !important; } /* Literal.String.Doc */ 101 | .s2 { color: $solar-cyan !important; } /* Literal.String.Double */ 102 | .se { color: $solar-red !important; } /* Literal.String.Escape */ 103 | //.sh { color: #d14 } /* Literal.String.Heredoc */ 104 | .si { color: $solar-blue !important; } /* Literal.String.Interpol */ 105 | //.sx { color: #d14 } /* Literal.String.Other */ 106 | .sr { color: $solar-cyan !important; } /* Literal.String.Regex */ 107 | .s1 { color: $solar-cyan !important; } /* Literal.String.Single */ 108 | //.ss { color: #990073 } /* Literal.String.Symbol */ 109 | //.il { color: #009999 } /* Literal.Number.Integer.Long */ 110 | div { .gd, .gd .x, .gi, .gi .x { display: inline-block; width: 100%; }} 111 | } 112 | -------------------------------------------------------------------------------- /_posts/2010-09-30-make-your-life-easier-with-gnu-make.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Make your life easier with GNU Make. 4 | tags: 5 | - cs241 6 | - make 7 | - school 8 | status: publish 9 | type: post 10 | published: true 11 | meta: 12 | _wp_old_slug: "" 13 | _edit_last: "1" 14 | _edit_lock: "1287294610" 15 | --- 16 | I've been trying to keep at least one post per month going, and I was sick with stomach flu today, so here goes: 17 | 18 | As our CS assignments get more and more complex, it's starting to become time consuming to build, test, and submit each assignment. I could either do this every time I want to test: 19 | 20 | lang:bash 21 | 22 | java cs241.binasm < a2p8.asm > a2p8.mips 23 | java mips.array a2p8.mips < a2p8.sample.in 24 | java mips.array a2p8.mips < a2p8.onenode.in 25 | 26 | Or I could just run `make`. 27 | 28 | What is make? 29 | ========= 30 | 31 | [GNU Make](http://www.gnu.org/software/make/) is a system for constructing executables or other non-source code files from source code. It allows you to specify dependencies for files and define rules for constructing them from source. For example, the rule to build `a2p8.mips` would look like this: 32 | 33 | lang:make 34 | 35 | a2p8.mips: a2p8.asm 36 | java cs241.binasm < a2p8.asm > a2p8.mips 37 | 38 | This says, to make `a2p8.mips`, run `java cs241.binasm < a2p.asm > a2p8.mips`. To use `make`, stick your ruleset in a file called `Makefile` in the same directory as your source then run `make`. This command will only run if `a2p8.asm` has changed since the last time it was run. These dependencies stack as well. For instance, say I need to join two files together before I compile them. I can define a ruleset like this: 39 | 40 | lang:make 41 | 42 | linked.mips: linked.asm 43 | java cs241.binasm < linked.asm > linked.mips 44 | 45 | linked.asm: a2p7.asm print.asm 46 | cat a2p7.asm print.asm > linked.asm 47 | 48 | This will link the files together before it tries to compile them. It'll also make sure it's up to date whenever you change either `a2p7.asm` or `print.asm`. The build rule at the top of the `Makefile` is called the default rule. This is the one that gets run when you just run `make` at the command line. You can also pass a parameter to tell `make` to construct something specific. For instance, I could run `make linked.asm` here to just build the linked file without actually compiling it to machine code. 49 | 50 | Automatic Variables in Make 51 | ================= 52 | One of the principles of writing good code is DRY: Don't Repeat Yourself. Make provides various ways to support this through the use of automatic variables. Automatic variables are ones that are set for you with useful values. For instance, the following rule: 53 | 54 | lang:make 55 | 56 | linked.asm: a2p7.asm print.asm 57 | cat a2p7.asm print.asm > linked.asm 58 | 59 | can be reduced to this: 60 | 61 | lang:make 62 | 63 | linked.asm: a2p7.asm print.asm 64 | cat $^ > $@ 65 | 66 | The variable `$^` is a space separated list of all the dependencies. `$@` is the target. See [GNU make - Automatic Variables](http://www.gnu.org/software/make/manual/make.html#Automatic-Variables) for the full list and description of such variables. 67 | 68 | Writing Implicit Rules 69 | ============= 70 | Another unnecessary piece of code duplication exists for compiling multiple files of the same type. For instance, the following is repetitive: 71 | 72 | lang:make 73 | 74 | a2p8.mips: a2p8.asm 75 | java cs241.binasm < $< > $@ 76 | 77 | a2p7.mips: a2p7.asm 78 | java cs241.binasm < $< > $@ 79 | 80 | Instead, I can define my own implicit rule for building all `*.mips` files like so: 81 | 82 | lang:make 83 | 84 | %.mips: %.asm 85 | java cs241.binasm < $*.asm > $*.mips 86 | 87 | Testing Using Make 88 | ============ 89 | One of the things I took away from my co-op term at The Working Group was the benefits of test driven development. Untested code is broken code, and manual testing is tedious and annoying. As your code gets more and more complicated, there will be more and more edge cases to deal with. These cannot be dealt with a single test case. Normally, you would be forced to write multiple input files and pass them each individually to the executable to see if everything worked. I don't like having to do this. So instead, I defined my own ruleset: 90 | 91 | lang:make 92 | 93 | ASSIGNMENT = a2p7 94 | 95 | test: linked.mips Empty.test All.test 96 | 97 | %.test: $(ASSIGNMENT).%.in 98 | @echo ----------- $* ------------------ 99 | java mips.array linked.mips < $^ 100 | 101 | %.mips: %.asm 102 | java cs241.binasm < $*.asm > $*.mips 103 | 104 | linked.asm: $(ASSIGNMENT).asm print.asm 105 | cat $^ > $@ 106 | 107 | This one merits a bit of explanation. The default task here is `test`, and it has no recipe associated with it. Instead, it just makes sure all of its dependencies are up to date. `Empty.test` and `All.test` here aren't actual files. They're simple methods of referring to another rule, in this case the `%.test` implicit rule. 108 | 109 | The `%.test` implicit rule depends on a corresponding input file. For example, `Empty.test` will rely on `a2p7.Empty.in`, which will then be passed in as input to the executable. The result from running looks like this: 110 | 111 | lang:bash 112 | 113 | $ make 114 | ----------- Empty ------------------ 115 | java mips.array linked.mips < a2p7.Empty.in 116 | Enter length of array: MIPS program completed normally. 117 | $01 = 0x000001c4 $02 = 0x00000000 $03 = 0x00000000 $04 = 0x00000000 118 | $05 = 0x00000000 $06 = 0x00000000 $07 = 0x00000000 $08 = 0x00000000 119 | $09 = 0x00000000 $10 = 0x00000000 $11 = 0x000001c4 $12 = 0x00000000 120 | $13 = 0x00000000 $14 = 0x00000000 $15 = 0x00000000 $16 = 0x00000000 121 | $17 = 0x00000000 $18 = 0x00000000 $19 = 0x00000000 $20 = 0x00000000 122 | $21 = 0x00000000 $22 = 0x00000000 $23 = 0x00000000 $24 = 0x00000000 123 | $25 = 0x00000000 $26 = 0x00000000 $27 = 0x00000000 $28 = 0x00000000 124 | $29 = 0x00000000 $30 = 0x01000000 $31 = 0x8123456c 125 | ----------- All ------------------ 126 | java mips.array linked.mips < a2p7.All.in 127 | Enter length of array: Enter array element 0: Enter array element 1: Enter array element 2: 123 128 | 0 129 | -456 130 | MIPS program completed normally. 131 | $01 = 0xfffffe38 $02 = 0x00000003 $03 = 0x00000001 $04 = 0x00000000 132 | $05 = 0x00000000 $06 = 0x00000000 $07 = 0x00000000 $08 = 0x00000000 133 | $09 = 0x00000000 $10 = 0x00000000 $11 = 0x000001d0 $12 = 0x00000000 134 | $13 = 0x00000000 $14 = 0x00000000 $15 = 0x00000000 $16 = 0x00000000 135 | $17 = 0x00000000 $18 = 0x00000000 $19 = 0x00000000 $20 = 0x00000000 136 | $21 = 0x00000000 $22 = 0x00000000 $23 = 0x00000000 $24 = 0x00000000 137 | $25 = 0x00000000 $26 = 0x00000000 $27 = 0x00000000 $28 = 0x00000000 138 | $29 = 0x00000000 $30 = 0x01000000 $31 = 0x8123456c 139 | 140 | The `echo` line above that simple outputs the name of the test before running it so it looks nicer on the console. The `@` before the echo prevents make from displaying the command, otherwise it would look like this: 141 | 142 | lang:bash 143 | 144 | $ make 145 | cat a2p7.asm print.asm > linked.asm 146 | java cs241.binasm < linked.asm > linked.mips 147 | echo ----------- Empty ------------------ 148 | ----------- Empty ------------------ 149 | 150 | Submitting to Marmoset from the Commandline 151 | ============================= 152 | Just for kicks and because I wanted to mess around with [Mechanize](http://github.com/tenderlove/mechanize), I built a ruby script which will submit to Marmoset for me. You can try it out yourself if you have Ruby and [Rubygems](https://rubygems.org/) by running 153 | 154 | lang:bash 155 | 156 | sudo gem install marmoset 157 | 158 | Or you can browse the code yourself here: [http://github.com/phleet/MarmosetSubmit]() 159 | 160 | Here's how you can integrate the submission process into your `Makefile`: 161 | 162 | lang:make 163 | 164 | submit: $(ASSIGNMENT).asm 165 | marmoset -u jlfwong -c cs241 -a $(ASSIGNMENT) -f $(ASSIGNMENT).asm 166 | 167 | For now, it only submits - there's no way of checking to see if it was successful or running the release tests. I can build that if people are really interested in that, or you can fork the github repo and do it yourself. 168 | 169 | Well, that's all for now. Time to study up for Electrodeath. 170 | -------------------------------------------------------------------------------- /_posts/2010-05-11-google-codejam-2010-solutions-qualification-round.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Google Codejam 2010 Solutions - Qualification 4 | tags: 5 | - c++ 6 | - Code Jam 7 | - codejam 8 | - google 9 | - solutions 10 | status: publish 11 | type: post 12 | published: true 13 | meta: 14 | _edit_lock: "1277339178" 15 | _edit_last: "1" 16 | --- 17 | I'm happy to have qualified for Codejam for the second year running - I managed to make it to Online Round 2 last year, so let's see if I can top that this year. 18 | 19 | The opening round was not very difficult, and were it not for a very stupid mistake on my part on the first question, I easily could have gotten perfect in under 2.5 hours. Instead I got 76 in 3.5 hours. Bah. 20 | 21 | Read the questions here: Qualification Round 2010 @ code.google.com 22 | 23 |

Problem 1: Snapper Chain

24 | This is the one I screwed up, because I somehow overlooked the fact that each snap was simply equivalent to an binary increment of the snappers states. Instead, I did a foolish simulation, which did actually pass me the small test case. But anyway, the real solution is O(1). 25 | 26 | For interest, here's what the snapper chain looks like in the first 30 iterations. The left side, labeled "P:" shows which of the snappers are powered, and the right side, labeled "S:" shows the current state of the snappers, with "#" being ON and "." being OFF in both cases. I did this so it would be easier to see the pattern. The first row is the initial set up, before any snaps. 27 | 28 | P: #......... S: .......... 29 | P: ##........ S: #......... 30 | P: #......... S: .#........ 31 | P: ###....... S: ##........ 32 | P: #......... S: ..#....... 33 | P: ##........ S: #.#....... 34 | P: #......... S: .##....... 35 | P: ####...... S: ###....... 36 | P: #......... S: ...#...... 37 | P: ##........ S: #..#...... 38 | P: #......... S: .#.#...... 39 | P: ###....... S: ##.#...... 40 | P: #......... S: ..##...... 41 | P: ##........ S: #.##...... 42 | P: #......... S: .###...... 43 | P: #####..... S: ####...... 44 | P: #......... S: ....#..... 45 | P: ##........ S: #...#..... 46 | P: #......... S: .#..#..... 47 | P: ###....... S: ##..#..... 48 | P: #......... S: ..#.#..... 49 | P: ##........ S: #.#.#..... 50 | P: #......... S: .##.#..... 51 | P: ####...... S: ###.#..... 52 | P: #......... S: ...##..... 53 | P: ##........ S: #..##..... 54 | P: #......... S: .#.##..... 55 | P: ###....... S: ##.##..... 56 | P: #......... S: ..###..... 57 | P: ##........ S: #.###..... 58 | 59 | What we're interested in is how to tell where a specific snapper is on. It turns 60 | out that this is quite simple - so long as all snappers before it are on the ON 61 | settings, it will be powered. This means that ultimately, the power column above 62 | is irrelevant. All we need to know is that the N-1 least significant bits are 63 | set. This is fairly trivial to check. 64 | 65 | lang:cpp 66 | 67 | #include 68 | using namespace std; 69 | int main() { 70 | int T,N,K; 71 | cin >> T; 72 | for(int t = 0; t < T; t++) { 73 | cin >> N >> K; 74 | int mask = (1 << N) - 1; 75 | printf("Case #%d: ",t+1); 76 | if ( (mask & K) == mask) puts("ON"); 77 | else puts("OFF"); 78 | } 79 | return 0; 80 | } 81 | 82 | Time for the large case: 83 | 84 | lang:bash 85 | 86 | $ time ./a.out < snapper.in > snapper.out 87 | 88 | real 0m0.071s 89 | user 0m0.026s 90 | sys 0m0.040s 91 | 92 | 93 |

Problem 2: Fair Warning

94 | This problem took me about 40 minutes to think of the solution for and about 5 95 | minutes to code. Honour's Algebra (MATH 115) came in handy here. You can first 96 | calculate T (the optimal anniversary) by looking at the gcd (greatest common 97 | divisor) of the differences between the time elapsed since the events. This 98 | works because in order for T to divide the time elapsed since the events, it 99 | must also divide the time elapsed between the events. After that, you just need 100 | to find the first y that makes any of the numbers divisible by T. I just wrote 101 | out the congruences on my whiteboard (which I probably shouldn't have needed to 102 | do) then typed in my answer. 103 | 104 | The only remaining problem here is dealing with the large numbers, which is 105 | hardly a problem in python. Here's my solution: 106 | 107 | lang:python 108 | 109 | def gcd(x,y): 110 | while x: 111 | x, y = y % x, x 112 | return y 113 | 114 | testcases = open("warning.in").readlines() 115 | t = 1 116 | for tc in testcases[1:]: 117 | tc = map(lambda x: int(x), tc.split(" ")[1:]) 118 | if len(tc) == 0: 119 | break 120 | tc.sort() 121 | #print tc 122 | 123 | diff = [] 124 | for i in range(len(tc)-1): 125 | diff += [tc[i+1] - tc[i]] 126 | 127 | T = diff[0] 128 | for d in diff[1:]: 129 | T = gcd(T,d) 130 | 131 | #print T 132 | print "Case #%d: %d" % (t, (-tc[0] % T)) 133 | 134 | t += 1 135 | 136 | Time for large case: 137 | 138 | lang:bash 139 | 140 | $ time python warning.py > warning.out 141 | 142 | real 0m0.250s 143 | user 0m0.037s 144 | sys 0m0.021s 145 | 146 |

Problem 3: Theme Park

147 | This is an optimization problem. There are a lot of different optimizations you 148 | can apply, but I figured "to hell with it" and decided the solution getting it 149 | down to O(R) was probably sufficient. If you want to look at a better list of 150 | optimizations, go look here: Google 152 | Codejam 2010 Qualification Round Contest Analysis. 153 | 154 | All I figured I needed to do was make it so I didn't have to find the groups 155 | every single time the ride was loaded. To do this, I simulated using a queue 156 | until I arrived at a position where the group at the front of the line had been 157 | at the front before. While doing this, I record how many euros were made for a 158 | given start of the line, and who ends up at the front of the line while they're 159 | on the roller coaster. After this, the O(R) loop is very simple. Just add the 160 | number of euros for the run, then move on to the next front of the line. 161 | 162 | lang:cpp 163 | 164 | #include 165 | #include 166 | #include 167 | #include 168 | #include 169 | #include 170 | #include 171 | #include 172 | #include 173 | #include 174 | #include 175 | #include 176 | using namespace std; 177 | 178 | #define FR(i,a,b) for(int i=(a);i<(b);++i) 179 | #define FOR(i,n) FR(i,0,n) 180 | #define SETMIN(a,b) a = min(a,b) 181 | #define SETMAX(a,b) a = max(a,b) 182 | #define PB push_back 183 | #define FORALL(i,v) for(typeof((v).end())i=(v).begin();i!=(v).end();++i) 184 | #define CLR(x,a) memset(x,a,sizeof(x)) 185 | #define BEND(v) v.begin(),v.end() 186 | #define MP make_pair 187 | #define A first 188 | #define B second 189 | 190 | typedef unsigned long long int ull; 191 | typedef long double ld; 192 | 193 | int main() { 194 | freopen("themepark.in","r",stdin); 195 | freopen("themepark.out","w",stdout); 196 | 197 | int T; 198 | cin >> T; 199 | FOR(t,T) { 200 | int R,k,N; 201 | cin >> R >> k >> N; 202 | int grps[1001], done[1001], pts[1001], next[1001]; 203 | CLR(done,0); 204 | queue > q; 205 | FOR(i,N) { 206 | cin >> grps[i]; 207 | q.push(MP(i,grps[i])); 208 | } 209 | 210 | while(!done[q.front().A]) { 211 | int cur = 0; 212 | int start = q.front().A; 213 | done[start] = 1; 214 | 215 | FOR(i,N) { 216 | if (cur + q.front().B > k) break; 217 | 218 | cur += q.front().B; 219 | q.push(q.front()); 220 | q.pop(); 221 | } 222 | next[start] = q.front().A; 223 | pts[start] = cur; 224 | } 225 | 226 | while (!q.empty()) q.pop(); 227 | 228 | int curFront = 0; 229 | ull ans = 0; 230 | FOR(i,R) { 231 | ans += pts[curFront]; 232 | curFront = next[curFront]; 233 | } 234 | 235 | printf("Case #%d: %lld\n", t+1, ans); 236 | } 237 | return 0; 238 | } 239 | 240 | Time for large test case: 241 | 242 | lang:bash 243 | 244 | $ time ./a.out 245 | 246 | real 0m11.606s 247 | user 0m11.542s 248 | sys 0m0.022s 249 | 250 | I might code up an O(N) solution later, which really isn't that difficult, but 251 | at this point in the contest, it simply didn't matter enough. 252 | -------------------------------------------------------------------------------- /_posts/2011-12-30-immersion-and-schadenfreude.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Immersion and Schadenfreude 4 | tags: 5 | - life 6 | - motivation 7 | status: publish 8 | type: post 9 | published: true 10 | --- 11 | 12 | ![Scale](/images/11-12-28-Scale.png) 13 | 14 | When I was in High School, I would try to absorb as much about computer science 15 | as I could from what I saw around me. I started writing competitions like 16 | [CCC][], [dwite][] and [ECOO-CS][]. I practiced for these competitions using 17 | [UVa Online Judge][UVa], the [USACO Training Program Gateway] and by reading 18 | [_The Programming Contest Training Manual_][skiena]. I wrote word game bots and 19 | learned a lot from hacking challenge sites like [hackquest][] and 20 | [hackthissite][]. 21 | 22 | But I also used to [dance][], [draw][], make [abstract 3D stuff][], game and 23 | [play badminton][] (bear with me — the part of this that doesn't sound 24 | like bragging is coming). 25 | 26 | When I got to university, I became fully immersed in computer science and 27 | software. I made the ACM team, started reading [r/programming][] and [Hacker 28 | News][], started blogging about the code I was writing, started preaching the 29 | virtues of vim to my classmates and just generally increased the surface area of 30 | my CS knowledge sponge. This, in itself, was not a bad thing. Some of the things 31 | I've learned through these methods have been invaluable, but the cost was too 32 | high. 33 | 34 | With the dramatically higher load of schoolwork from uWaterloo's Engineering 35 | program compared to high school, this influx of new activities meant the 36 | sacrifice of something. Preparing for interviews and trying to build an online 37 | presence made it easier to justify dropping off the professionally irrelevant 38 | things. I stopped drawing, stopped dancing, cut back on badminton and nearly 39 | stopped gaming. After that, I was entirely immersed in my field with little else 40 | around me. 41 | 42 | I started realizing how bad this had become when I found myself trying to think 43 | of something to code during my free time. _This is so wrong._ Coding on my own 44 | time had become this obligation I had to fulfill to maintain my sense of 45 | reputation and to counter any fear of failing to complete some non-specific goal 46 | of doing something amazing. Don't get me wrong — coding on your own time 47 | is a good idea, but only do it if you're working on something that excites you. 48 | 49 | So this seems like a relatively simple problem to fix, right? Just start doing 50 | the things you used to do. Only one problem. 51 | 52 | Motivation 53 | ========== 54 | 55 | Lack of motivation is the killer of all self-improvement endeavours. Everyone 56 | has had a thing or two that they commit to doing that trails off. Whether it be 57 | eating right, going to the gym, keeping in touch with family and friends, or 58 | quitting smoking, everyone has failed to make or break a habit. 59 | 60 | The most common pattern in my failures has been starting something, getting a 61 | rhythm going and then having one thing throw it off. Your schedule breaks down 62 | and you can't get back into it. This might be when exams start, when a project 63 | deadline is coming up or when you get sick. I can always convince myself that 64 | I'll get back to it later, but I never do. 65 | 66 | ### Incentives 67 | 68 | ![Incentives](/images/11-12-28-Incentives.png) 69 | 70 | One method to bolster motivation is to use incentives. Self-awarded incentives 71 | have failed me for two reasons. 72 | 73 | 1. Internally, I know that I can give myself the reward regardless of whether I 74 | achieved my goal. 75 | 2. Sometimes I would rather forfeit the incentive than do the work required to 76 | get it. 77 | 78 | Incentives that are gated by someone else are likely more successful, but if it 79 | isn't something you can give yourself, then it's probably not something you can 80 | reasonably ask of from a friend. 81 | 82 | ### Disincentives 83 | 84 | ![Disincentives](/images/11-12-28-Disincentives.png) 85 | 86 | While self-awarded incentives are limited by what you might give yourself 87 | anyway, disincentives have no such restrictions. The only problem here is making 88 | sure that the punishment is unavoidable. 89 | 90 | Self-imposed disincentives share the issue of convincing yourself that your goal 91 | was unachievable and abandoning your commitment. Luckily, certain kinds of 92 | disincentives are not only something you can reasonably ask of your friends, but 93 | something that some friends will thoroughly enjoy enforcing. 94 | 95 | ### Schadenfreude 96 | 97 | [Schadenfreude][] is a most excellent German word that [@ampersandy][] pointed 98 | out to me meaning "pleasure derived from the misfortune of others". While 99 | serious misfortune yields sympathy from our friends — there are some 100 | undesired outcomes that are just downright funny. Whether it be someone 101 | attempting an acrobatic stunt and failing spectacularly or coming to the 102 | realization that they've just landed on Park Place and Board Walk with hotels in 103 | the same turn — 104 | sometimes it's just funny. 105 | 106 | The Plan 107 | ======== 108 | 109 | This enjoyment of our friends' misfortunes is the primary motivational tool in 110 | my plan. The idea is to select a disincentive which your friends will gleefully 111 | enforce, tell everyone about it, then fight to deprive your friends of that 112 | sweet, sweet Schadenfreude. 113 | 114 | ### The Bait 115 | 116 | I couldn't think of a suitable motivating punishment, so I crowdsourced it to my 117 | beloved friends with the following post on Facebook: 118 | 119 | ![Bait](/images/11-12-28-Bait.png) 120 | 121 | ### The Restrictions 122 | 123 | The bait was followed by a list of restrictions I was imposing on the 124 | punishment. #5 and up weren't included in the list I gave to my friends, but are 125 | things I would recommend if you plan to make your own pledge. 126 | 127 | 1. **It has to only negatively affect me.** This rules out punishments that 128 | would hurt my productivity on group projects or at work. 129 | 2. **It has to have only short term negative consequences.** It should be sweet 130 | sadistic joy for my friends, but shouldn't ruin my career, relationships or 131 | skeletal structure. 132 | 3. **I can't be morally against the punishment.** While I will do everything I 133 | can to avoid suffering the punishment — I have to be willing to 134 | actually go through with it. 135 | 4. **It can't be illegal.** This one is pretty self-explanatory. 136 | 5. **It can't be charitable.** One option I've seen before is to donate to a 137 | charity if you fail your goal. This seems like a poor option because you'll 138 | feel good about yourself for donating if you fail and it would make you feel 139 | crummy for celebrating the fact that you succeeded and don't have to give to 140 | charity. 141 | 6. **It has to be all or nothing.** Make success/failure boolean. If you reduce 142 | the punishment with every step you make, then you might be alright with 143 | bearing the brunt of 5% of the punishment. The last 5% of your trek should be 144 | the most motivating — not the least. The point here is not to make an 145 | effort — the point is to succeed. 146 | 7. **It has to be impossible to fail the punishment.** This rules out any 147 | punishment that requires a lot of time on the part of the person being 148 | punished, such as hours of physical labour or reading of terrible books. 149 | 150 | ### The Response 151 | 152 | I got the following recommendations for punishment upon failure: 153 | 154 | * **Post all of the work you did, no matter how unfinished or terrible.** This 155 | is a decent suggestion — but I don't think posting my incomplete stuff 156 | would bother me all that much. 157 | * **Every day you don't draw, you have to start a company.** 158 | * **Chinese Water Torture.** 159 | * **Watch a movie with false depictions of programming for each day missed.** 160 | * **For every day missed, bike a kilometer**. 161 | 162 | But the winner was: 163 | 164 | ![Legs](/images/11-12-28-Legs.png) 165 | 166 | * **Get your legs waxed.** This meets all of my criteria, and I _really_ don't 167 | want to do this. 168 | 169 | ### The Pledge 170 | 171 | ** Starting January 1st, 2012, I will draw for a total of 121 hours before 172 | 11:59pm EST on May 1st, 2012. The accumulated time with be measured by me with 173 | minute accuracy using a stopwatch. If I fail to complete this goal, I will get 174 | my legs professionally waxed, and any of my friends who wish to attend will be 175 | allowed to do so. ** 176 | 177 | 178 | [CCC]: http://cemc.math.uwaterloo.ca/contests/computing.html 179 | [dwite]: http://dwite.ca/ 180 | [ECOO-CS]: http://ecoo.org/index.php?option=com_content&task=view&id=26&Itemid=57 181 | [UVa]: http://uva.onlinejudge.org/ 182 | [USACO Training Program Gateway]: http://ace.delos.com/usacogate 183 | [skiena]: http://www.amazon.com/dp/0387001638/?tag=stackoverfl08-20 184 | [hackquest]: http://hackquest.com/ 185 | [hackthissite]: http://www.hackthissite.org/ 186 | 187 | [dance]: http://www.youtube.com/watch?v=1PQchpvm3Ng&t=4m18s 188 | [draw]: http://phleet.deviantart.com/#/dkpjui 189 | [abstract 3D stuff]: http://phleet.deviantart.com/gallery/?offset=24#/dgwcs3 190 | [play badminton]: https://fbcdn-sphotos-a.akamaihd.net/hphotos-ak-ash4/199721_4379354147_508144147_35151_7735_n.jpg 191 | 192 | [r/programming]: http://www.reddit.com/r/programming 193 | [Hacker News]: http://news.ycombinator.com/ 194 | 195 | [Schadenfreude]: http://en.wikipedia.org/wiki/Schadenfreude 196 | [@ampersandy]: http://twitter.com/ampersandy 197 | -------------------------------------------------------------------------------- /_posts/2012-02-01-stop-returning-void.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Chaining, or Why You Should Stop Returning Void 4 | tags: 5 | - literate programming 6 | - javascript 7 | - c++ 8 | - jQuery 9 | - api 10 | --- 11 | 12 | Do you have code in your codebase that looks like this? 13 | 14 | lang:c++ 15 | 16 | formPanel.setSize(300, 150); 17 | formPanel.add(usernameLabel); 18 | formPanel.add(usernameField); 19 | formPanel.add(passwordLabel); 20 | formPanel.add(passwordField); 21 | 22 | frame.add(formPanel); 23 | 24 | Well, I'm here to tell you that your API is stupid. You should be trying to do 25 | this instead: 26 | 27 | lang:c++ 28 | 29 | formPanel 30 | .setSize(300, 150) 31 | .add(usernameLabel) 32 | .add(usernameField) 33 | .add(passwordLabel) 34 | .add(passwordField) 35 | .addTo(frame); 36 | 37 | Since you're repeatedly dealing with the same variable here, why should you need 38 | to write the name of that variable more than once? 39 | 40 | Put another way, this provides all of the same semantic information in fewer 41 | characters. Brevity is bliss. 42 | 43 | Implementation 44 | -------------- 45 | 46 | Chaining, is basically just returning `this` and using that result to call 47 | another member method. The idiom looks the same in most object oriented 48 | languages, but I'll use C++ in my examples because it presents some interesting 49 | problems. 50 | 51 | As a simple example in C++, you could implement a class with chaining getters 52 | and setters like this: 53 | 54 | lang:c++ 55 | 56 | class Rectangle { 57 | public: 58 | Rectangle() : 59 | _width(0), 60 | _height(0), 61 | _top(0), 62 | _left(0) 63 | {} 64 | 65 | int width() { return _width; } 66 | int height() { return _height; } 67 | int top() { return _top; } 68 | int left() { return _left; } 69 | 70 | Rectangle& width(int w) { _width = w; return *this; } 71 | Rectangle& height(int h) { _height = h; return *this; } 72 | Rectangle& top(int t) { _top = t; return *this; } 73 | Rectangle& left(int l) { _left = l; return *this; } 74 | 75 | private: 76 | int _width; 77 | int _height; 78 | int _top; 79 | int _left; 80 | }; 81 | 82 | Then you'll be able to use it like this: 83 | 84 | lang:c++ 85 | 86 | Rectangle rectangle; 87 | 88 | rectangle 89 | .width(100) 90 | .height(50) 91 | .top(10) 92 | .left(75); 93 | 94 | cout << "Rectangle:" << endl 95 | << "\twidth: " << rectangle.width() << endl 96 | << "\theight: " << rectangle.height() << endl 97 | << "\ttop: " << rectangle.top() << endl 98 | << "\tleft: " << rectangle.left() << endl 99 | << endl; 100 | 101 | *As an aside - notice that `cout` facilitates chaining by returning an 102 | `ostream&`* 103 | 104 | The setters in most libraries that don't support chaining will either return 105 | `void` or a response code indicating whether the setting was successful. 106 | 107 | Returning void doesn't provide any additional information, so I only see its 108 | benefit as a matter of performance, and I suspect dealing with an extra returned 109 | reference is not your performance bottleneck. 110 | 111 | Returning a response code has it's merits, but in most language with exceptions, 112 | you can just throw one in the case of an invalid property set instead. There are 113 | certainly legitimate reasons to use response codes, and that's why this article 114 | isn't called "Why You Should Stop Returning Response Codes". 115 | 116 | As an added bonus, it means you don't need to have constructor calls like 117 | 118 | lang:c++ 119 | 120 | Rectangle rectangle(100, 50, 10, 75); 121 | 122 | For an explanation of why I think constructor calls like this are terrible, see 123 | my post [Name your Arguments!](/2011/11/28/name-your-arguments/). 124 | 125 | Fun with Preprocessor Macros 126 | ---------------------------- 127 | 128 | If you notice in the C++ example above, there's a repeated pattern which we can 129 | get rid of if we use a bit of dirty macros. (You probably don't want to use 130 | these macros in production code.) While I was working on schoolwork, I wanted to 131 | see if I could build the API I wanted with less code - something like this: 132 | 133 | lang:c++ 134 | 135 | class Rectangle { 136 | public: 137 | Rectangle() : 138 | _width(0), 139 | _height(0), 140 | _top(0), 141 | _left(0) 142 | {} 143 | 144 | PROPERTY(Rectangle, int, width) 145 | PROPERTY(Rectangle, int, height) 146 | PROPERTY(Rectangle, int, top) 147 | PROPERTY(Rectangle, int, left) 148 | }; 149 | 150 | And this can be accomplished with preprocessor macros which look like this: 151 | 152 | lang:c++ 153 | 154 | #define GETTER(type, var) \ 155 | type& var() { return _##var; } 156 | 157 | #define SETTER(classtype, type, var) \ 158 | classtype& var(type _##var) { this->_##var = _##var; return *this; } 159 | 160 | #define PROPERTY(classtype, type, var) \ 161 | public: \ 162 | GETTER(type, var) \ 163 | SETTER(classtype, type, var) \ 164 | private: \ 165 | type _##var; 166 | 167 | **NOTE**: This macro leaves the class in private access, so anything following 168 | the `PROPERTY` directives will be private unless otherwise specified. 169 | 170 | Chaining, Static Typing, and Inheritance. 171 | ----------------------------------------- 172 | 173 | Given `RotatedRectangle` which inherits from `Rectangle`: 174 | 175 | lang:c++ 176 | 177 | class RotatedRectangle : public Rectangle { 178 | public: 179 | RotatedRectangle() : 180 | _rotation(0) 181 | {} 182 | 183 | int rotation() { return _rotation; } 184 | RotatedRectangle& rotation(int r) { _rotation = r; return *this; } 185 | 186 | private: 187 | int _rotation; 188 | }; 189 | 190 | if you try to do this 191 | 192 | lang:c++ 193 | 194 | rectangle 195 | .width(100) 196 | .height(50) 197 | .top(10) 198 | .left(75) 199 | .rotation(45); 200 | 201 | you'll wind up with this compiler error: 202 | 203 | error: ‘class Rectangle’ has no member named ‘rotation’ 204 | 205 | which is true. This happens because the return type of `Rectangle::left` is 206 | `Rectangle&`. The unfortunate reality of chaining in statically typed languages 207 | is that it doesn't play nice with inheritance. 208 | 209 | There are various solutions to this, like reimplementing all the methods or 210 | making a bunch of pure virtual functions on the parent classes, but none of them 211 | are very pretty. See [Method chaining + inheritance don't play well 212 | together?](http://stackoverflow.com/questions/551263/method-chaining-inheritance-dont-play-well-together) 213 | on Stack Overflow for more information. 214 | 215 | This isn't a problem in dynamically typed languages like Python and JavaScript 216 | because they'll just see if the method exists on that object at runtime. 217 | 218 | Chaining in jQuery 219 | ------------------ 220 | 221 | The first time I saw this kind of syntax was jQuery, and the API for it is an 222 | incredible amount better because of it. For example, it lets you do stuff like 223 | this: 224 | 225 | lang:javascript 226 | 227 | $('a#button') 228 | .text('Click Me!') 229 | .css({ 230 | backgroundColor : 'red', 231 | font-size : '72pt' 232 | }) 233 | .click(function() { 234 | alert('I have been clicked!'); 235 | }); 236 | 237 | jQuery also facilitates chaining between objects. Take a look at the following 238 | example which fades in a container div then turns all of the links green. 239 | 240 | lang:javascript 241 | 242 | $('.container') 243 | .fadeIn() 244 | .find('a') 245 | .css('color', 'green'); 246 | 247 | Since this just requires having methods that return other chainable objects, 248 | this is nothing special. What *does* make jQuery's chaining interesting is the 249 | ability to go back up the chain with `.end()`. 250 | 251 | I learned about this reading [Ben Kamens][]' post [.end() makes jQuery DOM 252 | Traversal Beautiful][bjkend], so I'm just going to take his excellent example 253 | verbatim. 254 | 255 | lang:javascript 256 | 257 | $("#container") 258 | .show() 259 | .find(".error") 260 | .hide() 261 | .end() 262 | .find(".zoo") 263 | .css("background-color", "white") 264 | .find(".monkeys") 265 | .empty() 266 | .end() 267 | .find(".title") 268 | .text("The zoo is empty") 269 | .end() 270 | .find("input") 271 | .val("") 272 | .end() 273 | .animate({height: 250}); 274 | 275 | `.end()` lets you structure your code in a way that mimics the structure of the 276 | elements - namely its tree structure. 277 | 278 | [Ben Kamens]: http://bjk5.com/ 279 | [bjkend]: http://bjk5.com/post/14819494947/end-makes-jquery-dom-traversal-beautiful 280 | 281 | In need of a better API? Why not Chaining? 282 | ------------------------------------------ 283 | 284 | Next time you start seeing the same variable start every line, consider 285 | chaining, especially if you're working in a dynamically typed language. 286 | -------------------------------------------------------------------------------- /_posts/2012-05-25-an-argument-for-mutable-local-history.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: An Argument for Mutable Local History 4 | tags: 5 | - mercurial 6 | - git 7 | - svn 8 | - khan-academy 9 | --- 10 | 11 | In my past co-op jobs, I've been using predominately git with an (unfortunate) 12 | smattering of subversion. This term at [Khan Academy][], I'm using mercurial for 13 | the most part. 14 | 15 | Mercurial does more or less everything I want it to, and it does admittedly have 16 | more sanely named commands than git. It also has an amazing system for 17 | formatting logs in any conceivable format, a robust plugin architecture and a 18 | really cool system for finding the commits you care about called [revsets][]. 19 | 20 | The thing I'm missing most from git is mutable local history. Editing history is 21 | intentionally difficult in mercurial because it's really easy to shoot yourself 22 | in the foot and make your entire team's life difficult if you don't know what 23 | you're doing. But there are some excellent use cases for it. 24 | 25 | **EDIT**: The above paragraph is a little misinformed. After a conversation with 26 | the very helpful people in #mercurial on IRC (thanks kiilerix), it seems that 27 | there are direct equivalents of amend and rebase. If you're running mercurial 28 | 2.2, there is literally a `--amend` flag on commit, and there's literally an `hg 29 | rebase` command if you turn on the [rebase extension][]. 30 | 31 | Keeping the Logs Clean - Amending Commits 32 | ========================================= 33 | 34 | If your history is immutable, you'll inevitably end up with logs that look like 35 | this: 36 | 37 | changeset: 11922:c669975527b7 38 | user: Jamie Wong 39 | date: Thu May 24 15:19:25 2012 -0400 40 | summary: whoops typo 41 | 42 | changeset: 11921:5290491fca66 43 | user: Jamie Wong 44 | date: Thu May 24 14:07:26 2012 -0400 45 | summary: lint fixes 46 | 47 | changeset: 11921:5290491fca66 48 | user: Jamie Wong 49 | date: Thu May 24 14:07:26 2012 -0400 50 | summary: Added button 51 | 52 | The problem with this is that nobody cares that you fixed lint or that there was 53 | a typo in your original commit. All they need to know is what changed since the 54 | last time they pulled. The "lint fix" and "typo" commits are usually noise that 55 | you can avoid. Combined with a bunch of merge commits, now only about a quarter 56 | of the commits describe actual features being integrated. 57 | 58 | Using git, after I made the lint and typo fixes, assuming I hadn't pushed yet 59 | (which I haven't, because the lint fixes and typos were caught during code 60 | review), I would just use `git commit --amend`, or `git rebase --interactive` to 61 | squash those commits down into a single commit. 62 | 63 | The result of this is a commit history where the vast majority of commits show 64 | the introduction of full features or bugfixes. Once the code is shared with 65 | everyone, it doesn't matter how many revisions you went through to get there, it 66 | just matters what the before and after look like. 67 | 68 | [Evan Priestley][] describes this more eloquently in the [Phabricator][] article 69 | [One Idea is One Commit][], and also notes the benefits to release engineering 70 | and automated testing. 71 | 72 | Local Features Go on Top - Rebasing 73 | =================================== 74 | 75 | If you're using local feature branches in mercurial or git or any other DVCS 76 | (which you should - they're awesome), then you'll be working on Feature 2 while 77 | Feature 1 is up for review. So your history looks something like this: 78 | 79 | o feature-1 80 | | 81 | | o feature-2 82 | | | 83 | |/ 84 | o master 85 | 86 | Workflow without Rebase 87 | ----------------------- 88 | 89 | Feature 1 gets approved, so you switch to the master branch, and run `pull` to 90 | make sure you're up to date with the rest of the repository. As it turns out, 91 | someone just merged a couple days of commits from upstream, so now you have 92 | something like this: 93 | 94 | o master 95 | | 96 | o more stuff from upstream 97 | | 98 | o 99 | 100 | ... 101 | 102 | o stuff from upstream 103 | | 104 | | o feature-1 105 | | | 106 | | | o feature-2 107 | | | | 108 | |/ / 109 | o--+ 110 | 111 | If enough commits just came down, your feature branches won't even show up on 112 | the same screen as `master` any more, which is annoying by itself. You then 113 | merge `feature-1` into `master` and push, and end up with this: 114 | 115 | o master 116 | |\ 117 | o | 118 | | | 119 | o | more stuff from upstream 120 | | | 121 | o | 122 | 123 | ... 124 | 125 | o | stuff from upstream 126 | | | 127 | | o feature-1 128 | | | 129 | | | o feature-2 130 | | | | 131 | |/ / 132 | o--+ 133 | 134 | So even after you merge, you *still* can't see `feature-1` on your screen, 135 | despite it being the latest thing you introduced. Now you want to test Feature 2 136 | against the latest codebase, so you have to merge in master, leaving me with 137 | this: 138 | 139 | o feature-2 140 | |\ 141 | o \ master 142 | |\ \ 143 | o | | 144 | | | | 145 | o | | more stuff from upstream 146 | | | | 147 | o | | 148 | 149 | ... 150 | 151 | o | | stuff from upstream 152 | | | | 153 | | o | feature-1 154 | | | | 155 | | | o 156 | | | | 157 | |/ / 158 | o--+ 159 | 160 | Now I have the merge commit at the top in my branch, which is going to make 161 | putting this up for review ugly. 162 | 163 | Workflow with Rebase 164 | -------------------- 165 | 166 | `git rebase` lets you move commits around (and really do whatever you want with 167 | them). 168 | 169 | **PSA**: Never, _never_ rebase commits that anyone else has access to. The only 170 | exception to this rule I can think of is using github pull requests, since 171 | nobody should be sending commits to your pull requests. `git push` actually warn 172 | you about this if you try to push commits that have been modified, and force you 173 | to use a `-f` flag to make sure you know what you're doing. 174 | 175 | That said - on with the workflow, starting from here: 176 | 177 | o master 178 | | 179 | o more stuff from upstream 180 | | 181 | o 182 | 183 | ... 184 | 185 | o stuff from upstream 186 | | 187 | | o feature-1 188 | | | 189 | | | o feature-2 190 | | | | 191 | |/ / 192 | o--+ 193 | 194 | This time, we're going to rebase instead of merge `feature-1` before we push it. 195 | 196 | $ git checkout feature-1 197 | $ git rebase master 198 | 199 | which leaves us with this: 200 | 201 | o feature-1 202 | | 203 | o master 204 | | 205 | o more stuff from upstream 206 | | 207 | o 208 | 209 | ... 210 | 211 | o stuff from upstream 212 | | 213 | | 214 | | 215 | | o feature-2 216 | | | 217 | |/ 218 | o 219 | 220 | This found the merge base between `feature-1` and `master`, took all the commits 221 | that were in the history of `feature-1` but not in `master`, cut them off the 222 | tree and grafted them back on at `master`. 223 | 224 | Now we can merge `feature-1` into `master` without creating a merge commit (this 225 | is called a "fast-forward" merge). 226 | 227 | $ git checkout master 228 | $ git merge feature-1 229 | $ git push 230 | 231 | Now `feature-1` and `master` are exactly the same, so we could remove the branch 232 | if we wanted. 233 | 234 | Now when we want to return to working on `feature-2`, we just rebase again and 235 | we're working with the latest code. 236 | 237 | $ git checkout feature-2 238 | $ git rebase master 239 | 240 | And we're back to a nice linear history - easy to navigate, easy to read. 241 | 242 | o feature-2 243 | | 244 | o master, feature-1 245 | | 246 | o more stuff from upstream 247 | | 248 | o 249 | 250 | ... 251 | 252 | o stuff from upstream 253 | | 254 | o 255 | 256 | 257 | Side Notes 258 | ---------- 259 | 260 | 1. One-commit-per-feature doesn't work for huge features, but it can at least 261 | work for the describable features within the large feature. When the entire 262 | monolithic feature is done, then all of the commits representing ideas can be 263 | merged into production. 264 | 265 | 2. Before you start using `git rebase` and `git commit --amend` willy-nilly, I 266 | strongly recommend having an understanding of `git reflog`. 267 | 268 | 3. If you're interested in getting a deeper understanding of git, I strongly 269 | recommend reading [Pro Git][], which is free online. 270 | 271 | 4. I realize that history editing is *possible* in mercurial, but it's 272 | definitely not as supported as it is in git. 273 | 274 | 5. If you're not using a distributed version control system (DVCS) like git or 275 | hg, you're doing it wrong for a whole bunch of reasons, but that's a rant for 276 | another time. 277 | 278 | 6. If you're stuck using subversion for whatever reason, *please* use 279 | [git-svn][]. 280 | 281 | [rebase extension]: http://mercurial.selenic.com/wiki/RebaseExtension 282 | [Pro Git]: http://git-scm.com/book 283 | [Khan Academy]: http://www.khanacademy.org/ 284 | [revsets]: http://www.selenic.com/hg/help/revsets 285 | [Evan Priestley]: https://twitter.com/#!/evanpriestley 286 | [git-svn]: http://trac.parrot.org/parrot/wiki/git-svn-tutorial 287 | [Phabricator]: http://phabricator.org/ 288 | [One Idea is One Commit]: http://www.phabricator.com/docs/phabricator/article/Recommendations_on_Revision_Control.html#one-idea-is-one-commit 289 | -------------------------------------------------------------------------------- /_sass/screen.sass: -------------------------------------------------------------------------------- 1 | // This import applies a global reset to any page that imports this stylesheet. 2 | @import blueprint/reset 3 | 4 | // To configure blueprint, edit the partials/base.sass file. 5 | @import partials/base 6 | 7 | // Import all the default blueprint modules so that we can access their mixins. 8 | @import blueprint 9 | 10 | // Import the non-default scaffolding module. 11 | @import blueprint/scaffolding 12 | 13 | @import compass/css3 14 | 15 | // CSS3 border radius 16 | @import compass/css3/border-radius 17 | 18 | // CSS3 image background 19 | @import compass/css3/images 20 | 21 | // To generate css equivalent to the blueprint css but with your 22 | // configuration applied, uncomment: 23 | // @include blueprint 24 | 25 | // If you are doing a lot of stylesheet concatenation, it is suggested 26 | // that you scope your blueprint styles, so that you can better control 27 | // what pages use blueprint when stylesheets are concatenated together. 28 | 29 | $font-color: #281e17 30 | $link-color: lighten($font-color, 35%) 31 | $link-hover-color: darken($link-color, 25%) 32 | $link-active-color: lighten(adjust-hue($link-color, 75deg), 10%) 33 | $link-visited-color: darken($link-color, 20%) 34 | 35 | $blueprint-font-size: 14px 36 | $header-color: $font-color 37 | 38 | $body-bg: #eee 39 | 40 | body.bp 41 | @include blueprint-typography(true) 42 | @include blueprint-utilities 43 | @include blueprint-debug 44 | @include blueprint-interaction 45 | 46 | @include blueprint-scaffolding 47 | // Remove the scaffolding when you're ready to start doing visual design. 48 | // Or leave it in if you're happy with how blueprint looks out-of-the-box 49 | 50 | overflow-y: scroll 51 | 52 | width: 100% 53 | min-height: 100% 54 | 55 | background-color: $body-bg 56 | 57 | a 58 | text-decoration: none 59 | border-bottom: 1px dotted $link-color 60 | 61 | &:hover 62 | border-bottom: none 63 | 64 | html, body 65 | height: 100% 66 | 67 | $sidebar-width: 150px 68 | $container-width: 850px 69 | $sidebar-bg: desaturate($font-color, 20%) 70 | 71 | .page-container 72 | min-height: 100% 73 | 74 | $left-pct: 30% 75 | 76 | .page-sidebar-container 77 | z-index: 9000 78 | 79 | background-color: $sidebar-bg 80 | position: fixed 81 | 82 | width: $left-pct 83 | height: 100% 84 | 85 | .page-content-container 86 | margin-left: $left-pct 87 | width: 100% - $left-pct 88 | 89 | body.bp .page-sidebar 90 | font-family: Questrial, "Helvetica Neue", Arial, Helvetica, sans-serif 91 | 92 | float: right 93 | width: $sidebar-width 94 | height: 100% 95 | 96 | $zero-wind-bg: lighten($sidebar-bg, 30%) 97 | 98 | h1 99 | margin: 20px 15px 10px 0 100 | font-family: "Sansita One", "Helvetica Neue", Arial, Helvetica, sans-serif 101 | 102 | color: lighten($zero-wind-bg, 10%) 103 | border: none 104 | font-size: 48px 105 | line-height: 42px 106 | +text-shadow(darken($sidebar-bg, 10%) 2px 2px 1px) 107 | text-align: right 108 | text-decoration: none 109 | 110 | &:hover 111 | color: lighten($zero-wind-bg, 20%) 112 | 113 | h2.my-name 114 | border: none 115 | font-size: 22px 116 | text-align: center 117 | color: $zero-wind-bg 118 | +text-shadow(lighten($sidebar-bg, 10%) 0 1px 2px, darken($sidebar-bg, 5%) 0 -1px 2px) 119 | 120 | .my-picture 121 | background: url(/images/me.png) 122 | width: 130px 123 | height: 130px 124 | border-radius: 10px 125 | background-repeat: none 126 | margin: 0 auto 127 | 128 | .short-bio 129 | color: $zero-wind-bg 130 | font-style: italic 131 | font-size: 12px 132 | padding: 0 15px 133 | margin: 0 0 5px 0 134 | text-align: justify 135 | 136 | p 137 | text-indent: 2em 138 | margin: 0 139 | 140 | .page-nav 141 | +text-shadow(lighten($sidebar-bg, 10%) 0 1px 1px, darken($sidebar-bg, 5%) 0 -1px 1px) 142 | 143 | $link-color: $zero-wind-bg 144 | 145 | a 146 | display: block 147 | line-height: 30px 148 | 149 | color: $link-color 150 | border: none 151 | 152 | &:visited 153 | color: $link-color 154 | 155 | &:hover 156 | color: lighten($link-color, 25%) 157 | 158 | ul 159 | margin: 0 160 | padding: 0 15px 161 | 162 | li 163 | list-style: none 164 | font-size: 16px 165 | text-align: right 166 | 167 | li + li 168 | border-top: 1px solid lighten($sidebar-bg, 10%) 169 | 170 | $padding: 15px 171 | $content-width: $container-width - $sidebar-width - 2*$padding 172 | 173 | body.bp .post-list 174 | ul.post-list-ul 175 | max-width: $content-width 176 | margin: 0 0 1.5em 0 177 | padding: 0 178 | 179 | li + li 180 | border-top: 1px solid darken($body-bg, 15%) 181 | 182 | li 183 | clear: both 184 | list-style: none 185 | font-size: 14px 186 | 187 | $date-width: 150px 188 | $line-height: 30px 189 | 190 | a 191 | width: $content-width - $date-width 192 | line-height: $line-height 193 | border: none 194 | 195 | span.date 196 | color: lighten($link-color, 20%) 197 | font-style: italic 198 | line-height: $line-height 199 | width: $date-width 200 | float: right 201 | text-align: right 202 | 203 | body.bp .page-content 204 | padding: $padding 205 | 206 | min-height: 100% 207 | max-width: $content-width 208 | 209 | font-size: 16px 210 | 211 | h1.page-title 212 | margin-bottom: 0 213 | 214 | .post-date 215 | font-style: italic 216 | text-align: left 217 | color: mix($font-color, $body-bg, 50%) 218 | font-size: 80% 219 | margin-bottom: 10px 220 | 221 | h1, h2, h3 222 | line-height: 125% 223 | padding-bottom: 5px 224 | 225 | font-family: Questrial, "Helvetica Neue", Arial, Helvetica, sans-serif 226 | color: $font-color 227 | +text-shadow(lighten($body-bg, 45%) 0 -1px 1px, lighten($body-bg, 45%) 0 1px 1px) 228 | 229 | h1 230 | font-size: 175% 231 | border-bottom: 4px solid darken($body-bg, 50%) 232 | 233 | h2 234 | font-size: 140% 235 | border-bottom: 1px solid darken($body-bg, 35%) 236 | 237 | h3 238 | font-size: 110% 239 | 240 | code 241 | font-size: 90% 242 | color: lighten($font-color, 10%) 243 | 244 | pre code, .highlight 245 | padding: 10px 246 | display: block 247 | border: 0px solid #888 248 | background: darken($sidebar-bg, 10%) 249 | font-size: 12px 250 | +border-radius(10px) 251 | overflow-y: hidden 252 | overflow-x: auto 253 | color: #839496 254 | margin: 1.5em 0 255 | 256 | .highlight pre 257 | margin: 0 258 | 259 | .post 260 | .table-of-contents 261 | ul 262 | list-style: none 263 | 264 | img 265 | -webkit-background-clip: padding-box 266 | +border-radius(10px) 267 | margin: 0 auto 268 | display: block 269 | +box-shadow(darken($body-bg, 50%), 0, 0, 0, 1px, darken($body-bg, 50%), 0, 0, 5px, 0) 270 | 271 | iframe 272 | margin: 0 auto 273 | display: block 274 | 275 | .solicitation 276 | font-style: italic 277 | margin: 25px 0 278 | padding-top: 20px 279 | border-top: 1px solid darken($body-bg, 35%) 280 | 281 | img.inline 282 | float: left 283 | margin-right: 10px 284 | display: inline 285 | 286 | @media screen and (max-height: 602px) 287 | body.bp 288 | .page-sidebar 289 | h1 290 | margin-top: 10px 291 | 292 | .my-picture 293 | height: 20% 294 | 295 | h2 296 | margin-bottom: 0.25em 297 | 298 | .short-bio 299 | margin-bottom: 0 300 | line-height: 1.4em 301 | 302 | .page-nav 303 | li 304 | a 305 | line-height: 28px 306 | 307 | @media screen and (max-height: 558px) 308 | body.bp 309 | .page-sidebar 310 | h1 311 | margin-top: 5px 312 | margin-bottom: 5px 313 | 314 | .my-picture 315 | background-position: 0 -6px 316 | height: 19% 317 | 318 | .short-bio 319 | display: none 320 | 321 | @media screen and (max-height: 460px) 322 | body.bp 323 | .page-sidebar 324 | .my-picture 325 | background-position: 0 -13px 326 | 327 | @media screen and (max-height: 412px) 328 | body.bp 329 | .page-sidebar 330 | h2 331 | margin: 0 332 | 333 | .my-picture 334 | display: none 335 | 336 | @media screen and (max-width: 875px) 337 | body.bp 338 | .page-sidebar-container 339 | width: 28% 340 | 341 | .page-content-container 342 | margin-left: 28% 343 | width: 72% 344 | 345 | @media screen and (max-width: 850px) 346 | body.bp 347 | .page-sidebar-container 348 | width: 26% 349 | 350 | .page-content-container 351 | margin-left: 26% 352 | width: 74% 353 | 354 | @media screen and (max-width: 825px) 355 | body.bp 356 | .page-sidebar-container 357 | width: 24% 358 | 359 | .page-content-container 360 | margin-left: 24% 361 | width: 76% 362 | 363 | @media screen and (max-width: 800px) 364 | body.bp 365 | .page-sidebar-container 366 | width: 22% 367 | 368 | .page-content-container 369 | margin-left: 22% 370 | width: 78% 371 | 372 | @media screen and (max-width: 775px) 373 | body.bp 374 | .page-sidebar-container 375 | width: 20% 376 | 377 | .page-content-container 378 | margin-left: 20% 379 | width: 80% 380 | 381 | @media screen and (max-device-width: 750px), screen and (max-width: 750px) 382 | body.bp 383 | .page-sidebar-container 384 | position: static 385 | width: 100% 386 | 387 | .page-sidebar 388 | height: auto 389 | width: 100% 390 | float: none 391 | 392 | h1 393 | float: left 394 | text-align: left 395 | margin: 11px 15px 5px 396 | 397 | h2.my-name 398 | float: left 399 | margin: 25px 0 0 400 | 401 | .short-bio, .my-picture 402 | display: none 403 | 404 | .page-nav 405 | clear: both 406 | font-size: 11px 407 | 408 | ul 409 | display: table 410 | padding: 0 411 | width: 100% 412 | 413 | li + li 414 | border-top: 0 415 | 416 | li 417 | display: table-cell 418 | font-size: 13px 419 | text-align: center 420 | padding: 0 1% 421 | 422 | .page-content-container 423 | margin-left: 0% 424 | width: 100% 425 | 426 | .page-content 427 | max-width: 100% 428 | padding-bottom: 40px 429 | 430 | .post-list 431 | ul.post-list-ul 432 | max-width: 100% 433 | margin-bottom: 0 434 | -------------------------------------------------------------------------------- /_posts/2011-10-03-meet-doctor-jekyll.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Meet Doctor Jekyll 4 | tags: 5 | - jekyll 6 | - pygments 7 | - sass 8 | - compass 9 | status: publish 10 | type: post 11 | published: true 12 | --- 13 | 14 | My wordpress blog has lasted longer than any site I've used before, but I 15 | finally got sick of it and switched. So now I'm on [Jekyll][]. 16 | 17 | Wordpress is an incredible blogging-platform-turned-CMS that provides many 18 | things I wouldn't want to do myself. It's been an incredible SEO asset and 19 | worked beautifully on shared hosting. I would still recommend it to anyone who 20 | wants to write a blog and doesn't want to spend much time customizing or setting 21 | things up. There are plenty of nice looking pre-made themes and plugins to do 22 | pretty much anything you want. 23 | 24 | ## Wordpress to Jekyll - Why I switched 25 | 26 | [Jekyll][] is "a blog-aware static site generator in Ruby". Basically what this 27 | means is that all the pages you see on this blog are generated once - not 28 | generated on demand every time they're requested. There's no DB backend, no 29 | scripting language running on the server side. These are just plain HTML pages. 30 | 31 | I started off with a template I got off here: 32 | [github.com/krisb/jekyll-template][]. But if you're planning on starting a blog 33 | similar to mine, don't start from scratch like I did - go check out 34 | [Octopress][]. It provides a lot of niceties for blogging hackers that Jekyll 35 | doesn't out of the box. 36 | 37 | [github.com/krisb/jekyll-template]: https://github.com/krisb/jekyll-template 38 | 39 | So why did I switch? 40 | 41 | ### Styling 42 | 43 | While Wordpress is completely skinnable - it's a pain in the ass. I really just 44 | wanted to start from scratch and only build the things I absolutely needed. 45 | There is one unifying layout and only 2 types of page on this site: the posts 46 | list and posts. It means I can set my own structure for everything and keep it 47 | much, much simpler than it would have been on Wordpress. 48 | 49 | ### Workflow 50 | 51 | I love vim. I feel like I'm dragging a huge weight when I need to edit text - 52 | especially code - without vim. Since Jekyll just generates my site from code, it 53 | means I can edit everything including this post in vim. All I do is spawn up a 54 | local Jekyll server (WEBrick) and there's my preview. Once I'm done, all I have 55 | to do is commit the changes to git, and then I pull down the changes on my 56 | server - which is now hosted on EC2, with help from [Andrew Tinits][]. 57 | 58 | 59 | ### Novelty 60 | 61 | I'll be honest here - I feel cooler running Jekyll than Wordpress. And having 62 | the source for my blog on github just feels right. Seriously: 63 | [github.com/phleet/blog][]. 64 | 65 | Plus there's nothing better to motivate you to write a blog post than redoing 66 | your entire blog. I promise I won't be another person who just writes a blog so 67 | they can write about how they set up their blog and then never write again (you 68 | know who you are.) 69 | 70 | ## The Tech 71 | 72 | Here's the part where I justify you spending the time reading this post by 73 | rambling about technology you might be interested in. 74 | 75 | ### Markdown 76 | 77 | [Markdown][] is a content writing language for the web which compiles 78 | (primarily) down to HTML. The idea is that it enables you to write documents 79 | that are still readable in plaintext, but compile to useful HTML. This means 80 | having a nice syntax for headers, emphasis, links, images, lists and a couple of 81 | other things. It's definitely much nicer than raw HTML for writing up blog 82 | posts. 83 | 84 | As a quick example, it takes this: 85 | 86 | ### Markdown 87 | 88 | Markdown was written by John Gruber of [Daring Fireball][]. 89 | 90 | As a quick example, it takes this: 91 | 92 | Hello World 93 | ----------- 94 | 95 | I am pretty awesome 96 | 97 | and produces this: 98 | 99 |

Hello World

100 | 101 |

I am pretty awesome

102 | 103 | [Daring Fireball]: http://daringfireball.net/ 104 | 105 | and produces this: 106 | 107 | lang:html 108 | 109 |

Markdown

110 | 111 |

Markdown was written by John Gruber of Daring Fireball.

112 | 113 |

As a quick example, it takes this:

114 | 115 |
Hello World
116 |     -----------
117 | 
118 |     I am pretty awesome
119 |     
120 | 121 |

and produces this:

122 | 123 |
<h2>Hello World</h2>
124 | 
125 |     <p>I am pretty awesome</p>
126 |     
127 | 128 | ### Sass 129 | 130 | [Sass][] is awesome syntactic sugar for CSS. It deals with a bunch of things 131 | that are simply a giant pain in vanilla CSS. One of my favorite things it does 132 | is deal with nesting for you. 133 | 134 | lang:sass 135 | 136 | .container 137 | h1 138 | font-size: 20px 139 | h2 140 | font-size: 18px 141 | 142 | &:hover 143 | text-decoration: underline 144 | 145 | Turns into: 146 | 147 | lang:css 148 | 149 | .container h1 { 150 | font-size: 20px; } 151 | .container h2 { 152 | font-size: 18px; } 153 | .container h2:hover { 154 | text-decoration: underline; } 155 | 156 | 157 | Since it runs through a compiler before producing CSS, it lets you do some very 158 | useful things before producing the raw CSS. One of the most valuable things for 159 | me while working on this site was variables. It means you can do things like 160 | this: 161 | 162 | lang:sass 163 | 164 | $base-color: #281e17 165 | $content-width: 500px 166 | $sidebar-width: 150px 167 | 168 | .container 169 | background-color: lighten($base-color, 50%) 170 | $padding: 20px 171 | padding: $padding 172 | width: $content-width + $sidebar-width - 2 * $padding 173 | 174 | .sidebar 175 | width: $sidebar-width 176 | background-color: lighten($base-color, 25%) 177 | 178 | .content 179 | width: $content-width 180 | color: $base-color 181 | 182 | And get back this: 183 | 184 | lang:css 185 | 186 | .container { 187 | background-color: #b99a85; 188 | padding: 20px; 189 | width: 610px; } 190 | .container .sidebar { 191 | width: 150px; 192 | background-color: #795b46; } 193 | .container .content { 194 | width: 500px; 195 | color: #281e17; } 196 | 197 | The really cool thing about the color functions is that you can base your entire 198 | website off only a few colours, then make everything relative to those. This 199 | site (at time of writing) actually only has two colours specifically defined - 200 | the content font color and the body background. Everything else is relative to 201 | those. 202 | 203 | ### Compass 204 | 205 | [Compass][] is a collection of mixins and utilities for sass. It's especially 206 | useful for CSS3 related tasks, since it does all the proprietary browser 207 | prefixes (`-O-, -moz-, -webkit-`) for you when you do things like `+box-radius`. 208 | 209 | It also comes with [Blueprint][], which gives you a great starting point to have 210 | all the default styles for things (margins, typography, resets) look nice out of 211 | the box. 212 | 213 | ### Pygments 214 | 215 | [Pygments][] is a syntax highlighter which produces HTML output which can then 216 | be styled with CSS. Github uses a wrapper around it called [Albino][] to do all 217 | the syntax highlighting you see on the site, and it's what I'm using to make 218 | pygments work in Jekyll. 219 | 220 | I'm actually using a custom Jekyll plugin to deal with all of this because I 221 | didn't want to have to stick liquid tags in my posts. In retrospect, this was 222 | probably really unnecessary, but it's done now. 223 | 224 | You can see the plugin here: [markdown.rb][] 225 | 226 | [markdown.rb]: https://github.com/phleet/blog/blob/master/_plugins/markdown.rb 227 | 228 | ### Google Web Fonts 229 | 230 | [Google Web Fonts][] let you use a number of beautiful fonts that may not be 231 | available on your visitor's machine on your websites. I'm currently using it for 232 | my logo, sidebar and headers. 233 | 234 | Using it is beautifully simple. All you have to do is drop a link tag in the 235 | head like the one I'm using: 236 | 237 | lang:html 238 | 239 | 240 | 241 | And then use it like normal in a CSS rule, like so: 242 | 243 | lang:css 244 | 245 | font-family: Questrial, "Helvetica Neue", Arial, Helvetica, sans-serif 246 | 247 | ### Nginx 248 | 249 | Since I had to set up my own web server to run this, I decided I'd try messing 250 | around with [nginx][] since it looks much nicer to configure than apache. For 251 | the longest time, I thought it was pronounced "en-jinx". The actual 252 | pronounciation, "engine-ex" made a lot more sense once I heard it. 253 | 254 | Setting up this server was extremely simple. Since I'm only serving static 255 | files, all I really needed was the following: 256 | 257 | lang:nginx 258 | 259 | # Redirect any subdomain to the main domain 260 | server { 261 | server_name *.jamie-wong.com; 262 | rewrite ^ http://jamie-wong.com$request_uri? permanent; 263 | } 264 | 265 | server { 266 | listen 80 default; 267 | server_name jamie-wong.com; 268 | server_name_in_redirect off; 269 | 270 | access_log /var/log/nginx/jamie-wong.com.log; 271 | 272 | index index.html index.php; 273 | 274 | try_files $uri $uri/ /404/ =404; 275 | 276 | # If it's in any of these folders I migrated from 277 | # my shared hosting on dreamhost, load it directly 278 | location ~ ^/(experiments|asciiconvert|life|Jobmine-Improved|V10|1yr)/ { 279 | root /var/www/jamie-wong.com/; 280 | } 281 | 282 | # Otherwise, it's probably a blog page, so load it 283 | # from Jekyll's generated _site directory 284 | location ~ / { 285 | root /var/www/jamie-wong.com/blog/_site/; 286 | } 287 | 288 | error_page 404 /404/; 289 | } 290 | 291 | This got a lot longer than I expected it to. Time for sleep - will proofread 292 | tomorrow. 293 | 294 | [nginx]: http://nginx.net/ 295 | [Google Web Fonts]: http://www.google.com/webfonts 296 | [Pygments]: http://pygments.org/ 297 | [Albino]: https://github.com/github/albino 298 | [Compass]: http://compass-style.org/ 299 | [Blueprint]: http://compass-style.org/reference/blueprint/ 300 | [Sass]: http://sass-lang.com/ 301 | [Markdown]: http://daringfireball.net/projects/markdown/ 302 | [github.com/phleet/blog]: https://github.com/phleet/blog 303 | [Andrew Tinits]: http://twitter.com/#!/amtinits 304 | [Jekyll]: https://github.com/mojombo/jekyll 305 | [Solarized]: http://ethanschoonover.com/solarized 306 | [Octopress]: http://octopress.org/ 307 | -------------------------------------------------------------------------------- /_posts/2009-12-06-lambda-functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Lambda Functions 4 | tags: 5 | - filter 6 | - lambda 7 | - map 8 | - python 9 | - reduce 10 | - sort 11 | - Tutorials 12 | status: publish 13 | type: post 14 | published: true 15 | meta: 16 | _edit_lock: "1265429985" 17 | _edit_last: "1" 18 | --- 19 | One day, a friend of mine called me up asking for help with his CS assignment. 20 | It was about lambda functions in scheme. Lambda functions had always been 21 | something confusing both in theory and practice for me. Why do you need them? 22 | Before I explain what they are, I'll start with a simple probably justifying 23 | _why_ they are. 24 | 25 | All code in this post is going to be in python, but most of it can be applied to 26 | other high level languages. I've only used lambda functions in python and 27 | scheme, but I'm sure the functionality exists in many, many other languages. 28 | 29 | Sorting 30 | ======= 31 | 32 | Sorting in python is very straight forward. If I have a list of strings in python and want to sort them, I do this: 33 | 34 | lang:python 35 | 36 | a = ["banana","apple","pear","elephant","zebra","mango"] 37 | a.sort() 38 | print a 39 | # OUT: ['apple', 'banana', 'elephant', 'mango', 'pear', 'zebra'] 40 | 41 | But what happens when you try to sort a list of words containing capital letters? 42 | 43 | lang:python 44 | 45 | a = ["pear","Police","apple","Airplane","banana","Bear"] 46 | a.sort() 47 | print a 48 | # OUT: ['Airplane', 'Bear', 'Police', 'apple', 'banana', 'pear'] 49 | 50 | The sorting is done purely based on the byte values of the characters. Since 'P' 51 | (ASCII 80) < 'a' (ASCII 97), "Police" is placed before "apple" in the list of 52 | words. Normally when you're trying to sort a list of words, you want to do it 53 | alphabetically. So how do you fix this? You make a comparator function. Here's 54 | python's definition of the sort function. 55 | 56 | lang:python 57 | 58 | L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*; 59 | cmp(x, y) -> -1, 0, 1 60 | 61 | For those of you who have used `qsort` in the standard C library, this isn't 62 | such an arcane concept. In python however, it is much easier (no `void*` 63 | madness). 64 | 65 | lang:python 66 | 67 | def alphacmp(x,y): 68 | return cmp(x.lower(),y.lower()) 69 | 70 | a = ["pear","Police","apple","Airplane","banana","Bear"] 71 | a.sort(alphacmp) 72 | print a 73 | # OUT: ['Airplane', 'apple', 'banana', 'Bear', 'pear', 'Police'] 74 | 75 | Okay, great! We now have our list sorted in the intuitive way. The lower() 76 | function will ensure that the lowercase version of the strings are being 77 | compared, removing the issues of the byte comparisons. But this code seems a 78 | little longer than it could be. Normally, you wouldn't create a function for 79 | task you do only once if it was just as readable within the code body. But the 80 | sort function needs a comparator function. So how can you give sort a comparator 81 | function without having to actually declare a function? The answer is lambda 82 | functions. 83 | 84 | Lambda Functions 85 | ================ 86 | 87 | Lambda functions are anonymous functions. What does that mean? They're functions 88 | without a declared name. Let's look at an example so you can see what I mean. In 89 | the above example, we can sort alphabetically without defining the `alphacmp` 90 | function like so: 91 | 92 | lang:python 93 | 94 | a = ["pear","Police","apple","Airplane","banana","Bear"] 95 | a.sort(lambda x,y: cmp(x.lower(),y.lower())) 96 | print a 97 | # OUT: ['Airplane', 'apple', 'banana', 'Bear', 'pear', 'Police'] 98 | 99 | So what does lambda x,y: cmp(x.lower(),y.lower()) mean? "lambda" says that the 100 | statements following define an anonymous function. "x,y:" defines the parameter 101 | list accepted by the anonymous function. "cmp(x.lower(),y.lower())" defines the 102 | return value for the lambda function. If you're still a little bit confused, 103 | read on for many many more examples. At some point, it will likely click and 104 | you'll see how valuable lambda really is. 105 | 106 | Another sorting example: what if I want to sort by length of the strings instead of alphabetically? Easily done with lambda. 107 | 108 | lang:python 109 | 110 | a = ["pencil","pen","cap","zebra","Blizzard","0xB4DC0DE","!"] 111 | a.sort(lambda x,y: len(x) - len(y)) 112 | print a 113 | # OUT: ['!', 'pen', 'cap', 'zebra', 'pencil', 'Blizzard', '0xB4DC0DE'] 114 | 115 | The use of lambda functions makes the use of many higher level functions much nicer. I'll be outlining a few of them here. 116 | 117 | map 118 | === 119 | 120 | > map(function, sequence[, sequence, ...]) -> list 121 | > 122 | > Return a list of the results of applying the function to the items of 123 | > the argument sequence(s). If more than one sequence is given, the 124 | > function is called with an argument list consisting of the corresponding 125 | > item of each sequence, substituting None for missing values when not all 126 | > sequences have the same length. If the function is None, return a list of 127 | > the items of the sequence (or a list of tuples if more than one sequence). 128 | 129 | More or less, what map does is apply a function on every element of an array 130 | then return an array of the corresponding return values. 131 | 132 | A very simple use for map would be making every word in a list upper case. 133 | 134 | lang:python 135 | 136 | a = ["abc","cattle","not even if there's a FIRE","Jeymi!?"] 137 | b = map(lambda x: x.upper(),a) 138 | print b 139 | # OUT: ['ABC', 'CATTLE', "NOT EVEN IF THERE'S A FIRE", 'JEYMI!?'] 140 | 141 | A more useful example involves printing out grids. Say I have a matrix of 142 | numbers, represented as a list of lists in python. The default output formatting 143 | for this in python is extremely difficult to look at. 144 | 145 | lang:python 146 | 147 | a = [ 148 | [1,212,-13], 149 | [41,5,614], 150 | [7,8,91] 151 | ] 152 | print a 153 | # OUT: [[1, 212, -13], [41, 5, 614], [7, 8, 91]] 154 | 155 | What would be better is to output one row per line. 156 | 157 | lang:python 158 | 159 | a = [ 160 | [1,212,-13], 161 | [41,5,614], 162 | [7,8,91] 163 | ] 164 | print "\n".join(map(lambda x: str(x), a)) 165 | #OUT: 166 | # [1, 212, -13] 167 | # [41, 5, 614] 168 | # [7, 8, 91] 169 | 170 | _The join function concatenates a list of string, separating the elements with 171 | the separator character - in this case: "\n"_ 172 | 173 | Much better! But what would be even better would be to print out the numbers in 174 | columns that line up nicely too. 175 | 176 | lang:python 177 | 178 | a = [ 179 | [1,212,-13], 180 | [41,5,614], 181 | [7,8,91] 182 | ] 183 | print "\n".join( 184 | map( 185 | lambda row: 186 | " ".join(map( 187 | lambda y: "%4d" % y, 188 | row 189 | )), 190 | a 191 | ) 192 | ) 193 | # OUT: 194 | # 1 212 -13 195 | # 41 5 614 196 | # 7 8 91 197 | 198 | 199 | `"%4d" % y` is applying the formatting string `%4d` to `y`, much the same way 200 | that `printf` in C and php do 201 | 202 | Now, using a lambda function inside of a lambda function is less than readable, 203 | so I'll write out what this function does first without the maps and lambdas. 204 | 205 | lang:python 206 | 207 | a = [ 208 | [1,212,-13], 209 | [41,5,614], 210 | [7,8,91] 211 | ] 212 | 213 | def rowformat(row): 214 | formatted_cells = [] 215 | for element in row: 216 | formatted_cells.append("%4d" % element) 217 | return " ".join(formatted_cells) 218 | 219 | formatted_rows = [] 220 | for row in a: 221 | formatted_rows.append(rowformat(row)) 222 | 223 | print "\n".join(formatted_rows) 224 | # OUT: 225 | # 1 212 -13 226 | # 41 5 614 227 | # 7 8 91 228 | 229 | In this case, `rowformat(row)` plays the exact same roles as `lambda row:`. 230 | 231 | filter 232 | ====== 233 | 234 | > filter(function or None, sequence) -> list, tuple, or string 235 | > 236 | > Return those items of sequence for which function(item) is true. If 237 | > function is None, return the items that are true. If sequence is a tuple 238 | > or string, return the same type, else return a list. 239 | 240 | The filter function does just what it's name suggests: filters out items that 241 | don't meet certain requirements. Specifically, any element which does not 242 | evaluate to true when passed to the provided function will be discarded. Note 243 | that this is _not_ in place - it returns the filtered list. 244 | 245 | Example: remove all odd numbers from a list of numbers 246 | 247 | lang:python 248 | 249 | a = [1,2,5,0,-15,100,1400,-1337,135] 250 | a = filter(lambda x: x % 2 == 0, a) 251 | print a 252 | # OUT: [2, 0, 100, 1400] 253 | 254 | Example: remove all vowels from a string 255 | 256 | lang:python 257 | 258 | a = "Hello there! My name is Jamie, not to be confused with Jeymi." 259 | a = "".join(filter(lambda x: "aeiou".find(x) == -1, list(a))) 260 | print a 261 | # OUT: Hll thr! My nm s Jm, nt t b cnfsd wth Jym. 262 | 263 | Example: build a list of all prime numbers between 0 and 100 inclusive 264 | 265 | lang:python 266 | 267 | # NOTE: This prime test is inefficient - used for readability) 268 | def isprime(x): 269 | if (x < 2): 270 | return False 271 | for i in range(2,x): 272 | if (x % i == 0): 273 | return False 274 | return True 275 | 276 | print filter(isprime,range(101)) 277 | # OUT (placed on multiple lines for readability) 278 | # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 279 | # 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] 280 | 281 | reduce 282 | ====== 283 | 284 | > reduce(function, sequence[, initial]) -> value 285 | > 286 | > Apply a function of two arguments cumulatively to the items of a sequence, 287 | > from left to right, so as to reduce the sequence to a single value. 288 | > For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates 289 | > ((((1+2)+3)+4)+5). If initial is present, it is placed before the items 290 | > of the sequence in the calculation, and serves as a default when the 291 | > sequence is empty. 292 | 293 | Example: Calculate 30 factorial without using local variables 294 | 295 | lang:python 296 | 297 | # Note that range(2,n) -> [2,3,...,n-1] 298 | print reduce(lambda x,y: x*y, range(2,31)) 299 | # OUT: 265252859812191058636308480000000 300 | 301 | Example: Find the longest word in a list of words 302 | 303 | lang:python 304 | 305 | a = ["pencil","pen","cap","zebra","Blizzard","0xB4DC0DE","!"] 306 | print reduce(lambda x,y: y if len(y) > len(x) else x, a) 307 | # OUT: 0xB4DC0DE 308 | 309 | And that's all for now. If you're in SE 2014 reading this and don't think you need to know how to use lambda functions - have fun with scheme. 310 | --------------------------------------------------------------------------------