Browse Source

New Set that use buckets based on typeof and constructor.name.

Herbert Vojčík 11 years ago
parent
commit
163eb672cd
6 changed files with 286 additions and 101 deletions
  1. 1 1
      js/IDE.js
  2. 195 64
      js/Kernel-Collections.js
  3. 3 3
      js/Kernel-Tests.js
  4. 1 1
      st/IDE.st
  5. 84 30
      st/Kernel-Collections.st
  6. 2 2
      st/Kernel-Tests.st

+ 1 - 1
js/IDE.js

@@ -7462,7 +7462,7 @@ selector: "inspectOn:",
 protocol: '*IDE',
 fn: function (anInspector){
 var self=this;
-var variables;
+var variables,i;
 function $Dictionary(){return smalltalk.Dictionary||(typeof Dictionary=="undefined"?nil:Dictionary)}
 return smalltalk.withContext(function($ctx1) { 
 var $1;

+ 195 - 64
js/Kernel-Collections.js

@@ -5298,7 +5298,7 @@ referencedClasses: []
 smalltalk.String.klass);
 
 
-smalltalk.addClass('Set', smalltalk.Collection, ['elements'], 'Kernel-Collections');
+smalltalk.addClass('Set', smalltalk.Collection, ['slowBuckets', 'fastBuckets', 'size'], 'Kernel-Collections');
 smalltalk.Set.comment="I represent an unordered set of objects without duplicates.";
 smalltalk.addMethod(
 smalltalk.method({
@@ -5347,21 +5347,54 @@ selector: "add:",
 protocol: 'adding/removing',
 fn: function (anObject){
 var self=this;
+var bucket;
 return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+bucket=self._bucketOfElement_(anObject);
+$2=_st(bucket)._second();
+if(($receiver = $2) == nil || $receiver == null){
+var obj,slowBucket;
+obj=_st(bucket)._first();
+$ctx1.sendIdx["first"]=1;
+obj;
+slowBucket=_st(bucket)._third();
+slowBucket;
+_st(slowBucket)._indexOf_ifAbsent_(obj,(function(){
+return smalltalk.withContext(function($ctx2) {
+_st(slowBucket)._add_(obj);
+self["@size"]=_st(self["@size"]).__plus((1));
+return self["@size"];
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)})}));
+$1=obj;
+} else {
+var primitiveBucket;
+primitiveBucket=$receiver;
+$1=self._add_in_(_st(bucket)._first(),primitiveBucket);
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"add:",{anObject:anObject,bucket:bucket},smalltalk.Set)})},
+args: ["anObject"],
+source: "add: anObject\x0a\x09| bucket |\x0a\x09bucket := self bucketOfElement: anObject.\x0a\x09^bucket second\x0a\x09\x09ifNil: [\x0a\x09\x09\x09| obj slowBucket |\x0a\x09\x09\x09obj := bucket first.\x0a\x09\x09\x09slowBucket := bucket third.\x0a\x09\x09\x09slowBucket indexOf: obj ifAbsent: [ slowBucket add: obj. size := size + 1 ].\x0a\x09\x09\x09obj ]\x0a\x09\x09ifNotNil: [ :primitiveBucket | self add: bucket first in: primitiveBucket ]",
+messageSends: ["bucketOfElement:", "ifNil:ifNotNil:", "second", "first", "third", "indexOf:ifAbsent:", "add:", "+", "add:in:"],
+referencedClasses: []
+}),
+smalltalk.Set);
 
