Sorting Instances by hierarchy and name

I am attempting to sort a table of Instances by their hierarchy and their name. My question is essentially does anybody have a quick way to do this?

As an example let’s say I have a table like this:

{
  Workspace.Baseplate
  Workspace,
  Workspace.Camera,
  Workspace.SpawnLocation.Decal,
  Players,
  Workspace.SpawnLocation,
  Lighting,
}

If I sorted that, it would turn into:

{
  Lighting,
  Players,
  Workspace,
  Workspace.Baseplate,
  Workspace.Camera,
  Workspace.SpawnLocation,
  Workspace.SpawnLocation.Decal,
}

I don’t even know where to begin with this without making a tree for the hierarchy, which seems like a bad solution. Any help or pointers would be appreciated.

1 Like

I haven’t really tested this but you can do something like this


local path={}

local function getpath(ins)
	if not path[ins]then
		path[ins]={}
		local cur=ins
		for i=1,1/0 do
			path[ins][i]=cur
			path[ins][cur]=i
			cur=cur.Parent
			if not cur then break end
		end
	end
	return path[ins]
end

local function lt(a,b)
	local ap=getpath(a)
	local bp=getpath(b)
	if a==b then return false end
	if ap[b]then return false end
	if bp[a]then return true end
	for i=1,#ap do
		if bp[ap[i]]then
			local acur=ap[i-1]
			local bcur=bp[bp[ap[i]]-1]
			return acur.Name<bcur.Name
		end
	end
	local acur=ap[#ap].Name
	local bcur=bp[#bp].Name
	return acur.Name<bcur.Name
end

local t=workspace:GetChildren()
table.sort(t,lt)
for k,v in next,t do
	print(k,v:GetFullName())
end
4 Likes

That works remarkably, but I’m a bit worried about performance. This visibly seized Studio when I ran it over a relatively simple game as a test (the original Happy Home in Robloxia), which has 2911 instances as descendants of Workspace. The upper limit of what I’m anticipating using this for is around 4000, which means I’ll have to do some optimizations or modifications.

It’s a very good base though, so thank you! I’m interested if other people have solutions, so I’ll leave this thread unsolved for now, but if nobody comes up with anything better I’ll be sure to mark yours.

Wouldn’t this work well?

table.sort(instances, function(a,b)
    return a:GetFullName() < b:GetFullName()
end)

Tested code:

Code
local t = {
  workspace.Baseplate,
  workspace,
  workspace.Camera,
  workspace.SpawnLocation.Decal,
  game:GetService('Players'),
  workspace.SpawnLocation,
  game:GetService('Lighting'),
}

table.sort(t, function(a,b)
    return a:GetFullName() < b:GetFullName()
end)

for i, v in pairs(t) do
	print(i, v:GetFullName())
end
Output

1 Lighting
2 Players
3 Workspace
4 Workspace.Baseplate
5 Workspace.Camera
6 Workspace.SpawnLocation
7 Workspace.SpawnLocation.Decal

P.S. huge credit to @wunder_wulfe for suggesting this

5 Likes

I think OP wants to have things that are deeply nested to be ordered last (otherwise they wouldn’t have mentioned hierarchy). e.g workspace.A.B.C.D would come after workspace.Z. Just sorting their full name doesn’t account for this. It’s possible that OP just chose a poor example.

EDIT:

workspace:

image

Results using Acreol’s script:

image

Results using your script:

image

The two scripts produce different results, so one (or both?) of the scripts must be wrong…

I can make it faster if you can tell me some information about the input (and your game hierarchy):
Do parts that need to be sorted change parents often?
Are they created/destroyed often?
What is the median depth of the parts in the game hierarchy? What is the max, and how often does it occur?
How can you provide the input? An array of parts to sort? An (array of) instance(s) whose descendants you want to sort?

Without loss of generality, the initial solution I said could be made faster if you microoptimize and binary search for their closest ancestor (but the binary search would only speed it up if you have really complicated hierarchies)

Edit: Also what will you be doing with the output? Do you need the entire hierarchy sorted? If its the first 100 or something we could partially sort instead

While I don’t think GetFullName should entirely be relied on, it seems the most performant way to do this. With roughly 2800 parts randomly inserted and parented into others;

local function parentDepth(instance)
    local depth = -1
    instance:GetFullName():gsub("%.",function(a) depth = depth + 1 end)
    return depth
end

local function compare(a,b)
    local depth1,depth2 = parentDepth(a),parentDepth(b)
    if depth1 == depth2 then
        return a:GetFullName() < b:GetFullName()
    else
        return depth1 < depth2
    end
end

local t = workspace:GetDescendants()
local start = tick()
table.sort( t, compare )
print("Time taken",tick() - start)
print("For",#t,"instances")
for i = 1,#t do
    print(t[i]:GetFullName())
end

gives me

Time taken 0.14167428016663
For 2865 instances
Workspace.A
Workspace.Baseplate
Workspace.Camera
Workspace.Part (x98)
Workspace.Terrain
Workspace.Z
Workspace.A.F
Workspace.A.Part (x9)
Workspace.Part.Part (x458)
Workspace.Z.B
Workspace.Z.Part (x2)
Workspace.A.F.F
Workspace.A.F.Part (x4)
Workspace.A.Part.Part (x35)
Workspace.Part.Part.Part (x919)
Workspace.Z.B.C
Workspace.Z.B.Part (x4)
Workspace.A.F.Part.Part (x8)
Workspace.A.Part.Part.Part (x61)
Workspace.Part.Part.Part.Part (x1252)
Workspace.Z.B.C.Part (x6)

The main limitation here would be not using any dots in the Instance names.

1 Like

If this is designed for making a tool like an explorer, this can be simplified by only actually sorting the GUIs and doing so when the parent changes. Given that there are list constraints, all that would be required is to change the layer order value and resize any containers. If this is designed for some other purpose, I think there would be ways to just parse them in order rather than sorting them which way be more performant than sorting into a list and then parsing. Doing something such as running a script for each child and storing their children in another array would allow you to execute things in order as you would go down the array by hitting the descendants in order, putting them after any siblings from the initial loop(s).

local printAll = function(parent)
    local children,descendants,immediate
    descendants = {}
    children = parent:GetChildren()
    for i = 1, #children do
        --print(children[i]:GetFullName()) --commented for benchmark
        immediate = children[i]:GetChildren()
        if #immediate > 0 then descendants[#descendants + 1] = immediate end
    end
    while #descendants > 0 do
        children = descendants
        descendants = {}
        for spot = 1, #children do
            for i = 1, #children[spot] do
                --print(children[spot][i]:GetFullName()) --commented for benchmark
                immediate = children[spot][i]:GetChildren()
                if #immediate > 0 then descendants[#descendants + 1] = immediate end
            end
        end
    end
end

This won’t really be alphabetical though as GetChildren outputs based on the order of parenting.
However, this alone without all the printing runs pretty performantly, giving ~0.008999 seconds to go over the entire contents of the workspace in the City template (10696 instances).

1 Like

Unfortunately GetFullName doesn’t escape the names of Instances, so it’s not super usable in this case.

The same problem arises with @TaaRt’s solution, so unfortunately I can’t use either of them (this is for something that people other than me will be using, so I can’t just not put a fullstop in Instance names).


This is indeed for an explorer-style GUI. I’m actually planning to use this sorting to determine the LayoutOrder of the various lines. The other option is manually traversing the game hierarchy and generating the LayoutOrders from there, which might be a better options. I couldn’t compare the two though because I didn’t know how to manage this method.

Layout orders shouldnt be necessary as i think it can be set to sort by naming. Simply just iterate through children and descendants and place a ui list layout inside of each group

1 Like