Top rank per group: Difference between revisions

From Rosetta Code
Content added Content deleted
m (Fixed lang tags.)
(added ocaml)
Line 525: Line 525:
Kim Arlich, id=E10001, salary=57000
Kim Arlich, id=E10001, salary=57000
Timothy Grove, id=E16398, salary=29900
Timothy Grove, id=E16398, salary=29900
</pre>

=={{header|OCaml}}==
<lang ocaml>open StdLabels

let to_string (name,_,s,_) = (Printf.sprintf "%s (%d)" name s)

let take n li =
let rec aux i acc = function
| _ when i >= n -> (List.rev acc)
| [] -> (List.rev acc)
| x::xs -> aux (succ i) (x::acc) xs
in
aux 0 [] li ;;

let toprank data n =
let len = List.length data in
let h = Hashtbl.create len in
List.iter data ~f:(fun ((_,_,_,dep) as employee) ->
Hashtbl.add h dep employee);
let deps =
List.fold_left data ~init:[] ~f:
(fun ac (_,_,_,v) -> if List.mem v ac then ac else v::ac) in
let f dep =
Printf.printf "Department: %s\n " dep;
let l = Hashtbl.find_all h dep in
let l2 = List.sort (fun (_,_,v1,_) (_,_,v2,_) -> compare v2 v1) l in
let l3 = (take n l2) in
print_endline(String.concat ", " (List.map to_string l3));
print_newline()
in
List.iter f deps;
;;

let data = [
"Tyler Bennett", "E10297", 32000, "D101";
"John Rappl", "E21437", 47000, "D050";
"George Woltman", "E00127", 53500, "D101";
"Adam Smith", "E63535", 18000, "D202";
"Claire Buckman", "E39876", 27800, "D202";
"David McClellan", "E04242", 41500, "D101";
"Rich Holcomb", "E01234", 49500, "D202";
"Nathan Adams", "E41298", 21900, "D050";
"Richard Potter", "E43128", 15900, "D101";
"David Motsinger", "E27002", 19250, "D202";
"Tim Sampair", "E03033", 27000, "D101";
"Kim Arlich", "E10001", 57000, "D190";
"Timothy Grove", "E16398", 29900, "D190";
]

let () =
toprank data 3;
;;</lang>

outputs:
<pre>Department: D190
Kim Arlich (57000), Timothy Grove (29900)

Department: D202
Rich Holcomb (49500), Claire Buckman (27800), David Motsinger (19250)

Department: D050
John Rappl (47000), Nathan Adams (21900)

Department: D101
George Woltman (53500), David McClellan (41500), Tyler Bennett (32000)
</pre>
</pre>



Revision as of 21:15, 16 December 2009

Task
Top rank per group
You are encouraged to solve this task according to the task description, using any language you may know.

Find the top N salaries in each group, where N is provided as a parameter.

Use this data as a formatted internal data structure (adapt it to your language-native idioms, rather than parse at runtime), or identify your external data source: <lang csv>Employee Name,Employee ID,Salary,Department Tyler Bennett,E10297,32000,D101 John Rappl,E21437,47000,D050 George Woltman,E00127,53500,D101 Adam Smith,E63535,18000,D202 Claire Buckman,E39876,27800,D202 David McClellan,E04242,41500,D101 Rich Holcomb,E01234,49500,D202 Nathan Adams,E41298,21900,D050 Richard Potter,E43128,15900,D101 David Motsinger,E27002,19250,D202 Tim Sampair,E03033,27000,D101 Kim Arlich,E10001,57000,D190 Timothy Grove,E16398,29900,D190</lang>

C++

<lang cpp>#include <string>

  1. include <set>
  2. include <list>
  3. include <map>
  4. include <iostream>


struct Employee { std::string Name; std::string ID; unsigned long Salary; std::string Department; Employee(std::string _Name = "", std::string _ID = "", unsigned long _Salary = 0, std::string _Department = "") { Name = _Name; ID = _ID; Salary = _Salary; Department = _Department; }

void display(std::ostream& out) const { out << Name << "\t" << ID << "\t" << Salary << "\t" << Department << std::endl; } };

// We'll tell std::set to use this to sort our employees. struct CompareEarners { bool operator()(const Employee& e1, const Employee& e2) { return (e1.Salary > e2.Salary); } };

// A few typedefs to make the code easier to type, read and maintain. typedef std::list<Employee> EMPLOYEELIST;

// Notice the CompareEarners; We're telling std::set to user our specified comparison mechanism // to sort its contents. typedef std::set<Employee, CompareEarners> DEPARTMENTPAYROLL;

typedef std::map<std::string, DEPARTMENTPAYROLL> DEPARTMENTLIST;

