如何在C#中对网站URL列表进行排序?

9

我有一个站点URL列表:

  • /node1
  • /node1/sub-node1
  • /node2
  • /node2/sub-node1

该列表以随机顺序给出,我需要对其进行排序,使得顶级节点首先出现,然后是子级节点等等(因为我不能创建/node2/sub-node1而没有/node2的存在)。有没有一种简洁的方法来做到这一点?

现在我只是进行递归调用,如果我不能创建sub-node1,因为node2已经存在,则创建node2。我想让列表的顺序决定创建顺序,并摆脱我的递归调用。


2
如果您能写下您已经尝试过的内容,那会很有帮助。 - It'sNotALie.
如果您在排序之前先反转每个字符串会怎样呢? - Reactgular
“create node2” 是什么意思?也许有一种实现方式,甚至不需要在列表中使用根级别... - BrunoLM
7个回答

6

我的第一个想法是按字符串长度排序...但后来我想到了这样的列表,它可能包括一些短名称的别名:

/longsitename/
/a
/a/b/c/
/a
/a/b/
/otherlongsitename/

...然后我认为更好的选择是首先按层级分隔符字符的数量进行排序:

IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
    return urls.OrderBy(s => s.Count(c => c == '/')).ThenBy(s => s);
}

我再仔细想了想,看到你问题中的这一行:

如果没有 /node2 ,我就无法创建 /node2/sub-node1

哦!节点的顺序或是同一节点内部的顺序并不重要,只要子节点总是在父节点之后。有了这个想法,我原来的想法就可以了,按照字符串长度排序就可以了:

IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
    return urls.OrderBy(s => s.Length);
}

最终使我想知道为什么我关心字符串的长度?如果我只是按顺序排列这些字符串,而不考虑它们的长度,那么具有相同开头的字符串将始终首先排序短字符串。因此,最终:

IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
    return urls.OrderBy(s => s);
}

我会保留第一个示例,因为如果将来需要更加词汇或逻辑排序的话,它可能会有用。


@newStackExchangeInstance 噢,抱歉。现在已经修复了。 - Joel Coehoorn
+1 同样的想法,但你首先提供了基本相同的代码示例。 - jerry
刚注意到你关于别名的评论。有趣的想法,但我认为任何类型的别名都可能在任意别名下崩溃。唯一的解决方法是识别和替换别名。 - jerry

2

有没有一种简洁的方法来做到这一点?

只需使用标准字符串排序对URI列表进行排序即可获得所需结果。通常,在字符串排序中,“a”将在“aa”之前排序,因此“/node1”应该排在“/node1/sub-node”之前。

例如:

List<string> test = new List<string> { "/node1/sub-node1", "/node2/sub-node1", "/node1",  "/node2"  };

foreach(var uri in test.OrderBy(s => s))
   Console.WriteLine(uri);

这将打印出:
/node1
/node1/sub-node1
/node2
/node2/sub-node1

好的,如果有一个 /node10 呢? - It'sNotALie.
1
@newStackExchangeInstance会没问题,因为他只关心/node10/node10/somethingelse之前的情况,因为这是用于文件夹构建的。 - Reed Copsey
它仍然会有一个不合逻辑的排序。 - It'sNotALie.
1
OP试图解决的问题是确保/node10/node10/sub-node1之前,从他所做的评论中可以看出,如果单个目录层次结构内的项目逻辑有序,那么/node10/node1之前并不重要。根项的顺序是无关紧要的。 - Scott Chamberlain

2
也许这对您有用:
var nodes = new[] { "/node1", "/node1/sub-node1", "/node2", "/node2/sub-node1" };
var orderedNodes = nodes
    .Select(n => new { Levels = Path.GetFullPath(n).Split('\\').Length, Node = n })
    .OrderBy(p => p.Levels).ThenBy(p => p.Node);

结果:

foreach(var nodeInfo in orderedNodes)
{
    Console.WriteLine("Path:{0} Depth:{1}", nodeInfo.Node, nodeInfo.Levels);
}

