В Fortran 90, каков хороший способ записи массива в текстовый файл, по строкам?

Я новичок в Fortran, и я хотел бы иметь возможность писать двумерный массив в текстовый файл, по строкам (пробелы между столбцами и каждая строка в своей собственной строке). Я пробовал следующее, И, похоже, работает в следующем простом примере:

PROGRAM test3
  IMPLICIT NONE

  INTEGER :: i, j, k, numrows, numcols
  INTEGER, DIMENSION(:,:), ALLOCATABLE :: a

  numrows=5001
  numcols=762
  ALLOCATE(a(numrows,numcols))
  k=1
  DO i=1,SIZE(a,1)
    DO j=1,SIZE(a,2)
      a(i,j)=k
      k=k+1
    END DO
  END DO

  OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace")
  DO i=1,numrows
    WRITE(12,*) (a(i,j), j=1,numcols)
  END DO
END PROGRAM test3

Как я уже сказал, Это, кажется, работает нормально в этом простой пример: полученный текстовый файл aoutput.txt, содержит номера 1-762 на линии 1, номера 763-1524 на линии 2, и так на.

строка мудрый запись в текстовый файл рутина, которую я должен использовать?

большое спасибо за ваше время. Я действительно ценю это.

3 ответов


здесь есть несколько проблем.

фундаментальный, что вы не должны использовать текст в качестве формата данных для значительной части данных. Он большой и медленный. Текстовый вывод хорош для того, что вы собираетесь прочитать сами; вы не собираетесь сидеть с распечаткой 3,81 миллиона целых чисел и листать их. Как показано в приведенном ниже коде, правильный вывод текста примерно в 10 раз медленнее и на 50% больше, чем двоичный вывод. При переходе к значениям с плавающей запятой, существуют проблемы потери точности с использованием строк ascii в качестве формата обмена данными. так далее.

если ваша цель-обмен данными с matlab, довольно легко записать данные в формат, который может читать matlab; вы можете использовать API matOpen/matPutVariable из matlab или просто записать его как массив HDF5, который может читать matlab. Или вы можете просто записать массив в RAW Fortran binary, как показано ниже, и иметь matlab прочитал его.

если вы должны использовать ascii для напишите огромные массивы (что, как уже упоминалось, является плохой и медленной идеей), тогда вы столкнетесь с проблемами с длиной записи по умолчанию в списке-drected IO. Лучше всего генерировать во время выполнения строку формата, которая правильно описывает ваш вывод, и безопаснее всего для такого большого (~5000 символов!) lines-установить длину записи явно на что-то большее, чем то, что вы будете распечатывать, чтобы библиотека Fortran IO не помогала вам разбивать строки.

в коде ниже,

  WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'

генерирует строку rowfmt, которая в этом случае будет (762(1X,I6)) какой формат вы будете использовать для печати, и до OPEN устанавливает длину записи как нечто большее, чем 7 * numcols + 1.

PROGRAM test3
  IMPLICIT NONE

  INTEGER :: i, j, k, numrows, numcols
  INTEGER, DIMENSION(:,:), ALLOCATABLE :: a
  CHARACTER(LEN=30) :: rowfmt
  INTEGER :: txtclock, binclock
  REAL    :: txttime, bintime

  numrows=5001
  numcols=762
  ALLOCATE(a(numrows,numcols))
  k=1
  DO i=1,SIZE(a,1)
    DO j=1,SIZE(a,2)
      a(i,j)=k
      k=k+1
    END DO
  END DO

  CALL tick(txtclock)
  WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'
  OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace", &
       RECL=(7*numcols+10))
  DO i=1,numrows
    WRITE(12,FMT=rowfmt) (a(i,j), j=1,numcols)
  END DO
  CLOSE(UNIT=12)
  txttime = tock(txtclock)

  CALL tick(binclock)
  OPEN(UNIT=13, FILE="boutput.dat", ACTION="write", STATUS="replace", &
       FORM="unformatted")
  WRITE(13) a
  CLOSE(UNIT=13)
  bintime = tock(binclock)

  PRINT *, 'ASCII  time = ', txttime
  PRINT *, 'Binary time = ', bintime

CONTAINS

    SUBROUTINE tick(t)
        INTEGER, INTENT(OUT) :: t

        CALL system_clock(t)
    END SUBROUTINE tick

    ! returns time in seconds from now to time described by t
    REAL FUNCTION tock(t)
        INTEGER, INTENT(IN) :: t
        INTEGER :: now, clock_rate

        call system_clock(now,clock_rate)

        tock = real(now - t)/real(clock_rate)
    END FUNCTION tock
END PROGRAM test3

Это может быть очень окольный и трудоемкий способ сделать это, но все равно... Вы можете просто распечатать каждый элемент массива отдельно, используя advance='no' (чтобы подавить вставку символа новой строки после того, что печаталось) в вашем write заявление. Как только вы закончите с линией, вы используете "нормальный"write утверждение, чтобы получить символ новой строки, и начать снова на следующей строке. Вот небольшой пример:

program testing

implicit none

integer :: i, j, k

k = 1

do i=1,4
   do j=1,10
      write(*, '(I2,X)', advance='no') k
      k = k + 1
   end do
   write(*, *) ''  ! this gives you the line break
end do

end program testing

при запуске этой программы выводится следует:

 1  2  3  4  5  6  7  8  9 10  
11 12 13 14 15 16 17 18 19 20  
21 22 23 24 25 26 27 28 29 30  
31 32 33 34 35 36 37 38 39 40

использование " * " направлено на список IO -- Fortran будет принимать решения за вас. Некоторые типы поведения не указаны. Вы можете получить больше контроля, используя оператор format. Если вы хотите положительно определить границы строк, вы пишете символ маркера после каждой строки. Что-то вроде:

  DO i=1,numrows
    WRITE(12,*) a(i,:)
    write (12, '("X")' )
  END DO

добавление несколько часов спустя:

возможно, при больших значениях numcols строки слишком длинны для некоторых программ, которые вы используете для изучения файла? Для выведите оператор, попробуйте:

WRITE(12, '( 10(2X, I11) )' ) a(i,:)

который разбивает каждую строку матрицы, если она имеет более 10 столбцов, на несколько более коротких строк в файле.