Linq查询以获取最多为N的所有数字(正数和负数),总和为数字K

Posted

技术标签:

【中文标题】Linq查询以获取最多为N的所有数字(正数和负数),总和为数字K【英文标题】:Linq query to get all numbers (positive and negative) up to N that sum up to number K 【发布时间】:2021-12-31 15:52:05 【问题描述】:

我有一个特定的任务来制作一个只使用 LINQ 的函数,这对我来说是一个挑战。 问题是这样的:

生成所有 N 个数加和减 (+-) 的表达式,总和为 K。

例如,如果我有 N = 3,K = 0。我应该在某些时候有 8 个数字组合,加号和减号(2 的 n 个组合的幂),并且使用一些 Where 子句,我应该只提取那些总和为 0(即 K)。 所以结果应该是这样的:

-1 -2 +3 = 0;

+1 +2 -3 = 0;

再一次,我只能使用 LINQ 查询,没有别的,而且我无法解决这个问题。有人可以帮忙吗?

【问题讨论】:

嗨。您能否给我们一些关于您迄今为止为尝试解决此问题而构建的 LINQ 查询的指示?只是想感受一下您尝试解决这个问题的程度。 如果您因为使用 LINQ 的要求而无法弄清楚如何做,请暂时放弃该要求,并以任何方式弄清楚如何做。一旦你得到它的工作,然后开始弄清楚如何使用 LINQ 来完成它。如果您手头有解决方案,您将更好地了解问题的本质 当然,到目前为止我尝试了多个查询,我能做到的最好的就是:return Enumerable.Range(0, rows).Select(i => Enumerable.Range(1, n) .Select(j => baseValues[i / (rows / (j * 2)) % 2] * j)) .Where(s => s.Sum() == k);这还不够好,因为有多个结果返回相同的数字。但对于非常小的输入,它会以某种方式起作用。 @FlyDog57 不要误会我的意思,我可以使用通常的 c# 代码解决这样的问题,我的问题是 LINQ,即使我脑子里有多种解决方案,例如,从一个空数组 ,然后用 1, -1 将它们加倍,然后再用 1, -2, 1, 2, -1, -2, - 1, 2 等等,最后提取正确的组合。但问题是我在 LINQ 方面还没有那么丰富的经验,而且我很难想出像上面的解决方案这样的查询。这就是我寻求帮助、提示或任何东西的原因,因为现在,我被困住了 @AQROBY - 请不要在问题本身中发布您的问题的答案。将它们发布为您问题的答案。这就是这个系统的工作原理。 【参考方案1】:

所以问题是关于决定是否应该添加或减去从 1 到 N 的每个整数?

public static IEnumerable<IEnumerable<int>> AllCombinations(int n, int k)
    => Enumerable.Range(0, 1 << n)
        .Select(i =>
            Enumerable.Range(0, n)
                .Select(j => (i & 1 << j) == 0 ? j + 1 : -j - 1)
        )
        .Where(r => r.Sum() == k);    

【讨论】:

