[SER222] Characterizing Algorithms (5/5): Analysis Approaches

Ruben Acuna
15 Aug 201707:56

Summary

TLDREn este video, se exploran dos enfoques para analizar cómo actúa un programa: el enfoque analítico y el empírico. El analítico examina el código línea por línea para generar una función de crecimiento exacta, pero es laborioso y toma tiempo. El empírico, por otro lado, mide el rendimiento del programa mediante la ejecución en diferentes entradas, proporcionando una aproximación más rápida, pero menos precisa. Ambos enfoques tienen sus ventajas y desventajas. Se discutirán ambos métodos con más detalle en futuras secciones del curso.

Takeaways

  • 🖥️ La función de crecimiento analiza cómo un programa actúa en función del número de operaciones que realiza.
  • 📊 En el pasado, se ha solicitado escribir una función de crecimiento basada en la cantidad de operaciones de un código.
  • 🔄 El ejemplo de código presentado crea un array y lo llena con datos aleatorios utilizando un bucle `for`.
  • 📈 El objetivo es escribir una función de crecimiento que indique cuántos pasos toma el código en función del tamaño de la entrada.
  • 📝 La aproximación analítica examina cada línea del código para contar el número exacto de operaciones que se ejecutan.
  • ⏳ Una desventaja de la aproximación analítica es que consume mucho tiempo, especialmente para programas largos.
  • 🧪 La aproximación empírica se basa en observar cuánto tarda un programa en ejecutarse con diferentes tamaños de entrada.
  • 📉 La aproximación empírica es más rápida, pero no garantiza una respuesta exacta y solo ofrece una aproximación.
  • ⚖️ Ambas aproximaciones tienen ventajas y desventajas: la analítica es precisa pero lenta, y la empírica es rápida pero menos exacta.
  • 📚 En próximas secciones, se profundizará en ambas aproximaciones para analizar algoritmos de manera efectiva.

Q & A

  • ¿Qué enfoque se menciona en el video para analizar cómo actúa un programa?

    -Se mencionan dos enfoques principales: el enfoque analítico y el enfoque empírico.

  • ¿Qué se busca lograr con una función de crecimiento en la programación?

    -Se busca estimar cuántos pasos o operaciones tomará un código en función del tamaño de la entrada, ayudando a comprender su eficiencia.

  • ¿Cómo se calcula el número de operaciones dentro de un bucle 'for' según el enfoque analítico?

    -Se cuenta cuántas veces se ejecutan las operaciones dentro del bucle. En este caso, el bucle se ejecuta N veces, por lo que la operación interna también se ejecutará N veces.

  • ¿Qué ventaja principal ofrece el enfoque analítico al analizar algoritmos?

    -La principal ventaja es que proporciona una respuesta exacta al analizar cada línea del código de manera detallada.

  • ¿Cuáles son las desventajas del enfoque analítico?

    -Las desventajas incluyen el tiempo requerido para analizar código complejo y la dificultad de obtener respuestas precisas sobre el tiempo de ejecución.

  • ¿En qué consiste el enfoque empírico para analizar un algoritmo?

    -El enfoque empírico consiste en ejecutar el programa con diferentes tamaños de entrada y observar el tiempo que toma, para luego inferir una función de crecimiento basada en esos resultados.

  • ¿Qué ventaja ofrece el enfoque empírico sobre el analítico?

    -El enfoque empírico es mucho más rápido de aplicar, ya que no requiere analizar línea por línea, sino solo ejecutar el programa y medir los resultados.

  • ¿Cuál es la desventaja principal del enfoque empírico?

    -Su desventaja es que se basa en aproximaciones y existe la posibilidad de que algunos inputs no probados puedan generar comportamientos inesperados.

  • ¿Por qué el enfoque empírico se considera más práctico en programas grandes?

    -Porque en programas grandes, analizar línea por línea usando el enfoque analítico puede ser extremadamente laborioso y consumir mucho tiempo.

  • ¿Cómo se relacionan los enfoques empírico y analítico con el concepto de 'caja negra'?

    -En el enfoque empírico, se trata al código como una 'caja negra', es decir, se ignoran los detalles internos y solo se observan los resultados. En el enfoque analítico, se analiza cada línea de código para entender completamente su comportamiento.

