Top rank per group

From Rosetta Code
Jump to: navigation, search
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 department, 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:

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

Contents

[edit] Ada

top.adb:

with Ada.Containers.Vectors;
with Ada.Text_IO;
 
procedure Top is
type Departments is (D050, D101, D190, D202);
type Employee_Data is record
Name  : String (1 .. 15);
ID  : String (1 .. 6);
Salary  : Positive;
Department : Departments;
end record;
 
package Employee_Vectors is new Ada.Containers.Vectors
(Element_Type => Employee_Data, Index_Type => Positive);
 
function Compare_Salary (Left, Right : Employee_Data) return Boolean is
begin
return Left.Salary > Right.Salary;
end Compare_Salary;
package Salary_Sort is new Employee_Vectors.Generic_Sorting
("<" => Compare_Salary);
 
function Compare_Department (Left, Right : Employee_Data) return Boolean is
begin
return Left.Department < Right.Department;
end Compare_Department;
package Department_Sort is new Employee_Vectors.Generic_Sorting
("<" => Compare_Department);
 
Example_Data : Employee_Vectors.Vector;
begin
-- fill data
Example_Data.Append (("Tyler Bennett ", "E10297", 32000, D101));
Example_Data.Append (("John Rappl ", "E21437", 47000, D050));
Example_Data.Append (("George Woltman ", "E00127", 53500, D101));
Example_Data.Append (("Adam Smith ", "E63535", 18000, D202));
Example_Data.Append (("Claire Buckman ", "E39876", 27800, D202));
Example_Data.Append (("David McClellan", "E04242", 41500, D101));
Example_Data.Append (("Rich Holcomb ", "E01234", 49500, D202));
Example_Data.Append (("Nathan Adams ", "E41298", 21900, D050));
Example_Data.Append (("Richard Potter ", "E43128", 15900, D101));
Example_Data.Append (("David Motsinger", "E27002", 19250, D202));
Example_Data.Append (("Tim Sampair ", "E03033", 27000, D101));
Example_Data.Append (("Kim Arlich ", "E10001", 57000, D190));
Example_Data.Append (("Timothy Grove ", "E16398", 29900, D190));
-- sort by salary
Salary_Sort.Sort (Example_Data);
-- output each department
for Department in Departments loop
declare
Position : Employee_Vectors.Cursor := Example_Data.First;
Employee : Employee_Data;
begin
Ada.Text_IO.Put_Line ("Department " & Departments'Image (Department));
for I in 1 .. 3 loop
Employee := Employee_Vectors.Element (Position);
while Employee.Department /= Department loop
Position := Employee_Vectors.Next (Position);
Employee := Employee_Vectors.Element (Position);
end loop;
Ada.Text_IO.Put_Line (" " & Employee.Name & " | " &
Employee.ID & " | " &
Positive'Image (Employee.Salary));
Position := Employee_Vectors.Next (Position);
end loop;
exception
when Constraint_Error =>
null;
end;
end loop;
end Top;
Output:
Department D050
   John Rappl      | E21437 |  47000
   Nathan Adams    | E41298 |  21900
Department D101
   George Woltman  | E00127 |  53500
   David McClellan | E04242 |  41500
   Tyler Bennett   | E10297 |  32000
Department D190
   Kim Arlich      | E10001 |  57000
   Timothy Grove   | E16398 |  29900
Department D202
   Rich Holcomb    | E01234 |  49500
   Claire Buckman  | E39876 |  27800
   David Motsinger | E27002 |  19250

[edit] Aime

void
Add_Employee(record employees, text name, text id, integer salary,
text department)
{
l_bill(r_n_list(employees, name), -1, name, id, salary, department);
}
 
void
collect(record top, record employees)
{
text name;
 
r_first(employees, name);
do {
list department, l;
 
l_set(l, r_q_list(employees, name));
 
if (r_key(top, l_q_text(l, 3))) {
integer i, salary;
 
l_set(department, r_q_list(top, l_q_text(l, 3)));
 
salary = l_q_integer(l, 2);
i = 0;
while (i < l_length(department)) {
if (l_q_integer(l_q_list(department, i), 2) < salary) {
break;
}
i += 1;
}
 
l_l_list(department, i, l);
 
if (l_length(department) == N + 1) {
l_delete(department, N);
}
} else {
l_set(department, r_n_list(top, l_q_text(l, 3)));
l_l_list(department, 0, l);
}
} while (r_greater(employees, name, name));
}
 
void
print(record top)
{
text department;
 
r_first(top, department);
do {
list employees;
integer i;
 
o_plan("Department ", department, "\n");
 
l_set(employees, r_q_list(top, department));
i = 0;
while (i < l_length(employees)) {
list l;
 
l_set(l, l_q_list(employees, i));
 
o_form(" ~ | ~ | ~\n", l_q_text(l, 0), l_q_text(l, 1),
l_q_integer(l, 2));
 
i += 1;
}
} while (r_greater(top, department, department));
}
 
integer
main(void)
{
record employees, top;
 
Add_Employee(employees, "Tyler Bennett ", "E10297", 32000, "D101");
Add_Employee(employees, "John Rappl ", "E21437", 47000, "D050");
Add_Employee(employees, "George Woltman ", "E00127", 53500, "D101");
Add_Employee(employees, "Adam Smith ", "E63535", 18000, "D202");
Add_Employee(employees, "Claire Buckman ", "E39876", 27800, "D202");
Add_Employee(employees, "David McClellan", "E04242", 41500, "D101");
Add_Employee(employees, "Rich Holcomb ", "E01234", 49500, "D202");
Add_Employee(employees, "Nathan Adams ", "E41298", 21900, "D050");
Add_Employee(employees, "Richard Potter ", "E43128", 15900, "D101");
Add_Employee(employees, "David Motsinger", "E27002", 19250, "D202");
Add_Employee(employees, "Tim Sampair ", "E03033", 27000, "D101");
Add_Employee(employees, "Kim Arlich ", "E10001", 57000, "D190");
Add_Employee(employees, "Timothy Grove ", "E16398", 29900, "D190");
 
collect(top, employees);
 
print(top);
 
return 0;
}
Run as:
aime rcs/top_rank_per_group c N 5
Output:
Department D050
  John Rappl      | E21437 | 47000
  Nathan Adams    | E41298 | 21900
Department D101
  George Woltman  | E00127 | 53500
  David McClellan | E04242 | 41500
  Tyler Bennett   | E10297 | 32000
  Tim Sampair     | E03033 | 27000
  Richard Potter  | E43128 | 15900
Department D190
  Kim Arlich      | E10001 | 57000
  Timothy Grove   | E16398 | 29900
Department D202
  Rich Holcomb    | E01234 | 49500
  Claire Buckman  | E39876 | 27800
  David Motsinger | E27002 | 19250
  Adam Smith      | E63535 | 18000

[edit] AutoHotkey

Departments = D050, D101, D190, D202 
StringSplit, Department_, Departments, `,, %A_Space%
 
; Employee Name, Employee ID, Salary, Department
Add_Employee("Tyler Bennett ", "E10297", 32000, "D101")
Add_Employee("John Rappl ", "E21437", 47000, "D050")
Add_Employee("George Woltman ", "E00127", 53500, "D101")
Add_Employee("Adam Smith ", "E63535", 18000, "D202")
Add_Employee("Claire Buckman ", "E39876", 27800, "D202")
Add_Employee("David McClellan", "E04242", 41500, "D101")
Add_Employee("Rich Holcomb ", "E01234", 49500, "D202")
Add_Employee("Nathan Adams ", "E41298", 21900, "D050")
Add_Employee("Richard Potter ", "E43128", 15900, "D101")
Add_Employee("David Motsinger", "E27002", 19250, "D202")
Add_Employee("Tim Sampair ", "E03033", 27000, "D101")
Add_Employee("Kim Arlich ", "E10001", 57000, "D190")
Add_Employee("Timothy Grove ", "E16398", 29900, "D190")
 
; display top 3 ranks for each department
Loop, %Department_0% ; all departments
MsgBox,, % "Department: " Department_%A_Index%
, % TopRank(3, Department_%A_Index%)
 
;---------------------------------------------------------------------------
TopRank(N, Department) { ; find the top N salaries in each department
;---------------------------------------------------------------------------
local Collection := Msg := ""
Loop, %m% ; all employees
If (Employee_%A_Index%_Dept = Department)
; collect all the salaries being paid in this department
Collection .= (Collection ? "," : "") Employee_%A_Index%_Salary
Sort, Collection, ND,R
StringSplit, Collection, Collection, `,
Loop, % (N < Collection0) ? N : Collection0 {
Salary := Collection%A_Index%
Loop, %m% ; find the respective employee
If (Employee_%A_Index%_Salary = Salary)
; and put out his/her details
Msg .= Employee_%A_Index%_Name "`t"
. Employee_%A_Index%_ID "`t"
. Employee_%A_Index%_Salary "`t"
. Employee_%A_Index%_Dept "`t`n"
}
Return, Msg
}
 
;---------------------------------------------------------------------------
Add_Employee(Name, ID, Salary, Department) {
;---------------------------------------------------------------------------
global
m++
Employee_%m%_Name := Name
Employee_%m%_ID := ID
Employee_%m%_Salary := Salary
Employee_%m%_Dept := Department
}
Output:
Department:  D050 
--------------------------- 
John Rappl        E21437   47000   D050    
Nathan Adams      E41298   21900   D050

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

Department:  D190 
--------------------------- 
Kim Arlich        E10001   57000   D190    
Timothy Grove     E16398   29900   D190

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

[edit] Bracmat

Bracmat has no dedicated sorting functions. However, when evaluating algebraic expressions, Bracmat sorts factors in products and terms in sums. Moreover, Bracmat simplifies products by, amongst other transformations, collecting exponents of the same base into a single exponent, which is the sum of the collected exponents: a^b*a^ca^(b+c). This built-in behaviour is made use of in this solution.

      (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)
 : ?employees
& ( toprank
= employees N n P "Employee Name" "Employee ID" SalaryDepartment
.  !arg:(?employees.?N)
& 1:?P
& whl
' (  !employees
 : (?"Employee Name",?"Employee ID",?Salary,?Department)
 ?employees
& !Department^(!Salary.!"Employee Name".!"Employee ID")*!P:?P
)
& out$(Top !N "salaries per department.")
& whl
' ( !P:%?Department^?employees*?P
& out$(str$("Department " !Department ":"))
& !N:?n
& whl
' ( !n+-1:~<0:?n
&  !employees
 : ?employees+(?Salary.?"Employee Name".?"Employee ID")
& out
$ (str$(" " !"Employee Name" " (" !"Employee ID" "):" !Salary))
)
)
)
& toprank$(!employees.3)
& ;
Output:
Top 3 salaries per department.
Department D050:
  JohnRappl (E21437):47000
  NathanAdams (E41298):21900
Department D101:
  GeorgeWoltman (E00127):53500
  DavidMcClellan (E04242):41500
  TylerBennett (E10297):32000
Department D190:
  KimArlich (E10001):57000
  TimothyGrove (E16398):29900
Department D202:
  RichHolcomb (E01234):49500
  ClaireBuckman (E39876):27800
  DavidMotsinger (E27002):19250

[edit] C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
typedef struct {
const char *name, *id, *dept;
int sal;
} person;
 
