如何更好地解析 Fortran 中的可变时间戳信息?
How can I better parse variable time stamp information in Fortran?
我正在 gfortran 中编写代码以将可变时间戳分成年、月和日的单独部分。我编写了这段代码,以便用户可以输入时间戳格式(即 YEAR/MON/DAY、DAY/MON/YEAR 等)。这总共创建了 6 种可能的组合。我已经编写了试图处理这个问题的代码,但我认为它很丑陋而且做得很差。
我当前的代码使用了一系列 'if' 和 'goto' 语句。用户提供'tsfo',时间戳格式。 'ts'是包含时间戳数据的字符数组(多达100,000个时间戳)。 'tsdelim'是年、月、日之间的分隔符。我必须从 'frd'(第一个时间戳)循环到 'nlines'(最后一个时间戳)。
这是相关代码。
* Choose which case to go to.
first = INDEX(tsfo,tsdelim)
second = INDEX(tsfo(first+1:),tsdelim) + first
if (INDEX(tsfo(1:first-1),'YYYY') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'MM') .ne. 0) THEN
goto 1001
else
goto 1002
end if
else if (INDEX(tsfo(1:first-1),'MM') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'DD') .ne. 0) THEN
goto 1003
else
goto 1004
end if
else if (INDEX(tsfo(1:first-1),'DD') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'MM') .ne. 0) THEN
goto 1005
else
goto 1006
end if
end if
first = 0
second = 0
* Obtain the Julian Day number of each data entry.
* Acquire the year, month, and day of the time stamp.
* Find 'first' and 'second' and act accordingly.
* Case 1: YYYY/MM/DD
1001 do i = frd,nlines
first = INDEX(ts(i),tsdelim)
second = INDEX(ts(i)(first+1:),tsdelim) + first
read (ts(i)(1:first-1), '(i4)') Y
read (ts(i)(first+1:second-1), '(i2)') M
read (ts(i)(second+1:second+2), '(i2)') D
* Calculate the Julian Day number using a function.
temp1(i) = JLDYNUM(Y,M,D)
end do
goto 1200
* Case 2: YYYY/DD/MM
1002 do i = frd,nlines
first = INDEX(ts(i),tsdelim)
second = INDEX(ts(i)(first+1:),tsdelim) + first
read (ts(i)(1:first-1), '(i4)') Y
read (ts(i)(second+1:second+2), '(i2)') M
read (ts(i)(first+1:second-1), '(i2)') D
* Calculate the Julian Day number using a function.
temp1(i) = JLDYNUM(Y,M,D)
end do
goto 1200
* Onto the next part of the code
1200 blah blah blah
我相信这段代码会起作用,但我认为这不是一个很好的方法。有没有更好的方法来解决这个问题?
请务必注意,必须为每个时间戳计算索引 'first' 和 'second',因为月份和日期都可以用 1 或 2 个整数表示。年份始终由 4 表示。
我会用另一种方式对不同的情况进行编码,例如:
module foo
implicit none
private
public encode_datecode
contains
integer function encode_datecode(datestr, sep)
character(len=*), intent(in) :: datestr, sep
integer :: first, second
character(len=1) :: c1, c2, c3
first = index(datestr, sep)
second = index(datestr(first+1:), sep) + first
c1 = datestr(1:1)
c2 = datestr(first+1:first+1)
c3 = datestr(second+1:second+1)
foo = num(c1) + 3*num(c2) + 9*num(c3)
end function encode_datecode
integer function num(c)
character(len=1) :: c
if (c == 'Y') then
num = 0
else if (c == 'M') then
num = 1
else if (c == 'D') then
num = 2
else
stop "Illegal character"
end if
end function num
end module foo
然后在SELECT
声明中处理法律案件(21、15、19、7、11、5)。
这利用了没有 'YDDY/MY/YM' 格式的事实。
如果您希望更好的二进制或十进制可读性,您也可以乘以 4 或 10 而不是 3。
只有六个排列要处理,我将构建一个查找 table,将整个 tsfo
字符串作为键以及年、月和日的位置(第 1、第 2 或3)作为价值观。任何不受支持的格式都应该产生一个错误,我没有在下面编码。随后当您遍历 ts
列表并拆分一个项目时,您知道要将哪些位置转换为年、月和日整数变量:
PROGRAM timestamp
IMPLICIT NONE
CHARACTER(len=10) :: ts1(3) = ["2000/3/4 ","2000/25/12","2000/31/07"]
CHARACTER(len=10) :: ts2(3) = ["3/4/2000 ","25/12/2000","31/07/2000"]
CALL parse("YYYY/DD/MM",ts1)
print*
CALL parse("DD/MM/YYYY",ts2)
CONTAINS
SUBROUTINE parse(tsfo,ts)
IMPLICIT NONE
CHARACTER(len=*),INTENT(in) :: tsfo, ts(:)
TYPE sti
CHARACTER(len=10) :: stamp = "1234567890"
INTEGER :: iy = -1, im = -1, id = -1
END TYPE sti
TYPE(sti),PARAMETER :: stamps(6) = [sti("YYYY/MM/DD",1,2,3), sti("YYYY/DD/MM",1,3,2),&
sti("MM/DD/YYYY",2,3,1), sti("DD/MM/YYYY",3,2,1),&
sti("MM/YYYY/DD",2,1,3), sti("DD/YYYY/MM",3,1,2)]
TYPE(sti) :: thisTsfo
INTEGER :: k, k1, k2
INTEGER :: y, m, d
CHARACTER(len=10) :: cc(3)
DO k=1,SIZE(stamps)
IF(TRIM(tsfo) == stamps(k)%stamp) THEN
thisTsfo = stamps(k)
EXIT
ENDIF
ENDDO
print*,thisTsfo
DO k=1,SIZE(ts)
k1 = INDEX(ts(k),"/")
k2 = INDEX(ts(k),"/",BACK=.TRUE.)
cc(1) = ts(k)(:k1-1)
cc(2) = ts(k)(k1+1:k2-1)
cc(3) = ts(k)(k2+1:)
READ(cc(thisTsfo%iy),'(i4)') y
READ(cc(thisTsfo%im),'(i2)') m
READ(cc(thisTsfo%id),'(i2)') d
PRINT*,ts(k),y,m,d
ENDDO
END SUBROUTINE parse
END PROGRAM timestamp
我正在 gfortran 中编写代码以将可变时间戳分成年、月和日的单独部分。我编写了这段代码,以便用户可以输入时间戳格式(即 YEAR/MON/DAY、DAY/MON/YEAR 等)。这总共创建了 6 种可能的组合。我已经编写了试图处理这个问题的代码,但我认为它很丑陋而且做得很差。
我当前的代码使用了一系列 'if' 和 'goto' 语句。用户提供'tsfo',时间戳格式。 'ts'是包含时间戳数据的字符数组(多达100,000个时间戳)。 'tsdelim'是年、月、日之间的分隔符。我必须从 'frd'(第一个时间戳)循环到 'nlines'(最后一个时间戳)。
这是相关代码。
* Choose which case to go to.
first = INDEX(tsfo,tsdelim)
second = INDEX(tsfo(first+1:),tsdelim) + first
if (INDEX(tsfo(1:first-1),'YYYY') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'MM') .ne. 0) THEN
goto 1001
else
goto 1002
end if
else if (INDEX(tsfo(1:first-1),'MM') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'DD') .ne. 0) THEN
goto 1003
else
goto 1004
end if
else if (INDEX(tsfo(1:first-1),'DD') .ne. 0) THEN
if (INDEX(tsfo(first+1:second-1),'MM') .ne. 0) THEN
goto 1005
else
goto 1006
end if
end if
first = 0
second = 0
* Obtain the Julian Day number of each data entry.
* Acquire the year, month, and day of the time stamp.
* Find 'first' and 'second' and act accordingly.
* Case 1: YYYY/MM/DD
1001 do i = frd,nlines
first = INDEX(ts(i),tsdelim)
second = INDEX(ts(i)(first+1:),tsdelim) + first
read (ts(i)(1:first-1), '(i4)') Y
read (ts(i)(first+1:second-1), '(i2)') M
read (ts(i)(second+1:second+2), '(i2)') D
* Calculate the Julian Day number using a function.
temp1(i) = JLDYNUM(Y,M,D)
end do
goto 1200
* Case 2: YYYY/DD/MM
1002 do i = frd,nlines
first = INDEX(ts(i),tsdelim)
second = INDEX(ts(i)(first+1:),tsdelim) + first
read (ts(i)(1:first-1), '(i4)') Y
read (ts(i)(second+1:second+2), '(i2)') M
read (ts(i)(first+1:second-1), '(i2)') D
* Calculate the Julian Day number using a function.
temp1(i) = JLDYNUM(Y,M,D)
end do
goto 1200
* Onto the next part of the code
1200 blah blah blah
我相信这段代码会起作用,但我认为这不是一个很好的方法。有没有更好的方法来解决这个问题?
请务必注意,必须为每个时间戳计算索引 'first' 和 'second',因为月份和日期都可以用 1 或 2 个整数表示。年份始终由 4 表示。
我会用另一种方式对不同的情况进行编码,例如:
module foo
implicit none
private
public encode_datecode
contains
integer function encode_datecode(datestr, sep)
character(len=*), intent(in) :: datestr, sep
integer :: first, second
character(len=1) :: c1, c2, c3
first = index(datestr, sep)
second = index(datestr(first+1:), sep) + first
c1 = datestr(1:1)
c2 = datestr(first+1:first+1)
c3 = datestr(second+1:second+1)
foo = num(c1) + 3*num(c2) + 9*num(c3)
end function encode_datecode
integer function num(c)
character(len=1) :: c
if (c == 'Y') then
num = 0
else if (c == 'M') then
num = 1
else if (c == 'D') then
num = 2
else
stop "Illegal character"
end if
end function num
end module foo
然后在SELECT
声明中处理法律案件(21、15、19、7、11、5)。
这利用了没有 'YDDY/MY/YM' 格式的事实。
如果您希望更好的二进制或十进制可读性,您也可以乘以 4 或 10 而不是 3。
只有六个排列要处理,我将构建一个查找 table,将整个 tsfo
字符串作为键以及年、月和日的位置(第 1、第 2 或3)作为价值观。任何不受支持的格式都应该产生一个错误,我没有在下面编码。随后当您遍历 ts
列表并拆分一个项目时,您知道要将哪些位置转换为年、月和日整数变量:
PROGRAM timestamp
IMPLICIT NONE
CHARACTER(len=10) :: ts1(3) = ["2000/3/4 ","2000/25/12","2000/31/07"]
CHARACTER(len=10) :: ts2(3) = ["3/4/2000 ","25/12/2000","31/07/2000"]
CALL parse("YYYY/DD/MM",ts1)
print*
CALL parse("DD/MM/YYYY",ts2)
CONTAINS
SUBROUTINE parse(tsfo,ts)
IMPLICIT NONE
CHARACTER(len=*),INTENT(in) :: tsfo, ts(:)
TYPE sti
CHARACTER(len=10) :: stamp = "1234567890"
INTEGER :: iy = -1, im = -1, id = -1
END TYPE sti
TYPE(sti),PARAMETER :: stamps(6) = [sti("YYYY/MM/DD",1,2,3), sti("YYYY/DD/MM",1,3,2),&
sti("MM/DD/YYYY",2,3,1), sti("DD/MM/YYYY",3,2,1),&
sti("MM/YYYY/DD",2,1,3), sti("DD/YYYY/MM",3,1,2)]
TYPE(sti) :: thisTsfo
INTEGER :: k, k1, k2
INTEGER :: y, m, d
CHARACTER(len=10) :: cc(3)
DO k=1,SIZE(stamps)
IF(TRIM(tsfo) == stamps(k)%stamp) THEN
thisTsfo = stamps(k)
EXIT
ENDIF
ENDDO
print*,thisTsfo
DO k=1,SIZE(ts)
k1 = INDEX(ts(k),"/")
k2 = INDEX(ts(k),"/",BACK=.TRUE.)
cc(1) = ts(k)(:k1-1)
cc(2) = ts(k)(k1+1:k2-1)
cc(3) = ts(k)(k2+1:)
READ(cc(thisTsfo%iy),'(i4)') y
READ(cc(thisTsfo%im),'(i2)') m
READ(cc(thisTsfo%id),'(i2)') d
PRINT*,ts(k),y,m,d
ENDDO
END SUBROUTINE parse
END PROGRAM timestamp