非常好。超快,没有创建不必要的对象。 虽然调用者可以第二次枚举内部枚举。与寻找答案的成本相比,成本应该相当低。 流畅的语法也不错public static IEnumerable&lt;IEnumerable&lt;int&gt;&gt; AllCombinations(int n, int k) =&gt; from i in Enumerable.Range(0, 1 &lt;&lt; n) let r = Enumerable.Range(0, n).Select(j =&gt; (j + 1) * ((i &amp; 1 &lt;&lt; j) == 0 ? 1 : -1)) where r.Sum() == k select r;. 太棒了! “ (i & 1 谢谢,这真的很棒,我设法找到了自己的解决方案,但这真的很聪明【参考方案2】:

这是一个骇人听闻的解决方案,仅使用蛮力。我敢肯定可以找到一个最优雅的。对于超过 30 个数字,它也将无法生成结果。

Enumerable
    .Range(0, (int)Math.Pow(2, N))
    .Select(i => Convert.ToString(~i, 2)
        .Reverse()
        .Take(N)
        .Select((c, i) => c == '1' ? i + 1 : -(i + 1))
        .ToList())
    .Where(l => l.Sum() == K)
    .ToList();

一些解释:

正如您所指出的,对于 N 个数字,有 2^N 和 + 或 - 的组合。所以第一步就是构建这2^N个组合。

Enumerable
    .Range(0, (int)Math.Pow(2, N))

构造前 2^N 个数字。让我们考虑在这个例子中 N = 10。

.Select(i => Convert.ToString(~i, 2)

将每个数字转换为它的二进制字符串表示,但反转(0 替换为 1,1 替换为 0)。反转的目的是得到数字的所有 32 位,否则数字 2,即二进制的 10 将由字符串“10”表示。我们想要一个与 N 一样长的字符串,即“0000000010”。有了这个技巧,我们得到的不是“01”,而是“1111111111111111111111111111101”。

然后将该字符串视为一个字符序列。

.Reverse()

该顺序相反:“1011111111111111111111111111111”,

.Take(N)

我们只取前 N 个字符:“1011111111”。

.Select((c, i) => c == '1' ? i + 1 : -(i + 1))
.ToList())

然后我们将每个字符投影为一个数字,即索引 + 1,其符号取决于字符本身:如果等于 '1' 则加,否则为 -。在目前使用的示例中给出:1, -2, 3, 4, 5, 6, 7, 8, 9, 10。

.Where(l => l.Sum() == K)
.ToList();

从那里我们构建了所有可能的序列,因此我们可以对它们求和并过滤总和等于目标的序列。

当然你可以添加一个检查: if |K| > (N^2 + N)/2,不用计算,不会有匹配的……

作为查询,您可以尝试在 Linqpad 中复制/粘贴:

void Main()

    Utils.Calculate(10, 1)
        .Select(e => new 
        
            Sum = e.Sum(),
            Elements = e
        )
        .Dump();


// You can define other methods, fields, classes and namespaces here
public static class Utils

    public static List<List<int>> Calculate(int N, int K)
    
        // Enumerable.Range will throw ArgumentOutOfRangeException 
        // if N <= 0 || N > 30
        
        return Enumerable
            .Range(0, (int)Math.Pow(2, N))
            .Select(i => Convert.ToString(~i, 2)
                .Reverse()
                .Take(N)
                .Select((c, i) => c == '1' ? i + 1 : -(i + 1))
                .ToList())
            .Where(l => l.Sum() == K)
            .ToList();
    

编辑 同样的逻辑也可以写成:

Enumerable.Range(1, (int)Math.Pow(2, N))
    .Select(i => int.MaxValue - i + 1)
    .Select(i => Convert.ToString(i, 2)
        .Skip(31-N)
        .Select((c, i) => c == '1' ? i + 1 : -(i + 1))
        .ToList())
    .Where(l => l.Sum() == K)
    .ToList()

列表的顺序与第一种方法不同,这对结果没有影响。

【讨论】:

(int)Math.Pow(2, N) 太糟糕了。 1 &lt;&lt; N 更好,因为它保持为整数。如果你愿意,可以把它变成一个函数,Func&lt;int, int&gt; pow2 = n =&gt; 1 &lt;&lt; n; 是的,你是对的,我应该使用移位和二的幂之间的关系,忘记了。正如我的介绍所说,我确信还有改进的空间。特别是关于字符串破解。你和@JeremyLakeman 证明了这一点。 非常感谢您的解决方案和解释毕竟是我在最后原帖里加的【参考方案3】:

这是一个相当简单的方法:

int n = 3;
int k = 0;

IEnumerable<int[]> query =
    from i in Enumerable.Range(0, 1 << n)
    let b = Convert.ToString(i, 2).PadLeft(n, '0')
    let r = Enumerable.Range(1, n)
    let d = r.Zip(b, (x, y) => y == '0' ? x : -x).ToArray()
    let s = d.Sum()
    where s == k
    select d;

跑步给我:

1, 2, -3
-1, -2, 3

例如,n = 9k = 33 我得到:

1, 2, 3, 4, 5, -6, 7, 8, 9
1, -2, 3, -4, 5, 6, 7, 8, 9
-1, 2, 3, 4, -5, 6, 7, 8, 9
-1, -2, -3, 4, 5, 6, 7, 8, 9

这样可以避免创建字符串:

IEnumerable<bool> GetBits(int q, int v) =>
    q == 0
    ? Enumerable.Empty<bool>()
    : GetBits(q - 1, v >> 1).StartWith((v & 1) == 1);

int n = 13;
int k = 75;

IEnumerable<IEnumerable<int>> query =
    from i in Enumerable.Range(0, 1 << n)
    let bs = GetBits(n, i)
    let rs = Enumerable.Range(1, n)
    let ds = rs.Zip(bs, (x, y) => y ? x : -x)
    let s = ds.Sum()
    where s == k
    select ds;

【讨论】:

谢谢,毕竟我已经设法找到了自己的解决方案,但这很棒,它确实引导我朝着正确的方向前进【参考方案4】:

感谢任何试图提供帮助的人,不幸的是,对于这个问题,所有的解决方案都太复杂了,所以我设法找到了自己的解决方案,这更简单一些,但你的解决方案引导我朝着正确的方向前进。

IEnumerable<string> seed = new[]  "" ;

var x = Enumerable.Range(0, n).Aggregate(seed, (a, _) => a.
        SelectMany(s => new[]  s + "+", s + "-" ));

return x.Select((a) => a.Select((a, b) => a == '+' ? b + 1 : (b + 1) * -1))
.Where(r => r.Sum() == k);

【讨论】:

请记住,使用此解决方案您正在创建大量字符串 - 这将对 GC 施加压力。 我根据你的方法做了一个替代方案:***.com/a/70148903/259769【参考方案5】:

根据 OP 的回答,这是一个替代方案,它使用 int[] 来建立候选人列表。

int n = 26;
int k = 8;

var sw = Stopwatch.StartNew();

IEnumerable<int[]> seed = new[]  new int[]   ;
var xss =
    Enumerable
        .Range(1, n)
        .Aggregate(
            seed,
            (a, i) => a.SelectMany(s => new[]
            
                s.Append(i).ToArray(),
                s.Append(-i).ToArray(),
            ))
        .Where(xs => xs.Sum() == k)
        .ToArray();

sw.Stop();
sw.ElapsedMilliseconds.Dump();

这比 OP 的字符串构建方法要快。

【讨论】:

以上是关于Linq查询以获取最多为N的所有数字(正数和负数),总和为数字K的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式

正则表达式

正则表达式

正则表达式

正则表达式

常用正则表达式