void initialize(EMPLOYEELIST& Employees) { // Initialize our employee list data source. Employees.push_back(Employee("Tyler Bennett", "E10297", 32000, "D101")); Employees.push_back(Employee("John Rappl", "E21437", 47000, "D050")); Employees.push_back(Employee("George Woltman", "E21437", 53500, "D101")); Employees.push_back(Employee("Adam Smith", "E21437", 18000, "D202")); Employees.push_back(Employee("Claire Buckman", "E39876", 27800, "D202")); Employees.push_back(Employee("David McClellan", "E04242", 41500, "D101")); Employees.push_back(Employee("Rich Holcomb", "E01234", 49500, "D202")); Employees.push_back(Employee("Nathan Adams", "E41298", 21900, "D050")); Employees.push_back(Employee("Richard Potter", "E43128", 15900, "D101")); Employees.push_back(Employee("David Motsinger", "E27002", 19250, "D202")); Employees.push_back(Employee("Tim Sampair", "E03033", 27000, "D101")); Employees.push_back(Employee("Kim Arlich", "E10001", 57000, "D190")); Employees.push_back(Employee("Timothy Grove", "E16398", 29900, "D190")); }

void group(EMPLOYEELIST& Employees, DEPARTMENTLIST& Departments) { // Loop through all of our employees. for( EMPLOYEELIST::iterator iEmployee = Employees.begin(); Employees.end() != iEmployee; ++iEmployee ) { DEPARTMENTPAYROLL& groupSet = Departments[iEmployee->Department];

// Add our employee to this group. groupSet.insert(*iEmployee); } }

void present(DEPARTMENTLIST& Departments, unsigned int N) { // Loop through all of our departments for( DEPARTMENTLIST::iterator iDepartment = Departments.begin(); Departments.end() != iDepartment; ++iDepartment ) { std::cout << "In department " << iDepartment->first << std::endl; std::cout << "Name\t\tID\tSalary\tDepartment" << std::endl; // Get the top three employees for each employee unsigned int rank = 1; for( DEPARTMENTPAYROLL::iterator iEmployee = iDepartment->second.begin(); ( iDepartment->second.end() != iEmployee) && (rank <= N); ++iEmployee, ++rank ) { iEmployee->display(std::cout); } std::cout << std::endl; } }

int main(int argc, char* argv[]) { // Our container for our list of employees. EMPLOYEELIST Employees;

// Fill our list of employees initialize(Employees);

// Our departments. DEPARTMENTLIST Departments;

// Sort our employees into their departments. // This will also rank them. group(Employees, Departments);

// Display the top 3 earners in each department. present(Departments, 3);

return 0; }</lang>

Output:

In department D050
Name            ID      Salary  Department
John Rappl      E21437  47000   D050
Nathan Adams    E41298  21900   D050

In department D101
Name            ID      Salary  Department
George Woltman  E21437  53500   D101
David McClellan E04242  41500   D101
Tyler Bennett   E10297  32000   D101

In department D190
Name            ID      Salary  Department
Kim Arlich      E10001  57000   D190
Timothy Grove   E16398  29900   D190

In department D202
Name            ID      Salary  Department
Rich Holcomb    E01234  49500   D202
Claire Buckman  E39876  27800   D202
David Motsinger E27002  19250   D202

Common Lisp

<lang lisp>(defun top-n-by-group (n data value-key group-key predicate &key (group-test 'eql))

 (let ((not-pred (complement predicate))
       (group-data (make-hash-table :test group-test)))
   (labels ((value (datum)
              (funcall value-key datum))
            (insert (x list)
              (merge 'list (list x) list not-pred :key #'value))
            (entry (group)
              "Return the entry for the group, creating it if
               necessary. An entry is a list whose first element is
               k, the number of items currently associated with the
               group (out of n total), and whose second element is
               the list of the k current top items for the group."
              (multiple-value-bind (entry presentp)
                  (gethash group group-data)
                (if presentp entry
                  (setf (gethash group group-data)
                        (list 0 '())))))
            (update-entry (entry datum)
              "Update the entry using datum. If there are already n
               items associated with the entry, then when datum's value
               is greater than the current least item, data is merged into
               the items, and the list (minus the first element) is
               stored in entry. Otherwise, if there are fewer than n
               items in the entry, datum is merged in, and the
               entry's k is increased by 1."
              (if (= n (first entry))
                (when (funcall predicate (value datum) (value (first (second entry))))
                  (setf (second entry)
                        (cdr (insert datum (second entry)))))
                (setf (first entry) (1+ (first entry))
                      (second entry) (insert datum (second entry))))))
     (dolist (datum data group-data)
       (update-entry (entry (funcall group-key datum)) datum)))))</lang>

Example

<lang lisp>> (defparameter *employee-data*

 '(("Tyler Bennett" E10297 32000 D101)
   ("John Rappl" E21437 47000 D050)
   ("George Woltman" E00127 53500 D101)
   ("Adam Smith" E63535 18000 D202)
   ("Claire Buckman" E39876 27800 D202)
   ("David McClellan" E04242 41500 D101)
   ("Rich Holcomb" E01234 49500 D202)
   ("Nathan Adams" E41298 21900 D050)
   ("Richard Potter" E43128 15900 D101)
   ("David Motsinger" E27002 19250 D202)
   ("Tim Sampair" E03033 27000 D101)
   ("Kim Arlich" E10001 57000 D190)
   ("Timothy Grove" E16398 29900 D190))
 "A list of lists of each employee's name, id, salary, and

department.")

  • EMPLOYEE-DATA*

> (top-n-by-group 3 *employee-data* 'third 'fourth '>)

  1. <EQL Hash Table{4} 2361A0E7>

> (describe *)

  1. <EQL Hash Table{4} 2361A0E7> is a HASH-TABLE

D101 (3 (("Tyler Bennett" E10297 32000 D101) ("David McClellan" E04242 41500 D101) ("George Woltman" E00127 53500 D101))) D050 (2 (("Nathan Adams" E41298 21900 D050) ("John Rappl" E21437 47000 D050))) D202 (3 (("David Motsinger" E27002 19250 D202) ("Claire Buckman" E39876 27800 D202) ("Rich Holcomb" E01234 49500 D202))) D190 (2 (("Timothy Grove" E16398 29900 D190) ("Kim Arlich" E10001 57000 D190)))</lang>

E

<lang e>/** Turn a list of arrays into a list of maps with the given keys. */ def addKeys(keys, rows) {

 def res := [].diverge()
 for row in rows { res.push(__makeMap.fromColumns(keys, row)) }
 return res.snapshot()

}

def data := addKeys(

 ["name",            "id",  "salary", "dept"],
[["Tyler Bennett",   "E10297", 32000, "D101"],
 ["John Rappl",      "E21437", 47000, "D050"],
 ["George Woltman",  "E00127", 53500, "D101"],
 ["Adam Smith",      "E63535", 18000, "D202"],
 ["Claire Buckman",  "E39876", 27800, "D202"],
 ["David McClellan", "E04242", 41500, "D101"],
 ["Rich Holcomb",    "E01234", 49500, "D202"],
 ["Nathan Adams",    "E41298", 21900, "D050"],
 ["Richard Potter",  "E43128", 15900, "D101"],
 ["David Motsinger", "E27002", 19250, "D202"],
 ["Tim Sampair",     "E03033", 27000, "D101"],
 ["Kim Arlich",      "E10001", 57000, "D190"],
 ["Timothy Grove",   "E16398", 29900, "D190"]])

def topSalaries(n, out) {

   var groups := [].asMap()
   for row in data {
       def [=> salary, => dept] | _ := row
       def top := groups.fetch(dept, fn {[]}).with([-salary, row]).sort()
       groups with= (dept, top.run(0, top.size().min(n)))
   }
   for dept => group in groups.sortKeys() {
       out.println(`Department $dept`)
       out.println(`---------------`)
       for [_, row] in group {
         out.println(`${row["id"]}  $$${row["salary"]}  ${row["name"]}`)
       }
       out.println()
   }

}</lang>

(Note: This uses an append-and-then-sort to maintain the list of top N; a sorted insert or a proper selection algorithm would be more efficient. As long as N is small, this does not matter much; the algorithm is O(n) with respect to the data set.)

<lang e>? topSalaries(3, stdout) Department D050


E21437 $47000 John Rappl E41298 $21900 Nathan Adams

Department D101


E00127 $53500 George Woltman E04242 $41500 David McClellan E10297 $32000 Tyler Bennett

Department D190


E10001 $57000 Kim Arlich E16398 $29900 Timothy Grove

Department D202


E01234 $49500 Rich Holcomb E39876 $27800 Claire Buckman E27002 $19250 David Motsinger</lang>

Haskell

<lang haskell>import Data.List import Control.Monad import Control.Arrow import Text.Printf import Data.Ord import Data.Function

groupingOn = ((==) `on`) comparingDwn = flip . comparing


type ID = Int type DEP = String type NAME = String type SALARY = Double data Employee = Employee {nr :: ID, dep :: DEP, name :: NAME, sal :: SALARY}

employees :: [Employee] employees = map (\(i,d,n,s) -> Employee i d n s)

           [(1001,"AB","Janssen A.H.",41000), (101,"KA","'t Woud B.",45000),
            (1013,"AB","de Bont C.A.",65000), (1101,"CC","Modaal A.M.J.",30000),
            (1203,"AB","Anders H.",50000),    (100,"KA","Ezelbips P.J.",52000),
            (1102,"CC","Zagt A.",33000),     (1103,"CC","Ternood T.R.",21000),
            (1104,"CC","Lageln M.",23000),   (1105,"CC","Amperwat A.",19000),
            (1106,"CC","Boon T.J.",25000), (1107,"CC","Beloop L.O.",31000),
            (1009,"CD","Janszoon A.",38665), (1026,"CD","Janszen H.P.",41000),
            (1011,"CC","de Goeij J.",39000), (106,"KA","Pragtweik J.M.V.",42300),
            (111,"KA","Bakeuro S.",31000),  (105,"KA","Clubdrager C.",39800),
            (104,"KA","Karendijk F.",23000), (107,"KA","Centjes R.M.",34000),
            (119,"KA","Tegenstroom H.L.",39000), (1111,"CD","Telmans R.M.",55500),
            (1093,"AB","de Slegte S.",46987), (1199,"CC","Uitlaat G.A.S.",44500)
           ]

dorank :: Int ->

         (Employee -> DEP) ->
         (Employee -> SALARY) ->
         [Employee]-> Employee

dorank n o1 o2 = map (take n. sortBy (comparingDwn o2))

                . groupBy (groupingOn o1) . sortBy (comparing o1)

toprank :: IO () toprank = do

  printf "%-16s %3s %10s\n" "NAME" "DEP" "TIP" 
  putStrLn $ replicate 31 '='
  mapM_ (mapM_ (ap (ap (printf "%-16s %3s %10.2g\n" . name) dep) sal)) $ dorank 3 dep sal employees</lang>

Output: top 3 per department

*Main> toprank
NAME             DEP        TIP
===============================
de Bont C.A.      AB   65000.00
Anders H.         AB   50000.00
de Slegte S.      AB   46987.00
Uitlaat G.A.S.    CC   44500.00
de Goeij J.       CC   39000.00
Zagt A.           CC   33000.00
Telmans R.M.      CD   55500.00
Janszen H.P.      CD   41000.00
Janszoon A.       CD   38665.00
Ezelbips P.J.     KA   52000.00
't Woud B.        KA   45000.00
Pragtweik J.M.V.  KA   42300.00

J

J has a rich set of primitive functions, which combine the power of an imperative language with the expressiveness of a declarative, SQL-like language:

<lang j>NB. Dynamically generate convenience functions ('`',,;:^:_1: N=:{.Employees) =:, (_&{"1)` ([^:(_ -: ])L:0)"0 _~ i.# E =: {: Employees

NB. Show top six ranked employees in each dept N , (<@:>"1@:|:@:((6 <. #) {. ] \: SALARY)/.~ DEPT) |: <"1&> E</lang>

+-----+-----+-----------------+------+
|ID   |DEPT |NAME             |SALARY|
+-----+-----+-----------------+------+
|1013 |AB   |de Bont C.A.     |65000 |
|1203 |AB   |Anders H.        |50000 |
|1093 |AB   |de Slegte S.     |46987 |
|1001 |AB   |Janssen A.H.     |41000 |
+-----+-----+-----------------+------+
|100  |KA   |Ezelbips P.J.    |52000 |
|101  |KA   |'t Woud B.       |45000 |
|106  |KA   |Pragtweik J.M.V. |42300 |
|105  |KA   |Clubdrager C.    |39800 |
|119  |KA   |Tegenstroom H.L. |39000 |
|107  |KA   |Centjes R.M.     |34000 |
+-----+-----+-----------------+------+
|1199 |CC   |Uitlaat G.A.S.   |44500 |
|1011 |CC   |de Goeij J.      |39000 |
|1102 |CC   |Zagt A.          |33000 |
|1107 |CC   |Beloop L.O.      |31000 |
|1101 |CC   |Modaal A.M.J.    |30000 |
|1106 |CC   |Boon T.J.        |25000 |
+-----+-----+-----------------+------+
|1111 |CD   |Telmans R.M.     |55500 |
|1026 |CD   |Janszen H.P.     |41000 |
|1009 |CD   |Janszoon A.      |38665 |
+-----+-----+-----------------+------+

using the data set:

   Employees=: (<;.1~(1 1{.~#);+./@:(;:E.S:0])@:{.)];._2 noun define
   ID   DEPT NAME             SALARY
   1001 AB   Janssen A.H.     41000 
   101  KA   't Woud B.       45000 
   1013 AB   de Bont C.A.     65000 
   1101 CC   Modaal A.M.J.    30000 
   1203 AB   Anders H.        50000 
   100  KA   Ezelbips P.J.    52000 
   1102 CC   Zagt A.          33000 
   1103 CC   Ternood T.R.     21000 
   1104 CC   Lageln M.        23000 
   1105 CC   Amperwat A.      19000 
   1106 CC   Boon T.J.        25000 
   1107 CC   Beloop L.O.      31000 
   1009 CD   Janszoon A.      38665 
   1026 CD   Janszen H.P.     41000 
   1011 CC   de Goeij J.      39000 
   106  KA   Pragtweik J.M.V. 42300 
   111  KA   Bakeuro S.       31000 
   105  KA   Clubdrager C.    39800 
   104  KA   Karendijk F.     23000 
   107  KA   Centjes R.M.     34000 
   119  KA   Tegenstroom H.L. 39000 
   1111 CD   Telmans R.M.     55500 
   1093 AB   de Slegte S.     46987 
   1199 CC   Uitlaat G.A.S.   44500 
   )

Named as a function where the (maximum) number of employees in each department is a parameter:

<lang j>reportTopSalaries=: 3 :'N , (<@:>"1@:|:@:((y <. #) {. ] \: SALARY)/.~ DEPT) |: <"1&> E'</lang> Example of use:

   reportTopSalaries 2
+-----+-----+-----------------+------+
|ID   |DEPT |NAME             |SALARY|
+-----+-----+-----------------+------+
|1013 |AB   |de Bont C.A.     |65000 |
|1203 |AB   |Anders H.        |50000 |
+-----+-----+-----------------+------+
|100  |KA   |Ezelbips P.J.    |52000 |
|101  |KA   |'t Woud B.       |45000 |
+-----+-----+-----------------+------+
|1199 |CC   |Uitlaat G.A.S.   |44500 |
|1011 |CC   |de Goeij J.      |39000 |
+-----+-----+-----------------+------+
|1111 |CD   |Telmans R.M.     |55500 |
|1026 |CD   |Janszen H.P.     |41000 |
+-----+-----+-----------------+------+

JavaScript

<lang javascript>var data = [

   {name: "Tyler Bennett",   id: "E10297", salary: 32000, dept: "D101"},
   {name: "John Rappl",      id: "E21437", salary: 47000, dept: "D050"},
   {name: "George Woltman",  id: "E00127", salary: 53500, dept: "D101"},
   {name: "Adam Smith",      id: "E63535", salary: 18000, dept: "D202"},
   {name: "Claire Buckman",  id: "E39876", salary: 27800, dept: "D202"},
   {name: "David McClellan", id: "E04242", salary: 41500, dept: "D101"},
   {name: "Rich Holcomb",    id: "E01234", salary: 49500, dept: "D202"},
   {name: "Nathan Adams",    id: "E41298", salary: 21900, dept: "D050"},
   {name: "Richard Potter",  id: "E43128", salary: 15900, dept: "D101"},
   {name: "David Motsinger", id: "E27002", salary: 19250, dept: "D202"},
   {name: "Tim Sampair",     id: "E03033", salary: 27000, dept: "D101"},
   {name: "Kim Arlich",      id: "E10001", salary: 57000, dept: "D190"},
   {name: "Timothy Grove",   id: "E16398", salary: 29900, dept: "D190"},

];

function top_rank(n) {

   var by_dept = group_by_dept(data);
   for (var dept in by_dept) {
       output(dept);
       for (var i = 0; i < n && i < by_dept[dept].length; i++) {
           var emp = by_dept[dept][i];
           output(emp.name + ", id=" + emp.id + ", salary=" + emp.salary);
       }
       output("");
   }

}

// group by dept, and sort by balary function group_by_dept(data) {

   var by_dept = {};
   for (var idx in data)  {
       var dept = data[idx].dept;
       if ( ! has_property(by_dept, dept)) {
           by_dept[dept] = new Array();
       }
       by_dept[dept].push(data[idx]);
   }
   for (var dept in by_dept) {
       // numeric sort
       by_dept[dept].sort(function (a,b){return a.salary - b.salary}).reverse();
   }
   return by_dept;

}

function has_property(obj, propname) {

   return typeof(obj[propname]) == "undefined" ? false : true;

}

function output(str) {

   try {
       WScript.Echo(str);  // WSH
   } catch(err) {
       print(str);  // Rhino
   }

}

top_rank(3);</lang>

outputs:

D101
George Woltman, id=E00127, salary=53500
David McClellan, id=E04242, salary=41500
Tyler Bennett, id=E10297, salary=32000

D050
John Rappl, id=E21437, salary=47000
Nathan Adams, id=E41298, salary=21900

D202
Rich Holcomb, id=E01234, salary=49500
Claire Buckman, id=E39876, salary=27800
David Motsinger, id=E27002, salary=19250

D190
Kim Arlich, id=E10001, salary=57000
Timothy Grove, id=E16398, salary=29900

OCaml

<lang ocaml>open StdLabels

let to_string (name,_,s,_) = (Printf.sprintf "%s (%d)" name s)

let take n li =

 let rec aux i acc = function
 | _ when i >= n -> (List.rev acc)
 | [] -> (List.rev acc)
 | x::xs -> aux (succ i) (x::acc) xs
 in
 aux 0 [] li ;;

let toprank data n =

 let len = List.length data in
 let h = Hashtbl.create len in
 List.iter data ~f:(fun ((_,_,_,dep) as employee) ->
   Hashtbl.add h dep employee);
 let deps =
   List.fold_left data ~init:[] ~f:
     (fun ac (_,_,_,v) -> if List.mem v ac then ac else v::ac) in
 let f dep =
   Printf.printf "Department: %s\n " dep;
   let l = Hashtbl.find_all h dep in
   let l2 = List.sort (fun (_,_,v1,_) (_,_,v2,_) -> compare v2 v1) l in
   let l3 = (take n l2) in
   print_endline(String.concat ", " (List.map to_string l3));
   print_newline()
 in
 List.iter f deps;

let data = [

 "Tyler Bennett",   "E10297",  32000,  "D101";   
 "John Rappl",      "E21437",  47000,  "D050";   
 "George Woltman",  "E00127",  53500,  "D101";   
 "Adam Smith",      "E63535",  18000,  "D202";   
 "Claire Buckman",  "E39876",  27800,  "D202";   
 "David McClellan", "E04242",  41500,  "D101";   
 "Rich Holcomb",    "E01234",  49500,  "D202";   
 "Nathan Adams",    "E41298",  21900,  "D050";   
 "Richard Potter",  "E43128",  15900,  "D101";   
 "David Motsinger", "E27002",  19250,  "D202";   
 "Tim Sampair",     "E03033",  27000,  "D101";   
 "Kim Arlich",      "E10001",  57000,  "D190";   
 "Timothy Grove",   "E16398",  29900,  "D190";   

]

let () =

 toprank data 3;
</lang>

outputs:

Department: D190
 Kim Arlich (57000), Timothy Grove (29900)

Department: D202
 Rich Holcomb (49500), Claire Buckman (27800), David Motsinger (19250)

Department: D050
 John Rappl (47000), Nathan Adams (21900)

Department: D101
 George Woltman (53500), David McClellan (41500), Tyler Bennett (32000)

Perl

<lang perl>sub zip

  {my @a = @{shift()};
   my @b = @{shift()};
   my @l;
   push @l, shift @a, shift @b while @a and @b;
   return @l;}

sub uniq

  {my %h;
   return grep {not $h{$_}++} @_;}

my @data =

   map {{ zip [qw(name id salary dept)], [split ','] }}
   split "\n",
   <<'EOF';

Tyler Bennett,E10297,32000,D101 John Rappl,E21437,47000,D050 George Woltman,E00127,53500,D101 Adam Smith,E63535,18000,D202 Claire Buckman,E39876,27800,D202 David McClellan,E04242,41500,D101 Rich Holcomb,E01234,49500,D202 Nathan Adams,E41298,21900,D050 Richard Potter,E43128,15900,D101 David Motsinger,E27002,19250,D202 Tim Sampair,E03033,27000,D101 Kim Arlich,E10001,57000,D190 Timothy Grove,E16398,29900,D190 EOF

@ARGV or die "Please provide a value for N.\n"; my $N = shift;

foreach my $d (sort {$a cmp $b} uniq map {$_->{dept}} @data)

  {print "$d\n";
   my @es =
       sort {$b->{salary} <=> $a->{salary}}
       grep {$_->{dept} eq $d}
       @data;
   foreach (1 .. $N)
      {@es or last;
       my $e = shift @es;
       printf "%-15s | %-6s | %5d\n", @{$e}{qw(name id salary)};}
   print "\n";}</lang>

PowerShell

<lang powershell>function New-Employee ($Name, $ID, $Salary, $Department) {

   New-Object PSObject `
       | Add-Member -PassThru NoteProperty EmployeeName $Name `
       | Add-Member -PassThru NoteProperty EmployeeID $ID `
       | Add-Member -PassThru NoteProperty Salary $Salary `
       | Add-Member -PassThru NoteProperty Department $Department

}

$data = (New-Employee 'Tyler Bennett' E10297 32000 D101),

       (New-Employee 'John Rappl'       E21437  47000  D050),
       (New-Employee 'George Woltman'   E00127  53500  D101),
       (New-Employee 'Adam Smith'       E63535  18000  D202),
       (New-Employee 'Claire Buckman'   E39876  27800  D202),
       (New-Employee 'David McClellan'  E04242  41500  D101),
       (New-Employee 'Rich Holcomb'     E01234  49500  D202),
       (New-Employee 'Nathan Adams'     E41298  21900  D050),
       (New-Employee 'Richard Potter'   E43128  15900  D101),
       (New-Employee 'David Motsinger'  E27002  19250  D202),
       (New-Employee 'Tim Sampair'      E03033  27000  D101),
       (New-Employee 'Kim Arlich'       E10001  57000  D190),
       (New-Employee 'Timothy Grove'    E16398  29900  D190)

function Get-TopRank ($n) {

   $data `
       | Group-Object Department `
       | ForEach-Object {
             $_.Group `
                 | Sort-Object Salary -Descending `
                 | Select-Object -First $n
         } `
       | Format-Table -GroupBy Department

}</lang> Output:

PS> Get-TopRank 2

   Department: D101

EmployeeName         EmployeeID    Salary Department
------------         ----------    ------ ----------
George Woltman       E00127         53500 D101
David McClellan      E04242         41500 D101

   Department: D050

EmployeeName         EmployeeID    Salary Department
------------         ----------    ------ ----------
John Rappl           E21437         47000 D050
Nathan Adams         E41298         21900 D050

   Department: D202

EmployeeName         EmployeeID    Salary Department
------------         ----------    ------ ----------
Rich Holcomb         E01234         49500 D202
Claire Buckman       E39876         27800 D202

   Department: D190

EmployeeName         EmployeeID    Salary Department
------------         ----------    ------ ----------
Kim Arlich           E10001         57000 D190
Timothy Grove        E16398         29900 D190

Python

<lang python>from collections import defaultdict from heapq import nlargest

data = [('Employee Name', 'Employee ID', 'Salary', 'Department'),

       ('Tyler Bennett', 'E10297', 32000, 'D101'),
       ('John Rappl', 'E21437', 47000, 'D050'),
       ('George Woltman', 'E00127', 53500, 'D101'),
       ('Adam Smith', 'E63535', 18000, 'D202'),
       ('Claire Buckman', 'E39876', 27800, 'D202'),
       ('David McClellan', 'E04242', 41500, 'D101'),
       ('Rich Holcomb', 'E01234', 49500, 'D202'),
       ('Nathan Adams', 'E41298', 21900, 'D050'),
       ('Richard Potter', 'E43128', 15900, 'D101'),
       ('David Motsinger', 'E27002', 19250, 'D202'),
       ('Tim Sampair', 'E03033', 27000, 'D101'),
       ('Kim Arlich', 'E10001', 57000, 'D190'),
       ('Timothy Grove', 'E16398', 29900, 'D190')]

departments = defaultdict(list) for rec in data[1:]:

   departments[rec[-1]].append(rec)

N = 3 format = "%-15s " * len(data[0]) for department, recs in departments.iteritems():

   print "Department", department
   print " ", format % data[0]
   for rec in nlargest(N, recs, key=lambda rec: rec[-2]):
       print " ", format % rec
   print</lang>

Output:

Department D101
  Employee Name   Employee ID     Salary          Department      
  George Woltman  E00127          53500           D101            
  David McClellan E04242          41500           D101            
  Tyler Bennett   E10297          32000           D101            

Department D202
  Employee Name   Employee ID     Salary          Department      
  Rich Holcomb    E01234          49500           D202            
  Claire Buckman  E39876          27800           D202            
  David Motsinger E27002          19250           D202            

Department D190
  Employee Name   Employee ID     Salary          Department      
  Kim Arlich      E10001          57000           D190            
  Timothy Grove   E16398          29900           D190            

Department D050
  Employee Name   Employee ID     Salary          Department      
  John Rappl      E21437          47000           D050            
  Nathan Adams    E41298          21900           D050            


Alternative Solution

Uses namedtuples for database records, and groupby builtin to group records by Department: <lang python>from collections import namedtuple from itertools import groupby

N = 2

db = Employee Name,Employee ID,Salary,Department Tyler Bennett,E10297,32000,D101 John Rappl,E21437,47000,D050 George Woltman,E00127,53500,D101 Adam Smith,E63535,18000,D202 Claire Buckman,E39876,27800,D202 David McClellan,E04242,41500,D101 Rich Holcomb,E01234,49500,D202 Nathan Adams,E41298,21900,D050 Richard Potter,E43128,15900,D101 David Motsinger,E27002,19250,D202 Tim Sampair,E03033,27000,D101 Kim Arlich,E10001,57000,D190 Timothy Grove,E16398,29900,D190

rows = db.split('\n') DBRecord = namedtuple('DBRecord', rows[0].replace(' ', '_')) records = [ DBRecord(*row.split(',')) for row in rows[1:] ] records.sort(key = lambda record: (record.Department, -float(record.Salary))) print '\n\n'.join('\n '.join([dpt] + [str(g) for g in grp][:N])

                 for dpt, grp in groupby(records,
                                         lambda record: record.Department))</lang>

Sample output

D050
  DBRecord(Employee_Name='John Rappl', Employee_ID='E21437', Salary='47000', Department='D050')
  DBRecord(Employee_Name='Nathan Adams', Employee_ID='E41298', Salary='21900', Department='D050')

D101
  DBRecord(Employee_Name='George Woltman', Employee_ID='E00127', Salary='53500', Department='D101')
  DBRecord(Employee_Name='David McClellan', Employee_ID='E04242', Salary='41500', Department='D101')

D190
  DBRecord(Employee_Name='Kim Arlich', Employee_ID='E10001', Salary='57000', Department='D190')
  DBRecord(Employee_Name='Timothy Grove', Employee_ID='E16398', Salary='29900', Department='D190')

D202
  DBRecord(Employee_Name='Rich Holcomb', Employee_ID='E01234', Salary='49500', Department='D202')
  DBRecord(Employee_Name='Claire Buckman', Employee_ID='E39876', Salary='27800', Department='D202')

R

First, read in the data. <lang R>dfr <- read.csv(tc <- textConnection( "Employee Name,Employee ID,Salary,Department Tyler Bennett,E10297,32000,D101 John Rappl,E21437,47000,D050 George Woltman,E00127,53500,D101 Adam Smith,E63535,18000,D202 Claire Buckman,E39876,27800,D202 David McClellan,E04242,41500,D101 Rich Holcomb,E01234,49500,D202 Nathan Adams,E41298,21900,D050 Richard Potter,E43128,15900,D101 David Motsinger,E27002,19250,D202 Tim Sampair,E03033,27000,D101 Kim Arlich,E10001,57000,D190 Timothy Grove,E16398,29900,D190")); close(tc)</lang> To just return the top salary, it's very simple using tapply. <lang R>with(dfr, tapply(Salary, Department, max))</lang> To return N salaries, we replace max with our own function. <lang R>get.top.N.salaries <- function(N) {

  with(dfr, tapply(Salary, Department, 
     function(x) 
     {
        sort(x); 
        lx <- length(x)
        if(N >= lx) return(x)
        x[-1:(N-lx)]
     }))

}

get.top.N.salaries(3)</lang>

$D050
[1] 47000 21900

$D101
[1] 41500 15900 27000

$D190
[1] 57000 29900

$D202
[1] 27800 49500 19250

To return the whole record for each of the top salaries, a different tack is required. <lang R>get.top.N.salaries2 <- function(N) {

  #Sort data frame by Department, then by Salary
  sorted <- dfr[with(dfr, order(Department, Salary, decreasing=TRUE)),]
  #Split the dataframe up, by Department
  bydept <- split(sorted, sorted$Department)
  #Return the first N values (or all of them
  lapply(bydept, 
     function(x) 
     {
        n <- min(N, nrow(x))
        x[1:n,]
     })

} get.top.N.salaries2(3)</lang>

 $D050
  Employee.Name Employee.ID Salary Department
2    John Rappl      E21437  47000       D050
8  Nathan Adams      E41298  21900       D050

$D101
    Employee.Name Employee.ID Salary Department
3  George Woltman      E00127  53500       D101
6 David McClellan      E04242  41500       D101
1   Tyler Bennett      E10297  32000       D101

$D190
   Employee.Name Employee.ID Salary Department
12    Kim Arlich      E10001  57000       D190
13 Timothy Grove      E16398  29900       D190

$D202
     Employee.Name Employee.ID Salary Department
7     Rich Holcomb      E01234  49500       D202
5   Claire Buckman      E39876  27800       D202
10 David Motsinger      E27002  19250       D202


Ruby

Without much thought to report formatting:

Works with: Ruby version 1.8.7+

<lang ruby>Employee = Struct.new(:name, :employee_id, :salary, :department)

def get_employees

 [
   Employee.new("Tyler Bennett", "E10297", 32000, "D101"), 
   Employee.new("John Rappl", "E21437", 47000, "D050"), 
   Employee.new("George Woltman", "E00127", 53500, "D101"), 
   Employee.new("Adam Smith", "E63535", 18000, "D202"), 
   Employee.new("Claire Buckman", "E39876", 27800, "D202"), 
   Employee.new("David McClellan", "E04242", 41500, "D101"), 
   Employee.new("Rich Holcomb", "E01234", 49500, "D202"), 
   Employee.new("Nathan Adams", "E41298", 21900, "D050"), 
   Employee.new("Richard Potter", "E43128", 15900, "D101"), 
   Employee.new("David Motsinger", "E27002", 19250, "D202"), 
   Employee.new("Tim Sampair", "E03033", 27000, "D101"), 
   Employee.new("Kim Arlich", "E10001", 57000, "D190"), 
   Employee.new("Timothy Grove", "E16398", 29900, "D190"), 
 ]

end

def show_top_salaries_per_group(groups, n)

 groups.each do |dept, emps|
   puts dept
   # sort by salary descending
   emps.sort_by {|emp| -emp.salary}[0,n].each {|e| p e}
   puts
 end

end

groups = get_employees.group_by {|emp| emp.department}

show_top_salaries_per_group(groups,3)</lang>

D101
#<struct Employee name="George Woltman", employee_id="E00127", salary=53500, department="D101">
#<struct Employee name="David McClellan", employee_id="E04242", salary=41500, department="D101">
#<struct Employee name="Tyler Bennett", employee_id="E10297", salary=32000, department="D101">

D190
#<struct Employee name="Kim Arlich", employee_id="E10001", salary=57000, department="D190">
#<struct Employee name="Timothy Grove", employee_id="E16398", salary=29900, department="D190">

D202
#<struct Employee name="Rich Holcomb", employee_id="E01234", salary=49500, department="D202">
#<struct Employee name="Claire Buckman", employee_id="E39876", salary=27800, department="D202">
#<struct Employee name="David Motsinger", employee_id="E27002", salary=19250, department="D202">

D050
#<struct Employee name="John Rappl", employee_id="E21437", salary=47000, department="D050">
#<struct Employee name="Nathan Adams", employee_id="E41298", salary=21900, department="D050">

SMEQL

The following SMEQL example returns the top 6 earners in each department based on this table schema:

<lang smeql>table: Employees


empID dept empName salary</lang>

Source Code:

<lang smeql>srt = orderBy(Employees, (dept, salary), order) top = group(srt, [(dept) dept2, max(order) order]) join(srt, top, a.dept=b.dept2 and b.order - a.order < 6)</lang>

Tcl

Works with: Tcl version 8.5

<lang tcl>package require Tcl 8.5

set text {Tyler Bennett,E10297,32000,D101 John Rappl,E21437,47000,D050 George Woltman,E00127,53500,D101 Adam Smith,E63535,18000,D202 Claire Buckman,E39876,27800,D202 David McClellan,E04242,41500,D101 Rich Holcomb,E01234,49500,D202 Nathan Adams,E41298,21900,D050 Richard Potter,E43128,15900,D101 David Motsinger,E27002,19250,D202 Tim Sampair,E03033,27000,D101 Kim Arlich,E10001,57000,D190 Timothy Grove,E16398,29900,D190}

set data [dict create] foreach line [split $text \n] {

   lassign [split $line ,] name id salary dept
   dict lappend data $dept [list $name $id $salary]

}

proc top_n_salaries {n data} {

   incr n -1
   dict for {dept employees} $data {
       puts "Department $dept"
       foreach emp [lrange [lsort -integer -decreasing -index 2 $employees] 0 $n] {
           puts [format "   %-20s %-8s %8d" {*}$emp]
       }
       puts ""
   }

}

top_n_salaries 3 $data</lang> outputs

Department D101
   George Woltman       E00127      53500
   David McClellan      E04242      41500
   Tyler Bennett        E10297      32000

Department D050
   John Rappl           E21437      47000
   Nathan Adams         E41298      21900

Department D202
   Rich Holcomb         E01234      49500
   Claire Buckman       E39876      27800
   David Motsinger      E27002      19250

Department D190
   Kim Arlich           E10001      57000
   Timothy Grove        E16398      29900

Ursala

The algorithm used by the top function is to lex the data into fields, partition by the last field, sort each partition descending by the second to last, take the first n strings in each partition, and display the list of them in a reasonably understandable form. <lang Ursala>#import std

  1. import nat

data =

-[ Employee Name,Employee ID,Salary,Department Tyler Bennett,E10297,32000,D101 John Rappl,E21437,47000,D050 George Woltman,E00127,53500,D101 Adam Smith,E63535,18000,D202 Claire Buckman,E39876,27800,D202 David McClellan,E04242,41500,D101 Rich Holcomb,E01234,49500,D202 Nathan Adams,E41298,21900,D050 Richard Potter,E43128,15900,D101 David Motsinger,E27002,19250,D202 Tim Sampair,E03033,27000,D101 Kim Arlich,E10001,57000,D190 Timothy Grove,E16398,29900,D190]-

top "n" = @tt sep`,*; mat0+ ^C(~&hz,mat`,*yS)*+ take/*"n"+ *zK2 (nleq+ %np~~)-<x&yzNC

  1. show+

main = top3 data</lang> output:

D190
Kim Arlich,E10001,57000
Timothy Grove,E16398,29900

D101
George Woltman,E00127,53500
David McClellan,E04242,41500
Tyler Bennett,E10297,32000

D202
Rich Holcomb,E01234,49500
Claire Buckman,E39876,27800
David Motsinger,E27002,19250

D050
John Rappl,E21437,47000
Nathan Adams,E41298,21900