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<IEnumerable<int>> AllCombinations(int n, int k) => from i in Enumerable.Range(0, 1 << n) let r = Enumerable.Range(0, n).Select(j => (j + 1) * ((i & 1 << 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 << N
更好,因为它保持为整数。如果你愿意,可以把它变成一个函数,Func<int, int> pow2 = n => 1 << 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 = 9
和 k = 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的主要内容,如果未能解决你的问题,请参考以下文章