Skip to content

Commit f0b9a0d

Browse files
committed
Merge branch 'develop_slice' into develop_find
2 parents 8d3dc7e + d38e0f4 commit f0b9a0d

File tree

3 files changed

+250
-61
lines changed

3 files changed

+250
-61
lines changed

doc/specs/stdlib_strings.md

+17-10
Original file line numberDiff line numberDiff line change
@@ -199,18 +199,25 @@ end program demo
199199

200200
#### Description
201201

202-
Extracts the characters from the defined region of the input string.
203-
Argument `first` and `last` defines the region for the function `slice` to operate.
204-
If the defined region is invalid (user provides atleast one invalid index), `first` and
205-
`last` indexes are converted to first and last valid indexes in this defined region respectively,
202+
Extracts the characters from the defined region of the input string by taking strides.
203+
204+
Deduction Process:
205+
Function first automatically deduces the optional arguments that are not provided by the user.
206+
This process is independent of both input `string` and permitted indexes of Fortran.
207+
Deduced `first` and `last` argument take +infinity or -infinity value whereas deduced `stride` argument takes +1 or -1 value.
208+
209+
Validation Process:
210+
Argument `first` and `last` defines this region for extraction by function `slice`.
211+
If the defined region is invalid i.e. region contains atleast one invalid index, `first` and
212+
`last` are converted to first and last valid indexes in this defined region respectively,
206213
if no valid index exists in this region an empty string is returned.
214+
`stride` can attain both negative or positive values but when the only invalid value
215+
0 is given, it is converted to 1.
207216

208-
`stride` can attain both negative or positive values but when the invalid value
209-
0 is given, it is converted to 1.
210-
Extraction starts from `first` index and takes stride of length `stride`.
211-
Extraction starts only if `last` index is crossable from `first` index by taking
212-
stride of length `stride`and is active until `last` index is crossed.
213-
Function automatically deduces the optional arguments that are not provided by the user.
217+
Extraction Process:
218+
After all this, extraction starts from `first` index and takes stride of length `stride`.
219+
Extraction starts only if `last` index is crossable from `first` index with stride `stride`
220+
and remains active until `last` index is crossed.
214221

215222
#### Syntax
216223

src/stdlib_strings.f90

+4-4
Original file line numberDiff line numberDiff line change
@@ -331,15 +331,15 @@ pure function slice_char(string, first, last, stride) result(sliced_string)
331331
character(len=:), allocatable :: sliced_string
332332
length_string = len(string)
333333

334-
first_index = 1
335-
last_index = length_string
334+
first_index = 0 ! first_index = -infinity
335+
last_index = length_string + 1 ! last_index = +infinity
336336
stride_vector = 1
337337

338338
if (present(stride)) then
339339
if (stride /= 0) then
340340
if (stride < 0) then
341-
first_index = length_string
342-
last_index = 1
341+
first_index = length_string + 1 ! first_index = +infinity
342+
last_index = 0 ! last_index = -infinity
343343
end if
344344
stride_vector = stride
345345
end if

src/tests/string/test_string_functions.f90

+229-47
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
! SPDX-Identifier: MIT
22
module test_string_functions
3+
use, intrinsic :: iso_fortran_env, only : error_unit
34
use stdlib_error, only : check
45
use stdlib_string_type, only : string_type, assignment(=), operator(==), &
56
to_lower, to_upper, to_title, to_sentence, reverse
67
use stdlib_strings, only: slice, find
8+
use stdlib_optval, only: optval
9+
use stdlib_ascii, only : to_string
710
implicit none
811

912
contains
@@ -53,57 +56,111 @@ subroutine test_reverse_string
5356

5457
end subroutine test_reverse_string
5558