Outlines

00:00

💻 Análisis de algoritmos mediante funciones de crecimiento

En este párrafo, se introduce el análisis de algoritmos y cómo se puede usar una función de crecimiento para medir la cantidad de operaciones que realiza un código. Se usa un fragmento de código simple que llena un arreglo con datos aleatorios mediante un bucle for, y se describe el proceso de contar las operaciones: desde la asignación inicial, la configuración de la variable del iterador, hasta las comparaciones y la cantidad de veces que se ejecuta el bucle. El objetivo es calcular una función de crecimiento que muestre cuántos pasos tomará el algoritmo dependiendo del tamaño de la entrada.

05:02

🧠 Simplificación de funciones de crecimiento

Aquí se detalla cómo simplificar una función de crecimiento, reduciendo una expresión compleja a una lineal. Aunque el código parece simple, su análisis revela que contiene cinco términos en la función de crecimiento. Se explica cómo cada operación en el código contribuye al tiempo de ejecución, y cómo estas operaciones se deben contar con precisión para calcular cuántos pasos toma el algoritmo. Se menciona también la posibilidad de estimar directamente cuánto tiempo tomará el código, no solo cuántas operaciones realiza.

📊 Introducción al enfoque empírico en el análisis de algoritmos

Se introduce el análisis empírico como una alternativa al análisis analítico. En lugar de examinar el código línea por línea, el enfoque empírico consiste en ejecutar el código con diferentes tamaños de entrada y medir el tiempo que toma para inferir una función de crecimiento. Este enfoque es más rápido y práctico, pero no garantiza precisión absoluta. A través de pruebas de tiempo en diferentes tamaños de arreglos, se puede aproximar cómo crece el tiempo de ejecución con respecto a la entrada, tratándose el código como una 'caja negra'.

⚖️ Comparación entre el análisis empírico y analítico

Este párrafo compara las ventajas y desventajas de los enfoques analítico y empírico. El análisis analítico es más preciso porque examina cada línea de código, pero es lento y difícil de aplicar a programas grandes. Por otro lado, el análisis empírico es más rápido y fácil, pero solo proporciona aproximaciones basadas en las entradas probadas, dejando espacio para comportamientos inesperados con entradas no probadas. Se resalta que el análisis analítico da respuestas exactas, mientras que el empírico ofrece una visión más aproximada basada en la evidencia.

⏱️ Profundización en las ventajas y desventajas de ambos enfoques

Aquí se discuten más a fondo las fortalezas y debilidades de ambos enfoques. Se destaca que el análisis empírico permite obtener resultados rápidamente y es más fácil de aplicar, mientras que el analítico, aunque es más exacto, puede ser extremadamente laborioso y difícil de ejecutar correctamente. También se menciona la dificultad de medir tiempos extremadamente pequeños en el análisis analítico, lo que puede hacer que ciertos resultados parezcan irrelevantes o poco prácticos. En resumen, ambos enfoques tienen su lugar dependiendo del tipo de análisis que se quiera realizar.

Mindmap

Keywords

💡Función de crecimiento

Una función de crecimiento describe cómo cambia la cantidad de operaciones realizadas por un programa a medida que aumenta el tamaño de la entrada. En el video, se muestra cómo se puede calcular esta función analizando línea por línea el código, y se utiliza un ejemplo de un bucle que llena una matriz con elementos aleatorios. Esta función permite predecir el tiempo necesario para ejecutar un programa a medida que aumenta el tamaño de los datos.

💡Enfoque analítico