-		var found, objAsReceiver;
-		objAsReceiver = _st(anObject);
-		for(var i=0; i < self['@elements'].length; i++) {
-			if(objAsReceiver.__eq(self['@elements'][i])) {
-				found = true;
-				break;
-			}
-		}
-		if(!found) {self['@elements'].push(anObject)}
+smalltalk.addMethod(
+smalltalk.method({
+selector: "add:in:",
+protocol: 'private',
+fn: function (anObject,anotherObject){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+
+		if (anotherObject.store[anObject]) { return false; }
+		self['@size']++;
+		return anotherObject.store[anObject] = true;
 	;
-return self}, function($ctx1) {$ctx1.fill(self,"add:",{anObject:anObject},smalltalk.Set)})},
-args: ["anObject"],
-source: "add: anObject\x0a\x09<\x0a\x09\x09var found, objAsReceiver;\x0a\x09\x09objAsReceiver = _st(anObject);\x0a\x09\x09for(var i=0; i < self['@elements'].length; i++) {\x0a\x09\x09\x09if(objAsReceiver.__eq(self['@elements'][i])) {\x0a\x09\x09\x09\x09found = true;\x0a\x09\x09\x09\x09break;\x0a\x09\x09\x09}\x0a\x09\x09}\x0a\x09\x09if(!found) {self['@elements'].push(anObject)}\x0a\x09>",
+return self}, function($ctx1) {$ctx1.fill(self,"add:in:",{anObject:anObject,anotherObject:anotherObject},smalltalk.Set)})},
+args: ["anObject", "anotherObject"],
+source: "add: anObject in: anotherObject\x0a\x09<\x0a\x09\x09if (anotherObject.store[anObject]) { return false; }\x0a\x09\x09self['@size']++;\x0a\x09\x09return anotherObject.store[anObject] = true;\x0a\x09>",
 messageSends: [],
 referencedClasses: []
 }),
@@ -5369,18 +5402,27 @@ smalltalk.Set);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "asArray",
-protocol: 'converting',
-fn: function (){
+selector: "bucketOfElement:",
+protocol: 'private',
+fn: function (anObject){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-var $1;
-$1=_st(self["@elements"])._copy();
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"asArray",{},smalltalk.Set)})},
-args: [],
-source: "asArray\x0a\x09^ elements copy",
-messageSends: ["copy"],
+
+		var prim = anObject == null ? (anObject = nil) : anObject.valueOf();
+		var el, bucket, type;
+		if ((type = typeof prim) === "object") {
+			el = self['@slowBuckets'];
+			type = anObject.constructor && anObject.constructor.name;
+			bucket = el[type];
+			if (!bucket) { bucket = el[type] = []; }
+			return [ anObject, null, bucket ];
+		}
+		return [ prim, self['@fastBuckets'][type] ];
+	;
+return self}, function($ctx1) {$ctx1.fill(self,"bucketOfElement:",{anObject:anObject},smalltalk.Set)})},
+args: ["anObject"],
+source: "bucketOfElement: anObject\x0a\x09<\x0a\x09\x09var prim = anObject == null ? (anObject = nil) : anObject.valueOf();\x0a\x09\x09var el, bucket, type;\x0a\x09\x09if ((type = typeof prim) === \x22object\x22) {\x0a\x09\x09\x09el = self['@slowBuckets'];\x0a\x09\x09\x09type = anObject.constructor && anObject.constructor.name;\x0a\x09\x09\x09bucket = el[type];\x0a\x09\x09\x09if (!bucket) { bucket = el[type] = []; }\x0a\x09\x09\x09return [ anObject, null, bucket ];\x0a\x09\x09}\x0a\x09\x09return [ prim, self['@fastBuckets'][type] ];\x0a\x09>",
+messageSends: [],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5391,14 +5433,20 @@ selector: "collect:",
 protocol: 'enumerating',
 fn: function (aBlock){
 var self=this;
+var collection;
 return smalltalk.withContext(function($ctx1) { 
 var $1;
-$1=_st(self._class())._withAll_(_st(self["@elements"])._collect_(aBlock));
+collection=_st(self._class())._new();
+self._do_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(collection)._add_(_st(aBlock)._value_(each));
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)})}));
+$1=collection;
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"collect:",{aBlock:aBlock},smalltalk.Set)})},
+}, function($ctx1) {$ctx1.fill(self,"collect:",{aBlock:aBlock,collection:collection},smalltalk.Set)})},
 args: ["aBlock"],
