【问题标题】:Best way to parse a string, based on delimiters?基于分隔符解析字符串的最佳方法?
【发布时间】:2016-12-02 16:30:31
【问题描述】:

如果我们有一个a="2016:03:30:00:00,2,5,10,,,,," 形式的字符串,那么指定和提取第 n 个元素的最佳方法是什么,由 *n-1*th 逗号分隔?例如。第二个元素在第一个逗号之后。

现在我正在从一个巨大的 CSV 文件中提取数据,以逗号分隔,逐行放入一个字符串数组中。数组的每一行都将具有上述形式(在第 n 列中可能有也可能没有值),并且每一行都有相同数量的分隔符。

我正在尝试仅对特定列进行处理,以便对它们进行平均等,但找不到隔离第 n 列的方法。 SCANINDEX 在有问题的字符多次出现时似乎没有帮助。

或者,是否有某种方法可以读取文件并仅将第 n 列分配给我的字符串数组?如果不将整个 CSV 行放入一个数组元素中,我找不到一种方法,所以现在我希望解析每一行中的字符串将是下一个最好的事情。但是,如果我可以将其读入数组,然后解析为多个列,那将是理想的。

旁白:Fortran 语言是否适合此类任务?如果需要,我可以在 C 甚至丑陋的 bash 脚本中备份并执行此操作,但尝试与其他并行运行的 Fortran 应用程序一起进行。

【问题讨论】:

    标签: arrays string file parsing fortran


    【解决方案1】:

    OP 写道 我们有一个格式为 a="2016:03:30:00:00,2,5,10,,,,," 的字符串,所以让我们开始吧,程序已将文件中的一行读入名为a 的字符变量中。似乎该行以日期/时间开头,然后具有固定数量的整数元素,其中一些可能不存在。给定一个声明,例如

    character(len=128) :: elements
    

    我们可以通过执行简单地去掉a的前17个字符(即日期和第一个逗号)

    elements = a(18:)
    

    a 的内容分配给字符变量elements,并去掉日期。所以在前面的语句elements 之后应该是这样的

    "2,5,10,,,,,"
    

    我们现在可以使用 Fortran 的列表定向输入来读取 elements 中的 7 个整数,语句如下:

    read(elements,*) nums(1:7)
    

    现在可以使用nums 做任何事情,例如只保留第 4 个元素并回收其他元素。

    这不是一个完整的答案,但我希望它能让 OP 足以弄清楚其余部分。如果不是,请澄清问题。

    【讨论】:

    • 这个想法奏效了,谢谢!但是“elements = a(18:)”这行是怎么回事?在实践中,18 需要比要剪切的字符数大一个,在示例中,包括第一个昏迷,它是 17。我认为 Fortran 索引从 1 开始,这意味着 17,但测试这个节目是你在 n 处的权利+1。
    • 18是我们感兴趣的行中第一个字符的位置;日期和第一个逗号占据前 17 个字符。所以子字符串a(18:) 是该行的其余部分,从第 18 个字符到结尾。
    【解决方案2】:

    以下代码与 HighPerformanceMark 的答案基本相同(即,对逗号分隔的值使用列表导向的输入),但是当一行以逗号结尾时(例如,行下面的 3 和 4)。因此,我在每一行手动添加了一个逗号来处理这种情况:

    program main
        implicit none
        integer, parameter :: nrow = 4, ncol = 9
        character(100) :: csvinp( nrow ), time
        integer :: dat( nrow, ncol ), irow, icol
    
        csvinp( 1 ) = "2016:03:30:00:00,2,5,10,1,2,34,5,3,2"
        csvinp( 2 ) = "2017:03:40:00:00,1,2,,4,,,,,9"
        csvinp( 3 ) = "2018:03:50:00:00,,2,3,,,,7,,"
        csvinp( 4 ) = "2019:03:60:00:00,,,,,,,,,"
    
        do irow = 1, nrow
            csvinp( irow ) = trim(csvinp( irow )) // ","   !! add one more comma
        enddo
    
        dat(:,:) = 0        !! (#)
        do irow = 1, nrow
            read( csvinp( irow ), * ) time, dat( irow, : )
    
            print *, "irow:", irow
            print *, "  time    = ", trim( time )
            print *, "  columns = ", dat( irow, : )
        enddo
    
        print *
        print *, "average of each column:"
        do icol = 1, ncol
            print *, "icol=", icol, "ave=", sum( dat( :, icol ) ) / real(nrow)
        enddo
    end
    

    结果:

     irow: 1
       time    = 2016:03:30:00:00
       columns =  2 5 10 1 2 34 5 3 2
     irow: 2
       time    = 2017:03:40:00:00
       columns =  1 2 0 4 0 0 0 0 9
     irow: 3
       time    = 2018:03:50:00:00
       columns =  0 2 3 0 0 0 7 0 0
     irow: 4
       time    = 2019:03:60:00:00
       columns =  0 0 0 0 0 0 0 0 0
    
     average of each column:
     icol= 1 ave= 0.75
     icol= 2 ave= 2.25
     icol= 3 ave= 3.25
     icol= 4 ave= 1.25
     icol= 5 ave= 0.5
     icol= 6 ave= 8.5
     icol= 7 ave= 3.0
     icol= 8 ave= 0.75
     icol= 9 ave= 2.75
    

    在这里,最初用一些所需值(例如 0)填充dat 似乎更好,因为如果字符串有空白列,dat 的相应元素不会被修改。比如我们把上面代码中的Line(#)改成dat = -100,我们得到

     irow: 1
       time    = 2016:03:30:00:00
       columns =  2 5 10 1 2 34 5 3 2
     irow: 2
       time    = 2017:03:40:00:00
       columns =  1 2 -100 4 -100 -100 -100 -100 9
     irow: 3
       time    = 2018:03:50:00:00
       columns =  -100 2 3 -100 -100 -100 7 -100 -100
     irow: 4
       time    = 2019:03:60:00:00
       columns =  -100 -100 -100 -100 -100 -100 -100 -100 -100
    
     average of each column:
     icol= 1 ave= -49.25
     icol= 2 ave= -22.75
     icol= 3 ave= -46.75
     icol= 4 ave= -48.75
     icol= 5 ave= -74.5
     icol= 6 ave= -66.5
     icol= 7 ave= -47.0
     icol= 8 ave= -74.25
     icol= 9 ave= -47.25
    

    虽然我不确定这种行为是否符合标准,但在 gfortran-6、ifort-16 和 Oracle fortran 12.5 中是相同的。 (实际上,我希望编译器在有空白列时填充 0,但事实并非如此。)

    【讨论】:

    • 为了避免文件结束错误(我也遇到过),我一直在使用诸如“read(unit,*,END=40)”之类的读取调用,它会跳出到标签40 并继续。我将不得不研究“read(csvinp(irow), *) time, dat(irow, : )”这一行,因为这也是我一直试图掌握的——但它看起来很有效。谢谢!
    • 如果我进行了更彻底的测试,我可能会在read 语句中使用iostat 参数来处理以逗号结尾的行出现的问题。这将处理一系列格式错误的行。
    • 如果您将 ':' 更改为 ',' 那么您可以按照@HighPerformanceMark 提到的方式读取数据,并且仍然可以读取 yyyy、mm、dd 等。
    猜你喜欢
    • 2017-02-03
    • 2012-12-03
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 2020-01-25
    相关资源
    最近更新 更多