diff --git a/src/mo_util_string.F90 b/src/mo_util_string.F90 index 3fcdd452b52d1d1e85d79c4f11ab11d0e44a98cd..52bfc6955ef2790cb2299ffe2da98e5a222c8fb6 100644 --- a/src/mo_util_string.F90 +++ b/src/mo_util_string.F90 @@ -77,6 +77,8 @@ MODULE mo_util_string MODULE PROCEDURE charArray_equal_char END INTERFACE charArray_equal + PUBLIC :: new_list + INTERFACE add_to_list MODULE PROCEDURE add_to_list, add_to_list_1 END INTERFACE add_to_list @@ -575,6 +577,20 @@ CONTAINS END DO END SUBROUTINE difference + !============================================================================== + !> Create a new empty string list + !! + !! Allocates space for 8 strings initially. Size doubles every time the list + !! has to be expanded, so adding to the list is constant amortized time. + SUBROUTINE new_list(str_list, nitems) + CHARACTER(len=*), ALLOCATABLE, INTENT(OUT) :: str_list(:) + INTEGER, INTENT(OUT) :: nitems + + ALLOCATE (str_list(8)) + nitems = 0 + + END SUBROUTINE new_list + !============================================================================== !+ Add entries from list 2 to list 1, if they are not already present !+ in list 1. @@ -582,20 +598,38 @@ CONTAINS ! This is a very crude implementation, quadratic complexity. ! SUBROUTINE add_to_list(str_list1, nitems1, str_list2, nitems2) - CHARACTER(len=*), INTENT(INOUT) :: str_list1(:) + CHARACTER(len=*), ALLOCATABLE, INTENT(INOUT) :: str_list1(:) INTEGER, INTENT(INOUT) :: nitems1 - CHARACTER(len=*), INTENT(IN) :: str_list2(:) - INTEGER, INTENT(IN) :: nitems2 + CHARACTER(len=*), INTENT(IN) :: str_list2(:) + INTEGER, OPTIONAL, INTENT(IN) :: nitems2 ! local variables - INTEGER :: iread, i + INTEGER :: iread, i, nitems2_ + CHARACTER(len=LEN(str_list1)), ALLOCATABLE :: tmp(:) + + IF (.NOT. ALLOCATED(str_list1)) THEN + CALL new_list(str_list1, nitems1) + END IF + + IF (PRESENT(nitems2)) THEN + nitems2_ = nitems2 + ELSE + nitems2_ = SIZE(str_list2) + END IF ! Loop over all items that should potentially be added - item_add_loop: DO iread = 1, nitems2 + item_add_loop: DO iread = 1, nitems2_ ! Loop over all items in the target list (list 1) to ! check if item is already in string list 1: DO i = 1, nitems1 IF (str_list1(i) == str_list2(iread)) CYCLE item_add_loop END DO + + IF (nitems1 >= SIZE(str_list1)) THEN + ALLOCATE (tmp(2*SIZE(str_list1))) + tmp(1:nitems1) = str_list1(1:nitems1) + CALL MOVE_ALLOC(tmp, str_list1) + END IF + nitems1 = nitems1 + 1 str_list1(nitems1) = str_list2(iread) END DO item_add_loop @@ -603,17 +637,29 @@ CONTAINS END SUBROUTINE add_to_list SUBROUTINE add_to_list_1(str_list, nitems, str) - CHARACTER(len=*), INTENT(INOUT) :: str_list(:) + CHARACTER(len=*), ALLOCATABLE, INTENT(INOUT) :: str_list(:) INTEGER, INTENT(INOUT) :: nitems CHARACTER(len=*), INTENT(IN) :: str ! local variables - INTEGER :: iread, i + INTEGER :: i + CHARACTER(len=LEN(str_list)), ALLOCATABLE :: tmp(:) + + IF (.NOT. ALLOCATED(str_list)) THEN + CALL new_list(str_list, nitems) + END IF ! Loop over all items in the target list to ! check if item is already in that list DO i = 1, nitems IF (str_list(i) == str) RETURN END DO + + IF (nitems >= SIZE(str_list)) THEN + ALLOCATE (tmp(2*SIZE(str_list))) + tmp(1:nitems) = str_list(1:nitems) + CALL MOVE_ALLOC(tmp, str_list) + END IF + nitems = nitems + 1 str_list(nitems) = str diff --git a/test/fortran/test_util_string.f90 b/test/fortran/test_util_string.f90 index 6429d9bfb3c2d6ccc18d99925bf6d8ae6c7b69f5..3839e9a6aed7645bbd6ad5f612977986056fe75d 100644 --- a/test/fortran/test_util_string.f90 +++ b/test/fortran/test_util_string.f90 @@ -111,33 +111,55 @@ CONTAINS .AND. str_list1(3) == 'word5'), .TRUE.) END SUBROUTINE TEST_difference + SUBROUTINE TEST_new_list + CHARACTER(len=30), ALLOCATABLE :: str_list(:) + INTEGER :: nitems + CALL TAG_TEST("TEST_new_list") + CALL new_list(str_list, nitems) + CALL ASSERT_EQUAL(nitems == 0 .AND. ALLOCATED(str_list), .TRUE.) + END SUBROUTINE TEST_new_list + SUBROUTINE TEST_add_to_list - CHARACTER(len=30), DIMENSION(5) :: str_list1 + CHARACTER(len=30), ALLOCATABLE, DIMENSION(:) :: str_list1 CHARACTER(len=30), DIMENSION(2) :: str_list2 INTEGER :: nitems1, nitems2 - CALL TAG_TEST("TEST_add_to_list") - str_list1 = ['word1', 'word2', 'word3', ' ', ' '] + CALL add_to_list(str_list1, nitems1, ['word1', 'word2', 'word3']) ! Implicit allocation + CALL TAG_TEST("TEST_add_to_list (1)") + CALL ASSERT_EQUAL(nitems1 == 3 .AND. ALLOCATED(str_list1) .AND. SIZE(str_list1) >= nitems1, .TRUE.) str_list2 = ['word2', 'word4'] - nitems1 = 3 nitems2 = SIZE(str_list2) CALL add_to_list(str_list1, nitems1, str_list2, nitems2) + CALL TAG_TEST("TEST_add_to_list (2)") CALL ASSERT_EQUAL((nitems1 == 4 .AND. str_list1(1) == 'word1' .AND. & str_list1(2) == 'word2' .AND. str_list1(3) == 'word3' & .AND. str_list1(4) == 'word4'), .TRUE.) + CALL add_to_list(str_list1, nitems1, ['5', '6', '7', '8', '9']) + CALL TAG_TEST("TEST_add_to_list (3)") + CALL ASSERT_EQUAL(nitems1 == 9 .AND. ALLOCATED(str_list1) .AND. SIZE(str_list1) >= nitems1 .AND. & + ALL(str_list1(1:nitems1) == [ & + 'word1', 'word2', 'word3', 'word4', '5 ', '6 ', '7 ', '8 ', '9 ']), .TRUE.) END SUBROUTINE TEST_add_to_list SUBROUTINE TEST_add_to_list_1 - CHARACTER(len=30), DIMENSION(5) :: str_list + CHARACTER(len=30), ALLOCATABLE, DIMENSION(:) :: str_list INTEGER :: nitems - CHARACTER(len=30) :: str - CALL TAG_TEST("TEST_add_to_list_1") - str_list = ['word1', 'word2', 'word3', ' ', ' '] - nitems = 3 - str = 'word4' - CALL add_to_list(str_list, nitems, str) + CALL add_to_list(str_list, nitems, 'word1') ! Implicit allocation + CALL TAG_TEST("TEST_add_to_list_1 (1)") + CALL ASSERT_EQUAL(nitems == 1 .AND. ALLOCATED(str_list) .AND. SIZE(str_list) >= nitems, .TRUE.) + CALL add_to_list(str_list, nitems, 'word2') + CALL add_to_list(str_list, nitems, 'word3') + CALL add_to_list(str_list, nitems, 'word4') + CALL add_to_list(str_list, nitems, 'word2') ! Try to add duplicate + CALL TAG_TEST("TEST_add_to_list_1 (2)") CALL ASSERT_EQUAL((nitems == 4 .AND. str_list(1) == 'word1' & .AND. str_list(2) == 'word2' .AND. str_list(3) == 'word3' & .AND. str_list(4) == 'word4'), .TRUE.) + CALL add_to_list(str_list, nitems, ['5', '6', '7', '8']) ! Fill to allocated size + CALL add_to_list(str_list, nitems, '9') ! Expand list + CALL TAG_TEST("TEST_add_to_list_1 (3)") + CALL ASSERT_EQUAL(nitems == 9 .AND. ALLOCATED(str_list) .AND. SIZE(str_list) >= nitems .AND. & + ALL(str_list(1:nitems) == [ & + 'word1', 'word2', 'word3', 'word4', '5 ', '6 ', '7 ', '8 ', '9 ']), .TRUE.) END SUBROUTINE TEST_add_to_list_1 SUBROUTINE TEST_remove_whitespace @@ -265,4 +287,3 @@ CONTAINS END SUBROUTINE TEST_associate_keyword_with_keywords END MODULE TEST_STRING -