-source: "collect: aBlock\x0a\x09^ self class withAll: (elements collect: aBlock)",
-messageSends: ["withAll:", "class", "collect:"],
+source: "collect: aBlock\x0a\x09| collection |\x0a\x09collection := self class new.\x0a\x09self do: [ :each | collection add: (aBlock value: each) ].\x0a\x09^ collection",
+messageSends: ["new", "class", "do:", "add:", "value:"],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5410,13 +5458,24 @@ protocol: 'enumerating',
 fn: function (aBlock,anotherBlock){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-var $1;
-$1=_st(self["@elements"])._detect_ifNone_(aBlock,anotherBlock);
-return $1;
+var $1,$2;
+var $early={};
+try {
+self._do_((function(each){
+return smalltalk.withContext(function($ctx2) {
+$1=_st(aBlock)._value_(each);
+if(smalltalk.assert($1)){
+throw $early=[each];
+};
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)})}));
+$2=_st(anotherBlock)._value();
+return $2;
+}
+catch(e) {if(e===$early)return e[0]; throw e}
 }, function($ctx1) {$ctx1.fill(self,"detect:ifNone:",{aBlock:aBlock,anotherBlock:anotherBlock},smalltalk.Set)})},
 args: ["aBlock", "anotherBlock"],
-source: "detect: aBlock ifNone: anotherBlock\x0a\x09^ elements detect: aBlock ifNone: anotherBlock",
-messageSends: ["detect:ifNone:"],
+source: "detect: aBlock ifNone: anotherBlock\x0a\x09self do: [ :each | (aBlock value: each) ifTrue: [ ^each ] ].\x0a\x09^ anotherBlock value",
+messageSends: ["do:", "ifTrue:", "value:", "value"],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5428,11 +5487,23 @@ protocol: 'enumerating',
 fn: function (aBlock){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-_st(self["@elements"])._do_(aBlock);
+
+		var el, keys, i;
+		el = self['@fastBuckets'];
+		keys = Object.keys(el);
+		for (i = 0; i < keys.length; ++i) {
+			var fastBucket = el[keys[i]], fn = fastBucket.fn, store = Object.keys(fastBucket.store);
+			if (fn) { for (var j = 0; j < store.length; ++j) { aBlock._value_(fn(store[j])); } }
+			else { store._do_(aBlock); }
+		}
+		el = self['@slowBuckets'];
+		keys = Object.keys(el);
+		for (i = 0; i < keys.length; ++i) { el[keys[i]]._do_(aBlock); }
+	;
 return self}, function($ctx1) {$ctx1.fill(self,"do:",{aBlock:aBlock},smalltalk.Set)})},
 args: ["aBlock"],
-source: "do: aBlock\x0a\x09elements do: aBlock",
-messageSends: ["do:"],
+source: "do: aBlock\x0a\x09<\x0a\x09\x09var el, keys, i;\x0a\x09\x09el = self['@fastBuckets'];\x0a\x09\x09keys = Object.keys(el);\x0a\x09\x09for (i = 0; i < keys.length; ++i) {\x0a\x09\x09\x09var fastBucket = el[keys[i]], fn = fastBucket.fn, store = Object.keys(fastBucket.store);\x0a\x09\x09\x09if (fn) { for (var j = 0; j < store.length; ++j) { aBlock._value_(fn(store[j])); } }\x0a\x09\x09\x09else { store._do_(aBlock); }\x0a\x09\x09}\x0a\x09\x09el = self['@slowBuckets'];\x0a\x09\x09keys = Object.keys(el);\x0a\x09\x09for (i = 0; i < keys.length; ++i) { el[keys[i]]._do_(aBlock); }\x0a\x09>",
+messageSends: [],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5443,14 +5514,42 @@ selector: "includes:",
 protocol: 'testing',
 fn: function (anObject){
 var self=this;
+var bucket;
 return smalltalk.withContext(function($ctx1) { 
-var $1;
-$1=_st(self["@elements"])._includes_(anObject);
+var $2,$3,$4,$1;
+bucket=self._bucketOfElement_(anObject);
+$2=_st(bucket)._second();
+if(($receiver = $2) == nil || $receiver == null){
+$3=_st(bucket)._third();
+$4=_st(bucket)._first();
+$ctx1.sendIdx["first"]=1;
+$1=_st($3)._includes_($4);
+} else {
+var primitiveBucket;
+primitiveBucket=$receiver;
+$1=self._includes_in_(_st(bucket)._first(),primitiveBucket);
+};
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"includes:",{anObject:anObject},smalltalk.Set)})},
+}, function($ctx1) {$ctx1.fill(self,"includes:",{anObject:anObject,bucket:bucket},smalltalk.Set)})},
 args: ["anObject"],
