stringset.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. // Copyright 2009 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Data structure for set of strings.
  16. *
  17. *
  18. * This class implements a set data structure for strings. Adding and removing
  19. * is O(1). It doesn't contain any bloat from {@link goog.structs.Set}, i.e.
  20. * it isn't optimized for IE6 garbage collector (see the description of
  21. * {@link goog.structs.Map#keys_} for details), and it distinguishes its
  22. * elements by their string value not by hash code.
  23. * The implementation assumes that no new keys are added to Object.prototype.
  24. */
  25. goog.provide('goog.structs.StringSet');
  26. goog.require('goog.asserts');
  27. goog.require('goog.iter');
  28. /**
  29. * Creates a set of strings.
  30. * @param {!Array<?>=} opt_elements Elements to add to the set. The non-string
  31. * items will be converted to strings, so 15 and '15' will mean the same.
  32. * @constructor
  33. * @final
  34. */
  35. goog.structs.StringSet = function(opt_elements) {
  36. /**
  37. * An object storing the escaped elements of the set in its keys.
  38. * @type {!Object}
  39. * @private
  40. */
  41. this.elements_ = {};
  42. if (opt_elements) {
  43. for (var i = 0; i < opt_elements.length; i++) {
  44. this.elements_[goog.structs.StringSet.encode_(opt_elements[i])] = null;
  45. }
  46. }
  47. goog.asserts.assertObjectPrototypeIsIntact();
  48. };
  49. /**
  50. * Empty object. Referring to it is faster than creating a new empty object in
  51. * {@code goog.structs.StringSet.encode_}.
  52. * @const {!Object}
  53. * @private
  54. */
  55. goog.structs.StringSet.EMPTY_OBJECT_ = {};
  56. /**
  57. * The '__proto__' and the '__count__' keys aren't enumerable in Firefox, and
  58. * 'toString', 'valueOf', 'constructor', etc. aren't enumerable in IE so they
  59. * have to be escaped before they are added to the internal object.
  60. * NOTE: When a new set is created, 50-80% of the CPU time is spent in encode.
  61. * @param {*} element The element to escape.
  62. * @return {*} The escaped element or the element itself if it doesn't have to
  63. * be escaped.
  64. * @private
  65. */
  66. goog.structs.StringSet.encode_ = function(element) {
  67. return element in goog.structs.StringSet.EMPTY_OBJECT_ ||
  68. String(element).charCodeAt(0) == 32 ?
  69. ' ' + element :
  70. element;
  71. };
  72. /**
  73. * Inverse function of {@code goog.structs.StringSet.encode_}.
  74. * NOTE: forEach would be 30% faster in FF if the compiler inlined decode.
  75. * @param {string} key The escaped element used as the key of the internal
  76. * object.
  77. * @return {string} The unescaped element.
  78. * @private
  79. */
  80. goog.structs.StringSet.decode_ = function(key) {
  81. return key.charCodeAt(0) == 32 ? key.substr(1) : key;
  82. };
  83. /**
  84. * Adds a single element to the set.
  85. * @param {*} element The element to add. It will be converted to string.
  86. */
  87. goog.structs.StringSet.prototype.add = function(element) {
  88. this.elements_[goog.structs.StringSet.encode_(element)] = null;
  89. };
  90. /**
  91. * Adds a the elements of an array to this set.
  92. * @param {!Array<?>} arr The array to add the elements of.
  93. */
  94. goog.structs.StringSet.prototype.addArray = function(arr) {
  95. for (var i = 0; i < arr.length; i++) {
  96. this.elements_[goog.structs.StringSet.encode_(arr[i])] = null;
  97. }
  98. };
  99. /**
  100. * Adds the elements which are in {@code set1} but not in {@code set2} to this
  101. * set.
  102. * @param {!goog.structs.StringSet} set1 First set.
  103. * @param {!goog.structs.StringSet} set2 Second set.
  104. * @private
  105. */
  106. goog.structs.StringSet.prototype.addDifference_ = function(set1, set2) {
  107. for (var key in set1.elements_) {
  108. if (!(key in set2.elements_)) {
  109. this.elements_[key] = null;
  110. }
  111. }
  112. };
  113. /**
  114. * Adds a the elements of a set to this set.
  115. * @param {!goog.structs.StringSet} stringSet The set to add the elements of.
  116. */
  117. goog.structs.StringSet.prototype.addSet = function(stringSet) {
  118. for (var key in stringSet.elements_) {
  119. this.elements_[key] = null;
  120. }
  121. };
  122. /**
  123. * Removes all elements of the set.
  124. */
  125. goog.structs.StringSet.prototype.clear = function() {
  126. this.elements_ = {};
  127. };
  128. /**
  129. * @return {!goog.structs.StringSet} Clone of the set.
  130. */
  131. goog.structs.StringSet.prototype.clone = function() {
  132. var ret = new goog.structs.StringSet;
  133. ret.addSet(this);
  134. return ret;
  135. };
  136. /**
  137. * Tells if the set contains the given element.
  138. * @param {*} element The element to check.
  139. * @return {boolean} Whether it is in the set.
  140. */
  141. goog.structs.StringSet.prototype.contains = function(element) {
  142. return goog.structs.StringSet.encode_(element) in this.elements_;
  143. };
  144. /**
  145. * Tells if the set contains all elements of the array.
  146. * @param {!Array<?>} arr The elements to check.
  147. * @return {boolean} Whether they are in the set.
  148. */
  149. goog.structs.StringSet.prototype.containsArray = function(arr) {
  150. for (var i = 0; i < arr.length; i++) {
  151. if (!(goog.structs.StringSet.encode_(arr[i]) in this.elements_)) {
  152. return false;
  153. }
  154. }
  155. return true;
  156. };
  157. /**
  158. * Tells if this set has the same elements as the given set.
  159. * @param {!goog.structs.StringSet} stringSet The other set.
  160. * @return {boolean} Whether they have the same elements.
  161. */
  162. goog.structs.StringSet.prototype.equals = function(stringSet) {
  163. return this.isSubsetOf(stringSet) && stringSet.isSubsetOf(this);
  164. };
  165. /**
  166. * Calls a function for each element in the set.
  167. * @param {function(string, undefined, !goog.structs.StringSet)} f The function
  168. * to call for every element. It takes the element, undefined (because sets
  169. * have no notion of keys), and the set.
  170. * @param {Object=} opt_obj The object to be used as the value of 'this'
  171. * within {@code f}.
  172. */
  173. goog.structs.StringSet.prototype.forEach = function(f, opt_obj) {
  174. for (var key in this.elements_) {
  175. f.call(opt_obj, goog.structs.StringSet.decode_(key), undefined, this);
  176. }
  177. };
  178. /**
  179. * Counts the number of elements in the set in linear time.
  180. * NOTE: getCount is always called at most once per set instance in google3.
  181. * If this usage pattern won't change, the linear getCount implementation is
  182. * better, because
  183. * <li>populating a set and getting the number of elements in it takes the same
  184. * amount of time as keeping a count_ member up to date and getting its value;
  185. * <li>if getCount is not called, adding and removing elements have no overhead.
  186. * @return {number} The number of elements in the set.
  187. */
  188. goog.structs.StringSet.prototype.getCount = Object.keys ? function() {
  189. return Object.keys(this.elements_).length;
  190. } : function() {
  191. var count = 0;
  192. for (var key in this.elements_) {
  193. count++;
  194. }
  195. return count;
  196. };
  197. /**
  198. * Calculates the difference of two sets.
  199. * @param {!goog.structs.StringSet} stringSet The set to subtract from this set.
  200. * @return {!goog.structs.StringSet} {@code this} minus {@code stringSet}.
  201. */
  202. goog.structs.StringSet.prototype.getDifference = function(stringSet) {
  203. var ret = new goog.structs.StringSet;
  204. ret.addDifference_(this, stringSet);
  205. return ret;
  206. };
  207. /**
  208. * Calculates the intersection of this set with another set.
  209. * @param {!goog.structs.StringSet} stringSet The set to take the intersection
  210. * with.
  211. * @return {!goog.structs.StringSet} A new set with the common elements.
  212. */
  213. goog.structs.StringSet.prototype.getIntersection = function(stringSet) {
  214. var ret = new goog.structs.StringSet;
  215. for (var key in this.elements_) {
  216. if (key in stringSet.elements_) {
  217. ret.elements_[key] = null;
  218. }
  219. }
  220. return ret;
  221. };
  222. /**
  223. * Calculates the symmetric difference of two sets.
  224. * @param {!goog.structs.StringSet} stringSet The other set.
  225. * @return {!goog.structs.StringSet} A new set with the elements in exactly one
  226. * of {@code this} and {@code stringSet}.
  227. */
  228. goog.structs.StringSet.prototype.getSymmetricDifference = function(stringSet) {
  229. var ret = new goog.structs.StringSet;
  230. ret.addDifference_(this, stringSet);
  231. ret.addDifference_(stringSet, this);
  232. return ret;
  233. };
  234. /**
  235. * Calculates the union of this set and another set.
  236. * @param {!goog.structs.StringSet} stringSet The set to take the union with.
  237. * @return {!goog.structs.StringSet} A new set with the union of elements.
  238. */
  239. goog.structs.StringSet.prototype.getUnion = function(stringSet) {
  240. var ret = this.clone();
  241. ret.addSet(stringSet);
  242. return ret;
  243. };
  244. /**
  245. * @return {!Array<string>} The elements of the set.
  246. */
  247. goog.structs.StringSet.prototype.getValues = Object.keys ? function() {
  248. // Object.keys was introduced in JavaScript 1.8.5, Array#map in 1.6.
  249. return Object.keys(this.elements_).map(goog.structs.StringSet.decode_, this);
  250. } : function() {
  251. var ret = [];
  252. for (var key in this.elements_) {
  253. ret.push(goog.structs.StringSet.decode_(key));
  254. }
  255. return ret;
  256. };
  257. /**
  258. * Tells if this set and the given set are disjoint.
  259. * @param {!goog.structs.StringSet} stringSet The other set.
  260. * @return {boolean} True iff they don't have common elements.
  261. */
  262. goog.structs.StringSet.prototype.isDisjoint = function(stringSet) {
  263. for (var key in this.elements_) {
  264. if (key in stringSet.elements_) {
  265. return false;
  266. }
  267. }
  268. return true;
  269. };
  270. /**
  271. * @return {boolean} Whether the set is empty.
  272. */
  273. goog.structs.StringSet.prototype.isEmpty = function() {
  274. for (var key in this.elements_) {
  275. return false;
  276. }
  277. return true;
  278. };
  279. /**
  280. * Tells if this set is the subset of the given set.
  281. * @param {!goog.structs.StringSet} stringSet The other set.
  282. * @return {boolean} Whether this set if the subset of that.
  283. */
  284. goog.structs.StringSet.prototype.isSubsetOf = function(stringSet) {
  285. for (var key in this.elements_) {
  286. if (!(key in stringSet.elements_)) {
  287. return false;
  288. }
  289. }
  290. return true;
  291. };
  292. /**
  293. * Tells if this set is the superset of the given set.
  294. * @param {!goog.structs.StringSet} stringSet The other set.
  295. * @return {boolean} Whether this set if the superset of that.
  296. */
  297. goog.structs.StringSet.prototype.isSupersetOf = function(stringSet) {
  298. return stringSet.isSubsetOf(this);
  299. };
  300. /**
  301. * Removes a single element from the set.
  302. * @param {*} element The element to remove.
  303. * @return {boolean} Whether the element was in the set.
  304. */
  305. goog.structs.StringSet.prototype.remove = function(element) {
  306. var key = goog.structs.StringSet.encode_(element);
  307. if (key in this.elements_) {
  308. delete this.elements_[key];
  309. return true;
  310. }
  311. return false;
  312. };
  313. /**
  314. * Removes all elements of the given array from this set.
  315. * @param {!Array<?>} arr The elements to remove.
  316. */
  317. goog.structs.StringSet.prototype.removeArray = function(arr) {
  318. for (var i = 0; i < arr.length; i++) {
  319. delete this.elements_[goog.structs.StringSet.encode_(arr[i])];
  320. }
  321. };
  322. /**
  323. * Removes all elements of the given set from this set.
  324. * @param {!goog.structs.StringSet} stringSet The set of elements to remove.
  325. */
  326. goog.structs.StringSet.prototype.removeSet = function(stringSet) {
  327. for (var key in stringSet.elements_) {
  328. delete this.elements_[key];
  329. }
  330. };
  331. /**
  332. * Returns an iterator that iterates over the elements in the set.
  333. * NOTE: creating the iterator copies the whole set so use {@link #forEach} when
  334. * possible.
  335. * @param {boolean=} opt_keys Ignored for sets.
  336. * @return {!goog.iter.Iterator} An iterator over the elements in the set.
  337. */
  338. goog.structs.StringSet.prototype.__iterator__ = function(opt_keys) {
  339. return goog.iter.toIterator(this.getValues());
  340. };