Top rank per group

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

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


11l

Translation of: Python
V 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’)]

DefaultDict[String, [(String, String, Int, String)]] departments
L(rec) data
   departments[rec[3]].append(rec)

V n = 3
L(department, recs) sorted(departments.items())
   print(‘Department #.’.format(department))
   print(‘ #<15  #<15  #<15  #<15 ’.format(‘Employee Name’, ‘Employee ID’, ‘Salary’, ‘Department’))
   L(rec) sorted(recs, key' rec -> rec[2], reverse' 1B)[0 .< n]
      print(‘ #<15  #<15  #<15  #<15 ’.format(rec[0], rec[1], rec[2], rec[3]))
   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

Action!

DEFINE PTR="CARD"
DEFINE ENTRY_SIZE="8"

TYPE Employee=[
  PTR name,id,dep ;CHAR ARRAY
  CARD salary]

BYTE ARRAY data(200)
BYTE count=[0]

PTR FUNC GetItemAddr(INT index)
  PTR addr

  addr=data+index*ENTRY_SIZE
RETURN (addr)

PROC Append(CHAR ARRAY n,i CARD s CHAR ARRAY d)
  Employee POINTER dst

  dst=GetItemAddr(count)
  dst.name=n
  dst.id=i
  dst.dep=d
  dst.salary=s
  count==+1
RETURN

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

PROC Swap(Employee POINTER e1,e2)
  PTR tmp

  tmp=e1.name e1.name=e2.name e2.name=tmp
  tmp=e1.id e1.id=e2.id e2.id=tmp
  tmp=e1.dep e1.dep=e2.dep e2.dep=tmp
  tmp=e1.salary e1.salary=e2.salary e2.salary=tmp
RETURN

PROC Sort()
  INT i,j,minpos,comp
  Employee POINTER e1,e2

  FOR i=0 TO count-2
  DO
    minpos=i
    FOR j=i+1 TO count-1
    DO
      e1=GetItemAddr(minpos)
      e2=GetItemAddr(j)
      comp=SCompare(e1.dep,e2.dep)
      IF comp>0 OR comp=0 AND e1.salary<e2.salary THEN
        minpos=j
      FI
    OD
    
    IF minpos#i THEN
      e1=GetItemAddr(minpos)
      e2=GetItemAddr(i)
      Swap(e1,e2)
    FI
  OD
RETURN

PROC TopRank(BYTE n)
  BYTE i,c
  CHAR ARRAY d
  Employee POINTER e

  i=0
  WHILE i<count
  DO
    e=GetItemAddr(i)
    IF i=0 OR SCompare(e.dep,d)#0 THEN
      d=e.dep c=0
      IF i>0 THEN PutE() FI
      PrintF("Department %S:%E",d)
      c==+1
      PrintF("  %U %S %S%E",e.salary,e.id,e.name)
    ELSEIF c<n THEN
      c==+1
      PrintF("  %U %S %S%E",e.salary,e.id,e.name)
    FI
    i==+1
  OD
RETURN

PROC Main()
  InitData()
  Sort()
  TopRank(3)
RETURN
Output:

Screenshot from Atari 8-bit computer

Department D050:
  47000 E21437 John Rappl
  21900 E41298 Nathan Adams

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

Department D190:
  57000 E10001 Kim Arlich
  29900 E16398 Timothy Grove

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

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

Aime

Add_Employee(record employees, text name, id, integer salary, text department)
{
    employees[name] = list(name, id, salary, department);
}

collect(record top, employees)
{
    for (, list l in employees) {
        top.v_index(l[3]).v_list(l[2]).link(-1, l);
    }
    for (text department, index x in top) {
        list t;

        x.ucall(l_ucall, 0, l_append, 1, t);
        if (N < ~t.reverse) {
            t.erase(N, -1);
        }
        top[department] = t;
    }
}

print_department(text department, list employees)
{
    o_("Department ", department, "\n");

    for (, list l in employees) {
        o_form("  ~ | ~ | ~\n", l[0], l[1], l[2]);
    }
}

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

    top.wcall(print_department, 0, 1);

    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

ALGOL 68

Using code from the ALGOL 68 sample in the Sort using a custom comparator task.

BEGIN # show the top rank salaries per department                             #
    # NODE to hold the employee data - will be the MODE that is sorted        #
    MODE SITEM = STRUCT( STRING employee name
                       , STRING employee id
                       , INT    salary
                       , STRING department
                       );
    # ---- begin code from the quicksort using a custom comparator task ----- #
    #--- Swap function ---#
    PROC swap = (REF[]SITEM array, INT first, INT second) VOID:
    (
        SITEM temp := array[first];
        array[first] := array[second];
        array[second]:= temp
    );
    #--- Quick sort partition arg function with custom comparision function ---#
    PROC quick = (REF[]SITEM array, INT first, INT last, PROC(SITEM,SITEM)INT compare) VOID:
    (
        INT   smaller := first + 1,  
              larger  := last;
        SITEM pivot   := array[first];
        WHILE smaller <= larger DO
            WHILE compare(array[smaller], pivot) < 0 AND smaller < last DO   
                smaller +:= 1        
            OD;
            WHILE compare( array[larger], pivot) > 0 AND larger > first DO   
                larger  -:= 1       
            OD; 
            IF smaller < larger THEN 
                swap(array, smaller, larger); 
                smaller +:= 1;
                larger  -:= 1
            ELSE
                smaller +:= 1
            FI
        OD;
        swap(array, first, larger);    
        IF first < larger-1 THEN
            quick(array, first, larger-1, compare)
        FI;
        IF last > larger +1 THEN
            quick(array, larger+1, last, compare)
        FI
    );
    #--- Quick sort array function with custom comparison function ---#
    PROC quicksort = (REF[]SITEM array, PROC(SITEM,SITEM)INT compare) VOID:
    (
      IF UPB array > LWB array THEN
        quick(array, LWB array, UPB array, compare)
      FI
    );
    # ==== end   code from the quicksort using a custom comparator task ===== #

    # sort comparison routine - sort ascending department, descending salary  #
    PROC sort by department then salary = ( SITEM a, SITEM b )INT:
         IF   department OF a < department OF b THEN -1
         ELIF department OF a > department OF b THEN  1
         ELIF     salary OF a >     salary OF b THEN -1
         ELIF     salary OF a <     salary OF b THEN  1
         ELSE                                         0
         FI # sort by department then salary # ;

    # returns s blank padded on the right to at least w characters            #
    PROC pad right = ( STRING s, INT w )STRING:
         IF INT len = ( UPB s - LWB s ) + 1; len >= w THEN s ELSE s + ( ( w - len ) * " " ) FI;
    # shows the top n ranked salaries in each department                      #
    PROC show top rank = ( []SITEM data, INT n )VOID:
         BEGIN
            # copy the data and sort it                                       #
            [ LWB data : UPB data ]SITEM sorted data := data;
            quicksort( sorted data, sort by department then salary );
            # show the top salaries per department                            #
            INT d pos := LWB sorted data;
            WHILE d pos <= UPB sorted data DO
                STRING curr department := department OF sorted data[ d pos ];
                print( ( "Department: ", curr department, newline ) );
                INT    e count         := 0;
                WHILE IF e count < n THEN
                          print( ( "  "
                                 , employee id   OF sorted data[ d pos ]
                                 , "  "
                                 , pad right( employee name
                                                 OF sorted data[ d pos ], 24 )
                                 , "  "
                                 , whole( salary OF sorted data[ d pos ], -6 )
                                 , newline
                                 )
                               )
                      FI;
                      e count +:= 1;
                      d pos   +:= 1;
                      IF d pos > UPB sorted data
                      THEN FALSE
                      ELSE curr department = department OF sorted data[ d pos ]
                      FI
                DO SKIP OD;
                print( ( newline ) )
            OD
        END # show top rank # ;

    # employee data                                                           #
    []SITEM 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" )
                        );
    # show the top two salaries by department                                 #
    show top rank( employees, 2 )
END
Output:
Department: D050
  E21437  John Rappl                 47000
  E41298  Nathan Adams               21900

Department: D101
  E00127  George Woltman             53500
  E04242  David McClellan            41500

Department: D190
  E10001  Kim Arlich                 57000
  E16398  Timothy Grove              29900

Department: D202
  E01234  Rich Holcomb               49500
  E39876  Claire Buckman             27800

AppleScript

It looks as if this task's requirements have changed — possibly more than once — since it was first set and named. As at the end of May 2021, it specifies the return of the top N salaries in each department and makes no mention at all of any of the other employee details. This makes it easier to decide how to handle the other thing it doesn't mention: what to return when two or more people in the same department are on the same pay. The interpretation here is "top N discrete salary values" (or of course fewer if a department doesn't have that many).

use AppleScript version "2.3.1" -- Mac OS X 10.9 (Mavericks) or later.
use sorter : script ¬
    "Custom Iterative Ternary Merge Sort" -- <www.macscripter.net/t/timsort-and-nigsort/71383/3>

on topNSalariesPerDepartment(employeeRecords, n)
    set output to {}
    set employeeCount to (count employeeRecords)
    if ((employeeCount > 0) and (n > 0)) then
        -- Sort a copy of the employee record list by department
        -- with descending subsorts on salary.
        copy employeeRecords to employeeRecords
        script comparer
            on isGreater(a, b)
                return ((a's department > b's department) or ¬
                    ((a's department = b's department) and (a's salary < b's salary)))
            end isGreater
        end script
        considering numeric strings
            tell sorter to sort(employeeRecords, 1, employeeCount, {comparer:comparer})
        end considering
        
        -- Initialise the output with data from the first record in the sorted list,
        -- then work through the rest of the list.
        set {department:previousDepartment, salary:previousSalary} to beginning of employeeRecords
        set {mv, topSalaries} to {"-", {previousSalary}}
        set end of output to {department:previousDepartment, salaries:topSalaries}
        repeat with i from 2 to employeeCount
            set {department:thisDepartment, salary:thisSalary} to item i of employeeRecords
            if (thisDepartment = previousDepartment) then
                if ((thisSalary < previousSalary) and ((count topSalaries) < n)) then
                    set end of topSalaries to thisSalary
                    set previousSalary to thisSalary
                end if
            else
                -- First record of the next department.
                -- Pad out the previous department's salary list if it has fewer than n entries.
                repeat (n - (count topSalaries)) times
                    set end of topSalaries to mv
                end repeat
                -- Start a result record for the new department and add it to the output.
                set topSalaries to {thisSalary}
                set end of output to {department:thisDepartment, salaries:topSalaries}
                set previousDepartment to thisDepartment
                set previousSalary to thisSalary
            end if
        end repeat
        -- Pad the last department's salary list if necessary.
        repeat (n - (count topSalaries)) times
            set end of topSalaries to mv
        end repeat
    end if
    
    return output
end topNSalariesPerDepartment

on join(lst, delim)
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delim
    set txt to lst as text
    set AppleScript's text item delimiters to astid
    return txt
end join

on task()
    set employeeRecords to {¬
        {|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"}, ¬
        {|name|:"Simila Pey", |ID|:"E16399", salary:29900, department:"D190"} ¬
            }
    set n to 4
    set topSalaryRecords to topNSalariesPerDepartment(employeeRecords, n)
    
    -- Derive a text report from the result.
    set report to {"Top " & n & " salaries per department:"}
    repeat with thisRecord in topSalaryRecords
        set end of report to thisRecord's department & ":    " & join(thisRecord's salaries, "  ")
    end repeat
    return join(report, linefeed)
end task

task()
Output:
"Top 4 salaries per department:
D050:    47000  21900  -  -
D101:    53500  41500  32000  27000
D190:    57000  29900  -  -
D202:    49500  27800  19250  18000"

Arturo

printTopEmployees: function [count, entries][
    data: read.csv.withHeaders entries
    departments: sort unique map data 'row -> row\Department
    loop departments 'd [
        print "----------------------"
        print ["department:" d]
        print "----------------------"
        loop first.n: count
            sort.descending.by:"Salary" 
                select data 'row [row\Department = d] 'employee ->
                    print [get employee "Employee Name" "->" employee\Salary]
        print ""
    ]
]

printTopEmployees 2 {
    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
}
Output:
----------------------
department: D050 
----------------------
John Rappl -> 47000 
Nathan Adams -> 21900 

----------------------
department: D101 
----------------------
George Woltman -> 53500 
David McClellan -> 41500 

----------------------
department: D190 
----------------------
Kim Arlich -> 57000 
Timothy Grove -> 29900 

----------------------
department: D202 
----------------------
Rich Holcomb -> 49500 
Claire Buckman -> 27800

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

AWK

# syntax: GAWK -f TOP_RANK_PER_GROUP.AWK [n]
#
# sorting:
#   PROCINFO["sorted_in"] is used by GAWK
#   SORTTYPE is used by Thompson Automation's TAWK
#
BEGIN {
    arrA[++n] = "Employee Name,Employee ID,Salary,Department" # raw data
    arrA[++n] = "Tyler Bennett,E10297,32000,D101"
    arrA[++n] = "John Rappl,E21437,47000,D050"
    arrA[++n] = "George Woltman,E00127,53500,D101"
    arrA[++n] = "Adam Smith,E63535,18000,D202"
    arrA[++n] = "Claire Buckman,E39876,27800,D202"
    arrA[++n] = "David McClellan,E04242,41500,D101"
    arrA[++n] = "Rich Holcomb,E01234,49500,D202"
    arrA[++n] = "Nathan Adams,E41298,21900,D050"
    arrA[++n] = "Richard Potter,E43128,15900,D101"
    arrA[++n] = "David Motsinger,E27002,19250,D202"
    arrA[++n] = "Tim Sampair,E03033,27000,D101"
    arrA[++n] = "Kim Arlich,E10001,57000,D190"
    arrA[++n] = "Timothy Grove,E16398,29900,D190"
    for (i=2; i<=n; i++) { # build internal structure
      split(arrA[i],arrB,",")
      arrC[arrB[4]][arrB[3]][arrB[2] " " arrB[1]] # I.E. arrC[dept][salary][id " " name]
    }
    show = (ARGV[1] == "") ? 1 : ARGV[1] # employees to show per department
    printf("DEPT SALARY EMPID  NAME\n\n") # produce report
    PROCINFO["sorted_in"] = "@ind_str_asc" ; SORTTYPE = 1
    for (i in arrC) {
      PROCINFO["sorted_in"] = "@ind_str_desc" ; SORTTYPE = 9
      shown = 0
      for (j in arrC[i]) {
        PROCINFO["sorted_in"] = "@ind_str_asc" ; SORTTYPE = 1
        for (k in arrC[i][j]) {
          if (shown++ < show) {
            printf("%-4s %6s %s\n",i,j,k)
            printed++
          }
        }
      }
      if (printed > 0) { print("") }
      printed = 0
    }
    exit(0)
}

Sample command and output:

GAWK -f TOP_RANK_PER_GROUP.AWK 3

DEPT SALARY EMPID  NAME

D050  47000 E21437 John Rappl
D050  21900 E41298 Nathan Adams

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

D190  57000 E10001 Kim Arlich
D190  29900 E16398 Timothy Grove

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

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

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

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

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

Ceylon

class Employee(name, id, salary, dept) {
    shared String name;
    shared String id;
    shared Integer salary;
    shared String dept;

    string => "``name`` ``id`` $``salary``.00 ``dept``";
}

Employee[] 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")
];

"This is the main function."
shared void run() {

    value topRanked = topSalaries(employees, 3);

    for (dept -> staff in topRanked) {
        print(dept);
        for (employee in staff) {
            print("\t``employee``");
        }
    }
}

Map<String, {Employee*}> topSalaries({Employee*} employees, Integer n) => map {
    for (dept -> staff in employees.group(Employee.dept))
    dept -> staff.sort(byDecreasing(Employee.salary)).take(n)
};

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}

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

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