-source: "includes: anObject\x0a\x09^ elements includes: anObject",
-messageSends: ["includes:"],
+source: "includes: anObject\x0a\x09| bucket |\x0a\x09bucket := self bucketOfElement: anObject.\x0a\x09^bucket second\x0a\x09\x09ifNil: [ bucket third includes: bucket first ]\x0a\x09\x09ifNotNil: [ :primitiveBucket | self includes: bucket first in: primitiveBucket ]",
+messageSends: ["bucketOfElement:", "ifNil:ifNotNil:", "second", "includes:", "third", "first", "includes:in:"],
+referencedClasses: []
+}),
+smalltalk.Set);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "includes:in:",
+protocol: 'private',
+fn: function (anObject,anotherObject){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return !!anotherObject.store[anObject];
+return self}, function($ctx1) {$ctx1.fill(self,"includes:in:",{anObject:anObject,anotherObject:anotherObject},smalltalk.Set)})},
+args: ["anObject", "anotherObject"],
+source: "includes: anObject in: anotherObject\x0a\x09<return !!anotherObject.store[anObject]>",
+messageSends: [],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5463,11 +5562,11 @@ fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 smalltalk.Set.superclass.fn.prototype._initialize.apply(_st(self), []);
-self["@elements"]=[];
+self._removeAll();
 return self}, function($ctx1) {$ctx1.fill(self,"initialize",{},smalltalk.Set)})},
 args: [],
-source: "initialize\x0a\x09super initialize.\x0a\x09elements := #()",
-messageSends: ["initialize"],
+source: "initialize\x0a\x09super initialize.\x0a\x09self removeAll",
+messageSends: ["initialize", "removeAll"],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5502,32 +5601,56 @@ smalltalk.Set);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "remove:",
+selector: "remove:ifAbsent:",
 protocol: 'adding/removing',
-fn: function (anObject){
+fn: function (anObject,aBlock){
 var self=this;
+var bucket;
 return smalltalk.withContext(function($ctx1) { 
-_st(self["@elements"])._remove_(anObject);
-return self}, function($ctx1) {$ctx1.fill(self,"remove:",{anObject:anObject},smalltalk.Set)})},
-args: ["anObject"],
-source: "remove: anObject\x0a\x09elements remove: anObject",
-messageSends: ["remove:"],
+var $2,$3,$4,$5,$1;
+var $early={};
+try {
+bucket=self._bucketOfElement_(anObject);
+$2=_st(bucket)._second();
+if(($receiver = $2) == nil || $receiver == null){
+$3=_st(bucket)._third();
+$4=_st(bucket)._first();
+$ctx1.sendIdx["first"]=1;
+_st($3)._remove_ifAbsent_($4,(function(){
+return smalltalk.withContext(function($ctx2) {
+$5=_st(aBlock)._value();
+throw $early=[$5];
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)})}));
+self["@size"]=_st(self["@size"]).__minus((1));
+$1=self["@size"];
+} else {
+var primitiveBucket;
+primitiveBucket=$receiver;
+$1=self._remove_in_(_st(bucket)._first(),primitiveBucket);
+};
+return $1;
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+}, function($ctx1) {$ctx1.fill(self,"remove:ifAbsent:",{anObject:anObject,aBlock:aBlock,bucket:bucket},smalltalk.Set)})},
+args: ["anObject", "aBlock"],
+source: "remove: anObject ifAbsent: aBlock\x0a\x09| bucket |\x0a\x09bucket := self bucketOfElement: anObject.\x0a\x09^bucket second\x0a\x09\x09ifNil: [ bucket third remove: bucket first ifAbsent: [ ^aBlock value ]. size := size - 1 ]\x0a\x09\x09ifNotNil: [ :primitiveBucket | self remove: bucket first in: primitiveBucket ]",
+messageSends: ["bucketOfElement:", "ifNil:ifNotNil:", "second", "remove:ifAbsent:", "third", "first", "value", "-", "remove:in:"],
 referencedClasses: []
 }),
 smalltalk.Set);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "remove:ifAbsent:",