56-
subroutine test_slice
59+
subroutine test_slice_string
5760
type(string_type) :: test_string
58-
character(len=:), allocatable :: test_char
5961
test_string = "abcdefghijklmnopqrstuvwxyz"
60-
test_char = "abcdefghijklmnopqrstuvwxyz"
61-
62-
call check(slice(test_string, 2, 16, 3) == "behkn", &
63-
'function slice failed', warn=.false.)
64-
call check(slice(test_char, first=15, stride=-1) == "onmlkjihgfedcba", &
65-
'function slice failed', warn=.false.)
66-
call check(slice(test_string, last=22, stride=-1) == "zyxwv", &
67-
'function slice failed', warn=.false.)
68-
call check(slice(test_char, 7, 2) == "gfedcb", &
69-
'function slice failed', warn=.false.)
70-
call check(slice(test_string, 7, 2, 1) == "", &
71-
'function slice failed', warn=.false.)
72-
call check(slice(test_char, 2, 6, -1) == "", &
73-
'function slice failed', warn=.false.)
74-
call check(slice(test_string, stride=-1) == "zyxwvutsrqponmlkjihgfedcba", &
75-
'function slice failed', warn=.false.)
76-
call check(slice(test_string, 7, 7, -4) == "g", &
77-
'function slice failed', warn=.false.)
78-
call check(slice(test_char, 7, 7, 3) == "g", &
79-
'function slice failed', warn=.false.)
80-
call check(slice(test_string, 7, 7, 3) == "g", &
81-
'function slice failed', warn=.false.)
82-
call check(slice(test_char, 7, -10) == "gfedcba", &
83-
'function slice failed', warn=.false.)
84-
call check(slice(test_string, 500, 22) == "zyxwv", &
85-
'function slice failed', warn=.false.)
86-
call check(slice(test_char, 50, 27) == "", &
87-
'function slice failed', warn=.false.)
88-
call check(slice(test_string, -20, -200) == "", &
89-
'function slice failed', warn=.false.)
90-
call check(slice(test_char, first=0, stride=-1) == "", &
91-
'function slice failed', warn=.false.)
92-
call check(slice(test_string, last=27, stride=-2) == "", &
93-
'function slice failed', warn=.false.)
94-
call check(slice(test_char, first=27, stride=2) == "", &
95-
'function slice failed', warn=.false.)
96-
call check(slice(test_string, -500, 500) == "abcdefghijklmnopqrstuvwxyz", &
97-
'function slice failed', warn=.false.)
9862

