【问题标题】:How to read a number into fortran with thousand-separator point and decimal-separator comma e.g. 123.456.891.234,56如何使用千位分隔符和小数分隔符逗号将数字读入 fortran,例如123.456.891.234,56
【发布时间】:2019-02-25 16:21:25
【问题描述】:

我有一个用分号 (;) 分隔的巨大文本文件。号码 具有千位分隔符和小数分隔符的格式 昏迷123.456.891.234,56

我不想用编辑器搜索和替换文件中的点,因为我没有更改文件的权限。

我可以把它读成字符串,然后试着去掉这些点。 但这似乎不是解决问题的好方法。

program prjRead
  implicit none

  integer:: a
  real(8) :: b
  real(8) :: c
  character(10) :: dummy

  open (123,file = "test.csv", DECIMAL='COMMA')

  read(123,*) dummy
  read(123,*) a, &
              b, &
              c

  write(*,*) a,b,c

end program prjRead

test.csv 的内容

integer;decimalcomma;thousandsep
5;56,67;123.456,78

在文件 prjRead.f90 的第 36 行(单位 = 123,文件 = 'test.csv') Fortran 运行时错误:列表输入第 3 项中的实数错误

【问题讨论】:

  • @francescalus 不是在开头语句中,而是在我调试读取文件的第一行时。
  • 在这种情况下,您还应该显示给出错误的读取语句。
  • 我个人会使用 sed 在阅读文件之前对其进行转换。
  • 因为我没有更改文件的权限。。好吧,即使您确实拥有这样的权限,您也不会这样做,对吗?您可以使用sed 将输入文件转换为具有正确特征的输出文件。
  • 那么,您不能将文件复制到“tmp.dat”吗? sed s/,/#/g a.dat | sed s/"\."/,/g | sed s/#/"\."/g > tmp.dat 这会将您的 5;56,67;123.456,78 转换为 5;56.67;123,456.78

标签: fortran fortran90


【解决方案1】:

如果您真的想在 Fortran 中执行此操作,这并不太难。首先,让我们有一个函数从字符串中删除所有出现的.

  FUNCTION drop_stops(instr) RESULT(outstr)
    CHARACTER(len=*), INTENT(in) :: instr
    CHARACTER(len=:), ALLOCATABLE :: outstr
    CHARACTER(len=1), DIMENSION(:), ALLOCATABLE :: str_array

    ALLOCATE(str_array(LEN_TRIM(instr)))
    str_array = TRANSFER(instr,str_array)
    str_array = PACK(str_array,str_array /= '.')
    ALLOCATE(CHARACTER(len=SIZE(str_array))::outstr)
    outstr = TRANSFER(str_array,outstr)
  END FUNCTION drop_stops

我相信这是显而易见的,除了您不熟悉的任何函数或语句的文档之外,不需要任何解释。

然后,坚持使用您的原始代码并声明另一个您可以读取的字符串变量dummy,就像您已经做的那样,然后编写类似

 dummy_without_stops = drop_stops(dummy)

现在您可以对其进行内部阅读以获取您感兴趣的数字,例如

read(dummy_without_stops,*,decimal='comma') a, b, c

请注意,drop_stops 中实现的方法取决于字符串中的字符按顺序排列在内存中,并与数组中字符的相同存储匹配。我相信这适用于 ASCII 字符,但对 ISO_10646 字符不太确定。

【讨论】:

  • 它就像一个魅力。一位同事向我展示了如何阅读整行。下周我会添加代码。
【解决方案2】:

您没有从输入文件中提供足够大的样本。但本质上,您需要先通过分隔符; 拆分文件内容。然后对于您获得的每个字符串编号,将所有千位分隔符. 替换为“”(无)。然后,将十进制符号 , 替换为普通十进制符号 .。这是通过下面的splitStr()replaceStr() 类型绑定过程来实现此目的的尝试,在您提供的示例文件内容行上进行了测试5;56,67;123.456,78

module String_mod

    use, intrinsic :: iso_fortran_env, only: IK=>int32, RK=>real64
    implicit none

    public

    character(*), parameter :: MODULE_NAME = "@String_mod"

    type :: CharVec_type
        character (:), allocatable  :: record
    end type CharVec_type

    type :: String_type
        character(:)      , allocatable   :: value
        type(CharVec_type), allocatable   :: Parts(:)
        integer(IK)                       :: nPart = 0
    contains
        procedure, nopass :: replaceStr, splitStr, str2num
    end type String_type

!***********************************************************************************************************************************
!***********************************************************************************************************************************

contains

!***********************************************************************************************************************************
!***********************************************************************************************************************************

    recursive function replaceStr(string,search,substitute) result(modifiedString)
        implicit none
        character(len=*), intent(in)  :: string, search, substitute
        character(len=:), allocatable :: modifiedString
        integer(IK)                   :: i, stringLen, searchLen
        stringLen = len(string)
        searchLen = len(search)
        if (stringLen==0 .or. searchLen==0) then
            modifiedString = ""
            return
        elseif (stringLen<searchLen) then
            modifiedString = string
            return
        end if
        i = 1
        do
            if (string(i:i+searchLen-1)==search) then
                modifiedString = string(1:i-1) // substitute // replaceStr(string(i+searchLen:stringLen),search,substitute)
                exit
            end if
            if (i+searchLen>stringLen) then
                modifiedString = string
                exit
            end if
            i = i + 1
            cycle
        end do
    end function replaceStr

