1 /**
2 
3  Set implemented as hash table
4 
5  Inherits @nogc and @safe properties from key properties.
6 
7  Implements next set ops
8   $(UL create - fill set from range)
9   $(UL add - add item to set; O(1))
10   $(UL remove - remove item from set if present; O(1))
11   $(UL length - number of items in set; O(1))
12   $(UL join - join sets; O(N))
13   $(UL intersection - create intersection of two sets; O(N))
14   $(UL difference - create difference of two sets; O(N))
15   $(UL iterate - create iterator over set items;)
16   $(UL in - if element presented in set; O(1))
17 */
18 module cachetools.containers.set;
19 
20 import std.algorithm;
21 import std.range;
22 
23 import cachetools.containers.hashmap;
24 
25 ///
26 /// create set from input range
27 ///
28 auto set(R)(R r) if (isInputRange!R) {
29     alias K = ElementType!R;
30     Set!K result;
31     r.each!(k => result.add(k));
32     return result;
33 }
34 
35 /// Set structure
36 struct Set(K) {
37 private:
38         HashMap!(K, bool) _map;
39 
40 public:
41 
42     /// Fill set from range
43     void create(R)(R range) {
44         _map.clear;
45         range.each!(k => _map.put(k, true));
46     }
47     /// add element to set
48     void add(K)(K k) {
49         _map.put(k, true);
50     }
51     /// remove element from set
52     void remove(K)(K k) {
53         _map.remove(k);
54     }
55     /// number of items in set
56     auto length() const {
57         return _map.length;
58     }
59     /// join other set to this set
60     void join(K)(Set!K other) {
61         if ( other.length == 0 ) return;
62 
63         foreach(ref b; other._map._buckets.bs) {
64             if ( b.hash >= ALLOCATED_HASH ) 
65                 _map.put(b.key, true);
66         }
67     }
68     /// create intersection of two sets
69     auto intersection(K)(Set!K other) {
70         Set!K result;
71 
72         if (other.length == 0 || this.length == 0 ) return result;
73 
74         if ( other.length < _map.length ) {
75             foreach (ref bucket; other._map._buckets.bs) {
76                 if ( bucket.hash >= ALLOCATED_HASH && bucket.key in _map )
77                     result.add(bucket.key);
78             }
79         } else {
80             foreach (ref bucket; _map._buckets.bs) {
81                 if (bucket.hash >= ALLOCATED_HASH && bucket.key in other._map)
82                     result.add(bucket.key);
83             }
84         }
85         return result;
86     }
87     /// create difference of two sets
88     auto difference(K)(Set!K other) {
89         Set!K result;
90         if ( other.length == 0 ) return this;
91         foreach (ref bucket; _map._buckets.bs) {
92             if (bucket.hash >= ALLOCATED_HASH && bucket.key !in other._map)
93                 result.add(bucket.key);
94         }
95         return result;
96     }
97     /// iterate over items
98     auto iterate() {
99         return _map.byKey;
100     }
101     /// if element present in set
102     bool opBinaryRight(string op)(K k) inout if (op=="in") {
103         return  k in _map?true:false;
104     }
105 }
106 
107 ///
108 @safe @nogc unittest {
109     import std.stdio;
110 
111     Set!string s;
112     s.add("hello");
113     assert(s.length == 1);
114     assert(equal(s.iterate, only("hello")));
115     s.remove("hello");
116     assert(s.length == 0);
117     s.remove("hello");
118     assert(s.length == 0);
119 
120     s.create(only("hello", "hello", "world"));
121     assert(s.length == 2);
122 
123     s.join(set(only("and", "bye")));
124     assert(s.length == 4);
125 
126     auto other = set(only("and", "bye", "!"));
127     auto cross0 = s.intersection(other);
128     assert("bye" in cross0);
129     assert("!"  !in cross0);
130     auto cross1 = other.intersection(s);
131     assert(cross0.length == cross1.length);
132     assert("and" in cross0 && "and" in cross1);
133     assert("bye" in cross0 && "bye" in cross1);
134 
135     auto nums = set(iota(10));
136     auto someNums = nums.difference(set(only(1,2,3)));
137     assert(0 in someNums);
138     assert(1 !in someNums);
139 
140     bool f(const Set!string s) {
141         return "yes" in s;
142     }
143 
144     Set!string ss;
145     ss.add("yes");
146     f(ss);
147     assert("yes" in ss);
148 }