## A Dozen Lines Of Code

### June 3, 2016

Today’s exercise demonstrates that it is sometimes possible to do a lot with a little.

Your task is to write some interesting and useful program in no more than a dozen lines of code. 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.

Pages: 1 2

### 6 Responses to “A Dozen Lines Of Code”

1. mcmillhj said

Sorting IPv4 addresses numerically, code with whitespace and shebang line minus the test data is 12 lines:

```#!perl

use strict;
use warnings;

map  { join '.', @\$_ }
sort { \$a-> <=> \$b-> || \$a-> <=> \$b-> || \$a-> <=> \$b-> || \$a-> <=> \$b-> }
map  { chomp; [split /\./] } readline(*DATA);

__DATA__
123.4.245.23
104.244.253.29
1.198.3.93
32.183.93.40
104.30.244.2
104.244.4.1
```

outputs:
1.198.3.93
32.183.93.40
104.30.244.2
104.244.4.1
104.244.253.29
123.4.245.23

2. Zack said

function fne(x::Union{Array{Int8,1}, BitArray{1}}) # Feature Normalized Entropy
n = length(x) # number of unique values of feature
ln = log(2, n)
z = 0.0
for c in collect(values(counter(x))); z += c*(ln – log(2, c)) / n; end
if typeof(x) <: BitArray
return z
else
me = log(2, length(unique(x))) # maximum entropy
return z / me
end
end

where counter() is a function that provides the frequencies of the elements of the (nominal) feature x.

The actual function fne() is much more comprehensive and makes use of the fe() auxiliary, that calculates the feature entropy. The normalized version of the feature entropy takes values in [0, 1] and is a much more intuitive metric for assessing a given (nominal) feature.

3. Globules said

```-- This program uses the Karplus-Strong algorithm to synthesize the sound of a
-- plucked string.  Excluding comments and blank lines, it consists of exactly
-- 12 lines.  After compiling it, run it like this:
--
--   ./karstr | play --type raw --rate 44100 --bits 64 \
--                   --encoding floating-point --channels 1 \
--                   --volume 0.5 --endian little -
--
-- where the `play' program is from the SOX package.  It takes the sound wave
-- data produced by karstr and plays it on the audio device.
--
-- The entire algorithm is implemented by the `karstr' function, which
-- highlights the use of laziness in Haskell.  In particular, the value `note'
-- is defined in terms of itself.  It's an infinite list of sound samples, but
-- only as many as are required by karstr's caller will actually be generated.
--
-- The function simulates a plucked string as a "recirculating delay line" whose
-- output is passed through a simple low-pass filter.  Its argument is a burst
-- of white noise, representing the superposition of many sound frequencies
-- immediately after the string is plucked.  The repeated application of the
-- low-pass filter simulates energy loss in the string, causing higher
-- frequencies to be dampened more quickly than lower frequencies.  This is what
-- leads to the initial "twang" sound, followed by a decay towards a more pure
-- tone.
--
-- The length of the white noise burst is what determines the frequency of the
-- note.

import Data.ByteString.Builder
import System.IO
import System.Random

karstr nz = note where note = nz ++ map (0.4995*) (zipWith (+) note (tail note))

main = let pluck = take (secs 10) . karstr
secs tm = round (44100 * tm)
freq hz = round (44100 / hz)
noise n = take n . randomRs (-1, 1)
in do nz <- fmap (noise (freq 130.81)) getStdGen
hSetBinaryMode stdout True
hSetBuffering  stdout (BlockBuffering Nothing)
hPutBuilder stdout . mconcat . map (doubleLE . (*0.2)) \$ pluck nz
```
4. Globules said

Definitely not a 12-line program, but a nice follow-up to the previous one. It uses the same Karplus-Strong algorithm, but mixes individual notes into a sequence of arpeggios.

```-- Compile, then run as:
--
--   ./arpeggios | play --type raw --rate 44100 --bits 64 \
--                      --encoding floating-point --channels 1 \
--                      --volume 0.5 --endian little -
--

import Data.ByteString.Builder
import Data.List (transpose)
import System.IO
import System.Random

-- The sampling rate (samples/second).
rate :: Double
rate = 44100

-- A list of samples simulating a plucked string.  Its frequency is determined
-- by the length of the white noise argument.
karstr :: [Double] -> [Double]
karstr nz = note
where note = nz ++ map (0.4995 *) (zipWith (+) note (tail note))

-- A sequence of notes each of whose start is delayed by the given number of
-- samples.
arpeggio :: Int -> [[Double]] -> [Double]
arpeggio n = mix . zipWith (++) pauses . map karstr
where mix = map sum . transpose
pauses = map (`replicate` 0) [0, n ..]

-- A chromatic scale starting at C3 (130.81 Hz).
scale :: RandomGen g => g -> [[Double]]
scale g = [noise (freq hz) g | i <- [0..11 :: Int], let hz = semitone i]
where noise n = take n . randomRs (-1, 1)
freq hz = round \$ rate / hz
semitone i = 130.81 * 2.0 ** (fromIntegral i / 12.0)

-- The number of samples for the given number of seconds.
secs :: Double -> Int
secs tm = round \$ rate * tm

-- Write a series of 64-bit, little-ending floating point samples to the handle.
putSamples :: Handle -> [Double] -> IO ()
putSamples h samps = do
hSetBinaryMode h True
hSetBuffering  h (BlockBuffering Nothing)
hPutBuilder stdout . mconcat . map (doubleLE . (* 0.2)) \$ samps

main :: IO ()
main = do
-- Convenient names for the notes we'll use.
[c, _, d, _, e, f, _, g, _, a, _, b] <- fmap scale getStdGen

-- A sequence of three note arpeggios.  The first three have a delay of 0.08
-- seconds between the start of each note, and each arpeggio is played for 1.5
-- seconds before beginning the next one.  The final arpeggio is played more
-- slowly and lasts longer in order to better hear the notes decay.
let arpeggios = concat [ take (secs 1.5) \$ arpeggio (secs 0.08) [a, c, e]
, take (secs 1.5) \$ arpeggio (secs 0.08) [f, a, c]
, take (secs 1.5) \$ arpeggio (secs 0.08) [d, f, a]
, take (secs 9.0) \$ arpeggio (secs 0.30) [e, g, b] ]
putSamples stdout arpeggios
```
5. Zack said

Awesome applications! How does Haskell compare with C in terms of efficiency and resource management?

6. Globules said

@Zack I’m sure there’s a lot to be said on that subject, but I’m far from the best person to say it. :-) I only play around with Haskell; I don’t use it in my day job. With that being said… Haskell has automatic memory management (i.e. garbage collection), so you may not want to use it where periodic short pauses can’t be tolerated (e.g. game programming where you want to maintain a high frame rate). Also, you have to be careful of “space leaks”, due to laziness, which is the term Haskellers use to describe memory that is unintentionally consumed by unevaluated functions and data. There are techniques and libraries for dealing with this, just as there are different ways of avoiding memory leaks, wild pointers, etc. in C/C++. In general, the freedom from having to manage your own memory is very liberating. With respect to the speed of the resulting code I’ve seen small programs equal that of C/C++. For more realistic programs I wouldn’t be surprised if Haskell was slower by a small constant factor.

For an overview of the areas in which Haskell does well (or not so well) I suggest checking out State of the Haskell ecosystem. (This is from the point-of-view of the libraries that are available. The compiler itself is solid.)