为啥许多编程语言中的集合不是真正的集合?
Posted
技术标签:
【中文标题】为啥许多编程语言中的集合不是真正的集合?【英文标题】:Why are sets in many programming languages not really sets?为什么许多编程语言中的集合不是真正的集合? 【发布时间】:2013-09-04 08:23:14 【问题描述】:在几种编程语言中,存在集合集合,它们被认为是有限集合的数学概念的实现。
但是,这不一定是真的,例如在C#
和Java
中,HashSet<T>
的两个实现都允许您添加任何HashSet<T>
集合作为其自身的成员。根据数学集合的现代定义,这是不允许的。
背景:
根据朴素集合论,集合的定义是:
集合是不同对象的集合。
然而,这个定义导致了著名的Russel's Paradox 以及其他悖论。为方便起见,罗素悖论是:
令 R 是所有不属于它们自己的集合的集合。如果 R 不是自身的成员,那么它的定义表明它必须 包含自己,如果它包含自己,那么它与自己的矛盾 定义为不属于自身成员的所有集合的集合。
所以根据现代集合论(见:ZFC),集合的定义是:
集合是不同对象的集合,其中没有一个是集合 自己。
具体来说,这是axiom of regularity 的结果。
那又怎样?这意味着什么?为什么 *** 上有这个问题?
罗素悖论的一个含义是并非所有集合都是集合。此外,这也是数学家放弃将集合定义为通常的英语定义的地方。所以我相信这个问题在一般的编程语言设计方面有很大的分量。
问题:
那么,为什么以某种形式在他们的设计中使用这些原则的编程语言在他们的语言库中实现 Set 时会忽略它呢?
其次,这在数学概念的其他实现中是否常见?
也许我有点挑剔,但如果这些都是 Set 的真正实现,那么为什么会忽略部分定义呢?
更新
添加 C# 和 Java 代码 sn-ps 示例行为:
Java 代码片段:
Set<Object> hashSet = new HashSet<Object>();
hashSet.add(1);
hashSet.add("Tiger");
hashSet.add(hashSet);
hashSet.add('f');
Object[] array = hashSet.toArray();
HashSet<Object> hash = (HashSet<Object>)array[3];
System.out.println("HashSet in HashSet:");
for (Object obj : hash)
System.out.println(obj);
System.out.println("\nPrinciple HashSet:");
for (Object obj : hashSet)
System.out.println(obj);
打印出来的内容:
HashSet in HashSet:
f
1
Tiger
[f, 1, Tiger, (this Collection)]
Principle HashSet:
f
1
Tiger
[f, 1, Tiger, (this Collection)]
C# 代码段:
HashSet<object> hashSet = new HashSet<object>();
hashSet.Add(1);
hashSet.Add("Tiger");
hashSet.Add(hashSet);
hashSet.Add('f');
object[] array = hashSet.ToArray();
var hash = (HashSet<object>)array[2];
Console.WriteLine("HashSet in HashSet:");
foreach (object obj in hash)
Console.WriteLine(obj);
Console.WriteLine("\nPrinciple HashSet:");
foreach (object obj in hashSet)
Console.WriteLine(obj);
打印出来的内容:
HashSet in HashSet:
1
Tiger
System.Collections.Generic.HashSet`1[System.Object]
f
Principle HashSet:
1
Tiger
System.Collections.Generic.HashSet`1[System.Object]
f
更新 2
关于Martijn Courteaux的第二点,它可以以计算效率的名义完成:
我用 C# 制作了两个测试集合。它们是相同的,除了其中一个的 Add 方法 - 我添加了以下检查:if (this != obj)
其中obj
是要添加到集合中的项目。
我分别记录了他们两个要添加 100,000 个随机整数的地方:
检查: ~ 28 毫秒
不检查: ~ 21 毫秒
这是一个相当显着的性能提升。
【问题讨论】:
您可能会惊讶地发现计算机的数字概念也不包括康托尔的无穷大。计算机是有限的;数学不是。 “集合是不同对象的集合,其中没有一个是集合本身。”如果您的集合是 S = HashSetSet<Object>
可以包含自己,因为Set<Object>
是Object
的子类型。只有在没有子类型的语言中(或更一般地,在不同类型的Set
s 没有共同超类型的语言中)类型系统才会阻止集合包含它们自己。
@stark:当然。然而,这与 finite 集合的实现有关。
【参考方案1】:
编程语言集确实不像 ZFC 集,但原因与您想象的完全不同:
你不能通过理解形成一个集合(即所有对象的集合......)。请注意,这已经阻止了所有(我相信)朴素集合论悖论,因此它们是无关紧要的。
它们通常不可能是无限的。
存在不是集合的对象(在 ZFC 中只有 个集合)。
它们通常是可变的(即您可以在集合中添加/删除元素)。
它们包含的对象可以是可变的。
所以答案
那么,为什么以某种形式在他们的设计中使用这些原则的编程语言在他们的语言库中实现 Set 时会忽略它呢?
是语言不使用这些原则。
【讨论】:
对#1 的小问题:您可以在现有集合中形成所有对象的集合(我不了解 Java/C#,但类似于set.filter(predicate)
) ,它反映了 ZFC 的理解公理(因此阻止了悖论)。但是,是的,编程语言并没有(直接)在 ZFC 上找到自己。
@AntalS-Z,我相信这对应于分离的公理(模式),而不是理解的公理。 ZFC 不允许(不受限制的)理解。
@ibid:你是对的,当然。我昨天脑子放了个屁。正如你所说,这是分离(又名 restricted 理解)。
最后,这以最好的方式回答了这个问题——它强调了一个事实,即计算机科学家经常必须根据机器是有限的、什么是实用的等现实做出选择。最终,这些选择通常会导致对纯数学思想的不良代理。【参考方案2】:
嗯,我认为这是因为一些原因:
出于非常特定的编程目的,您可能希望创建一个包含自身的集合。当您这样做时,您并不真正关心集合的数学含义,您只想享受Set
提供的功能:“添加” 元素到集合中而不创建重复条目。 (老实说,我想不出你想要这样做的情况。)
出于性能目的。您想要使用 Set 并让它包含自己的机会非常非常罕见。因此,每次尝试添加元素时检查它是否是集合本身将是对计算能力、能源、时间、性能和环境健康的浪费。
【讨论】:
我对您的第二点进行了性能比较(请参阅我更新的问题)。可能是以表演的名义做的。【参考方案3】:我不能代表 C#,但就 Java 而言,集合就是集合。如果您查看the javadoc for the Set interface,您会看到(强调我的):
注意:如果将可变对象用作集合元素,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是集合中的一个元素,则不指定集合的行为。 此禁令的一个特殊情况是不允许集合将自身作为元素包含在内。
禁令是否被积极执行尚不清楚(例如,将 HashSet 添加到自身不会引发任何异常),但至少有明确的文件表明您不应该尝试,因为这样就会未指定行为。
【讨论】:
HashSet 允许它,尽管文档说它不允许。它甚至在toString
方法中有一个特殊的子句,让它说 (this set)
而不是进入无限递归。
我用一些代码 sn-ps 更新了我的问题。正如@Ghostkeeper 所说,文档似乎没有遵循。
@DerekW 文档规定了接口的合同,并指出如果您不遵守该合同,则行为未指定。我不明白为什么没有明确强制执行它是一个问题:程序员有责任遵循管理他使用的对象的规则。数学也是一样,you can prove that 1=2如果你不遵守规则...
@assylias 好点。我只是认为 HashSet 会明确地强制执行它,因为它实现了该接口并且是官方 java.util 包的一部分。我现在倾向于认为出于性能目的排除了自我添加检查。【参考方案4】:
在java中,集合是数学集合。您可以插入对象,但集合中只能有一个。来自 javadoc 的关于 set 的信息:
“一个不包含重复元素的集合。更正式地说,集合不包含一对元素 e1 和 e2 使得 e1.equals(e2),并且最多包含一个 null 元素。正如其名称所暗示的那样,此接口模拟数学集合抽象。”
来源:http://docs.oracle.com/javase/7/docs/api/
【讨论】:
以上是关于为啥许多编程语言中的集合不是真正的集合?的主要内容,如果未能解决你的问题,请参考以下文章