В Fortran, как писать в обратном направлении из файла в другой файл блоками строк снизу вверх

У меня есть файл ASCII, который выглядит так:

____________________________________________
Header1 ...
Header2 ...
Header3 ...
block(1)data1 block(1)data2 block(1)data3
block(1)data4 block(1)data5 block(1)data6
block(2)data1 block(2)data2 block(2)data3
block(2)data4 block(2)data5 block(2)data6
...
block(n)data1 block(n)data2 block(n)data3
block(n)data4 block(n)data5 block(n)data6
____________________________________________

Я хотел бы преобразовать его в файл ASCII, который выглядит так:

____________________________________________
HeaderA ...
HeaderB ...
block(n)data1 block(n)data2 block(n)data3
block(n)data4 block(n)data5 block(n)data6
block(n-1)data1 block(n-1)data2 block(n-1)data3
block(n-1)data4 block(n-1)data5 block(n-1)data6
....
block(1)data1 block(1)data2 block(1)data3
block(1)data4 block(1)data5 block(1)data6
____________________________________________

Данные в основном представляют собой действительные числа, а размер набора данных слишком велик для использования размещаемых массивов. Так что приходится как-то читать и писать на лету.

Мне не удалось найти способ чтения или записи назад в файле.


person J. G.    schedule 13.07.2018    source источник
comment
Добро пожаловать в Stack Overflow! Не могли бы вы опубликовать минимальный, полный и проверяемый пример вашей попытки и указать, где именно вы застряли.   -  person brass monkey    schedule 13.07.2018
comment
Почему вы отметили это out-of-memory? Насколько велик этот файл?   -  person Ross    schedule 13.07.2018
comment
Прочитайте строки 1..n, где n определяется размером вашей рабочей памяти. Запишите их в «обратном» порядке к временному файлу 1. Повторяйте, пока у вас не будет куча временных файлов, объедините их в правильном порядке.   -  person High Performance Mark    schedule 13.07.2018


Ответы (3)


Я бы не использовал Fortran напрямую, а скорее последовательность команд Linux (или утилиты Cygwin/GNU в Windows). Fortran также возможен (см. второй вариант).

Схема (на основе команд ОС):

  • получить общее количество строк (например, wc)
  • получить первые 3 строки из файла (например, с head) в файл result file
  • process the main part
    • take last but 3 lines (e.g. with tail)
    • запустите результат, например, через скрипт awk, соединяющий соответствующие строки
    • запустить tac по результату
    • запустите еще один скрипт awk, разделив строки
    • добавить результат к result file

Другой идеей было бы (на языке программирования):

  • создайте массив с начальной позицией файла каждого блока (так что результат ftell).
  • переместить заголовок в новый файл
  • run through the array created above, from the end to the beginning
    • do an fseek to the indicated position
    • прочитайте соответствующее количество строк и запишите их снова
person albert    schedule 13.07.2018

путь к большому использованию распределяемых массивов.

Если данные помещаются в памяти, вы можете это сделать. Я проверил это, файл

header(1)
header(2)
header(3)
block(1).data1 block(1).data2 block(1).data3
block(1).data4 block(1).data5 block(1).data6
block(2).data1 block(2).data2 block(2).data3
block(2).data4 block(2).data5 block(2).data6
...
block(9999998).data1 block(9999998).data2 block(9999998).data3
block(9999998).data4 block(9999998).data5 block(9999998).data6
block(9999999).data1 block(9999999).data2 block(9999999).data3
block(9999999).data4 block(9999999).data5 block(9999999).data6

с размером файла 1,2 ГБ можно было бы изменить с помощью этого небольшого скрипта awk:

#!/usr/bin/awk
# if line contains word "header", print immediately, move on to next line.
/header/ {print; next}

# move every line to memory.
  {
    line[n++] = $0
  }

# When finished, print them out in order n-1, n, n-3, n-2, n-5, n-4, ...
END {
  for (i=n-2; i>=0; i-=2) {
    print(line[i])
    print(line[i+1])
  }
}

менее чем за 2 минуты.

Если это действительно невозможно, вам нужно сделать то, что сказал @high-performance-mark, и прочитать его в управляемых блоках, перевернуть его в памяти, а затем объединить их вместе в конце. Вот моя версия:

program reverse_order
  use iso_fortran_env, only: IOSTAT_END
  implicit none
  integer, parameter :: max_blocks_in_memory = 10000
  integer, parameter :: max_line_length=100
  character(len=max_line_length) :: line
  character(len=max_line_length) :: data(2, max_blocks_in_memory)

  character(len=*), parameter :: INFILE='data.txt'
  character(len=*), parameter :: OUTFILE='reversed_data.txt'
  character(len=*), parameter :: TMP_FILE_FORMAT='("/tmp/", I10.10,".txt")'
  character(len=len("/tmp/XXXXXXXXXX.txt")) :: tmp_file_name

  integer :: in_unit, out_unit, tmp_unit
  integer :: num_headers, i, j, tmp_file_number

  integer :: ios

! Open the input and output files
  open(newunit=in_unit, file=INFILE, action="READ", status='OLD')
  open(newunit=out_unit, file=OUTFILE, action='WRITE', status='REPLACE')

! Transfer the headers to the output file immediately.
  num_headers = 0
  do
    read(in_unit, '(A)') line
    if (index(line, 'header') == 0) exit
    num_headers = num_headers + 1
    write(out_unit, '(A)') trim(line)
  end do

! We've already read the first data line, so let's rewind and start anew.
  rewind(in_unit)