person ppl[] = {
{"Tyler Bennett", "E10297", "D101", 32000},
{"John Rappl", "E21437", "D050", 47000},
{"George Woltman", "E00127", "D101", 53500},
{"Adam Smith", "E63535", "D202", 18000},
{"Claire Buckman", "E39876", "D202", 27800},
{"David McClellan", "E04242", "D101", 41500},
{"Rich Holcomb", "E01234", "D202", 49500},
{"Nathan Adams", "E41298", "D050", 21900},
{"Richard Potter", "E43128", "D101", 15900},
{"David Motsinger", "E27002", "D202", 19250},
{"Tim Sampair", "E03033", "D101", 27000},
{"Kim Arlich", "E10001", "D190", 57000},
{"Timothy Grove", "E16398", "D190", 29900},
};
 
int pcmp(const void *a, const void *b)
{
const person *aa = a, *bb = b;
int x = strcmp(aa->dept, bb->dept);
if (x) return x;
return aa->sal > bb->sal ? -1 : aa->sal < bb->sal;
}
 
#define N sizeof(ppl)/sizeof(person)
void top(int n)
{
int i, rank;
qsort(ppl, N, sizeof(person), pcmp);
 
for (i = rank = 0; i < N; i++) {
if (i && strcmp(ppl[i].dept, ppl[i - 1].dept)) {
rank = 0;
printf("\n");
}
 
if (rank++ < n)
printf("%s %d: %s\n", ppl[i].dept, ppl[i].sal, ppl[i].name);
}
}
 
int main()
{
top(2);
return 0;
}
Output:
D050 47000: John Rappl
D050 21900: Nathan Adams

D101 53500: George Woltman
D101 41500: David McClellan

D190 57000: Kim Arlich
D190 29900: Timothy Grove

D202 49500: Rich Holcomb
D202 27800: Claire Buckman

[edit] C++

#include <string>
#include <set>
#include <list>
#include <map>
#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;
}
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

[edit] C#

using System;
using System.Collections.Generic;
using System.Linq;
 
public class Program
{
class Employee
{
public Employee(string name, string id, int salary, string department)
{
Name = name;
Id = id;
Salary = salary;
Department = department;
}
 
public string Name { get; private set; }
public string Id { get; private set; }
public int Salary { get; private set; }
public string Department { get; private set; }
 
public override string ToString()
{
return String.Format("{0, -25}\t{1}\t{2}", Name, Id, Salary);
}
}
 
private static void Main(string[] args)
{
var employees = new List<Employee>
{
new Employee("Tyler Bennett", "E10297", 32000, "D101"),
new Employee("John Rappl", "E21437", 47000, "D050"),
new Employee("George Woltman", "E21437", 53500, "D101"),
new Employee("Adam Smith", "E21437", 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")
};
 
DisplayTopNPerDepartment(employees, 2);
}
 
static void DisplayTopNPerDepartment(IEnumerable<Employee> employees, int n)
{
var topSalariesByDepartment =
from employee in employees
group employee by employee.Department
into g
select new
{
Department = g.Key,
TopEmployeesBySalary = g.OrderByDescending(e => e.Salary).Take(n)
};
 
foreach (var x in topSalariesByDepartment)
{
Console.WriteLine("Department: " + x.Department);
foreach (var employee in x.TopEmployeesBySalary)
Console.WriteLine(employee);
Console.WriteLine("----------------------------");
}
}
}
Output:
Department: D101
George Woltman           	E21437	53500
David McClellan          	E04242	41500
----------------------------
Department: D050
John Rappl               	E21437	47000
Nathan Adams             	E41298	21900
----------------------------
Department: D202
Rich Holcomb             	E01234	49500
Claire Buckman           	E39876	27800
----------------------------
Department: D190
Kim Arlich               	E10001	57000
Timothy Grove            	E16398	29900
----------------------------

Online demo: http://ideone.com/95TxAV

[edit] Clojure

(use '[clojure.contrib.seq-utils :only (group-by)])
 
(defstruct employee :Name :ID :Salary :Department)
 
(def 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))
(map #(apply (partial struct employee) %))))
 
 
(doseq [[dep emps] (group-by :Department data)]
(println "Department:" dep)
(doseq [e (take 3 (reverse (sort-by :Salary emps)))]
(println e)))
 
Output:
Department: D050
{:Name John Rappl, :ID E21437, :Salary 47000, :Department D050}
{:Name Nathan Adams, :ID E41298, :Salary 21900, :Department D050}
Department: D101
{:Name George Woltman, :ID E00127, :Salary 53500, :Department D101}
{:Name David McClellan, :ID E04242, :Salary 41500, :Department D101}
{:Name Tyler Bennett, :ID E10297, :Salary 32000, :Department D101}
Department: D190
{:Name Kim Arlich, :ID E10001, :Salary 57000, :Department D190}
{:Name Timothy Grove, :ID E16398, :Salary 29900, :Department D190}
Department: D202
{:Name Rich Holcomb, :ID E01234, :Salary 49500, :Department D202}
{:Name Claire Buckman, :ID E39876, :Salary 27800, :Department D202}
{:Name David Motsinger, :ID E27002, :Salary 19250, :Department D202}

[edit] Common 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)))))

Example

> (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 '>)
#<EQL Hash Table{4} 2361A0E7>
 
> (describe *)
 
#<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)))

[edit] D

import std.stdio, std.algorithm, std.conv, std.range;
 
struct Employee {
string name, id;
uint salary;
string department;
}
 
immutable Employee[] data = [
{"Tyler Bennett", "E10297", 32_000, "D101"},
{"John Rappl", "E21437", 47_000, "D050"},
{"George Woltman", "E00127", 53_500, "D101"},
{"Adam Smith", "E63535", 18_000, "D202"},
{"Claire Buckman", "E39876", 27_800, "D202"},
{"David McClellan", "E04242", 41_500, "D101"},
{"Rich Holcomb", "E01234", 49_500, "D202"},
{"Nathan Adams", "E41298", 21_900, "D050"},
{"Richard Potter", "E43128", 15_900, "D101"},
{"David Motsinger", "E27002", 19_250, "D202"},
{"Tim Sampair", "E03033", 27_000, "D101"},
{"Kim Arlich", "E10001", 57_000, "D190"},
{"Timothy Grove", "E16398", 29_900, "D190"}];
 
void main(in string[] args) {
immutable n = (args.length == 2) ? to!int(args[1]) : 3;
 
Employee[][string] departments;
foreach (immutable rec; data)
departments[rec.department] ~= rec;
 
foreach (dep, recs; departments) {
recs.topN!q{a.salary > b.salary}(n);
writefln("Department %s\n  %(%s\n  %)\n", dep, recs.take(n));
}
}
Output:
Department D202
  Employee("Rich Holcomb", "E01234", 49500, "D202")
  Employee("Claire Buckman", "E39876", 27800, "D202")
  Employee("David Motsinger", "E27002", 19250, "D202")

Department D190
  Employee("Kim Arlich", "E10001", 57000, "D190")
  Employee("Timothy Grove", "E16398", 29900, "D190")

Department D101
  Employee("George Woltman", "E00127", 53500, "D101")
  Employee("David McClellan", "E04242", 41500, "D101")
  Employee("Tyler Bennett", "E10297", 32000, "D101")

Department D050
  Employee("John Rappl", "E21437", 47000, "D050")
  Employee("Nathan Adams", "E41298", 21900, "D050")

[edit] 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()
}
}
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.
Output:
? 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

[edit] Erlang

<-module( top_rank_per_group  ).
 
-export( [employees/0, employees_in_department/2, highest_payed/2, task/1] ).
 
-record( employee, {name, id, salery, department} ).
 
