2011-06-29 43 views
10

Tôi mới sử dụng Fortran, và tôi muốn viết một mảng hai chiều vào một tệp văn bản, theo cách thông minh (khoảng cách giữa các cột và mỗi hàng trên một dòng riêng). Tôi đã thử những điều sau đây, và có vẻ như để làm việc trong các ví dụ đơn giản sau:Trong Fortran 90, cách tốt nhất để viết một mảng vào một tệp văn bản, hàng khôn ngoan là gì?

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 

Như tôi đã nói, điều này dường như làm việc tốt trong ví dụ đơn giản này: kết quả các tập tin văn bản, aoutput.txt, chứa các số 1-762 trên dòng 1, số 763-1524 trên dòng 2, v.v.

Tuy nhiên, khi tôi sử dụng các ý tưởng trên (ví dụ: dòng thứ năm cuối cùng, thứ tư đến cuối cùng, thứ ba đến cuối cùng và dòng thứ hai đến cuối cùng ở trên) phức tạp hơn chương trình, tôi gặp rắc rối; mỗi hàng được phân cách (bởi một dòng mới) chỉ có liên tục, có vẻ như. (Tôi chưa đăng và có thể sẽ không đăng bài, ở đây toàn bộ chương trình/tập lệnh phức tạp của tôi - vì nó khá dài.) Việc thiếu các dấu phân cách hàng nhất quán trong chương trình/tập lệnh phức tạp của tôi có thể gợi ý một lỗi khác trong mã của tôi, chứ không phải bốn dòng ghi-to-file thường xuyên ở trên, kể từ khi ví dụ đơn giản trên xuất hiện để làm việc được. Tuy nhiên, tôi tự hỏi, bạn có thể xin vui lòng giúp tôi nghĩ rằng nếu có một tốt hơn hàng-khôn ngoan ghi-to-văn bản tập tin thường xuyên mà tôi nên sử dụng?

Cảm ơn bạn rất nhiều vì đã dành thời gian. Tôi rất trân trọng điều này.

+1

Phương pháp bạn sử dụng có vẻ ổn với tôi. Bạn nói chương trình thực sự phức tạp hơn, nhưng điều duy nhất quan trọng là các chi tiết cụ thể của mảng, và tôi không thấy nó phức tạp hơn thế nào, chỉ lớn hơn. Bạn có chắc chắn các giới hạn (numrows, numcols) bạn sử dụng trong chương trình đó là chính xác không? – eriktous

+1

Bạn có thể trình bày một chương trình mẫu nhỏ hợp lý để tái tạo hành vi xấu mà bạn đang gặp phải không? Ví dụ là khá tốt, và không nhìn thấy bất cứ điều gì "cụ thể" thật khó để suy đoán về những lỗi có thể xảy ra trong chương trình lớn hơn của bạn. – Rook

Trả lời

12

Có một vài vấn đề ở đây.

Điều cơ bản là bạn không nên sử dụng văn bản dưới dạng định dạng dữ liệu cho một lượng lớn dữ liệu. Nó lớn và rất chậm. Văn bản đầu ra là tốt cho một cái gì đó bạn sẽ đọc cho mình; bạn sẽ không ngồi xuống với bản in 3,81 triệu số nguyên và lật qua chúng. Như mã dưới đây cho thấy, đầu ra văn bản chính xác là khoảng 10 x chậm hơn, và 50% lớn hơn, so với đầu ra nhị phân. Nếu bạn di chuyển đến các giá trị dấu chấm động, có các vấn đề mất chính xác khi sử dụng các chuỗi ascii làm định dạng trao đổi dữ liệu. v.v.

Nếu mục tiêu của bạn là trao đổi dữ liệu với MATLAB, việc ghi dữ liệu vào một khuôn dạng định dạng có thể đọc được khá dễ dàng; bạn có thể sử dụng matOpen/matPutVariable API từ MATLAB, hoặc chỉ viết nó ra như một mảng HDF5 mà MATLAB có thể đọc. Hoặc bạn có thể chỉ cần viết ra các mảng trong nguyên Fortran nhị phân như dưới đây và có matlab read it.

Nếu bạn phải sử dụng ascii để ghi ra các mảng lớn (như đã đề cập, là một ý tưởng xấu và chậm) thì bạn đang gặp sự cố với độ dài bản ghi mặc định trong IO được liệt kê. Tốt nhất là tạo ra khi chạy một chuỗi định dạng mô tả chính xác đầu ra của bạn, và an toàn nhất trên đầu trang này cho các dòng lớn (~ 5000 ký tự rộng!) Là đặt độ dài bản ghi một cách rõ ràng thành thứ gì đó lớn hơn những gì bạn sẽ in ra để thư viện fortran IO không giúp bạn phá vỡ các dòng.

Trong đoạn mã dưới đây,

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

tạo ra chuỗi rowfmt mà trong trường hợp này sẽ là (762(1X,I6)) đó là định dạng mà bạn sẽ sử dụng để in ra, và các tùy chọn để RECLOPEN bộ chiều dài kỷ lục để lớn hơn 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 
3

Sử dụng "*" là danh sách hướng IO - Fortran sẽ đưa ra quyết định cho bạn. Một số hành vi không được chỉ định. Bạn có thể giành quyền kiểm soát nhiều hơn bằng cách sử dụng câu lệnh định dạng. Nếu bạn muốn xác định tích cực ranh giới hàng bạn viết một biểu tượng điểm đánh dấu sau mỗi hàng. Một cái gì đó như:

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

Phụ lục vài giờ sau:

Có lẽ với giá trị lớn của numcols dòng quá dài đối với một số chương trình mà bạn đang sử dụng để kiểm tra các tập tin? Đối với tuyên bố đầu ra, hãy thử:

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

sẽ phá vỡ mỗi hàng của ma trận nếu có nhiều hơn 10 cột thành nhiều dòng ngắn hơn trong tệp.

10

Đây có thể là cách xoay vòng và tốn thời gian để thực hiện, nhưng dù sao ... Bạn có thể in riêng từng phần tử mảng một cách riêng biệt, sử dụng advance='no' (để chặn ký tự dòng mới sau khi đã được in) tuyên bố write của bạn. Khi bạn đã hoàn tất với một dòng, bạn sử dụng câu lệnh 'bình thường' write để lấy ký tự dòng mới và bắt đầu lại trên dòng tiếp theo. Dưới đây là một ví dụ nhỏ:

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 

Khi bạn chạy chương trình này đầu ra như sau:

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 
Các vấn đề liên quan