|
# compute.py |
|
# coding: utf-8 |
|
import re |
|
import pprint |
|
from functools import reduce |
|
from data import data, overrides |
|
|
|
# - support where queries |
|
# - [ ] basic queries |
|
# - [ ] boolean sets of queries |
|
# - [ ] nested queries |
|
# - write tests |
|
|
|
class RequiredKeyException(Exception): |
|
def __init__(self, keys=[], msg='', data={}): |
|
self.value = keys or msg |
|
self.data = data |
|
def __str__(self): |
|
return 'missing keys: ' + repr(self.value) + str(self.data) |
|
|
|
class UnknownPropertyException(Exception): |
|
def __init__(self, value): |
|
self.value = value |
|
def __str__(self): |
|
return 'unknown value: ' + repr(self.value) |
|
|
|
class BadTypeException(Exception): |
|
def __init__(self, msg, data): |
|
self.value = msg |
|
self.data = data |
|
def __str__(self): |
|
return self.value + ' ' + repr(self.data) |
|
|
|
def pluck(list, prop): |
|
return [i[prop] |
|
for i in list |
|
if prop in i] |
|
|
|
def add(a, b): |
|
return a + b |
|
|
|
def sum(iter): |
|
return reduce(add, iter, 0) |
|
|
|
# Helper Functions |
|
|
|
def isRequirement(name): |
|
return bool(re.match(r'[A-Z]|[0-9][A-Z\- ]', name)) |
|
|
|
|
|
def compare_course(course, to): |
|
# course might have more keys than the dict we're comparing it to |
|
# 'to' will have some combination of 'year', 'semester', 'department', 'number', and 'section' |
|
for key in ['year', 'semester', 'department', 'number', 'section']: |
|
if key in to and course[key] != to[key]: |
|
return False |
|
return True |
|
|
|
#compared = {key: val |
|
# for key, val in course.items() |
|
# if to.get(key, False)} |
|
#return bool(compared) |
|
|
|
|
|
def checkForCourse(filter, courses): |
|
return any([c for c in courses if compare_course(course=c, to=filter)]) |
|
|
|
|
|
def get_occurrences(course, courses): |
|
return [c for c in courses if compare_course(course=c, to=filter)] |
|
|
|
|
|
def assertKeys(dict, *keys): |
|
missingKeys = [key |
|
for key in keys |
|
if key not in dict] |
|
if missingKeys: |
|
raise RequiredKeyException(keys=missingKeys, data=dict) |
|
|
|
|
|
def countCourses(courses): |
|
#courses::pluck('crsid')::uniq()::len() |
|
return len(set(pluck(courses, 'crsid'))) |
|
|
|
|
|
def countDepartments(courses): |
|
# courses::pluck('departments')::sum() |
|
return sum(pluck(courses, 'departments')) |
|
|
|
|
|
def countCredits(courses): |
|
return sum(pluck(courses, 'credits')) |
|
|
|
|
|
def pathToOverride(path): |
|
return '.'.join(path).lower() |
|
|
|
|
|
def hasOverride(path, overrides): |
|
#print pathToOverride(path), overrides, pathToOverride(path) in overrides |
|
return pathToOverride(path) in overrides |
|
|
|
|
|
def getOverride(path, overrides): |
|
return overrides[pathToOverride(path)] |
|
|
|
|
|
def findOperatorType(operator): |
|
if '$eq' in operator: |
|
return '$eq' |
|
elif '$ne' in operator: |
|
return '$ne' |
|
elif '$lt' in operator: |
|
return '$lt' |
|
elif '$lte' in operator: |
|
return '$lte' |
|
elif '$gt' in operator: |
|
return '$gt' |
|
elif '$gte' in operator: |
|
return '$gte' |
|
else: |
|
raise RequiredKeyException(msg='no valid operators ($eq, $ne, $lt, $lte, $gt, $gte) could be found', data=operator) |
|
|
|
def compareCourseAgainstOperator(course, key, operator): |
|
# key: gereqs, operator: {$eq: "EIN"} |
|
# key: year, operator: { |
|
# "$type": "operator", |
|
# "$lte": { |
|
# "$name": "max", |
|
# "$prop": "year", |
|
# "$type": "function", |
|
# "$where": [{ |
|
# "$type": "qualification", "gereqs": { |
|
# "$type": "operator", "$eq": "BTS-T" |
|
# } |
|
# }] |
|
# } |
|
# } } |
|
|
|
kind = findOperatorType(operator) |
|
|
|
if type(operator[kind]) is dict: |
|
# we compute the value of the function-over-where-query style operators |
|
# earlier, in the filterByQualification function. |
|
assertKeys(operator[kind], '$computed-value') |
|
simplifiedOperator = {kind: operator[kind]['$computed-value']} |
|
return compareCourseAgainstOperator(course, key, simplifiedOperator) |
|
|
|
elif type(operator[kind]) is list: |
|
raise BadTypeException(msg='what would a comparison to a list even do? oh, wait; i suppose it could compare against one of several values... well, im not doing that right now. if you want it, edit the PEG and stick appropriate stuff in here (probably simplest to just call this function again with each possible value and return true if any are true.)') |
|
|
|
else: |
|
# it's a static value; a number or string |
|
if kind == '$eq': |
|
return (course[key] == operator[kind] |
|
if type(course[key]) is not list |
|
else operator[kind] in course[key]) |
|
elif kind == '$ne': |
|
return (course[key] != operator[kind] |
|
if type(course[key]) is not list |
|
else operator[kind] not in course[key]) |
|
elif kind == '$lt': |
|
return course[key] < operator[kind] |
|
elif kind == '$lte': |
|
return course[key] <= operator[kind] |
|
elif kind == '$gt': |
|
return course[key] > operator[kind] |
|
elif kind == '$gte': |
|
return course[key] >= operator[kind] |
|
|
|
|
|
def filterByQualification(list, qualification): |
|
# { "$type":"qualification", $key: "gereqs", $value: {"$type": "operator", "$eq": "EIN"} } |
|
# { "$type":"qualification", $key: "year", value: { |
|
# "$type": "operator", |
|
# "$lte": { |
|
# "$name": "max", |
|
# "$prop": "year", |
|
# "$type": "function", |
|
# "$where": { |
|
# "$type": "qualification", $key: "gereqs", $value: { |
|
# "$type": "operator", "$eq": "BTS-T" |
|
# } |
|
# } |
|
# } |
|
# } } |
|
|
|
operator = qualification['$value'] |
|
kind = findOperatorType(operator) |
|
|
|
if type(operator[kind]) is dict: |
|
value = operator[kind] |
|
if value['$type'] == 'function': |
|
func = None |
|
if value['$name'] == 'max': |
|
func = max |
|
elif value['$name'] == 'min': |
|
func = min |
|
filtered = filterfilterByWhereClause(value['$where']) |
|
items = pluck(value['$prop'], filtered) |
|
computed = func(items) |
|
value['$computed-value'] = computed |
|
|
|
print qualification |
|
key = qualification['$key'] |
|
operator = qualification['$value'] |
|
filtered = [course |
|
for course in list |
|
if compareCourseAgainstOperator(course, key, operator)] |
|
#if compareQualificationToCourse(qualification, course)] |
|
|
|
print len(list), len(filtered) |
|
return filtered |
|
|
|
|
|
def filterByWhereClause(list, clause): |
|
# {gereqs = EIN & year <= max(year) from {gereqs = BTS-T}} |
|
# { |
|
# "$type": "boolean", |
|
# "$and": [ |
|
# { "$type":"qualification", $key: "gereqs", $value: {"$type": "operator", "$eq": "EIN"} }, |
|
# { "$type":"qualification", $key: "year", $value: { |
|
# "$type": "operator", |
|
# "$lte": { |
|
# "$name": "max", |
|
# "$prop": "year", |
|
# "$type": "function", |
|
# "$where": { |
|
# "$type": "qualification", $key: "gereqs", $value: { |
|
# "$type": "operator", "$eq": "BTS-T" |
|
# } |
|
# } |
|
# } |
|
# } } |
|
# ] |
|
# } |
|
|
|
if clause['$type'] == 'qualifier': |
|
#print 'qualification' |
|
return filterByQualification(list, clause) |
|
|
|
elif clause['$type'] == 'boolean': |
|
#print 'boolean' |
|
if '$and' in clause: |
|
filtered = list |
|
for q in clause['$and']: |
|
filtered = filterByWhereClause(filtered, q) |
|
return filtered |
|
|
|
elif '$or' in clause: |
|
filtrations = set() |
|
for q in clause['$or']: |
|
filtrations = filtrations.union(filterByWhereClause(list, q)) |
|
return filtrations |
|
|
|
else: |
|
raise RequiredKeyException(msg='neither $or nor $and could be found', data=clause) |
|
|
|
else: |
|
raise BadTypeException(msg='wth kind of type is this clause?', data=clause) |
|
|
|
|
|
# Compute Functions: |
|
# There are two types of compute functions: those that need the surrounding context, and those that don't. |
|
|
|
|
|
# Contained Computes: |
|
# course, occurrence, where |
|
|
|
def compute_course(expr, courses): |
|
query = {key: value |
|
for key, value in expr.items() |
|
if key not in ['$type']} |
|
return checkForCourse(filter=query, courses=courses) |
|
|
|
|
|
def compute_occurrence(expr, courses): |
|
assertKeys(expr, '$course', '$count') |
|
clause = {key: val |
|
for key, val in expr['$course'] |
|
if key in ['department', 'number', 'section']} |
|
filtered = get_occurrences(clause, courses) |
|
return len(filtered) >= expr['$count'] |
|
|
|
|
|
def compute_where(expr, courses): |
|
assertKeys(expr, '$where', '$count') |
|
filtered = filterByWhereClause(courses, expr['$where']) |
|
expr['_matches'] = filtered |
|
return len(filtered) >= expr['$count'] |
|
|
|
|
|
# Contextual Computes: |
|
# boolean, modifier, of, reference |
|
|
|
def compute_boolean(expr, ctx, courses): |
|
if '$or' in expr: |
|
return any([compute_chunk(req, ctx, courses) for req in expr['$or']]) |
|
elif '$and' in expr: |
|
return all([compute_chunk(req, ctx, courses) for req in expr['$and']]) |
|
elif '$not' in expr: |
|
return not (compute_chunk(expr['$not'], ctx, courses)) |
|
else: |
|
print |
|
print result |
|
raise RequiredKeyException(msg='none of $or, $and, or $not could be found') |
|
|
|
|
|
def compute_of(expr, ctx, courses): |
|
assertKeys(expr, '$of', '$count') |
|
|
|
evaluated = [compute_chunk(req, ctx, courses) |
|
for req in expr['$of']] |
|
truthy = [i for i in evaluated if i] |
|
return len(truthy) >= expr['$count'] |
|
|
|
|
|
def compute_reference(expr, ctx, courses): |
|
assertKeys(expr, '$requirement') |
|
if expr['$requirement'] in ctx: |
|
target = ctx[expr['$requirement']] |
|
return target['computed'] |
|
else: |
|
return False |
|
|
|
|
|
def compute_modifier(expr, ctx, courses): |
|
assertKeys(expr, '$what', '$count', '$from') |
|
what = expr['$what'] |
|
|
|
if what not in ['course', 'department', 'credit']: |
|
raise UnknownPropertyException(what) |
|
|
|
if expr['$from'] == 'children': |
|
return 'not yet implemented' |
|
|
|
elif expr['$from'] == 'where': |
|
assertKeys(expr, '$where', '$count') |
|
filtered = filterByWhereClause(courses, expr['$where']) |
|
|
|
if what == 'course': |
|
num = countCourses(filtered) |
|
elif what == 'department': |
|
num = countDepartments(filtered) |
|
elif what == 'credit': |
|
num = countCredits(filtered) |
|
|
|
return num >= expr['$count'] |
|
|
|
|
|
# And, of course, the function that dispatches the appropriate compute: |
|
|
|
def compute_chunk(expr, ctx, courses): |
|
#print |
|
#print 'expression:', expr |
|
#print 'context:', ctx |
|
|
|
assertKeys(expr, '$type') |
|
type = expr['$type'] |
|
|
|
computed = False |
|
if type == 'boolean': |
|
computed = compute_boolean(expr, ctx, courses) |
|
elif type == 'course': |
|
computed = compute_course(expr, courses) |
|
elif type == 'modifier': |
|
computed = compute_modifier(expr, ctx, courses) |
|
elif type == 'occurrence': |
|
computed = compute_occurrence(expr, courses) |
|
elif type == 'of': |
|
computed = compute_of(expr, ctx, courses) |
|
elif type == 'reference': |
|
computed = compute_reference(expr, ctx, courses) |
|
elif type == 'where': |
|
computed = compute_where(expr, courses) |
|
|
|
expr['_result'] = computed |
|
return computed |
|
|
|
|
|
# The overall computation is done by compute, which is in charge of computing sub-requirements and such. |
|
|
|
def compute(requirement, path, courses=[], overrides={}): |
|
this_name = path[-1] |
|
#print '' |
|
#print requirement, this_name |
|
|
|
requirement = { |
|
name: compute(req, path+[name], courses, overrides) |
|
if isRequirement(name) |
|
else req |
|
for name, req in requirement.items() |
|
} |
|
|
|
computed = False |
|
|
|
if 'result' in requirement: |
|
computed = compute_chunk(requirement['result'], requirement, courses) |
|
# requirement['result'] = result |
|
|
|
elif 'message' in requirement: |
|
computed = False |
|
# show a button to toggle overriding |
|
pass |
|
|
|
else: |
|
#raise RequiredKeyException(msg='one of message or result is required') |
|
|
|
print('one of message or result is required') |
|
|
|
requirement['computed'] = computed |
|
|
|
if hasOverride(path, overrides): |
|
requirement['overridden'] = True |
|
requirement['computed'] = getOverride(path, overrides) |
|
|
|
return requirement |
|
|
|
|
|
def main(): |
|
assertKeys(data, 'name', 'result', 'type', 'revision') |
|
|
|
name = data['name'] |
|
type = data['type'] |
|
|
|
courses = [ |
|
{"department": ["ART"], "number": 102}, |
|
{"department": ["ART"], "number": 103}, |
|
{"department": ["ART"], "number": 104}, |
|
{"department": ["ART"], "number": 106}, |
|
{"department": ["ART"], "number": 205}, |
|
{"department": ["ART"], "number": 207}, |
|
{"department": ["ART"], "number": 221}, |
|
{"department": ["ART"], "number": 222}, |
|
{"department": ["ART"], "number": 223}, |
|
{"department": ["ART"], "number": 224}, |
|
{"department": ["ART"], "number": 225}, |
|
{"department": ["ART"], "number": 226}, |
|
{"department": ["ART"], "number": 227}, |
|
{"department": ["ART"], "number": 228}, |
|
{"department": ["ART"], "number": 229}, |
|
{"department": ["ART"], "number": 232}, |
|
{"department": ["ART"], "number": 233}, |
|
{"department": ["ART"], "number": 234}, |
|
{"department": ["ART"], "number": 236}, |
|
{"department": ["ART"], "number": 238}, |
|
{"department": ["ART"], "number": 343}, |
|
{'department': ['ART'], 'number': 398}, |
|
{'department': ['ART', 'ASIAN'], 'number': 259}, |
|
{'department': ['CSCI'], 'number': 241}, |
|
] |
|
|
|
pp = pprint.PrettyPrinter(width=40, indent=2) |
|
|
|
c = compute(requirement=data, path=[type, name], courses=courses, overrides=overrides) |
|
|
|
#print pprint.pformat(c, indent=2) |
|
print 'outcome:', c['computed'] |
|
#print data |
|
|
|
|
|
def test(): |
|
print compareCourseAgainstOperator( |
|
{'department': ['CSCI']}, |
|
'department', {'$eq': 'ART'}) |
|
|
|
if __name__ == "__main__": |
|
#test() |
|
main() |
|
|
If you are looking for an online casino that guarantees safe and secure gambling, then https://www.aucasinoslist.com/ for players from Australia can be extremely helpful. Use welcome bonuses to get started and enjoy an exciting gaming experience without any worries. Only verified casinos are collected here for your convenience. Among them, you can choose any one and start your immersion into the world of gambling.