Head And Tail
November 13, 2015
Today’s exercise is a simple file-handling task for beginning programmers: take the name of a text file as input and write as output the first and last lines of the file.
Your task is to write the file-handling program described above. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
Scala with iterator
def headAndTail(in: String) = { val lines = Source.fromFile(in).getLines() println(lines.next()) while (lines.hasNext) { val last = lines.next if (!lines.hasNext) println(last) } }Haskell:
FSharp :
Haskell:
headTail filename = (liftM lines $ readFile filename) >>= mapM_ putStrLn . (zipWith ($) [head,last]) . repeathead -1 $1; tail -1 $1
I memory-mapped the file as a byte array. I wanted to learn about that.
A source says it is guaranteed that an ASCII byte cannot occur in the middle of a UTF-8 character; other assumptions about what line-end characters occur where in the file remain because I couldn’t be bothered.
# tiedoston ht.jl ensimmäinen rivi """Displays the first and last line of the named file, assuming way too much about the byte-level contents of the files.""" function peek(name) s = Mmap.mmap(name, Array{UInt8,1}, filesize(name)) print("head: ", utf8(s[1:findfirst(s, 0x0a)])) print("tail: ", utf8(s[findprev(s, 0x0a, end - 1) + 1:end])) end peek(ARGS[1]) # tiedoston ht.jl viimeinen riviTesting with the source code file itself:
// C#:
string[] lines = File.ReadAllLines(@”file.txt”);
if (lines.Length != 0)
{
MessageBox.Show(lines[0] + “\n” + lines[lines.Length – 1]);
}
Here’s a C++ solution that just uses some basic Unix system functions – sbrk and brk for memory allocation, read and write for I/0. Probably should check return codes & allow for partial writes. Could be more intelligent about copying data from the input buffer too.
#include <unistd.h> #include <fcntl.h> static const size_t BSIZE = 256; int main(int argc, char *argv[]) { int fd = (argc == 1) ? 0 : open(argv[1], O_RDONLY); char *buff = (char*)sbrk(2*BSIZE); // Allocate char *start = buff + BSIZE, *end = start + BSIZE; char *p = start; bool first = true; while (true) { ssize_t n = read(fd,buff,BSIZE); if (n <= 0) break; for (ssize_t i = 0; i < n; i++) { if (p > start && *(p-1) == '\n') { if (first) write(1,start,p-start); first = false; p = start; } if (p == end) brk(end += BSIZE); // Extend *p++ = buff[i]; } } write(1,start,p-start); };; A Simple Clisp solution
(defun head-tail(name)
(let (last)
(with-open-file (file name)
(print (read-line file))
(loop for x = (read-line file nil) while x do
(setf last x))
(print last))))
Using the Reverse_file_buf from my solution to the 17 November 2015 exercise…
It’s pretty fast even on large files (despite the inefficiencies of Reverse_file_buf) because it jumps straight to the end to find the last line.
#include <algorithm> #include <cctype> #include <fstream> #include <iostream> #include <string> #include <vector> #include "Reverse_file_buf.hpp" //Ignores lines with only whitespace or control characters. //Not Unicode friendly. std::string find_first_line(std::istream& in) { std::string line; while (std::getline(in, line)) { if (std::any_of(line.begin(), line.end(), ::isgraph)) { return line; break; } } return ""; } int main(int argc, char** argv) { std::vector<std::string> args(argv + 1, argv + argc); if (args.empty()) args.push_back("praxis.cpp"); for (auto arg: args) { std::ifstream forwards(arg); std::cout << find_first_line(forwards) << '\n'; Reverse_file_buf rfb(arg); std::istream backwards(&rfb); auto line = find_first_line(backwards); std::reverse(line.begin(), line.end()); std::cout << line << '\n'; } }Some Racket Scheme solutions .