가장 쉬운 포트란 90 기반 데이터 추출 (parsing) by 바죠

가장 쉬운 포트란 90 기반 데이터 추출 (data parsing)

포트란(FORTRAN) 컴퓨터 언어로 파일 속의 데이터를 추출하기는 다른 언어에 비해서 어렵다.  예를 들어 파이썬의 경우 매우 잘 만들어진 루틴들이 존재한다.  파이썬 컴퓨터 언어의 경우, 한 발 더 나아가서, 특정 웹페이지도 쉽게 파싱할 수 있는 수준이다. 또한, awk 컴퓨터 언어를 사용하면 보다 쉽게 파일속의 데이터를 잘 잡아 낼 수 있다. 거의 유닉스 명령어처럼 간결한 한 줄 짜리 명령어 나열로 특정 데이터를 뽑아낼 수 있다. 다시 말해서, 주어진 파일들에서 특정한 데이터, 정보를 얻어내는 작업은 매우 다양한 방법으로 수행할 수 있다.   

AWK 언어를 활용할 경우 간단하게 파일 속의 데이터를 잡아 낼 수 있다 : http://incredible.egloos.com/5074249
컴퓨터 프로그램 입력을 다루는 키워드 작성 : http://incredible.egloos.com/7386526

원하는 또다른 양식의 파일로 출력을 할 수도 있다.  연속해서 간단한 산수 계산까지 한 번에 수행이 가능하다. 쉽게 말해서, grep 으로 특정 데이터를 찾아내고 "bc -l" 과 같은 계산기를 따로 불러서 사용할 필요가 없다.

전통적으로 포트란 컴퓨터 언어는 파싱보다는 집중된 사칙연산과 관련된 계산 위주로 발달했기 때문에 파싱에는 다소 어려움이 있다.  하지만, 포트란 컴퓨터 언어는 장시간 동안 사람들이 사용한 컴퓨터 언어이다.  분명히  파싱과 관련된 유사한 일들을 할 수밖에 없었을 것이다.  이것은 매우 분명한 사실이다.  어려운 이유는 당연히 기본적으로 지원되는 루틴들이 없어서 그렇다. 이러한 일들을 많이 하지 않기 때문이다.

그렇지만, 역시 안 되는 것이 아니다. 잘 된다. 더 잘하기, 더 쉽게 하기 위해서는 몇 가지 루틴들을 잘 활용할 필요가 있다.
보다 더 잘하기 위해서는 다른 사람들의 도움을 받을 필요가 있다. 이러한 관점에서 아래의 루틴들을 소개한다.

George Benthien 박사가 만든 것:
http://www.gbenthien.net/strings/index.html
http://www.gbenthien.net/strings/Strings.pdf
URL 연결이 원활하지 않아서 해당 파일을 이곳에 직접 첨부함:
strings.txt

SUBROUTINE READLINE(nunitr, line, ios)         : 문서의 한 줄을 읽어 들인다.
SUBROUTINE PARSE(str, delims, args, nargs)  : 특정 문구, 패턴을 찾아 낼 때 사용한다. delimiter, 분리자를 활용한다.
SUBROUTINE VALUE(str, number, ios)             : 숫자에 해당하는 것들 진짜 숫자로 표현한다. generic function
FUNCTION IS_LETTER(ch)                              : 문자 체크
FUNCTION IS_DIGIT(ch)                                  : 숫자 체크
function uppercase(str) result(ucstr)                  : 소문자를 모두 대문자로 변환시켜준다.

delimiter, 분리자는 통상 블랭크가 될 수 있다. 하지만, comma, semicolon, conlon 등 다양하게 활용될 수 있다.  하나의 문자열(스트링)을 분리할 때, delimiter 분리자를 활용한다. 이렇게 몇 개의 덩어리로 분리할 수 있다.  이중에는 단어, 숫자가 포함 될 수 있다.  물론, 단어들만으로 구성될 수도 있다. 숫자로만으로도 구성될 수 있다. 예를 들어 아래에서 처럼 하나의 문자열(스트링)이 있을 때,
abc  def  12.34   567
delims=' ' 처럼 주면 블랭크를 기준으로 문자열(스트링)을 나누게 된다.
abc
def
12.34
567
처럼 4개의 항목들로 나누어진다. 즉, 프로그램 상에서는 아래와 같은 상황으로 바뀌게 된다.
nargs=4
args(1)='abc'
args(2)='def'
args(3)='12.34'
args(4)='567'

