Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d2ac7ae

Browse files
authoredJun 12, 2021
implemented intelligent slice functionality (#414)
implemented intelligent slice functionality
2 parents 9929cdb + d38e0f4 commit d2ac7ae

File tree

5 files changed

+396
-4
lines changed

5 files changed

+396
-4
lines changed
 

‎doc/specs/stdlib_string_type.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,7 +1254,7 @@ The result is a scalar `string_type` value.
12541254

12551255
```fortran
12561256
program demo_to_title
1257-
use stdlib_string_type, only: string_type, to_title
1257+
use stdlib_string_type
12581258
implicit none
12591259
type(string_type) :: string, titlecase_string
12601260
@@ -1302,7 +1302,7 @@ The result is a scalar `string_type` value.
13021302

13031303
```fortran
13041304
program demo_to_sentence
1305-
use stdlib_string_type, only: string_type, to_sentence
1305+
use stdlib_string_type
13061306
implicit none
13071307
type(string_type) :: string, sentencecase_string
13081308

‎doc/specs/stdlib_strings.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,81 @@ program demo
192192
print'(a)', ends_with("pattern", "pat") ! F
193193
end program demo
194194
```
195+
196+
197+
<!-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
198+
### `slice`
199+
200+
#### Description
201+
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,
213+
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.
216+
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.
221+
222+
#### Syntax
223+
224+
`string = [[stdlib_strings(module):slice(interface)]] (string, first, last, stride)`
225+
226+
#### Status
227+
228+
Experimental
229+
230+
#### Class
231+
232+
Pure function.
233+
234+
#### Argument
235+
236+
- `string`: Character scalar or [[stdlib_string_type(module):string_type(type)]]
237+
This argument is intent(in).
238+
- `first`: integer
239+
This argument is intent(in) and optional.
240+
- `last`: integer
241+
This argument is intent(in) and optional.
242+
- `stride`: integer
243+
This argument is intent(in) and optional.
244+
245+
#### Result value
246+
247+
The result is of the same type as `string`.
248+
249+
#### Example
250+
251+
```fortran
252+
program demo_slice
253+
use stdlib_string_type
254+
use stdlib_strings, only : slice
255+
implicit none
256+
type(string_type) :: string
257+
character(len=10) :: char
258+
259+
string = "abcdefghij"
260+
! string <-- "abcdefghij"
261+
262+
char = "abcdefghij"
263+
! char <-- "abcdefghij"
264+
265+
print'(a)', slice("abcdefghij", 2, 6, 2) ! "bdf"
266+
print'(a)', slice(char, 2, 6, 2) ! "bdf"
267+
268+
string = slice(string, 2, 6, 2)
269+
! string <-- "bdf"
270+
271+
end program demo_slice
272+
```

‎src/Makefile.manual

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ stdlib_stats_var.o: \
125125
stdlib_stats_distribution_PRNG.o: \
126126
stdlib_kinds.o \
127127
stdlib_error.o
128-
stdlib_string_type.o: stdlib_ascii.o stdlib_kinds.o
129-
stdlib_strings.o: stdlib_ascii.o stdlib_string_type.o
128+
stdlib_string_type.o: stdlib_ascii.o \
129+
stdlib_kinds.o
130+
stdlib_strings.o: stdlib_ascii.o \
131+
stdlib_string_type.o
130132
stdlib_math.o: stdlib_kinds.o

‎src/stdlib_strings.f90

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module stdlib_strings
1111

1212
public :: strip, chomp
1313
public :: starts_with, ends_with
14+
public :: slice
1415

1516

1617
!> Remove leading and trailing whitespace characters.
@@ -57,6 +58,14 @@ module stdlib_strings
5758
module procedure :: ends_with_char_string
5859
module procedure :: ends_with_char_char
5960
end interface ends_with
61+
62+
!> Extracts characters from the input string to return a new string
63+
!>
64+
!> Version: experimental
65+
interface slice
66+
module procedure :: slice_string
67+
module procedure :: slice_char
68+
end interface slice
6069

6170

6271
contains
@@ -290,5 +299,72 @@ elemental function ends_with_string_string(string, substring) result(match)
290299

291300
end function ends_with_string_string
292301

302+
!> Extract the characters from the region between 'first' and 'last' index (both inclusive)
303+
!> of the input 'string' by taking strides of length 'stride'
304+
!> Returns a new string
305+
elemental function slice_string(string, first, last, stride) result(sliced_string)
306+
type(string_type), intent(in) :: string
307+
integer, intent(in), optional :: first, last, stride
308+
type(string_type) :: sliced_string
309+
310+
sliced_string = string_type(slice(char(string), first, last, stride))
311+
312+
end function slice_string
313+
314+
!> Extract the characters from the region between 'first' and 'last' index (both inclusive)
315+
!> of the input 'string' by taking strides of length 'stride'
316+
!> Returns a new string
317+
pure function slice_char(string, first, last, stride) result(sliced_string)
318+
character(len=*), intent(in) :: string
319+
integer, intent(in), optional :: first, last, stride
320+
integer :: first_index, last_index, stride_vector, strides_taken, length_string, i, j
321+
character(len=:), allocatable :: sliced_string
322+
length_string = len(string)
323+
324+
first_index = 0 ! first_index = -infinity
325+
last_index = length_string + 1 ! last_index = +infinity
326+
stride_vector = 1
327+
328+
if (present(stride)) then
329+
if (stride /= 0) then
330+
if (stride < 0) then
331+
first_index = length_string + 1 ! first_index = +infinity
332+
last_index = 0 ! last_index = -infinity
333+
end if
334+
stride_vector = stride
335+
end if
336+
else
337+
if (present(first) .and. present(last)) then
338+
if (last < first) then
339+
stride_vector = -1
340+
end if
341+
end if
342+
end if
343+
344+
if (present(first)) then
345+
first_index = first
346+
end if
347+
if (present(last)) then
348+
last_index = last
349+
end if
350+
351+
if (stride_vector > 0) then
352+
first_index = max(first_index, 1)
353+
last_index = min(last_index, length_string)
354+
else
355+
first_index = min(first_index, length_string)
356+
last_index = max(last_index, 1)
357+
end if
358+
359+
strides_taken = floor( real(last_index - first_index)/real(stride_vector) )
360+
allocate(character(len=max(0, strides_taken + 1)) :: sliced_string)
361+
362+
j = 1
363+
do i = first_index, last_index, stride_vector
364+
sliced_string(j:j) = string(i:i)
365+
j = j + 1
366+
end do
367+
end function slice_char
368+
293369

294370
end module stdlib_strings

‎src/tests/string/test_string_functions.f90

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +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
7+
use stdlib_strings, only: slice
8+
use stdlib_optval, only: optval
9+
use stdlib_ascii, only : to_string
610
implicit none
711

812
contains
@@ -52,6 +56,236 @@ subroutine test_reverse_string
5256

5357
end subroutine test_reverse_string
5458

59+
subroutine test_slice_string
60+
type(string_type) :: test_string
61+
test_string = "abcdefghijklmnopqrstuvwxyz"
62+
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+
150+
test_string = ""
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")
162+
163+
end subroutine test_slice_string
164+
165+
subroutine test_slice_gen
166+
character(len=*), parameter :: test = &
167+
& "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
168+
integer :: i, j, k
169+
integer, parameter :: offset = 3
170+
171+
do i = 1 - offset, len(test) + offset
172+
call check_slicer(test, first=i)
173+
end do
174+
175+
do i = 1 - offset, len(test) + offset
176+
call check_slicer(test, last=i)
177+
end do
178+
179+
do i = -len(test) - offset, len(test) + offset
180+
call check_slicer(test, stride=i)
181+
end do
182+
183+
do i = 1 - offset, len(test) + offset
184+
do j = 1 - offset, len(test) + offset
185+
call check_slicer(test, first=i, last=j)
186+
end do
187+
end do
188+
189+
do i = 1 - offset, len(test) + offset
190+
do j = -len(test) - offset, len(test) + offset
191+
call check_slicer(test, first=i, stride=j)
192+
end do
193+
end do
194+
195+
do i = 1 - offset, len(test) + offset
196+
do j = -len(test) - offset, len(test) + offset
197+
call check_slicer(test, last=i, stride=j)
198+
end do
199+
end do
200+
201+
do i = 1 - offset, len(test) + offset
202+
do j = 1 - offset, len(test) + offset
203+
do k = -len(test) - offset, len(test) + offset
204+
call check_slicer(test, first=i, last=j, stride=k)
205+
end do
206+
end do
207+
end do
208+
end subroutine test_slice_gen
209+
210+
subroutine check_slicer(string, first, last, stride)
211+
character(len=*), intent(in) :: string
212+
integer, intent(in), optional :: first
213+
integer, intent(in), optional :: last
214+
integer, intent(in), optional :: stride
215+
216+
character(len=:), allocatable :: actual, expected, message
217+
logical :: stat
218+
219+
actual = slice(string, first, last, stride)
220+
expected = reference_slice(string, first, last, stride)
221+
222+
stat = actual == expected
223+
224+
if (.not.stat) then
225+
message = "For input '"//string//"'"//new_line('a')
226+
227+
if (present(first)) then
228+
message = message // "first: "//to_string(first)//new_line('a')
229+
end if
230+
if (present(last)) then
231+
message = message // "last: "//to_string(last)//new_line('a')
232+
end if
233+
if (present(stride)) then
234+
message = message // "stride: "//to_string(stride)//new_line('a')
235+
end if
236+
message = message // "Expected: '"//expected//"' but got '"//actual//"'"
237+
end if
238+
call check(stat, message)
239+
240+
end subroutine check_slicer
241+
242+
pure function reference_slice(string, first, last, stride) result(sliced_string)
243+
character(len=*), intent(in) :: string
244+
integer, intent(in), optional :: first
245+
integer, intent(in), optional :: last
246+
integer, intent(in), optional :: stride
247+
character(len=:), allocatable :: sliced_string
248+
character(len=1), allocatable :: carray(:)
249+
250+
integer :: first_, last_, stride_
251+
252+
stride_ = 1
253+
if (present(stride)) then
254+
stride_ = merge(stride_, stride, stride == 0)
255+
else
256+
if (present(first) .and. present(last)) then
257+
if (last < first) stride_ = -1
258+
end if
259+
end if
260+
261+
if (stride_ < 0) then
262+
last_ = min(max(optval(last, 1), 1), len(string)+1)
263+
first_ = min(max(optval(first, len(string)), 0), len(string))
264+
else
265+
first_ = min(max(optval(first, 1), 1), len(string)+1)
266+
last_ = min(max(optval(last, len(string)), 0), len(string))
267+
end if
268+
269+
carray = string_to_carray(string)
270+
carray = carray(first_:last_:stride_)
271+
sliced_string = carray_to_string(carray)
272+
273+
end function reference_slice
274+
275+
pure function string_to_carray(string) result(carray)
276+
character(len=*), intent(in) :: string
277+
character(len=1) :: carray(len(string))
278+
279+
carray = transfer(string, carray)
280+
end function string_to_carray
281+
282+
pure function carray_to_string(carray) result(string)
283+
character(len=1), intent(in) :: carray(:)
284+
character(len=size(carray)) :: string
285+
286+
string = transfer(carray, string)
287+
end function carray_to_string
288+
55289
end module test_string_functions
56290

57291

@@ -64,5 +298,7 @@ program tester
64298
call test_to_title_string
65299
call test_to_sentence_string
66300
call test_reverse_string
301+
call test_slice_string
302+
call test_slice_gen
67303

68304
end program tester

0 commit comments

Comments
 (0)
Please sign in to comment.