userstats function initial implementation
authorKyle Spaans <kspaans@student.math.uwaterloo.ca>
Mon, 20 Apr 2009 17:46:42 +0000 (13:46 -0400)
committerKyle Spaans <kspaans@student.math.uwaterloo.ca>
Mon, 20 Apr 2009 17:46:42 +0000 (13:46 -0400)
Created new file for "userstats" code. Also refactored date-getting
code from ppd.ss into common.ss for use with userstats.
Currently, the total number of users, times of first and last posts
and average number of posts per day are calculated.

All data is saved in a hash table of data stuctures.

common.ss
nntp-to-dot.ss
ppd.ss
userstats.ss [new file with mode: 0644]

index 1723255..53810c4 100644 (file)
--- a/common.ss
+++ b/common.ss
@@ -7,9 +7,10 @@
 #lang scheme
 
 (require net/nntp)
+(require srfi/19)
 
 (provide message-getter get-refs make-dot-id ins-user-id ins-mid-u from-regexp
-         mid-regexp ref-regexp subj-regexp date-regexp)
+         mid-regexp ref-regexp subj-regexp date-regexp get-date)
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
         (hash-set! htable mid user)
         ;; Mid already exists?
        (error 'mid-collision))))
+
+;; get-date: string -> date
+;; Returns an SRFI-19 date object based on the date header from the newsgroup
+;; Date headers seem to be either 32,36,37,42 or 43 chars long
+;;   can this be generalized better?
+(define (get-date date-str)
+  (let [(mlen (string-length date-str))]
+    (cond
+      [(= 32 mlen) (string->date (substring date-str 6)
+                                 "~d ~b ~Y ~H:~M:~S ~z")]
+      [(= 36 mlen) (string->date (substring date-str 6)
+                                 "~a, ~d ~b ~Y ~H:~M:~S ~z")]
+      [(= 37 mlen) (string->date (substring date-str 6)
+                                 "~a, ~d ~b ~Y ~H:~M:~S ~z")]
+      [(= 42 mlen) (string->date (substring date-str 6)
+                                 "~a, ~d ~b ~Y ~H:~M:~S ~z")]
+      [(= 43 mlen) (string->date (substring date-str 6)
+                                 "~a, ~d ~b ~Y ~H:~M:~S ~z")]
+      [else (printf "date string was unknown length ~a~n" mlen) #xDEADBEEF])))
index 02ff1cf..deb1dae 100644 (file)
@@ -15,6 +15,7 @@
 (require "userrel.ss")
 (require "thread.ss")
 (require "ppd.ss")
+(require "userstats.ss")
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
@@ -39,7 +40,7 @@
 (define (read-all first last newsd)
   (cond
     [(= first last) (printf "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n")]
-    [else (let [(message (message-getter uwnews first (list from-regexp subj-regexp mid-regexp ref-regexp)))]
+    [else (let [(message (message-getter newsd first (list from-regexp subj-regexp mid-regexp ref-regexp date-regexp)))]
             (cond
               [(boolean? message) (void)]
               [else (for-each (lambda (header) (printf "~a~n" header))
@@ -74,7 +75,8 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; CODE TO EXECUTE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(posts-per-day first last uwnews)
+;(posts-per-day first last uwnews)
+(count-users first last uwnews)
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (close-output-port dotfile)
diff --git a/ppd.ss b/ppd.ss
index b53a739..223e059 100644 (file)
--- a/ppd.ss
+++ b/ppd.ss
     [else (let [(message (message-getter newsd first (list date-regexp)))]
             (cond
               [(boolean? message) (void)]
-              [else ;(printf "~a~n~a~n" (string-length (car message)) (car message))
-               (let [(mlen (string-length (car message)))]
-               (cond
-                 [(= 32 mlen) (set! date-list (cons (string->date (substring (car message) 5) "~d ~b ~Y ~H:~M:~S ~z") date-list))]
-                 [(= 36 mlen) (set! date-list (cons (string->date (substring (car message) 5) "~a, ~d ~b ~Y ~H:~M:~S ~z") date-list))]
-                 [(= 37 mlen) (set! date-list (cons (string->date (substring (car message) 5) "~a, ~d ~b ~Y ~H:~M:~S ~z") date-list))]
-                 [(= 43 mlen) (set! date-list (cons (string->date (substring (car message) 5) "~a, ~d ~b ~Y ~H:~M:~S ~z") date-list))]))]))
+              [else (set! date-list (cons (get-date (car message)) date-list))]))
           (posts-per-day (+ first 1) last newsd)]))
diff --git a/userstats.ss b/userstats.ss
new file mode 100644 (file)
index 0000000..047287a
--- /dev/null
@@ -0,0 +1,82 @@
+;; Copyright (C) 2009  Kyle Spaans
+;;  this program is distributed under the GPL v2 or (at your option)
+;;  any later version.
+
+;; Calculate a bunch of user statistics:
+;; - Number of users
+;; - Post counts of users
+;; - Average number of posts
+;; - posts per day...
+;; - new users versus time...
+
+#lang scheme
+
+(require "common.ss")
+(require srfi/19)
+
+(provide count-users)
+
+;; User Statistics Struct
+;; String - usename in the form of an email address, possibility of duplicates
+;; int    - total number of posts in newsgroup
+;; date   - SRFI-19 date struct, date of first post to group
+;; date   - "     "    ", date of last post to struct
+;; int    - number of days in range from first post day to past post day
+;; real   - number of posts / number of days
+(define-struct ustats (user nump firstp lastp days avgppd))
+
+;; Collect stats in the form of '(USERNAME ustats)
+(define users (make-hash))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; print-stats: void -> void
+;; Pretty print the contents of the user statistics table to stdout
+(define (print-stats)
+  (printf "Total number of users: ~a~n~n" (hash-count users))
+  (hash-for-each
+    users
+    (lambda (k v)
+      (printf "User: ~a~nCount: ~a~nFirst: ~a~nLast:  ~a~nAvgPPD: ~a~n~n"
+              (ustats-user v)
+              (ustats-nump v)
+              (ustats-firstp v)
+              (ustats-lastp v)
+              (ustats-avgppd v)))))
+
+;; count-users: int int newsgroup -> void
+;; build a hash table with each user from the group collecting stats:
+;; - number of posts
+;; - first/last post
+;; So if "last" post is empty, then user has only 1 post
+(define (count-users first last newsd)
+  (cond
+    [(= first last) (print-stats)]
+    [else (letrec [(message (message-getter newsd first (list from-regexp date-regexp)))
+                   (mesg-from (if (boolean? message) message (car message)))
+                   (mesg-date (if (boolean? message) message (cadr message)))]
+            (cond
+              [(boolean? message) (void)]
+              [else
+               (let [(result (hash-ref users mesg-from #f))]
+                 (cond
+                   [(boolean? result)
+                    ;(printf "First post ~a ::@:: ~a~n" mesg-from mesg-date)
+                    (hash-set! users mesg-from (make-ustats mesg-from 1 (get-date mesg-date) '() 1 1))]
+                   [else
+                                     ;; Calculate the ordinal day of the year (1-365) of the given date
+                                     ;; then subtract. Alternatively date->time and time-difference
+                                     ;; functions can be used to calculate this
+                    (let [(pdays (if (empty? (ustats-lastp result))
+                                     1
+                                     (+ 1 (- (date-year-day (ustats-lastp result))
+                                             (date-year-day (ustats-firstp result))))))
+                          (new-nump (+ 1 (ustats-nump result)))]
+                    (hash-set! users mesg-from (make-ustats
+                                                 mesg-from
+                                                 new-nump
+                                                 (ustats-firstp result)
+                                                 (get-date mesg-date)
+                                                 pdays
+                                                 (/ new-nump pdays))))]))]))
+          (count-users (+ first 1) last newsd)]))