! move past the headers.
  do i = 1, num_headers
    read(in_unit, *)
  end do


  tmp_file_number = 0

! Read the data from the input line max_blocks_in_memory blocks at a time.
  read_loop : do
    do i = 1, max_blocks_in_memory
      read(in_unit, '(A)', iostat=ios) data(1, i)
      if (ios == IOSTAT_END) then   ! Reached the end of the input file.
        if (i > 1) then         ! Still have final values in memory, write them
                                ! to output immediately.
          do j = i-1, 1, -1
            write(out_unit, '(A)') trim(data(1, j))
            write(out_unit, '(A)') trim(data(2, j))
          end do
        end if
        exit read_loop
      end if
      read(in_unit, '(A)') data(2, i)
    end do

!  Reasd a block of data, write it in reverse order into a temporary file.

    tmp_file_number = tmp_file_number + 1
    write(tmp_file_name, TMP_FILE_FORMAT) tmp_file_number
    open(newunit=tmp_unit, file=tmp_file_name, action="WRITE", status="NEW")
    do j = max_blocks_in_memory, 1, -1
      write(tmp_unit, '(A)') data(1, j)
      write(tmp_unit, '(A)') data(2, j)
    end do
    close(tmp_unit)
  end do read_loop

! Finished with input file, don't need it any more.
  close(unit=in_unit)

! Concatenate all the temporary files in reverse order to the output file.
  do j = tmp_file_number, 1, -1
    write(tmp_file_name, TMP_FILE_FORMAT) j
    open(newunit=tmp_unit, file=tmp_file_name, action="READ", status="OLD")
    do
      read(tmp_unit, '(A)', iostat=ios) line
      if (ios == IOSTAT_END) exit
      write(out_unit, '(A)') trim(line)
    end do
    close(tmp_unit, status="DELETE")  ! Done with this file, delete it after closing.

  end do

  close(unit=out_unit)

end program reverse_order
person chw21    schedule 16.07.2018

Что ж, у меня как бы есть ответ, но он не сработал, возможно, из-за ошибок компилятора или моего элементарного понимания позиционирования файлов в Фортране. Моя попытка состояла в том, чтобы открыть входной файл с помощью access = 'stream' и form = 'formatted'. Таким образом, я мог помещать позиции строк в стек и извлекать их, чтобы они выходили в обратном порядке. Затем, проходя по строкам в обратном порядке, я мог записать их в наш файл put.

program readblk
   implicit none
   integer iunit, junit
   integer i, size
   character(20) line
   type LLnode
      integer POS
      type(LLnode), pointer :: next => NULL()
   end type LLnode
   type(LLNODE), pointer :: list => NULL(), current => NULL()
   integer POS, temp(2)

   open(newunit=iunit,file='readblk.txt',status='old',access='stream',form='formatted')
   open(newunit=junit,file='writeblk.txt',status='replace')
   do i = 1, 3
      do
         read(iunit,'(a)',advance='no',EOR=10,size=size) line
         write(junit,'(a)',advance='no') line
      end do
10    continue
      write(junit,'(a)') line(1:size)
   end do
   do
      inquire(iunit,POS=POS)
      allocate(current)
      current%POS = POS
      current%next => list
      list => current
      read(iunit,'()',end=20)
   end do
20 continue
   current => list
   list => current%next
   deallocate(current)
   do while(associated(list))
      temp(2) = list%POS
      current => list%next
      deallocate(list)
      temp(1) = current%POS
      list => current%next
      deallocate(current)
      do i = 1, 2
write(*,*) temp(i)
         read(iunit,'(a)',advance='no',EOR=30,size=size,POS=temp(i)) line
         write(junit,'(a)',advance='no') line
         do
            read(iunit,'(a)',advance='no',EOR=30,size=size) line
            write(junit,'(a)',advance='no') line
         end do
30       continue
      write(junit,'(a)') line(1:size)
      end do
   end do
end program readblk

Вот мой входной файл:

Header line 1
Header line 2
Header line 3
1a34567890123456789012345678901234567890
1b34567890123456789012345678901234567890
2a34567890123456789012345678901234567890
2b34567890123456789012345678901234567890
3a34567890123456789012345678901234567890
3b34567890123456789012345678901234567890

Теперь с ifort мои позиции в файле распечатывались как

 214
 256
 130
 172
  44
  88

Обратите внимание, что первая строка находится в конце записи 3, а не в начале записи 4. Выходной файл был

Header line 1
Header line 2
Header line 3
3a34567890123456789012345678901234567890
3b34567890123456789012345678901234567890
2a34567890123456789012345678901234567890
2b34567890123456789012345678901234567890

1a34567890123456789012345678901234567890

С помощью gfortran позиции файла распечатываются как

 214
 256
 130
 172
  46
  88

На этот раз первая строка находится в начале записи 4, как я и ожидал. Однако выходной файл имел неудачное содержимое

Header line 1
Header line 2
Header line 3
3a34567890123456789012345678901234567890
3b34567890123456789012345678901234567890
2a34567890123456789012345678901234567890
2b34567890123456789012345678901234567890
3a34567890123456789012345678901234567890
3b345678901234567890123456789012341a34567890123456789012345678901234567890

Я надеялся на более положительный результат. Я не могу сказать, вызваны ли мои результаты плохим программированием или ошибками компилятора, но я разместил сообщение на случай, если кто-то еще сможет заставить мое чистое решение Fortran работать.

person user5713492    schedule 16.07.2018