Make header inherit from tupl
Rather than having a header class that contains a list of header, change it to directly be an immutable tuple. This simplifies the code because header can now be compared and indexed like any other tuple. Code had to be changed all over the place to cope with this new datatype.
This commit is contained in:
@@ -218,7 +218,7 @@ class node (object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.kind == RELATION:
|
if self.kind == RELATION:
|
||||||
return list(rels[self.name].header.attributes)
|
return list(rels[self.name].header)
|
||||||
elif self.kind == BINARY and self.name in (DIFFERENCE, UNION, INTERSECTION):
|
elif self.kind == BINARY and self.name in (DIFFERENCE, UNION, INTERSECTION):
|
||||||
return self.left.result_format(rels)
|
return self.left.result_format(rels)
|
||||||
elif self.kind == BINARY and self.name == DIVISION:
|
elif self.kind == BINARY and self.name == DIVISION:
|
||||||
|
@@ -86,7 +86,7 @@ class relation (object):
|
|||||||
writer = csv.writer(fp) # Creating csv writer
|
writer = csv.writer(fp) # Creating csv writer
|
||||||
|
|
||||||
# It wants an iterable containing iterables
|
# It wants an iterable containing iterables
|
||||||
head = (self.header.attributes,)
|
head = (self.header,)
|
||||||
writer.writerows(head)
|
writer.writerows(head)
|
||||||
|
|
||||||
# Writing content, already in the correct format
|
# Writing content, already in the correct format
|
||||||
@@ -101,10 +101,10 @@ class relation (object):
|
|||||||
Will return None if they don't share the same attributes'''
|
Will return None if they don't share the same attributes'''
|
||||||
if (self.__class__ != other.__class__):
|
if (self.__class__ != other.__class__):
|
||||||
raise Exception('Expected an instance of the same class')
|
raise Exception('Expected an instance of the same class')
|
||||||
if self.header.sharedAttributes(other.header) == len(self.header.attributes):
|
if self.header.sharedAttributes(other.header) == len(self.header):
|
||||||
return other.projection(list(self.header.attributes))
|
return other.projection(self.header)
|
||||||
raise Exception('Relations differ: [%s] [%s]' % (
|
raise Exception('Relations differ: [%s] [%s]' % (
|
||||||
','.join(self.header.attributes) , ','.join(other.header.attributes)
|
','.join(self.header) , ','.join(other.header)
|
||||||
))
|
))
|
||||||
|
|
||||||
def selection(self, expr):
|
def selection(self, expr):
|
||||||
@@ -112,11 +112,11 @@ class relation (object):
|
|||||||
constant, math operations and boolean ones.'''
|
constant, math operations and boolean ones.'''
|
||||||
attributes = {}
|
attributes = {}
|
||||||
newt = relation()
|
newt = relation()
|
||||||
newt.header = header(list(self.header.attributes))
|
newt.header = header(self.header)
|
||||||
for i in self.content:
|
for i in self.content:
|
||||||
# Fills the attributes dictionary with the values of the tuple
|
# Fills the attributes dictionary with the values of the tuple
|
||||||
for j in range(len(self.header.attributes)):
|
for j,attr in enumerate(self.header):
|
||||||
attributes[self.header.attributes[j]] = i[j].autocast()
|
attributes[attr] = i[j].autocast()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if eval(expr, attributes):
|
if eval(expr, attributes):
|
||||||
@@ -136,7 +136,7 @@ class relation (object):
|
|||||||
raise Exception(
|
raise Exception(
|
||||||
'Unable to perform product on relations with colliding attributes')
|
'Unable to perform product on relations with colliding attributes')
|
||||||
newt = relation()
|
newt = relation()
|
||||||
newt.header = header(self.header.attributes + other.header.attributes)
|
newt.header = header(self.header + other.header)
|
||||||
|
|
||||||
for i in self.content:
|
for i in self.content:
|
||||||
for j in other.content:
|
for j in other.content:
|
||||||
@@ -149,16 +149,16 @@ class relation (object):
|
|||||||
Will delete duplicate items
|
Will delete duplicate items
|
||||||
If an empty list or no parameters are provided, returns None'''
|
If an empty list or no parameters are provided, returns None'''
|
||||||
# Parameters are supplied in a list, instead with multiple parameters
|
# Parameters are supplied in a list, instead with multiple parameters
|
||||||
if isinstance(attributes[0], list):
|
if not isinstance(attributes[0], str):
|
||||||
attributes = attributes[0]
|
attributes = attributes[0]
|
||||||
|
|
||||||
ids = self.header.getAttributesId(attributes)
|
ids = self.header.getAttributesId(attributes)
|
||||||
|
|
||||||
if len(ids) == 0 or len(ids) != len(attributes):
|
if len(ids) == 0:
|
||||||
raise Exception('Invalid attributes for projection')
|
raise Exception('Invalid attributes for projection')
|
||||||
newt = relation()
|
newt = relation()
|
||||||
# Create the header
|
# Create the header
|
||||||
h = [self.header.attributes[i] for i in ids]
|
h = (self.header[i] for i in ids)
|
||||||
newt.header = header(h)
|
newt.header = header(h)
|
||||||
|
|
||||||
# Create the body
|
# Create the body
|
||||||
@@ -175,10 +175,7 @@ class relation (object):
|
|||||||
result = []
|
result = []
|
||||||
|
|
||||||
newt = relation()
|
newt = relation()
|
||||||
newt.header = header(list(self.header.attributes))
|
newt.header = self.header.rename(params)
|
||||||
|
|
||||||
for old, new in params.items():
|
|
||||||
newt.header.rename(old, new)
|
|
||||||
|
|
||||||
newt.content = self.content
|
newt.content = self.content
|
||||||
newt._readonly = True
|
newt._readonly = True
|
||||||
@@ -193,7 +190,7 @@ class relation (object):
|
|||||||
It is possible to use projection and rename to make headers match.'''
|
It is possible to use projection and rename to make headers match.'''
|
||||||
other = self._rearrange_(other) # Rearranges attributes' order
|
other = self._rearrange_(other) # Rearranges attributes' order
|
||||||
newt = relation()
|
newt = relation()
|
||||||
newt.header = header(list(self.header.attributes))
|
newt.header = header(self.header)
|
||||||
|
|
||||||
newt.content = self.content.intersection(other.content)
|
newt.content = self.content.intersection(other.content)
|
||||||
return newt
|
return newt
|
||||||
@@ -206,7 +203,7 @@ class relation (object):
|
|||||||
It is possible to use projection and rename to make headers match.'''
|
It is possible to use projection and rename to make headers match.'''
|
||||||
other = self._rearrange_(other) # Rearranges attributes' order
|
other = self._rearrange_(other) # Rearranges attributes' order
|
||||||
newt = relation()
|
newt = relation()
|
||||||
newt.header = header(list(self.header.attributes))
|
newt.header = header(self.header)
|
||||||
|
|
||||||
newt.content = self.content.difference(other.content)
|
newt.content = self.content.difference(other.content)
|
||||||
return newt
|
return newt
|
||||||
@@ -221,8 +218,7 @@ class relation (object):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
# d_headers are the headers from self that aren't also headers in other
|
# d_headers are the headers from self that aren't also headers in other
|
||||||
d_headers = list(
|
d_headers = tuple(set(self.header) - set(other.header))
|
||||||
set(self.header.attributes) - set(other.header.attributes))
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Wikipedia defines the division as follows:
|
Wikipedia defines the division as follows:
|
||||||
@@ -249,7 +245,7 @@ class relation (object):
|
|||||||
It is possible to use projection and rename to make headers match.'''
|
It is possible to use projection and rename to make headers match.'''
|
||||||
other = self._rearrange_(other) # Rearranges attributes' order
|
other = self._rearrange_(other) # Rearranges attributes' order
|
||||||
newt = relation()
|
newt = relation()
|
||||||
newt.header = header(list(self.header.attributes))
|
newt.header = header(self.header)
|
||||||
|
|
||||||
newt.content = self.content.union(other.content)
|
newt.content = self.content.union(other.content)
|
||||||
return newt
|
return newt
|
||||||
@@ -283,14 +279,11 @@ class relation (object):
|
|||||||
shared = self.header.intersection(other.header)
|
shared = self.header.intersection(other.header)
|
||||||
|
|
||||||
newt = relation() # Creates the new relation
|
newt = relation() # Creates the new relation
|
||||||
|
# Creating the header with all the fields, done like that because order is
|
||||||
|
# needed
|
||||||
|
h = (i for i in other.header if i not in shared)
|
||||||
|
newt.header = header(chain(self.header,h))
|
||||||
|
|
||||||
# Adds all the attributes of the 1st relation
|
|
||||||
newt.header = header(list(self.header.attributes))
|
|
||||||
|
|
||||||
# Adds all the attributes of the 2nd, when non shared
|
|
||||||
for i in other.header:
|
|
||||||
if i not in shared:
|
|
||||||
newt.header.attributes.append(i)
|
|
||||||
# Shared ids of self
|
# Shared ids of self
|
||||||
sid = self.header.getAttributesId(shared)
|
sid = self.header.getAttributesId(shared)
|
||||||
# Shared ids of the other relation
|
# Shared ids of the other relation
|
||||||
@@ -324,16 +317,14 @@ class relation (object):
|
|||||||
shared attributes, it will behave as cartesian product.'''
|
shared attributes, it will behave as cartesian product.'''
|
||||||
|
|
||||||
# List of attributes in common between the relations
|
# List of attributes in common between the relations
|
||||||
shared = set(self.header).intersection(set(other.header))
|
shared = self.header.intersection(other.header)
|
||||||
|
|
||||||
newt = relation() # Creates the new relation
|
newt = relation() # Creates the new relation
|
||||||
|
|
||||||
# Adding to the headers all the fields, done like that because order is
|
# Creating the header with all the fields, done like that because order is
|
||||||
# needed
|
# needed
|
||||||
newt.header = header(list(self.header.attributes))
|
h = (i for i in other.header if i not in shared)
|
||||||
for i in other.header.attributes:
|
newt.header = header(chain(self.header,h))
|
||||||
if i not in shared:
|
|
||||||
newt.header.attributes.append(i)
|
|
||||||
|
|
||||||
# Shared ids of self
|
# Shared ids of self
|
||||||
sid = self.header.getAttributesId(shared)
|
sid = self.header.getAttributesId(shared)
|
||||||
@@ -341,10 +332,7 @@ class relation (object):
|
|||||||
oid = other.header.getAttributesId(shared)
|
oid = other.header.getAttributesId(shared)
|
||||||
|
|
||||||
# Non shared ids of the other relation
|
# Non shared ids of the other relation
|
||||||
noid = []
|
noid = [i for i in range(len(other.header)) if i not in oid]
|
||||||
for i in range(len(other.header.attributes)):
|
|
||||||
if i not in oid:
|
|
||||||
noid.append(i)
|
|
||||||
|
|
||||||
for i in self.content:
|
for i in self.content:
|
||||||
for j in other.content:
|
for j in other.content:
|
||||||
@@ -364,7 +352,7 @@ class relation (object):
|
|||||||
if self.__class__ != other.__class__:
|
if self.__class__ != other.__class__:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if set(self.header.attributes) != set(other.header.attributes):
|
if set(self.header) != set(other.header):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Rearranges attributes' order so can compare tuples directly
|
# Rearranges attributes' order so can compare tuples directly
|
||||||
@@ -380,7 +368,7 @@ class relation (object):
|
|||||||
'''Returns a string representation of the relation, can be printed with
|
'''Returns a string representation of the relation, can be printed with
|
||||||
monospaced fonts'''
|
monospaced fonts'''
|
||||||
m_len = [] # Maximum lenght string
|
m_len = [] # Maximum lenght string
|
||||||
for f in self.header.attributes:
|
for f in self.header:
|
||||||
m_len.append(len(f))
|
m_len.append(len(f))
|
||||||
|
|
||||||
for f in self.content:
|
for f in self.content:
|
||||||
@@ -391,8 +379,8 @@ class relation (object):
|
|||||||
col += 1
|
col += 1
|
||||||
|
|
||||||
res = ""
|
res = ""
|
||||||
for f in range(len(self.header.attributes)):
|
for f,attr in enumerate(self.header):
|
||||||
res += "%s" % (self.header.attributes[f].ljust(2 + m_len[f]))
|
res += "%s" % (attr.ljust(2 + m_len[f]))
|
||||||
|
|
||||||
for r in self.content:
|
for r in self.content:
|
||||||
col = 0
|
col = 0
|
||||||
@@ -420,8 +408,8 @@ class relation (object):
|
|||||||
|
|
||||||
# new_content=[] #New content of the relation
|
# new_content=[] #New content of the relation
|
||||||
for i in self.content:
|
for i in self.content:
|
||||||
for j in range(len(self.header.attributes)):
|
for j,attr in enumerate(self.header):
|
||||||
attributes[self.header.attributes[j]] = i[j].autocast()
|
attributes[attr] = i[j].autocast()
|
||||||
|
|
||||||
if eval(expr, attributes): # If expr is true, changing the tuple
|
if eval(expr, attributes): # If expr is true, changing the tuple
|
||||||
affected += 1
|
affected += 1
|
||||||
@@ -441,10 +429,10 @@ class relation (object):
|
|||||||
All the values will be converted in string.
|
All the values will be converted in string.
|
||||||
Will return the number of inserted rows.'''
|
Will return the number of inserted rows.'''
|
||||||
|
|
||||||
if len(self.header.attributes) != len(values):
|
if len(self.header) != len(values):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'Tuple has the wrong size. Expected %d, got %d' % (
|
'Tuple has the wrong size. Expected %d, got %d' % (
|
||||||
len(self.header.attributes),
|
len(self.header),
|
||||||
len(values)
|
len(values)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -470,73 +458,56 @@ class relation (object):
|
|||||||
return len(self.content) - l
|
return len(self.content) - l
|
||||||
|
|
||||||
|
|
||||||
class header (object):
|
class header(tuple):
|
||||||
|
|
||||||
'''This class defines the header of a relation.
|
'''This class defines the header of a relation.
|
||||||
It is used within relations to know if requested operations are accepted'''
|
It is used within relations to know if requested operations are accepted'''
|
||||||
|
|
||||||
# Since relations are mutalbe we explicitly block hashing them
|
# Since relations are mutalbe we explicitly block hashing them
|
||||||
__hash__ = None
|
def __new__ (cls, fields):
|
||||||
|
return super(header, cls).__new__(cls, tuple(fields))
|
||||||
|
|
||||||
def __init__(self, attributes):
|
def __init__(self, *args, **kwargs):
|
||||||
'''Accepts a list with attributes' names. Names MUST be unique'''
|
'''Accepts a list with attributes' names. Names MUST be unique'''
|
||||||
self.attributes = attributes
|
|
||||||
|
|
||||||
for i in attributes:
|
for i in self:
|
||||||
if not is_valid_relation_name(i):
|
if not is_valid_relation_name(i):
|
||||||
raise Exception('"%s" is not a valid attribute name' % i)
|
raise Exception('"%s" is not a valid attribute name' % i)
|
||||||
|
|
||||||
if len(attributes) != len(set(attributes)):
|
if len(self) != len(set(self)):
|
||||||
raise Exception('Attribute names must be unique')
|
raise Exception('Attribute names must be unique')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "header(%s)" % (self.attributes.__repr__())
|
return "header(%s)" % super(header, self).__repr__()
|
||||||
|
|
||||||
def rename(self, old, new):
|
def rename(self, params):
|
||||||
'''Renames a field. Doesn't check if it is a duplicate.
|
'''Returns a new header, with renamed fields.
|
||||||
Returns True'''
|
|
||||||
|
|
||||||
|
params is a dictionary of {old:new} names
|
||||||
|
'''
|
||||||
|
attrs = list(self)
|
||||||
|
for old,new in params.items():
|
||||||
if not is_valid_relation_name(new):
|
if not is_valid_relation_name(new):
|
||||||
raise Exception('%s is not a valid attribute name' % new)
|
raise Exception('%s is not a valid attribute name' % new)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
id_ = self.attributes.index(old)
|
id_ = attrs.index(old)
|
||||||
self.attributes[id_] = new
|
attrs[id_] = new
|
||||||
except:
|
except:
|
||||||
raise Exception('Field not found: %s' & old)
|
raise Exception('Field not found: %s' % old)
|
||||||
return True
|
return header(attrs)
|
||||||
|
|
||||||
def sharedAttributes(self, other):
|
def sharedAttributes(self, other):
|
||||||
'''Returns how many attributes this header has in common with a given one'''
|
'''Returns how many attributes this header has in common with a given one'''
|
||||||
return len(set(self.attributes).intersection(set(other.attributes)))
|
return len(set(self).intersection(set(other)))
|
||||||
|
|
||||||
def union(self, other):
|
def union(self, other):
|
||||||
'''Returns the union of the sets of attributes with another header.'''
|
'''Returns the union of the sets of attributes with another header.'''
|
||||||
return set(self.attributes).union(set(other.attributes))
|
return set(self).union(set(other))
|
||||||
|
|
||||||
def intersection(self, other):
|
def intersection(self, other):
|
||||||
'''Returns the set of common attributes with another header.'''
|
'''Returns the set of common attributes with another header.'''
|
||||||
return set(self.attributes).intersection(set(other.attributes))
|
return set(self).intersection(set(other))
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
'''Returns String representation of the field's list'''
|
|
||||||
return self.attributes.__str__()
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.attributes == other.attributes
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return self.attributes != other.attributes
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return key in self.attributes
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.attributes)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.attributes)
|
|
||||||
|
|
||||||
def getAttributesId(self, param):
|
def getAttributesId(self, param):
|
||||||
'''Returns a list with numeric index corresponding to field's name'''
|
'''Returns a list with numeric index corresponding to field's name'''
|
||||||
return [self.attributes.index(i) for i in param]
|
return [self.index(i) for i in param]
|
||||||
|
Reference in New Issue
Block a user