63+
! Only one argument is given
64+
! Valid
65+
call check(slice(test_string, first=10) == "jklmnopqrstuvwxyz", &
66+
"Slice, Valid arguments: first=10") ! last=+inf
67+
call check(slice(test_string, last=10) == "abcdefghij", &
68+
"Slice, Valid arguments: last=10") ! first=-inf
69+
call check(slice(test_string, stride=3) == "adgjmpsvy", &
70+
"Slice, Valid arguments: stride=3") ! first=-inf, last=+inf
71+
call check(slice(test_string, stride=-3) == "zwtqnkheb", &
72+
"Slice, Valid arguments: stride=-3") ! first=+inf, last=-inf
73+
74+
! Invalid
75+
call check(slice(test_string, first=27) == "", &
76+
"Slice, Invalid arguments: first=27") ! last=+inf
77+
call check(slice(test_string, first=-10) == "abcdefghijklmnopqrstuvwxyz", &
78+
"Slice, Invalid arguments: first=-10") ! last=+inf
79+
call check(slice(test_string, last=-2) == "", &
80+
"Slice, Invalid arguments: last=-2") ! first=-inf
81+
call check(slice(test_string, last=30) == "abcdefghijklmnopqrstuvwxyz", &
82+
"Slice, Invalid arguments: last=30") ! first=-inf
83+
call check(slice(test_string, stride=0) == "abcdefghijklmnopqrstuvwxyz", &
84+
"Slice, Invalid arguments: stride=0") ! stride=1
85+
86+
! Only two arguments are given
87+
! Valid
88+
call check(slice(test_string, first=10, last=20) == "jklmnopqrst", &
89+
"Slice, Valid arguments: first=10, last=20")
90+
call check(slice(test_string, first=7, last=2) == "gfedcb", &
91+
"Slice, Valid arguments: first=7, last=2") ! stride=-1
92+
call check(slice(test_string, first=10, stride=-2) == "jhfdb", &
93+
"Slice, Valid arguments: first=10, stride=-2") ! last=-inf
94+
call check(slice(test_string, last=21, stride=-2) == "zxv", &
95+
"Slice, Valid arguments: last=21, stride=-2") ! first=+inf
96+
97+
! Atleast one argument is invalid
98+
call check(slice(test_string, first=30, last=-3) == "zyxwvutsrqponmlkjihgfedcba", &
99+
"Slice, Invalid arguments: first=30, last=-3")
100+
call check(slice(test_string, first=1, last=-20) == "a", &
101+
"Slice, Invalid arguments: first=1, last=-20")
102+
call check(slice(test_string, first=7, last=-10) == "gfedcba", &
103+
"Slice, Invalid arguments: first=7, last=-10")
104+
call check(slice(test_string, first=500, last=22) == "zyxwv", &
105+
"Slice, Invalid arguments: first=500, last=22")
106+
call check(slice(test_string, first=50, last=27) == "", &
107+
"Slice, Invalid arguments: first=50, last=27")
108+
call check(slice(test_string, first=-20, last=0) == "", &
109+
"Slice, Invalid arguments: first=-20, last=0")
110+
call check(slice(test_string, last=-3, stride=-2) == "zxvtrpnljhfdb", &
111+
"Slice, Invalid arguments: last=-3, stride=-2") ! first=+inf
112+
call check(slice(test_string, last=10, stride=0) == "abcdefghij", &
113+
"Slice, Invalid arguments: last=10, stride=0") ! stride=1
114+
call check(slice(test_string, first=-2, stride=-2) == "", &
115+
"Slice, Invalid arguments: first=-2, stride=-2") ! last=-inf
116+
call check(slice(test_string, first=27, stride=2) == "", &
117+
"Slice, Invalid arguments: first=27, stride=2") ! last=+inf
118+
call check(slice(test_string, last=27, stride=-1) == "", &
119+
"Slice, Invalid arguments: last=27, stride=-1") ! first=+inf
120+
121+
! All three arguments are given
122+
! Valid
123+
call check(slice(test_string, first=2, last=16, stride=3) == "behkn", &
124+
"Slice, Valid arguments: first=2, last=16, stride=3")
125+
call check(slice(test_string, first=16, last=2, stride=-3) == "pmjgd", &
126+
"Slice, Valid arguments: first=16, last=2, stride=-3")
127+
call check(slice(test_string, first=7, last=7, stride=-4) == "g", &
128+
"Slice, Valid arguments: first=7, last=7, stride=-4")
129+
call check(slice(test_string, first=7, last=7, stride=3) == "g", &
130+
"Slice, Valid arguments: first=7, last=7, stride=3")
131+
call check(slice(test_string, first=2, last=6, stride=-1) == "", &
132+
"Slice, Valid arguments: first=2, last=6, stride=-1")
133+
call check(slice(test_string, first=20, last=10, stride=2) == "", &
134+
"Slice, Valid arguments: first=20, last=10, stride=2")
135+
136+
! Atleast one argument is invalid
137+
call check(slice(test_string, first=20, last=30, stride=2) == "tvxz", &
138+
"Slice, Invalid arguments: first=20, last=30, stride=2")
139+
call check(slice(test_string, first=-20, last=30, stride=2) == "acegikmoqsuwy", &
140+
"Slice, Invalid arguments: first=-20, last=30, stride=2")
141+
call check(slice(test_string, first=26, last=30, stride=1) == "z", &
142+
"Slice, Invalid arguments: first=26, last=30, stride=1")
143+
call check(slice(test_string, first=1, last=-20, stride=-1) == "a", &
144+
"Slice, Invalid arguments: first=1, last=-20, stride=-1")
145+
call check(slice(test_string, first=26, last=20, stride=1) == "", &
146+
"Slice, Invalid arguments: first=26, last=20, stride=1")
147+
call check(slice(test_string, first=1, last=20, stride=-1) == "", &
148+
"Slice, Invalid arguments: first=1, last=20, stride=-1")
149+
99150
test_string = ""
100-
test_char = ""
101-
call check(slice(test_string, 2, 16, 3) == "", &
102-
'function slice failed', warn=.false.)
103-
call check(slice(test_char, 2, 16, 3) == "", &
104-
'function slice failed', warn=.false.)
151+
! Empty string input
152+
call check(slice(test_string, first=-2, last=6) == "", &
153+
"Slice, Empty string: first=-2, last=6")
154+
call check(slice(test_string, first=6, last=-2) == "", &
155+
"Slice, Empty string: first=6, last=-2")
156+
call check(slice(test_string, first=-10) == "", &
157+
"Slice, Empty string: first=-10") ! last=+inf
158+
call check(slice(test_string, last=10) == "", &
159+
"Slice, Empty string: last=10") ! first=-inf
160+
call check(slice(test_string) == "", &
161+
"Slice, Empty string: no arguments provided")
105162

106-
end subroutine test_slice
163+
end subroutine test_slice_string
107164

108165
subroutine test_find
109166
type(string_type) :: test_string, test_pattern
@@ -118,6 +175,130 @@ subroutine test_find
118175

119176
end subroutine test_find
120177

