按等级分组

Group hierarchically into levels

我有一个字符串列表,其中包含按升序排列的数字。这些数字的格式如下:

一级的两位数字是静态的。第一级之后的两位数字可以是任意数字,第三级也是如此。它是分层结构的。

如何通过循环列表将这些级别分组?

我会这样做:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.txt";
        static void Main(string[] args)
        {
            string[] lines = File.ReadAllLines(FILENAME);
            List<ParagraphTitles> titles = lines.Select(x => new ParagraphTitles(x)).ToList();

            List<ParagraphTitles> sortedTitles = titles.OrderBy(x => x).ToList();

            ParagraphTitles root = new ParagraphTitles();
            root.CreateTree(sortedTitles, 0);


        }
    }
    public class ParagraphTitles : IComparable<ParagraphTitles>
    {
        public int[] paragraphs { get; set; }
        public string sParagraphs { get; set; }
        public string text { get; set; }
        public List<ParagraphTitles> chapters { get; set; }

        public ParagraphTitles() { }
        public ParagraphTitles(string line)
        {
            int firstSpace = line.IndexOf(' ');
            sParagraphs = line.Substring(0, firstSpace);
            paragraphs = line.Substring(0, firstSpace).Trim().Split(new char[] { '.' }).Select(x => int.Parse(x)).ToArray();
            text = line.Substring(firstSpace + 1);
        }
        public int CompareTo(ParagraphTitles other)
        {
            int minSize = Math.Min(paragraphs.Length, other.paragraphs.Length);

            for (int i = 0; i < minSize; i++)
            {
                if (paragraphs[i] != other.paragraphs[i])
                {
                    return paragraphs[i].CompareTo(other.paragraphs[i]);
                }
            }

            return paragraphs.Length.CompareTo(other.paragraphs.Length);
        }
        public void CreateTree(List<ParagraphTitles> chapters, int level)
        {
            var groups = chapters.GroupBy(x => x.paragraphs[level]).ToList();

            foreach (var group in groups)
            {
                List<ParagraphTitles> orderedChapters = group.OrderBy(x => x.paragraphs.Length).ToList();
                if (this.chapters == null) this.chapters = new List<ParagraphTitles>();
                this.chapters.Add(orderedChapters.First());
                orderedChapters.First().CreateTree(orderedChapters.Skip(1).ToList(), level + 1);
            }
        }

    }
}

这是文件示例

