【问题标题】:How Can I Parse String and Get Random Sentences in C#?如何在 C# 中解析字符串并获取随机句子?
【发布时间】:2021-12-29 12:39:56
【问题描述】:

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

"{{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. 

【问题讨论】:

  • 创建 3/4 数组,从每个数组中随机取一个并连接 3/4 结果?
  • 语法不一致。您允许中心“,”左侧的“Hello world”加上“,”右侧的“行星”,但“,”右侧的“再见行星”。示例 1) 表明该示例等效于 {{Hello,Hi,Hey} {{world,earth},{Goodbye,farewell}} {planet,rock,globe{.,!}}}。示例 3) 建议:{{{Hello,Hi,Hey} {world,earth}},{{Goodbye,farewell} {planet,rock,globe{.,!}}}}。我建议将序列 { ... }{ ... } 优先于 OR { ... },{ ... }。即,{ a }{ b}, {c} 将等效于 ``{{ a }{ b}}, {c}`。
  • 如果逗号的优先级最低,那么在您的示例中,只有单词“globe”可以以点或感叹号结尾。

标签: c# algorithm data-structures tree text-parsing


【解决方案1】:

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

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

abstract public class TreeItem
{
    public abstract string GetRandomSentence();
}

TextItem 类只是将其文本作为“随机句子”返回:

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() 方法实现,该方法没有返回值,而是设置_token 字段,以使当前标记在Choice()Sequence() 两种解析方法中可用。

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

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


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

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());
}

输出可能如下所示:

再见摇滚
你好地球
再见地球。
嘿世界
再见摇滚
你好地球
嘿地球
告别星球
再见地球。
嘿世界
再见星球
你好世界
你好世界
再见星球
嘿地球
告别地球仪!
再见地球。
再见地球。
再见星球
告别摇滚

【讨论】:

  • 谢谢,绝对有效,如何删除重复项?
  • 这就是随机序列的工作原理。但是您可以存储前一条消息并将其与新消息进行比较,然后重复生成句子,直到新消息不同为止。在循环 string previous = null; 之前,然后在 for 循环内 string current; do { current = tree.GetRandomSentence(); } while (current == previous); previous = current; Console.WriteLine(current);。至少这会抑制连续重复。
【解决方案2】:

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

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

这里是sn-p的代码:

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();
            }
        }
    }
}

【讨论】:

  • 我只想要这个字符串 string tree = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}" ;不需要 string[] words = { "Hello", "Hi", "Hey", "world", "earth", "Goodbye", "farewell", "planet", "rock", "globe" };
  • 没有得到,@Shahram。您想专门使用您显示的树型字符串吗?就像我解释的那样,您必须将此数据解析为另一种类型,例如数组。
【解决方案3】:

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

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

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

解析就像是通过堆栈进行计算。这就是您处理等式括号的方式。

【讨论】:

    猜你喜欢
    • 2019-11-14
    • 1970-01-01
    • 2022-11-17
    • 1970-01-01
    • 1970-01-01
    • 2021-02-23
    • 2014-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多