Sort an array of composite structures

From Rosetta Code

Jump to: navigation, search

Programming Task
This is a programming task. It lays out a problem which Rosetta Code users are encouraged to solve, using languages they know.

Code examples should be formatted along the lines of one of the existing prototypes.

Sort an array of composite structures by a key. For example, if you define a composite structure that presents a name-value pair (in pseudocode):

Define structure pair such that: 
   name as a string
   value as a string

and an array of such pairs:

x: array of pairs

then define a sort routine that sorts the array x by the key name.

This task can always be accomplished with Sorting Using a Custom Comparator. If your language is not listed here, please see the other article.

Contents

[edit] Ada

There is no standard sort function for Ada. The example below implements a simple bubble sort.

with Ada.Text_Io;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

procedure Sort_Composite is
   type Composite_Record is record
      Name : Unbounded_String;
      Value : Unbounded_String;
   end record;
   
   type Pairs_Array is array(Positive range <>) of Composite_Record;
   
   procedure Swap(Left, Right : in out Composite_Record) is
      Temp : Composite_Record := Left;
   begin
      Left := Right;
      Right := Temp;
   end Swap; 
   
   -- Sort_Names uses a bubble sort
   
   procedure Sort_Name(Pairs : in out Pairs_Array) is
      Swap_Performed : Boolean := True;
   begin
      while Swap_Performed loop
         Swap_Performed := False;
         for I in Pairs'First..(Pairs'Last - 1) loop
            if Pairs(I).Name > Pairs(I + 1).Name then
               Swap (Pairs(I), Pairs(I + 1));
               Swap_Performed := True;
            end if;
         end loop;
      end loop;
   end Sort_Name;
   
   procedure Print(Item : Pairs_Array) is
   begin
      for I in Item'range loop
         Ada.Text_Io.Put_Line(To_String(Item(I).Name) & ", " & 
            to_String(Item(I).Value));
      end loop;
   end Print;
   type Names is (Fred, Barney, Wilma, Betty, Pebbles);
   type Values is (Home, Work, Cook, Eat, Bowl);
   My_Pairs : Pairs_Array(1..5);
begin
   for I in My_Pairs'range loop
      My_Pairs(I).Name := To_Unbounded_String(Names'Image(Names'Val(Integer(I - 1))));
      My_Pairs(I).Value := To_Unbounded_String(Values'Image(Values'Val(Integer(I - 1))));
   end loop;
   Print(My_Pairs);
   Ada.Text_Io.Put_Line("=========================");
   Sort_Name(My_Pairs);
   Print(My_Pairs);
end Sort_Composite;

[edit] Fortran

Works with: Fortran version 90 and later

Standard Fortran has no built-in sort function although some compilers add them. The following example uses an insertion sort.

PROGRAM EXAMPLE
  IMPLICIT NONE  

  TYPE Pair
    CHARACTER(6) :: name
    CHARACTER(1) :: value
  END TYPE Pair

  TYPE(Pair) :: rcc(10), temp
  INTEGER :: i, j

  rcc(1) = Pair("Black", "0")
  rcc(2) = Pair("Brown", "1")
  rcc(3) = Pair("Red", "2")
  rcc(4) = Pair("Orange", "3")
  rcc(5) = Pair("Yellow", "4") 
  rcc(6) = Pair("Green", "5")
  rcc(7) = Pair("Blue", "6")
  rcc(8) = Pair("Violet", "7")
  rcc(9) = Pair("Grey", "8")
  rcc(10) = Pair("White", "9")

  DO i = 2, SIZE(rcc)
     j = i - 1
     temp = rcc(i)
        DO WHILE (j>=1 .AND. LGT(rcc(j)%name, temp%name))
           rcc(j+1) = rcc(j)
           j = j - 1
        END DO
     rcc(j+1) = temp
  END DO

  WRITE (*,"(2A6)") rcc

END PROGRAM EXAMPLE

Output

Black      0
Blue       6
Brown      1
Green      5
Grey       8
Orange     3
Red        2
Violet     7
White      9
Yellow     4 

[edit] Common Lisp

In Common Lisp, the sort function takes a predicate that is used as the comparator. This parameter can be any two-argument function. Additionally, the sort function can take a keyword argument :key whose result is passed to the predicate.

Let's define a composite structure of U.S. states and average test scores.

