如何在 C# 中解析字符串并获取随机句子?

How Can I Parse String and Get Random Sentences in C#?

我正在尝试弄清楚如何将这种格式的字符串解析为任意深度的树状数据结构。 然后随机造句

"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"

哪里

, means  or
{ means expand
} means collapse up to parent

例如,我想得到这样的输出:

1) hello world planet.
2) hi earth globe!
3) goodby planet.
and etc. 

我认为这可能是一项复杂的工作,因为我使用了 this 教程,我强烈建议您阅读整个页面以了解其工作原理。

首先,您必须将此“树”作为数组传递。您可以解析字符串,手动设置数组或其他任何内容。这很重要,因为该树模型没有好的模型,所以最好使用已经可用的模型。此外,重要的是,如果您想设置正确的语法,则需要为这些单词添加“权重”并告诉代码如何正确设置以及以什么顺序设置。

这是代码片段:

using System;
using System.Text;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            string tree = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
            string[] words = { "Hello", "Hi", "Hey", "world", "earth", "Goodbye", "farewell", "planet", "rock", "globe" };
            RandomText text = new RandomText(words);
            text.AddContentParagraphs(12, 1, 3, 3, 3);
            string content = text.Content;
            Console.WriteLine(content);
        }
    }

    public class RandomText
    {
        static Random _random = new Random();
        StringBuilder _builder;
        string[] _words;

        public RandomText(string[] words)
        {
            _builder = new StringBuilder();
            _words = words;
        }

        public void AddContentParagraphs(int numberParagraphs, int minSentences,
        int maxSentences, int minWords, int maxWords)
        {
            for (int i = 0; i < numberParagraphs; i++)
            {
                AddParagraph(_random.Next(minSentences, maxSentences + 1),
                     minWords, maxWords);
                _builder.Append("\n\n");
            }
        }

        void AddParagraph(int numberSentences, int minWords, int maxWords)
        {
            for (int i = 0; i < numberSentences; i++)
            {
                int count = _random.Next(minWords, maxWords + 1);
                AddSentence(count);
            }
        }

        void AddSentence(int numberWords)
        {
            StringBuilder b = new StringBuilder();
            // Add n words together.
            for (int i = 0; i < numberWords; i++) // Number of words
            {
                b.Append(_words[_random.Next(_words.Length)]).Append(" ");
            }
            string sentence = b.ToString().Trim() + ". ";
            // Uppercase sentence
            sentence = char.ToUpper(sentence[0]) + sentence.Substring(1);
            // Add this sentence to the class
            _builder.Append(sentence);
        }

        public string Content
        {
            get
            {
                return _builder.ToString();
            }
        }
    }
}

必须解析输入字符串。因为它可以包含嵌套的大括号,所以我们需要一个递归解析器。但首先,我们需要一个数据模型来表示树结构。

我们可以在这棵树中包含三种不同类型的项目:文本、表示序列的列表和表示选择的列表。让我们从这个抽象基础 class:

派生出三个 classes
abstract public class TreeItem
{
    public abstract string GetRandomSentence();
}

TextItem class 只是 return 其文本为“随机句子”:

public class TextItem : TreeItem
{
    public TextItem(string text)
    {
        Text = text;
    }

    public string Text { get; }

    public override string GetRandomSentence()
    {
        return Text;
    }
}

序列连接其项目的文本:

public class SequenceItem : TreeItem
{
    public SequenceItem(List<TreeItem> items)
    {
        Items = items;
    }

    public List<TreeItem> Items { get; }

    public override string GetRandomSentence()
    {
        var sb = new StringBuilder();
        foreach (var item in Items) {
            sb.Append(item.GetRandomSentence());
        }
        return sb.ToString();
    }
}

选择项是唯一使用随机性从列表中随机选择一项的项:

public class ChoiceItem : TreeItem
{
    private static readonly Random _random = new();

    public ChoiceItem(List<TreeItem> items)
    {
        Items = items;
    }

    public List<TreeItem> Items { get; }

