[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

structures? pointers? Containers! (was: top10)



David Fanning wrote:
> 
> Otte Homan (ottehoman@my-deja.com) writes:
> 
> > I have the same problem - I'd like to create a
> > hierarchical data structure, basically an array,
> > of structures (so I can use DataFile(1),
> > DataFile(2), etc...) Each structure consists of
> > headers, parameters, and *dynamic* arrays. So the
> > structures are *more or less* the same, but
> > differ in the lenght of their arrays. Using IDL
> > 5.3 (sorry, our site has only this
> > version licensed) the contents of an array can
> > only be of one single type. My filestructures
> > are like this: {{header},{parameters},{data}},
> > where {data} is a structure with arrays of (from
> > file to file) different lengths. I only know at
> > runtime how long these arrays are.
> >
> > Any solution ?
> 
> Well, I reiterate. Pointers. The solution is pointers. :-)
> [...]

I would vote for containers. They are even more flexible as you can
store almost anything any size in there (as long as it is an object ;-)
And the beauty of objects goes even further: they'll do everything
automatically for you -- well, at least after you spent enough time and
brain food to program them.

Cheers,
Martin


PS: I recollect postings from me saying "Nooo, I will never use
objects!" Now I tend to write anything with them. BTW: thanks to Ben
Tupper who pointed out a bug and a little misconception, I reiterated on
my enhanced container object that allows object access by name (please
find it attached). For IDL 5.3 or later you can now search names with
the "real" Unix like pattern match (see StrMatch function). And the
IsContained method now also accepts name(s) as input.
   Example: retrieve all objects whose name starts with B, D, or M and
ends in n (sorry David!):
      theContainer->Get(Name='[BDM]*n')