00 The municipality's board Service text
00.01 The municipality's board Keywords Keywords
00.03 International business and the EU Service text Keywords Keywords
00.05 Visits, representation, etc. Keywords Keywords
00.06 Administration of foundations, grants and foundations Legal references Service text Keywords Keywords
00.07 Management principles Legal references Service text Keywords Keywords
00.13 Communication and information services Service text Keywords Keywords
00.14 Citizen Service Keywords Keywords
00.15 Administrative organization Service text Keywords Keywords
00.16 Experimental and development work Service text Keywords Keywords
00.17 Municipal / cross-sectoral cooperation Service text Keywords Keywords
00.18 Task and structural changes in municipalities, overall activities Legal references Service text Keywords Keywords
00.20 History of the municipality / institution, weapons, etc. Keywords Keywords
00.20.00 History of the municipality / institution, weapons, etc. in general [B] Legal references Service text Keywords Keywords
00.20.05 Naming of institutions, schools, etc. [B] Service text Keywords Keywords
00.22 The municipal council, committees, etc. - the municipality's board Service text Keywords Keywords
00.24 Municipal associations, associations, etc. Keywords Keywords
00.30 Budget - the municipality's financial management Legal references Service text Keywords Keywords
00.32 Accounting - the municipality's financial management Legal references Service text Keywords Keywords
00.34 Loans and borrowing - the municipality's financial management Legal references Service text Keywords Keywords
00.36 Mortgage deeds, bonds and shares Keywords Keywords
01 Spatial planning and nature conservation Service text
01.00 Physical planning and nature protection Keywords Keywords
01.01 National Planning Directives, etc. and regional development planning Service text Keywords Keywords
01.02 Municipal planning and local planning Service text Keywords Keywords
01.03 Zoning and land zone administration Legal references Service text Keywords Keywords
01.04 Subdivision conditions and other cadastral conditions Keywords Keywords
01.05 Nature conservation Legal references Service text Keywords Keywords
01.06 Geographical information systems Keywords Keywords
01.07 Holiday home areas Keywords Keywords
01.09 Raw material extraction Service text Keywords Keywords
01.10 Building protection and building preservation Keywords Keywords
01.11 Urban renewal and urban development Legal references Keywords Keywords
01.12 Allotment garden areas Keywords Keywords
01.13 Operation of agricultural land Legal references Keywords Keywords
01.15 Land distribution Service text Keywords Keywords
01.16 Assessment of Impacts on the Environment (EIA) [deleted] Legal references Service text Keywords Keywords
01.24 Coastal protection Legal references Service text Keywords Keywords
02 Byggeri
02.00 Construction Legal references Service text Keywords Keywords
02.01 Land use for development Legal references Keywords Keywords
02.02 The height and distance conditions of the buildings Legal references Keywords Keywords
02.03 Interior design of buildings Legal references Service text Keywords Keywords
02.04 Constructive provisions Keywords Keywords
02.05 Fire conditions Legal references Service text Keywords Keywords
02.08 Installations etc. Legal references Keywords Keywords
02.25 Fire protection measures for chimneys and fireplaces Legal references Keywords Keywords
02.34 Building permit and notification of construction work Legal references Keywords Keywords
03 Boliger
03.00 Boliger Stikord Stikord
03.01 Benyttelse af boliger Lovhenvisninger Servicetekst Stikord Stikord
03.02 Almene boliger, nybyggeri og renovering Lovhenvisninger Servicetekst Stikord Stikord
03.03 Private andelsboliger Lovhenvisninger Servicetekst Stikord Stikord
03.08 Drift, tilsyn og støtte til selvejende ungdomsboliger Lovhenvisninger Stikord Stikord
03.09 Lejeforhold mv. i private boliger Lovhenvisninger Servicetekst Stikord Stikord
03.10 Drift af og tilsyn med almene boliger (ikke-økonomisk) Lovhenvisninger Servicetekst Stikord Stikord
03.11 Økonomisk tilsyn med almene boliger Lovhenvisninger Servicetekst Stikord Stikord
03.12 Almene boliger - tvister mellem lejer og udlejer Lovhenvisninger Servicetekst Stikord Stikord
03.14 Støttede private ungdomsboliger Lovhenvisninger Servicetekst Stikord Stikord
03.16 Energibesparende foranstaltninger i pensionisters boliger [udgår 2021] Servicetekst Stikord Stikord
03.17 Statstilskud til omstilling af ældre boliger til kraftvarme [udgår 2021] Servicetekst Stikord Stikord
03.18 Statstilskud til omstilling af elopvarmede bygninger [udgår 2021] Servicetekst Stikord Stikord
03.22 Friplejeboliger Servicetekst Stikord Stikord
03.25 Boligplacering af flygtninge Lovhenvisninger Servicetekst Stikord Stikord
03.30 Landsbyggefondens årlige ramme til boligsocial indsats Lovhenvisninger Stikord Stikord

因为我太无聊了,所以我已经为您提供了一种解决此类问题的可能性:

支持任意数量 depths/levels 的树,其中每个叶子都可以有数据。

我只能建议你总是自己寻找解决方案,并在解决问题的过程中提出具体问题。

下面源代码中使用的示例数据:

"02" (without quote)
"02.00.05" (without quote)
"02.00.00" (without quote)
"99.99.99" (without quote)
"00.00.00.01" (without quote)

