4
\$\begingroup\$

This is a tiny etude on a theme given here, using Python: Provide a function that generates a human readable file size such as 10.0GiB, 13B, 42.2TiB from a given integer number. (A similar Ruby etude is here.) The function should

  • gracefully handle any input (even negative numbers, although they don't make sense for counting bytes - maybe they could represent negative offsets)
  • print numbers smaller than 1024 without a decimal point
  • print all other numbers with one decimal place and the appropriate unit (KiB, MiB, etc.)

It's more verbose than the python counterpart, so the question is whether this can be made more terse.

 humanReadableBytes :: Integer -> String
 humanReadableBytes size | null pairs = printf "%.0fZiB" (size'/1024^7)
 | otherwise = if unit=="" then printf "%dB" size 
 else printf "%.1f%sB" n unit
 where
 (n, unit) = head pairs
 pairs = dropWhile ((1024<).abs.fst) 
 (zip (map ((size'/).(1024^)) [0..]) units) :: [(Double, String)]
 size' = fromIntegral size
 units = ["","Ki","Mi","Gi","Ti","Pi","Ei","Zi"]

Right away it can be noted that the use of String is suboptimal, but Text.Printf does not allow Text, so printf ought to be replaced.

asked Aug 19, 2017 at 7:34
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

We begin with the "loop". map ((size'/).(1024^)) [0..] is iterate (/1024) size. This has not necessarily the same effect, since (x/y)/y is not the same as x/y2 when using floating point arithmetic, but it's fine for our cause. It also works like the Python code, which was your original intention.

We now have

humanReadableBytes :: Integer -> String
humanReadableBytes size | null pairs = printf "%.0fZiB" (size'/1024^7)
 | otherwise = if unit=="" then printf "%dB" size 
 else printf "%.1f%sB" n unit
where
 (n, unit) = head pairs
 pairs = zip (iterate (/1024) 'size)) units
 size' = fromIntegral size :: Double
 units = ["","Ki","Mi","Gi","Ti","Pi","Ei","Zi"]

And that's all we can improve in terms of terseness at that point without changing your code fundamentally. The main difference between the Python and the Haskell implementation is the mutation of the original num and the early exit from the function.

I would recommend to check the "B" case earlier by the way:

humanReadableBytes :: Integer -> String
humanReadableBytes size
 | abs size < 1024 = printf "%dB" size 
 | null pairs = printf "%.0fZiB" (size'/1024^7)
 | otherwise = printf "%.1f%sB" n unit
where
 ...
answered Aug 19, 2017 at 9:51
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.