Sorting Lines in 5 Languages

Jussi Pakkanen writes that C++ has become a scripting language. As evidence, he presents the following 19-line program in idiomatic C++11 which reads lines from the first command-line argument, sorts the lines, and writes them to the second argument.

#include<string>
#include<vector>
#include<algorithm>
#include<fstream>

using namespace std;

int main(int argc, char **argv) {
  ifstream ifile(argv[1]);
  ofstream ofile(argv[2]);
  string line;
  vector<string> data;
  while(getline(ifile, line)) {
    data.push_back(line);
  }
  sort(data.begin(), data.end());
  for(const auto &i : data) {
    ofile << i << std::endl;
  }
  return 0;
}

I’m an incorrigible fan of C++ from days of old; I still prefer it to straight C for most low-level systems programming tasks. And I have to admit that this is admirably concise for a C++ program.

However, I thought it might be fun to see what this program looks like in other “scripting” languages. I used a file containing the following lines to test my implementations.

D is for Durian
B is for Breadfruit
C is for Clementine
A is for Apple

Starting with Ruby, since it’s what I spend most of my time in these days (this example has been updated with feedback from Joel VanderWerf):

File.write(ARGV.pop, ARGF.sort.join)

This example is notable for using File.write , introduced in either 1.9.3 or 2.0, I can’t remember. It also uses ARGF to refer to “the concatenation of all files supplied on the command line”. At the point this is evaluated, the output file has already been pop‘ed off of the ARGV array.

Next, a Perl version.

open OUT, ">", pop(@ARGV);
print OUT sort(<>);

This being Perl, and my Perl being incredibly rusty, I’m sure it could be golfed down to a one-liner by a pro. In this code, the empty line-reading operator (<>) serves the same purpose as ARGF.each_line above.

Now, how about an example using the grandparent of all scripting languages, TCL.

set in  [open [lindex $argv 0]]
set out [open [lindex $argv 1] w]
while {[gets $in line] > 0} {
    lappend lines $line
}
set lines [lsort $lines]
foreach line $lines {
    puts $out $line
}

As you can see, TCL does not put the same premium on succinctness as some of the later scripting languages. Still, 9 lines ain’t bad.

Of course no comparison of scripting languages would be complete without an example in Bourne Shell:

cat $1 | sort > $2

EDIT: Stephen Ball points out that this is a superfluous use of cat. It should be:

sort $1 > $2

To sum up, the C++ version is twice as long as the longest of my scripting language implementations. So while C++11 has made advances, I don’t think I’ll be using it for everyday scripting tasks anytime soon.

Thank you to Jussi Pakkanen for giving me an excuse to dust off my Perl and TCL skills. Writing these was an entertaining diversion.

UPDATE: Here are some more versions people have kindly contributed.

A Go version, from Bill Mill:

package main

import ( "io/ioutil"; "os"; "strings"; "sort" )

func main() {
        content, _ := ioutil.ReadFile(os.Args[1])
        lines := strings.Split(strings.TrimSpace(string(content)), "\n")
        sort.Strings(lines)
        ioutil.WriteFile(os.Args[2], []byte(strings.Join(lines, "\n")), 0666)
}

A Python version, also from Bill Mill, and incorporating feedback from commenter Keith:

import sys
open(sys.argv[2], 'w').writelines(sorted(open(sys.argv[1])))

Here it is in Elixir, from Eduardo Krustofski:

[input, output] = System.argv
File.write!(output, File.stream!(input) |> Enum.sort |> Enum.join(""))

And here’s Clojure, from Ricardo Mendes:

(->> (slurp (nth *command-line-args* 1))
     clojure.string/split-lines
     sort
     (clojure.string/join "\n")
     (spit (nth *command-line-args* 2)))

A Rust version, from ajuckel:

extern mod extra;
use std::*;
use extra::*;
fn main() {
    let reader = io::file_reader(&Path(os::args()[1])).unwrap();
    let writer = io::file_writer(&Path(os::args()[2]), 
                                 [io::Append, io::Create]).unwrap();
    let mut lines = reader.read_lines();
    sort::quick_sort3(lines);
    for l in lines.iter() {
        writer.write_line(*l)
    }
}

Haskell, from Michael Xavier:

import Data.List (sort)
import System.Environment (getArgs)

main = do (inFile:outFile:_) <- getArgs
          writeFile outFile . unlines . sort . lines =<< readFile inFile

Lua, from Peter Aronoff:

io.input(arg[1])
io.open(arg[2], 'w')
io.output(arg[2])
lines = {}
for line in io.lines() do lines[#lines+1] = line end
table.sort(lines)
for _, line in ipairs(lines) do io.write(line, "\n") end

PHP, from joonty:

<?php
$input = file("php://stdin");
sort($input);
file_put_contents(end($argv), join($input));

Caius Durling contributed an AppleScript version, but since AppleScript has no built-in sort function it’s a bit long to include inline.

C#, from Ellyll:

sing System.IO;
using System.Linq;

namespace SortingExample
{
    public class Sorter
    {
        static void Main(string[] args)
        {
            var lines = File.ReadAllLines(args[0]).OrderBy(a => a);
            File.WriteAllLines(args[1], lines);
        }
    }
}

18 comments

  1. Note that you can do without the .readlines() in the Python version, leaving:

    open(sys.argv[2], ‘w’).writelines(sorted(open(sys.argv[1])))

  2. Not idiomatic Smalltalk but my attempt:

    inputFileName := Smalltalk arguments first.

    outputFileName := Smalltalk arguments second.

    FileStream readOnlyFileNamed: inputFileName

    do: [:inputStream | FileStream newFileNamed: outputFileName
    
      do: [:outputStream | inputStream contents lines sort
    
        do: [:each | outputStream nextPutAll: each] separatedBy: [outputStream crlf]]]
    
  3. My friend was complaining about perl being symbol heavy. Opposite:

    use File::Slurp;
    write_file pop(@ARGV), sort(read_file(@ARGV));
    

Leave a Reply

Your email address will not be published. Required fields are marked *