One of many aspects of a project I am responsible for is generating an abbreviation, that is no more than
n characters, for a given string. We use this in the organization for a couple things, and it only happens once for each "thing", so performance isn't hyper-critical.
An example is something like "The Main Test Company" — when asking for a 3-character abbreviation, I want to be able to generate a string like
MTC. Now this would also be the same string as
My Tiny Cat, but the idea is that a given company can be abbreviated and it can be done safely and effectively.
Also, if the abbreviations aren't large enough for the requested characters (say we request 4 characters from
My Awesome Company) it should, for each abbreviation character, start adding characters to the total (so our example is
I wrote the whole thing in F#, and it seems pretty readable and followable, I'm also including some test strings to verify functionality. Any comments are welcome.
let genAbbr ignoreList padding charCount (str : string) = if str.Length <= charCount then match padding with | Some c -> str |> Seq.append (String(c, charCount - str.Length)) |> Seq.toArray | None -> str.ToCharArray() |> Array.map Char.ToUpper |> String else let words = [|' '|] |> Array.append (str.ToCharArray()) |> Array.fold (fun (acc, cs) c -> match c |> Char.IsUpper, c = ' ', cs with | true, _,  -> (acc, [c]) | true, _, cs -> (cs::acc, [c]) | _, true,  -> (acc, ) | _, true, cs -> (cs::acc, ) | _, _, cs -> (acc, c::cs)) (, ) |> fst |> Array.ofList |> Array.map (Array.ofList >> Array.rev >> String) |> Array.rev |> Array.map (fun s -> s.ToCharArray()) let ignoreWords = words |> Array.filter (fun w -> ignoreList |> Array.contains ((w |> String).ToLower()) |> not) let abbrCs = if ignoreWords |> Array.length >= charCount then ignoreWords |> Array.map Seq.head else if words |> Array.length >= charCount then words |> Array.map Seq.head else let firstCaps = words |> Array.map (fun s -> if s.Length > charCount then s.[0..charCount] else s) |> Array.length printfn "%A %A %A" ignoreWords words firstCaps words |> Array.fold (fun (acc, r) w -> if w.Length > r then (w.[0..(r - 1)]::acc, 1) else (w::acc, r - (w.Length - 1))) (, charCount - firstCaps + 1) |> fst |> Array.ofList |> Array.rev |> Array.concat |> Array.map Char.ToUpper abbrCs |> Array.take charCount |> String
And lastly, some POC's:
["The Main Test Company"; "Main Test Company"; "The Main Company"; "The Company"; "Company"; "MainCompany"; "The MainCompany"; "MainTestCompany"; "SomeRX"; "SomeCompanyT"] |> List.map (genAbbr [|"the"|] None 3) // Should be ["MTC"; "MTC"; "TMC"; "THC"; "COM"; "MAC"; "TMC"; "MTC"; "SRX"; "SCT"]
Thanks for any commentary, and I look forward to this being brutally decimated. :)