2017-01-14 11 views
3

Я ищу, чтобы быстро получить только последнюю строку файла в Haskell --- начиная с конца, а не с начала --- и с некоторыми трудностями с помощью hSeek правильно.hSeek и SeekFromEnd в Haskell

Кажется, SeekFromEnd N ведет себя иначе, чем найти длину файла sz, и с помощью AbsoluteSeek идти (sz - N) байт.

outh <- openFile "test.csv" ReadMode 

λ> hIsSeekable outh 
True 

λ> hFileSize outh 
81619956 
λ> hSeek outh AbsoluteSeek 1000 
λ> hTell outh 
1000 

λ> hSeek outh SeekFromEnd 1000 
λ> hTell outh 
81620956 

λ> hSeek outh AbsoluteSeek 0 
λ> hGetLine outh 
"here's my data" 

λ> hSeek outh SeekFromEnd 10000 
-*** Exception: test.csv: hGetLine: end of file 

Хм, это странно.

Итак, я сделал функцию, которая делает это с абсолютной вместо:

λ> hSeek outh SeekFromEnd 100000 
λ> hTell outh 
81719956 

fromEnd outh = do 
    sz <- hFileSize outh 
    hSeek outh AbsoluteSeek (sz - 100000) 

λ> fromEnd outh 

λ> hTell outh 
81519956 

Так выход мудрым, они имеют разные ответы, которые странно. Кроме того, теперь я могу также использовать hGetLine, который SeekFromEnd не удалось на:

λ> hGetLine outh 
"partial output" 
λ> hGetLine outh 
"full output, lots of fields, partial output" 

Не ясно мне, что здесь происходит. Почему мой fromEnd ведет себя иначе, чем SeekFromEnd в разрешении hGetLine?

Вопрос 2: что/будет/будет правильной стратегией для начала в конце файла и поиска назад к первой новой строке (первая \ n после новой строки EOF)?

В этом вопросе я ищу конкретный ответ, используя SeekFromEnd.

+1

Кажется, что 'hSeek outh SeekFromEnd 1000' переместился на 1000 байт после окончания! Этого не должно быть. ["Невозможно установить отрицательную позицию ввода/вывода или физический файл, позицию ввода-вывода за пределами текущего конца файла."] (Http://hackage.haskell.org/package/ base-4.9.1.0/docs/System-IO.html # v: hSeek) Теперь я тоже смущен. В любом случае, я думаю, вы должны использовать offset -1000. – chi

+0

А, я должен был использовать древнюю науку вычитания. Спасибо, @chi, даже не заметил этого. – Mittenchops

+0

Возможный дубликат [Haskell Читать последнюю строку с ленивым mmap] (http://stackoverflow.com/questions/41656678/haskell-read-last-line-with-a-lazy-mmap) – Alec

ответ

2

Ожидается, что смещение до SeekFromEnd будет отрицательным.

Что касается получения последней строки файла, мы сталкиваемся с досадой, которую мы должны сканировать каждый символ с конца, один за другим, каждый раз при сбросе позиции. Тем не менее, мы делаем сделаем это - мы просто продолжаем двигаться назад, пока не встретим первый символ \n.

import System.IO 

-- | Given a file handle, find the last line. There are no guarantees as to the 
-- position of the handle after this call, and it is expected that the given 
-- handle is seekable. 
hGetLastLine :: Handle -> IO String 
hGetLastLine hdl = go "" (negate 1) 
    where 
    go s i = do 
    hSeek hdl SeekFromEnd i 
    c <- hGetChar hdl 
    if c == '\n' 
     then pure s 
     else go (c:s) (i-1) 

Вы можете добавить беловатый один здесь, как и большинство файлов обычно заканчиваются в \n (и это пустая строка, вероятно, не то, что вы хотите)

+1

Это очень быстро работает на моей машине. Мне пришлось изменить hGetLastLine hdl = go "" (отрицать 2) Чтобы сделать то, что вы сказали, с последним символом, который \ n. Я думаю, на окнах это должно быть 3 из-за \ r \ n ... – Mittenchops