12.34
567
은 value 서브루틴을 각각 활용하여 실제 숫자를 뽑아낼 수 있다.


예를 들어, 파일에서 숫자를 추출할 때에는 call value(string, number, ios) 라는 generic 서브루틴을 활용하면 된다. 지네릭 서브루틴이기 때문에 자료형에 상관없이 이 함수를 불러서 사용하면 된다.  즉, number가 실수형/정수형이면 그 곳에 실수형/정수형 값이 들어가 있기를 희망하는 것이다. str은 문자열(스트링) 형식으로 저장되어 있는 실수형 값이라고 볼 수 있다. ios 값이 0 이면 성공적으로 숫자를 뽑아내었다고 볼 수 있다. ios 값이 0 이 아니면 문제가 있음을 의미한다. 따라서 특별한 조치를 취해야만 할 것이다. 이 때, ios는 정수형 변수이다.  이 부분만 주의하면 일반적인 상황에서의 사실상 데이터 추출문제는 해결된다. 설사 데이터가 잘못 주어졌다고 하더라도 프로그래밍을 계속해서 진행할 수 있다는 뜻이다. 에러가 발생한 상황을 인식할 수 있다. 이러한 판단에 의존하여 적절한 대응을 프로그램에서 취할 수 있다.  

C 컴퓨터 언어에서 지원되는
a2i
a2f
같은 함수를 만들어 두었다. generic function으로 만들어 두었기 때문에 사용이 편리해졌다.

한 줄을 읽고 그 줄을 분석한다. 분석하여 원하는 패턴이 나올 경우, 원하는 숫자를 읽어 버린다. 예를 들어, 2, 4, 5, 6, 9 번째가 숫자일 때, 이들을 아래와 같이 읽어 들일 수 있다. num은 interger 이고, xnum은 real*8 이다. 물론, 이들은 블랭크로 분리되어 있다. 블랭크의 갯수는 여러개 또는 한 개가 모두 동일하게 인식된다. 따라서 실제 단어의 수가 nargs가 된다. nargs가 단어의 수가 되면, 이들 중에 진짜 단어들도 있고, 숫자들도 있을 수 있다.  숫자는 integer 또는 real*8 일 수도 있다. 이들을 각각 다르게 처리해야하지만, generic function의 특성을 이용하면 편리하게 동일한 함수를 부르고 변수형만 우리가 정확하게 집어 넣어 주면 된다. 

미리 정해진 단어들과 단어들 사이의 숫자들 읽기
tokenize a string

-----------------------------------------------------------------------------------
파이썬에서는 아래와 같이 파일에서 읽어 들인 line 또는 문자열(스트링)을 토크나이즈 할 수 있다.
str.split()
http://www.tutorialspoint.com/python/string_split.htm
-----------------------------------------------------------------------------------

       read(1,*)
       delims=' '
       read(1,'(a200)') str1
       call parse(str1,delims,args,nargs)
       call value(args(2),num,ios)
       ik1=num
       call value(args(4),xnum,ios)
       akpt(1,ik)=xnum
       call value(args(5),xnum,ios)
       akpt(2,ik)=xnum
       call value(args(6),xnum,ios)
       akpt(3,ik)=xnum
       call value(args(9),xnum,ios)
       wgt(ik)=xnum

http://rosettacode.org/wiki/Tokenize_a_string

