扩展csv字符串中的通配符以生成csv字符串的集合?

时间:2010-12-09 00:13:18

标签: c# linq

我想从单个csv字符串创建一个字符串集合(使用C#和可能的LINQ),其中每个值都是加号或减号后跟一个字符。例如:

"+A,+E,+B,-B,+C,+D"

该字符串还可以包含表示任意字符中的两个的外卡;一个带加号,一个带负号。如果一个字符串确实包含一个外卡我想替换外卡并生成一个没有外卡的字符串列表。比如说我有:

"+A,-A,*"

我想要生成的字符串是:

+A,-A,+A,-A
+A,-A,+B,-B
+A,-A,+C,-C
+A,-A,...
+A,-A,+Z,-Z

同样适用于多个通配符。字符串"*,*"将产生:

+A,-A,+A,-A
+A,-A,+B,-B
+A,-A,+C,-C
+A,-A,...
+A,-A,+Z,-Z
+B,-B,+A,-A
+B,-B,+B,-B
+B,-B,+C,-C
+B,-B,...
+B,-B,+Z,-Z
+C,-C,...

我的直觉告诉我必须有一个简单优雅的解决方案,但它今天在逃避我。有任何想法吗?这似乎是一个利用LINQ的完美算法?谢谢你的帮助!

3 个答案:

答案 0 :(得分:2)

IEnumerable<string> Wildcard = from c in Enumerable.Range(0, 26)
                               let ch = (char)('A' + c)
                               select string.Concat('+', ch, ',', '-', ch);

IEnumerable<string> ExpandLine(string[] xs, int i)
{
    var ys = (xs[i] == "*") ? Wildcard : new[] { xs[i] };

    if (i == xs.Length - 1)
        return ys;
    else
        return from y in ys
               from z in ExpandLine(xs, i + 1)
               select y + "," + z;
}

IEnumerable<string> ExpandLines(IEnumerable<string> xs)
{
    return from x in xs
           from y in ExpandLine(x.Split(','), 0)
           select y;
}

示例:

var result = ExpandLines(new[] { "+A,-A,*" }).ToList();

结果:

+A,-A,+A,-A
+A,-A,+B,-B
+A,-A,+C,-C
     :
     :
+A,-A,+Z,-Z

(26项)


示例2:

var result = ExpandLines(new[] { "+A,-A,*,*" }).ToList();

结果:

+A,-A,+A,-A,+A,-A
+A,-A,+A,-A,+B,-B
+A,-A,+A,-A,+C,-C
        :
        :
+A,-A,+Z,-Z,+X,-X
+A,-A,+Z,-Z,+Y,-Y
+A,-A,+Z,-Z,+Z,-Z

(676项)

答案 1 :(得分:2)

您的问题的一般化是生成符合特定上下文无关语法的每个字符串。我写了一篇关于如何在C#中做这样的事情的九部分系列文章;如果你有这方面更复杂的问题,你可能会感兴趣。

http://blogs.msdn.com/b/ericlippert/archive/tags/grammars/

答案 2 :(得分:1)

这是另一个:

IEnumerable<string> ExpandWildcards(IEnumerable<string> lines)
{
    return lines.SelectMany(ExpandWildcards);
}

IEnumerable<string> ExpandWildcards(string input)
{
    string[] parts = input.Split(',');
    var expanded = parts.Select(ExpandSingleItem);
    return expanded.CartesianProduct().Select(line => line.JoinStrings(","));
}

static readonly string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

IEnumerable<string> ExpandSingleItem(string item)
{
    if (item == "*")
        return _chars.Select(c => string.Format("+{0},-{0}", c));
    return new[] { item };
}

static class Extensions
{
    // CartesianProduct method by Eric Lippert (http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/)
    public 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}));                
    }

    public static string JoinStrings(this IEnumerable<string> strings, string separator)
    {
        return strings.Aggregate(
                        default(StringBuilder),
                        (sb, item) => sb == null
                            ? new StringBuilder(item)
                            : sb.Append(separator).Append(item),
                        sb => sb.ToString());
    }
}

不可否认,它比dtb的解决方案要长一点......但它不是递归的,如果每行有很多项目,这可能很重要。它给出了相同的结果。