Search a list of records: Difference between revisions

(Implementation in Standard ML)
(→‎{{header|Haskell}}: Add Fortran.)
Line 504:
4.58
</pre>
 
=={{header|Fortran}}==
In order to employ a compound data aggregate such as CITY with components CITY.NAME and CITY.POPULATION, F90 is required. Earlier, one would have employed a collection of separate arrays with similar names, such as CNAME and CPOP. This may still be desirable when array parameters from a data aggregate will be passed via copy-in and copy-out instead of by reference.
 
Fortran does not offer a "pass-by-name" facility, whereby the test function for the searches could be specified in the manner of [[Jensen's_Device]], something like <code>FINDFIRST(I,CITY(I).POPULATION < 5)</code> so one must devise a suitable procedure, and because the two components NAME and POPULATION have different types, the parameter matching protocol requires two different procedures. Via additional F90 syntax it is possible to define a "generic" routine with a single name that, when invoked, will in fact invoke the appropriate specific routine, however in this example one test is for equality, and the other is for "less than" so the commonality is weak. One could instead define two functions (one each for the two types of test, say NAMEISEQUAL and POPULATIONISLESS) and pass the appropriate function as a parameter to a more general FINFDFIRST routine, but this is messy.
 
Instead, two functions. They turn out to be general in the sense that they're not limited to a type of CITYSTAT. Instead, FIRSTMATCH searches an array of texts, and FIRSTLESS an array of floating-point numbers - they could be applied to any such arrays. But at a cost. They are not prepared to deal with arrays having a "stride" other than one, such as the CITY.NAME entries, whose successive elements are not in successive storage locations. Instead, the compiler generates code to copy the desired elements from the CITY aggregate into a temporary variable having that arrangement, and passes that to the routine by reference. One could use the special declaration <code>INTENT(IN)</code> for read-only activities, and that will at least abate the copy-back, but for small items and only ten at that, this overhead can be ignored. And it does allow the special usage <code>CITY.NAME(1:1)</code> to search only the first character of each name. An alternative declaration would be to have the components of the aggregate use the array aspect, as in <code>REAL POPULATION(10)</code> but this would deviate from the specification.
 
Although F90 allows an array to be defined with a lower bound other than the default of one, this requires such a non-default lower bound to be specified in every declaration, which is tiresome, and leads to confusion over counting. Still, to suit the requirement, the index found for Dar Es Salaam is reduced by one. Returning a value of zero for "not found" is helpful (-1 would be good too, but this can't be an unsigned integer) and it is often useful to have an actual element zero with a dummy entry, such as <code>CITYSTAT("--No such city!",6E66)</code> so that a WRITE statement need not be prefaced by a test so as to avoid an index-out-of-bounds error. In such a scheme, the searches would be specified by <code>FIRSTMATCH(CITY(1:n).NAME,"Dar Es Salaam")</code> to signify that the zero'th element was not involved. Coherent organisation is required for this. Incidentally, 0 (zero) in the source continuation field is treated as a space (no continuation), thus the letter o instead.
 
The specification mentions an ordered list: here, the items are indeed ordered in storage (as first, second, etc) but ordering more usefully refers to the values of the components. The example is presented in reducing order of population. More generally, the storage order might be arbitrary, and one would use indices, arrays such as XCNAME which would list the entries in the order of their names, and XCPOP their populations. These arrays would each be produced by an indexed sort process that would not move the data about, and so long as entries were not frequently altered (requiring re-sorting) these indices could be used for searching. <lang Fortran> MODULE SEMPERNOVIS !Keep it together.
TYPE CITYSTAT !Define a compound data type.
CHARACTER*28 NAME !Long enough?
REAL POPULATION !Accurate enough.
END TYPE CITYSTAT !Just two parts, but different types.
TYPE(CITYSTAT) CITY(10) !Righto, I'll have some.
DATA CITY/ !Supply the example's data.
1 CITYSTAT("Lagos", 21.0 ),
2 CITYSTAT("Cairo", 15.2 ),
3 CITYSTAT("Kinshasa-Brazzaville",11.3 ),
4 CITYSTAT("Greater Johannesburg", 7.55),
5 CITYSTAT("Mogadishu", 5.85),
6 CITYSTAT("Khartoum-Omdurman", 4.98),
7 CITYSTAT("Dar Es Salaam", 4.7 ),
8 CITYSTAT("Alexandria", 4.58),
9 CITYSTAT("Abidjan", 4.4 ),
o CITYSTAT("Casablanca", 3.98)/
CONTAINS
INTEGER FUNCTION FIRSTMATCH(TEXT,TARGET) !First matching.
CHARACTER*(*) TEXT(:) !An array of texts.
CHARACTER*(*) TARGET !The text to look for.
DO FIRSTMATCH = 1,UBOUND(TEXT,DIM = 1) !Scan the array from the start.
IF (TEXT(FIRSTMATCH) .EQ. TARGET) RETURN !An exact match? Ignoring trailing spaces.
END DO !Try the next.
FIRSTMATCH = 0 !No match. Oh dear.
END FUNCTION FIRSTMATCH
 
INTEGER FUNCTION FIRSTLESS(VAL,TARGET) !First matching.
REAL VAL(:) !An array of values.
REAL TARGET !The value to look for.
DO FIRSTLESS = 1,UBOUND(VAL,DIM = 1) !Step through the array from the start.
IF (VAL(FIRSTLESS) .LT. TARGET) RETURN !Suitable?
END DO !Try the next.
FIRSTLESS = 0 !No match. Oh dear.
END FUNCTION FIRSTLESS
END MODULE SEMPERNOVIS
 
PROGRAM POKE
USE SEMPERNOVIS !Ex Africa, ...
CHARACTER*(*) BLAH !Save on some typing.
PARAMETER (BLAH = "The first city in the list whose ") !But also, for layout.
 
WRITE (6,1) BLAH,FIRSTMATCH(CITY.NAME,"Dar Es Salaam") - 1 !My array starts with one.
1 FORMAT (A,"name is Dar Es Salaam, counting with zero, is #",I0)
 
WRITE (6,2) BLAH,CITY(FIRSTLESS(CITY.POPULATION,5.0)).NAME
2 FORMAT (A,"population is less than 5 is ",A)
 
WRITE (6,3) BLAH,CITY(FIRSTMATCH(CITY.NAME(1:1),"A")).POPULATION
3 FORMAT (A,"whose name starts with A has population",F5.2)
END</lang>
 
One could fuss further over the layout of the output, but here it is:
<pre>
The first city in the list whose name is Dar Es Salaam, counting with zero, is #6
The first city in the list whose population is less than 5 is Khartoum-Omdurman
The first city in the list whose whose name starts with 'A' has population 4.58
</pre>
 
 
=={{header|Haskell}}==
1,220

edits