PROGRAM Example
 
  CHARACTER(23) :: str = "Hello,How,Are,You,Today"
  CHARACTER(5) :: word(5)
  INTEGER :: pos1 = 1, pos2, n = 0, i
 
  DO
    pos2 = INDEX(str(pos1:), ",")
    IF (pos2 == 0) THEN
       n = n + 1
       word(n) = str(pos1:)
       EXIT
    END IF
    n = n + 1
    word(n) = str(pos1:pos1+pos2-2)
    pos1 = pos2+pos1
END DO
 
DO i = 1, n
   WRITE(*,"(2A)", ADVANCE="NO") TRIM(word(i)), "."
END DO
 
END PROGRAM Example

text = "Hello,How,Are,You,Today"
tokens = text.split(',')
print ('.'.join(tokens))


print ('.'.join('Hello,How,Are,You,Today'.split(',')))



-----------------------

character(len=20) function str(k)
!   "Convert an integer to string."
    integer, intent(in) :: k
    write (str, *) k
    str = adjustl(str)
end function str

program x
integer :: i
do i=1, 100
    open(11, file='Output'//trim(str(i))//'.txt')
    write (11, *) i
    close (11)
end do
end program x
http://stackoverflow.com/questions/1262695/converting-integers-to-strings-in-fortran


http://fortranwiki.org/fortran/show/strnum

! Convert numeric values to strings and vice-versa using internal file IO
program strnum
  implicit none
  character(len=25) :: str
  real :: num

  ! Convert a numeric value to a string using an internal write
  num = 3.14
  write (str, '(g12.5)') num
  print *, 'str: ', str                           ! str:   3.1400

  ! Convert a string to a numeric value using an internal read
  str = '17.2'
  read (str, '(g12.5)') num
  print *, 'num: ', num                           ! num:   17.20000
end program strnum


-------------------------------------------------------------

!234567890
       implicit none
       character*80 abc
       integer i

       abc='abcdef/'
       abc='abcdef'
       abc=adjustl(abc)
       abc=trim(abc)
       i=len_trim(abc)
       print*, i,abc
       if(abc(i:i) /= '/')  abc=trim(abc)//'/'
       abc=trim(abc)
       i=len_trim(abc)
       print*, i,abc
       stop
       end
-------------------------------------------------------------
       character(len=20) function str(k)
!      "Convert an integer to string."
       integer, intent(in) :: k
       write (str, *) k
       str = adjustl(str)
       end function str
!     And here is a test code:
       program x
       integer :: i
       character(len=20) :: str

       do i=1,100
           open(11, file='Output'//trim(str(i))//'.txt')
           write (11, *) i
           close (11)
       end do
       end program x

-------------------------------------------------------------
!234567890
!      character(len=10) :: str = '1.23e1'
!      character(len=10) :: str = ' 1.23e1'
       character(len=10) :: str = ' 1.23e1 '
       real    :: a

!      str=adjustl(str)
       read(str,*) a
       print*, a
       stop
       end

-------------------------------------------------------------

문자열(스트링) 처리가 자유로워지면 급기야 포트란 기반 쉘 스크립팅도 사실 가능하다.  셀, 펄에서나 사용가능한 것으로 생각하는 스크립팅도 할 수 있다.  여전히 문자열(스트링) 처리가 길고 복잡해 보이지만 가능하다.  
http://incredible.egloos.com/4842832

포트란 기반 시스템 콜도 유용하게 사용할 수 있다.
http://incredible.egloos.com/3474225
-------------------------------------------------------------
function io_isfigure(chr) result(check)

character, intent(in) :: chr
logical :: check
integer :: ichk

character(len=*), parameter :: figures='1234567890'

ichk = scan(figures,chr)
if (ichk == 0) then
check = .false.
else
check = .true.
end if

end function io_isfigure

!--------------------------------------------------------------------!
! Lower and upper case conversion !
!--------------------------------------------------------------------!

function io_lower(str_in) result(str_out)

implicit none

character(len=*), intent(in) :: str_in
character(len=len(str_in)) :: str_out

integer, parameter :: ilowerA = ichar('a')
integer, parameter :: iupperA = ichar('A')
integer, parameter :: iupperZ = ichar('Z')

integer :: i, ichr, nchr, iconv

iconv = ilowerA - iupperA

nchr = len(str_in)
do i = 1, nchr
ichr = ichar(str_in(i:i))
if ((ichr >= iupperA) .and. (ichr <= iupperZ)) then
str_out(i:i) = char(ichr + iconv)
else
str_out(i:i) = str_in(i:i)
end if
end do

end function io_lower

!--------------------------------------------------------------------

 2010-07-05 Alexander Urban (AU)

function io_upper(str_in) result(str_out)

implicit none

character(len=*), intent(in) :: str_in
character(len=len(str_in)) :: str_out

integer, parameter :: ilowerA = ichar('a')
integer, parameter :: ilowerZ = ichar('z')
integer, parameter :: iupperA = ichar('A')

integer :: i, ichr, nchr, iconv

iconv = iupperA - ilowerA

nchr = len(str_in)
do i = 1, nchr
ichr = ichar(str_in(i:i))
if ((ichr >= ilowerA) .and. (ichr <= ilowerZ)) then
str_out(i:i) = char(ichr + iconv)
else
str_out(i:i) = str_in(i:i)
end if
end do

end function io_upper
-------------------------------------------------------------
delimiter 를 정의하고 사용하는 방법, awk 언어에서의 사용방법
http://www.theunixschool.com/2012/07/awk-10-examples-to-read-files-with.html


포트란 문자열 분리
-------------------------------------------------------------

       islc=len(otname)
       write(6,*) islc
       islc=len_trim(otname)
       write(6,*) islc
       islc=len(trim(otname))
       write(6,*) islc


       islc=islc-6 ; cname1=otname(1:islc)
-------------------------------------------------------------
!234567890
       implicit none
       integer islc
       character*280 otname
       character*280 cname1
       character*80 string0
       logical lfault7

       otname='/home/ihlee/csa_lj/gSiC7/0001/OUTCAR'
       islc=len(otname)
       write(6,*) islc
       islc=len_trim(otname)
       write(6,*) islc
       islc=len(trim(otname))
       write(6,*) islc

       islc=islc-6 ; cname1=otname(1:islc)
!      write(6,*) trim(cname1)
       cname1=trim(cname1)//'CONTCAR'
!      write(6,*) trim(cname1)
       lfault7=.false.
       open(71,file=trim(cname1),form='formatted')
       do
       read(71,'(a80)',err=711,end=799) string0
       if(len_trim(string0) == 0) goto 700
       if(len_trim(string0) > 0) write(6,*) trim(string0)
       enddo
  711  continue
       lfault7=.true.
  799  continue
  700  continue
       close(71)

       stop
       end

위의 예제에서는 특정 디렉토리를 기준으로 
0001
0002
0003
...
0050
와 같은 형식으로 50개의 디렉토리를 만든 경우에 해당한다.
각 디렉토리마다 유사한 계산들이 동시에 수행될 수 있다.
계산의 방식은 동일하고 실제 데이터는 서로 다를 수 있다.
이러한 경우, 동일한 양식의 출력물들이 각 디렉토리마다 각기 생성될 것이다.
이들을 구별하는 것은 순전히 위에서 언급한 디렉토리 이름이다.
-------------------------------------------------------------
index
len
len_trim
repeat
scan
trim

index('hi there', 'there')     ==>  4
len('hi there')                   ==> 8
len_trim('hit there   ')         ==> 8
repeat('-',4)                     ==>  '----'
scan('hello', 'oe')            ==>  2
trim('   hi   ')                   ==> 'hi'


-------------------------------------------------------------
-------------------------------------------------------------

RosettaCode (rosettacode.org) 에서 가져온 것:
PROGRAM Example
 
  CHARACTER(23) :: str = "Hello,How,Are,You,Today"
  CHARACTER(5) :: word(5)
  INTEGER :: pos1 = 1, pos2, n = 0, i
 
  DO
    pos2 = INDEX(str(pos1:), ",")
    IF (pos2 == 0) THEN
       n = n + 1
       word(n) = str(pos1:)
       EXIT
    END IF
    n = n + 1
    word(n) = str(pos1:pos1+pos2-2)
    pos1 = pos2+pos1
END DO
 
DO i = 1, n
   WRITE(*,"(2A)", ADVANCE="NO") TRIM(word(i)), "."
END DO
 
END PROGRAM Example

-------------------------------------------------------------------------------------------------------------------

text = "Hello,How,Are,You,Today"
tokens = text.split(',')
print ('.'.join(tokens))


--------------------------------------------------------------------------------------------------------------------

       character*200 str1
       integer ios,nargs
       character*200 args(40)
       character*20 delims


       open(81,file=trim(otname),form='formatted')
       do
       read(81,'(a200)',err=921,end=909) str1
       delims=' '
       call parse(str1,delims,args,nargs)
       if(nargs == 7)then
       if(args(1) == 'energy'  )then
       if(args(2) == 'without' )then
       if(args(3) == 'entropy=')then
       call value(args(4),etot,ios)
!      print*, str1
!      print*, etot
                                endif
                                endif
                                endif
                     endif


--------------------------------------------------------------------------------------------------------------------
!234567890
!      Written by In-Ho Lee, KRISS, November 8, 2018.
       subroutine getwcc(testfile)
       USE strings, ONLY : parse,value,lowercase
       implicit none
       character*280 testfile
       integer i,j,kount
       real*8 test,vec(3)
       logical lfault9,lexist9,lswitch
       integer ios,nargs
       character*280 str1
       character*280 args(40)
       character*20 delims

       inquire(file=trim(testfile),exist=lexist9)
       if(.not. lexist9) goto 333
       lfault9=.false.
       lswitch=.false.
       open(38,file=trim(testfile),form='formatted')
       do
       read(38,'(a280)',err=311,end=499) str1
       delims=' ,'
       call parse(str1,delims,args,nargs)
       if(nargs == 2)then
       if(lowercase(args(1)) == 'final')then
       if(lowercase(args(2)) == 'state')then
       lswitch=.true.
!      write(6,*) nsw,' nsw'
                                        endif
                                        endif
                     endif
       if(lswitch)then
       if(nargs == 11)then
       if(lowercase(args(1)) == 'wf')then
       if(lowercase(args(2)) == 'centre')then
       if(lowercase(args(3)) == 'and')then
       if(lowercase(args(4)) == 'spread')then
       call value(args(7),vec(1),ios)
       call value(args(8),vec(2),ios)
       call value(args(9),vec(3),ios)
       write(6,*) vec(1),vec(2),vec(3)
                                         endif
                                      endif
                                         endif
                                     endif
                      endif
                  endif
       enddo
  311  continue
       lfault9=.true.
  499  continue
       close(38)
  333  continue
!      write(6,*) nsw,kount
       end
!234567890
       program test
       implicit none
       character*280 testfile

       testfile='test'
       call getwcc(testfile)
       end program test

--------------------------------------------------------------------------------------------------------------------
character(len=20) function str(k)
!   "Convert an integer to string."
    integer, intent(in) :: k
    write (str, *) k
    str = adjustl(str)
end function str
--------------------------------------------------------------------------------------------------------------------



핑백

덧글

  • 부경대학교_방재기상 2013/12/16 11:07 # 삭제 답글

    다 읽어 보지는 못했지만자료가 123#123#123#123 을 되어있을때
    character(len=1) :: e(100)
    read(1,*) (e(i),i=1,100)
    if( e(i) .ne. #) then
    print*, e(i)
    endif
    이렇게 읽었는데 되더라구요 차이가 있을까요?
  • 바죠 2013/12/16 11:14 #

    읽어졌다면 차이가 없는 것이죠.
    좀 더 편리하게 사용하지는 것이 목표입니다. 매우 다양한 방법, 즉, 여러 가지 방법으로 데이터를 읽을 수 있습니다.


댓글 입력 영역

최근 포토로그



MathJax