# from a list
a = set([1,2,3,4])
a

# using curly braces
a = {1,2,3,4}
a

# using a tuple
a = set((1,2,3,4))
a

# start with and empty set and add elements to it
a = set()
a.add('hello')
a

# be careful in assigning a string as an element to a set. If assigned as below, it'll be broken up and only unique
# characters are added to the set
a = set('this is a string')
a
# this will do the same thing
a = set(('string'))

# if you want to assign a string as an element then do 
a = set()
a.add('string')
a

# or 
a = set(('string',))
a

# or
a = set(['string'])
a

# or 
a = {'string'}
a

# this assignment will not work since we're one element of the set is a list which is a mutable object. 
# A set's elements should be immutable
a = {"a", (1,2), [3,4]}

# uniquness of the elements of a set
a = {1,2,3,3,4,4,7,8,-1,3,4} # we contains only one copy of a repeated element
a

"""
Explanation: Sets & Frozen Sets
A set in python is an unordered sequence of unique and immutable objects, although the set itself is mutable. A frozen set is the same as a set except that the set itself is immutable. Since the set is a collection of unordered elements, indexing is not applicable. 
Constructing sets
End of explanation
"""


A = {1,2,3,4}
A.add('string')
A

# update a set
A.update([26, 12, 9, 14])
print A

# A.discard(x) will remove x from the set. If x is not in the set, nothing happens. 
A = {'a', 'b', 'c', 'd', 'e'}
A.discard('b')
print "A after discarding the element 'b'", A
A.discard('f')
print "A after discarding the element 'f' (which does not belong to A)", A


# A.remove(x) works like A.discard(x) but if x is not in the set A, an KeyError exception is thrown
A = {'a', 'b', 'c', 'd', 'e'}
A.remove('b')
print "A after A.remove('b')", A
A.remove('f') # a KeyError is thrown
print "A after A.remove('f') ('f' does not belong to A)", A

"""
Explanation: Adding, Updating, and Removing elements from a set
End of explanation
"""


A = {'1', '2', '3', 1, 2, 3}
print "Is '1' in A?", '1' in A
print "Is '4' not in A?", '4' not in A
print "Is 5 in A", 5 in A
print "A is %s elements long"%len(A)

"""
Explanation: Set Membership & Length
End of explanation
"""


# A.intersection(B) or A&B retrurns a set with the common elements between A and B.
A = {"a","b","c","d","e"}
B = {"c","d","e","f","g"}
print "The intersection of A and B is ", A.intersection(B)
print "The intersection of A and B using A&B notation is ", A&B

# get the common elements of more than 2 sets
A = set([1,2,3,4,5])
B = set([4,5,6,7,8])
C = set([4,5,1,8,9])
print "First way to get the intersection of 3 sets ", set.intersection(A, B, C)
print "Second wat to get the intersection of 3 sets ", A&B&C

# A.issdisjoint(B) returns True if the two sets have a null intersection, otherwise it returns False
A = {'a', 'b', 'c', 'd', 'e', 'f'}
B = {'c', 'd', 'e'}
C = {'g', 'h', 'i', 'j'}
print "A and B have no common elements? ", A.isdisjoint(B)
print "A and C have no common elements? ", A.isdisjoint(C)

# A.union(B) or A | B returns a set of the merged elements of A and B 
# the same element in both sets is counted once of course
A = set([1,2,3])
B = set([2,3,4])
print "The merging of A and B is ", A.union(B)
print "The merging of A and B using another notation is ", A | B

# A.difference(B) or A-B returns the a set of the difference between A and B.
# This can be chanied to include many sets
A = {'a', 'b', 'c', 'd', 'e'}
B = {'c', 'd', 'g', 'h'}
C = {'d', 'b', 'f'}
print "The elements that are in A but not in B", A.difference(B)
print "The elemets that are in A but not in B or C", A.difference(B).difference(C)
print "The elements that are in A but not in B", A-B
print "The elemets that are in A but not in B or C", A-B-C

