需要算法来制作简单的程序(句子排列)

时间:2011-04-10 16:23:19

标签: c# .net linq algorithm

我真的不明白如何在C#上创建一个简单的算法来解决我的问题。所以,我们有句子:

{Hello|Hi|Hi-Hi} my {mate|m8|friend|friends}.

所以,我的程序应该会有很多句子看起来像:

Hello my mate.
Hello my m8.
Hello my friend.
Hello my friends.
Hi my mate.
...
Hi-Hi my friends.

我知道,有很多程序可以做到这一点,但我想自己做。当然,它也应该适用于此:

{Hello|Hi|Hi-Hi} my {mate|m8|friend|friends}, {i|we} want to {tell|say} you {hello|hi|hi-hi}.

5 个答案:

答案 0 :(得分:3)

更新我对使用regexen解析如此简单的输入感到不高兴;但我不喜欢在其他答案中找到的手动索引操作丛林。

因此,我使用基于枚举器的扫描程序替换了标记,并使用两个交替的标记状态。输入的复杂性更加合理,并且具有“Linqy”感觉(尽管它确实不是Linq)。我在帖子的最后为感兴趣的读者保留了原始的基于Regex的解析器。


这只是 必须 使用Eric Lippert's/IanG's CartesianProduct Linq extension method解决,其中程序的核心变为:

public static void Main(string[] args)
{
    const string data = @"{Hello|Hi|Hi-Hi} my {mate|m8|friend|friends}, {i|we} want to {tell|say} you {hello|hi|hi-hi}.";
    var pockets = Tokenize(data.GetEnumerator());

    foreach (var result in CartesianProduct(pockets))
        Console.WriteLine(string.Join("", result.ToArray()));
}

只使用两个regexen(chunkslegs)来解析为'pockets',这就变成了将CartesianProduct写入控制台的问题:)这是完整的工作代码(.NET 3.5 +):

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

namespace X 
{ 
    static class Y 
    {
        private static bool ReadTill(this IEnumerator<char> input, string stopChars, Action<StringBuilder> action)
        {
            var sb = new StringBuilder();

            try 
            {
                while (input.MoveNext())
                    if (stopChars.Contains(input.Current))
                        return true;
                    else
                        sb.Append(input.Current);
            } finally 
            {
                action(sb);
            }

            return false;
        }


        private static IEnumerable<IEnumerable<string>> Tokenize(IEnumerator<char> input)
        {
            var result = new List<IEnumerable<string>>();

            while(input.ReadTill("{", sb => result.Add(new [] { sb.ToString() })) &&
                  input.ReadTill("}", sb => result.Add(sb.ToString().Split('|')))) 
            {
                // Console.WriteLine("Expected cumulative results: " + result.Select(a => a.Count()).Aggregate(1, (i,j) => i*j));
            }

            return result;
        }

        public static void Main(string[] args)
        {
            const string data = @"{Hello|Hi|Hi-Hi} my {mate|m8|friend|friends}, {i|we} want to {tell|say} you {hello|hi|hi-hi}.";
            var pockets = Tokenize(data.GetEnumerator());

            foreach (var result in CartesianProduct(pockets))
                Console.WriteLine(string.Join("", result.ToArray()));
        }

        static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) 
        { 
            IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() }; 
            return sequences.Aggregate( 
                    emptyProduct, 
                    (accumulator, sequence) =>  
                    from accseq in accumulator  
                    from item in sequence  
                    select accseq.Concat(new[] {item}));                
        }
    }
}

基于旧正则表达式的解析:

static readonly Regex chunks = new Regex(@"^(?<chunk>{.*?}|.*?(?={|$))+$", RegexOptions.Compiled);
static readonly Regex legs = new Regex(@"^{((?<alternative>.*?)[\|}])+(?<=})$", RegexOptions.Compiled);

private static IEnumerable<String> All(this Regex regex, string text, string group)
{
    return !regex.IsMatch(text) 
                ? new [] { text } 
                : regex.Match(text).Groups[group].Captures.Cast<Capture>().Select(c => c.Value);
}

