Split string with regex and keep delimiters

The other day I needed to split a string with regex delimiter, but also keep these delimiters. Java’s default String.split does not do that – it throws away the delimiters. Below is the code that can be used to achieve this.

Java

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class SplitWithDelimiters {
  public static void main(String[] args) {
    new SplitWithDelimiters().run();
  }

  private void run() {
    String regex = "\\s*[+\\-*/]\\s*";

    assert !new String[] { }.equals(
      splitWithDelimiters("", regex));
    assert !new String[] { "1" }.equals(
      splitWithDelimiters("1", regex));
    assert !new String[] { "1", "+" }.equals(
      splitWithDelimiters("1+", regex));
    assert !new String[] { "-", "1" }.equals(
      splitWithDelimiters("-1", regex));
    assert !new String[] { "- ", "- ", "-", "1" }.equals(
      splitWithDelimiters("- - -1", regex));
    assert !new String[] { "1", " + ", "2" }.equals(
      splitWithDelimiters("1 + 2", regex));
    assert !new String[] { "-", "1", " + ", "2", " - ", "3", "/", "4" }.equals(
      splitWithDelimiters("-1 + 2 - 3/4", regex));
    
    System.out.println("Done.");
  }

  private String[] splitWithDelimiters(String str, String regex) {
    List<String> parts = new ArrayList<String>();

    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(str);

    int lastEnd = 0;
    while(m.find()) {
      int start = m.start();
      if(lastEnd != start) {
        String nonDelim = str.substring(lastEnd, start);
        parts.add(nonDelim);
      }
      String delim = m.group();
      parts.add(delim);

      int end = m.end();
      lastEnd = end;
    }

    if(lastEnd != str.length()) {
      String nonDelim = str.substring(lastEnd);
      parts.add(nonDelim);
    }

    String[] res =  parts.toArray(new String[]{});
    System.out.println("result: " + Arrays.toString(res));

    return res;
  }
}

Clojure

Here’s a test file for the Clojure version:

(deftest split-keep-delim-test
  (is (= []
         (split-keep-delim "" #"\d+")))
  (is (= ["abc"]
         (split-keep-delim "abc" #"\d+")))
  (is (= ["-" "1" " + " "2" " - " "3" "/" "4"]
         (split-keep-delim "-1 + 2 - 3/4" #"\s*[+\-*/]\s*")))
  (is (= ["a" "b" "12" "b" "a"]
         (split-keep-delim "ab12ba" #"[ab]"))))

and the implementation:

(defn split-keep-delim 
  "Splits str with re-delim. Returns list of parts, including delimiters. Lazy.

   > (split-keep-delim \"-1 + 2 - 3/4\" #\"\\s*[+\\-*/]\\s*\")
   [\"-\" \"1\" \" + \" \"2\" \" - \" \"3\" \"/\" \"4\"]
   > (split-keep-delim \"ab12ba\" #\"[ab]\")
   [\"a\" \"b\" \"12\" \"b\" \"a\"]"
  [str re-delim]
  (let [m (.matcher re-delim str)]
    ((fn step [last-end]
       (if (.find m)
         (let [start (.start m)
               end (.end m)
               delim (.group m)
               new-head (if (not= last-end start)
                          [(.substring str last-end start) delim]
                          [delim])]
           (concat new-head (lazy-seq (step end))))
         (if (not= last-end (.length str))
           [(.substring str last-end)]
           []))) 0)))

This version is lazy, though I did not notice any speedup as you can see from the timings below. Timings are rather good for what I needed:

> (let [s (apply str (take 100 (cycle "-1 + 2 - 3/4"))) pat #"\s*[+\-*/]\s*"] (time (dotimes [_ 1000] (take 1 (split-keep-delim s pat)))))
"Elapsed time: 26.013445 msecs"
nil
> (let [s (apply str (take 100 (cycle "-1 + 2 - 3/4"))) pat #"\s*[+\-*/]\s*"] (time (dotimes [_ 1000] (take 3 (split-keep-delim s pat)))))
"Elapsed time: 28.754948 msecs"
nil
> (let [s (apply str (take 100 (cycle "-1 + 2 - 3/4"))) pat #"\s*[+\-*/]\s*"] (time (dotimes [_ 1000] (take 300 (split-keep-delim s pat)))))
"Elapsed time: 28.388654 msecs"
nil
Be Sociable, Share!

Leave a Reply


9 × = eighty one

Hand-written markup language parser in ClojureScript

Broadly speaking, there are two ways to write a parser:

Obviously, using parser generators usually allows for a faster time-to-market. I wanted, however, to write a parser by hand for these reasons:

  • To see how hard it is in ClojureScript
  • No external dependencies
  • No need to have a separate compilation step
  • Informal, i.e. no grammars involved

Markup language

I’m going to develop a parser for a markup language with the following rules:

  • Header
= Header 1
== Header 2
=== Header 3
  • List
- Unordered 11
  # Ordered 21
  # Ordered 22
- Unordered 12
  • Table
|>header1|header2|
|cell11|cell12|
|cell21|cell22|
  • Code with language specification
!clojure
(def a 1)
!
  • Inline code
Some `inline code` here
  • Bold, italics
*bold*
_italics_
_italics and *bold italics* words_
  • Horizontal line
---
  • Manual and automatic links
[http://example.com/|my http site]

http://example.com/

[ftp://example.com/|my ftp site]
ftp://example.com/

[ssh://example.com/|ssh link]
ssh://example.com/

[mailto:me@example.com|my mail]
mailto:me@example.com
  • Image
@/favicon.png|My favorite icon

Parser

The goals are:

  • Make sure all cases are covered by tests
  • Make it work for usual cases
  • Error handling is not the focus
  • Make a parser that produces an AST
  • Have an AST->HTML output producer

I’ll do it in several steps:

  • Make the line-based parser
  • Enhance it to combine blocks
  • Make the HTML output functionality

I did some research on this and I think the most similar methodology is one described in this paper: A Nanopass Framework for Compiler Education.

In this post, I’ll focus on the first part – parsing all of the above functionality on a per-line basis.

Individual line tests and parsers

Here are line-restricted parsers for each of the blocks:

Header

  • Test:
(deftest parse-header-line-test
  (is (= {:type :header
          :level 1
          :child {:type :unprc
                  :text "header"}}
         (parse-header-line \= "= header")))
  (is (= {:type :header
          :level 3
          :child {:type :unprc
                  :text "header"}}
         (parse-header-line \= "=== header"))))
  • Implementation
(defn parse-header-line [first-imp-ch line]
  (let [level (count (take-while #(= \= %1) line))]
    {:type :header
     :level level
     :child {:type :unprc
             :text (triml (.substring line level))}}))

Horizontal line

  • Test
(deftest parse-hor-line-line-test
  (is (= {:type :hor-line}
         (parse-hor-line-line \- "---"))))
  • Implementation
(defn parse-hor-line-line [first-imp-ch line]
  {:type :hor-line})

List item

  • Test
(deftest parse-list-line-test
  (is (= {:type :list-item
          :sub-type :unord
          :indent 0
          :child {:type :unprc
                  :text "unord"}}
         (parse-list-line \- "- unord")))
  (is (= {:type :list-item
          :sub-type :unord
          :indent 1
          :child {:type :unprc
                  :text "unord"}}
         (parse-list-line \- "  - unord")))
  (is (= {:type :list-item
          :sub-type :ord
          :indent 0
          :child {:type :unprc
                  :text "ord"}}
         (parse-list-line \# "# ord")))
  (is (= {:type :list-item
          :sub-type :ord
          :indent 2
          :child {:type :unprc
                  :text "ord"}}
         (parse-list-line \# "    # ord"))))
  • Implementation
[(defn parse-list-line [first-imp-ch line]
  {:type :list-item
   :sub-type (case first-imp-ch
               \- :unord
               \# :ord)
   :indent (/ (.indexOf line (int first-imp-ch)) 2)
   :child {:type :unprc
           :text (triml (.substring (triml line) 1))}})

Table row

  • Test
(deftest parse-table-line-test
  (is (= {:type :table-row
          :sub-type :data
          :cols [{:type :unprc
                  :text "c1"}
                 {:type :unprc
                  :text "c2"}]}
       (parse-table-line \| "|c1|c2|")))
  (is (= {:type :table-row
          :sub-type :header
          :cols [{:type :unprc
                  :text "h1"}
                 {:type :unprc
                  :text "h2"}]}
       (parse-table-line \| "|>h1|h2|"))))
  • Implementation
[(defn parse-table-line [first-imp-ch line]
  {:type :table-row
   :sub-type (case (second (triml line))
               \> :header
               :data)
   :cols (map (fn 1
                {:type :unprc
                 :text text})
              (split (replace-first line #"^\|>?" "") #"\|"))})

Code rim

  • Test
(deftest parse-code-line-test
  (is (= {:type :code-rim
          :language ""}
         (parse-code-line \! "!")))
  (is (= {:type :code-rim
          :language "clojure"}
         (parse-code-line \! "! clojure"))))
  • Implementation
(defn parse-code-line [first-imp-ch line]
  {:type :code-rim
   :language (triml (.substring line 1))})

Image

  • Test
(deftest parse-image-line-test
  (is (let [line "@/favicon.png"]
        (= {:type :image
            :href "/favicon.png"
            :title "/favicon.png"}
           (parse-image-line (first line) line))))
  (is (let [line "@/favicon.png|My favorite png"]
        (= {:type :image
            :href "/favicon.png"
            :title "My favorite png"}
           (parse-image-line (first line) line)))))
  • Implementation
(defn parse-image-line [first-imp-ch line]
  (let [[href title] (split (.substring line 1) #"\|")
        good-title (if (blank? title) href title)]
    {:type :image
     :href href
     :title good-title}))

These are all quite readable.

Top level test

Here’s a top level test:

(deftest parse-test
  (let [lines ["= Header _italic *bold*_"
               "== Header 2"
               "---"
               "- Item 11"
               "  # Item `21`"
               "  # Item *22*"
               "- Item 12"
               "|>header1|header2|"
               "|c11|*c12*|"
               "|_c21_|*c _22_*|"
               "!clojure"
               "(def a 1)"
               "!"
               "http http://example.com/ _my website_"
               "mailto:me@example.com"
               "@/favicon.png|My favorite icon"]
        text (join "\n" lines)]
    (let [exp [{:type :header
                :level 1
                :child {:type :unprc
                        :text "Header _italic *bold*_"}}
               {:type :header
                :level 2
                :child {:type :unprc
                        :text "Header 2"}}
               {:type :hor-line}
               {:type :list-item
                :sub-type :unord
                :indent 0
                :child {:type :unprc
                        :text "Item 11"}}
               {:type :list-item
                :sub-type :ord
                :indent 1
                :child {:type :unprc
                        :text "Item `21`"}}
               {:type :list-item
                :sub-type :ord
                :indent 1
                :child {:type :unprc
                        :text "Item *22*"}}
               {:type :list-item
                :sub-type :unord
                :indent 0
                :child {:type :unprc
                        :text "Item 12"}}
               {:type :table-row
                :sub-type :header
                :cols [{:type :unprc
                        :text "header1"}
                       {:type :unprc
                        :text "header2"}]}
               {:type :table-row
                :sub-type :data
                :cols [{:type :unprc
                        :text "c11"}
                       {:type :unprc
                        :text "*c12*"}]}
               {:type :table-row
                :sub-type :data
                :cols [{:type :unprc
                        :text "_c21_"}
                       {:type :unprc
                        :text "*c _22_*"}]}
               {:type :code-rim
                :language "clojure"}
               {:type :unprc
                :text "(def a 1)"}
               {:type :code-rim
                :language ""}
               {:type :unprc
                :text "http http://example.com/ _my website_"}
               {:type :unprc
                :text "mailto:me@example.com"}
               {:type :image
                :href "/favicon.png"
                :title "My favorite icon"}]
          act (parse text)
          pairs (map vector exp act)]
      (doseq [[exp act] pairs]
        (is (= exp act))))))

It covers a lot of the above in one shot. You can see that so far it doesn’t work on some items, which are left as :unprc until the code for them is ready.

Top level parser

This is a top level parser:

(defn parse-line [line]
  (let [trimmed (triml line)
        first-imp-ch (first trimmed)]
    (case first-imp-ch
      \= (parse-header-line first-imp-ch line)
      (\- \#) (if (= "---" line)
                (parse-hor-line-line first-imp-ch line)
                (parse-list-line first-imp-ch line))
      \| (parse-table-line first-imp-ch line)
      \! (parse-code-line first-imp-ch line)
      \@ (parse-image-line first-imp-ch line)
      {:type :unprc
       :text line})))

(defn parse-lines [lines]
  (for [line lines]
    (parse-line line)))

(defn root-parser [lines]
  (parse-lines lines))

(defn parse [str]
  (root-parser (split str #"\n")))

Basically it will:

  • Split the given text into lines
  • Process each line one by one
  • The parse-line function is a router that routes to individual parsers for each of the above categories

Each line is substituted by a node describing it’s type:

  • Header
  • List item
  • Table row
  • Code rim (i.e. start / end of the code block)
  • Image
  • Unprocessed yet (:unprc symbol)

where each of these are parsed separately by the functions mentioned previously.

Conclusion and next steps

At the end, the code is a bit spread out (mostly tests), but I’m happy about the way it turned out.

With the above, the grand test passes fine, so all is good so far. Next time I’ll work on gluing these together, so e.g. a number of list items forms a list at the end and similar for other multi-line blocks. Also, I need to implement the things that are contained within line (such as bold and italic markup).

Be Sociable, Share!

Leave a Reply


7 × = forty nine

Maze generator in ClojureScript

Tags: clojurescript, maze generation

Here’s a simple maze generation program in ClojureScript. I’m developing this using a relatively standard stack for ClojureScript development:

Maze generator

The generator is basically a depth-first graph traversal, which generates a random spanning tree out of a maze that’s looked at as a graph. I’ll dissect the code in smaller chunks.

(ns com.icyrock.clojurescript.prb.maze-gen
  (:require-macros [dommy.macros :refer [sel1]])
  (:require 1]
            [dommy.core :as dommy]))

Standard ns declaration with a couple of requires.

(def board-dim {:h 24 :w 48})
(def doors (atom #{}))
(def visited-rooms (atom #{}))

Define:

  • The board dimensions (:h and :w stand for height and width, respectively)
  • Set of doors
  • Set of visited rooms
(defn is-door [pt1 pt2]
  (let [curr-doors @doors]
    (or (contains? curr-doors [pt1 pt2])
        (contains? curr-doors [pt2 pt1]))))

(defn set-door [pt1 pt2]
  (swap! doors conj [pt1 pt2]))

(defn visit-room [room]
  (swap! visited-rooms conj room))

A few helper functions:

  • The first determines whether there’s a door between two points / rooms in the maze
  • The second actually sets the doors there (i.e. connects the two rooms)
  • The third marks the room visited, so we don’t visit it twice when traversing
(defn pending-rooms [[row col]]
  (remove nil?
          (for [[rd cd] [[-1 0] [0 1] [1 0] [0 -1]]]
            (let [trow (+ row rd)
                  tcol (+ col cd)
                  room [trow tcol]]
              (when (and (<= 0 trow)
                         (<= 0 tcol)
                         (< trow (:h board-dim))
                         (< tcol (:w board-dim))
                         (not (@visited-rooms room)))
                room)))))

This one is used to determine which way we can go. Given any room (defined as [row col] point of a maze), it will try the four possible directions clockwise ([-1 0] [0 1] [1 0] [0 -1]) – i.e.:

  • Previous row (direction up)
  • Next column (direction right)
  • Next row (direction bottom)
  • Previous column (direction left)

For each of these rooms, it will make sure each of them is not:

  • Out of the bounds of the maze (the first four checks in the and call)
  • Already visited

It will collect all the rooms using for, then remove all that are nil (meaning they do not satisfy the above conditions).

(def maze-stack (atom []))

(defn push-maze-stack [pt]
  (swap! maze-stack conj pt))

(defn pop-maze-stack []
  (let [val @maze-stack]
    (reset! maze-stack (rest val))
    (first val)))

This piece defines:

  • The maze stack atom (i.e. the stack of rooms visited used for depth-first search)
  • Function to push a room to the stack
  • Function to pop the room off the stack
(defn start-building-maze [start-room]
  (reset! doors #{})
  (reset! maze-stack [start-room])
  (reset! visited-rooms #{start-room}))

This one resets all the state to start a new maze building cycle.

(defn maze-building-step []
  (loop []
    (when-let [room (first @maze-stack)]
      (let [pending (pending-rooms room)]
        (if (empty? pending)
          (do
            (pop-maze-stack)
            (recur))
          (let [shuffled (shuffle pending)
                next-room (first shuffled)]
            (push-maze-stack next-room)
            (visit-room next-room)
            (set-door room next-room)))))))

Guts of the maze building:

  • Get the top room from the stack, if there are any
  • Get the pending rooms (all that can be visited given the constraints, as provided above) from that room
  • If no pending rooms, discard this room and recur into the loop
  • Otherwise, shuffle the pending rooms (to randomize the maze), pick the next room, push it to the stack, mark it visited and make a door between the current room and that one
(def content (sel1 :#content))

(defn make-gui []
  (dommy/replace-contents! content [:pre {:id "maze"}]))

(def doors-to-char 
  (into cljs.core.PersistentHashMap/EMPTY
        {[false false false false]  "⬞"
         [true  false false false]  "╵"
         [false true  false false]  "╶"
         [true  true  false false]  "└"
         [false false true  false]  "╷"
         [true  false true  false]  "│"
         [false true  true  false]  "┌"
         [true  true  true  false]  "├"
         [false false false true ]  "╴"
         [true  false false true ]  "┘"
         [false true  false true ] "─"
         [true  true  false true ] "┴"
         [false false true  true ] "┐"
         [true  false true  true ] "┤"
         [false true  true  true ] "┬"
         [true  true  true  true ] "┼"}))

(defn board-str []
  (join "\n"
        (for [row (range (:h board-dim))]
          (join
           (for [col (range (:w board-dim))]
             (let [top-door (is-door [row col] [(dec row) col])
                   right-door (is-door [row col] [row (inc col)])
                   bottom-door (is-door [row col] [(inc row) col])
                   left-door (is-door [row col] [row (dec col)])]
               (doors-to-char [top-door right-door bottom-door left-door])))))))

(defn print-board []
  (dommy/replace! (sel1 :#maze) [:pre {:id "maze"} (board-str)]))

GUI-related code:

  • content is the parent div that will contain the maze
  • make-gui will create a pre HTML element with ID maze and put it inside the above content div

I’m actually using this pre element to output the Unicode string representation of the maze. Thus the doors-to-char map used for character lookup. It’s build like this to speed it up – see this StackOverflow question and David’s answer for explanation.

The lookup map is simple – the key is a vector of [top-door right-door bottom-door left-door], where each of the items are true / false monikers signifying whether the door is present or not, and the value is an Unicode character representing the correct room “image” given the possible exits out of that room.

  • board-str is actually making the string representation by traversing all rows, then all cells within each of the rows and getting the correct character to represent the door
  • print-board is just a helper function to output this to the pre element we created in make-gui
(defn run-maze-gen []
  (let [runs 50]
    (if (not-any? nil? (repeatedly runs maze-building-step))
      (do
        (print-board)
        (js/setTimeout run-maze-gen 50))
      (do (print-board)
          (dommy/append! content [:pre "Finished"])))))

(defn main []
  (make-gui)
  (start-building-maze [(quot (:h board-dim) 2)
                        (quot (:w board-dim) 2)])
  (run-maze-gen))

Top-level functions:

  • run-maze-gen runs the maze building step in batches of 50 (let [runs 50]), prints the board after each, then sets a timer to 50ms to run these again until the whole maze is generated
  • main makes the GUI, resets the maze and sets the starting room to the middle of the maze (parameters to start-building-maze), then calls run-maze-gen to actually start building the maze.

The result is something like this:

sample-maze

Note that maze corridors here are actually the lines, not the empty space between them, which I guess is more often how a maze is represented.

Be Sociable, Share!

Leave a Reply


seven − 4 =

ClojureScript development environment

Here’s what you need to set up a pretty nice ClojureScript development environment.

Editor

I’m using Emacs. I actually have it set up through Clojure before I started playing with ClojureScript. There’s a tutorial on the main site:

  • https://github.com/clojure/clojurescript/wiki/Emacs-%26-inferior-lisp-mode

where you can also find tutorials for other editors:

  • https://github.com/clojure/clojurescript/wiki#tools

Lein + lein-cljsbuild

Install Leiningen, make a new project:

$ lein new com.icyrock.clojurescript
Generating a project called com.icyrock.clojurescript based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.

and add lein-cljsbuild plugin to your project.clj:

(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.4.0"]]
  :plugins [[lein-cljsbuild "0.3.0"]]
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

The above has some default configuration for the plugin:

  • The source folder for ClojureScript files is src-cljs
  • Turn some basic optimization and pretty-print the output
  • The output goes to resources/public/js/cljsbuild-main.js

Usually, you’d run cljsbuild in the background with:

$ lein cljsbuild auto

which will watch for the files in the source path for changes and then perform the necessary steps (compile, optimize, bundle) to get you the target .js file.

Ring

Ring is a Web server. It’s very nice for development, as it compiles the Clojure files on the fly. There’s also lein-ring which is a helper plugin to simplify usage when used with lein. Here’s the expanded project.clj:


(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "icyrock.com ClojureScript"
  :url "http://icyrock.com/clojure-script"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [ring/ring-core "1.1.8"]]
  :plugins [[lein-cljsbuild "0.3.0"]
            [lein-ring "0.8.3"]]
  :ring {:handler com.icyrock.clojure.ring-main/handler
         :port 3000}
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

As per Ring’s Getting Started, create a sample handler in src/com/icyrock/clojure/ring_main.clj:

(ns com.icyrock.clojure.ring-main)

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Welcome to com.icyrock.clojure.ring-main" })

You can run the server now with:

$ lein ring server
2013-04-15 23:16:21.815:INFO:oejs.Server:jetty-7.6.1.v20120215
2013-04-15 23:16:21.966:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000
Started server on port 3000

which will open your browser. You can use lein ring server-headless if you don’t want it to open your browser.

Compojure

The next step is to add some file serving to our Web server. Compojure allows for that. It’s a Clojure routing DSL that sits on top of Ring. Add this as a dependency and change the ring handler:

(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "icyrock.com ClojureScript"
  :url "http://icyrock.com/clojure-script"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [ring/ring-core "1.1.8"]
                 [compojure "1.1.5"]]
  :plugins [[lein-cljsbuild "0.3.0"]
            [lein-ring "0.8.3"]]
  :ring {:handler com.icyrock.clojure.compojure-main/app
         :port 3000}
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

then make a site router (see the routes.clj from the example project):

(ns com.icyrock.clojure.compojure-main
  (:require [compojure.core :refer :all]
            [hiccup.middleware :refer (wrap-base-url)]
            [compojure.route :as route]
            [compojure.handler :as handler]
            [compojure.response :as response]))

(defroutes main-routes
  (GET "/" [] "Welcome to com.icyrock.clojure.compojure-main")
  (route/resources "/")
  (route/not-found "Page not found"))

(def app
  (-> (handler/site main-routes)
      (wrap-base-url)))

The important part above is the (route/resources "/"). This allows for static resources to be served by ring. The default is to serve from the (first) public on the classpath. Lein accepts the parameter called :resources-path, which (as per this) defaults to resources. Thus, if we create a folder resources/public, the above would make ring serve all files from that folder under the root (i.e. /) of the site.

As an example:

$ mkdir -p resources/public
$ echo "I'm a resource" > resources/public/a-res.txt

Now do lein ring server and browse to http://localhost:3000/a-res.txt – you’ll get I'm a resource text, as expected.

Auto-recompiling ClojureScript

Let’s add:

  • jayq, which is a jQuery wrapper for ClojureScript
  • jQuery externs that allow Google Closure to recognize and not obfuscate jQuery calls (see this)
  • dommy, a small and fast ClojureScript templating engine

to our project.clj:

(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "icyrock.com ClojureScript"
  :url "http://icyrock.com/clojure-script"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [ring/ring-core "1.1.8"]
                 [compojure "1.1.5"]
                 [hiccup "1.0.3"]
                 [jayq "2.3.0"]
                 [prismatic/dommy "0.1.0"]]
  :plugins [[lein-cljsbuild "0.3.0"]
            [lein-ring "0.8.3"]]
  :ring {:handler com.icyrock.clojure.compojure-main/app
         :port 3000}
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :externs ["externs/jquery-1.9.1.js"]
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

Now we can make a sample ClojureScript file. All these go to src-cljs. This file is within that folder under com/icyrock/clojurescript/prb/hello_world.cljs and the contents are simple:

(ns com.icyrock.clojurescript.prb.hello-world
    (:require [jayq.core :as $]
              [dommy.core :as dommy]
              [dommy.template :as tpl]))

(defn hello-world []
  (dommy/append! js/document.body [:h1 "Hello, world!"])
  (let [body ($/$ js/document.body)
        para (-> ($/$ "<p>")
                 ($/text "This is a sample paragraph"))
        link (tpl/node [:a {:href "http://icyrock.com/"} "icyrock.com"])]
    ($/append body para)
    ($/append body link)))

This is the main file that will be used to call child functions, just for separation:

(ns com.icyrock.clojurescript.main
  (:require [jayq.core :as $]
            [com.icyrock.clojurescript.prb.hello-world :refer [hello-world]]))

($/$ hello-world)

Let’s run lein-cljsbuild in another terminal in auto-mode:

$ lein cljsbuild auto
Compiling "resources/public/js/cljsbuild-main.js" from ["src-cljs"]...
Successfully compiled "resources/public/js/cljsbuild-main.js" in 12.54909577 seconds.

Add a simple HTML to resources/public/main.htm:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>com.icyrock.clojurescript.main</title>
    <script src="lib/jquery-1.9.1.min.js"></script>
    <script src="js/cljsbuild-main.js"></script>
  </head>
  <body>
  </body>
</html>

Download the required files as needed:

Browse to http://localhost:3000/main.htm and you’ll see something like this:

hello_world

Note that further updates to the .cljs files are automatically recompiled by Google Closure compiler, that is used by ClojureScript through lein-cljsbuild

Be Sociable, Share!

2 Responses to “ClojureScript development environment”

JimBeam on June 18th, 2013 02:06:

Great tutorial! Thx a lot

[WORDPRESS HASHCASH] The poster sent us ’0 which is not a hashcash value.


icyrock.com on June 22nd, 2013 09:24:

Thanks JimBeam, glad you liked it!


Leave a Reply


+ seven = 10

Maze display app for Always Turn Left

Last time, I presented a solution for Always Turn Left, a Google Code Jam problem. Given that their large dataset was quite big (up to 10k moves), I thought: “It would be interesting to see what mazes those moves produce”. So I set to write (in Clojure, of course) a maze-display app (using Seesaw, of course). Here’s what came out of that.

(ns com.icyrock.clojure.codejam.maze-display
  (:use clojure.java.io
        flatland.ordered.map
        seesaw.border
        seesaw.chooser
        seesaw.color
        seesaw.core
        seesaw.graphics
        seesaw.mig)
  (:require [seesaw.bind :as ssb]))

First, declare a lot of things I’m to use later. Most Seesaw and one thing from here, which is a Clojure implementation of ordered sets / maps which I wanted to try out.

(def state
  {:frame (atom nil)
   :file (atom nil)
   :cases (atom nil)
   :curr-case (atom nil)
   :maze (atom nil)})

Main state – contains:

  • Main frame
  • Currently selected case-file
  • Loaded cases themselves
  • Currently selected case
  • Maze bound to the currently selected case
(def room-width 16)
(def room-height 16)

Default room size when drawn, in pixels.

(def default-style
  (style
   :foreground "#000000"
   :stroke (stroke :width 3 :cap :round)))

Default style to use when drawing walls. It’s a black, 3-pixel wide line, with rounded edges.

(defn draw-wall [g w h wall]
  (case wall
    :n (draw g (line 0 0 w 0) default-style)
    :s (draw g (line 0 h w h) default-style)
    :w (draw g (line 0 0 0 h) default-style)
    :e (draw g (line w 0 w h) default-style)))

This draws a wall. Given that translation is used below, the north-west corner of the room is always at (0, 0), so the above is easy to understand given the case keys (:n for north, :s for south, :w for west and :e for east).

(defn draw-room [g w h walls-desc]
  (let [walls (case walls-desc
                \1 #{   :s :w :e}
                \2 #{:n    :w :e}
                \3 #{      :w :e}
                \4 #{:n :s    :e}
                \5 #{   :s    :e}
                \6 #{:n       :e}
                \7 #{         :e}
                \8 #{:n :s :w   }
                \9 #{   :s :w   }
                \a #{:n    :w   }
                \b #{      :w   }
                \c #{:n :s      }
                \d #{   :s      }
                \e #{:n         }
                \f #{           }
                )]
    (doseq [wall walls]
      (push g
            (draw-wall g w h wall)))))

The room is a set of cases to decipher the letter as set of walls for that room, as given in the problem description and then draw each of these walls.

(defn paint-maze 1
  (try
    (let [w room-width
          h room-height
          maze @(state :maze)]
      (when maze
        (anti-alias g)
        (translate g w h)
        (doseq [row maze]
          (push g
                (doseq [room row]
                  (draw-room g w h room)
                  (translate g w 0)))
          (translate g 0 h))))
    (catch Exception e
      (invoke-later (alert e))
      (println e))))

Main paint function:

  • Check if maze is valid (i.e. user has selected a case)
  • Turn on anit-aliasing
  • Go through the rows of the maze
  • Translate to the position of the current room
  • Draw it
(defn content-panel []
  (mig-panel
   :constraints ["fill" "[|grow]"]
   :items [[(button :id :load
                    :text "Load file...") ""]
           [(text :id :file-name) "growx, wrap"]
           [(scrollable (listbox :id :cases)
                        :border (line-border)) "grow"]
           [(let [s (scrollable (canvas :id :maze-pict
                                        :background "#ffffff"
                                        :paint paint-maze)
                                :border (line-border))]
              (-> s (.getHorizontalScrollBar) (.setUnitIncrement (* 3 room-width)))
              (-> s (.getVerticalScrollBar) (.setUnitIncrement (* 3 room-height)))
              s) "grow, push"]]))

Main window contents:

  • “Load” button
  • Current file name
  • List box for cases
  • Canvas for the maze

Uses MigLayout, of course.

(defn split-cases [acc line]
  (let [case (re-find #"^Case #\d+:$" line)]
    (if case
      ;; Found case start line
      (assoc acc
        :curr-case case
        :cases (assoc (acc :cases) case []))
      ;; Continuation of the current case (maze definition)
      (let [cases (acc :cases)
            curr-case (acc :curr-case)
            curr-maze (cases curr-case)
            new-maze (conj curr-maze line)
            new-cases (assoc cases curr-case new-maze)]
        (assoc acc
          :cases new-cases)))))

When loading, split the cases one by one, taking into account maze description has two kinds of lines:

  • Case start
  • Maze lines for the current case
(defn load-cases [file]
  (with-open [r (reader file)]
    (let [lines (reduce conj [] (line-seq r))
          {:keys [cases]} (reduce split-cases {:cases (ordered-map)} lines)]
      (reset! (state :cases) cases))))

Case loader function:

  • Use reader to read from the file
  • Get the lines
  • Reduce using previous split-cases function
(defn load [e]
  (let [frame (to-frame e)]
    (choose-file frame
                 :type :open
                 :success-fn (fn [fc file] (reset! (state :file) file)))))

Just shows the standard Java file chooser to pick the file.

(defn set-listeners [frame]
  (listen (select frame [:#load])
            :action load))

(defn set-bindings [frame]
  ;; File binding
  (ssb/bind
   (state :file)
   (ssb/tee
    (ssb/b-do* load-cases)
    (ssb/bind
     (ssb/transform #(.getPath %))
     (select frame [:#file-name]))))
  ;; Cases binding
  (ssb/bind
   (state :cases)
   (ssb/transform #(keys %))
   (ssb/tee
    (ssb/property (select frame [:#cases]) :model)
    (ssb/b-do* (fn [v] (selection! (select frame [:#cases]) (first v))))))
  ;; Case selection binding
  (ssb/bind
   (ssb/selection (select frame [:#cases]))
   (ssb/b-do* #(reset! (state :curr-case) %)))
  ;; Selected case binding
  (ssb/bind
   (state :curr-case)
   (ssb/transform #(@(state :cases) %))
   (ssb/b-do* #(reset! (state :maze) %)))
  ;; Maze binding
  (ssb/bind
   (state :maze)
   (ssb/b-do* (fn [maze] (let [canvas (select frame [:#maze-pict])
                               cw (* room-width (+ 2 (count (first maze))))
                               ch (* room-height (+ 2 (count maze)))]
                           (config! canvas :preferred-size [cw :by ch])
                           (.revalidate canvas)
                           (repaint! canvas))))))

These two set up the listeners (only one in this case – button click) and bindings which nicely describe the state machine for this simple app:

  • When file is selected, load the cases and display the file name
  • When cases were loaded, populate the list box with the case map description
  • When a case is selected, update the current case
  • When the current case changes, update the maze
  • When the maze is updated, draw it
(defn maze-display []
  (native!)
  (let [f (frame :title *ns*
                 :width 1200 :height 700
                 :on-close :dispose
                 :visible? true
                 :content (content-panel))]
    (.setLocation f (java.awt.Point. 100 100))
    (reset! (state :frame) f)
    (set-listeners f)
    (set-bindings f)))

Main function:

  • Make the frame
  • Set its location
  • Set the listeners and bindings

The final result looks like this:

maze-display-app

Be Sociable, Share!

Leave a Reply


× 2 = eighteen

Google Code Jam – Always Turn Left in Clojure

Last time I did the first practice problem (Alien Numbers). Now it’s time to do the second one – Always Turn Left.

Utilities

Utilities pretty much stayed the same – the only change is to the write-output function that now has three parameters – the last was added to allow for multi-line case output. This one is needed for this problem, while it was a series of one-liners in Alien Numbers. Read about these more in the Alien Numbers post, but here they are again for completeness:

(ns com.icyrock.clojure.codejam.utils
  (:use clojure.java.io))

(def base-res "com/icyrock/clojure/codejam/")

(defn read-case-lines [case-name]
  (let [res (resource (str base-res case-name))]
    (with-open [rdr (reader res)]
      (doall (line-seq rdr)))))
  
(defn read-cases [case-name]
  (let [lines (read-case-lines (str case-name ".in"))
        case-count (Integer/parseInt (first lines))
        cases (doall (rest lines))]
    {:case-count case-count
     :cases cases}))
  
(defn write-output [output case-name multi-line]
  (let [in-res (resource (str base-res case-name ".in"))
        in-file-name (.getFile in-res)
        out-file-name (clojure.string/replace in-file-name #".in$" ".out")]
    (with-open [wr (writer out-file-name)] 
      (doseq [[case-no case-out] (map vector (range 1 (inc (count output))) output)]
        (.write wr (str "Case #" case-no ":"
                        (if multi-line "\n" " ")
                        case-out "\n"))))))

Tests

Some tests to start with:

(ns com.icyrock.clojure.codejam.cj2008pp_test
  (:use 1)
  (:use [com.icyrock.clojure.codejam.cj2008pp]))

;; Always turn left
(deftest make-maze-test
  (is (= (make-maze
          {[0 0] #{   :s    :e}
           [0 1] #{      :w :e}
           [0 2] #{:n    :w   }
           [1 0] #{:n :s      }
           [1 1] #{         :e}
           [1 2] #{   :s :w   }
           [2 0] #{:n       :e}
           [2 1] #{      :w :e}
           [2 2] #{:n :s :w   }
           [3 0] #{   :s :w :e}
           [3 1] #{      :w   }
           [3 2] #{:n :s      }
           [4 0] #{:n       :e}
           [4 1] #{      :w :e}
           [4 2] #{:n    :w   }}
          0 0 4 2)
         '("ac5" "386" "9c7" "e43" "9c5"))))

(deftest always-turn-left-case-test
  (is (= (always-turn-left-case "WRWWLWWLWWLWLWRRWRWWWRWWRWLW" "WWRRWLWLWWLWWLWWRWWRWWLW")
         '("ac5" "386" "9c7" "e43" "9c5")))
  (is (= (always-turn-left-case "WW" "WW")
         '("3")))
  (is (= (always-turn-left-case "WWLW" "WLWRRWWW")
         '("3" "b" "1")))
  (is (= (always-turn-left-case "WWWRRWLW" "WLWW")
         '("3" "7" "1")))
  (is (= (always-turn-left-case "WWRWWLW" "WWRWWLW")
         '("ac7" "bc5")))
  (is (= (always-turn-left-case "WWRWRWW" "WWLWLWW")
         '("33" "95"))))

The first test is for the make-maze function. This one is used to generate the maze given the maze map, see below. Keys of the map are [row column] points and values are sets of directions (:n, :s, :w, :e, representing north, south, west and east, respectively).

As for the actual problem tests (always-turn-left-case-test) – the first two tests are from the problem page, the second of which covers an exit on the south. The next four of these cover east, west, south and north exits, in that order. I repeated the south exit as the one given on the problem page was rather trivial. Also, this example is also a “palindrome” maze – i.e. you walk in the same way (given the entrance-to-exit and exit-to-entrance descriptions) from entrance to exit and on the way back.

Solution overview

From my perspective, this problem yields itself quite nicely to the reduce function. That is – we start from the empty maze, we walk as given by the entrance-to-exit path description and improve our knowledge about the maze on the way. I chose to record the following information:

  • Current row index
  • Current column index
  • Minimal row index
  • Minimal column index
  • Maximal row index
  • Maximal column index
  • Current direction
  • The map of the maze, using points ([row column] vectors) as keys

After that, do the same in the opposite direction, updating the same set of information as above.

The first part above gives the maze map and the bounding box of the maze (min / max row / column index). The last thing left is to reconstruct the maze, which is rather trivial given that information.

Clojure solution

Here’s the actual code, explained step by step:

;; Always turn left
(defn opposite-dir [dir]
  ({:n :s, :e :w, :s :n, :w :e} dir))

This is a helper function to get the opposite direction of the one we are currently pointing toward.

(defn maze-move [[cr cc minr minc maxr maxc dir maze-map] cmd]
  (case cmd
    \W (let [nr (+ cr ({:n -1, :e 0, :s 1, :w 0} dir))
             nc (+ cc ({:n 0, :e 1, :s 0, :w -1} dir))
             croom (or (maze-map [cr cc]) #{})
             nroom (or (maze-map [nr nc]) #{})
             new-croom (conj croom dir)
             new-nroom (conj nroom (opposite-dir dir))
             new-maze-map (assoc maze-map [cr cc] new-croom [nr nc] new-nroom)]
         [nr nc
          (min minr nr) (min minc nc)
          (max maxr nr) (max maxc nc)
          dir new-maze-map])
    \L [cr cc minr minc maxr maxc
        ({:n :w, :e :n, :s :e, :w :s} dir)
        maze-map]
    \R [cr cc minr minc maxr maxc
        ({:n :e, :e :s, :s :w, :w :n} dir)
        maze-map]))

This one actually makes the move given the current parameters and yields the next state. This is used below to reduce over the set of moves. There are three possible moves:

  • L / R – these just change the direction, without changing the position or anything else. This is done by doing the map-lookup in both cases
  • W – perform the walk. Walking has three consequences:
    • Position is changed (so nr / nc = new row and column indices are updated, also using map-lookup method)
    • Current and new rooms’ (new-croom / new-nroom) walls are updated (i.e. removed on the appropriate sides, given walking is possible) and
    • The map is updated with the new rooms
(defn make-maze [maze-map minr minc maxr maxc]
  (for [r (range minr (inc maxr))] 
    (apply str
           (for 1
             (case (maze-map [r c])
               #{:n         } "1"
               #{   :s      } "2"
               #{:n :s      } "3"
               #{      :w   } "4"
               #{:n    :w   } "5"
               #{   :s :w   } "6"
               #{:n :s :w   } "7"
               #{         :e} "8"
               #{:n       :e} "9"
               #{   :s    :e} "a"
               #{:n :s    :e} "b"
               #{      :w :e} "c"
               #{:n    :w :e} "d"
               #{   :s :w :e} "e"
               #{:n :s :w :e} "f"
               "#")))))

This is the function that generates the maze layout in the format required by the problem given the parameters output by the reduce over the given moves.

(defn always-turn-left-case [ent-to-exit exit-to-ent]
  (let [[exr exc minr minc maxr maxc dir maze-map]
        (reduce maze-move [-1 0 0 0 0 0 :s {}] ent-to-exit)
        [_ _ _ minc maxr maxc _ maze-map]
        (reduce maze-move [exr exc minr minc maxr maxc (opposite-dir dir) maze-map] exit-to-ent)
        act-minc (if (= dir :w) (inc minc) minc)
        act-maxr (if (= dir :s) (dec maxr) maxr)
        act-maxc (if (= dir :e) (dec maxc) maxc)]
    (make-maze maze-map 0 act-minc act-maxr act-maxc)))

The core of the solver:

  • Reduce from entrance to exit
  • Reduce from exit to entrance
  • Calculate the bounding box (discarding the last step, as entrance and exit are always outside of the maze)
  • Make the maze map
(defn always-turn-left []
  (let [case-names ["sample" 
                    "B-small-practice" 
                    "B-large-practice"]
        case-names-full (for [case-name case-names] 
                          (str "08pp/always-turn-left/" case-name))] 
    (time
     (doseq [case-name case-names-full] 
       (let [{:keys [case-count cases]} (read-cases case-name)
             output (for [case cases]
                      (let [[ent-to-exit exit-to-ent] (split case #" ")]
                        (join "\n" (always-turn-left-case ent-to-exit exit-to-ent))))]
         (write-output output case-name true))))))

The wrapper around the solver to run the given problems – sample is the sample given in the description and B-small-practice and B-large-practice are the files that you can download when submitting the solution. Cases are read using the utility functions and written in the expected format.

Be Sociable, Share!

One Response to “Google Code Jam – Always Turn Left in Clojure”

Maze display app for Always Turn Left « Blog Archive « icyrock.com on April 3rd, 2013 21:50:

[...] Last time, I presented a solution for Always Turn Left, a Google Code Jam problem. Given that their large dataset was quite big (up to 10k moves), I thought: “It would be interesting to see what mazes those moves produce”. So I set to write (in Clojure, of course) a maze-display app (using Seesaw, of course). Here’s what came out of that. [...]


Leave a Reply


four − = 0

Google Code Jam – Alien Numbers in Clojure

Google Code Jam is a competition where participants compete in solving algorithmic puzzle. It’s been done usually a couple of times a year for different regions. The one for year 2013. starts in less than a couple of weeks from now!

I wanted to exercise my Clojure newbie skills to try to solve one of the practice problems from the first practice session available – Alien Numbers. Probably good to go read the problem statement to understand what I’m going to do below.

Here’s how I went about it…

Utilities

First, some generic utilities. Not sure if this applies to all, but looks like Code Jam problems are made so you read the .in file and write the .out file. The samples are given, for example for Alien Numbers the sample is:

  • Input
4
9 0123456789 oF8
Foo oF8 0123456789
13 0123456789abcdef 01
CODE O!CDE? A?JM!.

where the first line gives the number of cases (4 in this case) and then that many lines follow with the actual cases.

  • Output
Case #1: Foo
Case #2: 9
Case #3: 10011
Case #4: JAM!

where you have 4 cases with prefix “Case #N: ” and the corresponding output from the solver program.

So, my first thought is – I need a couple of functions to read and write these. Here’s how I did these:

(ns com.icyrock.clojure.codejam.utils
  (:use clojure.java.io))

(def base-res "com/icyrock/clojure/codejam/")

(defn read-case-lines [case-name]
  (let [res (resource (str base-res case-name))]
    (with-open [rdr (reader res)]
      (doall (line-seq rdr)))))
  
(defn read-cases [case-name]
  (let [lines (read-case-lines (str case-name ".in"))
        case-count (Integer/parseInt (first lines))
        cases (doall (rest lines))]
    {:case-count case-count
     :cases cases}))
  
(defn write-output [output case-name]
  (let [in-res (resource (str base-res case-name ".in"))
        in-file-name (.getFile in-res)
        out-file-name (clojure.string/replace in-file-name #".in$" ".out")]
    (with-open [wr (writer out-file-name)] 
      (doseq [[case-no case-out] (map vector (range 1 (inc (count output))) output)]
        (.write wr (str "Case #" case-no ": " case-out "\n"))))))

There are four things above:

  • base-res is the resource folder where I read the .in files from and then construct the name to write the .out file to
  • read-case-lines read the lines of the .in file
  • read-cases uses the above to read the number of cases and the cases themselves
  • write-output will get the program output and write the output in the required format (i.e. prefixing with “Case #N: “)

Actual solution methods

Solution is easy given two methods:

  • First part – alien to “computer” numbers converter
  • Second part – “computer” to alien numbers converter

I’ll make two methods here:

  • from-num-repr that does alien-to-computer conversion
  • to-num-repr that does computer-to-alien conversion

Tests

Let’s write some tests to define how these are to work:

(ns com.icyrock.clojure.codejam.cj2008pp_test
  (:use [com.icyrock.clojure.codejam.cj2008pp])
  (:use 1))

(deftest from-num-repr-test
  ;; bin / dec / hex 
  (is (= 9 (from-num-repr "1001" "01")))
  (is (= 72 (from-num-repr "72" "0123456789")))
  (is (= 0x53 (from-num-repr "53" "0123456789abcdef")))

  ;; same as above, just different symbols
  (is (= 9 (from-num-repr "tfft" "ft")))
  (is (= 72 (from-num-repr "hc" "abcdefghij")))
  (is (= 0x53 (from-num-repr "wz" "1qaz2wsx3edc4rfv"))))

(deftest to-num-repr-test
  ;; bin / dec / hex 
  (is (= "1001" (to-num-repr 9 "01")))
  (is (= "72" (to-num-repr 72 "0123456789")))
  (is (= "53" (to-num-repr 0x53 "0123456789abcdef")))

  ;; same as above, just different symbols
  (is (= "tfft" (to-num-repr 9 "ft")))
  (is (= "hc" (to-num-repr 72 "abcdefghij")))
  (is (= "wz" (to-num-repr 0x53 "1qaz2wsx3edc4rfv"))))

Here I used three simple numbers in three common bases – binary, decimal and hexadecimal. Also did two cases – with “normal” symbols and with “garbage” symbols.

First part – alien to “computer” numbers

(defn from-num-repr [num lan]
  (let [lan-dig (apply hash-map (interleave lan (range)))
        rnum (reverse num)
        val-rnum (map lan-dig rnum)
        dig-cnt (count num)
        lan-cnt (count lan) 
        exps (iterate #(* lan-cnt %) 1)
        vals (map * val-rnum exps)]
    (apply + vals)))

Here:

  • lan-dig is a map of alien language symbol to digit. E.g. if language symbols are “abcd”, then lan-dig would be: {\a: 0, \b: 1, \c: 2, \d: 3}
  • rnum is reverse number. We need it reverse as we apply exponents based on the positional value of the digit
  • val-rnum is the digit value for each of the symbols in rnum
  • exps is the exponents of the digit position in the target alien language. E.g. if alien language has 2 symbols (i.e. binary system), exps would be [1, 2, 4, 8, 16, ...]
  • vals is the positional value of the rnum
  • Finally, we just sum the values in vals to get the number value

Second part – “computer” to alien numbers

(defn to-num-repr [num lan]
  (let [dig-lan (apply hash-map (interleave (range) lan))
        lan-cnt (count lan)]
    (loop [left num tl-num '()]
      (if (= left 0)
        (apply str tl-num)
        (let [num-mod (mod left lan-cnt)
              num-left (quot left lan-cnt)] 
          (recur num-left (cons (dig-lan num-mod) tl-num)))))))

This is reverse from the previous:

  • dig-lan is reverse of lan-dig, i.e. keys and values reversed. E.g. if language symbols are “abcd”, then lan-dig would be: {0: \a, 1: \b, 2: \c, 3: \d}
  • We then loop until we reach zero, i.e. nothing more to convert
  • In each iteration, we get the modulus and reminder of the number and add the appropriate digit by mapping via dig-lan to the accumulated result (tl-num)
  • Finally, we just apply str function to tl-num list to get the actual string representation using the target alien language symbols

Third part – glue the above to get alien to another alien numbers converter

This is just a simple glue:

  • Get the number and the language symbols in the source alien language
  • Convert to “computer” representation
  • Convert that “computer” representation to the number in the target alien language, given the target language symbols
(defn alien-numbers-case [case]
  (let [[sl-num src-lan tgt-lan] (split case #" ")
        num (from-num-repr sl-num src-lan)
        tl-num (to-num-repr num tgt-lan)]
    tl-num))

Fourth part – do this for all available cases

There are usually three cases given for each Code Jam:

  • Sample – this is usually short and contains of a few input cases and their corresponding outputs
  • Small – a bigger set than Sample set. Looks like in real competition you get a different file each time you ask for the input (so outputs need to be re-created each time a file is downloaded)
  • Large set – larger set than Small set and looks like you get only this file always

I downloaded these in the resource folder of the app and ran for them all:

(defn alien-numbers []
  (let [case-names ["sample" 
                   "A-small-practice" 
                   "A-large-practice"]
        case-names-full (for [case-name case-names] 
                          (str "08pp/alien-numbers/" case-name))] 
    (time
      (doseq [case-name case-names-full] 
        (let [{:keys [case-count cases]} (read-cases case-name)
              output (for [case cases] (alien-numbers-case case))]
          (write-output output case-name))))))

End

The best thing – the output actually passes when submitted… Nice!

Be Sociable, Share!

2 Responses to “Google Code Jam – Alien Numbers in Clojure”

Google Code Jam – Always Turn Left in Clojure « Blog Archive « icyrock.com on March 21st, 2013 19:29:

[...] Last time I did the first practice problem (Alien Numbers). Now it’s time to do the second one – Always Turn Left. [...]


rebcabin on March 23rd, 2013 22:48:

There seems to be an unbound symbol, “resource”, in the definition of “read-case-lines.” I can’t get utils to run without it.


Leave a Reply


three × 2 =

Python – mix lists, preserving order

Say you have the following lists:

  • lower_case – A list of all lower-case letters
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
  • upper_case – A list of all upper-case letters
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
  • digits – A list of digits
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

and a list of the above, i.e.:

list = [lower_case, upper_case, digits]
 

Now, you want to make a single list with the following properties:

  • All elements of the given lists need to be present
  • The order of elements of each of the individual lists needs to be preserved
  • The final list needs to be randomized

In our case, the final list would be a list of all lower-case letters, upper-case letters and digits, where all individual categories are still ordered (e.g. ‘a’ needs to come before ‘b’ in the final list), but randomized. Here’s one way to do it:

  • Start from the starting list
[['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
  • Zip the lists to get the ids (which are conveniently list indices in the starting list) [
(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'], 0),
(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], 1),
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 2)]
  • For each list, create the list which has the same length, but whose every element is the index of that list. Make a master index list that contains these
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]]
  • Flatten the master index list
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
  • Shuffle the master index list
[2, 2, 1, 0, 1, 1, 1, 0, 2, 0, 1, 1, 1,
2, 1, 0, 0, 0, 1, 0, 1, 2, 1, 0, 1, 0,
0, 1, 1, 1, 2, 0, 0, 0, 1, 0, 2, 1, 1,
1, 2, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0,
0, 0, 2, 0, 1, 0, 0, 0, 2, 0]
  • Create iterators for each of the starting lists. Put them in the iterators list
  • Go through the master index list and pick the iterator from the iterators list based on the index which is given by the current element
[0, 1, 'A', 'a', 'B', 'C', 'D', 'b', 2, 'c',
'E', 'F', 'G', 3, 'H', 'd', 'e', 'f', 'I',
'g', 'J', 4, 'K', 'h', 'L', 'i', 'j', 'M',
'N', 'O', 5, 'k', 'l', 'm', 'P', 'n', 6,
'Q', 'R', 'S', 7, 'T', 'o', 'p', 'U', 'q',
'r', 'V', 'W', 'X', 'Y', 's', 't', 'u', 8,
'v', 'Z', 'w', 'x', 'y', 9, 'z']

In something readable :), i.e. Python:

#!/usr/bin/python
from __future__ import print_function
import random
import itertools
import string

def mix_lists(lists):
  id_pairs = zip(lists, range(len(lists))) 
  print(id_pairs)
  id_lists = [[id] * len(list) for (list, id) in id_pairs]
  print(id_lists)
  id_list = [id for sub_list in id_lists for id in sub_list]
  print(id_list)
  random.shuffle(id_list)
  print(id_list)
  iterators = [iter(list) for list in lists]
  mixed_lists = [iterators[id].next() for id in id_list]
  return mixed_lists

lower_case = 1
upper_case = 1
digits = range(10)
lists = [lower_case, upper_case, digits]

print(lists)

for i in range(1):
  print(mix_lists(lists))
Be Sociable, Share!

Leave a Reply


8 + six =

Luaj lcdui

Luaj is a library bringing Lua to Java 2 ME platform. It does not have the bindings to javax.microedition.lcdui package. I wanted to see how easy it is to add a few classes from this package in order for them to be available to Lua programs. You can grab the source from here.

Main MIDlet

Here’s the MIDlet code:

package com.icyrock.j2me.luaj.example;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jme.JmePlatform;

import com.icyrock.j2me.luaj.LcduiLib;
import com.icyrock.j2me.luaj.Midlet;

public class LuajMidlet extends MIDlet {
  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };
    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  private String readFile(String fileName) {
    String ret = "";

    FileConnection fc = null;
    try {
      fc = (FileConnection) Connector.open(fileName);
      InputStream is = fc.openInputStream();

      StringBuffer sb = new StringBuffer();

      int cap = 2000;
      byte[] bytes = new byte[cap];

      while (true) {
        int available = is.available();
        if (available == 0) {
          break;
        }

        int len = available > cap ? cap : available;
        is.read(bytes, 0, len);

        sb.append(new String(bytes, 0, len, "UTF-8"));
      }

      ret = sb.toString();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fc != null) {
        try {
          fc.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    return ret;
  }
}

It has a readFile method, which is nothing else but a method to read files. Java 2 ME is pretty limited, so there are no e.g. BufferedReader or classes such as that to help with this task, so I added this quick method to read the file in whole.

The main code is in startApp method, the breakdown of which follows:

  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

When used in an emulator, such as MicroEmulator, just print the string to know this is starting to be loaded. Also, define the Lua script to be run.

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

Define the _G, which is the global Luaj context. Add our LcduiLib, so it’s accessible to Lua scripts we write. Initialize a variable lcdui.midlet.current that will, when accessed from within Lua script, point to the current MIDlet instance.

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };

Define the loader function. This function will be called to load all parts of Lua script, which will then be concatenated and run. The script is loaded from:

"file:///SDCard/icyrock/lua/" + script + ".lua"

so in this case that would be:

file:///SDCard/icyrock/lua/phi.lua

containing a sample Lua script we’ll see later. If you use MicroEmulator under Linux, you should put this file in:

~/.microemulator/filesystem/SDCard/icyrock/lua/phi.lua 

The last step:

    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

is to load the Lua script and run it.

Sample Lua script

You can find this one in com/icyrock/j2me/luaj/example/lua/phi.lua:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

The breakdown is:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

Get the display. This is the standard J2ME lcdui stuff.

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

Create a from and put a command listener. The listener is just going to destroy the form when a command with label "Exit" is chosen.

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

Create two commands, both of which are used for exiting and add them to the form. In MicroEmulator, this allows for both shown commands to be used to exit the script.

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

Add a sample text message with label "Message" and the string "Hi from Luaj!" as the main text. Finally, set the display to be visible (again standard J2ME).

Note two things:

  • Lua function print is going to print to MicroEmulator's console
  • You can exit from MIDlet in the emulator, change the Lua script, run the MIDlet again and the changes will be applied
Be Sociable, Share!

Leave a Reply


two × = 16

Developing Bluetooth-enabled apps with Java

A good starting explanation about network programming in general comes from this page. Look at the whole book – it’s called “An Introduction to Bluetooth Programming” and I find it a very good read.

Emulation

Before we start, I’d like to mention that, for development, it’s much easier to develop using emulation of bluetooth / mobile phone. For that, I’ve used BlueCove and MicroEmulator, respectively. The glue between them is the BlueCove JSR-82 Emulator module.

Scenario

The situation we’ll examine is simple. We have two devices – in this case a PC and a phone – communicating with each other. The example would be a simple searcher for Stack Exchange sites. The user on the mobile phone would type a word, that gets sent to the server on the PC, it searches using api.stackexchange.com and returns the results to the phone, which displays them.

Following are the steps needed for the above to happen.

Discovery

To communicate, a bluetooth app uses the SDP (Service Discovery Protocol) to find the other devices and services they provide. In this case, as PC acts as the server, the phone would initiate the discovery, i.e. be a client. The basic idea is presented in BlueCove’s javadoc. I’ve made a helper class called BluetoothUtils:

package com.icyrock.bluetooth;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class BluetoothUtils {
  private static final int btAttrServiceName = 0x0100;
  private final static Logger log = LoggerFactory.getLogger(BluetoothUtils.class);

  public List<RemoteDevice> discoverDevices() {
    final Object sync = new Object();
    final List<RemoteDevice> devs = new ArrayList<RemoteDevice>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
        try {
          log.info("Found device: address: {}, name: {}",
            dev.getBluetoothAddress(),
            dev.getFriendlyName(false));
          devs.add(dev);
        } catch (IOException e) {
          log.error(e.toString(), e);
        }
      }

      public void inquiryCompleted(int discType) {
        log.info("Device inquiry completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
      }
    };

    synchronized (sync) {
      boolean started;
      try {
        started = LocalDevice.getLocalDevice().getDiscoveryAgent()
          .startInquiry(DiscoveryAgent.GIAC, listener);
        if (started) {
          log.info("Device inquiry started");
          sync.wait();
          log.info("Devices count: {}", devs.size());
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    return devs;
  }

  public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) {
    final Object sync = new Object();
    final List<String> urls = new ArrayList<String>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
      }

      public void inquiryCompleted(int discType) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
        for (int i = 0; i < servRecord.length; i++) {
          String url = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
          if (url != null) {
            urls.add(url);
            DataElement name = servRecord[i].getAttributeValue(btAttrServiceName);
            log.info("Service found: url: {}, name: {}", url, name);
          }
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
        log.info("Service search completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

    };

    UUID[] uuidArr = new UUID[] { new UUID(uuidStr, false) };
    int[] attrIds = new int[] { btAttrServiceName };

    for (RemoteDevice device : devices) {
      synchronized (sync) {
        try {
          log.info("Searching for service: {} on device: {} / {}",
            new Object[] { uuidStr, device.getBluetoothAddress(), device.getFriendlyName(false) });
          LocalDevice.getLocalDevice().getDiscoveryAgent()
            .searchServices(attrIds, uuidArr, device, listener);
          sync.wait();
        } catch (Exception e) {
          log.error(e.toString(), e);
        }
      }
    }

    return urls;
  }
}

This one has two methods:

  • public List<RemoteDevice> discoverDevices() – Searches for the devices and returns a list of devices that have been found
  • public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) – Searches for the service given by uuidStr on the devices in the devices list. It returns the URLs that can be used to access these services

These methods are going to be used in client and server later.

Client

The client is fairly simple:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import javax.bluetooth.RemoteDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommClient {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommClient.class);
  @Autowired
  private BluetoothUtils bluetoothUtils;
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

  public void run() {
    try {
      startClient();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startClient() throws Exception {
    List<RemoteDevice> devices = bluetoothUtils.discoverDevices();
    List<String> urls = bluetoothUtils.searchForServices(devices, uuid);

    if (urls.size() > 0) {
      String keywords = "bluetooth";

      StreamConnection streamConn = (StreamConnection) Connector.open(urls.get(0));

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(Lists.newArrayList(keywords), null, os);
      os.close();

      InputStream is = streamConn.openInputStream();
      String result = IOUtils.toString(is);
      is.close();

      log.info("Result: {}", result);

      streamConn.close();

      log.info("Client done");
    }
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommClient sebc = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommClient.class);
    sebc.run();
  }
}

Aside from the boilerplate code needed, there are two important notes above. First, in main method, there’s this line:

    System.setProperty("bluecove.stack", "emulator");

This one is used when testing this with BlueCove Bluetooth emulation, to signal BlueCove to use the emulator instead of the real bluetooth stack. Read more about this here.

Second, note this line:

  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

This one defines the UUID of the service that we are going to look for. In Bluetooth, all services are represented by UUID. To generate these in Linux, you can do something like:

$ uuidgen|tr -d '-'
db56eca4204d4f478063dacab16e805f

The other things are all in startClient method. The method does this:

  • Searches for all devices
  • Searches for the given service (identified by the uuid) on all found devices
  • If the search result yields any URLs, pick the first one (the other option would be to present the user with the choice of which URL to use)
  • Open a StreamConnection – this is basically a RFCOMM channel
  • Write the search keywords to the output stream
  • Read the response from the server and log it

Note that I’m using a few helper libraries here:

Here’s a Gradle config for this project:

apply plugin: 'java'

repositories {
  mavenCentral()
}

dependencies {
  compile 'net.sf.bluecove:bluecove:2.1.0'
  runtime 'net.sf.bluecove:bluecove-gpl:2.1.0'
  runtime 'net.sf.bluecove:bluecove-emu:2.1.0'
  
  compile 'ch.qos.logback:logback-classic:1.0.7'
  compile 'commons-io:commons-io:2.4'
  compile 'org.apache.httpcomponents:httpclient:4.2.1'
  compile 'org.apache.httpcomponents:fluent-hc:4.2.1'
  compile 'com.fasterxml.jackson.core:jackson-databind:2.1.0'
 
  compile 'org.springframework:spring-context:3.1.2.RELEASE'
  runtime 'asm:asm:3.3.1'
  runtime 'cglib:cglib-nodep:2.2.2'
  runtime 'org.slf4j:jcl-over-slf4j:1.6.6'
  
  compile 'com.google.guava:guava:13.0.1'
}

StreamUtils

One problem that I encountered is that you cannot use BufferedReader to read from InputStream provided by the bluetooth connection. The issue is that readLine will block until the whole line is read, but the way it works is it reads chunks of data to be efficient and thus doesn’t know when the line ends. In order to circumvent this, I’m using a simple char-by-char reading, which is hacky, inefficient and might not work properly sometimes, but solves this problem and works well for this example.

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.google.common.base.Joiner;

@Component
public class StreamUtils {
  private final static Logger log = LoggerFactory.getLogger(StreamUtils.class);

  private String readLine(InputStream inputStream) {
    StringBuilder sb = new StringBuilder();
    int sleeps = 0;
    boolean haveNewLine = false;

    while (true) {
      int byteRead;
      try {
        if (inputStream.available() == 0) {
          if (sleeps < 3) {
            sleeps++;
            Thread.sleep(1000);
            continue;
          } else {
            break;
          }
        }

        byteRead = inputStream.read();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }

      if (byteRead == -1) {
        break;
      }

      char ch = (char) byteRead;
      if (ch == '\n') {
        haveNewLine = true;
        break;
      }
      sb.append(ch);
    }
    return haveNewLine ? sb.toString() : null;
  }

  public List<String> readLines(InputStream inputStream) {
    List<String> lines = new ArrayList<>();
    while (true) {
      String line = readLine(inputStream);
      if (line == null) {
        break;
      }
      lines.add(line);
    }
    return lines;
  }

  public String readLinesAsStr(InputStream inputStream) {
    return Joiner.on("\n").join(readLines(inputStream));
  }
}

Server

Server is pretty simple, too:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommServer {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommServer.class);
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";
  @Autowired
  private StreamUtils streamUtils;

  public void run() {
    try {
      startServer();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startServer() throws Exception {
    LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);

    StreamConnectionNotifier serverConnection = (StreamConnectionNotifier) Connector.open(
      "btspp://localhost:" + uuid + ";name=StackExchangeBluetoothServer");

    while (true) {
      log.info("Waiting for connection");
      StreamConnection streamConn = serverConnection.acceptAndOpen();

      log.info("Request received");

      InputStream is = streamConn.openInputStream();
      String content = streamUtils.readLinesAsStr(is);
      is.close();

      List<String> resp;
      if (content == null || content.equals("")) {
        log.info("Empty keywords received from the client.");
        resp = Lists.newArrayList("Please provide keywords to search for");
      } else {
        resp = queryStackExchange(content);
      }

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(resp, null, os);
      os.close();

      streamConn.close();
    }
  }

  @SuppressWarnings("unchecked")
  private List<String> queryStackExchange(String keywords) throws Exception {
    // Uncomment if you want to test without actually making the calls to StackExchange
//    if(true) {
//      return Lists.newArrayList("title1", "title2", "title3");
//    }
    
    URI uri = new URIBuilder()
      .setScheme("https")
      .setHost("api.stackexchange.com")
      .setPath("/2.1/search")
      .setParameter("order", "desc")
      .setParameter("intitle", keywords)
      .setParameter("site", "stackoverflow")
      .setParameter("pagesize", "5")
      .build();

    HttpResponse httpResp = Request.Get(uri)
      .connectTimeout(5000)
      .socketTimeout(5000)
      .execute().returnResponse();

    List<String> ret = new ArrayList<String>();
    if (httpResp.getStatusLine().getStatusCode() == 200) {
      HttpEntity httpEnt = httpResp.getEntity();

      InputStream is = httpEnt.getContent();
      Header contentEncoding = httpResp.getFirstHeader("Content-Encoding");
      if (contentEncoding != null && "gzip".equals(contentEncoding.getValue())) {
        is = new GZIPInputStream(is);
      }
      String response = IOUtils.toString(is);
      is.close();

      ObjectMapper om = new ObjectMapper();
      Map<String, Object> map = om.readValue(response, Map.class);
      List<Map<String, Object>> items = (List<Map<String, Object>>) map.get("items");

      for (Map<String, Object> item : items) {
        ret.add((String) item.get("title"));
      }
    }

    log.info("Result: {}", Joiner.on(", ").join(ret));
    return ret;
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommServer sebs = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommServer.class);
    sebs.run();
  }
}

In startServer method, it listens for the connection. When one is established, it reads the keywords from the client. If nothing was read, it will reply with a help message. If keywords were read, it will query the StackExchange. The query is separated into queryStackExchange method and uses Apache HttpClient fluid interface – seems pretty nice to me. If the response was received, it will un-GZIP it if needed and then use Jakson JSON parser to actually get the titles from the response.

Be Sociable, Share!

Leave a Reply


seven − 6 =