Lisp Quiz #3

Lisp Quiz challenges and discussion
Post Reply
Unne
Posts: 32
Joined: Sat Jun 28, 2008 6:10 pm
Location: Oregon
Contact:

Lisp Quiz #3

Post by Unne » Sat Jun 13, 2009 1:08 am

Hope no one minds if I post a new quiz. This one is blatantly borrowed from Ruby Quiz.

Given a location (a city name, postal code, or some other identifier), print the current temperature.

Example:

Code: Select all

> (weather 12345)
The temperature in SCHENECTADY, NY is 55 degrees F.
This isn't a difficult problem, but may be a good way to explore some corners of your Lisp that you otherwise haven't touched yet. It's also a tool with a practical application (if you're curious what the weather is like).

Please don't post any code until Monday June 15th, to allow others a chance to stew over the problem a bit.

Bonus: Allow a choice between English and Metric units.
Bonus^2: Print other current weather conditions in addition to temperature.
Bonus^4: Print a complete weather forecast.

simon
Posts: 16
Joined: Wed May 13, 2009 9:12 am

Re: Lisp Quiz #3

Post by simon » Tue Jun 16, 2009 7:58 am

So nobody does these things, right?

I thought about your quiz question over coffee. I've never dealt with XML in lisp before, or web interfaces so I thought it might be fun to see how much is involved. Luckily people have thought about this sort of problem and come up with some neat programs.

So, here is some terrible, terrible code using drakma and cxml to do the simple task you asked:

Code: Select all

