|
Hi
Thanks both for the speedy replies. That solved my problem, I went with a slight variation of the example on Paul K's blog. I had actually been reading Paul's blog a few days ago and missed that handy example. His solution was very elegant, it took me a some time to figure out how it worked though. Regards Geoff Date: Fri, 24 Aug 2012 17:12:27 -0600 From: hyperhacker@gmail.com To: lua-l@lists.lua.org Subject: Re: Cant think of an elegant lua solution to this little problem On Aug 24, 2012 6:59 PM, "Jeff Smith" <spammealot1@live.co.uk> wrote: > > Hi > > I am trying to sort a big list of arbitrary keyname strings in a table. I am using the standard table sort with a custom sort function. The little snag I hit was with strings like this > > ABC_1 > ABC_2 > ABC_19 > ABC_20 > > I want to do a lexical sort on my strings but for strings like those above, sort in that order, but of course I get > > ABC_1 > ABC_19 > ABC_2 > ABC_20 > > I cant think of a concise Lua way of doing a lexical sort of strings but still manage to get these numeric type examples correct too. Any suggestions please ? > > Regards Geoff > > > I wrote such a function a while ago as part of a yet-unreleased module. Released into public domain. (Sorry if this sends twice. Gmail is being dippy) -- Splits a string into numeric and non-numeric segments. Used by -- comparenatural below. (Private function) -- @param str String to split. -- @param [number='%d'] character class to be considered numbers. -- @param [tonum=tonumber] function to convert strings to numbers. -- @return Table of segments. local function splitatnumbers(str, number, tonum) local piece, start = {}, 1 number = number or '%d' tonum = tonum or tonumber local pattern = '%f[' .. number .. ']' .. number .. '+%f[^' .. number .. ']' while true do --use fronteir pattern ( http://lua-users.org/wiki/FrontierPattern ) to --split string into numeric and non-numeric portions local sp, ep = str:find(pattern, start) if not sp then break end --insert leading portion if not empty if (sp-1) > start then piece[#piece+1] = str:sub(start, sp-1) end piece[#piece+1] = tonum(str:sub(sp, ep)) start = ep+1 end --insert trailing portion if not empty if start < #str then piece[#piece+1] = str:sub(start) end return piece end --- Compares two strings, preserving ordering when the strings contain -- embedded numbers. (e.g. 'foo2' < 'foo10') -- @param str1 string to compare. -- @param str2 string to compare. -- @param number character class to be considered numbers. (default '%d') -- @param tonum function to convert strings to numbers. (default tonumber) -- @return -1 if str1 < str2, 0 if str1 == str2, 1 if str1 > str2. function rena_string.comparenatural(str1, str2, number, tonum) number, tonum = (number or '%d'), (tonum or tonumber) local part1 = splitatnumbers(str1, number, tonum) local part2 = splitatnumbers(str2, number, tonum) --compare portions local len1, len2 = #part1, #part2 for i = 1, math.min(len1, len2) do local num1 = (type(part1[i]) == 'number') local num2 = (type(part2[i]) == 'number') if num1 and not num2 then return -1 --number < string elseif num2 and not num1 then return 1 elseif part1[i] < part2[i] then return -1 elseif part1[i] > part2[i] then return 1 end end if len1 < len2 then return -1 elseif len2 < len1 then return 1 else return 0 end end |