mathmaker  0.6(alpha)
mamk_misc/doc/mathmaker4doxygen/lib/randomly.py
00001 # -*- coding: utf-8 -*-
00002 
00003 # Mathmaker creates automatically maths exercises sheets
00004 # with their answers
00005 # Copyright 2006-2014 Nicolas Hainaux <nico_h@users.sourceforge.net>
00006 
00007 # This file is part of Mathmaker.
00008 
00009 # Mathmaker is free software; you can redistribute it and/or modify
00010 # it under the terms of the GNU General Public License as published by
00011 # the Free Software Foundation; either version 3 of the License, or
00012 # any later version.
00013 
00014 # Mathmaker is distributed in the hope that it will be useful,
00015 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017 # GNU General Public License for more details.
00018 
00019 # You should have received a copy of the GNU General Public License
00020 # along with Mathmaker; if not, write to the Free Software
00021 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00022 
00023 import math
00024 import random
00025 import lib.maths_lib
00026 from lib import error
00027 from lib.maths_lib import *
00028 
00029 # ------------------------------------------------------------------------------
00030 # --------------------------------------------------------------------------
00031 # ------------------------------------------------------------------------------
00032 ##
00033 #   @brief Collection of functions that return randomly results
00034 
00035 
00036 # --------------------------------------------------------------------------
00037 ##
00038 #   @brief Return a random integer
00039 #   @param min_value The lowest possible value of the result
00040 #   @param max_value The highest possible value of the result
00041 #   @param **options It is possible to use a weighted table
00042 #   The weighted table is a probability distribution : the sum of its values
00043 #   must be 1 and the number of values must match the number of possible
00044 #   results. Here's an example : [0.2, 0.4, 0.3, 0.1].
00045 #   @return An integer comprised between min_value & max_value
00046 def integer(min_value, max_value, **options):
00047     if not ('weighted_table' in options                                       \
00048             and len(options['weighted_table']) == max_value - min_value +1):
00049     #___
00050         return int(math.ceil(random.random()*(max_value - min_value + 1)      \
00051                              + min_value                                      \
00052                              )                                                \
00053                    - 1                                                        \
00054                    )
00055 
00056     else:
00057         # The probability scale will be calculated there from the weighted
00058         # table's date. For instance, [0.2, 0.4, 0.3, 0.1]
00059         #                       gives [0.2, 0.6, 0.9, 1.0] as a result
00060         proba_scale = list()
00061         current_sum = 0
00062         for i in range(len(options['weighted_table'])):
00063             current_sum += options['weighted_table'][i]
00064             proba_scale.append(current_sum)
00065 
00066         random_number = random.random()
00067         last_step = 0
00068 
00069         for i in range(len(proba_scale)):
00070             if random_number >= last_step and random_number < proba_scale[i]:
00071                 return min_value + i
00072             else:
00073                 last_step  = random_number
00074 
00075         return max_value
00076 
00077 
00078 
00079 
00080 
00081 # --------------------------------------------------------------------------
00082 ##
00083 #   @brief Returns a '+' or a '-'
00084 #   @param options plus_signs_ratio=0.3 sets the + signs ratio (default 0.5)
00085 #   @return A random sign ('+' or '-')
00086 def sign(**options):
00087     plus_signs_ratio = 0.5
00088 
00089     if 'plus_signs_ratio' in options                                          \
00090        and options['plus_signs_ratio'] >= 0                                   \
00091        and options['plus_signs_ratio'] <= 1:
00092     #___
00093         plus_signs_ratio = options['plus_signs_ratio']
00094 
00095     if (random.random() < plus_signs_ratio):
00096         return '+'
00097     else:
00098         return '-'
00099 
00100 
00101 
00102 
00103 
00104 # --------------------------------------------------------------------------
00105 ##
00106 #   @brief Pops an element from the provided list
00107 #   @param provided_list The list where to pop an element from
00108 #   @return The randomly chosen element
00109 def pop(provided_list, **options):
00110     if not ('weighted_table' in options \
00111             and len(options['weighted_table']) == len(provided_list)):
00112     #___
00113         random_rank = integer(0, len(provided_list) - 1)
00114 
00115         return provided_list.pop(random_rank)
00116 
00117     else:
00118         i = integer(0, len(provided_list) - 1, **options)
00119         return provided_list.pop(i)
00120 
00121 
00122 
00123 
00124 
00125 # --------------------------------------------------------------------------
00126 ##
00127 #   @brief Returns a randomly decimal number between 0 and 1
00128 #   @return A randomly decimal number between 0 and 1
00129 def decimal_0_1():
00130     return random.random()
00131 
00132 
00133 
00134 
00135 
00136 # --------------------------------------------------------------------------
00137 ##
00138 #   @brief Returns True|False with probability distribution 0,5 - 0,5
00139 #   @return True|False with probability distribution 0,5 - 0,5
00140 def heads_or_tails():
00141     if random.random() > 0.5:
00142         return True
00143     else:
00144         return False
00145 
00146 
00147 
00148 
00149 
00150 # --------------------------------------------------------------------------
00151 ##
00152 #   @brief Returns a randomly integer which is coprime to the given argument
00153 #   @param n The given number
00154 #   @param range As a list, numbers where to pop one what will be coprime to n.
00155 #   @return A randomly integer which is coprime to the given argument
00156 def coprime_to(n, range):
00157     collected_numbers = []
00158 
00159     for number in range:
00160         if lib.maths_lib.gcd(n, number) == 1:
00161             collected_numbers.append(number)
00162 
00163     if len(collected_numbers) == 0:
00164         raise error.ImpossibleAction("find a number coprime to n " \
00165                                      + "in the given range.")
00166 
00167     else:
00168         return pop(collected_numbers)
00169 
00170 
00171 
00172 
00173 # --------------------------------------------------------------------------
00174 ##
00175 #   @brief Returns a randomly integer which is coprime to the given argument
00176 #          but possibly not to the second.
00177 #   @param n The given number, what the result should be coprime to
00178 #   @param p The given number, what the result should not be coprime to
00179 #   @param range As a list, numbers where to look for.
00180 #   @return A randomly integer which is coprime to the given argument
00181 #          but possibly not to the second.
00182 def coprime_to_the_first(n, p, range):
00183     collected_numbers_coprime_to_n = []
00184     collected_numbers_not_coprime_to_p = []
00185     collected_numbers = []
00186 
00187     for number in range:
00188         if lib.maths_lib.gcd(n, number) == 1:
00189             collected_numbers_coprime_to_n.append(number)
00190 
00191     for number in range:
00192         if lib.maths_lib.gcd(p, number) > 1:
00193             collected_numbers_not_coprime_to_p.append(number)
00194 
00195     for number in collected_numbers_coprime_to_n:
00196         if number in collected_numbers_not_coprime_to_p:
00197             collected_numbers.append(number)
00198 
00199     if len(collected_numbers) >= 1:
00200         return pop(collected_numbers)
00201 
00202     else:
00203         if len(collected_numbers_coprime_to_n) == 0:
00204             raise error.ImpossibleAction("find a number coprime to n " \
00205                                          + "in the given range.")
00206 
00207         else:
00208             return pop(collected_numbers_coprime_to_n)
00209 
00210 
00211 
00212 
00213 # --------------------------------------------------------------------------
00214 ##
00215 #   @brief Returns a randomly integer which is not coprime to the given arg.
00216 #   @warning    If the given argument is a prime number, it'll be difficult
00217 #               to find an integer which is not coprime to it, especillay if
00218 #               the range is low.
00219 #   @param n The given number, what the result must be coprime to
00220 #   @param range As a list, numbers where to look for.
00221 #   @return A randomly integer which is not coprime to the given argument.
00222 def not_coprime_to(n, range, **options):
00223     collected_numbers = []
00224     check = False
00225     avoid = 0
00226 
00227     if 'excepted' in options:
00228         check = True
00229         avoid = options['excepted']
00230 
00231     for number in range:
00232         if lib.maths_lib.gcd(n, number) != 1:
00233             if not (check and number == avoid):
00234                 collected_numbers.append(number)
00235 
00236     if len(collected_numbers) == 0:
00237         raise error.ImpossibleAction("find a number not coprime to n " \
00238                                      + "in the given range.")
00239 
00240     else:
00241         return pop(collected_numbers)
00242 
00243 
00244 
00245 
00246 # --------------------------------------------------------------------------
00247 ##
00248 #   @brief When given a list of objects, returns a randomly mixed list of the
00249 #   @brief same objects. Must not return in the same order than given one.
00250 #   @param objects_list
00251 #   @return a list
00252 def mix(objects_list):
00253     result = []
00254 
00255     order_changed = False
00256 
00257     for i in range(len(objects_list) - 2):
00258         j = integer(0, len(objects_list) - 1)
00259 
00260         if i != j and order_changed == False:
00261             order_changed = True
00262 
00263         next_to_add = objects_list[j].clone()
00264         trash = objects_list.pop(j)
00265 
00266         result.append(next_to_add)
00267 
00268     if order_changed:
00269         next_to_add = pop(objects_list).clone()
00270         result.append(next_to_add)
00271         next_to_add = pop(objects_list).clone()
00272         result.append(next_to_add)
00273 
00274     else:
00275         next_to_add = objects_list[1].clone()
00276         result.append(next_to_add)
00277         next_to_add = objects_list[0].clone()
00278         result.append(next_to_add)
00279 
00280 
00281     return result
00282 
00283 
00284 
00285 
00286