zendar
zendar

Reputation: 13562

f# byte[] -> hex -> string conversion

I have byte array as input. I would like to convert that array to string that contains hexadecimal representation of array values. This is F# code:

let ByteToHex bytes = 
    bytes 
    |> Array.map (fun (x : byte) -> String.Format("{0:X2}", x))

let ConcatArray stringArray = String.Join(null, (ByteToHex  stringArray))

This produces result I need, but I would like to make it more compact so that I have only one function. I could not find function that would concat string representation of each byte at the end of ByteToHex.
I tried Array.concat, concat_map, I tried with lists, but the best I could get is array or list of strings.

Questions:

  1. What would be simplest, most elegant way to do this?
  2. Is there string formatting construct in F# so that I can replace String.Format from System assembly?

Example input: [|0x24uy; 0xA1uy; 0x00uy; 0x1Cuy|] should produce string "24A1001C"

Upvotes: 7

Views: 6270

Answers (7)

Wizr
Wizr

Reputation: 165

BitConverter has better performance

BitConverter.ToString(str).Replace("-", "")
Method Mean Error StdDev
BitConverter 398.4 ns 3.33 ns 2.78 ns
concat 1,465.9 ns 13.52 ns 10.56 ns
fold 1,497.6 ns 10.23 ns 8.54 ns
StringBuilder 1,805.8 ns 14.92 ns 13.23 ns

Upvotes: 0

jbtule
jbtule

Reputation: 31799

If you want to transform and accumulate in one step, fold is your answer. sprintf is the F# string format function.

let ByteToHex (bytes:byte[]) =
    bytes |> Array.fold (fun state x-> state + sprintf "%02X" x) ""

This can also be done with a StringBuilder

open System.Text

let ByteToHex (bytes:byte[]) =
        (StringBuilder(), bytes)
        ||> Array.fold (fun state -> sprintf "%02X" >> state.Append)  
        |> string

produces:

[|0x24uy; 0xA1uy; 0x00uy; 0x1Cuy|] |> ByteToHex;;
val it : string = "24A1001C"

Upvotes: 5

Tom
Tom

Reputation: 21

Here's another. I'm learning F#, so feel free to correct me with more idiomatic ways of doing this:

let bytesToHexString (bytes : byte[]) : string =
    bytes
    |> Seq.map (fun c -> c.ToString("X2"))
    |> Seq.reduce (+)

Upvotes: 2

Henrik
Henrik

Reputation: 9915

Here's another answer:

let hashFormat (h : byte[]) =
  let sb = StringBuilder(h.Length * 2)
  let rec hashFormat' = function
    | _ as currIndex when currIndex = h.Length -> sb.ToString()
    | _ as currIndex ->
      sb.AppendFormat("{0:X2}", h.[currIndex]) |> ignore
      hashFormat' (currIndex + 1)
  hashFormat' 0

The upside of this one is that it's tail-recursive and that it pre-allocates the exact amount of space in the string builder as will be required to convert the byte array to a hex-string.

For context, I have it in this module:

module EncodingUtils

open System
open System.Text
open System.Security.Cryptography
open Newtonsoft.Json

let private hmacmd5 = new HMACMD5()
let private encoding = System.Text.Encoding.UTF8
let private enc (str : string) = encoding.GetBytes str
let private json o = JsonConvert.SerializeObject o
let md5 a = a |> (json >> enc >> hmacmd5.ComputeHash >> hashFormat)

Meaning I can pass md5 any object and get back a JSON hash of it.

Upvotes: 3

nlucaroni
nlucaroni

Reputation: 47934

Looks fine to me. Just to point out another, in my opinion, very helpful function in the Printf module, have a look at ksprintf. It passes the result of a formated string into a function of your choice (in this case, the identity function).

val ksprintf : (string -> 'd) -> StringFormat<'a,'d> -> 'a  
    sprintf, but call the given 'final' function to generate the result.

Upvotes: 1

JaredPar
JaredPar

Reputation: 754585

There is nothing inherently wrong with your example. If you'd like to get it down to a single expression then use the String.contcat method.

let ByteToHex bytes = 
    bytes 
    |> Array.map (fun (x : byte) -> System.String.Format("{0:X2}", x))
    |> String.concat System.String.Empty

Under the hood, String.concat will just call into String.Join. Your code may have to be altered slighly though because based on your sample you import System. This may create a name resolution conflict between F# String and System.String.

Upvotes: 11

Marc Gravell
Marc Gravell

Reputation: 1062610

To be honest, that doesn't look terrible (although I also have very little F# experience). Does F# offer an easy way to iterate (foreach)? If this was C#, I might use something like (where raw is a byte[] argument):

        StringBuilder sb = new StringBuilder();
        foreach (byte b in raw) {
            sb.Append(b.ToString("x2"));
        }
        return sb.ToString()

I wonder how that translates to F#...

Upvotes: -1

Related Questions