El enfoque analítico implica examinar directamente el código fuente para contar el número exacto de operaciones que realiza. Se utiliza para obtener una solución exacta sobre cuántas operaciones realizará un programa según su tamaño de entrada. En el video, se menciona que aunque este enfoque ofrece una precisión absoluta, puede ser muy lento y tedioso para grandes cantidades de código.

💡Enfoque empírico

Este enfoque consiste en ejecutar el programa con diferentes tamaños de entrada y medir el tiempo de ejecución para inferir una función de crecimiento. Es una aproximación que trata al código como una 'caja negra', ignorando los detalles internos y basándose únicamente en la observación del comportamiento. En el video, se destaca que este enfoque es más rápido que el analítico, pero puede no capturar comportamientos inesperados.

💡Asignación

La asignación es el acto de dar un valor a una variable. En el video, se menciona que una de las primeras operaciones en el código es una asignación, que se contabiliza como una operación en la función de crecimiento. Es un concepto clave al analizar el número de operaciones básicas en un programa.

💡Bucle for

Un bucle for es una estructura de control que repite un bloque de código un número específico de veces. En el video, el bucle se utiliza para llenar una matriz con elementos aleatorios, y se analiza cómo cada iteración del bucle implica varias operaciones (como la asignación y la comparación). Este tipo de bucles es fundamental para determinar la complejidad temporal de un algoritmo.

💡Operación de comparación

Una comparación verifica una condición, como en el bucle for, donde se comprueba si el índice ha alcanzado un cierto valor. En el video, se menciona que esta operación se realiza N+1 veces, siendo N el número de elementos en la matriz. La cantidad de comparaciones es crucial para entender cómo crece el número de operaciones con el tamaño de los datos.

💡Complejidad temporal

La complejidad temporal describe cuánto tiempo toma ejecutar un algoritmo en función del tamaño de la entrada. En el video, se explora cómo calcular esta complejidad utilizando la función de crecimiento, y se menciona que diferentes enfoques (analítico y empírico) pueden ayudar a determinar el tiempo que tomará el programa.

💡Big O

Big O es una notación matemática que describe el peor caso de crecimiento de un algoritmo en función de su tamaño de entrada. Aunque no se menciona en detalle en el video, se alude a cómo el análisis analítico puede ayudarnos a determinar el Big O de un código, proporcionando una estimación de la eficiencia del algoritmo.

💡Iterador

Un iterador es una variable que cambia su valor en cada repetición de un bucle. En el video, se analiza cómo la inicialización y el incremento de un iterador forman parte de las operaciones que se cuentan para la función de crecimiento. El iterador es esencial para controlar cuántas veces se ejecuta un bucle.

💡Caja negra

La metáfora de la 'caja negra' se refiere a tratar un sistema sin conocer su funcionamiento interno. En el enfoque empírico descrito en el video, se trata al código como una caja negra, donde solo se observan los resultados (tiempo de ejecución) para inferir el comportamiento del programa, sin examinar directamente el código.

Highlights

Introduction of two approaches to analyzing how a program acts: analytical and empirical.

Analytical approach involves breaking down the code line by line to count operations and determine a growth function.

In the analytical example, the for-loop is analyzed with N + 1 comparisons and N increments.

The goal of the analytical approach is to derive a growth function that describes the code’s behavior based on input size.

The analytical approach provides an exact solution but can be time-consuming, especially for larger codebases.

Empirical approach treats the program as a black box, running it with different inputs and observing the results.

With the empirical approach, growth functions are inferred based on evidence from test runs with varying input sizes.

The empirical approach is faster but relies on approximation, which could miss edge cases or unexpected behaviors.

The analytical approach is advantageous because it provides an exact answer and eliminates unexpected behaviors.

A disadvantage of the analytical approach is the difficulty of getting accurate time estimates for small operations.

The empirical approach is easier to apply and allows for quicker analysis, often leading to results in a shorter time.

