如何从外部 .txt 文件读取数据,将它们存储在数组中,过滤并将它们写入 Fortran 90 中的新文件?

How to read data from external .txt file, store them in arrays, filter and write them in a new file in Fortran 90?

我在该网站上阅读过类似的已解决问题,但它们确实对我有帮助!所以,很抱歉提出类似的问题。

我有以下名为“Asteroids_Numbered.txt”的 .txt 文件(该文件有很多行,即 607013,但为了简单起见,我少放了很多行):

 Num   Name              Epoch      a          e        i         w        Node        M         H     G   Ref
------ ----------------- ----- ---------- ---------- --------- --------- --------- ----------- ----- ----- ----------
     1 Ceres             59600  2.7660431 0.07850100  10.58769  73.63704  80.26860 291.3755993  3.54  0.12 JPL 48
     2 Pallas            59600  2.7711069 0.22999297  34.92530 310.69725 172.91657 272.4799259  4.22  0.11 JPL 49
     3 Juno              59600  2.6687911 0.25688702  12.99186 247.94173 169.84780 261.2986327  5.28  0.32 JPL 123
     4 Vesta             59600  2.3612665 0.08823418   7.14172 151.09094 103.80392   7.0315225  3.40  0.32 JPL 36
     5 Astraea           59600  2.5751766 0.19009936   5.36762 358.74039 141.57036 160.9820880  6.99  0.15 JPL 125
     6 Hebe              59600  2.4256657 0.20306151  14.73873 239.50547 138.64097 347.4991368  5.65  0.24 JPL 100
     7 Iris              59600  2.3866161 0.22949924   5.51768 145.34355 259.52553  47.6423152  5.61  0.15 JPL 119
     8 Flora             59600  2.2017319 0.15606719   5.88872 285.55022 110.87251 136.2585358  6.54  0.28 JPL 127
     9 Metis             59600  2.3852921 0.12356142   5.57695   6.16423  68.89958 184.5626181  6.37  0.17 JPL 128
    10 Hygiea            59600  3.1418676 0.11162598   3.83093 312.49331 283.18419 328.8968591  5.55  0.15 JPL 105
    11 Parthenope        59600  2.4532814 0.09954681   4.63165 195.59824 125.52829 175.4211548  6.60  0.15 JPL 118
    12 Victoria          59600  2.3337809 0.22074254   8.37333  69.66955 235.36878  49.7506630  7.31  0.22 JPL 131
    13 Egeria            59600  2.5765835 0.08544364  16.53450  80.14708  43.20673  66.2442983  6.84  0.15 JPL 103
    14 Irene             59600  2.5859176 0.16587880   9.12082  97.71349  86.11601  42.0351479  6.53  0.15 JPL 96
    15 Eunomia           59600  2.6440754 0.18662534  11.75200  98.63169 292.92610 152.5002319  5.41  0.23 JPL 85
    16 Psyche            59600  2.9244847 0.13392662   3.09684 229.21980 150.03218 125.1275316  6.06  0.20 JPL 90
    17 Thetis            59600  2.4706187 0.13286003   5.59276 135.80674 125.54282 197.5734224  7.76  0.15 JPL 125
    18 Melpomene         59600  2.2957889 0.21790920  10.13249 228.11923 150.36173 190.3739342  6.53  0.25 JPL 116
    19 Fortuna           59600  2.4429040 0.15701789   1.57276 182.47214 211.04422  95.0887535  7.38  0.10 JPL 142
    20 Massalia          59600  2.4088126 0.14306413   0.70880 257.55922 205.97388  20.5136762  6.56  0.25 JPL 118
    21 Lutetia           59600  2.4351916 0.16354177   3.06364 250.15544  80.85386 243.3813245  7.52  0.11 JPL 118
    22 Kalliope          59600  2.9102024 0.09838131  13.70049 357.60063  65.99349  33.4836574  6.51  0.21 JPL 111

我如何创建一个程序来读取这个文件,将数据存储在一维数组中(每种数据一个,所以我想得到 12 个数组)然后根据一些条件过滤它们,例如倾角值 (i) 小于 2deg? 最后,如何将过滤后的数据存储到与原始文件格式相同的新文件中?

这是我的代码(只包含阅读部分):

