Source code for mathmaker.lib.core.utils

# -*- coding: utf-8 -*-

# Mathmaker creates automatically maths exercises sheets
# with their answers
# Copyright 2006-2017 Nicolas Hainaux <nh.techn@gmail.com>

# This file is part of Mathmaker.

# Mathmaker is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.

# Mathmaker is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with Mathmaker; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


# --------------------------------------------------------------------------
##
#   @brief Returns the reduced Product made from the given literals list
#   For example, [x, x², x**6] would return [x**9].
#   Notice that the Items' sign isn't managed here
#   @param provided_list The literal Items list
[docs]def reduce_literal_items_product(provided_list): from mathmaker.lib.core.base_calculus import Item, Product aux_dict = {} if not isinstance(provided_list, list): raise TypeError('A list is expected, not a ' + str(type(provided_list))) for i in range(len(provided_list)): if isinstance(provided_list[i], Item): if provided_list[i].is_literal(): if provided_list[i].raw_value not in aux_dict: aux_dict[provided_list[i].raw_value] = \ provided_list[i].exponent.raw_value else: aux_dict[provided_list[i].raw_value] += \ provided_list[i].exponent.raw_value elif isinstance(provided_list[i], Product): if len(provided_list[i].factor) == 1: if isinstance(provided_list[i].factor[0], Item): if provided_list[i].factor[0].is_literal(): if (provided_list[i].factor[0].raw_value not in aux_dict): # __ aux_dict[provided_list[i].factor[0].raw_value] = \ provided_list[i].exponent \ * provided_list[i].factor[0].exponent else: aux_dict[provided_list[i].factor[0].raw_value] += \ provided_list[i].exponent \ * provided_list[i].factor[0].exponent else: raise TypeError('A literal Item was expected, not ' 'a numeric one.') else: raise TypeError('Expected an Item, not a ' + str(type(provided_list[i].factor[0]))) else: raise ValueError('A list of length 1 was expected, not a ' 'list of length ' + str(len(provided_list[i].factor))) else: raise TypeError('A Product was expected, not a ' + str(type(provided_list[i]))) reduced_list = [] for key in aux_dict: reduced_list.append(Item(('+', key, aux_dict[key]))) return sorted(reduced_list, key=lambda elt: elt.get_first_letter())
# -------------------------------------------------------------------------- ## # @brief A substitute for append() in a special case, for dictionaries # This method checks if a "provided_key" is already in the lexicon. # If yes, then the associated_coeff term is added to the matching Sum. # If not, the key is created and a Sum is associated which will contain # associated_coeff as only term. # @param provided_key The key to use # @param associated_coeff The value to save in the or add to the Sum # @param lexi The dictionary where to put the couple key/coeff
[docs]def put_term_in_lexicon(provided_key, associated_coeff, lexi): from mathmaker.lib.core.base_calculus import Sum # This flag will remain False as long as the key isn't found in the lexicon key_s_been_found = False # We check the lexicon's keys one after the other # IMPORTANT NOTICE: the 'in' operator can't be used because it returns # True ONLY if the SAME OBJECT has been used as a key (and will return # False if another object of same kind and content is already there) for key in lexi: if provided_key == key: # it is important to use the key that was already there to put the # new coefficient in the lexicon lexi[key].append(associated_coeff) key_s_been_found = True if not key_s_been_found: new_coeff_sum = Sum([associated_coeff]) lexi[provided_key] = new_coeff_sum
[docs]def gather_literals(xpr): """ Return all literal Values|AngleItems of an expression. :param xpr: the expression to iter over :type xpr: Calculable """ from mathmaker.lib.core.root_calculus import Value, Calculable from mathmaker.lib.core.base_calculus import AngleItem if not isinstance(xpr, Calculable): raise TypeError('Expected a Calculable, not a ' + str(type(xpr))) if (isinstance(xpr, Value) or isinstance(xpr, AngleItem)): if xpr.is_literal(): return [xpr] else: return [] else: result = [] for elt in xpr: result += gather_literals(elt) return result
# -------------------------------------------------------------------------- ## # @brief Checks if the literals of a *Equality|*Expression can be replaced # @param objcts The list of literal expressions (or a list of lists of such) # @param subst_dict The dictionnary to use # @param how_many To tell if all the literals must be replaced # ('all'|'all_but_one'|'at_least_one'|int) # @return True|False
[docs]def check_lexicon_for_substitution(objcts, subst_dict, how_many): from mathmaker import settings from mathmaker.lib.core.root_calculus import Value, Calculable from mathmaker.lib.core.base_calculus import AngleItem log = settings.dbg_logger.getChild('Utils.check_lexicon_for_substitution') literals_list = [] if not (how_many == 'all' or how_many == 'all_but_one' or how_many == 'at_least_one' or type(how_many) == int): # __ raise ValueError('Argument how_many should contain ' "'all'|'all_but_one'|'at_least_one' or an int, " 'not ' + str(how_many)) if not type(objcts) == list: raise TypeError('Expected a list, not a ' + str(type(objcts))) # If objcts is a list of lists of Calculables, we "unpack" it before # checking its elements. if all(isinstance(elt, list) for elt in objcts): objcts = [e for elt in objcts for e in elt] for elt in objcts: if not isinstance(elt, Calculable): raise TypeError('Expected a Calculable, not a ' + str(type(elt))) if not type(subst_dict) == dict: raise TypeError('Expected a dict, not a ' + str(type(subst_dict))) for elt in subst_dict: if not ((isinstance(elt, Value) or isinstance(elt, AngleItem)) and elt.is_literal() and isinstance(subst_dict[elt], Value) and subst_dict[elt].is_numeric()): # __ raise TypeError('{{key: value}} pairs should be of type ' '{{literal Value|AngleItem: numeric Value}}, ' 'got instead: ' '{kv}: {vv}' .format(kv=str(type(elt)), vv=str(type(subst_dict[elt])) )) # Make the list of all literals for xpr in objcts: literals_list += gather_literals(xpr) literals_list = list(set(literals_list)) log.debug('literals_list= ' + repr(literals_list)) log.debug('subst_dict= ' + repr(subst_dict)) subst_dict_copy = {} for key in subst_dict: subst_dict_copy[key] = subst_dict[key].clone() # Now check if the literals of the expressions are all in the lexicon n = 0 N = len(literals_list) for i in range(len(literals_list)): collected_keys = [] for key in subst_dict_copy: if key == literals_list[i]: n += 1 collected_keys += [key] for k in collected_keys: subst_dict_copy.pop(k) if len(subst_dict_copy) != 0: log.debug('return False because len(subst_dict_copy) != 0 ' 'what means that some of the literals of subst_dict are not ' 'found in the expression.') return False if how_many == 'all': log.debug('Case all, return {r}'.format(r=(n == N))) return n == N elif how_many == 'all_but_one': log.debug('Case all_but_one, return {r}'.format(r=(n == N - 1))) return n == N - 1 elif how_many == 'at_least_one': log.debug('Case at_least_one, return {r}'.format(r=(n >= 1))) return n >= 1 else: log.debug('Case fixed number, return {r}'.format(r=(n == how_many))) return n == how_many