A disadvantage of the empirical approach is that it only provides evidence for the inputs tested, leaving the possibility of unseen behaviors with other inputs.

In empirical analysis, if a wide range of test inputs is used, a better understanding of the function’s behavior can be obtained.

The balance between analytical and empirical approaches depends on the specific context of the algorithm and the available resources.

The video concludes with the promise to explore both analytical and empirical approaches in detail in upcoming sections.

Transcripts

play00:05

In this video, I want to discuss different approaches

play00:07

to analyzing how a program acts, right?

play00:09

How to characterize it.

play00:10

So in the past, you often, you should have

play00:13

been asked to write what is call a growth function

play00:16

from a piece of code, so here you can see

play00:17

I just have some sample code,

play00:19

which basically is set up to create an array

play00:21

And populate it all with these different,

play00:23

random pieces of data.

play00:24

It's just a simple for-loop, it's only three lines,

play00:26

very straight forward.

play00:29

So, what you may have done in the past is looked at a bit,

play00:31

a bit of code like this and gone line by line

play00:33

and try to figure out how many operations that take place.

play00:36

So, you know, first thing up there we have maybe,

play00:38

well see, that's one line, it's an assignment,

play00:40

but there's actually a couple things happening there,

play00:41

but we can just say, "Hey that's just, you know,

play00:42

it's assignment, it's one operations,"

play00:44

we'll say that's one.

play00:45

And then, you know, we have the for-loop

play00:46

and there's one thing inside of it and I'll have to go then

play00:49

and analyze my for-loop and decide

play00:51

basically how many operations it contains, right?

play00:54

Our goal, right, in the end will basically be

play00:56

to write a growth function which,

play00:57

I have actually down there right?

play00:58

On the counts of exactly how many steps

play01:00

this piece of code will take if I have a certain size input.

play01:03

And the size of input here, right, is basically

play01:05

how many random elements are going to be in my array.

play01:07

So looking at our example, we have, for example,

play01:09

in our growth function our value over here of one.

play01:12

And the one is coming from this first line, right?

play01:16

Our assignment, that's one thing that's happening.

play01:18

Then looking my for-loop, well,

play01:20

the first thing that happens in the for-loop

play01:21

is I need to basically set up my iterator variable.

play01:24

So that's going to be take one operation.

play01:26

So that's where this one over here is coming from.

play01:28

Alright, and that is representing this bit over here.

play01:33

Then I have that, that check where I basically just see,

play01:36

am I done with this loop or not?

play01:37

And, well, it's a little bit annoying,

play01:39

but basically this is going to take N plus one of,

play01:41

well this is going to run N plus one times.

play01:44

Because you have N checks, right?

play01:46

'Cause it's gonna increment N times, and then, right?

play01:49

There's the final check where it's like,

play01:50

"Oh it's going to return false,"

play01:52

and the loop levels it out, so it's always

play01:53

just a little bit more.

play01:56

So N plus one over here.

play01:59

Right, and that is corresponding to how many times

play02:01

that's going to be checked, right?

play02:02

So, again I'm just counting the number of operations,

play02:04

right, I'm counting over here,

play02:05

where I had the number of equals, number of equals.

play02:07

Over here I'm counting the number of comparisons.

play02:10

Over here I have the incrementor

play02:13

that's going to be executed N times

play02:15

because it's going to go from zero to 100.

play02:16

Or zero nine nine.

play02:18

And so over here, I have N, right?

play02:21

That's how many times the line's going to run.

play02:24

Now that whole loop, right, that loop is going to run

play02:26

based on that, on those bounds, right?

play02:28

N number of times, right?

play02:29

Based on the size of the pool, right?

play02:31

Pool is the array we're populating stuff.

play02:32

So if I think about, "Oh, what's going to run

play02:34

inside of this," right?

play02:35

That, that line inside this thing here,

play02:37

right, this assignment, that will run as many times

play02:39

as the whole loop runs, the whole loop runs N times,

play02:41

