I make a living in the US southwest ostensibly working with my hands.
I'm installing an engineered floor for my friend Matt. I can do this
because by trade I'm a journeyman carpenter.
I use fortran shamelessly in the calculations that arise in this
context. The above screenshot is what Torrie the floor guy drew up when
Matt's insurer determined that a plumbing leak had forked up his carpet.
It's pretty close to correct.
So Torrie ordered 28 cases of flooring, which have 24 sq ft each. My
bid for labor was based on this. I'm just gonna start showing the
fortran now and apologize for lack of transitions.
$ gfortran -Wall -Wextra m1.f90 -o out
$ ./out
wood supply is 672.00000
living 258.86633
dining 177.88020
hall 28.285591
sunroom 53.108505
strip 23.138889
total1 is 541.27954
office 146.99132
foyer 44.358074
total2 is 732.62891
$ cat m1.f90
implicit integer (a-e)
sqft = 144
cases = 28
footage = 24
supply = cases * footage
print *, "wood supply is ", supply
hor = 208.25
vert = 179
r1 = hor * vert / sqft
print *, "living ", r1
hor = 208.25
vert = 123
r2 = hor * vert / sqft
print *, "dining ", r2
hor = 85.75
vert = 47.5
r3 = hor * vert / sqft
print *, "hall ", r3
hor = 79.25
vert = 96.5
r4 = hor * vert / sqft
print *, "sunroom ", r4
hor = 208.25
vert = 16
r5 = hor * vert / sqft
print *, "strip ", r5
total1 = r1+r2+r3+r4+r5
print *, " "
print *, "total1 is ", total1
hor = 118.25
vert = 179
r6 = hor * vert / sqft
print *, "office ", r6
hor = 40.75
vert = 156.75
r7 = hor * vert / sqft
print *, "foyer ", r7
total2 = total1+r6+r7
print *, "total2 is ", total2
end program
! gfortran -Wall -Wextra m1.f90 -o out
$
q1) Does anything above look wrong?
q2) The above calculation is obviously quick and dirty. It's quick
because fortran is blindlingly fast. It's dirty because it involves
spraying out corners with a compressor, so my eyes can easily err.
Given that the above is to calculate sqare footage in the diagram
referenced, does the above calculation look reasonable?
Living large and loving easy,
--
Uno
Well, one thing that strikes me as "wrong" (with the quotes because it
is not actually an error, but rather a stylistic choice) is the use of
implicit typing.
While I have been known to make exceptions in the face of pleas of
hardship, I have a general policy of declining to help debug code that
uses implicit typing. It makes the work of debugging substantially
harder, as well as substantially increasing the number of bugs typically
present. I figure that if I spend the extra effort to debug the messes
that sometimes result, I am acting as an "enabler" in the sense of (from
Merriam Webster online)
"one who enables another to persist in self-destructive behavior (as
substance abuse) by providing excuses or by making it possible to
avoid the consequences of such behavior"
Some people have been known to argue that implicit typing simplifies
things for them enough to make up for any increased debugging problems.
That position seems incompatible with asking for debugging help.
In this case, I stopped reading the code after seeing the IMPLICIT
statement.
--
Richard Maine | Good judgment comes from experience;
email: last name at domain . net | experience comes from bad judgment.
domain: summertriangle | -- Mark Twain
That's a reasonable reaction. The only variable that ended up in the
(a-e) range was 'cases'. It wou .. hold on,
$ gfortran -Wall -Wextra m1.f90 -o out
$ ./out
wood supply is 672.00000
living 258.86633
dining 177.88020
hall 28.285591
sunroom 53.108505
strip 23.138889
total1 is 541.27954
office 146.99132
foyer 44.358074
total2 is 732.62891
$ cat m1.f90
implicit real (a-z)
sqft = 144
total1 = r1+r2+r3+r4+r5
Better?
--
Uno
I guess you've grown tired of the Phred Phungus handle.
Why not use a spreadsheet, then go ask in a different group ?
No. 'cases' should be integer.
The whole idea behind 'implicit none' is to force you to declare
variables so that if you misspell something, the compiler catches it for
you:
real r1
r1 = 0.0
r1 = rl + 1.0
The compiler will catch the substitution of 'rl' for 'r1' ... unless, of
course, you've also declared 'rl', in which case you've set yourself up
for trouble by declaring two variable names that look almost identical.
Louis
Well, more specifically, it is unusual to write in Fortran
a specific case to a general problem.
It would be more usual to have the program prompt for and
read in the various values. Another possibility, also fairly
common, is to read in a data file specifying the needed values.
(The latter is sometimes preferred as you get to keep the file
for future reference.)
There are a few cases where one might write a program for
one specific problem, when there is no general problem.
One might, for example, use Fortran to calculate pi to many
decimal places. There is only one pi, and there isn't really
a generalization of the algorithm.
Now, what I expect to be the problem in floor engineering is that
floor products come in specific sizes, which don't divide up
so easily. What one really wants to do is to find the optimal
partition, number of cuts, of the pieces to fit a given floor size.
That is a much harder problem, but also probably more useful.
-- glen
> > Why not use a spreadsheet, then go ask in a different group ?
>
> Well, more specifically, it is unusual to write in Fortran
> a specific case to a general problem.
Not necessarily. If you need something that is in fortran, but not
in excel or some other language, then you would use fortran. A good
example of this is eigenvalue problems. Excel doesn't have any
eigenvalue routines built-in. I sometimes wish that it did, but it
doesn't.
$.02 -Ron Shepard
But I think Glen's point (I sometimes misunderstand him, but I think I
got this one - probably means I'm wrong :-)) was that you wouldn't write
Fortran code to solve a particular Eigenvalue problem. Instead, you
would write (or more likely, use one from a library) a routine that
solved a more general Eigenvalue problem, and you would feed it the
particular array in question (assuming one was talking array
eigenvalues).
Unusual doesn't mean never. I suppose there are some "one of"
eigenvalue problems that might need to be solved. Even so, it
doesn't seem hard to have the program instead read in the matrix.
That would allow the program to be used to solve future problems,
even if none ever show up.
The comment related to the "input data" being fixed in
the program, instead of being read in.
More generally, something like Mathematica, Matlab, or R,
interpreted languages that I am sure all include an eigenvalue
routine, might be appropriate.
-- glen
I think what Richard meant was that you should start with 'implicit
none' then explicitly declare each variable, e.g.
implicit none
real :: sqft, footage ! etc.
integer :: cases ! etc.
Simon
Yes.
In some other languages, one might feed a particular array,
though with the usual ability to change that array.
Not that I use Excel that much, but that would fit with the way
Excel is used. The Mathematica notebook also has a similar idea
about combining code and modifiable data.
As for Excel, one could write (or translate from Fortran) an
eigenvalue routine in VBA for use from Excel.
-- glen
> Ron Shepard <ron-s...@NOSPAM.comcast.net> wrote:
>
> > In article <hqkngl$h1l$1...@naig.caltech.edu>,
> > glen herrmannsfeldt <g...@ugcs.caltech.edu> wrote:
> >
> > > > Why not use a spreadsheet, then go ask in a different group ?
> > >
> > > Well, more specifically, it is unusual to write in Fortran
> > > a specific case to a general problem.
> >
> > Not necessarily. If you need something that is in fortran, but not
> > in excel or some other language, then you would use fortran. A good
> > example of this is eigenvalue problems. Excel doesn't have any
> > eigenvalue routines built-in. I sometimes wish that it did, but it
> > doesn't.
>
> But I think Glen's point (I sometimes misunderstand him, but I think I
> got this one - probably means I'm wrong :-)) was that you wouldn't write
> Fortran code to solve a particular Eigenvalue problem. Instead, you
> would write (or more likely, use one from a library) a routine that
> solved a more general Eigenvalue problem, and you would feed it the
> particular array in question (assuming one was talking array
> eigenvalues).
Perhaps I wasn't as clear as I should have been. Yes, I did mean
that you would use some general library routine to solve the
eigenvalue problem (probably fortran code from netlib, or LAPACK, or
EISPAC, or wherever). I wasn't talking about writing the eigenvalue
routine from scratch. What I meant is that if you had a specific
problem to solve then you would dimension the matrices with
constants, assign the matrix elements to the matrix with regular
assignment statements, call the eigenvalue routine, and print out
the results (or use the results in some subsequent step). You might
not go to the extra effort to make the matrix dimensions general, or
read in the matrix elements from a file, or do the other things that
you might do for a more general situation. And you would not
rewrite the fortran routine in some other language (VBA, or perl, or
whatever) because that would be too much effort for a one-of-a-kind
type of problem.
On the other hand, if excel had eigenvalue routines built in along
with the other math and statistical routines, this is the kind of
problem that you might instead solve that way. But excel doesn't
have those kinds of routines. Mathematica and Matlab and some of
the other options are good alternatives for this kind of thing, but
those packages are very expensive, not as commonly available as
excel, and, interestingly enough, not really any easier to use than
fortran for a one-of-a-kind solution to a general problem like this.
Other possibilities include using perl libraries or python packages.
I would say that if you already have the libraries or packages
installed, and you are familiar with using them, then that would be
another option that would require about the same amount of effort on
the programmers part as the fortran approach (and for small matrix
dimensions, the efficiency advantages of fortran over these other
approaches would not be significant). But if you don't have those
libraries already installed, and you have to go to the effort to
install the software and figure out how to use it, then the fortran
approach is probably easier in the end.
$.02 -Ron Shepard
> Well, more specifically, it is unusual to write in Fortran
> a specific case to a general problem.
>
> It would be more usual to have the program prompt for and
> read in the various values. Another possibility, also fairly
> common, is to read in a data file specifying the needed values.
> (The latter is sometimes preferred as you get to keep the file
> for future reference.)
>
> There are a few cases where one might write a program for
> one specific problem, when there is no general problem.
> One might, for example, use Fortran to calculate pi to many
> decimal places. There is only one pi, and there isn't really
> a generalization of the algorithm.
>
> Now, what I expect to be the problem in floor engineering is that
> floor products come in specific sizes, which don't divide up
> so easily. What one really wants to do is to find the optimal
> partition, number of cuts, of the pieces to fit a given floor size.
>
> That is a much harder problem, but also probably more useful.
But I do want to treat it in some generality. That the first version is
hard-coded has everything to do with me having to write it in time to
be able to recommend that we not do the area that was called r7, the foyer.
I was much happier to make the fortran keystrokes than take up that tile.
Now I'm looking for objects to be useful to me. I get a fair amount of
work like this, so I'd like to be able to whip this up like microwave
popcorn.
Eventually I would like to have a control that goes through rooms and
populates their data, but first I'll have some hard-coded values to sort
out the syntax.
$ gfortran -Wall -Wextra m2.f90 -o out
$ ./out
wood supply is 672.00000
calc is 4185.0000
$ cat m2.f90
implicit none
real :: footage, sqft, calc, supply
integer :: cases
type room
real :: hor
real :: vert
character(len=10) :: name
real :: subtract
end type room
type(room) :: living
sqft = 144
cases = 28
footage = 24
supply = cases * footage
print *, "wood supply is ", supply
living%hor = 42
living%vert = 100
living%subtract = 15
calc = living%hor * living%vert - living%subtract
print *, "calc is ", calc
end program
! gfortran -Wall -Wextra m2.f90 -o out
$
So I create a room type that isn't too different from the fortran in the
original post. One difference is the subtract component. This living
room is typical in that it has an overall area that you would measure in
the usual fashion, but you need to knock that down because it's got a
fireplace that takes up 15 sq ft, which is half a case of wood, so it's
a fudge factor I need.
Here's my question: how do I traverse these structures to do the same
type of summing up the areas in the original post, with the added
wrinkle that their will also be this subtract component, which should
default to zero?
Thanks for your comment, and cheers,
--
Uno
I hadn't actually thought of any one-of-a-kind eigenvalue
problem, but yes, if you had one.
> On the other hand, if excel had eigenvalue routines built in along
> with the other math and statistical routines, this is the kind of
> problem that you might instead solve that way. But excel doesn't
> have those kinds of routines. Mathematica and Matlab and some of
> the other options are good alternatives for this kind of thing, but
> those packages are very expensive, not as commonly available as
> excel, and, interestingly enough, not really any easier to use than
> fortran for a one-of-a-kind solution to a general problem like this.
But R (which I have used) and Octave (which I haven't) are free.
> Other possibilities include using perl libraries or python packages.
> I would say that if you already have the libraries or packages
> installed, and you are familiar with using them, then that would be
> another option that would require about the same amount of effort on
> the programmers part as the fortran approach (and for small matrix
> dimensions, the efficiency advantages of fortran over these other
> approaches would not be significant). But if you don't have those
> libraries already installed, and you have to go to the effort to
> install the software and figure out how to use it, then the fortran
> approach is probably easier in the end.
If you had a large number of such problems to do, and also had
to interface with previously written programs in one of those
langauges, then it might be worth adapting a library or package.
-- glen
> No. 'cases' should be integer.
>
> The whole idea behind 'implicit none' is to force you to declare
> variables so that if you misspell something, the compiler catches it for
> you:
>
> real r1
>
> r1 = 0.0
> r1 = rl + 1.0
>
> The compiler will catch the substitution of 'rl' for 'r1' ... unless, of
> course, you've also declared 'rl', in which case you've set yourself up
> for trouble by declaring two variable names that look almost identical.
But there's something to be said for
implicit real (a-z)
, in particular that there isn't much difference between 42 the integer
and 42 the real. So I can have lines like the very readable
sqft = 144
My answer is a real, and the promotions are accurate.
--
Uno
True. But there is a difference for division:
real :: sqft
sqft = 144
print *,sqft/13
is different from
integer :: sqft
sqft = 144
print *,sqft/13
Simon
[snip]
>
> Here's my question: how do I traverse these structures to do the same
> type of summing up the areas in the original post, with the added
> wrinkle that their will also be this subtract component, which should
> default to zero?
>
Now it's starting to sound like a C++ problem, but that's probably
because I don't know much about Oop in Fortran.
Btw: you would be the first carpenter I know of who uses Fortran :-)
I have also seen programs with IMPLICIT INTEGER (A-Z)
for the case of no floating point at all.
or, for some IBM compilers:
IMPLICIT INTEGER (A-$)
(the 27th letter of the alphabet)
-- glen
> Louis Krupp wrote:
>
> > No. 'cases' should be integer.
> >
> > The whole idea behind 'implicit none' is to force you to declare
> > variables so that if you misspell something, the compiler catches it for
> > you:
> >
> > real r1
> >
> > r1 = 0.0
> > r1 = rl + 1.0
> >
> > The compiler will catch the substitution of 'rl' for 'r1' ... unless, of
> > course, you've also declared 'rl', in which case you've set yourself up
> > for trouble by declaring two variable names that look almost identical.
>
> But there's something to be said for
>
> implicit real (a-z)
I don't think you understood Louis's comment - or my prior one. The
problem Louis refers to applies to *ANY* implicit typing. Having all the
types be real lowers the chance of being confused about what type
something is, but it does *NOTHING* to avoid the problem that Louis
refers to or many others of its ilk. I could go on for a long time
listing the related problems I have seen. There are things such as
if (condition) then a = 1
(which doesn't do anything at all like what you probably think), or
common /whatever/ lots,of,variables,......, and,
1 more,
where the last comma on the first line is in column 73, or many, many
other such problems.
> , in particular that there isn't much difference between 42 the integer
> and 42 the real.
I'm sorry, but there are very fundamental differences. I'm not going to
try to list them all. I don't even think it is practical to list them.
I'd say it was more the opposite - that they are different in every way
except for a handful. The implicit conversion on assignment that you
show is one of that small handful of places where they act somewhat
alike... and that's also a place where people very often make mistakes
(a common one being to write something like x=1.2, where x is double
precision).
Everything in my 40+ years of programming in Fortran says that it is a
serious mistake to treat data type so casually. A significant fraction
of the problems people ask about here relate to getting types wrong. I
advise the entire opposite of the approach you seem to be taking. Rather
than trying to paper over the differences in type, you should
consciously think about the type of every data entity.
> So I can have lines like the very readable
>
> sqft = 144
Oddly, I recall thinking of that line in your code as being particularly
hard to read. The type promotion aside, I found the variable name
confusing. Only by seeing how it was used in the code could I deduce
that this was a conversion factor to turn square inches into square feet
(by dividing).
It occurs to me that there is much similarity in the issues of units of
measure and types. Where units of measure are relevant in a program, it
needs to be completely clear what those units are. People should not
have to deduce it from the conversions used. I had to study the code to
deduce that the input units must be inches, while the output was in
square feet. It wasn't a very difficult deduction in this case, but I
did have to deduce it. Extend this kind of practice to a larger program
and it wil be a killer.
You ask elsewhere about the benefits of object orientation. I would say
that, for you at the current stage, they are zero - more like negative.
Much of object orientation centers around type-related concepts. If you
are still at the stage of trying to be able to ignore types, then I'd
say that there isn't much point in worrying about running marathons
before you get crawling down pretty well. As you bring more advanced
language concepts into play and have larger programs, being careless
about types tends to matter more. That's part of why I try to steer
people away from implicit typing. You can get by with it in simple codes
like the one at hand, but developing that habit will severely handicap
you ability to handle larger codes.
Maybe off topic for this dicsussion, or maybe not, but this
reminds me of noting what I believe is a difference between
physics (and maybe other sciences) and engineering in the use
of units.
It seems to me that engineers often don't give variables
quantities with the units included, but factor out the units.
One might, for example, say F(Newtons)=m(kg) times a(m/s**2),
such that the variables F, m, and a, have numerical values
in the specified units, but the variable doesn't include the
units. One would be expected to convert the input data to
the specified units, and convert the result to the desired units.
In physics, it would be more usual to say F=m times a, where
m might be 2kg, and a might be 5m/s**2, resulting in F
being 10 kg*m/s**2, otherwise known at 10N. As another
example, m might be 10g, and a 3m/s**2, such that F comes
out 30 g*m/s**2, not a common unit. It would then be converted
to the desired unit.
One reason for this might be that most programming languages
don't allow variables with units. One normally factors out
the given units, specified in the prompts or documentation,
and only gives the numerical value.
Propagating units through a compiled language would not be
so easy, though doing the conversion as part of the I/O library
wouldn't seem so hard.
-- glen
>> sqft = 144
>
> Oddly, I recall thinking of that line in your code as being particularly
> hard to read. The type promotion aside, I found the variable name
> confusing. Only by seeing how it was used in the code could I deduce
> that this was a conversion factor to turn square inches into square feet
> (by dividing).
It would have been better if it were
sqft = 144.0
Since I measure to the closest sixteenth, these numbers show up as a
rational number of inches, usually having a fractional part.
Most clients would be confused if I didn't do this small trick in
dimensional analysis. Dividing by a real is not something that
fortran's gonna drop the ball on.
--
Uno
What would have been better was a descriptive name for the conversion
factor that relates to how is is used was the point being made. Again,
in trivial code it's not terribly difficult to dig thru and retrieve
such things from context altho it takes time and effort. As code size
and complexity grows, the importance grows with it and it's quite
possible (actually quite common) that even the original coder will
puzzle over "what was I doing there????" when re-visiting code later if
it isn't made patently clear thru naming conventions and/or comments or
straightforward coding what was being implemented.
You seem oblivious to the underlying issues raised re: IMPLICIT NONE --
whether it's simply inexperience showing or stubbornness for the sake of
maintaining present prejudices and practice it doesn't bode well for
ease in debugging and code maintenance down the road...
--
Also off topic, but there is a proposal for adding "units" to
Fortran 2008++. I don't know the current status. This is a
funny period between finishing up the last one and starting
to get ready to think about the next standard. It's being
driven by Van Snyder and you can probably find a recent
unofficial draft on the J3 web site. A while ago it was
written as a potential add-on technical report. It
allowed you to specify "units" for things and do compound
units with type checking and magic conversion between units.
As with any first try at something, details change from time
to time.
Dick Hendrickson
I sure hope it is not one of the proposals that thinks the whole world is
just the SI units of physics. I once added a "units" checker to a simulation
package where the concern was with both financial and physical things. Lots
of bastard units and weird things like "constant dollars". I chickened
out on the harder problems so that the units applied to whole variables.
It was a serious bother to sort out cases where differing elements
of an array had separate units. Solved it by making separate variables.
Conversion constants were a big drag so an interim fix was the "universal"
unit that matched anything and everything. Universals were then maintained
out of the models so that numbers were just for multiples. Another drag
was get the arguments to things like log fixed up. Lots of logs and friends
for compound interest and such.
Much more useful was to specify units (really domains) for subscripts
and the variables used for subscripting. Eventually there were a few places
where mismatches had to be tolerated so those were hidden by explicit
conversion functions.
The experience was that there had been serious attention paid to getting
units correct before the checker. It found an error rate of around
5 per 1000 lines of models. Afterwards it was much easier to no longer
worry about the units as all one had to do was get them declared once
and they never got updated into an error state.
I like the way MathCAD does units. They have I think four basic
classifications, you define your own base units off of those
(e.g. kg := 1M; g := 0.001*kg), and then it will automatically do
conversions for you as well as catching mismatched units. And it makes
no implicit assumptions about your unit system, since you're the one who
defines your units at the outset.
If one is doing other sorts of things, like say demography where fertility has
units of person per person, then SI or whatever is not helful. Or try interest
on a converted currency that is held referenced to a base year with a
price index.
One might say that SI gives one nouns but the real action is in the
many adjectives
that make the world useful. The point is that having the physics dimensions is
inadequate whether they are in SI or any other system. There are errors beyond
mixing length and weight when one should not.
Or even just something like chemistry where one wants molar fractions
by specie.
According to SI those are just numbers but that is an example where an
incomplete
pretense at claiming correctness will be worse than no claim at all. In other
situations this is said as "Bad security is worse that no security as
some will be
lulled into trusting it and become complacent and careless".
[big snips, a lot of code]
> What would have been better was a descriptive name for the conversion
> factor that relates to how is is used was the point being made. Again,
> in trivial code it's not terribly difficult to dig thru and retrieve
> such things from context altho it takes time and effort. As code size
> and complexity grows, the importance grows with it and it's quite
> possible (actually quite common) that even the original coder will
> puzzle over "what was I doing there????" when re-visiting code later if
> it isn't made patently clear thru naming conventions and/or comments or
> straightforward coding what was being implemented.
Thanks for your response, dpb. I think my latest version addresses this
criticism.
>
> You seem oblivious to the underlying issues raised re: IMPLICIT NONE --
> whether it's simply inexperience showing or stubbornness for the sake of
> maintaining present prejudices and practice it doesn't bode well for
> ease in debugging and code maintenance down the road...
Well, dpb, I figure that bugs are only as bad as wasps, and when I'm
getting my first number, I'm gonna get stung a couple times.
I'm aware that implicit none enforces strict typing, and have been so
demonstrably for twenty years. I won't back off the fact that the
ultimate calculation is a real. That might have been a good comment to
add at the git-go.
I'd rather talk about subsequent versions of this program:
$ pwd
/home/dan/source/fortran_stuff
$ ls
b.f90 directory.mod dw3.f90 fortran_resources.pdf m3.f90 m3.f90~ out
$ gfortran -Wall -Wextra m3.f90 -o out
$ ./out
ernest 1.00000000 2.0000000
parent: forest_root
children: douglas helen
douglas 6.0000000 7.0000000
parent: ernest
helen 3.0000000 4.0000000 5.0000000
parent: ernest
children: john
john 8.0000000
parent: helen
ernest 1.00000000 2.0000000
parent: forest_root
children: douglas helen
douglas 6.0000000 7.0000000
parent: ernest
helen 3.0000000 4.0000000 5.0000000
parent: ernest
children: betty john
betty 9.0000000 10.0000000
parent: helen
children: nigel peter ruth
nigel 11.000000
parent: betty
peter 12.000000
parent: betty
ruth
parent: betty
john 8.0000000
parent: helen
ernest 1.00000000 2.0000000
parent: forest_root
children: douglas helen
douglas 6.0000000 7.0000000
parent: ernest
helen 3.0000000 4.0000000 5.0000000
parent: ernest
children: betty
betty 9.0000000 10.0000000
parent: helen
children: nigel peter ruth
nigel 11.000000
parent: betty
peter 12.000000
parent: betty
ruth
parent: betty
ernest 1.00000000 2.0000000
parent: forest_root
children: douglas helen
douglas 6.0000000 7.0000000
parent: ernest
helen 3.0000000 4.0000000 5.0000000
parent: ernest
children: john betty
john 8.0000000
parent: helen
betty 9.0000000 10.0000000
parent: helen
children: nigel peter ruth
nigel 11.000000
parent: betty
peter 12.000000
parent: betty
ruth
parent: betty
$ cat m3.f90
! (c) Copyright Michael Metcalf and John Reid, 1992. This file may be
! freely used and copied for educational purposes provided this notice
! remains attached. Extracted from "Fortran 90 Explained" Oxford
! University Press (Oxford and New York), ISBN 0-19-853772-7.
!
!A recurring problem in computing is the need to manipulate a linked
!data structure.
!This might be a simple linked list like the one encountered in Section
!2.13, but often a more general tree structure is required.
!
!The example in this Appendix consists of a module that establishes and
!navigates one or more such trees, organized as a 'forest', and a short
!test program for it. Here, each node is identified by a name and has
!any number of children, any number of siblings, and (optionally) some
!associated real data. Each root node is regarded as having a common
!parent, the 'forest root' node, whose name is 'forest root'. Thus,
!every node has a parent. The module provides facilities for adding a
!named node to a specified parent, for enquiring about all the nodes
!that are offspring of a specified node, for removing a tree or subtree,
!and for performing I/O operations on a tree or subtree.
!
!The user-callable interfaces are:
!
!start:
! must be called to initialize a forest.
!add_node:
! stores the data provided at the node whose parent is specified
! and sets up pointers to the parent and siblings (if any).
!remove_node:
! deallocate all the storage occupied by a complete tree or
! subtree.
!retrieve:
! retrieves the data stored at a specified node and the names of
! the parent and children.
!dump_tree:
! write a complete tree or subtree.
!restore_tree:
! read a complete tree or subtree.
!finish:
! deallocate all the storage occupied by all the trees of the forest.
!
module directory
!
! Strong typing imposed
implicit none
!
! Only subroutine interfaces, the length of the character
! component, and the I/O unit number are public
private
public start, add_node, remove_node, retrieve,
&
dump_tree, restore_tree, finish
!
! Module constants
character(*), parameter:: eot = 'End-of-Tree.....'
integer, parameter, public :: unit = 4, & ! I/O unit number
max_char = 16 ! length of character
component
!
! Define the basic tree type
type node
character(max_char) :: name ! name of node
real, pointer :: y(:) ! stored real data
type(node), pointer :: parent ! parent node
type(node), pointer :: sibling ! next sibling node
type(node), pointer :: child ! first child node
end type node
!
! Module variables
type(node), pointer :: current ! current node
type(node), pointer :: forest_root ! the root of the forest
integer :: max_data ! max size of data array
character(max_char), allocatable, target :: names(:)
! for returning list of names
! The module procedures
contains
subroutine start
! Initialize the tree.
allocate (forest_root)
current => forest_root
forest_root%name = 'forest_root'
nullify(forest_root%parent, forest_root%sibling, forest_root%child)
allocate(forest_root%y(0))
max_data = 0
allocate (names(0))
end subroutine start
subroutine find(name)
character(*), intent(in) :: name
! Make the module variable current point to the node with given name,
! or be null if the name is not there.
type(node), pointer :: root
! For efficiency, we search the tree rooted at current, and if this
! fails try its parent and so on until the forest root is reached.
if (associated(current)) then
root => current
nullify (current)
else
root => forest_root
end if
do
call look(root)
if (associated(current)) return
root => root%parent
if (.not.associated(root)) exit
end do
contains
recursive subroutine look(root)
type(node), pointer :: root
! Look for name in the tree rooted at root. If found, make the
! module variable current point to the node
type(node), pointer :: child
!
if (root%name == name) then
current => root
else
child => root%child
do
if (.not.associated(child)) exit
call look(child)
if (associated(current)) return
child => child%sibling
end do
end if
end subroutine look
end subroutine find
subroutine add_node(name, name_of_parent, data)
character(*), intent(in) :: name, name_of_parent
! For a root, name = ''
real, intent(in), optional :: data(:)
! Allocate a new tree node of type node, store the given name and
! data there, set pointers to the parent and to its next sibling
! (if any). If the parent is not found, the new node is treated as
! a root. It is assumed that the node is not already present in the
! forest.
type(node), pointer :: new_node
!
allocate (new_node)
new_node%name = name
if (present(data)) then
allocate(new_node%y(size(data)))
new_node%y = data
max_data = max(max_data, size(data))
else
allocate(new_node%y(0))
end if
!
! If name of parent is not null, search for it. If not found, print message.
if (name_of_parent == '') then
current => forest_root
else
call find (name_of_parent)
if (.not.associated(current)) then
print *, 'no parent ', name_of_parent, ' found for ', name
current => forest_root
end if
end if
new_node%parent => current
new_node%sibling => current%child
current%child => new_node
nullify(new_node%child)
end subroutine add_node
subroutine remove_node(name)
character(*), intent(in) :: name
! Remove node and the subtree rooted on it (if any),
! deallocating associated pointer targets.
type(node), pointer :: parent, child, sibling
!
call find (name)
if (associated(current)) then
parent => current%parent
child => parent%child
if (.not.associated(child, current)) then
! Make it the first child, looping through the siblings to find it
! and resetting the links
parent%child => current
sibling => child
do
if (associated (sibling%sibling, current)) exit
sibling => sibling%sibling
end do
sibling%sibling => current%sibling
current%sibling => child
end if
call remove(current)
end if
end subroutine remove_node
recursive subroutine remove (old_node)
! Remove a first child node and the subtree rooted on it (if any),
! deallocating associated pointer targets.
type(node), pointer :: old_node
type(node), pointer :: child, sibling
!
child => old_node%child
do
if (.not.associated(child)) exit
sibling => child%sibling
call remove(child)
child => sibling
end do
! remove leaf node
if (associated(old_node%parent)) old_node%parent%child =>
old_node%sibling
deallocate (old_node%y)
deallocate (old_node)
end subroutine remove
subroutine retrieve(name, data, parent, children)
character(*), intent(in) :: name
real, pointer :: data(:)
character(max_char), intent(out) :: parent
character(max_char), pointer :: children(:)
! Returns a pointer to the data at the node, the name of the
! parent, and a pointer to the names of the children.
integer count, i
type(node), pointer :: child
!
call find (name)
if (associated(current)) then
data => current%y
parent = current%parent%name
! count the number of children
count = 0
child => current%child
do
if (.not.associated(child)) exit
count = count + 1
child => child%sibling
end do
deallocate (names)
allocate (names(count))
! and store their names
children => names
child => current%child
do i = 1, count
children(i) = child%name
child => child%sibling
end do
else
nullify(data)
parent = ''
nullify(children)
end if
end subroutine retrieve
subroutine dump_tree(root)
character(*), intent(in) :: root
! Write out a complete tree followed by an end-of-tree record
! unformatted on the file unit.
call find (root)
if (associated(current)) then
call out(current)
end if
write(unit) eot, 0, eot
contains
recursive subroutine out(root)
! Traverse a complete tree or subtree, writing out its contents
type(node), intent(in) :: root ! root node of tree
! Local variable
type(node), pointer :: child
!
write(unit) root%name, size(root%y), root%y, root%parent%name
child => root%child
do
if (.not.associated(child)) exit
call out (child)
child => child%sibling
end do
end subroutine out
end subroutine dump_tree
subroutine restore_tree
! Reads a subtree unformatted from the file unit.
character(max_char) :: name
integer length_y
real, allocatable :: y(:)
character(max_char) :: name_of_parent
!
allocate(y(max_data))
do
read (unit) name, length_y, y(:length_y), name_of_parent
if (name == eot) exit
call add_node( name, name_of_parent, y(:length_y) )
end do
deallocate(y)
end subroutine restore_tree
subroutine finish
! Deallocate all allocated targets.
call remove (forest_root)
deallocate(names)
end subroutine finish
end module directory
program test
use directory
implicit none
!
! Initialize a tree
call start
! Fill it with some data
call add_node('ernest','',(/1.,2./))
call add_node('helen','ernest',(/3.,4.,5./))
call add_node('douglas','ernest',(/6.,7./))
call add_node('john','helen',(/8./))
call add_node('betty','helen',(/9.,10./))
call add_node('nigel','betty',(/11./))
call add_node('peter','betty',(/12./))
call add_node('ruth','betty')
! Manipulate subtrees
open(unit, form='unformatted', status='scratch')
call dump_tree('betty')
call remove_node('betty')
write(*,*); call print_tree('ernest')
rewind unit
call restore_tree
rewind unit
write(*,*); call print_tree('ernest')
call dump_tree('john')
call remove_node('john')
write(*,*); call print_tree('ernest')
rewind unit
call restore_tree
write(*,*); call print_tree('ernest')
! Return storage
call finish
contains
recursive subroutine print_tree(name)
! To print the data contained in a subtree
character(*) :: name
integer i
real, pointer :: data(:)
character(max_char) parent, self
character(max_char), pointer :: children(:)
character(max_char), allocatable :: siblings(:)
!
call retrieve(name, data, parent, children)
if (.not.associated(data)) return
self = name; write(*,*) self, data
write(*,*) ' parent: ', parent
if (size(children) > 0 ) write(*,*) ' children: ', children
allocate(siblings(size(children)))
siblings = children
do i = 1, size(children)
call print_tree(siblings(i))
end do
end subroutine print_tree
end program test
! gfortran -Wall -Wextra m3.f90 -o out
$
So, how do I populate nodes with a room?
--
Uno
> dpb wrote:
> > You seem oblivious to the underlying issues raised re: IMPLICIT NONE
...
> I'm aware that implicit none enforces strict typing,
Actually, no, that is *NOT* the point being made. If that's what you
think it is about, then you are still missing it. The type aspect is one
part, but in many ways not the most important part. As Louis said
>>> The whole idea behind 'implicit none' is to force you to declare
>>> variables so that if you misspell something, the compiler catches it
>>> for you:
and as I tried to elaborate
>>> I don't think you understood Louis's comment - or my prior one. The
>>> problem Louis refers to applies to *ANY* implicit typing. Having all
>>> the types be real lowers the chance of being confused about what
>>> type something is, but it does *NOTHING* to avoid the problem that
>>> Louis refers to or many others of its ilk.
but the message doesn't appear to be getting through.
The most important part about implicit none for the current purposes is
*NOT* that it makes you declare what type each variable is. Yes,
implicit none does that, but that's not the most important part here.
The important part is that implicit none makes you declare what names
you are using for variables. That is what helps avoid the typographical
errors. It is perhaps unfortunate coincidence of terms that a "typing
error" could refer either to an error relating to data type or a
typographical error. Those are entirely unrelated problems, except that
the same words can be used in both contexts and implicit none can relate
to both. But the errors I am talking about are of the typographical
variety, which has nothing to do with strict typing.
In the olden days, compilers would print maps with a list of
all the variables and the address in memory for each.
(Relative address in the object module.) If you saw an unexpected
variable there, you would know to check for typographical errors.
> It is perhaps unfortunate coincidence of terms that a "typing
> error" could refer either to an error relating to data type or a
> typographical error. Those are entirely unrelated problems, except that
> the same words can be used in both contexts and implicit none can relate
> to both. But the errors I am talking about are of the typographical
> variety, which has nothing to do with strict typing.
Also, some typographical errors are easier to make in mixed
case coding. The one/ell error wasn't there in upper case only days.
Most printers designed for computers printed zero and oh different
enough that you could tell them apart from printouts. One of my
favorite was a Xerox printer that printed oh like a script oh with
a little loop at the top. Easy to tell from zero. Some screen
fonts now make it very difficult to tell them apart.
-- glen
I am happy to accept any constructive criticism when I ask for a source
check, so I thank you for your reply.
Louis' source was something like this:
$ gfortran -Wall -Wextra r1.f90 -o out
r1.f90:6.7:
r1 = rl + 1.0
1
Error: Symbol 'rl' at (1) has no IMPLICIT type
$ cat r1.f90
implicit none
real r1
r1 = 0.0
r1 = rl + 1.0
endprogram
! gfortran -Wall -Wextra r1.f90 -o out
$
I read Louis very carefully and didn't remember that he was a dude in
c.l.f.!
My contention is that it's strict typing that reveals the mistyping. As
I re-read your reply, I understood of two natures of mistyping:
1) integer :: pi
2) real :: sqfu
I certainly don't want to challenge your opinion on the fortran source
that crosses your desktop.
--
Uno