# A.difference_update(B) or A = A - B find the difference between A and B and assigns it back to A. 
# In other words, it removes the elements from A that are in B and assigns it to A. 
A = {'a', 'b', 'c', 'd', 'e'}
B = {'c', 'd', 'g', 'h'}
A.difference_update(B)
print "A after A.difference_update(B)",  A
A = {'a', 'b', 'c', 'd', 'e'}
B = {'c', 'd', 'g', 'h'}
A = A - B
print "A after A=A-B",  A

"""
Explanation: Set Intersection, Disjoint, Union, and Difference
End of explanation
"""


A = {'a', 'b', 'c', 'd', 'e', 'f'}
B = {'c', 'd', 'e'}
print "Is A a subset of B? ", A.issubset(B)
print "Is A a superset of B? ", A.issuperset(B)
print "Is B a subset of A? ", B.issubset(A)
print "Is B a superset of A? ", B.issuperset(A)

# using the other notation
print "Is A a subset of B? ", A <= B
print "Is A a superset of B? ", A >= B
print "Is B a subset of A? ", B <= A
print "Is B a superset of A? ", B >= A
print "Is B a superset of B? ", B >= B
print "Is B a subset of B? ", B <= B

print "Is A a proper subset of B? ", A < B 
print "Is B a proper subset of A? ", B < A 

print "Is A a proper superset of B? ", A > B 
print "Is B a proper superset of A? ", B > A 

print "Is B a proper subset of B? ", B < B
print "Is B a proper superset of B? ", B > B

# pop() removes and returns an arbitrary set element. The method raises a KeyError if the set is empty
x = {"a","b","c","d","e"}
x.pop()
x.pop()


"""
Explanation: Subsets and supersets
A.issubset(B) returns True if A is a subset of B. A <= B is another way of writing it.<br>
A.issuperset(B) returns True of A is a superset of B. A >= is another way of writing it.
"<" is used to check if a set is a proper subset of another set. A proper subset B of the set A means that all the elements of B are in A but A has at least one more element that is not in B. Formally: a proper subset of a set A is a subset of A that is not equal to A.
">" is used to check if a set is a proper superset of another set. A set A that is a proper superset of B means that all elements of B are in A but A contains at least one element that is not in B. Formally: A proper superset of a set A is a superset of A that is not equal to A.
End of explanation
"""


A = set([1, 2, 3])
B = set([3, 4, 5])
print "Symmetric diffrence between A and B ", A.symmetric_difference(B)
print "Symmetric difference between A and B using A ^ B is ", A ^ B 
A.symmetric_difference_update(B) # this assigns the results of symmetric_difference to A
print "After symmetric_difference_update A is ", A

"""
Explanation: Symmetric difference
The symmetric difference of two sets is the set of elements which are in one of either set, but not in both.
End of explanation
"""


# print the elements of the set
A = set("James")
for n in A:
    print n,

# print the elements of A on the same line
A = {'string', 1,2,3,4,5,3,'a',(1,2,3)}
for n in A:
    print n,

# print the elements of A each on it's own line
for n in A:
    print n

# A.clear() clear the elemets of A
A = {'string', 1,2,3,4,5,3,'a',(1,2,3)}
print "A before A.clear()", A
A.clear()
print "A after A.clear()", A

# A.copy() copies the contents of A into another set B so clearing A wont affect the contents of B
A = {1,2,3,3,4,4,7,8,-1,3,4} # we contains only one copy of a repeated element
B = A.copy()
print "A is ", A
print "B after B=A.copy() is", B
A.clear()
print "A after A.clear()", A
print "B after A.clear()", B

# B = A makes a "pointer" from A to B so modifying A will also modify B
A = {1,2,3,3,4,4,7,8,-1,3,4} # we contains only one copy of a repeated element
B = A
print "A is ", A
print "B after B=A.copy() is", B
A.clear()
print "A after A.clear()", A
print "B after A.clear()", B

"""
Explanation: Printing, Copying, & Clearing a set
End of explanation
"""


