在写入文件时,在数组的子集中间获取换行符



我有一个连接多个文件的Fortran例程。给定的输入文件:

f1.txt

1  1 2 3
2  1 2 3
3  1 2 3

f2.txt

1  4 5 6 7
2  4 5 6 7
3  4 5 6 7

输出

1  1 2 3 4 5 6 7
2  1 2 3 4 5 6 7
3  1 2 3 4 5 6 7

没什么大不了的,很简单。如果接受任意数量的文件,行将它们连接起来,同时忽略第一列。每个文件的列数可能不同,但行数可能不同。列包含数值,而不仅仅是整数。

问题

出于某种原因,当列的数量不同时,新行会在确定的位置偷偷进入。我可以确定的是,当"切换"到列数较少的文件时会发生这种情况。这不应该发生(换行),但确实发生了。为什么?

PROGRAM TEST
IMPLICIT NONE
character(255), dimension(2) :: fns
integer :: files, nlines, result
character(255) :: fnout
integer, dimension(2) :: ncols
! subroutine rowconcat(files, fns, fnout, nlines, ncols, result)
files=2
fns(1) = 'f2.txt'
fns(2) = 'f1.txt'
fnout = 'fres.txt'
nlines=3
ncols(1) = 2
ncols(2) = 4

CALL rowconcat(files, fns, fnout, nlines, ncols, result)  ! In rowconcat.f95
END
! Concatenates genotype matrices (from e.g. multiple chromosomes) into one.
! No row ID checking.
subroutine rowconcat(files, fns, fnout, nlines, ncols, result)
  implicit none
  integer, intent(in) :: files, nlines
  character(255), intent(in) :: fnout
  character(255), dimension(files), intent(in) :: fns
  integer, dimension(files), intent(in) :: ncols
  integer, intent(out) :: result
  integer :: i, j, id, stat
  integer, dimension(files) :: units
  real, dimension(:,:), allocatable :: row
  character(50) :: fmt0
  character(50), dimension(files) :: fmt
  character(4), dimension(files) :: advance
  allocate(row(files,maxval(ncols, 1)))
  !row(:) = 9 
  advance(:) = 'no'
  advance(files) = 'yes'
  print *, files, nlines
  print *, advance
  do i=1,files
    print *, trim(fns(i)), ncols(i)
  enddo
  !print *,nlines,ncols
  do i=1,files
    units(i) = 200 + i
    open(units(i), file=fns(i), status='OLD')
    write(fmt0, '(i5)') ncols(i)
    fmt(i)='('//trim(adjustl(fmt0))//'F5.2)'
  end do
  open(55, file=fnout, status='UNKNOWN')
  print *, fmt
  do j=1,nlines
    do i=1,files
      read(units(i), *, iostat=stat) id, row(i,1:ncols(i))
      if (stat /= 0) exit
      !print *, ncols(i), row(1:ncols(i)), 'Bla.'
      if (i == 1) write(55, '(i20)', advance='no') id
      write(55, fmt, advance=advance(i)) row(i,1:ncols(i))
    end do
    if (stat /= 0) exit
  end do
  deallocate(row)
  close(55)
  do i=1,files
    close(units(i))
  end do
  result=stat
end subroutine rowconcat

(子程序是R包的一部分,所以有一些包装器函数,但上面的代码再现了这个问题。)

结果

                   1 1.00 2.00 3.00 4.00 5.00 6.00
 7.00
                   2 1.00 2.00 3.00 4.00 5.00 6.00
 7.00
                   3 1.00 2.00 3.00 4.00 5.00 6.00
 7.00

不,它不是用词包装的。在最后一个元素之前,一条新的线悄悄地进来了。为什么?

交换这两个文件,例程就会按预期工作。但是,它在某种程度上取决于列数的顺序。

正如注释中所指出的,您的代码不会重现您的输出。您的初始设置与输入文件不一致。

files=2
fns(1) = 'f2.txt'
fns(2) = 'f1.txt'
fnout = 'fres.txt'
nlines=3
ncols(1) = 2
ncols(2) = 4

应该是

files=2
fns(1) = 'f1.txt'
fns(2) = 'f2.txt'
fnout = 'fres.txt'
nlines=3
ncols(1) = 3
ncols(2) = 4

在您的子例程中,您有一些混乱的代码和大量的逻辑来管理您的读写。正如@AlexanderVogt所说,读取数组并立即写出它更有意义。在内存限制您一次将所有数据加载到内存中的能力的情况下,我将在中途与您见面,转而执行基于行的IO。考虑一下rowconcat:的修改版本

subroutine rowconcat(files, fns, fnout, nlines, ncols, result)
  implicit none
  integer, intent(in) :: files, nlines
  character(255), intent(in) :: fnout
  character(255), dimension(files), intent(in) :: fns
  integer, dimension(files), intent(in) :: ncols
  integer, intent(out) :: result
  integer :: i, j, id, stat, total_columns, start_column
  integer, dimension(files) :: units
  real, dimension(:), allocatable :: row
  character(50) :: fmt
  character(8) :: str_cols
  total_columns = sum(ncols)
  allocate(row(total_columns))
  do i=1,files
    units(i) = 200 + i
    open(units(i), file=fns(i), status='OLD')
  end do
  open(55, file=fnout, status='UNKNOWN')
  write(str_cols,'(i8)') total_columns
  write(fmt,'(A)') '(I20,'//trim(adjustl(str_cols))//'F5.2)'
  do j=1,nlines
     start_column = 1
     do i=1,files
        read(units(i), *, iostat=stat) id, row(start_column:start_column+ncols(i)-1)
        start_column = start_column+ncols(i)
        if (stat /= 0) exit
     end do
     write(55, fmt, iostat=stat) id, row
     if (stat /= 0) exit
  end do
  deallocate(row)
  close(55)
  do i=1,files
    close(units(i))
  end do
  result=stat
end subroutine rowconcat

对此的主要变化是

  • row中分配元素的整个输出行
  • 对所有写入使用单一格式fmt
  • 对于每条线路
    • 对于每个文件:将文件中的元素读取到适当位置的行中
    • 输出行这将内务管理逻辑简化为只记下您在哪一列开始填充下一个文件行

我没有费心去诊断为什么你的代码不起作用,因为从表面上看,它会产生胡言乱语。直到我把子程序修改到我的版本中,我才注意到你的输入值被破坏了,当时我没有心情恢复工作代码,重新开始,试图看看你做错了什么。

最新更新