CL-USER> (defparameter *test-scores* '(("texas" 68.9) ("ohio" 87.8) ("california" 76.2) ("new york" 88.2)) )
*TEST-SCORES*

We can sort by the state name by supplying a one-argument key function that is called by the sort function to determine the value to compare. In this case, the function is first will retrieve the state name:

CL-USER> (sort (copy-list *test-scores*) #'string-lessp :key #'first)
(("california" 76.2) ("new york" 88.2) ("ohio" 87.8) ("texas" 68.9))

we can also sort by the test scores by supplying a different key function that return the test score instead:

CL-USER> (sort (copy-list *test-scores*) #'< :key #'second)
(("texas" 68.9) ("california" 76.2) ("ohio" 87.8) ("new york" 88.2))

[edit] D

Works with: D version 2.011+

module sortcomposite ;
import std.stdio ;
import std.algorithm ;
 
struct S {
  string name ;
  string value ;
  int    amount ;
  string toString() {
    return "<" ~ name ~ "|" ~ value ~ "|" ~ std.string.toString(amount) ~ ">" ;
  }
}
 
T[] csort(string FIELD , T)(T[] arr) {
  mixin("sort!(\"a." ~ FIELD ~ " < b." ~ FIELD ~ "\")(arr) ;") ;
  //compile as statement :
  //      sort!("a.FIELD < b.FIELD")(arr) ;
  return arr ;
}
 
void main() {
  S[] datas  = [S("Joe",   "0x47f", 13), S("Bob",   "0x100", 31), 
                S("Alice", "0x3e8", 50), S("Harry", "0x2d4", 43)] ;
 
  sort!("a.name < b.name")(datas) ;
  writefln(datas) ;
  writefln(csort!("value")(datas)) ;
  writefln(csort!("amount")(datas)) ;
}

Limitation: The field name has to be compile-time determined.

[edit] Haskell

   import List
   
   data Person = P String Int deriving Eq
   instance Show Person where
       show (P name val) = "Person "++name++" with value "++(show val)
   instance Ord Person where
       compare (P n1 _) (P n2 _) = compare n1 n2
   
   people = [(P "Joe" 12), (P "Bob" 8), (P "Alice" 9), (P "Harry" 2)]
   sortedPeople = sort people
   sortedPeopleByVal = sortBy (\(P _ v1) (P _ v2)->compare v1 v2) people

[edit] J

The function /: sorts anything. For example:

   names =: ;: 'Perlis Wilkes Hamming Minsky Wilkinson McCarthy'
   values=: ;: 'Alan Maurice Richard Marvin James John'
   pairs =: values ,. names
   pairs /: names
+-------+---------+
|Richard|Hamming  |
+-------+---------+
|John   |McCarthy |
+-------+---------+
|Marvin |Minsky   |
+-------+---------+
|Alan   |Perlis   |
+-------+---------+
|Maurice|Wilkes   |
+-------+---------+
|James  |Wilkinson|
+-------+---------+

[edit] MAXScript

fn keyCmp comp1 comp2 =
(
    case of
    (
        (comp1[1] > comp2[1]):	1
        (comp1[1] < comp2[1]):	-1
        default:		0
    )
)

people = #(#("joe", 39), #("dave", 37), #("bob", 42))
qsort people keyCmp
print people

[edit] OCaml

# let people = [("Joe", 12); ("Bob", 8); ("Alice", 9); ("Harry", 2)];;
val people : (string * int) list =
  [("Joe", 12); ("Bob", 8); ("Alice", 9); ("Harry", 2)]
# let sortedPeopleByVal = List.sort (fun (_, v1) (_, v2) -> compare v1 v2) people;;
val sortedPeopleByVal : (string * int) list =
  [("Harry", 2); ("Bob", 8); ("Alice", 9); ("Joe", 12)]

[edit] Perl

Sort by name using cmp:

@people = (['joe', 120], ['foo', 31], ['bar', 51]);
@people = sort { $a->[0] cmp $b->[0] } @people;

Sort by number using <=>:

@people = (['joe', 120], ['foo', 31], ['bar', 51]);
@people = sort { $a->[1] <=> $b->[1] } @people;

[edit] Python

The easy case describe in this task, the name is the first item of a tuple, simple sort just works:

people = [('joe', 120), ('foo', 31), ('bar', 51)]
people.sort()

If the name is not the first one, you can use a cmp function:

people = [(120, 'joe'), (31, 'foo'), (51, 'bar')]
people.sort(lambda x: x[1])

But it is slow. To make it much faster, use Schwartzian_transform:

people = [(120, 'joe'), (31, 'foo'), (51, 'bar')]
tmp = [(person[1], person) for person in people]
tmp.sort()
people = [person for key, person in tmp]

Recent Python versions add a key argument, which make this much cleaner:

people = [(120, 'joe'), (31, 'foo'), (51, 'bar')]
people.sort(key=lambda x: x[0])

The most Pythonic (and faster) version is using itemgetter:

from operator import itemgetter
people = [(120, 'joe'), (31, 'foo'), (51, 'bar')]
people.sort(key=itemgetter(0))

[edit] Ruby

 Person = Struct.new(:name,:value)
 list = [Person.new("Joe",3),
         Person.new("Bill",4),
         Person.new("Alice",20),
         Person.new("Harry",3)]
 list.sort_by{|x|x.name}
Personal tools