-protocol: 'adding/removing',
-fn: function (anObject,aBlock){
+selector: "remove:in:",
+protocol: 'private',
+fn: function (anObject,anotherObject){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-_st(self["@elements"])._remove_ifAbsent_(anObject,aBlock);
-return self}, function($ctx1) {$ctx1.fill(self,"remove:ifAbsent:",{anObject:anObject,aBlock:aBlock},smalltalk.Set)})},
-args: ["anObject", "aBlock"],
-source: "remove: anObject ifAbsent: aBlock\x0a\x09elements remove: anObject ifAbsent: aBlock",
-messageSends: ["remove:ifAbsent:"],
+if (delete anotherObject.store[anObject]) self['@size']--;
+return self}, function($ctx1) {$ctx1.fill(self,"remove:in:",{anObject:anObject,anotherObject:anotherObject},smalltalk.Set)})},
+args: ["anObject", "anotherObject"],
+source: "remove: anObject in: anotherObject\x0a\x09<if (delete anotherObject.store[anObject]) self['@size']-->",
+messageSends: [],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5539,11 +5662,19 @@ protocol: 'adding/removing',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-_st(self["@elements"])._removeAll();
+
+		self['@slowBuckets'] = Object.create(null);
+		self['@fastBuckets'] = {
+			'boolean': { store: Object.create(null), fn: Boolean },
+			'number': { store: Object.create(null), fn: Number },
+			'string': { store: Object.create(null) }
+		};
+		self['@size'] = 0;
+	;
 return self}, function($ctx1) {$ctx1.fill(self,"removeAll",{},smalltalk.Set)})},
 args: [],
-source: "removeAll\x0a\x09elements removeAll",
-messageSends: ["removeAll"],
+source: "removeAll\x0a\x09<\x0a\x09\x09self['@slowBuckets'] = Object.create(null);\x0a\x09\x09self['@fastBuckets'] = {\x0a\x09\x09\x09'boolean': { store: Object.create(null), fn: Boolean },\x0a\x09\x09\x09'number': { store: Object.create(null), fn: Number },\x0a\x09\x09\x09'string': { store: Object.create(null) }\x0a\x09\x09};\x0a\x09\x09self['@size'] = 0;\x0a\x09>",
+messageSends: [],
 referencedClasses: []
 }),
 smalltalk.Set);
@@ -5583,12 +5714,12 @@ fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 var $1;
-$1=_st(self["@elements"])._size();
+$1=self["@size"];
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"size",{},smalltalk.Set)})},
 args: [],
-source: "size\x0a\x09^ elements size",
-messageSends: ["size"],
+source: "size\x0a\x09^ size",
+messageSends: [],
 referencedClasses: []
 }),
 smalltalk.Set);

+ 3 - 3
js/Kernel-Tests.js

@@ -5704,13 +5704,13 @@ _st(set)._add_((3));
 $ctx1.sendIdx["add:"]=4;
 $9=_st(set)._printString();
 $ctx1.sendIdx["printString"]=5;
-self._assert_equals_($9,"a Set ('foo' 3)");
+self._assert_equals_($9,"a Set (3 'foo')");
 $ctx1.sendIdx["assert:equals:"]=5;
 _st(set)._add_((3));
-self._assert_equals_(_st(set)._printString(),"a Set ('foo' 3)");
+self._assert_equals_(_st(set)._printString(),"a Set (3 'foo')");
 return self}, function($ctx1) {$ctx1.fill(self,"testPrintString",{set:set},smalltalk.SetTest)})},
 args: [],