public static void Main(string[] args)
{
    const string data = @"{Hello|Hi|Hi-Hi} my {mate|m8|friend|friends}, {i|we} want to {tell|say} you {hello|hi|hi-hi}.";
    var pockets = chunks.All(data, "chunk").Select(v => legs.All(v, "alternative"));

其余的没有改变

答案 1 :(得分:2)

不确定您需要Linq(@ user568262)或“简单”递归(@Azad Salahli)。这是我的看法:

using System;
using System.Text;

class Program
{
    static Random rng = new Random();

    static string GetChoiceTemplatingResult(string t)
    {
        StringBuilder res = new StringBuilder();

        for (int i = 0; i < t.Length; ++i)
            if (t[i] == '{')
            {
                int j;
                for (j = i + 1; j < t.Length; ++j)
                    if (t[j] == '}')
                    {
                        if (j - i < 1) continue;
                        var choices = t.Substring(i + 1, j - i - 1).Split('|');
                        res.Append(choices[rng.Next(choices.Length)]);
                        i = j;
                        break;
                    }
                if (j == t.Length)
                    throw new InvalidOperationException("No matching } found.");
            }
            else
                res.Append(t[i]);

        return res.ToString();
    }

    static void Main(string[] args)
    {
        Console.WriteLine(GetChoiceTemplatingResult(
            "{Hello|Hi|Hi-Hi} my {mate|m8|friend|friends}, {i|we} want to {tell|say} you {hello|hi|hi-hi}."));
    }
}

答案 2 :(得分:1)

正如其他人所说,您可以通过将字符串拆分为一系列集合,然后获取所有这些集合的笛卡尔积来解决您的问题。我在这里写了一些关于生成任意Cartesial产品的文章:

http://blogs.msdn.com/b/ericlippert/archive/2010/06/28/computing-a-cartesian-product-with-linq.aspx

另一种比这更强大的方法是为您的语言声明一个语法,然后编写一个程序来生成该语言中的每个字符串。我写了一系列关于如何这样做的文章。它从这里开始:

http://blogs.msdn.com/b/ericlippert/archive/2010/04/26/every-program-there-is-part-one.aspx

答案 3 :(得分:0)

这看起来并不重要。你需要
1.做一些解析,提取你要组合的所有单词列表,
2.获取这些单词的所有实际组合(由于您想要组合的列表数量不固定,因此更加困难)
3.重建原始句子,将所有组合放在他们来自的组中

第1部分(解析部分)可能是最简单的:可以使用像这样的正则表达式来完成

    // get all the text within {} pairs
    var pattern = @"\{(.*?)\}";
    var query = "{Hello|Hi|Hi-Hi} my {mate|m8|friend|friends}.";
    var matches = Regex.Matches(query, pattern);

    // create a List of Lists
    for(int i=0; i< matches.Count; i++)
    {
        var nl = matches[i].Groups[1].ToString().Split('|').ToList();
        lists.Add(nl);
        // build a "template" string like "{0} my {1}"
        query = query.Replace(matches[i].Groups[1].ToString(), i.ToString());
    }

第2部分(获取列表列表并获取所有结果组合),您可以参考this answer

对于第3部分(重建原始句子),您现在可以使用查询中的“模板”字符串,并使用String.Format替换所有{0},{1} .. ..与第2部分的组合值

// just one example, 
// you will need to loop through all the combinations obtained from part 2    
var OneResultingCombination = new List<string>() {"hi", "mate"};
var oneResult = string.Format(query, OneResultingCombination.ToArray());

答案 4 :(得分:0)

您可以使用元组来保存每个集合的索引值。

例如,你会有类似的东西:

List<string> Greetings = new List<string>()
{
    "Hello",
    "Hi",
    "Hallo"
};

List<string> Targets = new List<string>()
{
    "Mate",
    "m8",
    "friend",
    "friends"
};

所以现在你有问候,让我们创建随机数并获取项目。

static void Main(string[] args)
{
    List<string> Greetings = new List<string>()
    {
        "Hello",
        "Hi",
        "Hallo"
    };

    List<string> Targets = new List<string>()
    {
        "Mate",
        "m8",
        "friend",
        "friends"
    };

    var combinations = new List<Tuple<int, int>>();

    Random random = new Random();

    //Say you want 5 unique combinations.
    while (combinations.Count < 6)
    {
        Tuple<int, int> tmpCombination = new Tuple<int, int>(random.Next(Greetings.Count), random.Next(Targets.Count));

        if (!combinations.Contains(tmpCombination))
        {
            combinations.Add(tmpCombination);
        }
    }

    foreach (var item in combinations)
    {
        Console.WriteLine("{0} my {1}", Greetings[item.Item1], Targets[item.Item2]);
    }

    Console.ReadKey();
}