Importare una espressione matematica in un albero binario

di il
6 risposte

Importare una espressione matematica in un albero binario

Ciao a tutti.
Dovrei creare un programma in Python che risolvi le derivate. La mia idea era quella di importare l'espressione in un albero binario, e poi procedere con la risoluzione delle derivate (in modo da gestire anche espressioni composte ed annidae).

L'espressione pensavo di prelevarla in input come stringa e poi dividere i singoli caratteri (operatori, operandi, parentesi) in una lista di elementi (tramite funzione split, se non ricordo male).

Mi stanno però venendo dei dubbi su come importare correttamente questa lista di elementi in un albero.

La classe dell'albero che farei è:

class Albero:
    def __init__ (self, Contenuto, Sinistra=None, Destra=None):
        self.Contenuto = Contenuto
        self.Sinistra = Sinistra
        Self.Destra = Destra

I miei dubbi sono:
* Io creo ogni nodo con
 a = Albero(1, none, none) 
ma questo implica che io conosca già il numero totale dei nodi. O meglio, come faccio a "creare dinamicamente" il nome del nodo che sto istanziando?

* Dovrei ancora ragionarci magari meglio, ma ad occhio, non mi sembra una cosa semplicissima individuare, all'interno della lista che ho, le "posizioni" di ciascun nodo. In espressioni un pochino complesse, individuare l'ultima operazione da effettuare (che sarà la radice dell'albero) non mi sembra molto intuitivo.

Forse ho sbagliato a gestire il tutto come una lista. Però non ho altre idee, non che una stringa sia più semplice...
Sarebbe più semplice se riesco ad ottenere una lista che contiene altre sottoliste, quindi in qualche modo già gerarchicamente divisa.

Se avete delle idee o consigli, sono sicuramente ben accetti!

Grazie!

6 Risposte

  • Re: Importare una espressione matematica in un albero binario

    Quello che ti serve e':

    1) definire la grammatica delle espressioni su cui vorrai calcolare la derivata, mediante la classica BNF, ad esempio
    2) implementare un parser ricorsivo discendente per generare l'albero, oppure utilizzare PLY (Python Lex Yacc) per fare la stessa operazione
    3) una volta che hai l'albero, implementare l'operatore derivata che genera il nuovo albero
    4) implementare la stampoa dell'espressione derivata

    In pratica, per la lettura della stringa, devi implementare il lexer, ed il parser, cosi' come viene fatto nell'implementazione dei compilatori.

    La 'strtok' e' abbondantemente insufficinte per questo tipo di operazioni, visto che stai coinvolgento parentesi, somme sottrazioni moltiplicazioni e divisioni, il che implica anche un problema sulla priorita' degli operatori.
  • Re: Importare una espressione matematica in un albero binario

    Mi accorgo sempre di più dell'inutilità dell'università. Questi argomenti, come la grammatica BNF, li abbiamo visti, ma tutti a livello troppo teorico e astratto.
    Ora che voglio sporcarmici le mani nella pratica, riprendendo gli appunti per ripassare e vedere meglio come impostare il tutto, capisco che è tutto fumo... e soltanto fumo.
    Ed ora, sinceramente, non saprei da che parte girarmi!

    Vabè, scusate la digressione, a volte mi chiedo come mai sto buttando i miei anni in un posto che non mi insegna e lascia nulla.


    Torniamo al nostro problema:
    In realtà io, per motivi di semplicità, avevo posto dei requisiti: Cioè che l'espresione matematica in ingresso sia formata da un operatore e due operandi. Ciascun operando più essere una sottoespressione, sempre formata da due operandi ed un operatore.
    Quindi si ha sempre la seguente forma/grammatica

    S -> <operando> <operatore> <operando>.
    <operatore> -> + | *
    <operando> -> [numeri] | (<operando> <operatore> <operando>)

    Tutte le precedenze degli operatori vengono gestite tramite parentesi.
    Quindi prevedo che non capiterà mai una espressione tipo: 3 + 4 * x

    (ma in realtà, io pensavo di non far capitare neanche 3 + 4 +x )


    Facendo tutte queste semplificazioni, dovrebbe semplificarsi di molto la risoluzione (oppure dici che la riduzione di complessità non è poi così tanto significativa?)


    Avresti dei consigli o testi da indicarmi per capire, una volta definita questa grammatica, come implementare il parser che genera l'albero?

    Grazie!
  • Re: Importare una espressione matematica in un albero binario

    loverdrive ha scritto:


    Mi accorgo sempre di più dell'inutilità dell'università. Questi argomenti, come la grammatica BNF, li abbiamo visti, ma tutti a livello troppo teorico e astratto.
    Che scemenza: LI HAI VISTI, e sai di che si tratta, e' questo l'importante!

    Sai quanti programmatori medi sanno di che cosa si tratta? Si possono contare sulle dita della mano di un monco.

    E ti diro' di piu', visto che ho un po' di esperienza in piu' di te: ora ti sembra che siano tutte scemenze, ma solo perche' non le hai ancora digerite/assimilate. Con il tempo ti accorgerai di quanto sia stato importante/fondamentale aver avuto un approccio teorico a tutta una serie di argomenti.

    Se sai la teoria, la pratica e' solo una questione di applicazione di quei concetti. Ma non solo: ti ritrovi ad avere la capcita' di applicare tali concetti anche in contesti che, per il programmatore medio non ha nessun senso, permettendoti di avere una visione molto piu' ampia e articolata della semplice implementazione di un banale programmino.

    Ed e' questo che fa la differenza tra il bravo programmatore ed il programmatore medio (richiesto nel 99% dei casi da parte del mondo del lavoro).

    Quindi, non disprezzare l'universita', perche' questa ti da una preparazione che il programmatore medio non potra' mai avere.
  • Re: Importare una espressione matematica in un albero binario

    Se implementi la grammatica in modo corretto (ed al momento non lo hai fatto), la maggiore complessita si traduce in un 1% di differenza rispetto all'espressione iniziale: trascurabile.

    Ma non solo: con la corretta grammatica (2% di complessita' rispetto a quella base), puoi implementare espressioni polinomiali di ordine qualunque.

    Per ora puoi lasciar perdere il supporto alle funzioni, anche se questo introdurrebbe solo un'altro 1% di complessita' in piu'!

    Pero' devi definire la corretta grammatica: la parte complessa sta' tutta qui, il resto e' banale.
  • Re: Importare una espressione matematica in un albero binario

    Come ho gia' detto,per l'implementazione del parser:

    - o usi PLY ,
    - o implementi un parser ricorsivo discendente .
    - o cerchi di risolvere il problema in modo pasticciato e approssimativo, sforzandoti di non utilizzare lo strumento nato apposta per risolvere questo tipo di problemi, allo stesso modo in cui lo risolverebbe il programmatore medio.

    Sei tu che devi scegliere
  • Re: Importare una espressione matematica in un albero binario

    Ho dovuto accantonare il progetto per alcuni esami universitari. Ora che li ho finiti, vorrei concludere il tutto.

    Innanzitutto, diciamo che ho risolto in modo un pochino diverso da come pensavo di strutturarlo inizialmente. Divido la stringa in ingresso tramite regular expression, splittando la stringa in una lista.

    Copio qua sotto il codice, funzionante:
    
    #
    #  La funzione principale e' derive(). Accetta in ingresso una stringa e una variabile.
    #  Trasforma l'espressione da derivare, che e' sottoforma di stringa, in una lista in cui il primo e il terzo elemento sono operandi
    #  mentre il secondo e' l'operatore.
    #  Chiama poi la funzione di derivazione vera e propria, derivative() che applica le regole di derivazione sugli operatori.
    #  Infine, derive_solver() calcola la derivata sui singoli operandi.
    #
    
    
    import string
    import re
    
    def exp_split(mystring):	# prende in input una espressione matematica (sottoforma di stringa) e la memorizza in una lista, dividendo gli operandi e operatori
    	exp_splitted = re.split("([+-/*])", mystring.replace(" ", ""), maxsplit = 1)
    	if len(exp_splitted[0]) > 2:
    		exp_splitted[0] = exp_split(exp_splitted[0])
    	if len(exp_splitted) > 2:  			
    		if len(exp_splitted[2]) > 2:
    			exp_splitted[2] = exp_split(exp_splitted[2])
    	return exp_splitted
    		
    	
    def rimuovi_parentesi(lista):		    # da chiamare solo quando l'intera espressione e' gia' scomposta in liste
    	lunghezza = len(lista)
    	for x in range(lunghezza):
    		if type(lista[x]) == type([]):
    			rimuovi_parentesi(lista[x])
    		else:
    			lista[x] = lista[x].replace("(", "")
    			lista[x] = lista[x].replace(")", "")
    	return lista
    				
    
    def derive_solver(elemento, var):
    	if type(elemento) == type(0):
    		return "0"
    	elif elemento == var:
    		return "1"
    	else:
    		return "0"
    
    
    def derivative(lst, var):
    	if len(lst) == 1:		# Se viene chiamata la funzione derivative() su un singolo elemento, quest'ultimo viene passato a derive_solver() e risolto.
    		return derive_solver(lst[0], var)
    		
    	if lst[1] == '+':
    		return [derivative(lst[0], var), '+', derivative(lst[2], var)]
    		
    	if lst[1]  == '*':
    		return [[derivative(lst[0], var), '*', lst[2]], '+', [lst[0], '*', derivative(lst[2], var)]] 
    
    
    def lst_to_str(lst):	# prende in input una espressione memorizzata in una lista, e la converte in stringa
    	if type(lst) != type([]):
    		return lst
    	elif type(lst[0]) == type([]):
    		lst[0] = lst_to_str(lst[0])
    	elif type(lst[2]) == type([]):
    		lst[2] = lst_to_str(lst[2])
    	return "(" + str(lst[0]) + " " + str(lst[1]) + " " + str(lst[2]) + ")"
    		
    	
    def calculate_result(lst):	# data in input una espressione (sottoforma di lista) gia' derivata, effettua i calcoli per semplificarla
    	if len(lst) == 1:		# Se e' gia' un singolo elemento, restituisce l'elemento stesso senza fare nulla.
    		return lst
    	letter = string.lowercase + string.uppercase  # creo una stringa contenente tutte le lettere (maiuscole e minuscole). La usero' dopo per verificare che l'operando sia un simbolo
    	if type(lst[0]) == type([]):		# Se il primo operando e' una lista, prima risolvo quella
    		lst[0] = calculate_result(lst[0])
    	if type(lst[2]) == type([]):
    		lst[2] = calculate_result(lst[2])	# Se il secondo operando e' una lista, prima risolvo quella
    	if lst[1] == '+':
    		if (string.find(string.digits, str(lst[0])) != -1) and (string.find(string.digits, str(lst[2])) != -1):  # vero se gli operandi della lista sono numeri
    				return int(lst[0]) + int(lst[2])
    		if lst[0] == '0':
    			return lst[2]
    		if lst[2] == '0':
    			return lst[0]
    		if (string.find(letter, str(lst[0])) != -1) or (string.find(letter, str(lst[2])) != -1):  # vero se almeno un operando e' un simbolo
    			return [lst[0], lst[1], lst[2]]
    		if type(lst[0]) == type([]):	# se il primo operando e' una lista, valuto solo il secondo
    			if lst[2] == '0':
    				return lst[0]
    			else:
    				return [lst[0], lst[1], lst[2]]
    		if type(lst[2]) == type([]):	# se il secondo operando e' una lista, valuto solo il primo
    			if lst[0] == '0':
    				return lst[2]
    			else:
    				return [lst[0], lst[1], lst[2]]
    
    	if lst[1] == '*':
    		if (string.find(string.digits, str(lst[0])) != -1) and (string.find(string.digits, str(lst[2])) != -1):  # vero se gli operandi della lista sono numeri
    				return int(lst[0]) * int(lst[2])
    		if (lst[0] == '0') or (lst[2] == '0'):	# se un operando e' 0, ritorna 0
    			return 0
    		if (lst[0] == '1'):	# se il primo operando e' 1, ritorna il secondo
    			return lst[2]
    		if (lst[2] == '1'):	# se il secondo operando e' 1, ritorna il primo
    			return lst[0]
    		if (string.find(letter, str(lst[0])) != -1) or (string.find(letter, str(lst[2])) != -1):  # vero se almeno un operando e' un simbolo
    			return [lst[0], lst[1], lst[2]]
    		if type(lst[0]) == type([]):	# se il primo operando e' una lista, valuto solo il secondo
    			if lst[2] == '0':
    				return '0'
    			else:
    				return [lst[0], lst[1], lst[2]]
    		if type(lst[2]) == type([]):	# se il secondo operando e' una lista, valuto solo il primo
    			if lst[0] == '0':
    				return '0'
    			else:
    				return [lst[0], lst[1], lst[2]]
    			
    def derive(exp_raw, var):
    	lst = exp_split(exp_raw) 	# Prende la stringa dell'espressione in input e la converte in una lista
    	lst = rimuovi_parentesi(lst)	# Rimuove le parentesi tonde
    	risultato_lista = derivative(lst, var)	# Effettua la funzione di derivazione vera e propria
    	return lst_to_str(calculate_result(risultato_lista))
    	
    	
    	
    print derive('1 + (3 * x)', 'x')
    

    Ora il problema è un altro: vorrei provare a renderlo multithreading. La funzione su cui chiamare i thread è derivative, che esegue la risoluzione della derivata dei due operandi in parallelo.

    Ho provato a informarmi in rete, e poi a mettere in pratica, ma mi da errore.
    Copio qua sotto il codice modificato, per renderlo multithread:
    
    #
    #  La funzione principale e' derive(). Accetta in ingresso una stringa e una variabile.
    #  Trasforma l'espressione da derivare, che e' sottoforma di stringa, in una lista in cui il primo e il terzo elemento sono operandi
    #  mentre il secondo e' l'operatore.
    #  Chiama poi la funzione di derivazione vera e propria, derivative() che applica le regole di derivazione sugli operatori.
    #  Infine, derive_solver() calcola la derivata sui singoli operandi.
    #
    
    
    import string
    import re
    import threading
    
    def exp_split(mystring):	# prende in input una espressione matematica (sottoforma di stringa) e la memorizza in una lista, dividendo gli operandi e operatori
    	exp_splitted = re.split("([+-/*])", mystring.replace(" ", ""), maxsplit = 1)
    	if len(exp_splitted[0]) > 2:
    		exp_splitted[0] = exp_split(exp_splitted[0])
    	if len(exp_splitted) > 2:  			
    		if len(exp_splitted[2]) > 2:
    			exp_splitted[2] = exp_split(exp_splitted[2])
    	return exp_splitted
    		
    	
    def rimuovi_parentesi(lista):		    # da chiamare solo quando l'intera espressione e' gia' scomposta in liste
    	lunghezza = len(lista)
    	for x in range(lunghezza):
    		if type(lista[x]) == type([]):
    			rimuovi_parentesi(lista[x])
    		else:
    			lista[x] = lista[x].replace("(", "")
    			lista[x] = lista[x].replace(")", "")
    	return lista
    				
    
    def derive_solver(elemento, var):
    	if type(elemento) == type(0):
    		return "0"
    	elif elemento == var:
    		return "1"
    	else:
    		return "0"
    
    
    def derivative(lst, var):
    	t1 = threading.Thread(target = derivative,  args=(lst[0], var))
    	t2 = threading.Thread(target = derivative,  args=(lst[2], var))
    	if len(lst) == 1:		# Se viene chiamata la funzione derivative() su un singolo elemento, quest'ultimo viene passato a derive_solver() e risolto.
    		return derive_solver(lst[0], var)
    		
    	if lst[1] == '+':
    		return [t1.start(), '+', t2.start()]
    		
    	if lst[1]  == '*':
    		return [[t1.start(), '*', lst[2]], '+', [lst[0], '*', t2.start()]] 
    
    
    def lst_to_str(lst):	# prende in input una espressione memorizzata in una lista, e la converte in stringa
    	if type(lst) != type([]):
    		return lst
    	elif type(lst[0]) == type([]):
    		lst[0] = lst_to_str(lst[0])
    	elif type(lst[2]) == type([]):
    		lst[2] = lst_to_str(lst[2])
    	return "(" + str(lst[0]) + " " + str(lst[1]) + " " + str(lst[2]) + ")"
    		
    	
    def calculate_result(lst):	# data in input una espressione (sottoforma di lista) gia' derivata, effettua i calcoli per semplificarla
    	if len(lst) == 1:		# Se e' gia' un singolo elemento, restituisce l'elemento stesso senza fare nulla.
    		return lst
    	letter = string.lowercase + string.uppercase  # creo una stringa contenente tutte le lettere (maiuscole e minuscole). La usero' dopo per verificare che l'operando sia un simbolo
    	if type(lst[0]) == type([]):		# Se il primo operando e' una lista, prima risolvo quella
    		lst[0] = calculate_result(lst[0])
    	if type(lst[2]) == type([]):
    		lst[2] = calculate_result(lst[2])	# Se il secondo operando e' una lista, prima risolvo quella
    	if lst[1] == '+':
    		if (string.find(string.digits, str(lst[0])) != -1) and (string.find(string.digits, str(lst[2])) != -1):  # vero se gli operandi della lista sono numeri
    				return int(lst[0]) + int(lst[2])
    		if lst[0] == '0':
    			return lst[2]
    		if lst[2] == '0':
    			return lst[0]
    		if (string.find(letter, str(lst[0])) != -1) or (string.find(letter, str(lst[2])) != -1):  # vero se almeno un operando e' un simbolo
    			return [lst[0], lst[1], lst[2]]
    		if type(lst[0]) == type([]):	# se il primo operando e' una lista, valuto solo il secondo
    			if lst[2] == '0':
    				return lst[0]
    			else:
    				return [lst[0], lst[1], lst[2]]
    		if type(lst[2]) == type([]):	# se il secondo operando e' una lista, valuto solo il primo
    			if lst[0] == '0':
    				return lst[2]
    			else:
    				return [lst[0], lst[1], lst[2]]
    
    	if lst[1] == '*':
    		if (string.find(string.digits, str(lst[0])) != -1) and (string.find(string.digits, str(lst[2])) != -1):  # vero se gli operandi della lista sono numeri
    				return int(lst[0]) * int(lst[2])
    		if (lst[0] == '0') or (lst[2] == '0'):	# se un operando e' 0, ritorna 0
    			return 0
    		if (lst[0] == '1'):	# se il primo operando e' 1, ritorna il secondo
    			return lst[2]
    		if (lst[2] == '1'):	# se il secondo operando e' 1, ritorna il primo
    			return lst[0]
    		if (string.find(letter, str(lst[0])) != -1) or (string.find(letter, str(lst[2])) != -1):  # vero se almeno un operando e' un simbolo
    			return [lst[0], lst[1], lst[2]]
    		if type(lst[0]) == type([]):	# se il primo operando e' una lista, valuto solo il secondo
    			if lst[2] == '0':
    				return '0'
    			else:
    				return [lst[0], lst[1], lst[2]]
    		if type(lst[2]) == type([]):	# se il secondo operando e' una lista, valuto solo il primo
    			if lst[0] == '0':
    				return '0'
    			else:
    				return [lst[0], lst[1], lst[2]]
    			
    def derive(exp_raw, var):
    	lst = exp_split(exp_raw) 	# Prende la stringa dell'espressione in input e la converte in una lista
    	lst = rimuovi_parentesi(lst)	# Rimuove le parentesi tonde
    	t = threading.Thread(target = derivative,  args=(lst, var)) 
    	risultato_lista = derivative(lst, var)	# Effettua la funzione di derivazione vera e propria
    	return lst_to_str(calculate_result(risultato_lista))
    	
    	
    	
    print derive('1 + (3 * x)', 'x')
    
    Ma come accennavo, mi restituisce un sacco di errori.
    Gli esempi in rete, applicano tutti la definizione e chiamata del thread tramite ciclo for. Però nel mio caso, per non stravolgere l'intera struttura del programma, pensavo di chiamare i thread singolarmente, senza fare uso di cicli for.
Devi accedere o registrarti per scrivere nel forum
6 risposte