employees() ->
[#employee{name="Tyler Bennett", id="E10297", salery=32000, department="D101"},
#employee{name="John Rappl", id="E21437", salery=47000, department="D101"},
#employee{name="George Woltman", id="E00127", salery=53500, department="D050"},
#employee{name="Adam Smith", id="E63535", salery=18000, department="D202"},
#employee{name="Claire Buckman", id="E39876", salery=27800, department="D202"},
#employee{name="David McClellan", id="E04242", salery=41500, department="D101"},
#employee{name="Rich Holcomb", id="E01234", salery=49500, department="D202"},
#employee{name="Nathan Adams", id="E41298", salery=21900, department="D050"},
#employee{name="Richard Potter", id="E43128", salery=15900, department="D101"},
#employee{name="David Motsinger", id="E27002", salery=19250, department="D202"},
#employee{name="Tim Sampair", id="E03033", salery=27000, department="D101"},
#employee{name="Kim Arlich", id="E10001", salery=57000, department="D190"},
#employee{name="Timothy Grove", id="E16398", salery=29900, department="D190"}].
 
employees_in_department( Department, Employees ) -> [X || #employee{department=D}=X <- Employees, D =:= Department].
 
highest_payed( N, Employees ) ->
{Highest, _T} = lists:split( N, lists:reverse(lists:keysort(#employee.salery, Employees)) ),
Highest.
 
task( N ) ->
Employees = employees(),
Departments = lists:usort( [X || #employee{department=X} <- Employees] ),
Employees_in_departments = [employees_in_department(X, Employees) || X <- Departments],
Highest_payed_in_departments = [highest_payed(N, Xs) || Xs <- Employees_in_departments],
[task_write(X) || X <- Highest_payed_in_departments].
 
 
 
task_write( Highest_payeds ) ->
[io:fwrite( "~p ~p: ~p~n", [Department, Salery, Name]) || #employee{department=Department, salery=Salery, name=Name} <- Highest_payeds],
io:nl().
 
Output:
<14> top_rank_per_group:task(2).
"D050" 53500: "George Woltman"
"D050" 21900: "Nathan Adams"

"D101" 47000: "John Rappl"
"D101" 41500: "David McClellan"

"D190" 57000: "Kim Arlich"
"D190" 29900: "Timothy Grove"

"D202" 49500: "Rich Holcomb"
"D202" 27800: "Claire Buckman"

[edit] F#

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 n =
Seq.groupBy (fun (_, _, _, d) -> d) data
|> Seq.map (snd >> Seq.sortBy (fun (_, _, s, _) -> -s) >> Seq.take n)

[edit] Factor

USING: accessors assocs fry io kernel math.parser sequences
sorting ;
IN: top-rank
 
TUPLE: employee name id salary department ;
 
CONSTANT: employees {
T{ employee f "Tyler Bennett" "E10297" 32000 "D101" }
T{ employee f "John Rappl" "E21437" 47000 "D050" }
T{ employee f "George Woltman" "E00127" 53500 "D101" }
T{ employee f "Adam Smith" "E63535" 18000 "D202" }
T{ employee f "Claire Buckman" "E39876" 27800 "D202" }
T{ employee f "David McClellan" "E04242" 41500 "D101" }
T{ employee f "Rich Holcomb" "E01234" 49500 "D202" }
T{ employee f "Nathan Adams" "E41298" 21900 "D050" }
T{ employee f "Richard Potter" "E43128" 15900 "D101" }
T{ employee f "David Motsinger" "E27002" 19250 "D202" }
T{ employee f "Tim Sampair" "E03033" 27000 "D101" }
T{ employee f "Kim Arlich" "E10001" 57000 "D190" }
T{ employee f "Timothy Grove" "E16398" 29900 "D190" }
}
 
: group-by ( seq quot -- hash )
H{ } clone [ '[ dup @ _ push-at ] each ] keep ; inline
 
: prepare-departments ( seq -- departments )
[ department>> ] group-by
[ [ salary>> ] inv-sort-with ] assoc-map ;
 
: first-n-each ( seq n quot -- )
[ short head-slice ] dip each ; inline
 
: main ( -- )
employees prepare-departments [
[ "Department " write write ":" print ] dip
3 [
[ id>> write " $" write ]
[ salary>> number>string write " " write ]
[ name>> print ] tri
] first-n-each
nl
] assoc-each ;
Output:
<pre>
Department D101:
E00127  $53500  George Woltman
E04242  $41500  David McClellan
E10297  $32000  Tyler Bennett

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

Department D190:
E10001  $57000  Kim Arlich
E16398  $29900  Timothy Grove

Department D050:
E21437  $47000  John Rappl
E41298  $21900  Nathan Adams

[edit] FunL

data Employee( name, id, salary, dept )
 
employees = [
Employee( 'Tyler Bennett', 'E10297', 32000, 'D101' ),
Employee( 'John Rappl', 'E21437', 47000, 'D050' ),
Employee( 'George Woltman', 'E00127', 53500, 'D101' ),
Employee( 'Adam Smith', 'E63535', 18000, 'D202' ),
Employee( 'Claire Buckman', 'E39876', 27800, 'D202' ),
Employee( 'David McClellan', 'E04242', 41500, 'D101' ),
Employee( 'Rich Holcomb', 'E01234', 49500, 'D202' ),
Employee( 'Nathan Adams', 'E41298', 21900, 'D050' ),
Employee( 'Richard Potter', 'E43128', 15900, 'D101' ),
Employee( 'David Motsinger', 'E27002', 19250, 'D202' ),
Employee( 'Tim Sampair', 'E03033', 27000, 'D101' ),
Employee( 'Kim Arlich', 'E10001', 57000, 'D190' ),
Employee( 'Timothy Grove', 'E16398', 29900, 'D190' )
]
 
N = 2
 
for (dept, empl) <- employees.groupBy( e -> e.dept ).>toList().sortWith( (<) )
println( dept )
 
for e <- empl.sortWith( (a, b) -> a.salary > b.salary ).take( N )
printf( "  %-16s  %6s  %7d\n", e.name, e.id, e.salary )
 
println()
Output:
D050
    John Rappl        E21437    47000
    Nathan Adams      E41298    21900

D101
    George Woltman    E00127    53500
    David McClellan   E04242    41500

D190
    Kim Arlich        E10001    57000
    Timothy Grove     E16398    29900

D202
    Rich Holcomb      E01234    49500
    Claire Buckman    E39876    27800

[edit] Go

Handling the possibility of ties:

package main
 
import (
"fmt"
"sort"
)
 
// language-native data description
type employee struct {
name, id string
salary int
dept string
}
 
var data = []employee{
{"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"},
}
 
// n provided as first parameter of function topN
func main() {
topN(3, data).print()
 
}
 
// return type of function topN
type result struct {
n int
dlist [][][]int // dimensions are department,salary,index into data
}
 
func (r result) print() {
fmt.Println("Top", r.n, "salaries per department")
printEs := func(es []int) {
for _, ei := range es {
fmt.Printf("  %s %16s %7d\n",
data[ei].id, data[ei].name, data[ei].salary)
}
}
for _, slist := range r.dlist {
fmt.Println(data[slist[0][0]].dept)
nRemaining := r.n
sLast := len(slist) - 1
for si := 0; si < sLast; si++ {
printEs(slist[si])
nRemaining -= len(slist[si])
}
if len(slist[sLast]) > nRemaining {
fmt.Println(" * * * * * * * tie * * * * * * *")
}
printEs(slist[sLast])
}
}
 
func topN(n int, data []employee) *result {
// summarize: construct map with unique deptartment names,
// unique salaries under each of those,
// and then a list of employees under each of those,
// with employees identified by index into data.
salsum := make(map[string]map[int][]int)
for i, e := range data {
dsum, ok := salsum[e.dept]
if !ok {
dsum = make(map[int][]int)
salsum[e.dept] = dsum
}
dsum[e.salary] = append(dsum[e.salary], i)
}
 
// begin constructing result array.
// top level is list of departments.
dlist := make([][][]int, len(salsum))
 
// sort departments for readability
deptSort := make([]string, len(salsum))
i := 0
for dept := range salsum {
deptSort[i] = dept
i++
}
sort.Strings(deptSort)
 
for di, dept := range deptSort {
dsum := salsum[dept]
// sort salaries for selection (and readability)
salSort := make([]int, len(dsum))
i := 0
for s := range dsum {
salSort[i] = s
i++
}
sort.Ints(salSort)
nRemaining := n
// construct level 2, unique salaries
var slist [][]int
// iterate from end so as to select highest
for si := len(salSort) - 1; si >= 0; si-- {
ssum := dsum[salSort[si]]
// construct level 3, indexes into data
elist := make([]int, len(ssum))
slist = append(slist, elist)
copy(elist, ssum)
nRemaining -= len(ssum)
if nRemaining <= 0 {
break
}
}
dlist[di] = slist
}
return &result{n, dlist}
}
Output:
Top 3 salaries per department
D050
    E21437       John Rappl   47000
    E41298     Nathan Adams   21900
D101
    E00127   George Woltman   53500
    E04242  David McClellan   41500
    E10297    Tyler Bennett   32000
D190
    E10001       Kim Arlich   57000
    E16398    Timothy Grove   29900
D202
    E01234     Rich Holcomb   49500
    E39876   Claire Buckman   27800
    E27002  David Motsinger   19250
Output with additional data demonstrating ties:
Top 3 salaries per department
D050
    E21437       John Rappl   47000
    E41298     Nathan Adams   21900
D101
    E00127   George Woltman   53500
    E00128   George Waltman   53500
    E04242  David McClellan   41500
D190
    E10001       Kim Arlich   57000
    * * * * * * * tie * * * * * * *
    E16398    Timothy Grove   29900
    E16399    Timothy Grave   29900
    E16400    Timothy Grive   29900
D202
    E01234     Rich Holcomb   49500
    E39876   Claire Buckman   27800
    E27002  David Motsinger   19250

[edit] Groovy

def displayRank(employees, number) {
employees.groupBy { it.Department }.sort().each { department, staff ->
println "Department $department"
println " Name ID Salary"
staff.sort { e1, e2 -> e2.Salary <=> e1.Salary }
staff[0..<Math.min(number, staff.size())].each { e ->
println " ${e.Name.padRight(20)}${e.ID}${sprintf('%8d', e.Salary)}"
}
println()
}
}
 
def employees = [
[Name: 'Tyler Bennett', ID: 'E10297', Salary: 32000, Department: 'D101'],
[Name: 'John Rappl', ID: 'E21437', Salary: 47000, Department: 'D050'],
[Name: 'George Woltman', ID: 'E00127', Salary: 53500, Department: 'D101'],
[Name: 'Adam Smith', ID: 'E63535', Salary: 18000, Department: 'D202'],
[Name: 'Claire Buckman', ID: 'E39876', Salary: 27800, Department: 'D202'],
[Name: 'David McClellan', ID: 'E04242', Salary: 41500, Department: 'D101'],
[Name: 'Rich Holcomb', ID: 'E01234', Salary: 49500, Department: 'D202'],
[Name: 'Nathan Adams', ID: 'E41298', Salary: 21900, Department: 'D050'],
[Name: 'Richard Potter', ID: 'E43128', Salary: 15900, Department: 'D101'],
[Name: 'David Motsinger', ID: 'E27002', Salary: 19250, Department: 'D202'],
[Name: 'Tim Sampair', ID: 'E03033', Salary: 27000, Department: 'D101'],
[Name: 'Kim Arlich', ID: 'E10001', Salary: 57000, Department: 'D190'],
[Name: 'Timothy Grove', ID: 'E16398', Salary: 29900, Department: 'D190']
]
displayRank(employees, 3)
Output:
Department D050
    Name                ID      Salary
    John Rappl          E21437   47000
    Nathan Adams        E41298   21900

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

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

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

[edit] 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
Output:
*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

[edit] HicEst

CHARACTER source="Test.txt", outP='Top_rank.txt', fmt='A20,A8,i6,2x,A10'
CHARACTER name*20, employee_ID*10, department*10, temp*10
REAL :: idx(1), N_top_salaries=3
 
! Open the source with 4 "," separated columns, skip line 1:
OPEN(FIle=source, Format='SL=1;4,;', LENgth=L)
ALLOCATE(idx, L)
 
! Sort salary column 3 descending, then department column 4, store in idx:
SORT(FIle=source, Column=3, Descending=1, Column=4, Index=idx)
 
! Display a spreadsheet-like scrolling dialog of the presorted source:
DLG(Text=idx, Text=source, Format=fmt, Y)
 
! Output the first N top salaries of each department_
OPEN(FIle=outP)
DO i = 1, L
rank = rank + 1
READ(FIle=source, Row=idx(i)) name, employee_ID, salary, department
IF(temp /= department) THEN
rank = 1
WRITE(FIle=outP)
temp = department
ENDIF
IF(rank <= N_top_salaries) THEN
WRITE(FIle=outP, Format=fmt) name, employee_ID, salary, department
ENDIF
ENDDO
 
END
Output:
John Rappl          E21437   47000  D050
Nathan Adams        E41298   21900  D050

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

Kim Arlich          E10001   57000  D190
Timothy Grove       E16398   29900  D190

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

Note: In place of writing the sorted result to file outP, DLG allows the direct export of the formatted and sorted result. Use of the CLUSter= and Groups= options of SORT performs a variance controlled cluster sort of up to 8 columns instead of the department step sort in this example.

[edit] Icon and Unicon

record Employee(name,id,salary,dept)
 
procedure getEmployees ()
employees := [
Employee("Tyler Bennett","E10297",32000,"D101"),
Employee("John Rappl","E21437",47000,"D050"),
Employee("George Woltman","E00127",53500,"D101"),
Employee("Adam Smith","E63535",18000,"D202"),
Employee("Claire Buckman","E39876",27800,"D202"),
Employee("David McClellan","E04242",41500,"D101"),
Employee("Rich Holcomb","E01234",49500,"D202"),
Employee("Nathan Adams","E41298",21900,"D050"),
Employee("Richard Potter","E43128",15900,"D101"),
Employee("David Motsinger","E27002",19250,"D202"),
Employee("Tim Sampair","E03033",27000,"D101"),
Employee("Kim Arlich","E10001",57000,"D190"),
Employee("Timothy Grove","E16398",29900,"D190")
]
return employees
end
 
procedure show_employee (employee)
every writes (!employee || " ")
write ()
end
 
procedure main (args)
N := integer(args[1]) # N set from command line
employees := getEmployees ()
groups := set()
every employee := !employees do insert(groups, employee.dept)
 
every group := !groups do {
write ("== Top " || N || " in group " || group)
employeesInGroup := []
every employee := !employees do {
if employee.dept == group then put(employeesInGroup, employee)
}
# sort by third field and reverse, so highest salary comes first
employeesInGroup := reverse(sortf(employeesInGroup, 3))
# show the first N records, up to the end of the list
every show_employee (!employeesInGroup[1:(1+min(N,*employeesInGroup))])
}
end
Output:
$ ./top-rank-by-group 2
== Top 2 in group D101
George Woltman E00127 53500 D101 
David McClellan E04242 41500 D101 
== Top 2 in group D202
Rich Holcomb E01234 49500 D202 
Claire Buckman E39876 27800 D202 
== Top 2 in group D050
John Rappl E21437 47000 D050 
Nathan Adams E41298 21900 D050 
== Top 2 in group D190
Kim Arlich E10001 57000 D190 
Timothy Grove E16398 29900 D190 

$ ./top-rank-by-group 4
== Top 4 in group D101
George Woltman E00127 53500 D101 
David McClellan E04242 41500 D101 
Tyler Bennett E10297 32000 D101 
Tim Sampair E03033 27000 D101 
== Top 4 in group D202
Rich Holcomb E01234 49500 D202 
Claire Buckman E39876 27800 D202 
David Motsinger E27002 19250 D202 
Adam Smith E63535 18000 D202 
== Top 4 in group D050
John Rappl E21437 47000 D050 
Nathan Adams E41298 21900 D050 
== Top 4 in group D190
Kim Arlich E10001 57000 D190 
Timothy Grove E16398 29900 D190

[edit] 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:

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
 +-----+-----+-----------------+------+
 |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:

reportTopSalaries=: 3 :'N , (<@:>"1@:|:@:((y <. #) {. ] \: SALARY)/.~ DEPT) |: <"1&> E'
Output:
    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 |
 +-----+-----+-----------------+------+

[edit] 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 b.salary - a.salary});
}
return by_dept;
}
 
function has_property(obj, propname) {
return typeof(obj[propname]) != "undefined";
}
 
function output(str) {
try {
WScript.Echo(str); // WSH
} catch(err) {
print(str); // Rhino
}
}
 
top_rank(3);
Output:
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

[edit] jq

The task description invites use of the "language-native" data structure, which for jq is JSON, and so the following assumes that the file data.json contains an array of objects, each having as keys the strings on the header line. Thus, the first object in the array looks like this:

  {
"Employee Name": "Tyler Bennett",
"Employee ID": "E10297",
"Salary": "32000",
"Department": "D101"
}

[edit] Program

def top_rank_per_department(n):
group_by(.Department)
| reduce .[] as $dept
([]; ($dept
| map(.Salary)
| sort # from least to most
| .[length - n:] # top n salaries
| reverse) as $max
| ($dept[0] | .Department) as $dept
| . + [ { "Department": $dept, "top_salaries": $max } ] );
 

[edit] Example

With the above program, the top two salaries in each dapartment can be found as shown in the following transcript:
 
$ jq 'top_rank_per_department(2) data.json
[
{
"Department": "D050",
"top_salaries": [
"47000",
"21900"
]
},
{
"Department": "D101",
"top_salaries": [
"53500",
"41500"
]
},
{
"Department": "D190",
"top_salaries": [
"57000",
"29900"
]
},
{
"Department": "D202",
"top_salaries": [
"49500",
"27800"
]
}
]

[edit] Lua

N = 2
 
lst = { { "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" }
}
 
dep = {}
for i = 1, #lst do
if dep[ lst[i][4] ] == nil then
dep[ lst[i][4] ] = {}
dep[ lst[i][4] ][1] = lst[i]
else
dep[ lst[i][4] ][#dep[lst[i][4]]+1] = lst[i]
end
end
 
for i, _ in pairs( dep ) do
table.sort( dep[i], function (a,b) return a[3] > b[3] end )
 
print( "Department:", dep[i][1][4] )
for l = 1, math.min( N, #dep[i] ) do
print( "", dep[i][l][1], dep[i][l][2], dep[i][l][3] )
end
print ""
end
Output:
Department:	D050
	John Rappl	E21437	47000
	Nathan Adams	E41298	21900

Department:	D202
	Rich Holcomb	E01234	49500
	Claire Buckman	E39876	27800

Department:	D190
	Kim Arlich	E10001	57000
	Timothy Grove	E16398	29900

Department:	D101
	George Woltman	E00127	53500
	David McClellan	E04242	41500

[edit] Mathematica

InitialList ={{"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"}};
 
TrimmedList=Map[ If[Length[#]>3,Take[#,3],#]& ,
Map[Reverse[SortBy[#,#[[3]]&]]&,GatherBy[InitialList,Last]]
];
 
Scan[((Print["Department ",#[[1,4]],"\n","Employee","\t","Id","\t","Salary"]&[#])&[#];
(Scan[Print[#[[1]],"\t",#[[2]],"\t",#[[3]]]&,#] )& [#])&,TrimmedList]
Output:
<pre>Department D101
Employee	Id	Salary
George Woltman	E00127	53500
David McClellan	E04242	41500
Tyler Bennett	E10297	32000
Department D050
Employee	Id	Salary
John Rappl	E21437	47000
Nathan Adams	E41298	21900
Department D202
Employee	Id	Salary
Rich Holcomb	E01234	49500
Claire Buckman	E39876	27800
David Motsinger	E27002	19250
Department D190
Employee	Id	Salary
Kim Arlich	E10001	57000
Timothy Grove	E16398	29900

[edit] Nimrod

Translation of: C
import algorithm
 
type Record = tuple[name, id: string, salary: int, department: string]
 
var people: seq[Record] =
@[("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")]
 
proc pcmp(a, b): int =
result = cmp(a.department, b.department)
if result != 0: return
result = cmp(b.salary, a.salary)
 
proc top(n) =
sort(people, pcmp)
 
var rank = 0
for i, p in people:
if i > 0 and p.department != people[i-1].department:
rank = 0
echo ""
 
if rank < n:
echo p.department," ",p.salary," ",p.name
 
inc rank
 
top(2)

Output:

D050 47000 John Rappl
D050 21900 Nathan Adams

D101 53500 George Woltman
D101 41500 David McClellan

D190 57000 Kim Arlich
D190 29900 Timothy Grove

D202 49500 Rich Holcomb
D202 27800 Claire Buckman

[edit] 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;
;;
Output:
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)

--Bbsingapore 10:00, 13 January 2012 (UTC)

[edit] Oz

declare
%% Create a list of employee records.
Data = {Map
[['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]]
 
fun {$ [Name Id Salary Department]}
employee(name:Name id:Id salary:Salary department:Department)
end}
 
fun {TopEarners Employees N}
{Record.map {GroupBy Employees department}
fun {$ Employees}
{List.take
{Sort Employees CompareSalary}
N}
end}
end
 
fun {CompareSalary E1 E2}
E1.salary > E2.salary
end
 
%% Groups the records Xs by the value of feature F and returns
%% the result as a record that maps values of F to list of records.
fun {GroupBy Xs F}
Groups = {Dictionary.new}
in
for X in Xs do
Groups.(X.F) := X|{CondSelect Groups X.F nil}
end
{Dictionary.toRecord unit Groups}
end
in
{Inspect {TopEarners Data 3}}

[edit] PARI/GP

Works with: PARI/GP version 2.4.3 and above
This code uses the select() function, which was added in PARI version 2.4.2. The order of the arguments changed between versions; to use in 2.4.2 change select(function, vector) to select(vector, function).
{V=[["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,V)={
my(dept=vecsort(vector(#V,i,V[i][4]),,8),d,v);
for(i=1,#dept,
d=dept[i];
print(d);
v=select(u->u[4]==d,V);
v=vecsort(v,3,4); \\ Sort by salary (#3) descending (flag 0x4)
for(j=1,min(n,#v),
print("\t",v[j][1],"\t",v[j][2],"\t",v[j][3])
)
);
};
 
top(2,V)

[edit] Pascal

Works with: Free_Pascal
Library: Classes
Library: Math
program TopRankPerGroup(output);
 
uses
Classes, Math;
 
type
TData = record
name: string;
ID: string;
salary: longint;
dept: string
end;
PTData = ^TData;
 
const
data: array [1..13] of TData =
( (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 CompareSalary(Item1, Item2: PTData): longint;
begin
CompareSalary := Item2^.salary - Item1^.salary;
end;
 
var
depts : TStringList;
deptList: Tlist;
number, i, j: integer;
 
begin
write ('Enter the number of ranks: ');
readln (number);
depts := TStringList.Create;
depts.Sorted := true;
depts.Duplicates := dupIgnore;
for i := low(data) to high(data) do
depts.Add(data[i].dept);
 
for i := 0 to depts.Count - 1 do
begin
writeln;
writeln('Department: ', depts.Strings[i]);
deptList := TList.Create;
for j := low(data) to high(data) do
if data[j].dept = depts.Strings[i] then
deptList.Add(@data[j]);
deptList.Sort(TListSortCompare(@CompareSalary));
for j := 0 to min(deptList.count, number) - 1 do
begin
write (PTData(deptList.Items[j])^.name, ', ');
write ('ID: ', PTData(deptList.Items[j])^.ID, ', ');
write ('Salary: ', PTData(deptList.Items[j])^.Salary);
writeln;
end;
deptList.Destroy;
end;
end.
 
Output:
% ./TopRankPerGroup 
Enter the number of ranks: 3

Department: D050
John Rappl, ID: E21437, Salary: 47000
Nathan Adams, ID: E41298, Salary: 21900

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

Department: D190
Kim Arlich, ID: E10001, Salary: 57000
Timothy Grove, ID: E16398, Salary: 29900

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

[edit] 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;
grep {!$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 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";
}

[edit] Perl 6

We use tab-separated fields here from a heredoc; q:to/---/ begins the heredoc. The Z=> operator zips two lists into a list of pairs. In MAIN, the classify method generates pairs where each key is a different department, and each value all the entries in that department. We then sort the pairs and process each department separately. Within each department, we sort on salary (negated to reverse the order). The last statement is essentially a list comprehension that uses a slice subscript with the ^ "up to" operator to take the first N elements of the sorted employee list. The :v modifier returns only valid values. The .<Name> form is a slice hash subscript with literals strings. That in turn is just the subscript form of the <...> ("quote words") form, which is more familar to Perl 5 programmers as qw/.../. We used that form earlier to label the initial data set.

This program also makes heavy use of method calls that start with dot. In Perl 6 this means a method call on the current topic, $_, which is automatically set by any for or map construct that doesn't declare an explicit formal parameter on its closure.

my @data = do for q:to/---/.lines -> $line {
E10297 32000 D101 Tyler Bennett
E21437 47000 D050 John Rappl
E00127 53500 D101 George Woltman
E63535 18000 D202 Adam Smith
E39876 27800 D202 Claire Buckman
E04242 41500 D101 David McClellan
E01234 49500 D202 Rich Holcomb
E41298 21900 D050 Nathan Adams
E43128 15900 D101 Richard Potter
E27002 19250 D202 David Motsinger
E03033 27000 D101 Tim Sampair
E10001 57000 D190 Kim Arlich
E16398 29900 D190 Timothy Grove
---
 
$%( < Id Salary Dept Name >
Z=>
$line.split(/ \t+ /)
)
}
 
sub MAIN(Int $N = 3) {
for @data.classify({ .<Dept> }).sort».value {
my @es = .sort: { -.<Salary> }
say '' if (state $bline)++;
say .< Dept Id Salary Name > for @es[^$N]:v;
}
}
Output:
D050 E21437 47000 John_Rappl
D050 E41298 21900 Nathan_Adams

D101 E00127 53500 George_Woltman
D101 E04242 41500 David_McClellan
D101 E10297 32000 Tyler_Bennett

D190 E10001 57000 Kim_Arlich
D190 E16398 29900 Timothy_Grove

D202 E01234 49500 Rich_Holcomb
D202 E39876 27800 Claire_Buckman
D202 E27002 19250 David_Motsinger

[edit] PHP

$data = Array(
Array("Tyler Bennett","E10297",32000,"D101"),
Array("John Rappl","E21437",47000,"D050"),
Array("George Woltman","E00127",53500,"D101"),
Array("Adam Smith","E63535",18000,"D202"),
Array("Claire Buckman","E39876",27800,"D202"),
Array("David McClellan","E04242",41500,"D101"),
Array("Rich Holcomb","E01234",49500,"D202"),
Array("Nathan Adams","E41298",21900,"D050"),
Array("Richard Potter","E43128",15900,"D101"),
Array("David Motsinger","E27002",19250,"D202"),
Array("Tim Sampair","E03033",27000,"D101"),
Array("Kim Arlich","E10001",57000,"D190"),
Array("Timothy Grove","E16398",29900,"D190")
);
function top_sal($num){
global $data;
$depts = Array();
foreach($data as $key => $arr){
if(!isset($depts[$arr[3]])) $depts[$arr[3]] = Array();
$depts[$arr[3]][] = $key;
}
function topsalsort($a,$b){
global $data;
if ($data[$a][2] == $data[$b][2]) {
return 0;
}
return ($data[$a][2] < $data[$b][2]) ? 1 : -1;
}
foreach ($depts as $key => $val){
usort($depts[$key],"topsalsort");
}
ksort($depts);
echo '<pre>';
foreach ($depts as $key => $val){
echo $key . '<br>';
echo 'Name ID Salary<br>';
$count = 0;
foreach($val as $value){
echo $data[$value][0] . ' ' . $data[$value][1] . ' ' . $data[$value][2] . '<br>';
$count++;
if($count>=$num) break;
}
echo '<br>';
}
echo '</pre>';
}
top_sal(3);
Output:
D050
Name			ID		Salary
John Rappl		E21437		47000
Nathan Adams		E41298		21900

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

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

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

[edit] PicoLisp

# Employee Name, ID, Salary, Department
(de *Employees
("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) )
 
(de topEmployees (N)
(let Fmt (4 -16 -7 7)
(for Dept (by cadddr group *Employees)
(prinl "Department " (cadddr (car Dept)) ":")
(tab Fmt NIL "Name" "ID" "Salary")
(for (I . D) (flip (by caddr sort Dept))
(tab Fmt (pack I ". ") (car D) (cadr D) (caddr D))
(T (= I N)) )
(prinl) ) ) )
 
(topEmployees 3)
Output:
Department D101:
    Name            ID      Salary
 1. George Woltman  E00127   53500
 2. David McClellan E04242   41500
 3. Tyler Bennett   E10297   32000

Department D050:
    Name            ID      Salary
 1. John Rappl      E21437   47000
 2. Nathan Adams    E41298   21900

Department D202:
    Name            ID      Salary
 1. Rich Holcomb    E01234   49500
 2. Claire Buckman  E39876   27800
 3. David Motsinger E27002   19250

Department D190:
    Name            ID      Salary
 1. Kim Arlich      E10001   57000
 2. Timothy Grove   E16398   29900

[edit] PL/I

(subrg, stringrange, stringsize):
rank: procedure options (main); /* 10 November 2013 */
 
declare 1 employee (13),
2 name char (15) varying,
2 ID char (6),
2 salary fixed (5),
2 department char (4);
declare done(hbound(employee)) bit (1);
declare ptr(hbound(employee)) fixed binary;
declare true bit(1) value ('1'b), false bit(1) value ('0'b);
declare dept character (4);
declare text character (80) varying;
declare (i, j, l, k, m, n, p, t) fixed binary;
declare in file input;
 
open file (in) title ('/TOP-RANK.DAT, RECSIZE(80), TYPE(TEXT)' );
 
on endfile (in) go to completed_input;
j = 0;
do forever;
get file (in) edit (text) (L);
j = j + 1;
i = index(text, ',');
name(j) = substr(text, 1, i-1);
k = index(text, ',', i+1);
ID(j) = substr(text, i+1, k-(i+1));
i = k; k = index(text, ',', i+1);
salary(j) = substr(text, i+1, k-(i+1));
department(j) = substr(text, k+1);
end;
 
completed_input:
m = hbound(employee);
put skip list ('How many highest-paid employees do you want?');
get (n);
put skip edit ('Looking for the ', trim(n),
' highest-paid employees in each department') (a);
done = false;
do i = 1 to m;
do j = 1 to m;
if done(j) then iterate;
dept = department(j);
/* done(j) = true; */
leave;
end;
/* Locate all the employees of this department. */
k = 0;
do j = 1 to m;
if ^done(j) & (department(j) = dept) then
do;
k = k + 1;
ptr(k) = j;
done(j) = true;
end;
end;
if k = 0 then leave; /* (No more departments.) */
 
put skip list ('Employees in department ' || dept || ' are:-' );
do j = 1 to k;
put skip list (employee(ptr(j)));
end;
/* We now have k employees in "dept". Now find the maximum n salaries. */
/* ptr points to all of them. */
/* Use a bubble sort to move n values to one end. */
do p = 1 to min(n, k);
do j = 1 to k-1;
if salary(ptr(j)) > salary(ptr(j+1)) then
do;
t = ptr(j+1); ptr(j+1) = ptr(j); ptr(j) = t;
end;
end;
end;
 
/* Having moved the largest n values to the end of our list, */
/* we print them. */
put skip list ('Highest-paid employees in department ' || dept || ':-');
do j = k to k-min(k,n)+1 by -1;
put skip list (employee(ptr(j)) );
end;
end;
put skip list ('FINISHED');
end rank;
Output:
How many highest-paid employees do you want? 

Looking for the 2 highest-paid employees in each department
Employees in department D101 are:- 
Tyler Bennett           E10297                     32000                D101 
George Woltman          E00127                     53500                D101 
David McClellan         E04242                     41500                D101 
Richard Potter          E43128                     15900                D101 
Tim Sampair             E03033                     27000                D101 
Highest-paid employees in department D101:- 
George Woltman          E00127                     53500                D101 
David McClellan         E04242                     41500                D101 
Employees in department D050 are:- 
John Rappl              E21437                     47000                D050 
Nathan Adams            E41298                     21900                D050 
Highest-paid employees in department D050:- 
John Rappl              E21437                     47000                D050 
Nathan Adams            E41298                     21900                D050 
Employees in department D202 are:- 
Adam Smith              E63535                     18000                D202 
Claire Buckman          E39876                     27800                D202 
Rich Holcomb            E01234                     49500                D202 
David Motsinger         E27002                     19250                D202 
Highest-paid employees in department D202:- 
Rich Holcomb            E01234                     49500                D202 
Claire Buckman          E39876                     27800                D202 
Employees in department D190 are:- 
Kim Arlich              E10001                     57000                D190 
Timothy Grove           E16398                     29900                D190 
Highest-paid employees in department D190:- 
Kim Arlich              E10001                     57000                D190 
Timothy Grove           E16398                     29900                D190 
FINISHED

[edit] PL/SQL

CREATE OR REPLACE PROCEDURE "Top rank per group"(TOP_N IN PLS_INTEGER DEFAULT 3) AS
CURSOR CSR_EMP(TOP_N PLS_INTEGER) IS
SELECT CASE LINE
WHEN 10 THEN
'Tot.' || LPAD(POPULATION, 2) || ' Employees in ' || TIE_COUNT ||
' deps.Avg salary:' || TO_CHAR(SALARY, '99990.99')
WHEN 30 THEN
'-'
WHEN 50 THEN
'Department: ' || DEPT_ID || ', pop: ' || POPULATION ||
'. Avg Salary: ' || TO_CHAR(SALARY, '99990.99')
WHEN 70 THEN
LPAD('Employee ID', 14) || LPAD('Employee name', 20) ||
LPAD('Salary', 9) || 'Rank'
WHEN 90 THEN
LPAD('+', 14, '-') || LPAD('+', 20, '-') || LPAD('+', 9, '-') ||
LPAD('+', 4, '-')
ELSE
LPAD(' ', 8) || LPAD(EMP_ID, 6) || LPAD(EMP_NAME, 20) ||
TO_CHAR(SALARY, '99990.99') ||
LPAD(CASE WHEN TIE_COUNT = 1 THEN ' ' ELSE 'T' END || RANK, 4)
END "Top rank per group"
FROM (SELECT 10 LINE
,NULL EMP_ID
,NULL EMP_NAME
,' ' DEPT_ID
,AVG(SALARY) SALARY
,0 RANK
,COUNT(DISTINCT DEPT_ID) TIE_COUNT
,COUNT(*) POPULATION
FROM EMP
UNION ALL
SELECT 30 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 50 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,AVG(SALARY) SALARY
,0 RANK
,0 TIE_COUNT
,COUNT(*) POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 70 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 90 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 110 LINE
,EMP_ID
,EMP_NAME
,DEPT_ID
,SALARY
,(SELECT COUNT(DISTINCT EMP4.SALARY)
FROM EMP EMP4
WHERE EMP4.DEPT_ID = EMP3.DEPT_ID
AND EMP4.SALARY >= EMP3.SALARY) RANK
,(SELECT COUNT(*)
FROM EMP EMP2
WHERE EMP2.DEPT_ID = EMP3.DEPT_ID
AND EMP2.SALARY = EMP3.SALARY) TIE_COUNT
,0 POPULATION
FROM EMP EMP3
WHERE TOP_N >= -- Here is the meat, Correlated subquery
(SELECT COUNT(DISTINCT EMP4.SALARY)
FROM EMP EMP4
WHERE EMP4.DEPT_ID = EMP3.DEPT_ID
AND EMP4.SALARY >= EMP3.SALARY))
ORDER BY DEPT_ID
,LINE
,SALARY DESC
,EMP_ID;
 
V_EMP CSR_EMP%ROWTYPE;
BEGIN
FOR V_EMP IN CSR_EMP(TOP_N)
LOOP
DBMS_OUTPUT.PUT_LINE(v_emp."Top rank per group");
END LOOP;
END;

[edit] 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
}
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

[edit] PureBasic

Structure Employees
Name$
ID$
Salary.i
Department$
EndStructure
 
Procedure displayTopEarners(List MyEmployees.Employees(), n)
Protected filename$ = OpenFileRequester("Top rank per group", "DataFile.txt", "", 0)
If ReadFile(0, filename$)
Protected InData.Employees, txt.s, MaxNameLength
 
While Eof(0) = 0
AddElement(MyEmployees())
txt = ReadString(0)
With MyEmployees()
\Name$ = StringField(txt, 1, ",")
\ID$ = StringField(txt, 2, ",")
\Salary = Val(StringField(txt, 3, ","))
\Department$ = StringField(txt, 4, ",")
If Len(\Name$) > MaxNameLength: MaxNameLength = Len(\Name$): EndIf
EndWith
Wend
CloseFile(0)
Else
MessageRequester("Information", "Couldn't open the file!")
End
EndIf
 
If OpenConsole()
Protected OldDepartment$, count
 
SortStructuredList(MyEmployees(), #PB_Sort_Descending, OffsetOf(Employees\Salary), #PB_Sort_integer)
SortStructuredList(MyEmployees(), #PB_Sort_Ascending, OffsetOf(Employees\Department$), #PB_Sort_String)
ForEach MyEmployees()
With MyEmployees()
If \Department$ <> OldDepartment$
If OldDepartment$ <> ""
PrintN(#CRLF$)
EndIf
OldDepartment$ = \Department$
PrintN("Department " + \Department$ + #CRLF$ + "---------------")
PrintN(LSet("Name", MaxNameLength + 3) + LSet("ID", 7) + LSet("Salary", 7))
count = 0
EndIf
count + 1
If count <= n
PrintN(LSet(\Name$, MaxNameLength + 1) + " " + RSet(\ID$, 7) + " $" + Str(\Salary))
EndIf
EndWith
Next
PrintN(#CRLF$ + #CRLF$ + "Press ENTER to exit"): Input()
EndIf
EndProcedure
 
NewList MyEmployees.Employees()
 
displayTopEarners(MyEmployees(), 3)
Save this as 'DataFile.txt and let the program open this file
 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
Output:
Department D050
---------------
Name              ID     Salary
John Rappl        E21437 $47000
Nathan Adams      E41298 $21900


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


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


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


Press ENTER to exit

[edit] Python

Python 2.7/3.x compatible.

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 sorted(departments.items()):
print ("Department %s" % department)
print (format % data[0])
for rec in nlargest(N, recs, key=lambda rec: rec[-2]):
print (format % rec)
print('')
Output:
Department D050
 Employee Name    Employee ID      Salary           Department      
 John Rappl       E21437           47000            D050            
 Nathan Adams     E41298           21900            D050            

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

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

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


Alternative Solution

Uses namedtuples for database records, and groupby builtin to group records by Department:

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))
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')

[edit] R

First, read in the data.

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)

To just return the top salary, it's very simple using tapply.

with(dfr, tapply(Salary, Department, max))

To return N salaries, we replace max with our own function.

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)
$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.

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)
Output:
 $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

[edit] Racket

#lang racket
 
(struct employee (name id salary dept))
(define employees
(list (employee "Tyler Bennett" "E10297" 32000 "D101")
(employee "John Rappl" "E21437" 47000 "D050")
(employee "George Woltman" "E00127" 53500 "D101")
(employee "Adam Smith" "E63535" 18000 "D202")
(employee "Claire Buckman" "E39876" 27800 "D202")
(employee "David McClellan" "E04242" 41500 "D101")
(employee "Rich Holcomb" "E01234" 49500 "D202")
(employee "Nathan Adams" "E41298" 21900 "D050")
(employee "Richard Potter" "E43128" 15900 "D101")
(employee "David Motsinger" "E27002" 19250 "D202")
(employee "Tim Sampair" "E03033" 27000 "D101")
(employee "Kim Arlich" "E10001" 57000 "D190")
(employee "Timothy Grove" "E16398" 29900 "D190")))
 
(define (top/dept N)
(for/list ([dept (remove-duplicates (map employee-dept employees))])
(define people
(filter (λ(e) (equal? dept (employee-dept e))) employees))
(cons dept (take (sort people > #:key employee-salary) N))))
 
(for ([dept (top/dept 2)])
(printf "Department ~a:\n" (car dept))
(for ([e (cdr dept)])
(printf " $~a: ~a (~a)\n"
(employee-salary e)
(employee-name e)
(employee-id e))))
 
Output:
Department D101:
  $53500: George Woltman (E00127)
  $41500: David McClellan (E04242)
Department D050:
  $47000: John Rappl (E21437)
  $21900: Nathan Adams (E41298)
Department D202:
  $49500: Rich Holcomb (E01234)
  $27800: Claire Buckman (E39876)
Department D190:
  $57000: Kim Arlich (E10001)
  $29900: Timothy Grove (E16398)

[edit] REXX

[edit] version 1

/*REXX program shows top N salaries in each department (internal table).*/
parse arg topN . /*get number for top N salaries. */
if topN=='' then topN=1 /*if none, then assume only 1. */
say 'Finding top' topN 'salaries in each department.'; say
@.= /*employee name, ID, salary, dept.*/
@.1 = "Tyler Bennett,E10297,32000,D101"
@.2 = "John Rappl,E21437,47000,D050"
@.3 = "George Woltman,E00127,53500,D101"
@.4 = "Adam Smith,E63535,18000,D202"
@.5 = "Claire Buckman,E39876,27800,D202"
@.6 = "David McClellan,E04242,41500,D101"
@.7 = "Rich Holcomb,E01234,49500,D202"
@.8 = "Nathan Adams,E41298,21900,D050"
@.9 = "Richard Potter,E43128,15900,D101"
@.10 = "David Motsinger,E27002,19250,D202"
@.11 = "Tim Sampair,E03033,27000,D101"
@.12 = "Kim Arlich,E10001,57000,D190"
@.13 = "Timothy Grove,E16398,29900,D190"
depts=
do j=1 until @.j=='' /*build the database elements. */
parse var @.j name.j ',' id.j "," sal.j ',' dept.j
if wordpos(dept.j,depts)==0 then depts=depts dept.j
end /*j*/
employees=j-1
#d=words(depts)
say employees 'employees,' #d "departments:" depts; say
 
do dep=1 for #d; say /*process each department. */
Xdept=word(depts,dep) /*current dept. being processed. */
do topN; highSal=0 /*process the top N salaries. */
h=0 /*point to the highest paid emp. */
do e=1 for employees /*process each employee in Dept. */
if dept.e\==Xdept | sal.e<highSal then iterate
highSal=sal.e; h=e /*We found a higher salary. */
end /*e*/
 
if h==0 then iterate /*no highest paid this time? */
say 'department: ' dept.h " $"sal.h id.h name.h /*show & tell.*/
dept.h= /*make sure we see employee again*/
end /*do topN*/
end /*dep*/
/*stick a fork in it, we're done.*/

output when the input is: 2

Finding top 2 salaries in each department.

13 employees, 4 departments:  D101 D050 D202 D190

department:  D101  $53500 E00127 George Woltman
department:  D101  $41500 E04242 David McClellan

department:  D050  $47000 E21437 John Rappl
department:  D050  $21900 E41298 Nathan Adams

department:  D202  $49500 E01234 Rich Holcomb
department:  D202  $27800 E39876 Claire Buckman

department:  D190  $57000 E10001 Kim Arlich
department:  D190  $29900 E16398 Timothy Grove

output when the input is: 100

Finding top 100 salaries in each department.

13 employees, 4 departments:  D101 D050 D202 D190

department:  D101  $53500 E00127 George Woltman
department:  D101  $41500 E04242 David McClellan
department:  D101  $32000 E10297 Tyler Bennett
department:  D101  $27000 E03033 Tim Sampair
department:  D101  $15900 E43128 Richard Potter

department:  D050  $47000 E21437 John Rappl
department:  D050  $21900 E41298 Nathan Adams

department:  D202  $49500 E01234 Rich Holcomb
department:  D202  $27800 E39876 Claire Buckman
department:  D202  $19250 E27002 David Motsinger
department:  D202  $18000 E63535 Adam Smith

department:  D190  $57000 E10001 Kim Arlich
department:  D190  $29900 E16398 Timothy Grove

[edit] version 2

/* REXX ---------------------------------------------------------------
* 12.02.2014 Walter Pachl
*--------------------------------------------------------------------*/

Parse Arg topn . /* get number for top N salaries. */
Select
When topn='' Then /* no argument */
topn=1 /* assume only 1. */
When topn='?' Then /* user wants help */
Call help
When datatype(topn)<>'NUM' Then /* Argument is not a number */
Call exit 'Invalid argument ('topn'). Must be a number!'
Otherwise
Nop
End
Parse Value '0 0 0 0' with en dn esal. de. deptl
/*employee name, ID, salary, dept.*/
Call read "Tyler Bennett,E10297,32000,D101"
Call read "George Woltman,E00127,53500,D101"
Call read "John Rappl,E21437,47000,D050"
Call read "Adam Smith,E63535,18000,D202"
Call read "Claire Buckman,E39876,27800,D202"
Call read "David McClellan,E04242,41500,D101"
Call read "Rich Holcomb,E01234,49500,D202"
Call read "Nathan Adams,E41298,21900,D050"
Call read "Richard Potter,E43128,15900,D101"
Call read "David Motsinger,E27002,19250,D202"
Call read "Tim Sampair,E03033,27000,D101"
Call read "Kim Arlich,E10001,57000,D190"
Call read "Timothy Grove,E16398,29900,D190"
Say en 'employees,' dn "departments:" deptl
Do e=1 To en
d=dept.e
Do di=1 To de.d
If esal.d.di<sal.e Then
Leave
End
Do j=de.d To di By -1
j1=j+1
esal.d.j1=esal.d.j
enum.d.j1=enum.d.j
End
esal.d.di=sal.e
enum.d.di=id.e
de.d=de.d+1
End
/*---------------------------------------------------------------------
* Output
*--------------------------------------------------------------------*/

Say ' '
Say 'Showing top' topn 'salaries in each department.'
Say ' '
Do While deptl<>''
Parse Var deptl d deptl
Do i=1 To min(topn,de.d)
id=enum.d.i
Say 'department: 'd' $'esal.d.i id name.id
End
Say ' '
End
Exit
 
read:
en=en+1
Parse Arg name ',' id.en "," sal.en ',' dept.en
If wordpos(dept.en,deptl)=0 Then Do
dn=dn+1
deptl=deptl dept.en
End
z=id.en
name.z=name
Return
 
exit: Say arg(1)
help: Say 'Syntax: rexx topsal [topn]'
Exit

output

13 employees, 4 departments:  D101 D050 D202 D190

Showing top 2 salaries in each department.

department:  D101  $53500 E00127 George Woltman
department:  D101  $41500 E04242 David McClellan

department:  D050  $47000 E21437 John Rappl
department:  D050  $21900 E41298 Nathan Adams

department:  D202  $49500 E01234 Rich Holcomb
department:  D202  $27800 E39876 Claire Buckman

department:  D190  $57000 E10001 Kim Arlich
department:  D190  $29900 E16398 Timothy Grove

[edit] Ruby

Without much thought to report formatting:

Works with: Ruby version 1.8.7+
Employee = Struct.new(:name, :employee_id, :salary, :department)
 
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"),
]
 
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}.first(n).each do |e|
puts "  %-16s %6s %7d" % [e.name, e.employee_id, e.salary]
end
puts
end
end
 
groups = employees.group_by {|emp| emp.department}.sort
 
show_top_salaries_per_group(groups,3)
Output:
D050
    John Rappl       E21437   47000
    Nathan Adams     E41298   21900

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

D190
    Kim Arlich       E10001   57000
    Timothy Grove    E16398   29900

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

[edit] Run BASIC

perSal$ = "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"
 
while word$(perSal$,n+1,chr$(13)) <> "" : n = n + 1 : wend ' get count of employees
dim depSal$(n)
for i = 1 to n
depSal$(i) = word$(perSal$,i,chr$(13))
next i
sw = 1
while sw = 1
sw = 0
for i = 1 to n -1
if word$(depSal$(i),4,",")+word$(depSal$(i),3,",") > word$(depSal$(i+1),4,",")+word$(depSal$(i+1),3,",") then
temp$ = depSal$(i)
depSal$(i) = depSal$(i+1)
depSal$(i+1) = temp$
sw = 1
end if
next i
wend
print "Employee Name";chr$(9);"ID";chr$(9);"Salary"
for i = 1 to n
if dep$ <> word$(depSal$(i),4,",") then
dep$ = word$(depSal$(i),4,",")
print : print"Department:";dep$
end if
print word$(depSal$(i),1,",");chr$(9);word$(depSal$(i),2,",");chr$(9);word$(depSal$(i),3,",")
next i
Output:
Employee Name	ID	Salary

Department:D050
Nathan Adams	E41298	21900

Department:D050;
John Rappl	E21437	47000

Department:D101
Richard Potter	E43128	15900
Tim Sampair	E03033	27000
Tyler Bennett	E10297	32000
David McClellan	E04242	41500
George Woltman	E00127	53500

Department:D190
Timothy Grove	E16398	29900
Kim Arlich	E10001	57000

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

Department:D202;
Adam Smith	E63535	18000

[edit] Scala

[edit] Application idiomatic version

import scala.io.Source
import scala.language.implicitConversions
import scala.language.reflectiveCalls
import scala.collection.immutable.TreeMap
 
object TopRank extends App {
val topN = 3
 
val rawData = """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"
"".stripMargin
 
class Employee(name: String, id: String,
val salary: Int,
val department: String) {
override def toString = s"$id\t$salary\t$name"
}
 
// A TreeMap has sorted keys
val data: TreeMap[String, Seq[TopRank.Employee]] = // TreeMap is a sorted map
TreeMap((Source.fromString(rawData) getLines ()).toSeq // Runtime parsing
.drop(1) // Drop header
.map(_.split(";")) //read fields into list of employees
.map(emp => new Employee(emp(0), emp(1), emp(2).toInt, emp(3)))
.groupBy(_.department).toSeq: _*)
 
implicit def iterableWithAvg[T: Numeric](data: Iterable[T]) = new {
def average[T](ts: Iterable[T])(implicit num: Numeric[T]) = {
num.toDouble(ts.sum) / ts.size
}
def avg = average(data)
}
 
val a = data.flatMap { case (_, emps) => emps.map(_.salary) }.avg
 
println(s"Reporting top $topN salaries in each department.\n")
 
println(s"Total of ${data.foldLeft(0)(_ + _._2.size)} employees in ${data.size} departments")
 
println(f"Average salary: $a%8.2f\n")
 
data.foreach {
case (dep, emps) => println(f"Department: $dep pop: ${emps.size} avg: ${emps.map(_.salary).avg}%8.2f\n"
+ emps.sortBy(-_.salary).take(topN)
.map(_.toString).mkString("\t", "\n\t", ""))
}
}
Output:
Reporting top 3 salaries in each department.

Total of 13 employees in 4 departments
Average salary: 33865,38

Department: D190  pop: 2 avg: 43450,00
	E10001	57000	Kim Arlich
	E16398	29900	Timothy Grove
Department: D050  pop: 2 avg: 34450,00
	E21437	47000	John Rappl
	E41298	21900	Nathan Adams
Department: D101  pop: 5 avg: 33980,00
	E00127	53500	George Woltman
	E04242	41500	David McClellan
	E10297	32000	Tyler Bennett
Department: D202  pop: 4 avg: 28637,50
	E01234	49500	Rich Holcomb
	E39876	27800	Claire Buckman
	E27002	19250	David Motsinger

[edit] using SLICK ORM

Version 1.3.1
import scala.slick.driver.H2Driver.simple._
import scala.slick.lifted.ProvenShape
 
// A Employees table with 4 columns: Employee ID, Employee Name, Department, Salary
class Emp(tag: Tag) extends Table[(String, String, String, Double)](tag, "EMP") {
def id: Column[String] = column[String]("EMP_ID", O.PrimaryKey) // This is the primary key column
def name: Column[String] = column[String]("EMP_NAME", O.NotNull)
def deptId: Column[String] = column[String]("DEPT_ID", O.NotNull)
def salary: Column[Double] = column[Double]("SALARY", O.NotNull)
 
// Every table needs a * projection with the same type as the table's type parameter
def * : ProvenShape[(String, String, String, Double)] = (id, name, deptId, salary)
}
 
// The main application
object TopNrankSLICK extends App {
 
val topN = 3
 
// The query interface for the Emp table
val employees = TableQuery[Emp]
 
// Create a connection (called a "session") to an in-memory H2 database
Database.forURL("jdbc:h2:mem:hello", driver = "org.h2.Driver").withSession {
implicit session =>
 
// Create the schema
employees.ddl.create
 
// Fill the database
val employeesInsertResult: Option[Int] = employees ++= Seq(
("E10297", "Tyler Bennett", "D101", 32000),
("E21437", "John Rappl", "D050", 47000),
("E00127", "George Woltman", "D101", 53500),
("E63535", "Adam Smith", "D202", 18000),
("E39876", "Claire Buckman", "D202", 27800),
("E04242", "David McClellan", "D101", 41500),
("E01234", "Rich Holcomb", "D202", 49500),
("E41298", "Nathan Adams", "D050", 21900),
("E43128", "Richard Potter", "D101", 15900),
("E27002", "David Motsinger", "D202", 19250),
("E03033", "Tim Sampair", "D101", 27000),
("E10001", "Kim Arlich", "D190", 57000),
("E16398", "Timothy Grove", "D190", 29900),
("E16399", "Timothy Grave", "D190", 29900),
("E16400", "Timothy Grive", "D190", 29900))
 
/* Manual SQL / String Interpolation */
// Required import for the sql interpolator
import scala.slick.jdbc.StaticQuery.interpolation
 
// Construct a SQL statement manually with an interpolated value
val plainQuery = // First the bun - formatting SELECT clause
sql"""select case LINE
when 10 then
'Tot.' || LPAD(POPULATION, 2) || ' Employees in ' || TIE_COUNT ||
' deps.Avg salary:' || TO_CHAR(SALARY, '99990.99')
when 30 then
'-'
when 50 then
'Department: ' || DEPT_ID || ', pop: ' || POPULATION ||
'. Avg Salary: ' || TO_CHAR(SALARY, '99990.99')
when 70 then
LPAD('Employee ID', 14) || LPAD('Employee name', 20) ||
LPAD('Salary', 9) || 'Rank'
when 90 then
LPAD('+', 14, '-') || LPAD('+', 20, '-') || LPAD('+', 9, '-') ||
LPAD('+', 4, '-')
else
LPAD(' ', 8) || LPAD(EMP_ID, 6) || LPAD(EMP_NAME, 20) ||
TO_CHAR(SALARY, '99990.99') || LPAD(case when TIE_COUNT = 1 then ' ' else 'T' end || RANK, 4)
end "
Top rank per group"
from (select 10 LINE
,null EMP_ID
,null EMP_NAME
,' ' DEPT_ID
,avg(SALARY) SALARY
,0 RANK
,count(distinct DEPT_ID) TIE_COUNT
,count(*) POPULATION
from EMP
union all
select 30 LINE
,null EMP_ID
,null EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
from EMP
group by DEPT_ID
union all
select 50 LINE
,null EMP_ID
,null EMP_NAME
,DEPT_ID
,avg(SALARY) SALARY
,0 RANK
,0 TIE_COUNT
,count(*) POPULATION
from EMP
group by DEPT_ID
union all
select 70 LINE
,null EMP_ID
,null EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
from EMP
group by DEPT_ID
union all
select 90 LINE
,null EMP_ID
,null EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
from EMP
group by DEPT_ID
union all
select 110 LINE
,EMP_ID
,EMP_NAME
,DEPT_ID
,SALARY
,(select count(distinct EMP4.SALARY)
from EMP EMP4
where EMP4.DEPT_ID = EMP3.DEPT_ID
and EMP4.SALARY >= EMP3.SALARY) RANK
,(select count(*)
from EMP EMP2
where EMP2.DEPT_ID = EMP3.DEPT_ID
and EMP2.SALARY = EMP3.SALARY) TIE_COUNT
,0 POPULATION
from EMP EMP3
where $topN >= -- Here is the meat, Correlated subquery
(select count(distinct EMP4.SALARY)
from EMP EMP4
where EMP4.DEPT_ID = EMP3.DEPT_ID
and EMP4.SALARY >= EMP3.SALARY))
order by DEPT_ID ,LINE ,SALARY desc, EMP_ID"
"".as[String]
 
// Execute the query
plainQuery.foreach(println(_))
} // session
} // TopNrankSLICK
Output:
Tot.15 Employees in 4 deps.Avg salary: 33336.67
-
Department: D050, pop: 2. Avg Salary:  34450.00
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E21437          John Rappl 47000.00   1
        E41298        Nathan Adams 21900.00   2
-
Department: D101, pop: 5. Avg Salary:  33980.00
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E00127      George Woltman 53500.00   1
        E04242     David McClellan 41500.00   2
        E10297       Tyler Bennett 32000.00   3
-
Department: D190, pop: 4. Avg Salary:  36675.00
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E10001          Kim Arlich 57000.00   1
        E16398       Timothy Grove 29900.00  T2
        E16399       Timothy Grave 29900.00  T2
        E16400       Timothy Grive 29900.00  T2
-
Department: D202, pop: 4. Avg Salary:  28637.50
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E01234        Rich Holcomb 49500.00   1
        E39876      Claire Buckman 27800.00   2
        E27002     David Motsinger 19250.00   3

[edit] SMEQL

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

table: Employees
----------------
empID
dept
empName
salary

Source Code:

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)

Note: Since SMEQL is a query language, it has no built-in I/O capability to prompt the user for the threshold number ("6" in this example). However, it would be possible to retrieve the threshold from another table, but would make the example a bit confusing. In practice, an application language would most likely pass the value to it as a parameter.

[edit] SQL

CREATE TABLE EMP
(
EMP_ID varchar2(6 CHAR),
EMP_NAMEvarchar2(20 CHAR),
DEPT_ID varchar2(4 CHAR),
SALARY NUMBER(10,2)
);
 
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E21437','John Rappl','D050',47000);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E10297','Tyler Bennett','D101',32000);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E00127','George Woltman','D101',53500);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E63535','Adam Smith','D202',18000);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E39876','Claire Buckman','D202',27800);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E04242','David McClellan','D101',41500);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E41298','Nathan Adams','D050',21900);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E43128','Richard Potter','D101',15900);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E27002','David Motsinger','D202',19250);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E03033','Tim Sampair','D101',27000);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E10001','Kim Arlich','D190',57000);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E16398','Timothy Grove','D190',29900);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E01234','Rich Holcomb','D202',49500);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E16399','Timothy Grave','D190',29900);
INSERT INTO EMP (EMP_ID, EMP_NAME, DEPT_ID, SALARY)
VALUES ('E16400','Timothy Grive','D190',29900);
COMMIT;
SELECT CASE LINE
WHEN 10 THEN
'Tot.' || LPAD(POPULATION, 2) || ' Employees in ' || TIE_COUNT ||
' deps.Avg salary:' || TO_CHAR(SALARY, '99990.99')
WHEN 30 THEN
'-'
WHEN 50 THEN
'Department: ' || DEPT_ID || ', pop: ' || POPULATION ||
'. Avg Salary: ' || TO_CHAR(SALARY, '99990.99')
WHEN 70 THEN
LPAD('Employee ID', 14) || LPAD('Employee name', 20) ||
LPAD('Salary', 9) || 'Rank'
WHEN 90 THEN
LPAD('+', 14, '-') || LPAD('+', 20, '-') || LPAD('+', 9, '-') ||
LPAD('+', 4, '-')
ELSE
LPAD(' ', 8) || LPAD(EMP_ID, 6) || LPAD(EMP_NAME, 20) ||
TO_CHAR(SALARY, '99990.99') || LPAD(CASE WHEN TIE_COUNT = 1 THEN ' ' ELSE 'T' END || RANK, 4)
END "Top rank per group"
FROM (SELECT 10 LINE
,NULL EMP_ID
,NULL EMP_NAME
,' ' DEPT_ID
,avg(SALARY) SALARY
,0 RANK
,COUNT(DISTINCT DEPT_ID) TIE_COUNT
,COUNT(*) POPULATION
FROM EMP
UNION ALL
SELECT 30 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 50 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,avg(SALARY) SALARY
,0 RANK
,0 TIE_COUNT
,COUNT(*) POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 70 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 90 LINE
,NULL EMP_ID
,NULL EMP_NAME
,DEPT_ID
,0 SALARY
,0 RANK
,0 TIE_COUNT
,0 POPULATION
FROM EMP
GROUP BY DEPT_ID
UNION ALL
SELECT 110 LINE
,EMP_ID
,EMP_NAME
,DEPT_ID
,SALARY
,(SELECT COUNT(DISTINCT EMP4.SALARY)
FROM EMP EMP4
WHERE EMP4.DEPT_ID = EMP3.DEPT_ID
AND EMP4.SALARY >= EMP3.SALARY) RANK
,(SELECT COUNT(*)
FROM EMP EMP2
WHERE EMP2.DEPT_ID = EMP3.DEPT_ID
AND EMP2.SALARY = EMP3.SALARY) TIE_COUNT
,0 POPULATION
FROM EMP EMP3
WHERE $topN >= -- Here is the meat, Correlated subquery
(SELECT COUNT(DISTINCT EMP4.SALARY)
FROM EMP EMP4
WHERE EMP4.DEPT_ID = EMP3.DEPT_ID
AND EMP4.SALARY >= EMP3.SALARY))
ORDER BY DEPT_ID ,LINE ,SALARY DESC, EMP_ID;
Output:
Tot.15 employees in 4 deps.Avg salary: 33336.67
-
Department: D050, pop: 2. Avg salary:  34450.00
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E21437          John Rappl 47000.00   1
        E41298        Nathan Adams 21900.00   2
-
Department: D101, pop: 5. Avg salary:  33980.00
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E00127      George Woltman 53500.00   1
        E04242     David McClellan 41500.00   2
        E10297       Tyler Bennett 32000.00   3
-
Department: D190, pop: 4. Avg salary:  36675.00
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E10001          Kim Arlich 57000.00   1
        E16398       Timothy Grove 29900.00  T2
        E16399       Timothy Grave 29900.00  T2
        E16400       Timothy Grive 29900.00  T2
-
Department: D202, pop: 4. Avg salary:  28637.50
   Employee ID       Employee name   SalaryRank
-------------+-------------------+--------+---+
        E01234        Rich Holcomb 49500.00   1
        E39876      Claire Buckman 27800.00   2
        E27002     David Motsinger 19250.00   3

Note the T2 tie ranking aka ex aequo.

[edit] Tcl

Works with: Tcl version 8.5
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
Output:
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

[edit] TUSCRIPT

$$ MODE TUSCRIPT
MODE DATA
$$ SET dates=*
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
$$ MODE TUSCRIPT
SET nix=SPLIT (dates,":,:",EmployeeName,Employee_ID,Salary,Department)
SET d=MIXED_SORT (department),d=REDUCE(d)
SET index=DIGIT_INDEX(salary), index=REVERSE(index)
SET employeeName=INDEX_SORT (employeeName,index)
SET employee_ID =INDEX_SORT (employee_ID,index)
SET Salary=INDEX_SORT (salary,index)
SET Department=INDEX_SORT (Department,index)
COMPILE
LOOP l=d
PRINT "Department ", l
SET rtable=QUOTES (l)
BUILD R_TABLE pos = rtable
SET id=FILTER_INDEX (department,pos,-)
RELEASE R_TABLE pos
SET en =SELECT (employeeName,#id)
SET ei =SELECT (employee_ID,#id)
SET sal =SELECT (salary,#id)
SET he =CENTER ("employeeName",-16)
SET hi =CENTER ("employee ID",-11)
SET hs =CENTER ("Salary",+10)
SET line=REPEAT ("-",37)
PRINT he,hi,hs
PRINT line
LOOP e=en,i=ei,s=sal
SET e=CENTER (e,-16), i=CENTER (i,-11), s=CENTER (s,+10)
PRINT e,i,s
ENDLOOP
PRINT " "
ENDLOOP
ENDCOMPILE
Output:
Department D050
employeeName    employee ID    Salary
-------------------------------------
John Rappl      E21437          47000
Nathan Adams    E41298          21900

Department D101
employeeName    employee ID    Salary
-------------------------------------
George Woltman  E00127          53500
David McClellan E04242          41500
Tyler Bennett   E10297          32000
Tim Sampair     E03033          27000
Richard Potter  E43128          15900

Department D190
employeeName    employee ID    Salary
-------------------------------------
Kim Arlich      E10001          57000
Timothy Grove   E16398          29900

Department D202
employeeName    employee ID    Salary
-------------------------------------
Rich Holcomb    E01234          49500
Claire Buckman  E39876          27800
David Motsinger E27002          19250
Adam Smith      E63535          18000	 

[edit] TXR

[edit] Template Output Version

This version massages the data in a way that is suitable for generating the output template-wise with an @(output) block.

The data is in a file, exactly as given in the problem. Parameter N is accepted from command line.

@(next :args)
@{n-param}
@(next "top-rank-per-group.dat")
Employee Name,Employee ID,Salary,Department
@(collect :vars (record))
@name,@id,@salary,@dept
@(bind record (@(int-str salary) dept name id))
@(end)
@(bind (dept salary dept2 name id)
@(let* ((n (int-str n-param))
(dept-hash [group-by second record :equal-based])
(dept (hash-keys dept-hash))
(ranked (collect-each ((rec (hash-values dept-hash)))
[apply mapcar list [[sort rec > first] 0..n]])))
(cons dept [apply mapcar list ranked])))
@(output)
@ (repeat)
Department: @dept
@ (repeat)
@{name 15} (@id) $@{salary -6}
@ (end)
@ (end)
@(end)
Output:
Department: D101
  George Woltman  (E00127)  $ 53500
  David McClellan (E04242)  $ 41500
  Tyler Bennett   (E10297)  $ 32000
Department: D202
  Rich Holcomb    (E01234)  $ 49500
  Claire Buckman  (E39876)  $ 27800
  David Motsinger (E27002)  $ 19250
Department: D050
  John Rappl      (E21437)  $ 47000
  Nathan Adams    (E41298)  $ 21900
Department: D190
  Kim Arlich      (E10001)  $ 57000
  Timothy Grove   (E16398)  $ 29900

Breakdown:

Descend into argument list:

@(next :args)

Collect first argument as n-param variable:

@{n-param}

Drill into data file:

@(next "top-rank-per-group.dat")

Match header exactly:

Employee Name,Employee ID,Salary,Department

Now iterate over the data, requiring a variable called record to be bound in each iteration, and suppress all other variables from emerging. In the body of the collect, bind four variables. Then use these four variables to create a four-element list which is bound to the variable record. The int-str function converts the textual variable salary to an integer:

@(collect :vars (record))
@name,@id,@salary,@dept
@(bind record (@(int-str salary) dept name id))
@(end)
 

Next, we bind five variables to the output of some TXR Lisp code, which will return five lists:

@(bind (dept salary dept2 name id)
@(let* ((n (int-str n-param))
(dept-hash [group-by second record :equal-based])
(dept (hash-keys dept-hash))
(ranked (collect-each ((rec (hash-values dept-hash)))
[apply mapcar list [[sort rec > first] 0..n]])))
(cons dept [apply mapcar list ranked])))

This code binds some successive variables. n is an integer conversion of the command line argument. dept-hash is a hash whose keys are department strings, and whose values are lists of records belonging to each respective department (the records collected previously). The hash keys are the departments; these are extracted into a variable called dept for later use. The ranked variable takes the ranking information.

The salary ranking info is obtained by sorting each department's records by descending salary and then taking a 0..n slice of the list.

The "apply mapcar list" is a Lisp pattern for doing a matrix transpose. We use it twice: once within the department over the list of records, and then over the list of lists of records.

The reason for these transpositions is to convert the data into individual nested lists, once for each field. This is the format needed by the TXR @(output) clause:

@(output)
@ (repeat)
Department: @dept
@ (repeat)
@{name 15} (@id) $@{salary -6}
@ (end)
@ (end)
@(end)

Here, all these variables are individual lists. The dept variable is a flat list; one nesting of @(repeat) iterates over it. The other variables are nested lists; a nested repeat drills into these.

[edit] Lisp Output Version

In this version, the Lisp processing block performs the output, so the conversion of records into lists for the template language is omitted, simplifying the code.

The output is identical to the previous version.

@(next :args)
@{n-param}
@(next "top-rank-per-group.dat")
Employee Name,Employee ID,Salary,Department
@(collect :vars (record))
@name,@id,@salary,@dept
@(bind record (@(int-str salary) dept name id))
@(end)
@(do
(let* ((n (int-str n-param))
(dept-hash [group-by second record :equal-based])
(ranked (collect-each ((rec (hash-values dept-hash)))
[[sort rec > first] 0..n])))
(each ((d (hash-keys dept-hash))
(dept-recs ranked))
(put-line `Department: @d`)
(each ((r dept-recs))
(put-line ` @{r[2] 15} (@{r[3]}) $@{r[0] -6}`)))))

[edit] 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.

#import std
#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
 
#show+
 
main = top3 data
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

[edit] zkl

This reads a data file, the contents of which are as given by the task description (minus the header). To protect privacy, identifying information has been stripped from the output (aka laziness enhanced coding).

fcn setAppend(d,[(key,data)]){ d[key]=d.find(key,T).append(data) } //-->(key,(data,data...))
 
fcn topNsalaries(n){
File("data.txt").pump(setAppend.fp(data:=D()),fcn(line){ //-->Dictionary(dept:salaries)
line=line.strip().split(",");
T(line[-1],line[-2]); //-->(dept,salary)
});
dss:=data.pump(List,fcn([(dept,ss)],N){ //-->(dept,(salaries), dept...)
T(dept).append(ss.sort()[-N,*].reverse());
}.fp1(n)).sort(fcn(a,b){a[0]<b[0]});
foreach d,ss in (dss){
"%s: %s".fmt(d,ss.concat(",")).println();
}
}(3);
Output:
D050: 47000,21900
D101: 53500,41500,32000
D190: 57000,29900
D202: 49500,27800,19250
Personal tools
Namespaces

Variants
Actions
Community
Explore
Misc
Toolbox