Lua sort table alphabetically, except numbers
Asked Answered
N

3

5

I would want to sort a table alphabetically. Except numbers.

The code below shows how the table is sorted with comparator function:

function( a,b ) return a.N < b.N end

Gives me:

obj = {
    [1] = {
        ["N"] = "Green 1";
    };
    [2] = {
        ["N"] = "Green 11";
    };
    [3] = {
        ["N"] = "Green 2";
    };
    [4] = {
        ["N"] = "Red 1";
    };
}

But I want it to be sorted like this:

obj = {
    [1] = {
        ["N"] = "Green 1";
    };
    [2] = {
        ["N"] = "Green 2";
    };
    [3] = {
        ["N"] = "Green 11";
    };
    [4] = {
        ["N"] = "Red 1";
    };
}

Is it possible?

Nibelungenlied answered 12/1, 2015 at 19:59 Comment(1)
Remove all numbers from the key before you sort? Sort on only the first word? etc. If your table is large or you are going to be doing this often keeping a cache of the "sort key" you generate is likely a good idea too.Saudra
S
3

Was going to post this originally, but the solution posted by lhf answered your question. Since you are still having issues, give the following a try.

local function cmp(a, b)
   a = tostring(a.N)
   b = tostring(b.N)
   local patt = '^(.-)%s*(%d+)$'
   local _,_, col1, num1 = a:find(patt)
   local _,_, col2, num2 = b:find(patt)
   if (col1 and col2) and col1 == col2 then
      return tonumber(num1) < tonumber(num2)
   end
   return a < b
end

local obj = {
   { N = '1'           },
   { N = 'Green1'      }, -- works with optional space
   { N = 'Green'       }, -- works when doesn't fit the format
   { N = 'Sky blue99'  },
   { N = 'Green 11'    },
   { N = 'Green 2'     },
   { N = 'Red 02'      }, -- works when has leading zeros
   { N = 'Red    01'   }, -- works with padding spaces
   { N = 'Sky blue 42' }, -- works with multi-word color names
   { N = 99            }, -- works with numbers
}

table.sort(obj, cmp)
for i,v in ipairs(obj) do
   print(i, v.N)
end

Prints:

1   1
2   99
3   Green
4   Green1
5   Green 2
6   Green 11
7   Red    01
8   Red 02
9   Sky blue 42
10  Sky blue99
Swivet answered 12/1, 2015 at 22:13 Comment(5)
Thanks, this works better. Changed pattern to: '^(%D+)(%d+)$' to allow { N = 'Green1' }, seems to work but may have overlooked something.Nibelungenlied
@Nibelungenlied That pattern looks good and should work as long as there aren't any digits in the color name, (.+) works for any character(s).Swivet
Ok, thanks. If I use '^(.+)(%d+)$' or '^(%g)(%d+)$' it doesn't work.Nibelungenlied
Ah, that worked! Found another problem though, if N = 1 Can work around it, but would be nice if that worked too.Nibelungenlied
@Nibelungenlied Ok, updated again to handle numbers. This is getting outside the scope of your original question, you might want to update it so that your need to handle spaces (or not) and numbers is specified.Swivet
M
6

Try this:

local function split(a)
    local x,y=a.N:match("(%S+)%s+(%S+)")
    return x,tonumber(y)
end

table.sort(obj,
    function (a,b)
        local a1,a2=split(a)
        local b1,b2=split(b)
        return a1<b1 or (a1==b1 and a2<b2)
    end
)
Mages answered 12/1, 2015 at 20:30 Comment(3)
Got a problem though, didn't mention that some strings in table got no number. So i get a error.Nibelungenlied
Get error on this line: return a1<b1 or (a1==b1 and a2<b2)Nibelungenlied
Ok, fixed it with: local function split(a) if string.match(a.N, '%d+') then local x,y=a.N:match("(%D+)(%d+)") return x,tonumber(y) else local x,y = a.N,0 return x,y end endNibelungenlied
J
3

@lhf's solution should work for you, although you may need to consider if you need to handle corner cases, like comparing "Green 1" with "Green 02" or "Green 2" with "Green 02". I've reviewed several methods to implement alphanum sorting and compared their results in a blog post. You may also check the discussion on the lua mail list on this very topic.

Jeanajeanbaptiste answered 12/1, 2015 at 20:41 Comment(0)
S
3

Was going to post this originally, but the solution posted by lhf answered your question. Since you are still having issues, give the following a try.

local function cmp(a, b)
   a = tostring(a.N)
   b = tostring(b.N)
   local patt = '^(.-)%s*(%d+)$'
   local _,_, col1, num1 = a:find(patt)
   local _,_, col2, num2 = b:find(patt)
   if (col1 and col2) and col1 == col2 then
      return tonumber(num1) < tonumber(num2)
   end
   return a < b
end

local obj = {
   { N = '1'           },
   { N = 'Green1'      }, -- works with optional space
   { N = 'Green'       }, -- works when doesn't fit the format
   { N = 'Sky blue99'  },
   { N = 'Green 11'    },
   { N = 'Green 2'     },
   { N = 'Red 02'      }, -- works when has leading zeros
   { N = 'Red    01'   }, -- works with padding spaces
   { N = 'Sky blue 42' }, -- works with multi-word color names
   { N = 99            }, -- works with numbers
}

table.sort(obj, cmp)
for i,v in ipairs(obj) do
   print(i, v.N)
end

Prints:

1   1
2   99
3   Green
4   Green1
5   Green 2
6   Green 11
7   Red    01
8   Red 02
9   Sky blue 42
10  Sky blue99
Swivet answered 12/1, 2015 at 22:13 Comment(5)
Thanks, this works better. Changed pattern to: '^(%D+)(%d+)$' to allow { N = 'Green1' }, seems to work but may have overlooked something.Nibelungenlied
@Nibelungenlied That pattern looks good and should work as long as there aren't any digits in the color name, (.+) works for any character(s).Swivet
Ok, thanks. If I use '^(.+)(%d+)$' or '^(%g)(%d+)$' it doesn't work.Nibelungenlied
Ah, that worked! Found another problem though, if N = 1 Can work around it, but would be nice if that worked too.Nibelungenlied
@Nibelungenlied Ok, updated again to handle numbers. This is getting outside the scope of your original question, you might want to update it so that your need to handle spaces (or not) and numbers is specified.Swivet

© 2022 - 2024 — McMap. All rights reserved.