-source: "testPrintString\x0a\x09| set |\x0a\x09set := Set new.\x0a\x09self assert: set printString equals: 'a Set ()'.\x0a\x09set add: 1; add: 3.\x0a\x09self assert: set printString equals: 'a Set (1 3)'.\x0a\x09set add: 'foo'.\x0a\x09self assert: set printString equals: 'a Set (1 3 ''foo'')'.\x0a\x09set remove: 1; remove: 3.\x0a\x09self assert: set printString equals: 'a Set (''foo'')'.\x0a\x09set add: 3.\x0a\x09self assert: set printString equals: 'a Set (''foo'' 3)'.\x0a\x09set add: 3.\x0a\x09self assert: set printString equals: 'a Set (''foo'' 3)'",
+source: "testPrintString\x0a\x09| set |\x0a\x09set := Set new.\x0a\x09self assert: set printString equals: 'a Set ()'.\x0a\x09set add: 1; add: 3.\x0a\x09self assert: set printString equals: 'a Set (1 3)'.\x0a\x09set add: 'foo'.\x0a\x09self assert: set printString equals: 'a Set (1 3 ''foo'')'.\x0a\x09set remove: 1; remove: 3.\x0a\x09self assert: set printString equals: 'a Set (''foo'')'.\x0a\x09set add: 3.\x0a\x09self assert: set printString equals: 'a Set (3 ''foo'')'.\x0a\x09set add: 3.\x0a\x09self assert: set printString equals: 'a Set (3 ''foo'')'",
 messageSends: ["new", "assert:equals:", "printString", "add:", "remove:"],
 referencedClasses: ["Set"]
 }),

+ 1 - 1
st/IDE.st

@@ -2377,7 +2377,7 @@ inspectOn: anInspector
 !Set methodsFor: '*IDE'!
 
 inspectOn: anInspector
