Search code examples
sortingrandomlualua-table

Lua - Sort table and randomize ties


I have a table with two values, one is a name (string and unique) and the other is a number value (in this case hearts). What I want is this: sort the table by hearts but scramble randomly the items when there is a tie (e.g. hearts is equal). By a standard sorting function, in case of ties the order is always the same and I need it to be different every time the sorting function works. This is anexample:

tbl = {{name = "a", hearts = 5}, {name = "b", hearts = 2}, {name = "c", hearts = 6}, {name = "d", hearts = 2}, {name = "e", hearts = 2}, {name = "f", hearts = 7}}
sort1 = function (a, b) return a.hearts > b.hearts end
sort2 = function (a, b)
    if a.hearts ~= b.hearts then return a.hearts > b.hearts
    else return a.name > b.name end
end

table.sort(tbl, sort2)

local s = ""
for i = 1, #tbl do
    s = s .. tbl[i].name .. "(" .. tbl[i].hearts .. ") "
end
print(s)

Now, with the function sort2 I think I quite got the problem. The problem is, what happens when a.hearts == b.hearts? In my code it just orders the ties by their name, not what I want. I have two ideas:

  1. First scramble randomly all the items in the table, then apply sort1.
  2. Add a value to every element of the table, called rnd, that is a random number. Then in sort2, when a.hearts == b.hearts order the items by a.rnd > b.rnd.
  3. In sort2, when a.hearts == b.hearts generate randomly true or false and return it. It doesn't work, and I understand that this happens because the random true/false makes the order function crash since there could be inconsistencies.

I don't like 1 (because I would like to do everything inside the sorting function) and 2 (since it requires to add a value), I would like to do something like 3 but working. The question is: is there a way do to this in a simple manner, and what is an optimal way of doing this? (maybe, method 1 or 2 are optimal and I don't get it).

Bonus question. Moreover, I need to fix an item and sort the others. For example, suppose we want "c" to be first. Is it good to make a separate table with only the items to sort, sort the table and then add the fixed items?


Solution

  • -- example table
    local tbl = {
      { name = "a", hearts = 5 },
      { name = "b", hearts = 2 },
      { name = "c", hearts = 6 },
      { name = "d", hearts = 2 },
      { name = "e", hearts = 2 },
      { name = "f", hearts = 7 },
    }
    
    -- avoid same results on subsequent requests
    math.randomseed( os.time() )
    
    ---
    -- Randomly sort a table
    --
    -- @param tbl Table to be sorted
    -- @param corrections Table with your corrections
    --
    function rnd_sort( tbl, corrections )
       local rnd = corrections or {}
       table.sort( tbl,
          function ( a, b)
             rnd[a.name] = rnd[a.name] or math.random()
             rnd[b.name] = rnd[b.name] or math.random()
             return a.hearts + rnd[a.name] > b.hearts + rnd[b.name]
          end )
    end
    
    ---
    -- Show the values of our table for debug purposes
    --
    function show( tbl )
       local s = ""
       for i = 1, #tbl do
           s = s .. tbl[i].name .. "(" .. tbl[i].hearts .. ") "
       end
       print(s)
    end
    
    for i = 1, 10 do
       rnd_sort(tbl)
       show(tbl)
    end
    
    rnd_sort( tbl, {c=1000000} )  -- now "c" will be the first
    show(tbl)