From 4e4bda4c14459c557e1f2cdc41964ccb2f5ebb8b Mon Sep 17 00:00:00 2001 From: arjenmarkus <arjen.markus895@gmail.com> Date: Fri, 18 Dec 2020 17:41:02 +0100 Subject: [PATCH 1/3] Preliminary implementation of stringlist module This commit introduces a very preliminary version of a module to handle lists of strings (that is: a structure containing strings of varying lengths) --- src/CMakeLists.txt | 1 + src/stdlib_stringlist.f90 | 148 +++++++++++++++++++++++ src/tests/CMakeLists.txt | 1 + src/tests/stringlist/CMakeLists.txt | 1 + src/tests/stringlist/test_stringlist.f90 | 63 ++++++++++ 5 files changed, 214 insertions(+) create mode 100644 src/stdlib_stringlist.f90 create mode 100644 src/tests/stringlist/CMakeLists.txt create mode 100644 src/tests/stringlist/test_stringlist.f90 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 02604959e..6605a460d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRC stdlib_error.f90 stdlib_kinds.f90 stdlib_logger.f90 + stdlib_stringlist.f90 stdlib_system.F90 ${outFiles} ) diff --git a/src/stdlib_stringlist.f90 b/src/stdlib_stringlist.f90 new file mode 100644 index 000000000..08600aa1f --- /dev/null +++ b/src/stdlib_stringlist.f90 @@ -0,0 +1,148 @@ +! stringlist.f90 -- +! Module for storing and manipulating lists of strings +! The strings may have arbitrary lengths, not necessarily the same +! +! Note: very preliminary +! +module stdlib_stringlists + implicit none + + private + integer, parameter, public :: list_head = 0 + integer, parameter, public :: list_end = -1 + public :: t_stringlist + + + integer, parameter :: initial_size = 20 + + type t_string + character(len=:), allocatable :: value + end type t_string + + type t_stringlist + private + integer :: size = 0 + type(t_string), dimension(:), allocatable :: string + contains + procedure :: insert => insert_string + procedure :: get => get_string + procedure :: length => length_list + end type t_stringlist + +contains + +! length_list -- +! Return the size (length) of the list +! +! Arguments: +! list The list of strings to retrieve the string from +! +integer function length_list( list ) + class(t_stringlist), intent(in) :: list + + length_list = list%size +end function length_list + +! insert_string -- +! Insert a new string into the list +! +! Arguments: +! list The list of strings where the new string should be inserted +! idx Index after which to insert the string +! string The string in question +! +subroutine insert_string( list, idx, string ) + class(t_stringlist), intent(inout) :: list + integer, intent(in) :: idx + character(len=*), intent(in) :: string + + integer :: i + integer :: idxnew + type(t_string) :: new_element + type(t_string), dimension(:), allocatable :: empty_strings + + ! + ! Initialise the list if necessary + ! + if ( .not. allocated(list%string) ) then + allocate( list%string(initial_size) ) + do i = 1,size(list%string) + list%string(i)%value = '' + enddo + endif + + ! + ! Check the index: + ! - if the index is list_head, then shift the entire array + ! - if the index is list_end or negative in general, determine the absolute index + ! - if the index is large than the registered size, expand the list + ! - shift everything after the absolute index + ! + new_element%value = string + + if ( idx == list_head ) then + list%size = list%size + 1 + list%string = [new_element, list%string] + else + idxnew = idx + if ( idx <= list_end ) then + idxnew = list%size - (abs(idx) - 1) + if ( idxnew <= 0 ) then + idxnew = 0 + endif + endif + + if ( idxnew <= size(list%string) ) then + list%size = max( idxnew+1, list%size + 1 ) + list%string = [list%string(1:idxnew), new_element, list%string(idxnew+1:)] + else + allocate( empty_strings(idxnew-size(list%string)) ) + do i = 1,size(empty_strings) + empty_strings(i)%value = '' + enddo + list%string = [list%string, empty_strings, new_element] + list%size = idxnew + 1 + endif + endif +end subroutine insert_string + +! get_string -- +! Get the string at a particular index +! +! Arguments: +! list The list of strings to retrieve the string from +! idx Index after which to insert the string +! +function get_string( list, idx ) + class(t_stringlist), intent(inout) :: list + integer, intent(in) :: idx + character(len=:), allocatable :: get_string + + integer :: idxnew + type(t_string) :: new_element + + ! + ! Examine the actual index: + ! - if the index is larger than the size, return an empty string + ! - if the index is equal to list_head, interpret it as index 1 + ! - if the index is negative, calculate the absolute index + ! + if ( idx > list%size ) then + get_string = '' + else + idxnew = idx + if ( idx == list_head ) then + idxnew = 1 + elseif ( idx <= list_end ) then + idxnew = list%size - (abs(idx) - 1) + endif + + if ( idxnew < 1 ) then + get_string = '' + else + get_string = list%string(idxnew)%value + endif + endif +end function get_string + +end module stdlib_stringlists diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index c3b09e34d..701db67e2 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(optval) add_subdirectory(stats) add_subdirectory(system) add_subdirectory(quadrature) +add_subdirectory(stringlist) ADDTEST(always_skip) set_tests_properties(always_skip PROPERTIES SKIP_RETURN_CODE 77) diff --git a/src/tests/stringlist/CMakeLists.txt b/src/tests/stringlist/CMakeLists.txt new file mode 100644 index 000000000..a591ef543 --- /dev/null +++ b/src/tests/stringlist/CMakeLists.txt @@ -0,0 +1 @@ +ADDTEST(stringlist) diff --git a/src/tests/stringlist/test_stringlist.f90 b/src/tests/stringlist/test_stringlist.f90 new file mode 100644 index 000000000..715661096 --- /dev/null +++ b/src/tests/stringlist/test_stringlist.f90 @@ -0,0 +1,63 @@ +! test_stringlist.f90 -- +! Test program for the stdlib_stringlist module +! +! Straightforward test program: try to cover "all" special cases +! +program test_stringlists + use stdlib_stringlists + + implicit none + + type(t_stringlist) :: list + integer :: i + + ! + ! The straightforward cases: insert a few strings and retrieve them + ! + call list%insert( list_head, "C" ) + call list%insert( list_head, "B" ) + call list%insert( list_head, "A" ) + + ! + ! Now insert a string at an arbitrary location - it will be at position 11 + ! + call list%insert( 10, "Number 10" ) + + ! + ! Append a string to the end of the list - that will be position 12 + ! + call list%insert( list_end, "Appended" ) + + ! + ! Insert a string near the end of the list - that will be position 12-2+1 + ! + call list%insert( list_end-2, "Appended 2" ) + + ! + ! Insert a string beyond the beginning - that should appear at the start + ! + call list%insert( list_end-30, "Effectively prepended" ) + + ! + ! Print the result + ! + do i = 1,list%length() + write(*,*) i, '>', list%get(i), '<' + enddo + + ! + ! And select two elements at the end + ! + write(*,*) 'end-1' , '>', list%get(list_end-1), '<' + write(*,*) 'end' , '>', list%get(list_end), '<' + write(*,*) 'end-30', '>', list%get(list_end-30), '<' + + ! + ! Okay, finally set an element really far and read it back + ! + call list%insert( 40, "Really far away" ) + write(*,*) '40', '>', list%get(40), '<' + write(*,*) '41', '>', list%get(41), '<' + write(*,*) list%length() + +end program test_stringlists From 11650c3eb5b18ad1e17e86eaa5f11c9415f967b8 Mon Sep 17 00:00:00 2001 From: arjenmarkus <arjen.markus895@gmail.com> Date: Sun, 20 Dec 2020 14:49:35 +0100 Subject: [PATCH 2/3] Add sorting function Added a function to sort the list of strings (implementation inspired by the alternative set-up of a long string with indices) --- src/stdlib_stringlist.f90 | 103 ++++++++++++++++++++++- src/tests/stringlist/test_stringlist.f90 | 9 ++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/stdlib_stringlist.f90 b/src/stdlib_stringlist.f90 index 08600aa1f..650b581ea 100644 --- a/src/stdlib_stringlist.f90 +++ b/src/stdlib_stringlist.f90 @@ -27,10 +27,48 @@ module stdlib_stringlists procedure :: insert => insert_string procedure :: get => get_string procedure :: length => length_list + procedure :: sort => sort_list end type t_stringlist + + interface operator(<) + module procedure string_lower + end interface + + interface operator(>) + module procedure string_greater + end interface + + interface operator(==) + module procedure string_equal + end interface + contains +! compare t_string derived types +! Required by sorting functions +! +elemental logical function string_lower( string1, string2 ) + type(t_string), intent(in) :: string1 + type(t_string), intent(in) :: string2 + + string_lower = string1%value < string2%value +end function string_lower + +elemental logical function string_greater( string1, string2 ) + type(t_string), intent(in) :: string1 + type(t_string), intent(in) :: string2 + + string_greater = string1%value > string2%value +end function string_greater + +elemental logical function string_equal( string1, string2 ) + type(t_string), intent(in) :: string1 + type(t_string), intent(in) :: string2 + + string_equal = string1%value == string2%value +end function string_equal + ! length_list -- ! Return the size (length) of the list ! @@ -119,7 +157,6 @@ function get_string( list, idx ) character(len=:), allocatable :: get_string integer :: idxnew - type(t_string) :: new_element ! ! Examine the actual index: @@ -145,4 +182,68 @@ function get_string( list, idx ) endif end function get_string +! sort_list -- +! Sort the list and return the result as a new list +! +! Arguments: +! list The list of strings to retrieve the string from +! ascending Whether to sort as ascending (true) or not (false) +! +function sort_list( list, ascending ) + class(t_stringlist), intent(in) :: list + logical, intent(in) :: ascending + + integer :: i + integer, dimension(:), allocatable :: idx + class(t_stringlist), allocatable :: sort_list + + ! + ! Allocate and fill the index array, then sort the indices + ! based on the strings + ! + idx = [ (i ,i=1,list%size) ] + + if ( ascending ) then + idx = sort_ascending( idx ) + else + idx = sort_descending( idx ) + endif + + allocate( sort_list ) + allocate( sort_list%string(list%size) ) + + do i = 1,list%size + sort_list%string(i) = list%string(idx(i)) + enddo + sort_list%size = list%size + +contains +recursive function sort_ascending( idx ) result(idxnew) + integer, dimension(:) :: idx + integer, dimension(size(idx)) :: idxnew + + if ( size(idx) > 1 ) then + idxnew = [ sort_ascending( pack( idx, list%string(idx) < list%string(idx(1)) ) ), & + pack( idx, list%string(idx) == list%string(idx(1)) ) , & + sort_ascending( pack( idx, list%string(idx) > list%string(idx(1)) ) ) ] + else + idxnew = idx + endif +end function sort_ascending + +recursive function sort_descending( idx ) result(idxnew) + integer, dimension(:) :: idx + integer, dimension(size(idx)) :: idxnew + + if ( size(idx) > 1 ) then + idxnew = [ sort_descending( pack( idx, list%string(idx) > list%string(idx(1)) ) ), & + pack( idx, list%string(idx) == list%string(idx(1)) ) , & + sort_descending( pack( idx, list%string(idx) < list%string(idx(1)) ) ) ] + else + idxnew = idx + endif +end function sort_descending + +end function sort_list + end module stdlib_stringlists diff --git a/src/tests/stringlist/test_stringlist.f90 b/src/tests/stringlist/test_stringlist.f90 index 715661096..af74c9016 100644 --- a/src/tests/stringlist/test_stringlist.f90 +++ b/src/tests/stringlist/test_stringlist.f90 @@ -9,6 +9,7 @@ program test_stringlists implicit none type(t_stringlist) :: list + type(t_stringlist) :: list_sorted integer :: i ! @@ -60,4 +61,12 @@ program test_stringlists write(*,*) '41', '>', list%get(41), '<' write(*,*) list%length() + ! + ! Sort the list and print the result + ! + list_sorted = list%sort( .false. ) + do i = 1,list_sorted%length() + write(*,*) i, '>', list_sorted%get(i), '<' + enddo + end program test_stringlists From ed8e43010582e278a23113ad730b26885706ee76 Mon Sep 17 00:00:00 2001 From: arjenmarkus <arjen.markus895@gmail.com> Date: Thu, 31 Dec 2020 09:57:46 +0100 Subject: [PATCH 3/3] Extend the implementation of stdlib_stringlist Add documentation of the interface for stdlib_stringlist. Add several subroutines and functions. Note, however, that it is not quite complete (see the head of stdlib_stringliust.f90 for some details) --- doc/specs/index.md | 1 + doc/specs/stdlib_stringlist.md | 467 +++++++++++++++++++++++ src/stdlib_stringlist.f90 | 306 +++++++++++++-- src/tests/stringlist/test_stringlist.f90 | 53 ++- 4 files changed, 788 insertions(+), 39 deletions(-) create mode 100644 doc/specs/stdlib_stringlist.md diff --git a/doc/specs/index.md b/doc/specs/index.md index 6ea78b52e..0261e99d9 100644 --- a/doc/specs/index.md +++ b/doc/specs/index.md @@ -19,6 +19,7 @@ This is and index/directory of the specifications (specs) for each new module/fe - [optval](./stdlib_optval.html) - Fallback value for optional arguments - [quadrature](./stdlib_quadrature.html) - Numerical integration - [stats](./stdlib_stats.html) - Descriptive Statistics + - [stringlist](./stdlib_stringlist.html) - Handling lists of strings ## Missing specs diff --git a/doc/specs/stdlib_stringlist.md b/doc/specs/stdlib_stringlist.md new file mode 100644 index 000000000..41e081ca2 --- /dev/null +++ b/doc/specs/stdlib_stringlist.md @@ -0,0 +1,467 @@ +--- +title: stringlist +--- +# Lists of strings + +[TOC] + +## Introduction + +Fortran has supported variable-length strings since the 2003 standard, +but it does not have a native type to handle collections of strings of +different lengths. Such collections are quite useful though and the +language allows us to define a derived type that can handle such +collections. + +The `stdlib_stringlist` module defines a derived type that is capable of +storing a list of strings and of manipulating them. + +Methods include: + +* inserting strings at a given position +* replacing strings at a given position +* deleting a single string or a range of strings +* retrieving a string or a range of strings at a given position +* finding the position of a particular string or a string which contains some substring +* sorting the list + +## Positions in a list of strings + +The module implements what are effectively infinitely long lists: a position is +represented as a positive integer, but there is no "out-of-bound" index. That is, +the following piece of code will simply work: + +```fortran +type(stringlist_type) :: list + +! Add two strings ... +call list%insert( list_head, "The first string" ) +call list%insert( 20, "The last string" ) + +write(*,*) 'The last: ', list%get(list_end) +write(*,*) 'Beyond that: ', list%get(30) +``` +The special position `list_head` represents the position *before* the first element. Likewise, +the special position `list_end` represents the position of the *last* element. You can +use these positions to insert a string before the first string that is already in the +list or to insert after the last string that has been inserted. + +If you specify a position beyond the last, the `list%get()` method simply returns an empty +string. + +You can also specify *negative* positions, but they are interpreted as going back from the +last inserted string. If you need the last but one string, you can do so innthis way: + +```fortran +write(*,*) 'The last but onw: ', list%get(list_end-1) +``` + +So, it is possible to do simple arithmetic. + +*Note:* this does not work for the head of the list. + +## The derived type: stringlist_type + +### Status + +Experimental + +### Description + +The type holds a small number of components and gives access to a number of procedures, +some of which are implemented as subroutines, others as functions or as operations. + + +### Public `stringlist_type` methods + +The following methods are defined: + +Method | Class | Description +---------------------|------------|------------ +[`delete`](./stdlib_stringlist.html#delete-delete_one_or_more_strings) | Subroutine | Delete one or more strings from the list +[`destroy`](./stdlib_stringlist.html#destroy_destroy_all_strings_in_the_list) | Subroutine | Destroy the contents of the list +[`get`](./stdlib_stringlist.html#get-get_a_single_string_from_a_list) | Function | Get a string from a particular position +[`index`](./stdlib_stringlist.html#index-find_the_index_of_a_particular_string_in_the_list) | Function | Find the index of a string in a list +[`index_sub`](./stdlib_stringlist.html#index_sub-find_the_index_of_a_particular_string_containing_the_given_substring) | Function | Find the index of a string containing a partilcar substring +[`insert`](./stdlib_stringlist.html#insert-insert_one_or_more_strings_after_a_given_position) | Subroutine | Insert a string or a list after a given position +[`length`](./stdlib_stringlist.html#length-return_the_length_of_the_list) | Function | Return the index of the last set position +[`range`](./stdlib_stringlist.html#range-retrieve_a_range_of_string_from_the_list) | Function | Retrieve a range of strings from the list +[`replace`](./stdlib_stringlist.html#replace-replace_one_or_more_strings_between_two_given_positions) | Subroutine | Replace one or more stringa between two positions +[`sort`](./stdlib_stringlist.html#sort-return_a_sorted_list) | Function | Sort the list and return the result as a new list +[`=`](./stdlib_stringlist.html#assign-copy_the_contents_of_a_list) | Assignment | Copy a list +[`//`](./stdlib_stringlist.html#//-concatenate_a_list_with_one_or_more_strings) | Operation | Concatenate a list with a string or concatenate two lists + + +## Details of the methods + +### `delete` - delete one or more strings + +#### Status + +Experimental + +#### Description + +Delete one or more strings from the list via a given position or positions. + +#### Syntax + +`call list % [[stringlist_type(type):delete(bound)]]( first [, last] )` + +#### Class + +Subroutine + +#### Arguments + +`list`: the stringlist variable from which to delete one or more strings + +`first`: the index of the first string to be deleted + +`last` (optional): the index of the last string to be deleted. If left out, only one string is deleted. +If the value is lower than that of `first`, the range is considered to be empty and nothing is deleted. + + +### `destroy` - destroy all strings in the list + +#### Status + +Experimental + +#### Description + +Destroy the entire contents of the list. As the variable holding the list is simply a derived type, the variable +itself is not destroyed. + +#### Syntax + +`call list % [[stringlist_type(type):destroy(bound)]]` + +#### Class + +Subroutine + +#### Arguments + +`list`: the stringlist variable from which to delete all strings + + +### `get` - get a single string from the list + +#### Status + +Experimental + +#### Description + +Get the string at the given position. + +#### Syntax + +`string = list % [[stringlist_type(type):get(bound) ( idx )]]` + +#### Class + +Function + +#### Arguments + +`list`: the stringlist variable to retrieve a string from + +`idx`: the index of the string to be retrieved (see [`the section on positions`](./stdlib_stringlist.html#position-in-a-list-of-strings) + +#### Result value + +A copy of the string stored at the indicated position. + + +### `index` - find the index of a particular string in the list + +#### Status + +Experimental + +#### Description + +Get the position of the first stored string that matches the given string, if `back` is not present or false. If `back` is +false, return the position of the last stored string that matches. Note that trailing blanks are ignored. + +#### Syntax + +`idx = list % [[stringlist_type(type):index(bound) ( string, back )]]` + +#### Class + +Function + +#### Arguments + +`list`: the stringlist variable to retrieve a string from + +`string`: the string to be found in the list + +`back` (optional): logical argument indicating the first occurrence should be returned (`false`) or the last (`true`) + +#### Result value + +The result is either the index of the string in the list or -1 if the string was not found + +#### Example + +Because trailing blanks are ignored, the following calls will give the same result: + +```fortran + write(*,*) list%index( 'A' ) + write(*,*) list%index( 'A ' ) +``` + + +### `index_sub` - find the index of a string containing the given substring in the list + +#### Status + +Experimental + +#### Description + +Get the position of the first stored string that contains the given substring, if `back` is not present or false. If `back` is +false, return the position of the last stored string that contains it. + +#### Syntax + +`idx = list % [[stringlist_type(type):index_sub(bound) ( substring, back )]]` + +#### Class + +Function + +#### Arguments + +`list`: the stringlist variable to retrieve a string from + +`substring`: the substring in question + +`back` (optional): logical argument indicating the first occurrence should be returned (`false`) or the last (`true`) + +#### Result value + +The result is either the index of the string in the list or -1 if the string was not found + + +### `insert` - insert one or more strings after a given position + +#### Status + +Experimental + +#### Description + +Insert one or more strings after a given position. The position may be anything as explained in the section on positions. +A single string may be inserted, another list of strings or a plain array of strings. In all cases trailing blanks, if any, +are retained. + +#### Syntax + +`idx = list % [[stringlist_type(type):insert(bound) ( idx, string )]]` + +#### Class + +Subroutine + +#### Arguments + +`list`: the stringlist variable to insert the string(s) into + +`idx`: the position after which the strings should be inserted + +`string`: the string to be inserted, a list of strings or a plain array of strings + + +### `length` - return the length of the list + +#### Status + +Experimental + +#### Description + +Return the length of the list, defined as the highest index for which a string has been assigned. You can place strings +in any position without needing to fill in the intervening positions. + +#### Syntax + +`length = list % [[stringlist_type(type):length(bound) ()]]` + +#### Class + +Function + +#### Arguments + +`list`: the stringlist variable to retrieve the length from + +#### Result value + +Returns the highest index of a string that has been set. + + + +### `range` - retrieve a range of strings from the list + +#### Status + +Experimental + +#### Description + +Retrieve the strings occurring between the given positions as a new list. + +#### Syntax + +`rangelist = list % [[stringlist_type(type):range(bound) ( first, last )]]` + +#### Class + +Function + +#### Arguments + +`list`: the stringlist variable to insert the string(s) into + +`first`: the position of the first string to be retrieved + +`last`: the position of the last string to be retrieved + +#### Result value + +The result is a new list containing all the strings that appear from the first to the last position, inclusively. + + + +### `replace` - replace one or more strings between two given positions + +#### Status + +Experimental + +#### Description + +Replace one or more strings between two given positions. The new strings may be given as a single string, a list of +strings or a plain array. + +#### Syntax + +`call list % [[stringlist_type(type):replace(bound) ( first, last, string )]]` + +#### Class + +Subroutine + +#### Arguments + +`list`: the stringlist variable to replace the string(s) in + + +`first`: the position of the first string to be retrieved + +`last`: the position of the last string to be retrieved + +`string`: the string to be inserted, a list of strings or a plain array of strings + + + +### `sort` - return a sorted list + +#### Status + +Experimental + +#### Description + +Create a new list consisting of the sorted strings of the given list. The strings are sorted according to ASCII, either +in ascending order or descending order. + +#### Syntax + +`sortedlist = list % [[stringlist_type(type):sort(bound) ( ascending )]]` + +#### Class + +Subroutine + +#### Arguments + +`list`: the stringlist variable of which the contents should be copied + +`ascending` (optional): if not present or true, sort the list in ascending order, otherwise descending + +#### Result value + +The contents of the given list is sorted and then stored in the new list. + + +### `=` - copy the contents of a list + +#### Status + +Experimental + +#### Description + +Copy an existing list to a new one. The original list remains unchanged. + +#### Syntax + +`copylist = list` + +#### Class + +Assignment + +#### Operands + +`list`: the stringlist variable to be copied + + + +### `//` - concatenate a list with one or more strings + +#### Status + +Experimental + +#### Description + +Concatenate a list with a string, a list of strings or a plain array + +#### Syntax + +`concatenatedlist = list // string` + +`concatenatedlist = string // list` + +#### Class + +Assignment + +#### Operands + +`list`: the stringlist variable to be concatenated + +`string`: the string to be concatenated, a list of strings or a plain array of strings + +#### Result value + +A stringlist that contains the concatenation of the two operands. + + + +## TODO + +Additional methods: + +filter + +map + +Suggestions from the discussion diff --git a/src/stdlib_stringlist.f90 b/src/stdlib_stringlist.f90 index 650b581ea..60fc9cdba 100644 --- a/src/stdlib_stringlist.f90 +++ b/src/stdlib_stringlist.f90 @@ -1,35 +1,48 @@ -! stringlist.f90 -- +! stdlib_stringlist.f90 -- ! Module for storing and manipulating lists of strings ! The strings may have arbitrary lengths, not necessarily the same ! ! Note: very preliminary ! -module stdlib_stringlists +! TODO: +! insert( list_end, ... ) in an empty list? +! +! Not implemented yet: +! insert a list or an array of character strings +! replace a string, list or an array of character strings +! concatenate a list with another list or an array +! +module stdlib_stringlist implicit none private integer, parameter, public :: list_head = 0 integer, parameter, public :: list_end = -1 - public :: t_stringlist + public :: stringlist_type + public :: operator(//) integer, parameter :: initial_size = 20 - type t_string + type string_type character(len=:), allocatable :: value - end type t_string + end type string_type - type t_stringlist + type stringlist_type private integer :: size = 0 - type(t_string), dimension(:), allocatable :: string + type(string_type), dimension(:), allocatable :: string contains - procedure :: insert => insert_string - procedure :: get => get_string - procedure :: length => length_list - procedure :: sort => sort_list - end type t_stringlist - + procedure :: destroy => destroy_list + procedure :: insert => insert_string + procedure :: get => get_string + procedure :: length => length_list + procedure :: sort => sort_list + procedure :: index => index_of_string + procedure :: index_sub => index_of_substring + procedure :: delete => delete_strings + procedure :: range => range_list + end type stringlist_type interface operator(<) module procedure string_lower @@ -43,32 +56,69 @@ module stdlib_stringlists module procedure string_equal end interface + interface operator(//) + module procedure append_string + module procedure prepend_string + end interface contains -! compare t_string derived types +! compare string_type derived types ! Required by sorting functions ! elemental logical function string_lower( string1, string2 ) - type(t_string), intent(in) :: string1 - type(t_string), intent(in) :: string2 + type(string_type), intent(in) :: string1 + type(string_type), intent(in) :: string2 string_lower = string1%value < string2%value end function string_lower elemental logical function string_greater( string1, string2 ) - type(t_string), intent(in) :: string1 - type(t_string), intent(in) :: string2 + type(string_type), intent(in) :: string1 + type(string_type), intent(in) :: string2 string_greater = string1%value > string2%value end function string_greater elemental logical function string_equal( string1, string2 ) - type(t_string), intent(in) :: string1 - type(t_string), intent(in) :: string2 + type(string_type), intent(in) :: string1 + type(string_type), intent(in) :: string2 string_equal = string1%value == string2%value end function string_equal +function append_string( list, string ) + type(stringlist_type), intent(in) :: list + character(len=*), intent(in) :: string + type(stringlist_type) :: append_string + + append_string = list + call append_string%insert( list_end, string ) +end function append_string + +function prepend_string( string, list ) + character(len=*), intent(in) :: string + type(stringlist_type), intent(in) :: list + type(stringlist_type) :: prepend_string + + prepend_string = list + call prepend_string%insert( list_head, string ) +end function prepend_string + +! TODO: concatenate two string lists + +! destroy_list -- +! Destroy the contetns of the list +! +! Arguments: +! list The list of strings in question +! +subroutine destroy_list( list ) + class(stringlist_type), intent(inout) :: list + + list%size = 0 + deallocate( list%string ) +end subroutine destroy_list + ! length_list -- ! Return the size (length) of the list ! @@ -76,12 +126,33 @@ end function string_equal ! list The list of strings to retrieve the string from ! integer function length_list( list ) - class(t_stringlist), intent(in) :: list + class(stringlist_type), intent(in) :: list length_list = list%size end function length_list -! insert_string -- +! abspos -- +! Return the absolute position in the list +! +! Arguments: +! list The list of strings to retrieve the string from +! pos Possibly relative position +! +! Note: +! This is an auxiliary function only intended for internal use +! +integer function abspos( list, pos ) + class(stringlist_type), intent(in) :: list + integer, intent(in) :: pos + + if ( pos >= list_head .or. pos <= list%size ) then + abspos = pos + elseif ( pos <= list_end ) then + abspos = list%size - (abs(pos) - 1) + endif +end function abspos + +! inserstring_type -- ! Insert a new string into the list ! ! Arguments: @@ -90,14 +161,14 @@ end function length_list ! string The string in question ! subroutine insert_string( list, idx, string ) - class(t_stringlist), intent(inout) :: list + class(stringlist_type), intent(inout) :: list integer, intent(in) :: idx character(len=*), intent(in) :: string integer :: i integer :: idxnew - type(t_string) :: new_element - type(t_string), dimension(:), allocatable :: empty_strings + type(string_type) :: new_element + type(string_type), dimension(:), allocatable :: empty_strings ! ! Initialise the list if necessary @@ -144,7 +215,7 @@ subroutine insert_string( list, idx, string ) endif end subroutine insert_string -! get_string -- +! get_string_type -- ! Get the string at a particular index ! ! Arguments: @@ -152,7 +223,7 @@ end subroutine insert_string ! idx Index after which to insert the string ! function get_string( list, idx ) - class(t_stringlist), intent(inout) :: list + class(stringlist_type), intent(inout) :: list integer, intent(in) :: idx character(len=:), allocatable :: get_string @@ -190,12 +261,13 @@ end function get_string ! ascending Whether to sort as ascending (true) or not (false) ! function sort_list( list, ascending ) - class(t_stringlist), intent(in) :: list - logical, intent(in) :: ascending + class(stringlist_type), intent(in) :: list + logical, intent(in), optional :: ascending - integer :: i - integer, dimension(:), allocatable :: idx - class(t_stringlist), allocatable :: sort_list + integer :: i + integer, dimension(:), allocatable :: idx + class(stringlist_type), allocatable :: sort_list + logical :: ascending_order ! ! Allocate and fill the index array, then sort the indices @@ -203,7 +275,12 @@ function sort_list( list, ascending ) ! idx = [ (i ,i=1,list%size) ] - if ( ascending ) then + ascending_order = .true. + if ( present(ascending) ) then + ascending_order = ascending + endif + + if ( ascending_order ) then idx = sort_ascending( idx ) else idx = sort_descending( idx ) @@ -246,4 +323,165 @@ end function sort_descending end function sort_list -end module stdlib_stringlists +! index_of_string -- +! Return the index in the list of a particular string +! +! Arguments: +! list The list of strings in which to search the string +! string The string to be found +! back Whether to search from the end (true) or not (false, default) +! +integer function index_of_string( list, string, back ) + class(stringlist_type), intent(in) :: list + character(len=*), intent(in) :: string + logical, intent(in), optional :: back + + integer :: idx + integer :: i + logical :: start_backwards + + start_backwards = .false. + if ( present(back) ) then + start_backwards = back + endif + + idx = -1 + if ( start_backwards) then + do i = list%size,1,-1 + if ( list%string(i)%value == string ) then + idx = i + exit + endif + enddo + else + do i = 1,list%size + if ( list%string(i)%value == string ) then + idx = i + exit + endif + enddo + endif + + index_of_string = idx +end function index_of_string + +! index_of_substring -- +! Return the index in the list of a string containing a particular substring +! +! Arguments: +! list The list of strings in which to search the string +! substring The substring to be found +! back Whether to search from the end (true) or not (false, default) +! +integer function index_of_substring( list, substring, back ) + class(stringlist_type), intent(in) :: list + character(len=*), intent(in) :: substring + logical, intent(in), optional :: back + + integer :: idx + integer :: i + logical :: start_backwards + + start_backwards = .false. + if ( present(back) ) then + start_backwards = back + endif + + idx = -1 + if ( start_backwards) then + do i = list%size,1,-1 + if ( index(list%string(i)%value, substring) > 0 ) then + idx = i + exit + endif + enddo + else + do i = 1,list%size + if ( index(list%string(i)%value, substring) > 0 ) then + idx = i + exit + endif + enddo + endif + + index_of_substring = idx +end function index_of_substring + +! delete_strings -- +! Delete one or more strings from the list +! +! Arguments: +! list The list of strings in which to search the string +! first The position of the first string to be deleted +! last The position of the last string to be deleted +! +! Note: +! If the range defined by first and last has a zero length or first > last, +! then nothing happens. +! +subroutine delete_strings( list, first, last ) + class(stringlist_type), intent(inout) :: list + integer, intent(in) :: first + integer, intent(in) :: last + + integer :: firstpos + integer :: lastpos + integer :: i + integer :: j + + firstpos = abspos( list, first ) + lastpos = min( abspos( list, last ), list%size ) + + if ( firstpos > lastpos ) then + return + else + do i = lastpos+1,list%size + j = firstpos + i - lastpos - 1 + call move_alloc( list%string(i)%value, list%string(j)%value ) + enddo + do i = list%size - (lastpos-firstpos), list%size + list%string(i)%value = '' + enddo + + list%size = list%size - (lastpos-firstpos + 1) + endif +end subroutine delete_strings + +! range_list -- +! Return a sublist given by the first and last position +! +! Arguments: +! list The list of strings in which to search the string +! first The position of the first string to be deleted +! last The position of the last string to be deleted +! +! Note: +! If the range defined by first and last has a zero length or first > last, +! then return an empty list +! +function range_list( list, first, last ) + class(stringlist_type), intent(inout) :: list + integer, intent(in) :: first + integer, intent(in) :: last + class(stringlist_type), allocatable :: range_list + + integer :: firstpos + integer :: lastpos + integer :: i + integer :: j + + allocate( range_list ) + + firstpos = abspos( list, first ) + lastpos = min( abspos( list, last ), list%size ) + + if ( firstpos > lastpos ) then + allocate( range_list%string(0) ) + return + else + range_list%size = lastpos - firstpos + 1 + range_list%string = list%string(firstpos:lastpos) + endif +end function range_list + +end module stdlib_stringlist diff --git a/src/tests/stringlist/test_stringlist.f90 b/src/tests/stringlist/test_stringlist.f90 index af74c9016..0c82cd53a 100644 --- a/src/tests/stringlist/test_stringlist.f90 +++ b/src/tests/stringlist/test_stringlist.f90 @@ -1,16 +1,17 @@ ! test_stringlist.f90 -- -! Test program for the stdlib_stringlist module ! +! +! test_stringlists ! Straightforward test program: try to cover "all" special cases ! program test_stringlists - use stdlib_stringlists + use stdlib_stringlist implicit none - type(t_stringlist) :: list - type(t_stringlist) :: list_sorted - integer :: i + type(stringlist_type) :: list, list_sorted, list_appended, list_to_search + type(stringlist_type) :: list_to_delete, sublist + integer :: i ! ! The straightforward cases: insert a few strings and retrieve them @@ -61,12 +62,54 @@ program test_stringlists write(*,*) '41', '>', list%get(41), '<' write(*,*) list%length() + list_appended = list // "Another string" + + write(*,*) 'List with appended string:' + do i = 1,list_appended%length() + write(*,*) i, '>', list_appended%get(i), '<' + enddo + ! ! Sort the list and print the result ! + write(*,*) 'Sorted list:' list_sorted = list%sort( .false. ) do i = 1,list_sorted%length() write(*,*) i, '>', list_sorted%get(i), '<' enddo + ! + ! Searching strings + ! + call list_to_search%insert( 0, 'A' ) + call list_to_search%insert( 1, 'A ' ) + call list_to_search%insert( 2, 'BB' ) + call list_to_search%insert( 3, 'CB' ) + + write(*,*) 'First "A": ', list_to_search%index( 'A' ) + write(*,*) 'Last "A": ', list_to_search%index( 'A', back =.true. ) + write(*,*) 'First "B": ', list_to_search%index_sub( 'B' ) + write(*,*) 'Last "B": ', list_to_search%index_sub( 'B', back =.true. ) + + ! + ! Deleting a string + ! + write(*,*) 'Deleting a string: ' + list_to_delete = list_to_search + call list_to_delete%delete( 1, 2 ) + + do i = 1,list_to_delete%length() + write(*,*) '>', list_to_delete%get(i), '<' + enddo + + ! + ! Get a range + ! + write(*,*) 'Get a sublist: ' + sublist = list_to_search%range( 2, 3 ) + + do i = 1,sublist%length() + write(*,*) '>', sublist%get(i), '<' + enddo + end program test_stringlists