Delphi

Works with: Delphi version 6.0


type TEmployeeInfo = record
 Name,ID: string;
 Salary: double;
 Dept: string;
 end;

var EmployeeInfo: array [0..12] of TEmployeeInfo = (
	(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 SalarySort(Item1, Item2: Pointer): Integer;
var Info1,Info2: TEmployeeInfo;
begin
Info1:=TEmployeeInfo(Item1^);
Info2:=TEmployeeInfo(Item2^);
Result:=AnsiCompareStr(Info1.Dept,Info2.Dept);
If Result=0 then Result:=Trunc(Info1.Salary-Info2.Salary);
end;

procedure ShowTopSalaries(Memo: TMemo);
var List: TList;
var Info: TEmployeeInfo;
var I: integer;
var S,OldDept: string;

	procedure NewDepartment(Name: string);
	begin
	Memo.Lines.Add('');
	Memo.Lines.Add('Department: '+Name);
	Memo.Lines.Add('Employee Name    Employee ID      Salary           Department');
	OldDept:=Name;
	end;


begin
List:=TList.Create;
try
for I:=0 to High(EmployeeInfo) do
 List.Add(@EmployeeInfo[I]);
List.Sort(SalarySort);
OldDept:='';
for I:=0 to List.Count-1 do
	begin
	Info:=TEmployeeInfo(List[I]^);
	if OldDept<>Info.Dept then NewDepartment(Info.Dept);
	S:=Format('%-18S %9S %11.0m %8S',[Info.Name,Info.ID,Info.Salary,Info.Dept]);
	Memo.Lines.Add(S);
	end;

finally List.Free; end;
end;
Output:
Department: D050
Employee Name    Employee ID      Salary     Dept
Nathan Adams          E41298     $21,900     D050
John Rappl            E21437     $47,000     D050

Department: D101
Employee Name    Employee ID      Salary     Dept
Richard Potter        E43128     $15,900     D101
Tim Sampair           E03033     $27,000     D101
Tyler Bennett         E10297     $32,000     D101
David McClellan       E04242     $41,500     D101
George Woltman        E00127     $53,500     D101

Department: D190
Employee Name    Employee ID      Salary     Dept
Timothy Grove         E16398     $29,900     D190
Kim Arlich            E10001     $57,000     D190

Department: D202
Employee Name    Employee ID      Salary     Dept
Adam Smith            E63535     $18,000     D202
David Motsinger       E27002     $19,250     D202
Claire Buckman        E39876     $27,800     D202
Rich Holcomb          E01234     $49,500     D202

Elapsed Time: 35.284 ms.

Dyalect

type Employee(name,id,salary,department) with Lookup
 
func Employee.ToString() {
    "$\(this.salary) (name: \(this.name), id: \(this.id), department: \(this.department)"
}
 
let 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")
]
 
func topNSalaries(n) {
    //We sort employees based on salary
    employees.Sort((x,y) => y.salary - x.salary)
    let max =
        if n > employees.Length() - 1 { 
            employees.Length() - 1 
        } else { 
            n 
        }
    for i in 0..max {
        yield employees[i]
    }
}
 
var seq = topNSalaries(5)
 
for e in seq {
    print(e)
}
Output:
$57000 (name: Kim Arlich, id: E10001, department: D190
$53500 (name: George Woltman, id: E00127, department: D101
$49500 (name: Rich Holcomb, id: E01234, department: D202
$47000 (name: John Rappl, id: E21437, department: D050
$41500 (name: David McClellan, id: E04242, department: D101
$32000 (name: Tyler Bennett, id: E10297, department: D101

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

EchoLisp

We use the sql.lib to select, sort and group records from the table 'emps'. The output is appended in a new table, 'high'.

(lib 'struct) ;; tables are based upon structures
(lib 'sql)  ;; sql-select function

;; input table
(define emps  (make-table (struct emp (name id salary dept))))
;; output table
(define high  (make-table (struct out (dept name salary))))

;; sort/group procedure
(define (get-high num-records: N into: high)
(sql-select emp.dept emp.name emp.salary 
  from emps  
  group-by emp.dept 
  order-by emp.salary desc limit N into high))
Output:
(define emps_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)
("Simon Gallubert" E00000 42 D666)
("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)))

(list->table emps_file emps ) ;; load the table

(get-high 2 high)
(table-print high)

[0]   D050  John Rappl       47000 
[1]   D050  Nathan Adams     21900 
[2]   D101  George Woltman   53500 
[3]   D101  David McClellan  41500 
[4]   D190  Kim Arlich       57000 
[5]   D190  Timothy Grove    29900 
[6]   D202  Rich Holcomb     49500 
[7]   D202  Claire Buckman   27800 
[8]   D666  Simon Gallubert  42  

(sql-delete from high)
(get-high 1 high)
(table-print high)
[0]   D050  John Rappl       47000 
[1]   D101  George Woltman   53500 
[2]   D190  Kim Arlich       57000 
[3]   D202  Rich Holcomb     49500 
[4]   D666  Simon Gallubert  42

Elena

ELENA 6.x :

import system'collections;
import system'routines;
import extensions;
import extensions'routines;
import extensions'text;
 
class Employee
{
    string Name       : prop;
    string ID         : prop;
    int    Salary     : prop;
    string Department : prop;
 
    string toPrintable()
        = new StringWriter()
            .writePaddingRight(Name, 25)
            .writePaddingRight(ID, 12)
            .writePaddingRight(Salary.toPrintable(), 12)
            .write(Department);
}
 
extension reportOp
{
    topNPerDepartment(n)
        = self.groupBy::(x => x.Department ).selectBy::(x)
        {
            ^ new {
                Department = x.Key;
 
                Employees
                    = x.orderBy::(f,l => f.Salary > l.Salary ).top(n).summarize(new ArrayList());
            }
        };
}
 
public program()
{
    var employees := new Employee[]
    {
        new Employee{ this Name := "Tyler Bennett"; this ID := "E10297"; this Salary:=32000; this Department:="D101";},
        new Employee{ this Name := "John Rappl"; this ID := "E21437"; this Salary:=47000; this Department:="D050";},
        new Employee{ this Name := "George Woltman"; this ID := "E00127"; this Salary:=53500; this Department:="D101";},
        new Employee{ this Name := "Adam Smith"; this ID := "E63535"; this Salary:=18000; this Department:="D202";},
        new Employee{ this Name := "Claire Buckman"; this ID := "E39876"; this Salary:=27800; this Department:="D202";},
        new Employee{ this Name := "David McClellan"; this ID := "E04242"; this Salary:=41500; this Department:="D101";},
        new Employee{ this Name := "Rich Holcomb"; this ID := "E01234"; this Salary:=49500; this Department:="D202";},
        new Employee{ this Name := "Nathan Adams"; this ID := "E41298"; this Salary:=21900; this Department:="D050";},
        new Employee{ this Name := "Richard Potter"; this ID := "E43128"; this Salary:=15900; this Department:="D101";},
        new Employee{ this Name := "David Motsinger"; this ID := "E27002"; this Salary:=19250; this Department:="D202";},
        new Employee{ this Name := "Tim Sampair"; this ID := "E03033"; this Salary:=27000; this Department:="D101";},
        new Employee{ this Name := "Kim Arlich"; this ID := "E10001"; this Salary:=57000; this Department:="D190";},
        new Employee{ this Name := "Timothy Grove"; this ID := "E16398"; this Salary:=29900; this Department:="D190";}
    };
 
    employees.topNPerDepartment(2).forEach::(info)
    {
        console.printLine("Department: ",info.Department);
 
        info.Employees.forEach(printingLn);
 
        console.writeLine("---------------------------------------------")
    };
 
    console.readChar()
}
Output:
Department: D101
George Woltman           E00127      53500       D101
David McClellan          E04242      41500       D101
---------------------------------------------
Department: D050
John Rappl               E21437      47000       D050
Nathan Adams             E41298      21900       D050
---------------------------------------------
Department: D202
Rich Holcomb             E01234      49500       D202
Claire Buckman           E39876      27800       D202
---------------------------------------------
Department: D190
Kim Arlich               E10001      57000       D190
Timothy Grove            E16398      29900       D190
---------------------------------------------

Elixir

Quick implementation using piping and anonymous functions.

defmodule TopRank do
  def per_groupe(data, n) do
    String.split(data, ~r/(\n|\r\n|\r)/, trim: true)
    |> Enum.drop(1)
    |> Enum.map(fn person -> String.split(person,",") end)
    |> Enum.group_by(fn person -> department(person) end)
    |> Enum.each(fn {department,group} ->
         IO.puts "Department: #{department}"
         Enum.sort_by(group, fn person -> -salary(person) end)
         |> Enum.take(n)
         |> Enum.each(fn person -> IO.puts str_format(person) end)
       end)
  end
  
  defp salary([_,_,x,_]), do: String.to_integer(x)
  defp department([_,_,_,x]), do: x
  defp str_format([a,b,c,_]), do: "  #{a} - #{b} - #{c} annual salary"
end

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
"""
TopRank.per_groupe(data, 3)
Output:
Department: D050
  John Rappl - E21437 - 47000 annual salary
  Nathan Adams - E41298 - 21900 annual salary
Department: D101
  George Woltman - E00127 - 53500 annual salary
  David McClellan - E04242 - 41500 annual salary
  Tyler Bennett - E10297 - 32000 annual salary
Department: D190
  Kim Arlich - E10001 - 57000 annual salary
  Timothy Grove - E16398 - 29900 annual salary
Department: D202
  Rich Holcomb - E01234 - 49500 annual salary
  Claire Buckman - E39876 - 27800 annual salary
  David Motsinger - E27002 - 19250 annual salary

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"

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)

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

Forth

\ Written in ANS-Forth; tested under VFX.
\ Requires the novice package: http://www.forth.org/novice.html
\ The following should already be done:
\ include novice.4th
\ include list.4th

marker TopRank.4th

\ This demonstrates how I typically use lists. A program such as this does not need any explicit iteration, so it is more like Factor than C.
\ I would define high-level languages as those that allow programs to be written without explicit iteration. Iteration is a major source of bugs.
\ The C library has QSORT that hides iteration, but user-written code very rarely uses this technique, and doesn't in the TopRank example.


\ ******
\ ****** The following defines our data-structure.
\ ****** Pretty much every struct definition has these basic functions.
\ ******

list
    w field .name       \ string
    w field .id         \ string
    w field .salary     \ integer
    w field .dept       \ string
constant employee

: init-employee ( name id salary department node -- node )
    init-list >r
    hstr    r@ .dept !
            r@ .salary !
    hstr    r@ .id !
    hstr    r@ .name !
    r> ;
    
: new-employee ( name id salary dept -- node )
    employee alloc
    init-employee ;
    
: <kill-employee> ( node -- )
    dup .name @     dealloc
    dup .id @       dealloc
    dup .dept @     dealloc
    dealloc ;

: kill-employee ( head -- )
    each[  <kill-employee>  ]each ;
    
: <clone-employee> ( node -- new-node )
    clone-node
    dup .name @     hstr    over .name !
    dup .id @       hstr    over .id !
    dup .dept @     hstr    over .dept ! ;
    
: clone-employee ( head -- new-head )    
    nil
    swap each[  <clone-employee> link  ]each ;

: <show-employee> ( node -- )
    dup .id @       count type  4 spaces
    dup .dept @     count type  4 spaces
    dup .salary @   .           4 spaces
    dup .name @     count type  cr
    drop ;
    
: show-employee ( head -- )    
    cr
    each[  <show-employee>  ]each ;

    
\ ******
\ ****** The following code is specific to the query that we want to do in this example problem.
\ ******

: employee-dept-salary ( new-node node -- new-node ? )                      \ for use by FIND-PRIOR or INSERT-ORDERED
    2dup  
    .dept @ count   rot .dept @ count   compare     ?dup if  A>B =  nip exit then
    .salary @       over .salary @      < ;

: <top> ( n rank current-dept head node -- n rank current-dept head )    
    2>r                         \ -- n rank current-dept
    dup count  r@ .dept @ count  compare  A=B <> if                         \ we have a new department
        2drop                                                               \ discard RANK and CURRENT-DEPT
        0  r@ .dept @  then                                                 \ start again with a new RANK and CURRENT-DEPT
    rover rover > if                                                        \ if N > RANK then it is good
        r> r>  over             \ -- n rank current-dept node head node
        <clone-employee> link   \ -- n rank current-dept node head
        >r >r  then             \ -- n rank current-dept
    swap 1+  swap                                                           \ increment RANK
    rdrop  r> ;                 \ -- n rank current-dept head
    
: top ( n head -- new-head )    \ make a new list of the top N salary earners in each dept      \ requires that list be sorted by dept-salary
    >r
    0  c" xxx"  nil             \ -- n rank current-dept new-head           \ initially for an invalid department
    r>  ['] <top>  each 
    3nip ;
    
    
\ ******
\ ****** The following is a test of the program using sample data.
\ ******
    
nil  ' employee-dept-salary
c" Tyler Bennett"       c" E10297"  32000  c" D101"     new-employee  insert-ordered
c" John Rappl"          c" E21437"  47000  c" D050"     new-employee  insert-ordered
c" George Woltman"      c" E00127"  53500  c" D101"     new-employee  insert-ordered
c" Adam Smith"          c" E63535"  18000  c" D202"     new-employee  insert-ordered
c" Claire Buckman"      c" E39876"  27800  c" D202"     new-employee  insert-ordered
c" David McClellan"     c" E04242"  41500  c" D101"     new-employee  insert-ordered
c" Rich Holcomb"        c" E01234"  49500  c" D202"     new-employee  insert-ordered
c" Nathan Adams"        c" E41298"  21900  c" D050"     new-employee  insert-ordered
c" Richard Potter"      c" E43128"  15900  c" D101"     new-employee  insert-ordered
c" David Motsinger"     c" E27002"  19250  c" D202"     new-employee  insert-ordered
c" Tim Sampair"         c" E03033"  27000  c" D101"     new-employee  insert-ordered
c" Kim Arlich"          c" E10001"  57000  c" D190"     new-employee  insert-ordered
c" Timothy Grove"       c" E16398"  29900  c" D190"     new-employee  insert-ordered
drop  constant test-data

cr .( N = 0 )
0 test-data top  dup show-employee  kill-employee

cr .( N = 1 )
1 test-data top  dup show-employee  kill-employee

cr .( N = 2 )
2 test-data top  dup show-employee  kill-employee

cr .( N = 3 )
3 test-data top  dup show-employee  kill-employee

test-data kill-employee
Output:
N = 0 

N = 1 
E21437    D050    47000     John Rappl
E00127    D101    53500     George Woltman
E10001    D190    57000     Kim Arlich
E01234    D202    49500     Rich Holcomb

N = 2 
E21437    D050    47000     John Rappl
E41298    D050    21900     Nathan Adams
E00127    D101    53500     George Woltman
E04242    D101    41500     David McClellan
E10001    D190    57000     Kim Arlich
E16398    D190    29900     Timothy Grove
E01234    D202    49500     Rich Holcomb
E39876    D202    27800     Claire Buckman

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

Fortran

The example data can easily be declared via DATA statements, as with
      DATA EMPLOYEE(1:3)/
     1 GRIST("Tyler Bennett","E10297",32000,"D101"),
     2 GRIST("John Rappl","E21437",47000,"D050"),
     3 GRIST("George Woltman","E00127",53500,"D101")/

(just showing the first three), however this does not allow the preparation of the header line, because GRIST("Employee Name","Employee ID","Salary","Department") would be invalid since the "salary" entry is a floating-point number in the data aggregate, not text. The header line could be discarded, but this is in violation of the ideas of properly-described data files. So, the plan is to read the data from an input file containing the full example input, with its header line given special treatment. No attempt is made to detect or report improperly-formatted data. The READ statements could refer to an internally-defined block of text, but reading an actual disc file is more normal. Ad-hoc decisions are made for the size of the CHARACTER variables, that are "surely big enough" for the example data, likewise with the size of the array to hold all the records.

For simplicity, and following the specification to not parse the data at runtime but to use built-in facilities, free-format input is intended. This does not require text variables to be quoted, however, because both a comma and a space are accepted as delimiters the specified data cannot be read as provided since some text fields contain a space. Such text fields must be placed within quotes to avoid missteps. This of course must also be done if a DATA statement as above were to be employed. On the other hand, the example data contains only names with two parts separated by a space - and this could be relied on as a separator to read a two-part name variable. Similarly, all the salary values have the same number of digits and so could be sorted as text variables to give the desired numerical order. The resulting scheme will be easily disrupted by data other than those of the example! I recall working at the NZ Post Office in the 1970s when there came a time that higher-paid staff achieved fortnightly pays in excess of $999·99 and the computer system couldn't handle it. Similarly, I have a three-part name. Asian names are often written with family first, and there should be a comma when this ordering is used in English: Bloggs, Joe; Kim, Il-sung; Kim, Jong-il; Kim, Jong-un. To have such name texts read as one variable would definitely require them to be enclosed in quotes so that their included comma is not taken as a delimiter in free-format input, but a more likely usage would be to read two fields and argue over two-part names only. Some Asian names are one-part, even as a formal usage. The question is whether the names part is a "block" of text to be shown as-is, or whether it is to have components.

The only remaining requirement is that the type of datum being read matches the type of the corresponding variable in the input-list that is to receive that datum; happily this is the case as each record has four fields, always in the same order and the same type. (I have got nowhere explaining to a data supplier that the header line can use different names for the columns if they are always in the same order, or, the columns can be in a different order provided that their column names are always the same - but varying both the names and the order forces me to personally inspect the data file and make guesses. Bah.) Except for the header line, which has four character fields and so can be read into a CHARACTER array, as ever of a "suitable" size. There is alas no facility whereby, during the input processing itself the receiving variable can be resized to suit the actual size of its incoming datum, as it is being read. Fortran array indexing is done by calculating storage locations employing known and fixed size components. So, one must make ad-hoc choices that will work for the example, but which may run into trouble more generally. F90 supports facilities for allocating items of a size determined at run time, however this has to be done before the item is put to use, such as in a READ statement. But the input data are not to be parsed by special code, so reading an input line into a working area to determine the necessary sizes, allocating storage areas of suitable size, then transferring data from the scratchpad to the freshly-created recipients is disallowed here.

The source is unstructured by subroutines and the like, but uses the facilities of F90 whereby a compound data aggregate can be defined and used with a systematic nomenclature for the parts. Thus, array EMPLOYEE has components with suitable names. Previously, one would define separate simple arrays of the appropriate type, using a similar naming system in self-defence: perhaps ENAME, EID, ESAL, EDEPT or similar.

If the requirement had been to report the highest-ranked salary, the deed could have been done via the F90 function MAXLOC, which locates the array index of the (first-encountered?) maximum value of an array of values; further, it allows an additional MASK parameter which could be used to select only those entries having a particular "department" code. However, aside from the multiple scans that would be required for multiple departmental codes, there is the requirement for the top N salaries, and anyway, there would still be the need to identify the different departmental codes once only. Accordingly, the way ahead is the classic: sort the data and then work through the sorted sequence. Since standard Fortran provides no "sort" function in its standard libraries nor does it offer a pre-processor that might include a sort generator and the like to generate the specific code needed for a particular data aggregate and sort key specification, one must engage in some repetition. For this purpose, the "comb" sort is convenient with its code requiring just one comparison and one "swap" action, so in-line code is not troublesome. On the other hand, when dealing with compound sort keys, a three-way IF statement (or equivalent) is necessary whereby the first field of the sort key is compared with appropriate action for the < and > cases, but for the = case the comparison moves on to the second key. Three different consequences from the one comparison. Despite the deprecations of the modernisers this is still possible for numerical comparisons, but alas, character comparisons are not allowed in an arithmetic-IF statement - though one could struggle with ICHAR action. Thus, two comparisons are made where only one should suffice.

Finally, standard Fortran does not offer a means of receiving parameters as might be supplied when a code file is activated in a command-line environment. There may be special library routines with names such as GETARG, but they're not standard. So, a READ statement is employed. Or, one could rewrite this routine as a subroutine having one parameter so as to approximate the form of the specification more closely. Extra statements...
      PROGRAM TOP RANK   !Just do it.
      CHARACTER*28 HEADING(4)   !The first line is a header.
      TYPE GRIST        !But this is the stuff.
       CHARACTER*28 NAME        !Arbitrary sizes.
       CHARACTER*6 ID           !Possibly imperfect.
       REAL*8 SALARY            !Single precision is a bit thin.
       CHARACTER*6 DEPARTMENT       !Not a number.
      END TYPE GRIST        !So much for the aggregate.
      INTEGER HORDE     !Some parameterisation.
      PARAMETER (HORDE = 66)    !This should suffice.
      TYPE(GRIST) EMPLOYEE(HORDE),HIC   !An extra for the sort.
      LOGICAL CURSE     !Possible early completion.
      INTEGER I,N,H     !Steppers.
      INTEGER II,R      !Needed for the results.
      INTEGER KBD,MSG,IN    !I/O unit numbers.

      KBD = 5   !Standard input.
      MSG = 6   !Standard output.
      IN = 10   !Suitable for an input file.
      WRITE (MSG,1)
    1 FORMAT ("Reads a set of employee information from TopRank.csv"/
     1"Then for each department code, shows the N highest paid.")
      OPEN (IN,NAME = "TopRank.csv",FORM = "FORMATTED")
      READ (IN,*) HEADING   !Column headings: the "Salary" heading is not numeric.
Chug through the input.
      N = 0     !None so far.
   10 READ (IN,*,END = 20) EMPLOYEE(N + 1)  !Get the next record.
      N = N + 1                 !We did. Count it in.
      IF (N.GT.HORDE) STOP "Too many employee records!" !Ah, suspicion.
      GO TO 10                  !Perhaps there will be another.
Collate the collection.
   20 CLOSE(IN)         !Finished with the input.
Crank up a comb sort, which requires only one comparison statement. Especially good for compound fields.
      H = N - 1         !Last - first, and not + 1.
   21 H = MAX(1,H*10/13)    !The special feature.
      CURSE = .FALSE.       !So far, so good.
      DO I = N - H,1,-1     !If H = 1, this is a Bubblesort.
        IF (EMPLOYEE(I).DEPARTMENT.LT.EMPLOYEE(I + H).DEPARTMENT) CYCLE !In order by department.
        IF (EMPLOYEE(I).DEPARTMENT.EQ.EMPLOYEE(I + H).DEPARTMENT    !Or, Equal department,
     *  .AND. EMPLOYEE(I).SALARY.GT.EMPLOYEE(I + H).SALARY) CYCLE   !And in decreasing order by salary.
        CURSE = .TRUE.          !No escape. the elements are in the wrong order.
        HIC = EMPLOYEE(I)       !So a SWAP statement would be good. But alas.
        EMPLOYEE(I) = EMPLOYEE(I + H)   !For large data aggregates, an indexed sort would be good.
        EMPLOYEE(I + H) = HIC       !But, just slog away.
      END DO                !Consider the next pairing.
      IF (CURSE .OR. H.GT.1) GO TO 21   !Work remains?

Cast forth results.
   30 WRITE (6,31) N    !Announce, and solicit a parameter.
   31 FORMAT (I0," employees. How many per dept? ",$)   !The $ sez "don't start a new line."
      READ (KBD,*) R    !The parameter.
      IF (R.LE.0) STOP  !bah.
      WRITE (MSG,32) "Rank",HEADING
   32 FORMAT (/,A6,1X,A28,2X,A12,1X,A10,A)  !Compare to FORMAT 33.
      HIC.DEPARTMENT = "...Not this"    !Different from all departmental codes.
      DO I = 1,N    !Scan the sorted data.
        IF (HIC.DEPARTMENT.EQ.EMPLOYEE(I).DEPARTMENT) THEN  !Another?
          II = II + 1       !Same department, so count another adherent.
         ELSE           !But with a change of department code,
          II = 1        !Start a fresh count.
          HIC.DEPARTMENT = EMPLOYEE(I).DEPARTMENT   !And remember the new code.
          WRITE (MSG,*) "For ",HIC.DEPARTMENT   !Announce the new department's code.
        END IF          !So much for grouping.
        IF (II.LE.R) WRITE (MSG,33) II,EMPLOYEE(I)  !Still within the desired rank?
   33   FORMAT (I6,1X,A28,1X,A12,F11.2,1X,A)    !Some layout. Includes the repeated departmental code name.
      END DO            !On to the next.

      END   !That was straightforward.

As ever, some tricky sizing of fields in the FORMAT statements, and a collection of ad-hoc constants. Cross-linking the integers via PARAMETER statements and the like would add a whole lot of blather, so if field sizes are changed, a fair amount of fiddling would follow. A more accomplished data processing routine would include this.

Output:

Reads a set of employee information from TopRank.csv
Then for each department code, shows the N highest paid.
13 employees. How many per dept? 3

  Rank Employee Name                 Employee ID  Salary    Department
 For D050
     1 John Rappl                         E21437   47000.00 D050
     2 Nathan Adams                       E41298   21900.00 D050
 For D101
     1 George Woltman                     E00127   53500.00 D101
     2 David McClellan                    E04242   41500.00 D101
     3 Tyler Bennett                      E10297   32000.00 D101
 For D190
     1 Kim Arlich                         E10001   57000.00 D190
     2 Timothy Grove                      E16398   29900.00 D190
 For D202
     1 Rich Holcomb                       E01234   49500.00 D202
     2 Claire Buckman                     E39876   27800.00 D202
     3 David Motsinger                    E27002   19250.00 D202

FreeBASIC

#define N 3      'show the top three employees of each rank

'here is all the data to be read in

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

type employee
    'define a data type for employees
    nm as string*32   'name
    en as string*6    'employee number
    sl as uinteger    'salary
    dp as string*4    'department
    fl as boolean      'a flag
end type

dim as employee emp(1 to 13)
dim as uinteger e, d, x, ce, cs
dim as string*4 dept(1 to 4) = {"D050", "D101", "D190", "D202"}

for e = 1 to 13
    'put all the employee data into an array
    read emp(e).nm
    read emp(e).en
    read emp(e).sl
    read emp(e).dp
    emp(e).fl = false
next e

for d = 1 to 4    'look at each department
    print "Department ";dept(d);":"
    for x = 1 to N    'top N employees
        cs = 0
        ce = 0
        for e = 1 to 13    'look through employees
            if emp(e).dp = dept(d) andalso emp(e).fl = false andalso emp(e).sl > cs then
                emp(ce).fl = false 'unflag the previous champion so they can be found on the next pass
                ce = e
                cs = emp(e).sl
                emp(e).fl = true   'flag this employee so that on the next pass we can get the next richest
            endif
        next e
        if ce>0 then print emp(ce).nm;"  ";emp(ce).en;"  ";emp(ce).sl;"  ";emp(ce).dp
    next x
    print
next d
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

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

Go

package main

import (
    "fmt"
    "sort"
)

// language-native data description
type Employee struct {
    Name, ID string
    Salary   int
    Dept     string
}

type EmployeeList []*Employee

var data = EmployeeList{
    {"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"},
    // Extra data to demonstrate ties
    {"Tie A", "E16399", 29900, "D190"},
    {"Tie B", "E16400", 29900, "D190"},
    {"No Tie", "E16401", 29899, "D190"},
}

// We only need one type of ordering/grouping for this task so we could directly
// implement sort.Interface on EmployeeList (or a byDeptSalary alias type) with
// the appropriate Less method.
//
// Instead, we'll add a bit here that makes it easier to use arbitrary orderings.
// This is like the "SortKeys" Planet sorting example in the sort package
// documentation, see https://golang.org/pkg/sort

type By func(e1, e2 *Employee) bool
type employeeSorter struct {
    list EmployeeList
    by   func(e1, e2 *Employee) bool
}

func (by By) Sort(list EmployeeList)         { sort.Sort(&employeeSorter{list, by}) }
func (s *employeeSorter) Len() int           { return len(s.list) }
func (s *employeeSorter) Swap(i, j int)      { s.list[i], s.list[j] = s.list[j], s.list[i] }
func (s *employeeSorter) Less(i, j int) bool { return s.by(s.list[i], s.list[j]) }

// For this specific task we could just write the data to an io.Writer
// but in general it's better to return the data in a usable form (for
// example, perhaps other code want's to do something like compare the
// averages of the top N by department).
//
// So we go through the extra effort of returning an []EmployeeList, a
// list of employee lists, one per deparment. The lists are trimmed to
// to the top 'n', which can be larger than n if there are ties for the
// nth salary (callers that don't care about ties could just trim more.)
func (el EmployeeList) TopSalariesByDept(n int) []EmployeeList {
    if n <= 0 || len(el) == 0 {
        return nil
    }
    deptSalary := func(e1, e2 *Employee) bool {
        if e1.Dept != e2.Dept {
            return e1.Dept < e2.Dept
        }
        if e1.Salary != e2.Salary {
            return e1.Salary > e2.Salary
        }
        // Always have some unique field as the last one in a sort list
        return e1.ID < e2.ID
    }

    // We could just sort the data in place for this task. But
    // perhaps messing with the order is undesirable or there is
    // other concurrent access. So we'll make a copy and sort that.
    // It's just pointers so the amount to copy is relatively small.
    sorted := make(EmployeeList, len(el))
    copy(sorted, el)
    By(deptSalary).Sort(sorted)

    perDept := []EmployeeList{}
    var lastDept string
    var lastSalary int
    for _, e := range sorted {
        if e.Dept != lastDept || len(perDept) == 0 {
            lastDept = e.Dept
            perDept = append(perDept, EmployeeList{e})
        } else {
            i := len(perDept) - 1
            if len(perDept[i]) >= n && e.Salary != lastSalary {
                continue
            }
            perDept[i] = append(perDept[i], e)
            lastSalary = e.Salary
        }
    }
    return perDept
}

func main() {
    const n = 3
    top := data.TopSalariesByDept(n)
    if len(top) == 0 {
        fmt.Println("Nothing to show.")
        return
    }
    fmt.Printf("Top %d salaries per department\n", n)
    for _, list := range top {
        fmt.Println(list[0].Dept)
        for _, e := range list {
            fmt.Printf("    %s %16s %7d\n", e.ID, e.Name, e.Salary)
        }
    }
}
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
    E04242  David McClellan   41500
    E10297    Tyler Bennett   32000
D190
    E10001       Kim Arlich   57000
    E16398    Timothy Grove   29900
    E16399            Tie A   29900
    E16400            Tie B   29900
D202
    E01234     Rich Holcomb   49500
    E39876   Claire Buckman   27800
    E27002  David Motsinger   19250

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

Haskell

Data.List

Works with: GHC version 7.10.3
import Data.List (sortBy, groupBy)

import Text.Printf (printf)

import Data.Ord (comparing)

import Data.Function (on)

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 =
  fmap
    (\(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)
    ]

firstN :: Int
       -> (Employee -> DEP)
       -> (Employee -> SALARY)
       -> [Employee]
       -> [[Employee]]
firstN n o1 o2 x =
  fmap
    (take n . sortBy (comparingDown o2))
    (groupBy (groupingOn o1) (sortBy (comparing o1) x))

groupingOn
  :: Eq a1
  => (a -> a1) -> a -> a -> Bool
groupingOn = ((==) `on`)

comparingDown
  :: Ord a
  => (b -> a) -> b -> b -> Ordering
comparingDown = flip . comparing

main :: IO ()
main = do
  printf "%-16s %3s %10s\n" "NAME" "DEP" "TIP"
  putStrLn $ replicate 31 '='
  mapM_ (traverse ((printf "%-16s %3s %10.2g\n" . name) <*> dep <*> sal)) $
    firstN 3 dep sal employees
Output:
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

Data.Map

Works with: GHC version 7.10.3

Alternatively, if we store the data in key-value maps, rather than in a cons list, we can use Map.lookup and Map.filter, Map.keys and Map.elems, to pull out the data and shape reports fairly flexibly.

import Data.List (intercalate, nub, sort, sortBy, transpose)
import qualified Data.Map as M
import Data.Maybe (fromJust)
import Data.Ord (comparing)

main :: IO ()
main =
  (putStrLn . unlines) $
    table
      "   "
      ( (reportLines <*> highSalaryKeys 3)
          =<< (sort . nub) (M.elems mapDepts)
      )

reportLines :: String -> [(Int, Int)] -> [[String]]
reportLines dept =
  fmap
    ( \(k, n) ->
        [ fromJust $ M.lookup k mapNames,
          dept,
          show n
        ]
    )

highSalaryKeys :: Int -> String -> [(Int, Int)]
highSalaryKeys n dept =
  take n $
    sortBy ((flip . comparing) snd) $
      (,) <*> (fromJust . flip M.lookup mapSalaries)
        <$> M.keys (M.filter (== dept) mapDepts)

mapNames, mapDepts :: M.Map Int String
[mapNames, mapDepts] =
  (readPairs <$> [nameKV, deptKV]) <*> [xs]

mapSalaries :: M.Map Int Int
mapSalaries = readPairs salaryKV xs

xs :: [(Int, String, String, Int)]
xs =
  [ (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)
  ]

readPairs ::
  Ord k =>
  (a1 -> (k, a)) ->
  [a1] ->
  M.Map k a
readPairs f xs = M.fromList (f <$> xs)

nameKV,
  deptKV ::
    (Int, String, String, Int) -> (Int, String)
nameKV (k, _, name, _) = (k, name)
deptKV (k, dept, _, _) = (k, dept)

salaryKV :: (Int, String, String, Int) -> (Int, Int)
salaryKV (k, _, _, salary) = (k, salary)

table :: String -> [[String]] -> [String]
table delim rows =
  let justifyLeft c n s = take n (s <> replicate n c)
      justifyRight c n s =
        drop
          (length s)
          (replicate n c <> s)
   in intercalate delim
        <$> transpose
          ( ( fmap
                =<< justifyLeft ' '
                  . maximum
                  . fmap length
            )
              <$> transpose rows
          )
Output:
de Bont C.A.       AB   65000
Anders H.          AB   50000
de Slegte S.       AB   46987
Uitlaat G.A.S.     CC   44500
de Goeij J.        CC   39000
Zagt A.            CC   33000
Telmans R.M.       CD   55500
Janszen H.P.       CD   41000
Janszoon A.        CD   38665
Ezelbips P.J.      KA   52000
't Woud B.         KA   45000
Pragtweik J.M.V.   KA   42300

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.

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

IS-BASIC

100 PROGRAM "Employee.bas"(N)
110 LET NR=100:LET X=0
120 STRING NAME$(1 TO NR)*20,ID$(1 TO NR)*6,DEPT$(1 TO NR)*4
130 NUMERIC SALARY(1 TO NR)
140 DO UNTIL N>0 AND N<=NR
150   INPUT PROMPT "Enter the number of ranks: ":N$
160   LET N=VAL(N$)
170 LOOP
180 CALL READDATA
190 CALL SORT
200 CALL LST
210 END
220 DEF READDATA
230   LET EOF=0
240   OPEN #1:"Employee.dat"
250   WHEN EXCEPTION USE IOERROR
260     DO
270       LET X=X+1
280       INPUT #1:NAME$(X),ID$(X),SALARY(X),DEPT$(X)
290     LOOP UNTIL EOF OR X=NR
300   END WHEN
310   HANDLER IOERROR
320     IF EXTYPE<>8001 THEN PRINT EXSTRING$(EXTYPE)
330     CLOSE #1
340     LET X=X-1:LET EOF=-1
350     IF X=0 THEN PRINT "No data.":STOP 
360     CONTINUE
370   END HANDLER
380 END DEF
390 DEF SORT
400   LET GAP=X:LET SW=1
410   DO WHILE GAP>1 OR SW
420     LET GAP=MAX(INT(GAP/1.3),1):LET SW=0
430     FOR I=1 TO X-GAP
440       IF DEPT$(I)>DEPT$(I+GAP) OR DEPT$(I)=DEPT$(I+GAP) AND SALARY(I)<SALARY(I+GAP) THEN
450         LET T$=NAME$(I):LET NAME$(I)=NAME$(I+GAP):LET NAME$(I+GAP)=T$
460         LET T$=DEPT$(I):LET DEPT$(I)=DEPT$(I+GAP):LET DEPT$(I+GAP)=T$
470         LET T$=ID$(I):LET ID$(I)=ID$(I+GAP):LET ID$(I+GAP)=T$
480         LET T=SALARY(I):LET SALARY(I)=SALARY(I+GAP):LET SALARY(I+GAP)=T
490         LET SW=1
500       END IF
510     NEXT
520   LOOP
530 END DEF
540 DEF LST
550   LET J=1:LET PREV$=""
560   FOR I=1 TO X
570     IF DEPT$(I)<>PREV$ THEN PRINT "Department ";DEPT$(I):LET PREV$=DEPT$(I):LET J=1
580     IF J<=N THEN PRINT "  ";NAME$(I);TAB(23);ID$(I),SALARY(I):LET J=J+1
590   NEXT
600 END DEF

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

Java

Works with: Java version 8
import java.io.File;
import java.util.*;

public class TopRankPerGroup {

    private static class Employee {
        final String name;
        final String id;
        final String department;
        final int salary;

        Employee(String[] rec) {
            name = rec[0];
            id = rec[1];
            salary = Integer.parseInt(rec[2]);
            department = rec[3];
        }

        @Override
        public String toString() {
            return String.format("%s %s %d %s", id, name, salary, department);
        }
    }

    public static void main(String[] args) throws Exception {
        int N = args.length > 0 ? Integer.parseInt(args[0]) : 3;

        Map<String, List<Employee>> records = new TreeMap<>();
        try (Scanner sc = new Scanner(new File("data.txt"))) {
            while (sc.hasNextLine()) {
                String[] rec = sc.nextLine().trim().split(", ");

                List<Employee> lst = records.get(rec[3]);
                if (lst == null) {
                    lst = new ArrayList<>();
                    records.put(rec[3], lst);
                }
                lst.add(new Employee(rec));
            }
        }

        records.forEach((key, val) -> {
            System.out.printf("%nDepartment %s%n", key);
            val.stream()
                .sorted((a, b) -> Integer.compare(b.salary, a.salary))
                .limit(N).forEach(System.out::println);
        });
    }
}
Department D050
E21437 John Rappl 47000 D050
E41298 Nathan Adams 21900 D050

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

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

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

JavaScript

Iterative Solution

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 salary
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


Map and reduce

ES5

var collectDept = function (arrOfObj) {
  var collect = arrOfObj.reduce(function (rtnObj, obj) {
    if (rtnObj[obj.dept] === undefined) {
      rtnObj[obj.dept] = [];
    }
    rtnObj[obj.dept].push(obj);
    return rtnObj;
  }, {});

  return Object.keys(collect).map(function (key) {
    return collect[key];
  });
};

var sortSalary = function (arrOfSalaryArrs) {
  return arrOfSalaryArrs.map(function (item) {
    return item.sort(function (a, b) {
      if (a.salary > b.salary) { return -1; }
      if (a.salary < b.salary) { return 1; }
      return 0;
    });
  });
};

var getNTopSalariesByDept = function (n, data) {
  if (n < 0) { return; }

  return sortSalary(collectDept(data)).map(function (list) {
    return list.slice(0,n);
  });
};

ES6

By composition of generic functions:

(() => {
    'use strict';

    // topNSalariesPerDept :: Int -> [[String]] -> [String]
    const topNSalariesPerDept = (n, records) =>
        foldl(
            (a, k, i) => (a[toLower(k)] = x => x[i], a),
            this,
            head(records)
        ) && map(intercalate(','),
            concatMap(take(n),
                reverse(
                    groupBy(
                        on(same, department),
                        sortBy(
                            flip(
                                mappendComparing([
                                    department,
                                    salary
                                ])
                            ),
                            tail(records)
                        )
                    )
                )
            )
        );

    // GENERIC FUNCTIONS -----------------------------------------------------

    // comparing :: (a -> b) -> (a -> a -> Ordering)
    const comparing = f =>
        (x, y) => {
            const
                a = f(x),
                b = f(y);
            return a < b ? -1 : (a > b ? 1 : 0);
        };

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) =>
        xs.length > 0 ? (() => {
            const unit = typeof xs[0] === 'string' ? '' : [];
            return unit.concat.apply(unit, xs.map(f));
        })() : [];

    // curry :: Function -> Function
    const curry = (f, ...args) => {
        const go = xs => xs.length >= f.length ? (f.apply(null, xs)) :
            function () {
                return go(xs.concat(Array.from(arguments)));
            };
        return go([].slice.call(args, 1));
    };

    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = f => (a, b) => f.apply(null, [b, a]);

    // foldl :: (b -> a -> b) -> b -> [a] -> b
    const foldl = (f, a, xs) => xs.reduce(f, a);

    // groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
    const groupBy = (f, xs) => {
        const dct = xs.slice(1)
            .reduce((a, x) => {
                const
                    h = a.active.length > 0 ? a.active[0] : undefined,
                    blnGroup = h !== undefined && f(h, x);
                return {
                    active: blnGroup ? a.active.concat([x]) : [x],
                    sofar: blnGroup ? a.sofar : a.sofar.concat([a.active])
                };
            }, {
                active: xs.length > 0 ? [xs[0]] : [],
                sofar: []
            });
        return dct.sofar.concat(dct.active.length > 0 ? [dct.active] : []);
    };

    // head :: [a] -> a
    const head = xs => xs.length ? xs[0] : undefined;

    // intercalate :: String -> [a] -> String
    const intercalate = curry((s, xs) => xs.join(s));

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // mappendComparing :: [(a -> b)] -> (a -> a -> Ordering)
    const mappendComparing = fs => (x, y) =>
        fs.reduce((ord, f) => (ord !== 0) ? (
            ord
        ) : (() => {
            const
                a = f(x),
                b = f(y);
            return a < b ? -1 : a > b ? 1 : 0
        })(), 0);

    // on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
    const on = (f, g) => (a, b) => f(g(a), g(b));

    // reverse :: [a] -> [a]
    const reverse = xs =>
        typeof xs === 'string' ? (
            xs.split('')
            .reverse()
            .join('')
        ) : xs.slice(0)
        .reverse();

    // same :: a -> a -> Bool
    const same = (a, b) => a === b

    // show :: Int -> a -> Indented String
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[1], null, x[0]] : x
        );

    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = (f, xs) =>
        xs.slice()
        .sort(f);

    // tail :: [a] -> [a]
    const tail = xs => xs.length ? xs.slice(1) : undefined;

    // take :: Int -> [a] -> [a]
    const take = curry((n, xs) => xs.slice(0, n));

    // toLower :: Text -> Text
    const toLower = s => s.toLowerCase();

    // TEST ------------------------------------------------------------------

    const xs = [
        ["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"]
    ];

    return show(2,
        topNSalariesPerDept(3, xs)
    );
})();
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"
]

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"
  }

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 } ] );

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"
    ]
  }
]

Jsish

Based on Javascript, imperative solution

#!/usr/bin/env jsish
/* Top rank per group, in Jsish */
function top_rank(n) {
    var by_dept = group_by_dept(data);
    for (var dept in by_dept) {
        puts(dept);
        for (var i = 0; i < n && i < by_dept[dept].length; i++) {
            var emp = by_dept[dept][i];
            puts(emp.name + ", id=" + emp.id + ", salary=" + emp.salary);
        }
        puts("");
    }
}
 
// group by dept, and sort by salary
function group_by_dept(data) {
    var by_dept = {};
    for (var idx in data)  {
        var dept = data[idx].dept;
        if ( !by_dept.hasOwnProperty(dept)) {
            by_dept[dept] = new Array();
        }
        by_dept[dept].push(data[idx]);
    }
    for (var dept in by_dept) {
        by_dept[dept].sort(function (a,b) { return b.salary - a.salary; });
    }
    return by_dept;
}
 
if (Interp.conf('unitTest')) {
    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"}
    ];
     
    top_rank(3);
}

/*
=!EXPECTSTART!=
D050
John Rappl, id=E21437, salary=47000
Nathan Adams, id=E41298, salary=21900

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

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

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

=!EXPECTEND!=
*/
Output:
prompt$ jsish --U topRankPerGroup.jsi
D050
John Rappl, id=E21437, salary=47000
Nathan Adams, id=E41298, salary=21900

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

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

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

prompt$ jsish -u topRankPerGroup.jsi
[PASS] topRankPerGroup.jsi

Julia

# v0.6.0

using DataFrames

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

# To get only values
function firstnby(n::Int, y::Array, by::Array)
    # Check that each value belong to one and one only class
    if length(y) != length(by); error("y and by must have the same length"); end

    # Initialize resulting dictionary
    rst = Dict{eltype(by), Array{eltype(y)}}()

    # For each class...
    for cl in unique(by)
        # ...select the values of that class...
        i = find(x -> x == cl, by)
        # ...sort them and store them in result...
        rst[cl] = sort(y[i]; rev=true)
        # ...if length is greater than n select only first n elements
        if length(i) > n
            rst[cl] = rst[cl][1:n]
        end
    end
    return rst
end

for (cl, val) in firstnby(3, Array(df[:Salary]), Array(df[:Department]))
    println("$cl => $val")
end

# To get the full row...
function firstnby(n::Int, df::DataFrame, y::Symbol, by::Symbol)
    rst = Dict{eltype(df[by]), DataFrame}()

    for cl in unique(df[by])
        i = find(x -> x == cl, df[by])
        rst[cl] = sort(df[i, :]; cols=order(y; rev=true))
        if length(i) > n
            rst[cl] = rst[cl][1:n, :]
        end
    end
    return rst
end

for (cl, data) in firstnby(3, df, :Salary, :Department)
    println("\n$cl:\n$data")
end
Output:
D202 => [49500, 27800, 19250]
D101 => [53500, 41500, 32000]
D050 => [47000, 21900]
D190 => [57000, 29900]

D202:
3×4 DataFrames.DataFrame
│ Row │ EmployeeName      │ EmployeeID │ Salary │ Department │
├─────┼───────────────────┼────────────┼────────┼────────────┤
│ 1   │ "Rich Holcomb"    │ "E01234"   │ 49500  │ "D202"     │
│ 2   │ "Claire Buckman"  │ "E39876"   │ 27800  │ "D202"     │
│ 3   │ "David Motsinger" │ "E27002"   │ 19250  │ "D202"     │

D101:
3×4 DataFrames.DataFrame
│ Row │ EmployeeName      │ EmployeeID │ Salary │ Department │
├─────┼───────────────────┼────────────┼────────┼────────────┤
│ 1   │ "George Woltman"  │ "E00127"   │ 53500  │ "D101"     │
│ 2   │ "David McClellan" │ "E04242"   │ 41500  │ "D101"     │
│ 3   │ "Tyler Bennett"   │ "E10297"   │ 32000  │ "D101"     │

D050:
2×4 DataFrames.DataFrame
│ Row │ EmployeeName   │ EmployeeID │ Salary │ Department │
├─────┼────────────────┼────────────┼────────┼────────────┤
│ 1   │ "John Rappl"   │ "E21437"   │ 47000  │ "D050"     │
│ 2   │ "Nathan Adams" │ "E41298"   │ 21900  │ "D050"     │

D190:
2×4 DataFrames.DataFrame
│ Row │ EmployeeName    │ EmployeeID │ Salary │ Department │
├─────┼─────────────────┼────────────┼────────┼────────────┤
│ 1   │ "Kim Arlich"    │ "E10001"   │ 57000  │ "D190"     │
│ 2   │ "Timothy Grove" │ "E16398"   │ 29900  │ "D190"     │

Kotlin

// version 1.1.2

data class Employee(val name: String, val id: String, val salary: Int, val dept: String)

const val N = 2 //say

fun main(args: Array<String>) {
    val employees = listOf(
        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")
    )
    val employeesByDept = employees.sortedBy { it.dept }.groupBy { it.dept }
    println("Highest $N salaries by department:\n")
    for ((key, value) in employeesByDept) {
        val topRanked = value.sortedByDescending { it.salary }.take(N)
        println("Dept $key => ")
        for (i in 0 until N) with (topRanked[i]) { println("${name.padEnd(15)} $id $salary") }
        println()
    }
}
Output:
Highest 2 salaries by department:

Dept D050 => 
John Rappl      E21437 47000
Nathan Adams    E41298 21900

Dept D101 => 
George Woltman  E00127 53500
David McClellan E04242 41500

Dept D190 => 
Kim Arlich      E10001 57000
Timothy Grove   E16398 29900

Dept D202 => 
Rich Holcomb    E01234 49500
Claire Buckman  E39876 27800

Ksh

#!/bin/ksh
exec 2> /tmp/Top_rank_per_group.err

# Top rank per group

#	# Variables:
#
integer TOP_NUM=2

typeset -T Empsal_t=(
	typeset -h		'Employee Name'			ename=''
	typeset -h		'Employee ID'			eid=''
	typeset -i -h	'Employee Salary'		esalary
	typeset -h		'Emplyee Department'	edept=''

	function init_employee {
		typeset buff ; buff="$1"

		typeset oldIFS ; oldIFS="$IFS"
		typeset arr ; typeset -a arr
		IFS=\,
		arr=( ${buff} )

		_.ename="${arr[0]}"
		_.eid="${arr[1]}"
		_.esalary=${arr[2]}
		_.edept="${arr[3]}"

		IFS="${oldIFS}"
	}
)

edata='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'

 ######
# main #
 ######

#	# Employee data into array of Types
#
typeset -a empsal_t		# array of Type variables (objects)
integer j i=0
echo "${edata}" | while read; do
	Empsal_t empsal_t[i]					# Create Type (object)
	empsal_t[i++].init_employee "$REPLY"	# Initialize Type (object)
done

#	# Sort the array of Type variables
#
set -a -s -A empsal_t -K edept,esalary:n:r,ename

#	# BUG work around! duplicate the now sorted Type array and use it for output
#
sorted=$(typeset -p empsal_t) && sorted=${sorted/empsal_t/sorted} && eval ${sorted}

for ((i=0; i<${#sorted[*]}; i++)); do
	if [[ ${sorted[i].edept} != ${prevdept} ]] || (( j < TOP_NUM )); then
		[[ ${sorted[i].edept} != ${prevdept} ]] && j=0
		print "${sorted[i].edept} ${sorted[i].esalary} ${sorted[i].eid} ${sorted[i].ename}"
		prevdept=${sorted[i].edept}
		(( j++ ))
	fi
done
Output:

D050 47000 E21437 John Rappl D050 21900 E41298 Nathan Adams D101 53500 E00127 George Woltman D101 41500 E04242 David McClellan D190 57000 E10001 Kim Arlich D190 29900 E16398 Timothy Grove D202 49500 E01234 Rich Holcomb

D202 27800 E39876 Claire Buckman

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

Object-Oriented

--Employee Class
local EmployeeMethods = {
    
}
local EmployeeMetamethods = {
    __index=EmployeeMethods,
    __tostring=function(self)
        return ("%s  %s  %s  %s"):format(self.Name,self.EmployeeId,self.Salary,self.DepartmentName)
    end,
    __metatable="Locked",
}
local EmployeeBase = {}

EmployeeBase.new = function(Name,EmployeeId,Salary,DepartmentName)
    return setmetatable({
        Name=Name,
        EmployeeId=EmployeeId,
        Salary=Salary,
        DepartmentName=DepartmentName,
    },EmployeeMetamethods)
end

--Department Class
local DepartmentMethods = {
    NewEmployee=function(self,Employee)
        table.insert(self.__Employees,Employee)
    end,
    CalculateHighestSalaries=function(self,Amount)
        local Highest = {}
        local EL = #self.__Employees
        table.sort(self.__Employees,function(a,b)
            return a.Salary > b.Salary
        end)
        for i=1,Amount do
            if i>EL then
               break 
            end
            table.insert(Highest,self.__Employees[i])
        end
        return Highest
    end,
}
local DepartmentMetamethods = {
    __index=DepartmentMethods,
    __tostring=function(self)
        return ("Department %s"):format(self.Name)
    end,
    __metatable="Locked",
}
local DepartmentBase = {
    __Departments={},
}

DepartmentBase.new = function(Name)
    local Department = DepartmentBase.__Departments[Name]
    if Department then return Department end
    Department = setmetatable({
        __Employees={},
        Name=Name,
    },DepartmentMetamethods)
    DepartmentBase.__Departments[Name] = Department
    return Department
end

--Main Program

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

for _,Employee in next,Employees do
    local Department = DepartmentBase.new(Employee.DepartmentName)
    Department:NewEmployee(Employee)
end

for _,Department in next,DepartmentBase.__Departments do
    local Highest = Department:CalculateHighestSalaries(2)
    print(Department)
    for _,Employee in next,Highest do
        print("\t"..tostring(Employee))
    end
end
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
Department D190
	Kim Arlich  E10001  57000  D190
	Timothy Grove  E16398  29900  D190
Department D202
	Rich Holcomb  E01234  49500  D202
	Claire Buckman  E39876  27800  D202

M2000 Interpreter

Module Checkit {
      ' erase stack of values, so we can add data
      Flush
      Input "N=",N
      Enum Departments {D050,D101,D190,D202}

      \\ Inventory Department need unique keys
      Inventory Department
      \\ each item in this inventory should be an inventory too
      Class Empl {
            name$, id$, salary
            Class:
            Module Empl(.name$, .id$, .salary) {}
      }
      Data "Tyler Bennett","E10297",32000,D101
      Data "John Rappl","E21437",47000,D050
      Data "George Woltman","E00127",53500,D101
      Data "Adam Smith","E63535",18000,D202
      Data "Claire Buckman","E39876",27800,D202
      Data "David McClellan","E04242",41500,D101
      Data "Rich Holcomb","E01234",49500,D202
      Data "Nathan Adams","E41298",21900,D050
      Data "Richard Potter","E43128",15900,D101
      Data "David Motsinger","E27002",19250,D202
      Data "Tim Sampair","E03033",27000,D101
      Data "Kim Arlich","E10001",57000,D190
      Data "Timothy Grove","E16398",29900,D190
      Data ""
      Read name$
      While name$<>""  {
            Read id$, salary, dep
            Rem : Print name$, id$, salary, dep
            If Exist(Department, dep) Then {
                  z=Eval(Department)    ' get pointer to inventory
                  AppendOne()            
            } Else {
                  z=queue
                  AppendDep()            
                  AppendOne()
            }
            Read name$
      }
      Sort Department as number
      i=each(Department)
      \\ make depname as type of Departments
      depname=D050
      Print  "Dep.  Employee Name       Emp. ID Salary"
      While i {
            \\ when we pass a number to a enum variable
            \\ if the number exist, get that enum item else raise error
            depname=val(eval$(i, i^))
            \\ z is a pointer to inventory 
            z=Eval(i)
            Sort descending z as number
            k=each(z,1,N)
            While k {
                  Empl=Eval(k)
                  For Empl {
                        \\ eval$(depname) return the name of enum variable (like D050)
                        Print Format$("{0:6}{1:20}{2:8}{3::-8}",Eval$(depname), .name$, .id$, .salary)
                  }
            }
      }
      Print "Done"
      Sub AppendDep()
            Append Department, dep:=z
      End Sub
      Sub AppendOne()
                  Append z, salary:=Empl(name$, id$, salary)
      End Sub
}
Checkit
Output:
N=1
Dep.  Employee Name       Emp. ID Salary
D050  John Rappl          E21437     47000
D101  George Woltman      E00127     53500
D190  Kim Arlich          E10001     57000
D202  Rich Holcomb        E01234     49500
Done

N=2
Dep.  Employee Name       Emp. ID Salary
D050  John Rappl          E21437     47000
D050  Nathan Adams        E41298     21900
D101  George Woltman      E00127     53500
D101  David McClellan     E04242     41500
D190  Kim Arlich          E10001     57000
D190  Timothy Grove       E16398     29900
D202  Rich Holcomb        E01234     49500
D202  Claire Buckman      E39876     27800
Done

Mathematica/Wolfram Language

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

Nim

Translation of: C
import algorithm

type Record = tuple[name, id: string; salary: int; department: string]

var people = [("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: Record): int =
  result = cmp(a.department, b.department)
  if result == 0:
    result = cmp(b.salary, a.salary)

proc printTop(people: openArray[Record]; n: Positive) =
  var people = sorted(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

people.printTop(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

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)

Oforth

First sort by department. Then group elements with same department.

Then sort each department by salaries and keep only the first n elements.

Object Class new: Employee(name, id, salary, dep)
 
Employee method: initialize  := dep  := salary := id := name ;
Employee method: salary  @salary ;
Employee method: dep     @dep ;
Employee method: <<      "[" << @dep << "," << @name << "," << @salary << "]" << ;
 
: topRank(n)
| employees |
   ListBuffer new ->employees
 
   Employee new("Tyler Bennett",   "E10297", 32000, "D101") employees add
   Employee new("John Rappl",      "E21437", 47000, "D050") employees add
   Employee new("George Woltman",  "E00127", 53500, "D101") employees add
   Employee new("Adam Smith",      "E63535", 18000, "D202") employees add
   Employee new("Claire Buckman",  "E39876", 27800, "D202") employees add
   Employee new("David McClellan", "E04242", 41500, "D101") employees add
   Employee new("Rich Holcomb",    "E01234", 49500, "D202") employees add
   Employee new("Nathan Adams",    "E41298", 21900, "D050") employees add
   Employee new("Richard Potter",  "E43128", 15900, "D101") employees add
   Employee new("David Motsinger", "E27002", 19250, "D202") employees add
   Employee new("Tim Sampair",     "E03033", 27000, "D101") employees add
   Employee new("Kim Arlich",      "E10001", 57000, "D190") employees add
   Employee new("Timothy Grove",   "E16398", 29900, "D190") employees add
 
   #dep employees sortBy groupWith( #dep ) 
   map(#[ sortBy(#[ salary neg ]) left(n) ]) apply(#println) ;
Output:
>topRank(3)
[[D050,John Rappl,47000], [D050,Nathan Adams,21900]]
[[D101,George Woltman,53500], [D101,David McClellan,41500], [D101,Tyler Bennett,32000]]
[[D190,Kim Arlich,57000], [D190,Timothy Grove,29900]]
[[D202,Rich Holcomb,49500], [D202,Claire Buckman,27800], [D202,David Motsinger,19250]]

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}}

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)

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

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

my $N = shift || 3;

foreach my $d (uniq sort 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";
}
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

Phix

with javascript_semantics
constant N=3
 
--                      Employee Name,Employee ID,Salary,Department
enum                    /*NAME,*/        /*ID,*/  SAL=3, DEPT=4
constant 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"}}
 
function by_dept_sal(integer i, j)
    return compare(employees[i][DEPT]&-employees[i][SAL],
                   employees[j][DEPT]&-employees[j][SAL])
end function
 
sequence tags = custom_sort(by_dept_sal,tagset(length(employees)))
 
string lastdep = ""
integer dcount = 0
printf(1,"Top %d salaries by department\n",{N})
for i=1 to length(employees) do
    object emp = employees[tags[i]]
    if emp[DEPT]!=lastdep then
        lastdep = emp[DEPT]
        dcount = 1
        printf(1,"\n")
    else
        dcount += 1
    end if
    if dcount<=N then
        ?emp
    end if
end for
Output:
Top 3 salaries by department

{"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"}

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

Picat

go =>
  Emp = [
          % 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"]
        ],
  print_top_ranks(Emp,3),
  nl.

print_top_ranks(Emp,N) =>
  printf("Top %d ranks per department:\n", N),
  foreach(Dept in [Dept : [_,_,_,Dept] in Emp].sort_remove_dups())
     printf("Department %s\n", Dept),
     Emp2 = sort_down([[Salary,Name] : [Name,_,Salary,D] in Emp, D = Dept]),
     foreach({[Salary,Name],E} in zip(Emp2,1..Emp2.length), E <= N)
       printf("%-20s %-10s\n", Name, Salary)
     end,
     nl
  end.
Output:
Top 3 ranks per department:
Department D050
John Rappl           47000      
Nathan Adams         21900      

Department D101
George Woltman       53500      
David McClellan      41500      
Tyler Bennett        32000      

Department D190
Kim Arlich           57000      
Timothy Grove        29900      

Department D202
Rich Holcomb         49500      
Claire Buckman       27800      
David Motsinger      19250

Using data as facts

go2 =>
  Emp = findall([Name,ID,Salary,Dept],emp(Name,ID,Salary,Dept)),
  print_top_ranks(Emp,3),
  nl.

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

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

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

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;

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

Prolog

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

departments(Depts) :-  % Find the set of departments
  findall(Dpt, emp(_,_,_,Dpt), DList), list_to_set(DList, Depts).

greater(emp(_,_,Sal1,_), emp(_,_,Sal2,_)) :- 
  Sal1 > Sal2.  % First employee salary greater than second

% Maintains a decreasing ordered list of employees truncated after (N) items.
%  Rule 1: For N=0, always return an empty set.
%  Rule 2: Add employee with greater salary at start of list, call with N-1
%  Rule 3: Try to add new employee at N-1
%  Rule 4: for an empty input list regardless of N, add the new employee
topSalary(0, _, _, []).
topSalary(N, Emp, [E|R], [Emp|Res]) :-  
  greater(Emp,E), N0 is N - 1, !, topSalary(N0, E, R, Res).
topSalary(N, Emp, [E|R], [E|Res]) :-  
  N0 is N - 1, !, topSalary(N0, Emp, R, Res).
topSalary(_, Emp, [], [Emp]).

% For each employee, add him to the list if top salary
topEmps(N, [Emp|Emps], R, Res) :-     
  topSalary(N, Emp, R, Rt), !, topEmps(N, Emps, Rt, Res).
topEmps(_, [], Res, Res).

% For each department, find the list of top employees in that department
topDeps(N, [Dept|T], [dept(Dept,Ro)|Res]) :-
  findall(emp(Name, Id, Sal, Dept), emp(Name, Id, Sal, Dept), Emps),
  topEmps(N, Emps, [], Ro), !, topDeps(N, T, Res).  
topDeps(_, [], []).

% Calculate and report the list of highest salaried employees per department
topDeps(N) :-
  departments(D), topDeps(N, D, Res),
  member(dept(Dept,R), Res),
  writef('Department: %w\n', [Dept]),
  member(emp(Name,Id,Sal,_), R),
  writef('  ID: %w\t%w\tSalary: %w\n', [Id,Name,Sal]),
  fail.
topDeps(_).
Output:
?- topDeps(2).
Department: D101
  ID: E00127    George Woltman  Salary: 53500
  ID: E04242    David McClellan Salary: 41500
Department: D050
  ID: E21437    John Rappl  Salary: 47000
  ID: E41298    Nathan Adams    Salary: 21900
Department: D202
  ID: E01234    Rich Holcomb    Salary: 49500
  ID: E39876    Claire Buckman  Salary: 27800
Department: D190
  ID: E10001    Kim Arlich  Salary: 57000
  ID: E16398    Timothy Grove   Salary: 29900
true.

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

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:

Works with: Python version 3.7
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')

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

With dplyr

With the dplyr package we can use group_by and top_n to select the top n records in each group.

library(dplyr)
dfr %>% 
  group_by(Department) %>% 
  top_n(2, Salary)

Provides:

Employee.Name   Employee.ID Salary Department
  <fct>           <fct>        <int> <fct>     
John Rappl      E21437       47000 D050      
George Woltman  E00127       53500 D101      
Claire Buckman  E39876       27800 D202      
David McClellan E04242       41500 D101      
Rich Holcomb    E01234       49500 D202      
Nathan Adams    E41298       21900 D050      
Kim Arlich      E10001       57000 D190      
Timothy Grove   E16398       29900 D190      

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)

Raku

(formerly Perl 6) We use whitespace-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 Raku 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(/ \s\s+ /)
    )
}

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

REXX

version 1

/*REXX program displays the top   N   salaries in each department (internal table).     */
parse arg topN .                                 /*get optional # for the top N salaries*/
if topN=='' | topN==","  then topN= 1            /*Not specified?  Then use the default.*/
say 'Finding the 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=                                           /*build people database from  @  array.*/
              do j=1  until @.j=='';  parse var @.j name.j ',' id.j "," sal.j ',' dept.j .
              if wordpos(dept.j, depts)==0  then depts= depts dept.j       /*a new DEPT?*/
              end   /*j*/
employees= j-1                                   /*adjust for the bumped  DO loop index.*/
say 'There are '   employees   "employees, "    words(depts)     'departments: '     depts
say
   do dep=1  for words(depts);       say         /*process each of the departments.     */
   Xdept= word(depts, dep)                       /*current department being processed.  */
      do topN;        h= 0;          highSal= 0  /*process the top  N  salaries.        */
               do e=1  for employees             /*process each employee in department. */
               if dept.e\==Xdept | sal.e<highSal then iterate  /*is this the wrong info?*/
               highSal= sal.e;        h= e       /*a higher salary was just discovered. */
               end   /*e*/
      if h==0  then iterate                      /*do we have no highest paid this time?*/
      say 'department:  '      dept.h       " $" || sal.h+0        id.h      space(name.h)
      dept.h=                                    /*make sure we see the employee again. */
      end         /*topN*/
   end             /*dep*/                       /*stick a fork in it,  we're all done. */
output   when using the input:     2
Finding the top  2  salaries in each department.

There are  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 using the input:     100
Finding the top  100  salaries in each department.

There are  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

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

Ring

# Project : Top rank per group

load "stdlib.ring"
salary = "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"

temp = substr(salary, ",", nl)
temp = str2list(temp)
depsal = newlist(13,4)
for n = 1 to len(temp)
     n1 = ceil(n/4)
     n2 = n%4
     if n2 = 0
        n2 = 4
     ok
     depsal[n1][n2] = temp[n]
next
for n = 1 to len(depsal)-1
     for m = n+1 to len(depsal)
          if strcmp(depsal[m][4], depsal[n][4]) < 0 
              tmp = depsal[n]
              depsal[n] = depsal[m]
              depsal[m] = tmp
          ok
      next
next
for n = 1 to len(depsal)-1
     for m = n+1 to len(depsal)
           if (depsal[m][4] = depsal[n][4]) and (depsal[m][3] > depsal[n][3]) 
               tmp = depsal[n]
               depsal[n] = depsal[m]
               depsal[m] = tmp
           ok
      next
next
see "Department : " + depsal[1][4] + nl
see "Name                   " + "Id             " + "Salary" + nl + nl
see "" + depsal[1][1] + "      " + depsal[1][2] + "      " + depsal[1][3]+ nl
for n = 1 to len(depsal)-1
    if (depsal[n+1][4] != depsal[n][4])
        see nl
        see "Department : " + depsal[n+1][4] + nl
        see "Name                   " + "Id             " + "Salary" + nl + nl
        see "" + depsal[n+1][1] + "      " + depsal[n+1][2] + "      " + depsal[n+1][3]+ nl
     else
        see "" + depsal[n+1][1] + "      " + depsal[n+1][2] + "      " + depsal[n+1][3]+ nl
     ok
next

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
Tim Sampair            E03033         27000
Richard Potter         E43128         15900

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
Adam Smith             E63535         18000

Ruby

Without much thought to report formatting:

Works with: Ruby version 2.2+
require "csv"

data = <<EOS
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
EOS

def show_top_salaries_per_group(data, n)
  table = CSV.parse(data, :headers=>true, :header_converters=>:symbol)
  groups = table.group_by{|emp| emp[:department]}.sort
  groups.each do |dept, emps|
    puts dept
    # max by salary
    emps.max_by(n) {|emp| emp[:salary].to_i}.each do |e|
      puts "    %-16s %6s %7d" % [e[:employee_name], e[:employee_id], e[:salary]]
    end
    puts
  end
end

show_top_salaries_per_group(data, 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

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

Rust

The implementation counts with ties.

#[derive(Debug)]
struct Employee<S> {
    // Allow S to be any suitable string representation
    id: S,
    name: S,
    department: S,
    salary: u32,
}

impl<S> Employee<S> {
    fn new(name: S, id: S, salary: u32, department: S) -> Self {
        Self {
            id,
            name,
            department,
            salary,
        }
    }
}

#[rustfmt::skip]
fn load_data() -> Vec<Employee<&'static str>> {
    vec![
        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"),
        // Added to demonstrate various tie situations
        Employee::new("Kim Tie",          "E16400", 57000, "D190"),
        Employee::new("Timothy Tie",      "E16401", 29900, "D190"),
        Employee::new("Timothy Kim",      "E16401", 19900, "D190"),
    ]
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let n = {
        println!("How many top salaries to list? ");
        let mut buf = String::new();
        std::io::stdin().read_line(&mut buf)?;
        buf.trim().parse::<u32>()?
    };

    let mut employees = load_data();

    // Reverse order, then just pick top N employees
    employees.sort_by(|a, b| b.salary.cmp(&a.salary));

    let sorted = employees
        .into_iter()
        .fold(std::collections::BTreeMap::new(), |mut acc, next| {
            // We store the number of unique salaries as well to handle
            // ties (and list always all employees with the same salary)
            let mut bucket = acc
                .entry(next.department)
                .or_insert_with(|| (0, Vec::<Employee<_>>::new()));

            match bucket.1.last().map(|e| e.salary) {
                Some(last_salary) if last_salary == next.salary => {
                    if bucket.0 <= n {
                        bucket.1.push(next);
                    }
                }

                _ => {
                    if bucket.0 < n {
                        bucket.0 += 1; // Next unique salary
                        bucket.1.push(next);
                    }
                }
            }

            acc
        });

    for (department, (_, employees)) in sorted {
        println!("{}", department);

        employees
            .iter()
            .for_each(|employee| println!("    {:?}", employee));
    }

    Ok(())
}
Output:
How many top salaries to list?
3
D050
    Employee { id: "E21437", name: "John Rappl", department: "D050", salary: 47000 }
    Employee { id: "E41298", name: "Nathan Adams", department: "D050", salary: 21900 }
D101
    Employee { id: "E00127", name: "George Woltman", department: "D101", salary: 53500 }
    Employee { id: "E04242", name: "David McClellan", department: "D101", salary: 41500 }
    Employee { id: "E10297", name: "Tyler Bennett", department: "D101", salary: 32000 }
D190
    Employee { id: "E10001", name: "Kim Arlich", department: "D190", salary: 57000 }
    Employee { id: "E16400", name: "Kim Tie", department: "D190", salary: 57000 }
    Employee { id: "E16398", name: "Timothy Grove", department: "D190", salary: 29900 }
    Employee { id: "E16401", name: "Timothy Tie", department: "D190", salary: 29900 }
    Employee { id: "E16401", name: "Timothy Kim", department: "D190", salary: 19900 }
D202
    Employee { id: "E01234", name: "Rich Holcomb", department: "D202", salary: 49500 }
    Employee { id: "E39876", name: "Claire Buckman", department: "D202", salary: 27800 }
    Employee { id: "E27002", name: "David Motsinger", department: "D202", salary: 19250 }

Scala

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

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

Scheme

Works with: Gauche Scheme
(use gauche.record)

;;  This will let us treat a list as though it is a structure (record).
(define-record-type (employee (pseudo-rtd <list>)) #t #t
  name id salary dept)

(define (get-fields str)
  (map (^x (if (#/^\d/ x) (string->number x) x))
       (string-split str #\,)))

(define (print-record column-widths record)
  (display "  ")
  (print (string-join
    (map
      (^(x width)
        (if (number? x)
          (format "~vD" width x)
          (format "~vA" width x)))
      record
      column-widths))))

(define (get-column-widths records)
  (apply
    map
      (lambda column
        (apply max (map (compose string-length x->string) column)))
      records))

(define records
  (map get-fields
    (string-split
      "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"
      #/\s*\n\s*/)))

(define (top-salaries n records)
  (let ((departments (sort (delete-duplicates (map employee-dept records))))
        (col-widths (get-column-widths records))
        (sorted-by-salary (sort records > employee-salary)))
    (dolist (dept departments)
      (print dept)
      (let1 matches (filter (^x (string=? dept (employee-dept x)))
                            sorted-by-salary)
        (for-each
          (pa$ print-record col-widths)
          (take* matches n))))))

Testing:

gosh> (top-salaries 0 records)
D050
D101
D190
D202

gosh> (top-salaries 2 records)
D050
  John Rappl      E21437 47000 D050
  Nathan Adams    E41298 21900 D050
D101
  George Woltman  E00127 53500 D101
  David McClellan E04242 41500 D101
D190
  Kim Arlich      E10001 57000 D190
  Timothy Grove   E16398 29900 D190
D202
  Rich Holcomb    E01234 49500 D202
  Claire Buckman  E39876 27800 D202

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

Sidef

Translation of: Perl
var data = <<'EOF'.lines.map{ <name id salary dept> ~Z .split(',') -> flat.to_h }
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

var n = (ARGV ? Num(ARGV[0]) : "usage: #{__MAIN__} [n]\n".die)

for d in (data.map {|h| h{:dept} }.uniq.sort) {
    var es = data.grep { _{:dept} == d }.sort_by { -Num(_{:salary}) }
    say d
    n.times {
        es || break
        printf("%-15s | %-6s | %5d\n", es.shift(){<name id salary>...})
    }
    print "\n"
}
Output:
$ sidef top_rank.sf 3
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

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.

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.

Since there is no requirement for the layout - using the data from the table above

WITH ranked_emp AS (
    SELECT  emp_name
           ,dept_id
           ,salary
           ,DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary ) ranking
    FROM    emp
    )
SELECT  dept_id
       ,ranking
       ,emp_name
       ,salary
FROM    ranked_emp
WHERE   ranking <= 2
ORDER BY dept_id, ranking;
DEPT    RANKING EMP_NAME                 SALARY
---- ---------- -------------------- ----------
D050          1 Nathan Adams              21900
D050          2 John Rappl                47000
D101          1 Richard Potter            15900
D101          2 Tim Sampair               27000
D190          1 Timothy Grove             29900
D190          1 Timothy Grave             29900
D190          1 Timothy Grive             29900
D190          2 Kim Arlich                57000
D202          1 Adam Smith                18000
D202          2 David Motsinger           19250

Stata

The following shows the top k salaries. If there are less than k employees in a department, this will print all salaries.

import delimited employees.csv
local k 2
bysort department (salary): list salary if _N-_n<`k'

Swift

struct Employee {
  var name: String
  var id: String
  var salary: Int
  var department: String
}

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

func highestSalaries(employees: [Employee], n: Int = 1) -> [String: [Employee]] {
  return employees.reduce(into: [:], {acc, employee in
    guard var cur = acc[employee.department] else {
      acc[employee.department] = [employee]

      return
    }

    if cur.count < n {
      cur.append(employee)
    } else if cur.last!.salary < employee.salary {
      cur[n - 1] = employee
    }

    acc[employee.department] = cur.sorted(by: { $0.salary > $1.salary })
  })
}

for (dept, employees) in highestSalaries(employees: employees, n: 3) {
  let employeeString = employees.map({ "\($0.name): \($0.salary)" }).joined(separator: "\n\t")

  print("\(dept)'s highest paid employees are: \n\t\(employeeString)")
}
Output:
D050's highest paid employees are: 
    John Rappl: 47000
    Nathan Adams: 21900
D101's highest paid employees are: 
    George Woltman: 53500
    David McClellan: 41500
    Tyler Bennett: 32000
D202's highest paid employees are: 
    Rich Holcomb: 49500
    Claire Buckman: 27800
    David Motsinger: 19250
D190's highest paid employees are: 
    Kim Arlich: 57000
    Timothy Grove: 29900

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


Transd

Version using built-in database-like functionality.

#lang transd


MainModule: {
tbl : String( 
`EmployeeName,EmployeeID,Salary:Int,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`),

    N: 2,
    _start: (λ 
    (with tabl Table()
        (load-table tabl tbl)
        (build-index tabl "Department")
        (with rows (tsd-query tabl 
            select: ["Department"] 
            as: [[String()]] :distinct sortby: "Department" )
        (for row in rows do
            (with recs (tsd-query tabl 
                select: all 
                    as: [[String(), String(), Int(), String()]]
            satisfying: (lambda Department String() 
                            (eq Department (get row 0))) 
                sortby: "Salary" :desc 
                 limit: N)
           (for rec in recs do (textout rec "\n")))))))
}
Output:
[D050, E21437, John Rappl, 47000]
[D050, E41298, Nathan Adams, 21900]
[D101, E00127, George Woltman, 53500]
[D101, E04242, David McClellan, 41500]
[D190, E10001, Kim Arlich, 57000]
[D190, E16398, Timothy Grove, 29900]
[D202, E01234, Rich Holcomb, 49500]
[D202, E39876, Claire Buckman, 27800]

Version using untyped vectors.

#lang transd

MainModule: {
    tbl : String( 
`"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"`),

    Record : typealias(Tuple<String String Int String>()),
    N: 2,
    curN: 0,
    dep: String(),
    deps: Index<String Int>(),
    _start: (λ 
    (with recs Vector<Record>( untypedTable tbl "," "\n" )
          res Vector<Record>()
        (sort recs Range(-1 0)
            (lambda l Record() r Record() -> Bool()
                (ret (less (get l 2) (get r 2)))))

        (for el in recs do 
            (= dep (get el 3))
            (= curN (snd (get-s deps dep 0)))
            (if (< curN N) (add res el) (set deps dep (+ curN 1)))
        )

        (sort res 
            (lambda l Record() r Record() -> Bool()
                (ret (less (get l 3) (get r 3)))))

        (for i in res do (textout i "\n"))
    ))
}
Output:
[[John Rappl, E21437, 47000, D050]]
[[Nathan Adams, E41298, 21900, D050]]
[[George Woltman, E00127, 53500, D101]]
[[David McClellan, E04242, 41500, D101]]
[[Kim Arlich, E10001, 57000, D190]]
[[Timothy Grove, E16398, 29900, D190]]
[[Rich Holcomb, E01234, 49500, D202]]
[[Claire Buckman, E39876, 27800, D202]]

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    

TXR

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.

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}`)))))

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

VBA

Creating an Excel pivot table to display the top n salaried employees per department. Data is read from a file.

Private Sub top_rank(filename As String, n As Integer)
    Workbooks.OpenText filename:=filename, Comma:=True
    Dim ws As Worksheet
    Set ws = Sheets.Add: ws.Name = "output"
    ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:= _
        "data!R1C1:R14C4", Version:=6).CreatePivotTable TableDestination:= _
        "output!R3C1", TableName:="TableName", DefaultVersion:=6
    With Sheets("output").PivotTables("TableName")
        .InGridDropZones = True
        .RowAxisLayout xlTabularRow
        .AddDataField Sheets("output").PivotTables("TableName"). _
                PivotFields("Salary"), "Top rank", xlSum
        .PivotFields("Department").Orientation = xlRowField
        .PivotFields("Department").Position = 1
        .PivotFields("Salary").Orientation = xlRowField
        .PivotFields("Salary").Position = 2
        .PivotFields("Employee Name").Orientation = xlRowField
        .PivotFields("Employee Name").Position = 3
        .PivotFields("Employee ID").Orientation = xlRowField
        .PivotFields("Employee ID").Position = 4
        .PivotFields("Salary").PivotFilters.Add2 Type:=xlTopCount, _
            DataField:=Sheets("output").PivotTables("TableName"). _
            PivotFields("Top rank"), Value1:=n
        .PivotFields("Salary").Subtotals = Array(False, False, False, False, _
            False, False, False, False, False, False, False, False)
        .PivotFields("Employee Name").Subtotals = Array(False, False, False, _
            False, False, False, False, False, False, False, False, False)
        .PivotFields("Department").Subtotals = Array(False, False, False, False, _
            False, False, False, False, False, False, False, False)
        .ColumnGrand = False
        .PivotFields("Salary").AutoSort xlDescending, "Salary"
    End With
End Sub

Public Sub main()
    top_rank filename:="D:\data.txt", n:=3
End Sub

Wren

Translation of: Kotlin
Library: Wren-dynamic
Library: Wren-sort
Library: Wren-seq
Library: Wren-fmt
import "./dynamic" for Tuple
import "./sort" for Sort, Cmp
import "./seq" for Lst
import "./fmt" for Fmt

var Employee = Tuple.create("Employee", ["name", "id", "salary", "dept"])

var N = 2 // say

var 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")
]
var cmpByDept = Fn.new { |employee1, employee2| Cmp.string.call(employee1.dept, employee2.dept) }
Sort.insertion(employees, cmpByDept)
var groupsByDept = Lst.groups(employees) { |e| e.dept }
System.print("Highest %(N) salaries by department:\n")
for (group in groupsByDept) {
    var dept = group[0]
    var groupEmployees = group[1].map { |i| i[0] }.toList
    var cmpBySalary = Fn.new { |employee1, employee2| Cmp.numDesc.call(employee1.salary, employee2.salary) }
    Sort.insertion(groupEmployees, cmpBySalary)
    var topRanked = groupEmployees.take(N)
    System.print("Dept %(dept) => ")
    topRanked.each { |e| Fmt.print("$-15s $s $d", e.name, e.id, e.salary) }
    System.print()
}
Output:
Highest 2 salaries by department:

Dept D050 => 
John Rappl      E21437 47000
Nathan Adams    E41298 21900

Dept D101 => 
George Woltman  E00127 53500
David McClellan E04242 41500

Dept D190 => 
Kim Arlich      E10001 57000
Timothy Grove   E16398 29900

Dept D202 => 
Rich Holcomb    E01234 49500
Claire Buckman  E39876 27800

XPL0

proc Sort(Array, Field, Size);  \Sort Array in descending order by Field
int  Array, Field, Size, I, J, T;
[for J:= Size-1 downto 0 do
    for I:= 0 to J-1 do
        if Array(I,Field) < Array(I+1,Field) then
            [T:= Array(I);  Array(I):= Array(I+1);  Array(I+1):= T];
];

int Data, I, I0, Dept, N, Cnt;
[Data:=[["Tyler Bennett",   "E10297", 32000, 101],
        ["John Rappl",      "E21437", 47000, 050],
        ["George Woltman",  "E00127", 53500, 101],
        ["Adam Smith",      "E63535", 18000, 202],
        ["Claire Buckman",  "E39876", 27800, 202],
        ["David McClellan", "E04242", 41500, 101],
        ["Rich Holcomb",    "E01234", 49500, 202],
        ["Nathan Adams",    "E41298", 21900, 050],
        ["Richard Potter",  "E43128", 15900, 101],
        ["David Motsinger", "E27002", 19250, 202],
        ["Tim Sampair",     "E03033", 27000, 101],
        ["Kim Arlich",      "E10001", 57000, 190],
        ["Timothy Grove",   "E16398", 29900, 190],
        [0,                  0,       0,     000]];     \sentinel
I:= 0;                  \find number of employees = Data size
while Data(I,0) # 0 do I:= I+1;
Sort(Data, 3, I);       \sort by department field (3)
I:= 0;                  \sort by salary within each department
while Data(I,0) do
        [Dept:= Data(I,3);
        I0:= I;
        repeat I:= I+1 until Data(I,3) # Dept;
        Sort(@Data(I0), 2, I-I0);
        ];
N:= IntIn(0);                           \get parameter
I:= 0;
loop    [Dept:= Data(I,3);              \for each department
        Text(0, "Department D");
        if Dept < 100 then ChOut(0, ^0);  IntOut(0, Dept);  Text(0, ":^m^j");
        Cnt:= 0;
        loop    [if Cnt >= N then
                    while Data(I,3) = Dept do I:= I+1;  \skip any remaining
                if Data(I,3) # Dept then quit;
                IntOut(0, Data(I,2)); Text(0, "   ");   \salary
                Text(0, Data(I,1));   Text(0, "   ");   \ID
                Text(0, Data(I,0));   CrLf(0);          \name
                Cnt:= Cnt+1;
                I:= I+1;
                ];
        if Data(I,0) = 0 then quit;
        ];
]
Output:
3
Department D202:
49500   E01234   Rich Holcomb
27800   E39876   Claire Buckman
19250   E27002   David Motsinger
Department D190:
57000   E10001   Kim Arlich
29900   E16398   Timothy Grove
Department D101:
53500   E00127   George Woltman
41500   E04242   David McClellan
32000   E10297   Tyler Bennett
Department D050:
47000   E21437   John Rappl
21900   E41298   Nathan Adams

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