Path:/node1 Depth:2
Path:/node2 Depth:2
Path:/node1/sub-node1 Depth:3
Path:/node2/sub-node1 Depth:3

这不是他要找的...他需要下一个节点之前节点的所有子节点。此外,他的分隔符字符是 / 而不是 '\\' - It'sNotALie.
@newStackExchangeInstance他并不需要在下一个节点之前获取一个节点的所有子节点。他所需要的只是在父节点之后列出子节点。除了这个规则之外,其他事情可以混乱无序。 - Joel Coehoorn
不过我仍然不会使用这个。.Split() 操作速度较慢。 - Joel Coehoorn
哦,还要添加RemoveEmptyEntries字符串拆分选项。 - Joel Coehoorn

0

如果您需要在所有第二级节点之前获取所有第一级节点,那么请按斜杠/的数量进行排序:

string[] array = {"/node1","/node1/sub-node1", "/node2", "/node2/sub-node1"};

array = array.OrderBy(s => s.Count(c => c == '/')).ToArray();

foreach(string s in array)
    System.Console.WriteLine(s);

结果:

/node1
/node2
/node1/sub-node1
/node2/sub-node1

如果您只需要在子节点之前获取父节点,那么这个过程就非常简单了。
Array.Sort(array);

结果:

/node1
/node1/sub-node1
/node2
/node2/sub-node1

0
var values = new string[]{"/node1", "/node1/sub-node1" ,"/node2", "/node2/sub-node1"};
foreach(var val in values.OrderBy(e => e))
{
    Console.WriteLine(val);
}

看起来我和别人同时在打字。 - Casey

0
最好使用自然排序,因为您的字符串混合了字符串和数字。因为如果您使用其他排序方法或技术,并且您有像这个例子一样的情况:
List<string> test = new List<string> { "/node1/sub-node1" ,"/node13","/node10","/node2/sub-node1", "/node1", "/node2" };

输出结果将是:

/node1
/node1/sub-node1
/node10
/node13
/node2
/node2/sub-node1

这个没有排序。

你可以查看这个实现


0

递归实际上是你应该使用的,因为这最容易用树形结构表示。

public class PathNode {
    public readonly string Name;
    private readonly IDictionary<string, PathNode> _children;

    public PathNode(string name) {
        Name = name;
        _children = new Dictionary<string, PathNode>(StringComparer.InvariantCultureIgnoreCase);
    }

    public PathNode AddChild(string name) {
        PathNode child;

        if (_children.TryGetValue(name, out child)) {
            return child;
        }

        child = new PathNode(name);

        _children.Add(name, child);

        return child;
    }

    public void Traverse(Action<PathNode> action) {
        action(this);

        foreach (var pathNode in _children.OrderBy(kvp => kvp.Key)) {
            pathNode.Value.Traverse(action);
        }
    }
}

之后你可以像这样使用:

var root = new PathNode(String.Empty);

var links = new[] { "/node1/sub-node1", "/node1", "/node2/sub-node-2", "/node2", "/node2/sub-node-1" };

foreach (var link in links) {
    if (String.IsNullOrWhiteSpace(link)) {
        continue;
    }

    var node = root;

    var lastIndex = link.IndexOf("/", StringComparison.InvariantCultureIgnoreCase);

    if (lastIndex < 0) {
        node.AddChild(link);
        continue;
    }

    while (lastIndex >= 0) {
        lastIndex = link.IndexOf("/", lastIndex + 1, StringComparison.InvariantCultureIgnoreCase);

        node = node.AddChild(lastIndex > 0 
            ? link.Substring(0, lastIndex) // Still inside the link 
            : link // No more slashies
        );
    }
}

var orderedLinks = new List<string>();

root.Traverse(pn => orderedLinks.Add(pn.Name));

foreach (var orderedLink in orderedLinks.Where(l => !String.IsNullOrWhiteSpace(l))) {
    Console.Out.WriteLine(orderedLink);
}

应该打印:

/node1
/node1/sub-node1
/node2
/node2/sub-node-1
/node2/sub-node-2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接