-	| variables |
+	| variables i |
 	variables := Dictionary new.
 	variables at: '#self' put: self.
 	elements withIndexDo: [ :each :i |

+ 84 - 30
st/Kernel-Collections.st

@@ -1732,7 +1732,7 @@ randomNotIn: aString
 ! !
 
 Collection subclass: #Set
-	instanceVariableNames: 'elements'
+	instanceVariableNames: 'slowBuckets fastBuckets size'
 	package: 'Kernel-Collections'!
 !Set commentStamp!
 I represent an unordered set of objects without duplicates.!
@@ -1740,35 +1740,42 @@ I represent an unordered set of objects without duplicates.!
 !Set methodsFor: 'accessing'!
 
 size
-	^ elements size
+	^ size
 ! !
 
 !Set methodsFor: 'adding/removing'!
 
 add: anObject
-	<
-		var found, objAsReceiver;
-		objAsReceiver = _st(anObject);
-		for(var i=0; i < self['@elements'].length; i++) {
-			if(objAsReceiver.__eq(self['@elements'][i])) {
-				found = true;
-				break;
-			}
-		}
-		if(!!found) {self['@elements'].push(anObject)}
-	>
-!
-
-remove: anObject
-	elements remove: anObject
+	| bucket |
+	bucket := self bucketOfElement: anObject.
+	^bucket second
+		ifNil: [
+			| obj slowBucket |
+			obj := bucket first.
+			slowBucket := bucket third.
+			slowBucket indexOf: obj ifAbsent: [ slowBucket add: obj. size := size + 1 ].
+			obj ]
+		ifNotNil: [ :primitiveBucket | self add: bucket first in: primitiveBucket ]
 !
 
 remove: anObject ifAbsent: aBlock
-	elements remove: anObject ifAbsent: aBlock
+	| bucket |
+	bucket := self bucketOfElement: anObject.
+	^bucket second
+		ifNil: [ bucket third remove: bucket first ifAbsent: [ ^aBlock value ]. size := size - 1 ]
+		ifNotNil: [ :primitiveBucket | self remove: bucket first in: primitiveBucket ]
 !
 
 removeAll
-	elements removeAll
+	<
+		self['@slowBuckets'] = Object.create(null);
+		self['@fastBuckets'] = {
+			'boolean': { store: Object.create(null), fn: Boolean },
+			'number': { store: Object.create(null), fn: Number },
+			'string': { store: Object.create(null) }
+		};
+		self['@size'] = 0;
+	>
 ! !
 
 !Set methodsFor: 'comparing'!
@@ -1780,24 +1787,34 @@ removeAll
 	^ true
 ! !
 
-!Set methodsFor: 'converting'!
-
-asArray
-	^ elements copy
-! !
-
 !Set methodsFor: 'enumerating'!
 
 collect: aBlock
-	^ self class withAll: (elements collect: aBlock)
+	| collection |
+	collection := self class new.
+	self do: [ :each | collection add: (aBlock value: each) ].
+	^ collection
 !
 
 detect: aBlock ifNone: anotherBlock
-	^ elements detect: aBlock ifNone: anotherBlock
+	self do: [ :each | (aBlock value: each) ifTrue: [ ^each ] ].
+	^ anotherBlock value
 !
 
 do: aBlock
-	elements do: aBlock
+	<
+		var el, keys, i;
+		el = self['@fastBuckets'];
+		keys = Object.keys(el);
+		for (i = 0; i < keys.length; ++i) {
+			var fastBucket = el[keys[i]], fn = fastBucket.fn, store = Object.keys(fastBucket.store);
+			if (fn) { for (var j = 0; j < store.length; ++j) { aBlock._value_(fn(store[j])); } }
+			else { store._do_(aBlock); }
+		}
+		el = self['@slowBuckets'];
+		keys = Object.keys(el);
+		for (i = 0; i < keys.length; ++i) { el[keys[i]]._do_(aBlock); }
+	>
 !
 
 select: aBlock
@@ -1813,7 +1830,7 @@ select: aBlock
 
 initialize
 	super initialize.
-	elements := #()
+	self removeAll
 ! !
 
 !Set methodsFor: 'printing'!
@@ -1828,10 +1845,47 @@ printOn: aStream
 	aStream nextPutAll: ')'
 ! !
 
+!Set methodsFor: 'private'!
+
+add: anObject in: anotherObject
+	<
+		if (anotherObject.store[anObject]) { return false; }
+		self['@size']++;
+		return anotherObject.store[anObject] = true;
+	>
+!
+
+bucketOfElement: anObject
+	<
+		var prim = anObject == null ? (anObject = nil) : anObject.valueOf();
+		var el, bucket, type;
+		if ((type = typeof prim) === "object") {
+			el = self['@slowBuckets'];
+			type = anObject.constructor && anObject.constructor.name;
+			bucket = el[type];
+			if (!!bucket) { bucket = el[type] = []; }
+			return [ anObject, null, bucket ];
+		}
+		return [ prim, self['@fastBuckets'][type] ];
+	>
+!
+
+includes: anObject in: anotherObject
+	<return !!!!anotherObject.store[anObject]>
+!
+
+remove: anObject in: anotherObject
+	<if (delete anotherObject.store[anObject]) self['@size']-->
+! !
+
 !Set methodsFor: 'testing'!
 
 includes: anObject
-	^ elements includes: anObject
+	| bucket |
+	bucket := self bucketOfElement: anObject.
+	^bucket second
+		ifNil: [ bucket third includes: bucket first ]
+		ifNotNil: [ :primitiveBucket | self includes: bucket first in: primitiveBucket ]
 ! !
 
 Object subclass: #Queue

+ 2 - 2
st/Kernel-Tests.st

@@ -1590,9 +1590,9 @@ testPrintString
 	set remove: 1; remove: 3.
 	self assert: set printString equals: 'a Set (''foo'')'.
 	set add: 3.
-	self assert: set printString equals: 'a Set (''foo'' 3)'.
+	self assert: set printString equals: 'a Set (3 ''foo'')'.
 	set add: 3.
-	self assert: set printString equals: 'a Set (''foo'' 3)'
+	self assert: set printString equals: 'a Set (3 ''foo'')'
 !
 
 testUnboxedObjects