so that thing is actually going to run N times itself.

play02:45

And so even though I've just had three lines of code here,

play02:47

right, notice I have a expression, right,

play02:50

that has five terms in it.

play02:52

So it took a little bit of time, and I can simplify it

play02:53

so I can get basically a linear expression which tells me,

play02:55

you know, for a size, for an input of this big a size,

play02:58

how many operations will it take, right?

play02:59

And those operations are a little bit mixed, right?

play03:01

Some of them are equal, some of them are, are less than,

play03:03

right, but it's the number of operations.

play03:05

It tells me exactly how many things will happen.

play03:08

Probably this is what you've done before.

play03:10

So this is, this is great, because now I can tell, you know,

play03:12

exactly how long this algorithm will take to run, right?

play03:14

Hopefully I can have this idea of,

play03:15

"Oh, an equal takes, you know, this amount of time,

play03:17

less than takes this amount of time,"

play03:18

and so I can actually even directly estimate how long

play03:21

this function will take to run, right?

play03:22

Not just the number of operations it'll take.

play03:25

More in particular, import, but how long it'll take as well.

play03:27

So that's very nice.

play03:29

This is kind of an analytical approach.

play03:30

We're looking directly at the code, right?

play03:32

We're, we're examining what's in it, and we're writing

play03:34

a growth function from what we see in there.

play03:37

And there's actually some additional rules,

play03:38

talking about a few of them.

play03:39

But there's rules, basically, to directly figure out

play03:42

what is the big O, right?

play03:43

What is the growth function, excuse me,

play03:45

of this bit of code, right?

play03:46

There's rules to follow.

play03:47

So this is analytical approach.

play03:48

And it's really, it's nice if you can do it,

play03:50

because it gives us an exact answer.

play03:53

Unfortunately, there's also some problems with it.

play03:55

So there's actually a second way that I now want to

play03:57

introduce that we can use to analyze algorithms.

play04:02

This is going to be an empirical approach using observation.

play04:06

While the last approach we gave, right, analytical,

play04:09

is very nice 'cause it gives us a perfect answer,

play04:12

and exact solution, it generally just

play04:14

takes too much time to do, right?

play04:15

We had three lines of code there,

play04:16

and you can imagine that if you have a program

play04:18

where it was a hundred lines, you know,

play04:19

you'll be there for a week trying to analyze it.

play04:21

So that's, you know,

play04:22

not something we really have time to do.

play04:24

So the empirical approach goes like this,

play04:26

we have that same snippet of code, but then let's try

play04:27

running it on different sizes of inputs.

play04:29

So maybe I run it on an array of size 10,

play04:31

and it takes 20 seconds.

play04:32

I run it on an array of size 20 and it takes 40 seconds.

play04:35

Okay, I have basically some evidence

play04:38

about how this thing acts.

play04:39

And from that I can infer the growth functions so I can say,

play04:42

"Hey, this thing probably grows like F of N equal two N,"

play04:46

right, however many inputs I give it, right?

play04:48

That times two is the number of seconds

play04:49

this algorithm will take to run.

play04:51

Alright, so in this case I'm treating my code

play04:53

basically like a black box, right?

play04:55

I'm ignoring the fact that there is, you know,

play04:56

this first sign that creates an array,

play04:58

there's a loop, right, that has a start, a finish, right?

play05:02

An incrementor, you know there's stuff,

play05:03

I'm ignoring all that, right?

play05:04

It's just a black box, I'm just going to run it on

play05:06

different inputs and I'm going to see what result

play05:08

they give back to me.

play05:09

And based on what I observe from those results,

play05:11

I'll guess, or I'll approximate a growth function for it.

play05:14

So this is generally a lot easier to do

play05:16

because it takes less time, right?

play05:17

I don't need to look at every single line of code.

play05:20

So these are two approaches, right?

play05:21

We can either take the analytical route,

play05:23

which you've seen in previous courses,

play05:25