-- 
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[ Dr. Martin Schultz   Max-Planck-Institut fuer Meteorologie    [[
[[                      Bundesstr. 55, 20146 Hamburg             [[
[[                      phone: +49 40 41173-308                  [[
[[                      fax:   +49 40 41173-298                  [[
[[ martin.schultz@dkrz.de                                        [[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
;+
; NAME:
;   MGS_Container (Object)
;
; PURPOSE:
;      This object extends the IDL_Container object (see online help)
;   by allowing access to objects by name. If objects stored in
;   the container possess a structure element called 'name' and allow
;   access to this element via a GetProperty method, MGS_Container
;   allows the following in excess of IDL_Container's functionality:
;   1. retrieve the container position objects via their name attributes
;   2. retrieve object references via the objects' name attributes
;   3. remove objects via their name attributes
;      Objects without a name attribute (or without a GetProperty
;   method) are internally handled as objects with an empty name string.
;      The Get, and Remove methods accept a single string or a string
;   vector with the 'name' keyword. If you run IDL >= 5.3, names may
;   contain Unix like wildcards ('*', '?', and '[...]'; see online
;   help for StrMatch), in IDL 5.2 they may at least end with a '*'.
;   A search expression '*' will return all objects including those
;   with no name attribute (or an empty string as name). To exclude
;   unnamed objects from the result, use the NoEmpty keyword.
;      A new method GetPosition accepts an object list as
;   argument. This can either be a list of object references or a
;   string (or string vector). This method returns an index vector
;   which can be used as 'position' value in other methods.
;
; AUTHOR:
;   Dr. Martin Schultz
;   Max-Planck-Institut fuer Meteorologie
;   Bundesstr. 55, D-20146 Hamburg
;   email: martin.schultz@dkrz.de
;
; CATEGORY:
;   Objects, General Programming
;
; CALLING SEQUENCE:
;   theContainer = Obj_New( 'MGS_Container' )
;   theContainer->Add, objectref
;   objectref = theContainer->Get( [keywords] )
;   Obj_Destroy, theContainer
;
; KEYWORDS TO THE INITIALIZATION ROUTINE:
;   None.
;
; PROCEDURES AND METHODS:
;   For a more detailed explanation on individual methods or
;   procedures, see the comments in the respective code sections.
;   The following list contains only "public" procedures and methods. 
;   Methods marked with '*' are changed or new with respect to the
;   parent IDL_Container object.
;
; NON-OBJECT PROCEDURES:
;   None
;   
; PROCEDURE METHODS:
;   MGS_Container::Add, objref [Position=index] 
;            Add an object to the container.
;   MGS_Container::Move, source, dest
;            Move the position of an object within the container.
; * MGS_Container::Remove, [ objectref | Position=index | /All |
;                            Name=string ]
;            Delete an object from the container. NEW: can specify
;            objects by name.
;
; FUNCTION METHODS:
;   MGS_Container::Count()
;            Return number of objects stored in container.
; * MGS_Container::Get( /All | IsA=classname | Position=index |
;                       Name=string  [, Count=variable] )
;            Return references to selected objects from the
;            container. If no object matches the search criteria, a
;            long value of -1 is returned (as in IDL_Container). 
;            NEW: can specify objects by name.
; * MGS_Container::IsContained( objref [, Position=index] )
;            Test if object(s) is stored in container.
; * MGS_Container::GetNames( [Position=index] )
;            NEW method. Return the names of the objects stored in the
;            container. Objects without a name field yield an empty string.
; * MGS_Container::GetPosition( objlist )
;            NEW method. Returns the index values of objects specified
;            by object reference or name. Objlist can be a string vector
;            where each string can have a trailing '*' as wildcard. 
;
; MODIFICATION HISTORY:
;
;   mgs, 19 Jul 2000 : VERSION 1.00
;   mgs, 03 Aug 2000 : bug fix in Get - check for empty position
;                                       (thanks to Ben Tupper)
;                      IsContained method now also allows name
;                      specification and contains core of GetPosition
;                      method.
;   mgs, 10 Aug 2000 : now uses StrMatch for wildcard expansion if IDL>=5.3
;
;-
;
;###########################################################################
;
; LICENSE
;
; This software is OSI Certified Open Source Software.
; OSI Certified is a certification mark of the Open Source Initiative.
;
; Copyright © 2000 Martin Schultz
;
; This software is provided "as-is", without any express or
; implied warranty. In no event will the authors be held liable
; for any damages arising from the use of this software.
;
; Permission is granted to anyone to use this software for any
; purpose, including commercial applications, and to alter it and
; redistribute it freely, subject to the following restrictions:
;
; 1. The origin of this software must not be misrepresented; you must
;    not claim you wrote the original software. If you use this software
;    in a product, an acknowledgment in the product documentation
;    would be appreciated, but is not required.
;
; 2. Altered source versions must be plainly marked as such, and must
;    not be misrepresented as being the original software.
;
; 3. This notice may not be removed or altered from any source distribution. 
;
; For more information on Open Source Software, visit the Open Source
; web site: http://www.opensource.org.
;
;###########################################################################


; -------------------------------------------------------------------------------------
; Undefine:
; This method applies a trick published by Andrew Coole, Adelaide, AUS
; to render an undefined variable from within an IDL subroutine.

pro MGS_Container::Undefine, arg

   if (n_elements(arg) eq 0) then return   ; already undefined
   tempvar = SIZE(TEMPORARY(arg))

end

;---------------------------------------------------------------------------
; GetOneName (*private*):
; This method returns the NAME field of the object stored at the
; position given as argument. If the object contains no NAME field or
; has no GetProperty method, an empty string is returned.
; Note: position must be a scalar!

FUNCTION MGS_Container::GetOneName, Position

   ;; Establish error handler in case object has no GetProperty method
   CATCH, theError
   IF theError NE 0 THEN BEGIN
      CATCH, /Cancel
      RETURN, ''
   ENDIF

   ;; Get the object reference of the specified object
   theObject = self->Get(Position=position)
   IF not Obj_Valid(theObject) THEN RETURN, ''

   ;; Atempt to call the object's GetProperty method to retrieve the
   ;; name
   theObject->GetProperty, name=name

   ;; If successful, return object name. Otherwise, error handler
   ;; should return empty string.
   RETURN, name

END


;---------------------------------------------------------------------------
; GetNames:
; This method returns the NAME fields of all objects stored in the
; container. If the optional POSITION keyword is used, only the names
; of the objects at the indices given by POSITION will be returned.
; Invalid position indices yield empty strings as names.

FUNCTION MGS_Container::GetNames, Position=position

   ;; If no position vector was passed, get number of objects stored
   ;; in container and create index list
   IF N_Elements(position) GT 0 THEN BEGIN
      index = position
   ENDIF ELSE BEGIN
      objcount = self->Count()
      IF objcount GT 0 THEN $
        index = lindgen(objcount) $
      ELSE $
        RETURN, ''
   ENDELSE

   ;; Create result array
   result = StrArr(N_Elements(index))

   ;; Get individual object names
   FOR i=0L, N_Elements(index)-1 DO $
     result[i] = self->GetOneName(index[i])

   RETURN, result

END


;---------------------------------------------------------------------------
; GetPosition:
; This method returns the position indices of the objects specified as
; object reference (in this case the IDL_Container method IsContained
; is used) or specified by names. A -1 is returned if no object in the
; container matches the search or if OBJLIST is neither of type string
; nor objref. 
;   Object names can contain wildcards ('*', '?', and '[...]' for IDL
; >=5.3). Name search is case-insensitive unless you pass the
; case_sensitive keyword (see IsContained method). A search pattern
; '*' will also return unnamed objects unless you set the NoEmpty keyword.


FUNCTION MGS_Container::GetPosition, objlist,   $
                      case_sensitive=case_sensitive,  $
                      noempty=noempty


   ;; Establish error handler to be safe
   CATCH, theError
   IF theError NE 0 THEN BEGIN
      CATCH, /Cancel
      RETURN, -1L
   ENDIF

   ;; If no objlist is given, return immediately
   IF N_Elements(objlist) EQ 0 THEN RETURN, -1L

   ;; Check if objlist is of type object reference or string
   type = Size(objlist, /TNAME)

   ;; For these, use the inherited IsContained method
   IF type EQ 'OBJREF' OR type EQ 'STRING' THEN BEGIN
      dummy = self->IsContained(objlist,   $
                                case_sensitive=case_sensitive,  $
                                noempty=noempty,  $
                                Position=index )
      RETURN, index
   ENDIF

   ;; Reaching this portion of the routine means invalid type for
   ;; objlist. Simply return -1.
   Message, 'Objlist must be of type OBJ_REF or STRING!', /Continue
   RETURN, -1L
END

   
;---------------------------------------------------------------------------
; Get:
; This method returns object references for objects that match
; specified criteria - or all objects if the All keyword is set.
; The IDL_Container method Get is extended to include a Name
; keyword. All and Position take precedence over name. As in the
; original, count can be used to return the number of objects
; retrieved. 

FUNCTION MGS_Container::Get, All=all,   $
                      Position=position, $
                      Name=name,  $
                      Case_Sensitive=case_sensitive,  $
                      NoEmpty=NoEmpty,  $
                      Count=count,  $
                      _Ref_Extra=e


   ;; Initialize count value
   count = 0L

   ;; If All or Position keywords are used, call inherited method
   IF Keyword_Set(all) THEN  $
     RETURN, self->IDL_Container::Get(/All, count=count)

   IF N_Elements(Position) GT 0 THEN $
     RETURN, self->IDL_Container::Get(Position=position, count=count)

   ;; Look for named objects ?
   IF N_Elements(name) GT 0 THEN BEGIN
      ;; Find object positions
      namepos = self->GetPosition(name, $
                                 case_sensitive=case_sensitive,  $
                                 noempty=noempty)
      ;; If no match, return empty object reference
      IF namepos[0] LT 0 THEN BEGIN
         dummy = obj_new()
         RETURN, dummy
      ENDIF
      ;; Return objects
     RETURN, self->IDL_Container::Get(Position=namepos, count=count)
   ENDIF

   ;; Otherwise call inherited method with extra keywords
   RETURN, self->IDL_Container::Get(_extra=e, count=count)

END


;---------------------------------------------------------------------------
; IsContained:
; This method tests if a specific object reference or an object with 
; a certain name is in the container. The IsContained method of
; IDL_Container is extended so that it checks the type of objref.
; If ChildObject is a string or string array, a pattern match is
; performed. For IDL >= 5.3 the StrMatch function is used which allows
; Unix like wildcard search ('*', '?', and '[...]'). For IDL <= 5.2 a
; primitive wildcard search is implemented which only allows for a
; trailing '*'.


FUNCTION MGS_CONTAINER::IsContained, ChildObject,   $
                      Position = Position,  $
                      Case_Sensitive=case_sensitive, $
                      NoEmpty=NoEmpty

   ;; If ChildObject is empty then return immediately
   IF N_Elements(ChildObject) EQ 0 THEN RETURN,0

   type = Size(ChildObject, /TName)

   CASE Type OF
      'OBJREF':  BEGIN
         result =  Self->IDL_Container::IsContained(ChildObject, Position = Position)
         RETURN, result
      END
      
      'STRING':  BEGIN        
         ;; For strings, interpret them as object names. Get the names of
         ;; all objects and apply (version dependent) pattern matching.
         result = 0
         position = -1L
         names = self->GetNames()
         names = StrTrim( names, 2)
         IF NOT Keyword_Set(case_sensitive) THEN $
            names = StrUpCase(names)

         ;; Return if container has no objects stored
         IF self->Count() EQ 0 THEN RETURN, result

         ;; Loop through objlist and find matching indices
         FOR i=0L, N_Elements(ChildObject)-1 DO BEGIN
            testname = StrTrim( ChildObject[i], 2)

            IF (!Version.Release GE 5.3) THEN BEGIN
               ;; Use the StrMatch function
               ;; StrMatch returns a logical array with 1 for every
               ;; string that matches the search expression
               lw = StrMatch(names,testname,fold_case=1-Keyword_Set(case_sensitive))
               ;; Convert logical array to index array
               w = Where(lw, wcnt)
            ENDIF ELSE BEGIN
               ;; Restricted wildcard use (only '*' at the end of the expression)
               ;; Check if wildcard is used in name
               IF NOT Keyword_Set(case_sensitive) THEN $
                  testname = StrUpCase(testname)
               sloppy = 0
               IF (( p = StrPos(testname,'*') )) GE 0 THEN BEGIN
                  l = StrLen(testname)
                  IF l-p NE 1 THEN BEGIN ; wildcard must be last character
                     Message, 'Invalid name specification '+ChildObject[i]+'!', /Continue
                     GOTO, skip_it
                  ENDIF
                  testname = StrMid(testname, 0, l-1)
                  sloppy = 1
               ENDIF

               ;; Look for matching object names
               IF sloppy THEN BEGIN
                  w = Where(StrPos(names,testname) EQ 0, wcnt)
               ENDIF ELSE BEGIN
                  w = Where(names EQ testname, wcnt)
               ENDELSE
            ENDELSE

            ;; Add indices to list
            IF wcnt GT 0 THEN position = [ position, w ]

skip_it:
         ENDFOR

         ;; Remove first dummy position if any names have been found
         ;; and set result value to 1
         IF N_Elements(position) GT 1 THEN BEGIN
            result = 1
            position = position[1:*]
         ENDIF

         ;; Remove position of unnamed objects if NoEmpty keyword is
         ;; set
         IF Keyword_Set(NoEmpty) AND position[0] GE 0 THEN BEGIN
            we = Where(names EQ '', wcnt)
            IF wcnt GT 0 THEN BEGIN
               ;; Convert index array to logical array
               ;; and delete "empty" elements
               larr = intarr(N_Elements(names))
               larr[position] = 1
               larr[we] = 0
               position = Where(larr, wcnt)
               IF wcnt EQ 0 THEN result = 0
            ENDIF
         ENDIF

         RETURN, result
      END

      ELSE:      BEGIN
         Message, 'ChildObject must be of type OBJREF or STRING!',/Continue
         RETURN,0
      END
   ENDCASE
END 


;---------------------------------------------------------------------------
; Remove:
; This method removes objects from the container. Similarily to the
; Get method, this method extends the IDL_Container::Remove method to
; allow object specification by name.
; Note: Unlike the original method of IDL_Container, the child_object
; argument, or the position or name keywords can contain more than one
; element. The unique position values are sorted and reversed before
; removing objects.

PRO MGS_Container::Remove, child_object,    $
                 name=name,     $
                 case_sensitive=case_sensitive,  $
                 noempty=noempty,  $
                 position=position, $
                 All=all,   $
                 _Ref_Extra=e

   ;; If all keyword is set, call parent method accordingly
   IF Keyword_Set(all) THEN BEGIN
      self->IDL_Container::Remove, /All
      RETURN
   ENDIF

   ;; If object references are given, loop over them and call the
   ;; parent object's remove method for each of them
   IF N_Elements(child_object) GT 0 THEN BEGIN
      FOR i=0L, N_Elements(child_object)-1 DO $
        self->IDL_Container::Remove, child_object[i]
      RETURN
   ENDIF

   ;; If names are provided, convert them to position indices
   IF N_Elements(name) GT 0 THEN $
      index = self->GetPosition(name, $
                               case_sensitive=case_sensitive,  $
                               noempty=noempty)

   ;; If position indices are provided, make local copy
   IF N_Elements(position) GT 0 THEN $
      index = long(position)

   ;; Handle name and position cases: find unique position values,
   ;; sort and reverse them and loop 
   maxindex = self->Count()
   IF N_Elements(index) GT 0 THEN BEGIN
      ;; Need to sort index array and remove items from the end
      index = Reverse( index[ Uniq( index, Sort(index) ) ] )
      ok = where(index GE 0 AND index LT maxindex, cnt)
      IF cnt EQ 0 THEN RETURN   ;; no valid index values
      index = index[ok]
      FOR i=0L, N_Elements(index)-1 DO $
        self->IDL_Container::Remove, Position=index[i]
      RETURN
   ENDIF

   ;; Handle other (future extensions) cases
   self->IDL_Container::Remove, _extra=e

END


;---------------------------------------------------------------------------
; Cleanup:
; This method frees all object values and destroys all objects stored
; in the container. This is already handled in IDL_Container, so
; Cleanup only needs to call the inherited object's method.

PRO MGS_Container::Cleanup

   self->IDL_Container::Cleanup

END


;---------------------------------------------------------------------------
; Init:
; This method initializes the object values. There are no arguments,
; so the only action is to call the parent's Init method

FUNCTION MGS_Container::INIT

   RETURN, self->IDL_Container::Init()

END


;---------------------------------------------------------------------------
; MGS_Container__Define:
; This is the object definition for the container object. It inherits
; everything from IDL_Container. 

PRO MGS_Container__Define

   object_class = {  MGS_Container,    $
                     INHERITS  IDL_Container  $
                  }

END