178+
subroutine test_slice_gen
179+
character(len=*), parameter :: test = &
180+
& "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
181+
integer :: i, j, k
182+
integer, parameter :: offset = 3
183+
184+
do i = 1 - offset, len(test) + offset
185+
call check_slicer(test, first=i)
186+
end do
187+
188+
do i = 1 - offset, len(test) + offset
189+
call check_slicer(test, last=i)
190+
end do
191+
192+
do i = -len(test) - offset, len(test) + offset
193+
call check_slicer(test, stride=i)
194+
end do
195+
196+
do i = 1 - offset, len(test) + offset
197+
do j = 1 - offset, len(test) + offset
198+
call check_slicer(test, first=i, last=j)
199+
end do
200+
end do
201+
202+
do i = 1 - offset, len(test) + offset
203+
do j = -len(test) - offset, len(test) + offset
204+
call check_slicer(test, first=i, stride=j)
205+
end do
206+
end do
207+
208+
do i = 1 - offset, len(test) + offset
209+
do j = -len(test) - offset, len(test) + offset
210+
call check_slicer(test, last=i, stride=j)
211+
end do
212+
end do
213+
214+
do i = 1 - offset, len(test) + offset
215+
do j = 1 - offset, len(test) + offset
216+
do k = -len(test) - offset, len(test) + offset
217+
call check_slicer(test, first=i, last=j, stride=k)
218+
end do
219+
end do
220+
end do
221+
end subroutine test_slice_gen
222+
223+
subroutine check_slicer(string, first, last, stride)
224+
character(len=*), intent(in) :: string
225+
integer, intent(in), optional :: first
226+
integer, intent(in), optional :: last
227+
integer, intent(in), optional :: stride
228+
229+
character(len=:), allocatable :: actual, expected, message
230+
logical :: stat
231+
232+
actual = slice(string, first, last, stride)
233+
expected = reference_slice(string, first, last, stride)
234+
235+
stat = actual == expected
236+
237+
if (.not.stat) then
238+
message = "For input '"//string//"'"//new_line('a')
239+
240+
if (present(first)) then
241+
message = message // "first: "//to_string(first)//new_line('a')
242+
end if
243+
if (present(last)) then
244+
message = message // "last: "//to_string(last)//new_line('a')
245+
end if
246+
if (present(stride)) then
247+
message = message // "stride: "//to_string(stride)//new_line('a')
248+
end if
249+
message = message // "Expected: '"//expected//"' but got '"//actual//"'"
250+
end if
251+
call check(stat, message)
252+
253+
end subroutine check_slicer
254+
255+
pure function reference_slice(string, first, last, stride) result(sliced_string)
256+
character(len=*), intent(in) :: string
257+
integer, intent(in), optional :: first
258+
integer, intent(in), optional :: last
259+
integer, intent(in), optional :: stride
260+
character(len=:), allocatable :: sliced_string
261+
character(len=1), allocatable :: carray(:)
262+
263+
integer :: first_, last_, stride_
264+
265+
stride_ = 1
266+
if (present(stride)) then
267+
stride_ = merge(stride_, stride, stride == 0)
268+
else
269+
if (present(first) .and. present(last)) then
270+
if (last < first) stride_ = -1
271+
end if
272+
end if
273+
274+
if (stride_ < 0) then
275+
last_ = min(max(optval(last, 1), 1), len(string)+1)
276+
first_ = min(max(optval(first, len(string)), 0), len(string))
277+
else
278+
first_ = min(max(optval(first, 1), 1), len(string)+1)
279+
last_ = min(max(optval(last, len(string)), 0), len(string))
280+
end if
281+
282+
carray = string_to_carray(string)
283+
carray = carray(first_:last_:stride_)
284+
sliced_string = carray_to_string(carray)
285+
286+
end function reference_slice
287+
288+
pure function string_to_carray(string) result(carray)
289+
character(len=*), intent(in) :: string
290+
character(len=1) :: carray(len(string))
291+
292+
carray = transfer(string, carray)
293+
end function string_to_carray
294+
295+
pure function carray_to_string(carray) result(string)
296+
character(len=1), intent(in) :: carray(:)
297+
character(len=size(carray)) :: string
298+
299+
string = transfer(carray, string)
300+
end function carray_to_string
301+
121302
end module test_string_functions
122303

123304

@@ -130,7 +311,8 @@ program tester
130311
call test_to_title_string
131312
call test_to_sentence_string
132313
call test_reverse_string
133-
call test_slice
314+
call test_slice_string
134315
call test_find
316+
call test_slice_gen
135317

136318
end program tester

0 commit comments

Comments
 (0)