program Read_write_ephemerides_Main

    implicit none 
    
    !Declarations
    character*100 :: input_path,input_filename, output_path, output_filename
    double precision, dimension(:,:), allocatable :: Epoch_TDB, a_AU, e, i_deg, w_deg, Node_deg, M_deg, H_mag, G
    character*30, dimension(4) :: str_output
    character, dimension (:,:), allocatable :: Name, Ref
    integer :: i,iu, i_counter
    integer, dimension (:,:), allocatable :: Number
    logical :: bContinue

    ! Definition of constants, paths names and file names
    iu = 10
    input_path = 'D:\MSc_Aerospace_Eng\Thesis\Fortran_projects\Read_write_ephemerides\InputFiles\'
    input_filename = 'Asteroids_Numbered.txt'
    !output_path = 'D:\MSc_Aerospace_Eng\Thesis\Fortran_projects\Read_write_ephemerides\OutputFiles\'
    !output_filename = 'prova_ast_num.txt'
    
    ! Reading of Asteroids_numbered file
    open(unit = iu, file = trim(input_path) // trim(input_filename), status='old', & 
        access = 'sequential',form = 'formatted', action='read')
    read(iu,'(//)')     ! skip first 2 lines
    read(iu,'(i10,a25,f10.0,6(f12.8),2(f5.4),f5.4)')  Number, Name, Epoch_TDB, a_AU, e, i_deg, w_deg, Node_deg, M_deg, H_mag, G, Ref
    close(unit = iu,status='keep')
     
    ! Creation of output file
    !open(unit = iu, file = trim(output_path) // trim(output_filename1), status = 'unknown', action = 'write')
    !write(iu,'(i10,a25,f10.0,6(f12.8),2(f5.4),f5.4)')  Number, Name, Epoch_TDB, a_AU, e, i_deg, w_deg, Node_deg, M_deg, H_mag, G, Ref
    !close(unit = iu,status='keep')
    !
    
    stop
    
end program Read_write_ephemerides_Main
    

编辑:代码已更新 有用的说明:我在 Microsoft Visual Studio 2022

中使用 Intel Fortran 编译器

要扩展@HighPerformanceMark 的评论,最好的办法是定义一个 Asteroid 类型,它包含有关小行星的所有信息,然后创建一个 Asteroid 数组.

Asteroid类型

Asteroid 类型最初应该只包含有关小行星的数据,

type :: Asteroid
  integer :: num
  character(:), allocatable :: name
  integer :: epoch
  real(dp) :: a
  real(dp) :: e
  real(dp) :: i
  real(dp) :: w
  real(dp) :: node
  real(dp) :: m
  real(dp) :: h
  real(dp) :: g
  character(:), allocatable :: ref_name
  integer :: ref_number
end type

其中 dp defines double precision.

这允许您拥有一个 Asteroid 数组,例如

type(Asteroid) :: asteroids(22)

asteroids(1) = Asteroid(1, "Ceres", ..., "JPL", 48)
...
asteroids(22) = Asteroid(22, "Kalliope", ..., "JPL", 111)

write(*,*) asteroids(1)%name ! Writes "Ceres".

读写Asteroids

您希望能够 readwrite 小行星往返于文件,您可以使用 user defined input/output 来实现。为此,您需要一个 readAsteroid 的子程序,例如

subroutine read_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
  class(Asteroid), intent(inout) :: this
  integer, intent(in) :: unit
  character(*), intent(in) :: iotype
  integer, intent(in) :: v_list(:)
  integer, intent(out) :: iostat
  character(*), intent(inout) :: iomsg
  
  character(100) :: name
  character(100) :: ref_name
  
  read(unit, *, iostat=iostat, iomsg=iomsg) &
     & this%num, &
     & name, &
     & this%epoch, &
     & this%a, &
     & this%e, &
     & this%i, &
     & this%w, &
     & this%node, &
     & this%m, &
     & this%h, &
     & this%g, &
     & ref_name, &
     & this%ref_number
  
  this%name = trim(name)
  this%ref_name = trim(ref_name)
end subroutine

和另一个 writeAsteroid,例如

subroutine write_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
  class(Asteroid), intent(in) :: this
  integer, intent(in) :: unit
  character(*), intent(in) :: iotype
  integer, intent(in) :: v_list(:)
  integer, intent(out) :: iostat
  character(*), intent(inout) :: iomsg
  
  write(unit, *, iostat=iostat, iomsg=iomsg) &
     & this%num, &
     & this%name, &
     & this%epoch, &
     & this%a, &
     & this%e, &
     & this%i, &
     & this%w, &
     & this%node, &
     & this%m, &
     & this%h, &
     & this%g, &
     & this%ref_name, &
     & this%ref_number
end subroutine

您还需要将 bindings 添加到 Asteroid 类型,以便它知道使用 read_Asteroidwrite_Asteroid 进行读写。这看起来像

type :: Asteroid
  integer :: num
  ...
  integer :: ref_number
contains
  ! `read` binding.
  generic :: read(formatted) => read_Asteroid
  procedure :: read_Asteroid
  
  ! `write` binding.
  generic :: write(formatted) => write_Asteroid
  procedure :: write_Asteroid
end type

N.B。因为 Asteroid 类型有 allocatable 个组件(nameref_name),这些组件不是由 read 语句分配的,所以在写 read_Asteroid. 可以用来读取allocatables;首先读取到超大缓冲区,然后将数据复制到 allocatable 变量。 (感谢@francescalus 在这里指出我的代码以前的问题)。

现在可以直接 readwrite 小行星,例如

character(1000) :: line
type(Asteroid) :: Ceres

line = "1 Ceres 59600 2.766 0.07850 10.58 73.63 80.26 291.3 3.54 0.12 JPL 48"
read(line, *) Ceres
write(*, *) Ceres

示例代码

综上所述,这是一个示例代码,它读取一个充满小行星的文件,然后用 i < 2:

写入这些文件
module asteroid_module
  implicit none
  
  ! Define `dp`, which defines double precision.
  integer, parameter :: dp = selected_real_kind(15, 307)
  
  ! Define the `Asteroid` type.
  type :: Asteroid
    integer :: num
    character(:), allocatable :: name
    integer :: epoch
    real(dp) :: a
    real(dp) :: e
    real(dp) :: i
    real(dp) :: w
    real(dp) :: node
    real(dp) :: m
    real(dp) :: h
    real(dp) :: g
    character(:), allocatable :: ref_name
    integer :: ref_number
  contains
    ! `read` binding.
    generic :: read(formatted) => read_Asteroid
    procedure :: read_Asteroid
    
    ! `write` binding.
    generic :: write(formatted) => write_Asteroid
    procedure :: write_Asteroid
  end type
contains

! Define how to `read` an `Asteroid`.
subroutine read_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
  class(Asteroid), intent(inout) :: this
  integer, intent(in) :: unit
  character(*), intent(in) :: iotype
  integer, intent(in) :: v_list(:)
  integer, intent(out) :: iostat
  character(*), intent(inout) :: iomsg
  
  character(100) :: name
  character(100) :: ref_name
  
  read(unit, *, iostat=iostat, iomsg=iomsg) &
     & this%num, &
     & name, &
     & this%epoch, &
     & this%a, &
     & this%e, &
     & this%i, &
     & this%w, &
     & this%node, &
     & this%m, &
     & this%h, &
     & this%g, &
     & ref_name, &
     & this%ref_number
  
  this%name = trim(name)
  this%ref_name = trim(ref_name)
end subroutine

! Define how to `write` an `Asteroid`.
subroutine write_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
  class(Asteroid), intent(in) :: this
  integer, intent(in) :: unit
  character(*), intent(in) :: iotype
  integer, intent(in) :: v_list(:)
  integer, intent(out) :: iostat
  character(*), intent(inout) :: iomsg
  
  write(unit, *, iostat=iostat, iomsg=iomsg) &
     & this%num, &
     & this%name, &
     & this%epoch, &
     & this%a, &
     & this%e, &
     & this%i, &
     & this%w, &
     & this%node, &
     & this%m, &
     & this%h, &
     & this%g, &
     & this%ref_name, &
     & this%ref_number
end subroutine
end module

program example
  use asteroid_module
  implicit none
  
  character(1000) :: line
  integer :: iostat
  integer :: file_length
  type(Asteroid), allocatable :: asteroids(:)
  integer :: i
  
  ! Count the number of lines in the file.
  file_length = 0
  open(10, file="input.txt")
  do
    read(10, '(A)',iostat=iostat) line
    if (iostat/=0) then
      exit
    endif
    file_length = file_length + 1
  enddo
  close(10)
  
  ! Allocate the array to hold the asteroids.
  allocate(asteroids(file_length-2))
  
  ! Read the asteroids into the array.
  open(10, file="input.txt")
  read(10, '(A)') line
  read(10, '(A)') line
  do i=1,size(asteroids)
    read(10, '(A)') line
    read(line, *) asteroids(i)
  enddo
  close(10)
  
  ! Write the asteroids with `i` < 2 to a file.
  open(10, file="output.txt")
  do i=1,size(asteroids)
    if (asteroids(i)%i < 2.0_dp) then
      write(10,*) asteroids(i)
    endif
  enddo
  close(10)
end program

让我们在继续下一部分之前解决一件事:如果这只是一个“过滤”任务,请将其视为过滤任务。

在 Fortran 2018 中,这可能很简单

  implicit none
  character(1234) line
  integer iostat, nchars

  do
    read (*,'(A)',iostat=iostat,size=nchars) line
    if (iostat.lt.0) exit
    if (KEEP_LINE) print *, line(:nchars)    ! Implement conditional
  end do
end program

(如果您的编译器不是 Fortran 2018 编译器,您需要将其复杂化。)该程序在 Unix-sense:

中充当过滤器
./program < input_file > output_file

对于这个问题,过滤器类似于“传递前两行;传递第六列数字小于 2 的后面的行”。我将把确切的规范留作练习,注意我们可以用

完成这项工作
awk 'NR<3||<2' < input_file > output_file

请注意,您可以简单地 extract the sixth column 而无需为每一列创建变量 - 或者您可以注意到它是 line(52:) 的第一列。


这就是过滤掉的方式。让我们看看如何创建数据结构并在 Fortran 程序中使用它做一些事情。

正如 High Performance Mark 评论的那样,非常梦幻 expanded on 我们可以为这个“数据 table” 创建一个派生类型(如果所有列都是相同的数据类型,我们可能只需要2 级内在类型,尽管即使在这种情况下派生类型也有帮助):

  type asteroids_t
     integer :: num
     character(18) :: name
     integer :: epoch
     real :: a, e, i, w, node, m, h, g
     character(10) :: ref
  end type asteroids_t

(根据需要设置每个组件的种类参数,但实数可能是双精度)

我们有一个输入和输出格式:

  character(*), parameter :: FMT='(i6,a,i7,f10.7,f11.8,3f10.5,f12.7,2f6.2,a)'

(请注意,我们不能对输入使用 list-directed 格式,因为最后一列的字符中有一个 space。同样,解决这个问题是一个练习。)

假设我们有一个适当大小的数组(请参阅有关读取行数未知的文件的一般问题,或此处的 veryreverie 的回答以获取详细信息)我们就可以开始了。为了清楚起见,我将使用明确的大小。

  type(asteroids_t) asteroids(NUMBER_OF_ASTEROIDS)
  integer, allocatable :: pass(:)
  read FMT, asteroids
  ... ! Work, including setting pass for filter
  print FMT, asteroids(pass)

将所有这些放在一起形成一个 quick-and-dirty 程序:

  implicit none

  type asteroids_t
     integer :: num
     character(18) :: name
     integer :: epoch
     real(KIND(0d0)) :: a, e, i, w, node, m, h, g
     character(10) :: ref
  end type asteroids_t

  type(asteroids_t) :: asteroids(22)
  character(118) :: header(2)
  character(*), parameter :: FMT='(i6,a,i7,f10.7,f11.8,3f10.5,f12.7,2f6.2,a)'
  integer :: i
  
  read '(A)', header
  print '(A)', header

  read FMT, asteroids
  print FMT, asteroids(PACK([(i,i=1,SIZE(asteroids))], asteroids%i<2))
end program

需要注意的关键点是我们可以用“正常”处理我们的派生类型input/output:项目asteroids被扩展为数组元素,然后每个数组元素被扩展为组件。因为我们的派生类型没有私有、指针或 allocatable 组件,所以我们可以使用这种简单的处理形式。


作为更高级的 material,请注意此处示例中的“幻数”。我们已经知道如何删除神奇的小行星计数 (22) 以及 header 行(2 和 118)的神奇数字和长度。但也许我们担心那些字符组件(18 和 10)的长度。

我们的数据结构与输入数据文件的形式紧密耦合,但是如果我们有两个名称长度不同的数据集怎么办?重写或复制我们的派生类型来处理这个问题确实很痛苦。让我们来解决这个问题:

  type asteroids_t(len_name, len_ref)
     integer, len :: len_name=18, len_ref=10
     integer :: num
     character(len_name) :: name
     integer :: epoch
     real(KIND(0d0)) :: a, e, i, w, node, m, h, g
     character(len_ref) :: ref
  end type asteroids_t

  type(asteroids_t) asteroids_set_1(22)
  type(asteroids(25,8)) asteroids_set_2(22)

! There's no magic character length in FMT
  read FMT, asteroids_set_1
  read FMT, asteroids_set_2

列宽甚至可以推迟到 run-time 处解决(未显示)。您可以在其他地方更详细地阅读这些 参数化 派生类型。