!***********************************************************************************************************************************
!***********************************************************************************************************************************

    function splitStr(string,delimiter)

        implicit none
        character(len=*)  , intent(in)  :: string,delimiter
        character(len=:)  , allocatable :: dummyStr
        type(CharVec_type), allocatable :: splitStr(:)
        integer(IK)                     :: maxNumSplit
        integer(IK)                     :: stringLen, delimLen, splitCounter, currentPos

        dummyStr  = string
        delimLen  = len(delimiter)
        stringLen = len(dummyStr)

        if (delimLen==0) then
            allocate(splitStr(1))
            splitStr(1)%record = string
            return
        end if

        maxNumSplit = 1 + stringLen / delimLen
        allocate(splitStr(maxNumSplit))
        splitCounter = 1
        loopParseString: do
            if (stringLen<delimLen) then
                splitStr(splitCounter)%record = dummyStr
                exit loopParseString
            elseif (stringLen==delimLen) then
                if (dummyStr==delimiter) then
                    splitStr(splitCounter)%record = ""
                end if
                exit loopParseString
            elseif (dummyStr(1:delimLen)==delimiter) then
                dummyStr = dummyStr(delimLen+1:stringLen)
                stringLen = len(dummyStr)
                cycle loopParseString
            else
                currentPos = 2
                loopSearchString: do
                    if (dummyStr(currentPos:currentPos+delimLen-1)==delimiter) then
                        splitStr(splitCounter)%record = dummyStr(1:currentPos-1)
                        if (currentPos+delimLen>stringLen) then
                            exit loopParseString
                        else
                            splitCounter = splitCounter + 1
                            dummyStr = dummyStr(currentPos+delimLen:stringLen)
                            stringLen = len(dummyStr)
                            cycle loopParseString
                        end if
                    else
                        currentPos = currentPos + 1
                        if (stringLen<currentPos+delimLen-1) then
                            splitStr(splitCounter)%record = dummyStr
                            exit loopParseString
                        end if
                        cycle loopSearchString
                    end if
                end do loopSearchString
            end if
        end do loopParseString
        splitStr = splitStr(1:splitCounter)

    end function splitStr

!***********************************************************************************************************************************
!***********************************************************************************************************************************

    pure elemental function str2num(str)
        implicit none
        character(len=*), intent(in) :: str
        real(RK)                  :: str2num
        read(str,*) str2num
    end function str2num

!***********************************************************************************************************************************
!***********************************************************************************************************************************

end module String_mod

program readFile_prog
    use String_mod, only: String_type
    implicit none
    ! Rules: comma means decimal point. Dot means thousands separator. delimiter is ;.
    character(*), parameter :: FileToRead = "5;56,67;123.456,78"
    type(String_type)       :: String
    integer                 :: i

    ! read file
    String%value = FileToRead

    ! split file contents into individual numbers 
    String%Parts = String%splitStr(String%value,";")

    ! count the number of integers in the file
    String%nPart = size(String%Parts)

    do i = 1, String%nPart

        ! For each number, remove the thousands separator, by replacing "." with ""
        String%Parts(i)%record = String%replaceStr(String%Parts(i)%record,".","")

        ! now replace comma decimal symbols with regular . decimal notation
        String%Parts(i)%record = String%replaceStr(String%Parts(i)%record,",",".")

        ! Covert the integer number from character type to integer and print it on screen
        write(*,"(*(g0,:,' '))") "Number(",i,") = ", String%str2num(String%Parts(i)%record)

    end do

end program readFile_prog

这是输出:

$gfortran -std=f2008 *.f95 -o main
$main
Number( 1 ) =  5.0000000000000000
Number( 2 ) =  56.670000000000002
Number( 3 ) =  123456.78000000000

【讨论】:

    【解决方案3】:
    program prjRead
      implicit none
    
      integer:: a
      real(8) :: b
      real(8) :: c
      character(18) :: header
      character(18) :: cAsString
      character(18) :: cWithOutStops
    
      open (123,file = "test.csv", DECIMAL='COMMA')
    
      read(123,*) header
      read(123,*) a, &
                  b, &
                  cAsString
    
      cWithOutStops = drop_stops(cAsString)
    
      read(cWithOutStops,*,decimal='comma')  c
    
      write(*,*) "gives c without decimal places"
      write(*,*) a,b,c
    
      write(*,*) "********************"
      write(*,*) "second try, the function drop_stops does what it 
    promisses. If have to feed it with the right string"
      cAsString ="123.456,78"
      cWithOutStops = drop_stops(cAsString)
      read(cWithOutStops,*,decimal='comma')  c
    
      write(*,*) a,b,c
    
    contains
    
    FUNCTION drop_stops(instr) RESULT(outstr)
        CHARACTER(len=*), INTENT(in) :: instr
        CHARACTER(len=:), ALLOCATABLE :: outstr
        CHARACTER(len=1), DIMENSION(:), ALLOCATABLE :: str_array
    
        ALLOCATE(str_array(LEN_TRIM(instr)))
        str_array = TRANSFER(instr,str_array)
        str_array = PACK(str_array,str_array /= '.')
        ALLOCATE(CHARACTER(len=SIZE(str_array))::outstr)
        outstr = TRANSFER(str_array,outstr)
      END FUNCTION drop_stops
    end program prjRead
    

    输出

    给出不带小数位的 c 5   56.670000000000002        123456.00000000000 ******************** 第二次尝试,函数 drop_stops 做了什么 它承诺。如果必须用正确的字符串喂它 5   56.670000000000002        123456.78000000000

    HighPerformanceMarks 解决方案即将问世。我只是缺乏在阅读中将数字 c 吞下的技巧。或者我读取 2 个字符串并将它们连接起来。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-27
      • 1970-01-01
      • 2013-11-07
      • 1970-01-01
      • 2010-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多