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 MYAC
).
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. :)