源代码不言自明:

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Whosebug_65742844
{
    /// <summary>
    /// The structure "LevelNode{AnyContentHere}" represents a tree.
    /// <br/>
    /// <br/>Meaning of words:
    /// <br/>Levels = string of levels (2000005) separated by dots (20.00.00.05) :P
    /// <br/>Level = single level without dot that has been taken from Levels.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Entry point for console application.
        /// </summary>
        static void Main()
        {
            // Its purpose: decide in this example where an item gets inserted in implementation of sorted dictionary.
            var comparer = new LevelComparer();

            // Represents the root level (node).
            var rootLevelNode = new LevelNode<AnyContentHere>(comparer);
            // Temporary means in this context, that..
            LevelNode<AnyContentHere> tempLevelNode;

            // this assignment..
            tempLevelNode = rootLevelNode.GetNodeOrCreate("02");
            tempLevelNode.Value = new AnyContentHere();

            // and now by this one.. :)
            tempLevelNode = rootLevelNode.GetNodeOrCreate("02.00.05");
            tempLevelNode.Value = new AnyContentHere();

            // and now by this one..
            tempLevelNode = rootLevelNode.GetNodeOrCreate("02.00.00");
            tempLevelNode.Value = new AnyContentHere();

            // and now by this one..
            tempLevelNode = rootLevelNode.GetNodeOrCreate("99.99.99");
            tempLevelNode.Value = new AnyContentHere();

            // and now by this one.. :)
            tempLevelNode = rootLevelNode.GetNodeOrCreate("00.00.00.01");
            tempLevelNode.Value = new AnyContentHere();

            // Visualizes root level node and its children and their children and th..
            VisualizeLevelNodeAndChildrenRecursively(rootLevelNode);

            ;
        }

        public static void VisualizeLevelNodeAndChildrenRecursively(LevelNode<AnyContentHere> levelNode, int numberOfChild = 0, int indexOfNode = 0)
        {
            Console.WriteLine($"{new string('[=11=]', numberOfChild * 2)}{indexOfNode}: Group {levelNode.Key ?? "<root>"} contains {levelNode.Nodes.Count} direct children");

            foreach (var levelNodeChild in levelNode.Nodes.Values)
            {
                indexOfNode++;
                VisualizeLevelNodeAndChildrenRecursively(levelNodeChild, numberOfChild: numberOfChild + 1, indexOfNode);
                indexOfNode--;
            }
        }
    }

    /// <summary>
    /// Just an abstraction. It represents the root, a node, a leave or in whatever view you want to call it. 
    /// <br/>It can contain a key, a comparer, (child) nodes and a value.
    /// </summary>
    /// <typeparam name="TDerived">Because we need type of THIS as return value of methods below we need to know the dervied type</typeparam>
    /// <typeparam name="TKey">The key type you want to use.</typeparam>
    /// <typeparam name="TValue">The value you want to store.</typeparam>
    public abstract class SpecializedNode<TDerived, TKey, TValue>
        where TDerived : class
        where TKey : notnull
    {
        [MaybeNull, AllowNull]
        public TKey Key { get; }
        public IComparer<TKey> Comparer { get; }
        public SortedDictionary<TKey, TDerived> Nodes { get; }
        [MaybeNull, AllowNull]
        public TValue Value { get; set; }

        public SpecializedNode([AllowNull] TKey key, IComparer<TKey> comparer)
        {
            Nodes = new SortedDictionary<TKey, TDerived>(comparer);
            Key = key;
            Comparer = comparer;
        }

        /// <summary>
        /// Gets exitsing node or creates it iteratively.
        /// </summary>
        public abstract TDerived GetNodeOrCreate(TKey key);
    }

    public class LevelNode<T> : SpecializedNode<LevelNode<T>, string, T>
    {
        protected LevelNode(string key, IComparer<string> comparer)
            : base(key, comparer) { }

        public LevelNode(IComparer<string> comparer)
            : base(default(string), comparer) { }

        /// <summary>
        /// Gets existing node or creates it.
        /// </summary>
        /// <param name="level"></param>
        /// <returns></returns>
        protected LevelNode<T> getNodeOrCreate(string level)
        {
            LevelNode<T> levelNode;

            if (!Nodes.TryGetValue(level, out levelNode!))
            {
                levelNode = new LevelNode<T>(level, Comparer);
                Nodes.Add(level, levelNode);
            }

            return levelNode;
        }

        /// <summary>
        /// Gets exitsing node or creates it iteratively.
        /// </summary>
        /// <param name="levels">String of levels (2000005) separated by dots (20.00.00.05).</param>
        /// <returns>Node of latest level in <paramref name="levels"/>.</returns>
        public override LevelNode<T> GetNodeOrCreate(string levels)
        {
            if (levels == null)
            {
                return this;
            }

            // Because we don't like recursion we want to work iteratively.
            // We begin by assigning start node..
            LevelNode<T>? levelNode = this;
            // start dot index..
            var dotIndex = levels.IndexOf('.');
            // and the to be shortened levels.
            var toBeShortendLevels = levels;

            /// While a dot from left to right has been found in <see cref="toBeShortendLevels"/>,
            /// search for next level node child.
            while (dotIndex != -1)
            {
                // We know next dot, so we cut out everything before dot.
                // This will be our level (see top what "level" means).
                var level = toBeShortendLevels.Substring(0, dotIndex);
                // ITERATIVE:  Now let's get child node.
                levelNode = levelNode.getNodeOrCreate(level);
                // ITERATIVE: Now we cut out everything after the dot and the dot itself.
                toBeShortendLevels = toBeShortendLevels.Substring(dotIndex + 1);
                // ITERATIVE: Let's check for next dot index.
                dotIndex = toBeShortendLevels.IndexOf('.');
            }

            // When the while loop finished we haven't yet the last level node but the node before that.
            // This gets us the last node we want.
            levelNode = levelNode.getNodeOrCreate(toBeShortendLevels);
            return levelNode;
        }
    }

    /// <summary>
    /// This comparer wraps <see cref="Comparer{string}.Default"/> internally.
    /// We can do so, because <see cref="string"/> is a value type and for value
    /// types <see cref="Comparer{T}"/> has been already implemented and is usable
    /// through <see cref="Comparer{T}.Default"/>.
    /// This comparer relies on the fact that comparing level x and y are going
    /// to have SAME length (left filled zeroes).

    /// </summary>
    public class LevelComparer : IComparer<string>
    {
        private Comparer<string> comparer;

        public LevelComparer() =>
            comparer = Comparer<string>.Default;

        /// <summary>
        /// If x greater than y then return 1, if x equals y then return 0 but if x smaller y then return -1.
        /// 
        /// x = 01, y == 00
        /// x = 20, y == 02
        /// ...
        /// => 1
        /// 
        /// x = 02, y == 02
        /// ...
        /// => 0
        /// 
        /// x = 00, y = 05
        /// ...
        /// => -1
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public int Compare([AllowNull] string x, [AllowNull] string y) =>
            comparer.Compare(x, y);
    }

    // Can declare a string inside this class or you remove this and 
    // replace "AnyContentHere" with data type "string" for example.
    public class AnyContentHere { }
}

控制台输出为:

0: Group <root> contains 3 direct children
  1: Group 00 contains 1 direct children
    2: Group 00 contains 1 direct children
      3: Group 00 contains 1 direct children
        4: Group 01 contains 0 direct children
  1: Group 02 contains 1 direct children
    2: Group 00 contains 2 direct children
      3: Group 00 contains 0 direct children
      3: Group 05 contains 0 direct children
  1: Group 99 contains 1 direct children
    2: Group 99 contains 1 direct children
      3: Group 99 contains 0 direct children

我只能建议您复制代码并将其粘贴到新创建的控制台项目中,如果您还不了解代码的工作原理,请设置断点。

当然,我不会让您下班以您实施的方式阅读数据,并将级别(键)和数据(句子)分开。如果需要,我可以建议您 HtmlAgilityPack 解析 html。