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.
Sorting IPv4 addresses numerically, code with whitespace and shebang line minus the test data is 12 lines:
#!perl use strict; use warnings; my @sorted_ip_addrs = map { join '.', @$_ } sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] || $a->[3] <=> $b->[3] } map { chomp; [split /\./] } readline(*DATA); print join("\n", @sorted_ip_addrs), "\n"; __DATA__ 123.4.245.23 104.244.253.29 1.198.3.93 32.183.93.40 104.30.244.2 104.244.4.1outputs:
1.198.3.93
32.183.93.40
104.30.244.2
104.244.4.1
104.244.253.29
123.4.245.23
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.
A Haskell program.
-- 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 nzDefinitely 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 arpeggiosAwesome applications! How does Haskell compare with C in terms of efficiency and resource management?
@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.)