检查一个数组是不是是另一个数组的子集
Posted
技术标签:
【中文标题】检查一个数组是不是是另一个数组的子集【英文标题】:Check whether an array is a subset of another检查一个数组是否是另一个数组的子集 【发布时间】:2010-09-24 21:09:30 【问题描述】:知道如何检查该列表是否是另一个列表的子集吗?
具体来说,我有
List<double> t1 = new List<double> 1, 3, 5 ;
List<double> t2 = new List<double> 1, 5 ;
如何使用 LINQ 检查 t2 是否为 t1 的子集?
【问题讨论】:
如果列表已排序(如您的示例所示),这应该可以在 O(n+m) 时间内完成。 【参考方案1】:bool isSubset = !t2.Except(t1).Any();
【讨论】:
我已经创建了扩展方法geekswithblogs.net/mnf/archive/2011/05/13/… @Bul Ikana 这段代码的工作很简单,如果没有为该作业提供 IEqualityComparer,扩展方法在内部调用被覆盖的对象类方法的 Equals 和 GetHashCode。 如果列表长度为n和m,这个算法的时间复杂度是多少? 如果将其归结为一个名为 ContainsAll 的 linq 方法会很好 我想知道交叉路口是否会更快 -t1.Intersect(t2).Count() == t2.Count()
【参考方案2】:
如果使用集合,请使用 HashSet 而不是 List。然后你可以简单地使用IsSubsetOf()
HashSet<double> t1 = new HashSet<double>1,3,5;
HashSet<double> t2 = new HashSet<double>1,5;
bool isSubset = t2.IsSubsetOf(t1);
抱歉,它不使用 LINQ。 :-(
如果您需要使用列表,那么@Jared 的解决方案适用于需要删除任何存在的重复元素的警告。
【讨论】:
没错。你想要一个集合操作,使用为他们设计的类。 Cameron 的解决方案很有创意,但不如 HashSet 清晰/富有表现力。 嗯,我不同意,因为这个问题专门说“使用 LINQ”。 @JaredPar:那又怎样?向某人展示正确的方式不是比他们想走的路更好吗? 一个列表保持它的顺序,但一个集合没有。如果顺序很重要,这将给出不正确的结果。【参考方案3】:这是一个比此处发布的其他解决方案更有效的解决方案,尤其是***解决方案:
bool isSubset = t2.All(elem => t1.Contains(elem));
如果您可以在 t2 中找到一个不在 t1 中的元素,那么您就知道 t2 不是 t1 的子集。与使用 .Except 或 .Intersect 的解决方案不同,这种方法的优点是它全部就地完成,无需分配额外的空间。此外,该解决方案能够在找到违反子集条件的单个元素时立即中断,而其他元素继续搜索。下面是解决方案的最佳长形式,在我的测试中它只比上述速记解决方案快一点。
bool isSubset = true;
foreach (var element in t2)
if (!t1.Contains(element))
isSubset = false;
break;
我对所有解决方案进行了一些基本的性能分析,结果非常惊人。这两个解决方案比 .Except() 和 .Intersect() 解决方案快大约 100 倍,并且不使用额外的内存。
【讨论】:
这正是!t2.Except(t1).Any()
正在做的事情。 Linq 来回工作。 Any()
询问IEnumerable
是否至少有一个元素。在这种情况下,t2.Except(t1)
只发出t2
的第一个元素,它不在t1
中。如果t2
的第一个元素不在t1
中,则运行速度最快,如果t2
的所有元素都在t1
中,则运行时间最长。
在使用某种基准测试时,我发现,当您使用t1=1,2,3,...9999
和t2=9999,9998,99997...9000
时,您会得到以下测量结果:!t2.Except(t1).Any(): 1ms -> t2.All(e => t1.Contains(e)): 702ms
。范围越大,情况就越糟。
这不是 Linq 的工作方式。 t2.Except (t1)
返回的是 IEnumerable
而不是 Collection
。如果您完全迭代它,它只会发出所有可能的项目,例如通过ToArray ()
或ToList ()
或使用foreach
而不会破坏内部。搜索 linq deferred execution 以了解有关该概念的更多信息。
我完全了解延迟执行在 Linq 中的工作原理。您可以随心所欲地推迟执行,但是当您想确定 t2 是否是 t1 的子集时,您需要遍历整个列表才能弄清楚。这个事实是无法回避的。
让我们以您的评论为例 t2=1,2,3,4,5,6,7,8
t1=2,4,6,8
t2.Except(t1)
=> t2 的第一个元素 = 1 => 1的差异> 到 t1 是 1 (检查 2,4,6,8)=> Except()
发出第一个元素 1 => Any()
得到一个元素 = > Any()
结果为真 => 不再检查 t2 中的元素。【参考方案4】:
如果你是单元测试,你也可以使用CollectionAssert.IsSubsetOf 方法:
CollectionAssert.IsSubsetOf(subset, superset);
在上述情况下,这意味着:
CollectionAssert.IsSubsetOf(t2, t1);
【讨论】:
【参考方案5】:@Cameron 作为扩展方法的解决方案:
public static bool IsSubsetOf<T>(this IEnumerable<T> a, IEnumerable<T> b)
return !a.Except(b).Any();
用法:
bool isSubset = t2.IsSubsetOf(t1);
(这与@Michael 博客上发布的类似,但不完全相同)
【讨论】:
【参考方案6】:我花了一些时间使用 BenchmarkDotNet 对这个答案中的 3 个有效解决方案进行基准测试。
!t2.Except(t1).Any()
调用了 except().Any()
t2.IsSubsetOf(t1)
称为HashSet
t2.All(elem => t1.Contains(elem))
调用所有(=>包含)
我改变了 3 个参数:
超集计数 子集长度 项目的可变性所有超集的长度都是随机的,并且包含[0; Variability)
范围内的项目。子集的长度固定,包含[0; Variability)
范围内的项目。
赢家是 All(=>Contains) 方法 - 它有时比使用 HashSet 快 30 倍,比 except().Any() 快 50 倍。
代码可以在github找到
更新
正如@Gert Arnold 在 cmets 中提到的那样,我的基准测试中有一个错误。我在每次迭代中都调用了ToHashSet()
。
因此,我还构建了一组预构建的哈希集,并添加了一个名为 prebuilt HashSet
的纯哈希集测试。
它不是绝对的赢家,有时它会失去All(=>Contains)
算法。在我看来,重点在于超集的可变性(和重复)。当值的可变性较低时 - 哈希集获胜,因为它会将其从数据中删除。
决赛桌在这里:
| Method | SupersetsInIteration | SubsetLength | Variability | Mean | Error | StdDev | Median |
|------------------- |--------------------- |------------- |------------ |--------------:|-------------:|-------------:|--------------:|
| Except().Any() | 1000 | 10 | 100 | 1,485.89 μs | 7.363 μs | 6.887 μs | 1,488.36 μs |
| ToHashSet() | 1000 | 10 | 100 | 1,015.59 μs | 5.099 μs | 4.770 μs | 1,015.43 μs |
| prebuilt HashSet | 1000 | 10 | 100 | 38.76 μs | 0.065 μs | 0.054 μs | 38.78 μs |
| All(=>Contains) | 1000 | 10 | 100 | 105.46 μs | 0.320 μs | 0.267 μs | 105.38 μs |
| Except().Any() | 1000 | 10 | 10000 | 1,912.17 μs | 38.180 μs | 87.725 μs | 1,890.72 μs |
| ToHashSet() | 1000 | 10 | 10000 | 1,038.70 μs | 20.028 μs | 40.459 μs | 1,019.35 μs |
| prebuilt HashSet | 1000 | 10 | 10000 | 28.22 μs | 0.165 μs | 0.155 μs | 28.24 μs |
| All(=>Contains) | 1000 | 10 | 10000 | 81.47 μs | 0.117 μs | 0.109 μs | 81.45 μs |
| Except().Any() | 1000 | 50 | 100 | 4,888.22 μs | 81.268 μs | 76.019 μs | 4,854.42 μs |
| ToHashSet() | 1000 | 50 | 100 | 4,323.23 μs | 21.424 μs | 18.992 μs | 4,315.16 μs |
| prebuilt HashSet | 1000 | 50 | 100 | 186.53 μs | 1.257 μs | 1.176 μs | 186.35 μs |
| All(=>Contains) | 1000 | 50 | 100 | 1,173.37 μs | 2.667 μs | 2.227 μs | 1,173.08 μs |
| Except().Any() | 1000 | 50 | 10000 | 7,148.22 μs | 20.545 μs | 19.218 μs | 7,138.22 μs |
| ToHashSet() | 1000 | 50 | 10000 | 4,576.69 μs | 20.955 μs | 17.499 μs | 4,574.34 μs |
| prebuilt HashSet | 1000 | 50 | 10000 | 33.87 μs | 0.160 μs | 0.142 μs | 33.85 μs |
| All(=>Contains) | 1000 | 50 | 10000 | 131.34 μs | 0.569 μs | 0.475 μs | 131.24 μs |
| Except().Any() | 10000 | 10 | 100 | 14,798.42 μs | 120.423 μs | 112.643 μs | 14,775.43 μs |
| ToHashSet() | 10000 | 10 | 100 | 10,263.52 μs | 64.082 μs | 59.942 μs | 10,265.58 μs |
| prebuilt HashSet | 10000 | 10 | 100 | 1,241.19 μs | 4.248 μs | 3.973 μs | 1,241.75 μs |
| All(=>Contains) | 10000 | 10 | 100 | 1,058.41 μs | 6.766 μs | 6.329 μs | 1,059.22 μs |
| Except().Any() | 10000 | 10 | 10000 | 16,318.65 μs | 97.878 μs | 91.555 μs | 16,310.02 μs |
| ToHashSet() | 10000 | 10 | 10000 | 10,393.23 μs | 68.236 μs | 63.828 μs | 10,386.27 μs |
| prebuilt HashSet | 10000 | 10 | 10000 | 1,087.21 μs | 2.812 μs | 2.631 μs | 1,085.89 μs |
| All(=>Contains) | 10000 | 10 | 10000 | 847.88 μs | 1.536 μs | 1.436 μs | 847.34 μs |
| Except().Any() | 10000 | 50 | 100 | 48,257.76 μs | 232.573 μs | 181.578 μs | 48,236.31 μs |
| ToHashSet() | 10000 | 50 | 100 | 43,938.46 μs | 994.200 μs | 2,687.877 μs | 42,877.97 μs |
| prebuilt HashSet | 10000 | 50 | 100 | 4,634.98 μs | 16.757 μs | 15.675 μs | 4,643.17 μs |
| All(=>Contains) | 10000 | 50 | 100 | 10,256.62 μs | 26.440 μs | 24.732 μs | 10,243.34 μs |
| Except().Any() | 10000 | 50 | 10000 | 73,192.15 μs | 479.584 μs | 425.139 μs | 73,077.26 μs |
| ToHashSet() | 10000 | 50 | 10000 | 45,880.72 μs | 141.497 μs | 125.433 μs | 45,860.50 μs |
| prebuilt HashSet | 10000 | 50 | 10000 | 1,620.61 μs | 3.507 μs | 3.280 μs | 1,620.52 μs |
| All(=>Contains) | 10000 | 50 | 10000 | 1,460.01 μs | 1.819 μs | 1.702 μs | 1,459.49 μs |
| Except().Any() | 100000 | 10 | 100 | 149,047.91 μs | 1,696.388 μs | 1,586.803 μs | 149,063.20 μs |
| ToHashSet() | 100000 | 10 | 100 | 100,657.74 μs | 150.890 μs | 117.805 μs | 100,654.39 μs |
| prebuilt HashSet | 100000 | 10 | 100 | 12,753.33 μs | 17.257 μs | 15.298 μs | 12,749.85 μs |
| All(=>Contains) | 100000 | 10 | 100 | 11,238.79 μs | 54.228 μs | 50.725 μs | 11,247.03 μs |
| Except().Any() | 100000 | 10 | 10000 | 163,277.55 μs | 1,096.107 μs | 1,025.299 μs | 163,556.98 μs |
| ToHashSet() | 100000 | 10 | 10000 | 99,927.78 μs | 403.811 μs | 337.201 μs | 99,812.12 μs |
| prebuilt HashSet | 100000 | 10 | 10000 | 11,671.99 μs | 6.753 μs | 5.986 μs | 11,672.28 μs |
| All(=>Contains) | 100000 | 10 | 10000 | 8,217.51 μs | 67.959 μs | 56.749 μs | 8,225.85 μs |
| Except().Any() | 100000 | 50 | 100 | 493,925.76 μs | 2,169.048 μs | 1,922.805 μs | 493,386.70 μs |
| ToHashSet() | 100000 | 50 | 100 | 432,214.15 μs | 1,261.673 μs | 1,180.169 μs | 431,624.50 μs |
| prebuilt HashSet | 100000 | 50 | 100 | 49,593.29 μs | 75.300 μs | 66.751 μs | 49,598.45 μs |
| All(=>Contains) | 100000 | 50 | 100 | 98,662.71 μs | 119.057 μs | 111.366 μs | 98,656.00 μs |
| Except().Any() | 100000 | 50 | 10000 | 733,526.81 μs | 8,728.516 μs | 8,164.659 μs | 733,455.20 μs |
| ToHashSet() | 100000 | 50 | 10000 | 460,166.27 μs | 7,227.011 μs | 6,760.150 μs | 457,359.70 μs |
| prebuilt HashSet | 100000 | 50 | 10000 | 17,443.96 μs | 10.839 μs | 9.608 μs | 17,443.40 μs |
| All(=>Contains) | 100000 | 50 | 10000 | 14,222.31 μs | 47.090 μs | 44.048 μs | 14,217.94 μs |
【讨论】:
比较不公平。您在测量代码中包含ToHashSet()
。如果您创建散列集并仅比较IsSubsetOf
,结果会大不相同。毫无疑问HashSet
会轻松获胜。
好点,谢谢!我将在全局设置中对准备好的哈希集重新运行测试。我已经将它包含在基准代码中,因为我们通常不会在常见的业务代码中使用这种特定的数据结构,但我会检查一下。【参考方案7】:
基于@Cameron 和@Neil 的答案,我编写了一个扩展方法,它使用与 Enumerable 类相同的术语。
/// <summary>
/// Determines whether a sequence contains the specified elements by using the default equality comparer.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">A sequence in which to locate the values.</param>
/// <param name="values">The values to locate in the sequence.</param>
/// <returns>true if the source sequence contains elements that have the specified values; otherwise, false.</returns>
public static bool ContainsAll<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> values)
return !values.Except(source).Any();
【讨论】:
【参考方案8】:假设我们有 2 个数组,array1
和 array2
,我们想检查 array2
中是否存在所有 array1
值:
array1.All(ar1 => array2.Any(ar2 => ar2.Equals(ar1)));
注意:ar2.Equals(ar1)
如果以其他方式描述相等,则可以替换。
【讨论】:
【参考方案9】:试试这个
static bool IsSubSet<A>(A[] set, A[] toCheck)
return set.Length == (toCheck.Intersect(set)).Count();
这里的想法是 Intersect 只会返回两个数组中的值。此时如果结果集的长度与原始集相同,那么“set”中的所有元素也都在“check”中,因此“set”是“toCheck”的子集
注意:如果“set”有重复项,我的解决方案将不起作用。我不会改变它,因为我不想窃取别人的选票。
提示:我投票支持卡梅伦的答案。
【讨论】:
如果它们确实是集合,则此方法有效,但如果第二个“集合”包含重复元素则无效,因为它实际上是一个列表。您可能希望使用 HashSet这里我们检查子列表(即
t2
)中是否存在不包含在父列表(即t1
)中的任何元素。如果不存在,则该列表是另一个列表的子集
例如:
bool isSubset = !(t2.Any(x => !t1.Contains(x)));
【讨论】:
not Any not
与普通的All
相同。以上是关于检查一个数组是不是是另一个数组的子集的主要内容,如果未能解决你的问题,请参考以下文章
如何检查一个数组是不是是 JavaScript 中另一个数组的子集?