or the empirical route.

play05:26

We'll talk about both them in this class.

play05:29

Now of course we should keep in mind that

play05:30

there are different times we want to use these.

play05:32

So let's think about the analytical approach.

play05:34

What are some of the advantages of it?

play05:36

Well, the main advantage, right,

play05:38

is it gives us an exact answer.

play05:39

Empirical, right, is ultimately an approximation, right?

play05:43

Because we're not looking at every single line,

play05:44

there's always a chance

play05:45

that something unexpected could happen.

play05:47

With an analytical approach, we would really go

play05:49

and look at each line in our program

play05:50

and figure out how it behaves.

play05:51

So there's definitely gonna be no kind of character,

play05:53

or no unexpected things that is, that are hidden from us,

play05:56

right, there's no, no chance that something bad will happen.

play05:59

That's the strong advantage of analytical.

play06:01

Disadvantages, well, a disadvantage of analytical

play06:04

is mainly the time it takes to do the analysis.

play06:07

If I'll have a huge amount of code, right,

play06:09

it'll take me forever to write that function.

play06:11

Also, right, it's, well, it takes us time to write it,

play06:14

it's also hard to get it to be correct,

play06:15

and then if we want to do things like estimate time for it,

play06:19

well, it can be very hard to do.

play06:20

We have to basically go and search for

play06:21

all these different little different constants

play06:22

and figure out, you know,

play06:23

how long does this operation take to run,

play06:24

and sometimes, right, when you have operations

play06:25

that are really quick, well, it's like,

play06:27

"Oh you know these things take, you know, .0000001 seconds,"

play06:31

right, and so it's like, well, you know,

play06:34

does that really even make sense, right?

play06:35

Those things are almost too small to measure.

play06:37

So that's one of the downsides.

play06:39

So, empirical, advantages of empirical,

play06:42

it's easier to do, right?

play06:43

It's kind of the flip side of analytical, right?

play06:45

We can look at a function as a black box and hopefully,

play06:47

right, just run it a few times, right?

play06:49

And in an hour we'll have, you know, a graph,

play06:51

or, you know, a picture of, of our different results

play06:53

we got from our different size inputs

play06:55

and from that we can guess how it acts, right?

play06:56

So it's very, very much quicker, right,

play06:58

for us to apply that.

play06:59

Disadvantage of empirical, right,

play07:02

is basically the flip side of the advantage for analytic,

play07:04

right, so with the empirical, we have this issue of

play07:07

"Oh, I've run it a bunch of different times

play07:09

with these different inputs but, and I have evidence

play07:11

where I know, you know, how did that function perform

play07:13

for that particular input," but that's really just evidence,

play07:16

right, so I know that it took 20 seconds for this input,

play07:17

40 seconds for this other input,

play07:19

but there are a million inputs that I did not try.

play07:21

So maybe those will act similarly, right?

play07:23

Maybe if I try an input of size 40, right,

play07:25

it'll take 80 seconds, that'd be great,

play07:27

But there's always that chance that there'll be some input

play07:29

that invokes some unexpected behavior

play07:30

and gives us something that's completely unexpected for us,

play07:33

right, that's going to be a huge issue.

play07:35

We don't get to see everything.

play07:37

Now, if we do have kind of a good suit of tests, right?

play07:39

If we, if we go and we probe

play07:41

and check a whole bunch of different inputs

play07:42

of that function, then we should have

play07:44

some measure of certitude,

play07:45

but it's not going to be guaranteed.

play07:48

Alright, so those are our two approaches.

play07:50

In our next sections we'll go into depth on how to do,

play07:53

how to take both of those approaches

play07:54

to analyze an algorithm.

Rate This

5.0 / 5 (0 votes)

Related Tags
análisis de códigoalgoritmosfunción de crecimientoenfoque analíticoenfoque empíricocomplejidad temporaloptimización de códigobucle forBig Odesempeño del código
Do you need a summary in English?