(defun geo-lookup (query)
  (drakma:http-request
   (format nil "http://api.wunderground.com/auto/wui/geo/GeoLookupXML/index.xml?query=~A" 
           (if (numberp query) query (substitute #\+ #\space query)))))  ; hack around spaces in query

(defun current-conditions (query)
  (drakma:http-request
   (format nil "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=~A" query)))

(defun temperature-at (&optional (query 94107))
  (klacks:with-open-source (source (cxml:make-source (geo-lookup query)))
    (klacks:find-element source "location")
    (loop for tag in (list "country" "state" "city" "lat" "lon") collecting 
         (progn
           (klacks:find-element source tag)
           (klacks:peek-next source)
           (klacks:current-characters source))
       into list
       finally  (format t "The temperature near ~A, ~A, ~A, (lat:~A, lon:~A) is " 
                        (third list) (second list) (first list) (fourth list) (fifth list)))
    (klacks:find-element source "icao")
    (klacks:peek-next source)
    (klacks:with-open-source (source (cxml:make-source (current-conditions (klacks:current-characters source))))
      (klacks:find-element source "temperature_string")
      (klacks:peek-next source)
      (format t "~A~%" (klacks:current-characters source)))))
This is the first time I've used either library (and many of their dependencies, it seems) so I'm probably going about it all wrong.

Also, the code is horribly brittle and shouldn't be though of as anything but a proof of concept. Someone with more time that I have at the moment could write something fairly straightforward to unmarshall these xml structures properly into classes and do more interesting things with them....

Anyway, it has no error recovery, no flexibility, no validation, and makes unreasonable assumptions about the data, but at least it works with different queries:

Code: Select all

CL-USER> (temperature-at 10451)
The temperature near Bronx, NY, US, (lat:40.82016373, lon:-73.92166138) is 63 F (17 C)
CL-USER> (temperature-at "san diego, ca")
The temperature near San Diego, CA, US, (lat:32.72109985, lon:-117.16430664) is 67 F (19 C)
And know I know where to go looking if/when I need to deal with XML data, so your quiz is a success for me.

Unne
Posts: 32
Joined: Sat Jun 28, 2008 6:10 pm
Location: Oregon
Contact:

Re: Lisp Quiz #3

Post by Unne » Tue Jun 16, 2009 8:14 pm

simon wrote:So nobody does these things, right?
Enough people to make it interesting, at least. Your code looks good to me. I've heard good things about CXML recently so I was interested to see what people would do with it. I didn't know about Drakma at all; it's cool that it's so easy to fetch the contents of a URL in CL.

Here's my Clojure code. My first attempt (at the bottom of the source code) uses Yahoo weather's RSS feed, which requires a US ZIP code or worldwide weather-station code. Wunderground looks like it has a better API for querying cities and I liked your code so I ported it to Clojure for comparison, also included below.

Code: Select all

(ns weather
  (:use (clojure [xml :only [parse]]
                 [zip :only [xml-zip]])
        (clojure.contrib duck-streams str-utils pprint)
        (clojure.contrib.zip-filter xml)))

(defn clean [uri]
  (re-gsub #"\s+" "+" uri))

(defn fetch-xml [uri]
  (xml-zip
   (parse
    (org.xml.sax.InputSource.
     (java.io.StringReader.
      (slurp* (java.net.URI. (clean uri))))))))

(defn fetch-location [query]
  (let [x (fetch-xml (str "http://api.wunderground.com/auto/wui/geo/GeoLookupXML/index.xml?query=" query))
        loc-code (xml1-> x :nearby_weather_stations :airport :station :icao text)
        [city state country] (map #(xml1-> x % text) [:city :state :country])
        loc (str city ", "
                 (when-not (empty? state) (str state ", "))
                 country)]
    [loc loc-code]))

(defn fetch-weather [loc-code]
  (let [x (fetch-xml (str "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=" loc-code))]
    (xml1-> x :temperature_string text)))

(defn wunderground-weather [location]
  (let [[loc loc-code] (fetch-location location)]
    (println "The temperature near" loc "is" (fetch-weather loc-code))))

(defn yahoo-weather
  ([loc-code] (yahoo-weather loc-code "c"))
  ([loc-code unit]
     (let [rss (fetch-xml (str "http://weather.yahooapis.com/forecastrss?p=" loc-code "&u=" unit))
           [units loc wind atm ast] (map #(xml1-> rss :channel (keyword (str "yweather:" %)))
                                         ["units" "location" "wind" "atmosphere" "astronomy"])
           conditions (xml1-> rss :channel :item :yweather:condition)
           date (re-find #"\d+:\d+.*" (xml1-> rss :channel :item :pubDate text))
           fors (xml-> rss :channel :item :yweather:forecast)]
       
       (cl-format true
"Weather for ~a, ~a (~a)
    Temperature: ~d° ~a
     Wind Chill: ~a° ~a, ~a ~a
     Conditions: ~a
       Humidity: ~a%
      Barometer: ~a ~a
 Sunrise/Sunset: ~a / ~a

Forecast:
~{  ~{~a: ~a. Hi ~2d, Lo ~2d.~}~^~%~}
"
              (attr loc :city) (attr loc :region) date
              (attr conditions :temp) (attr units :temperature)
              (attr wind :chill) (attr units :temperature) (attr wind :speed) (attr units :speed)
              (attr conditions :text)
              (attr atm :humidity)
              (attr atm :pressure) (attr units :pressure)
              (attr ast :sunrise) (attr ast :sunset)
              (map #(list (attr % :day)
                             (attr % :text)
                             (attr % :high)
                             (attr % :low))
                      fors)))))
Example

Code: Select all

weather> (wunderground-weather 12345)
The temperature near Schenectady, NY, US is 64 F (18 C)

weather> (yahoo-weather 12345)
Weather for Schenectady, NY (10:51 pm EDT)
    Temperature: 17° C
     Wind Chill: 17° C, 9.66 kph
     Conditions: Fair
       Humidity: 65%
      Barometer: 1024 mb
 Sunrise/Sunset: 5:17 am / 8:36 pm

Forecast:
  Tue: Mostly Clear. Hi 22, Lo 12.
  Wed: Partly Cloudy. Hi 24, Lo 13.
nil
simon wrote:And know I know where to go looking if/when I need to deal with XML data, so your quiz is a success for me.
Great! Same for me.

omouse
Posts: 3
Joined: Tue Sep 08, 2009 5:08 pm

Re: Lisp Quiz #3

Post by omouse » Tue Sep 08, 2009 11:47 pm

Small suggestion: How about making a class or some sort of "interface" that defines standard stuff for weather? I noticed that the two code examples above are using two different sources for the weather info but their output is nearly the same.

Post Reply