    public override string GetRandomSentence()
    {
        int index = _random.Next(Items.Count);
        return Items[index].GetRandomSentence();
    }
}

请注意,序列项和选择项都对它们的项递归调用 GetRandomSentence() 以递归地下降树。


这是简单的部分。现在让我们创建一个解析器。

public class Parser
{
    enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }

    int _index;
    string _definition;
    Token _token;
    string _text; // If token is Token.Text;

    public TreeItem Parse(string definition)
    {
        _index = 0;
        _definition = definition;
        GetToken();
        return Choice();
    }

    private void GetToken()
    {
        if (_index >= _definition.Length) {
            _token = Token.EndOfString;
            return;
        }
        switch (_definition[_index]) {
            case '{':
                _index++;
                _token = Token.LeftBrace;
                break;
            case '}':
                _index++;
                _token = Token.RightBrace;
                break;
            case ',':
                _index++;
                _token = Token.Comma;
                break;
            default:
                int startIndex = _index;
                do {
                    _index++;
                } while (_index < _definition.Length & !"{},".Contains(_definition[_index]));
                _text = _definition[startIndex.._index];
                _token = Token.Text;
                break;
        }
    }

    private TreeItem Choice()
    {
        var items = new List<TreeItem>();
        while (_token != Token.EndOfString && _token != Token.RightBrace) {
            items.Add(Sequence());
            if (_token == Token.Comma) {
                GetToken();
            }
        }
        if (items.Count == 0) {
            return new TextItem("");
        }
        if (items.Count == 1) {
            return items[0];
        }
        return new ChoiceItem(items);
    }

    private TreeItem Sequence()
    {
        var items = new List<TreeItem>();
        while (true) {
            if (_token == Token.Text) {
                items.Add(new TextItem(_text));
                GetToken();
            } else if (_token == Token.LeftBrace) {
                GetToken();
                items.Add(Choice());
                if (_token == Token.RightBrace) {
                    GetToken();
                }
            } else {
                break;
            }
        }
        if (items.Count == 0) {
            return new TextItem("");
        }
        if (items.Count == 1) {
            return items[0];
        }
        return new SequenceItem(items);
    }
}

它包含一个词法分析器,即一种将输入文本拆分为标记的低级机制。我们有四种标记:文本、“{”、“}”和“,”。我们将这些代币表示为

enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }

我们还添加了一个 EndOfString 标记来告诉解析器已到达输入字符串的末尾。当标记为 Text 时,我们将此文本存储在字段 _text 中。词法分析器由 GetToken() 方法实现,该方法没有 return 值,而是设置 _token 字段,以使当前标记在两种解析方法中可用 Choice()Sequence().

一个困难是,当我们遇到一个项目时,我们不知道它是单个项目还是序列或选择的一部分。我们假设整个句子的定义是一个由序列组成的选择,这使得序列优先于选择(就像数学中的“*”优先于“+”)。

ChoiceSequence 都在临时列表中收集项目。如果此列表仅包含一项,则此项将被 returned 而不是选择列表或序列列表。


您可以像这样测试这个解析器:

const string example = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
var parser = new Parser();
var tree = parser.Parse(example);
for (int i = 0; i < 20; i++) {
    Console.WriteLine(tree.GetRandomSentence());
}

输出可能如下所示:

Goodbye rock
Hi earth
Goodbye globe.
Hey world
Goodbye rock
Hi earth
Hey earth
farewell planet
Goodbye globe.
Hey world
Goodbye planet
Hello world
Hello world
Goodbye planet
Hey earth
farewell globe!
Goodbye globe.
Goodbye globe.
Goodbye planet
farewell rock

如果问题是如何解析文本。我想也许你可以使用堆栈来解析它。

"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"

基本上,当您读取一个 char 不是 '}' 时,您将 char 压入堆栈。当你得到一个 '}' 时,你会从堆栈中弹出很多次,直到你到达一个 '{'。 但是它有更多的细节,因为你有一个规则 ',' for OR.

解析就像堆栈计算一样。这就是你处理方程括号的方式。