Las expresiones son la unidad básica con la cual se arman las sentencias en Octave. Una expresión da un valor al ser evaluada, el cual se puede imprimir, validar (en el sentido lógico), guardar en una variable, pasar a una función, o asignar su valor a una variable con un operador de asignación.
Una expresión indicial permite referenciar o extraer parte de los elementos de una matriz o vector. Dada la matriz:
octave> a = [1, 2; 3, 4]
a =
1 2
3 4
octave> a(1,[1 2])
ans =
1 2
octave>
En general la expresión `a(i1,i2)'
retorna los valores de la submatriz
de `a'
conteniendo las filas cuyos índices estn en `i1'
y columnas en
`i2'
. En el ejemplo previo podemos reemplazar `a(1,[1
2])'
por `a(1,1:2)'
. Una forma equivalente muy útil y compacta es
`a(1,:)'
. En esta expresión `:'
quiere decir todos los
valores que toma el índice correspondiente. Como el `:'
está en
el índice de filas y la matriz tiene dos columnas `:'
es
equivalente a `[1,2]'
.
Las filas o columnas aparecen en el orden en que aparecen sus índices
en `i1'
o `i2'
. Por ejemplo:
octave> a(1,[2 1])
ans =
2 1
octave>
De esta forma se puede extender vectores a matrices. Por ejemplo, sea
`a'
un vector columna, entonces una forma de obtener una matriz
que contenga al vector `a'
repetido 3 veces es el llamado
"truco de Tom" ("Tom's trick")
octave> a=rand(3,1)
a =
0.456580
0.080549
0.202045
octave> a(:,[1 1 1])
ans =
0.456580 0.456580 0.456580
0.080549 0.080549 0.080549
0.202045 0.202045 0.202045
octave>
Octave acepta un tipo especial de indexamiento llamado "cero-uno" ("zero-one indexing"). Esto es una extensión de Octave (es decir que en este sentido no es compatible con Matlab). La idea es que si el vector de índices está formado sólo por unos y ceros entonces podemos extraer ciertas columnas (o filas) con un vector de índices que tenga 1 en aquellas columnas que queremos extraer y 0 en las otras. Por ejemplo:
octave> a(:,[0 1])
ans =
2
4
octave>
extrae la segunda columna de `a'
. Cuando todos los índices son 1, esta
modalidad de indexación entra en conflicto con la
tradicional. Por ejemplo `a(:,[1 1])'
puede interpretarse como:
`a'
si lo
interpretamos en la forma de indexación tradicional`a'
, si lo interpretamos como indexación "cero-uno".Para evitar esta ambigüedad podemos indicar explícitamente a Octave
cual de las dos formas de indexación debe emplear en caso de haber
conflicto. Esto se hace a través de una variable interna
llamada `prefer_zero_one_indexing'
. Puesto a 1 indica que
en caso de conflicto use indexación "cero-uno" y si esta puesto a 0
que utilice la convencional. Notar que este es el caso del "truco
de Tom" mencionado, pero en ese caso no hay conflicto ya que en
general la dimensión que se expande es originariamente 1.
octave> a
a =
1 2
3 4
octave> prefer_zero_one_indexing=1;
octave> a(1,[1 1])
ans =
1 2
octave> prefer_zero_one_indexing=0;
octave> a(1,[1 1])
ans =
1 1
octave>
Existen toda una serie de variables como ésta que permiten configurar a gusto el comportamiento de Octave en varias situaciones.
Si la variable `do_fortran_indexing'
es puesta a 1, entonces una
matriz puede ser indexada por un sólo índice, como es usual en
Fortran. Recordar que en ese caso, los elementos son recorridos por
columna, es decir `a(1)=a(1,1), a(2)=a(2,1),'
... `a(rows(a)+1)=a(1,2)'
etc... Por ejemplo:
octave> do_fortran_indexing =1
do_fortran_indexing = 1
octave> a(:)
ans =
1
2
3
4
5
6
7
8
9
octave> a=rand(2)
a =
0.48575 0.46822
0.57453 0.50091
octave> a(:)
ans =
0.48575
0.57453
0.46822
0.50091
octave>
Una "función" es el equivalente en Fortran de una
rutina. Existen una serie de funciones "internas" lo cual
quiere decir que son accesibles desde cualquier programa. Por ejemplo
la función `sqrt()'
que calcula la raíz cuadrada es una de
ellas. Además, el usuario puede definir sus propias funciones editando
un archivo de texto y escribiendo una serie de sentencias. De
hecho Octave viene con una librería de tales funciones que, como no
son tan requeridas no se han introducido como internas.
La forma de llamar a una función es a través de una "llamada de función":
sqrt (x^2 + y^2) # un argumento
ones (n, m) # dos argumentos
rand () # ningun argumento
cada función "espera" un número de argumentos, por ejemplo
`sqrt()'
espera un solo agumento:
sqrt(ARGUMENTO)
Algunas de las funciones pueden esperar un número variable de
argumentos. Esto es muy usado por ejemplo cuando hay parámetros
optativos, como tolerancias. En el siguiente ejemplo generamos una
matriz random "casi-simétrica" (con un pequeña componente
aleatoria `O(1e-3)'
). `is_symmetric'
retorna un valor
`0'
(Boolean "false")lo cual indica que la matriz no es
simétrica con la tolerancia interna (`O(1e-16)'
) mientras que si
le indicamos una tolerancia de `1e-2'
a través de un segundo
parámetro, entonces `is_symmetric'
sí retorna 3 (la dimension de
la matriz) que corresponde a un valor lógico "true".
octave> a=rand(3);
octave> a=a+a'+1e-3*rand(3)
a =
1.4658882 1.2932925 1.1064879
1.2933913 1.8328507 0.2221298
1.1065347 0.2216927 0.0087181
octave> is_symmetric(a)
ans = 0
octave> is_symmetric(a,1e-2)
ans = 3
octave>
Una llamada a función también puede retornar múltiples valores, por
ejemplo la función `eig'
retorna la descomposición normal de una
matriz en forma de la matriz diagonal de autovalores y la matriz de
autovectores:
octave> a=[2 1; 1 2]
a =
2 1
1 2
octave> eig(a)
ans =
1
3
octave> [v,d]=eig(a)
v =
-0.70711 0.70711
0.70711 0.70711
d =
1 0
0 3
octave>
Nótese que en la primera llamada no hay miembro izquierdo en la asignación y el valor retornado es un vector con los autovalores. Al escribir una función de Octave podemos saber cuantos argumentos se están requiriendo y dependiendo de esto retornar los valores apropiados.
El mecanismo de paso de argumentos en Octave es "por valor",
en contraposición con Fortran donde los valores se pasan por
referencia. Esto significa que en realidad la función ve
internamente una copia de la variable. Esto evita tener que hacer
copias internas de la variable para evitar que su valor fuera de la
rutina sea modificado accidentalmente. También permite pasar
constantes como argumentos incluso en el caso en que la función va a
modificar los valores internamente. Esto parecería representar un
desperdicio de memoria ya que en el siguiente caso `x'
ocupa 8Mb
de memoria y al pasarlo como argumento a `f()'
la copia interna
ocupará otros 8Mb. Sin embargo Octave es lo sufuecientemente astuto
como para crear la copia sólo si la variable va a ser modificada
internamente
.
x = rand (1000);
f (x);
Salvo en casos especiales, se puede llamar a funciones recursivamente. Por ejemplo, se puede calcular el factorial de un entero de la siguiente manera:
function retval = fact (n)
if (n > 0)
retval = n * fact (n-1);
else
retval = 1;
endif
endfunction
En general es ineficiente hacer esto ya que cada vez que la función es
llamada se guarda una copia de todas las variables de la
función. Una forma mucho más eficiente es usar `prod (1:n)'
,
o `gamma (n+1)'
.
Para matrices `X'
e `Y'
podemos hacer las siguientes
operaciones:
`X+Y, X-Y'
suma y resta de matrices. Las dimensiones deben
coincidir `X * Y'
Multiplicación de matrices. La dimensión interna
debe coincidir, es decir las dimensiones deben ser `n x m'
y
`m x p'
`X .* Y', `X ./ Y'
multiplicación y división elemento a
elemento. `X \ Y', `X / Y'
división a izquierda y a derecha. `X ˆ Y' o `X ** Y'
potencia de matrices. `X .ˆ Y' o `X .** Y'
operador de potencia elemento a
elemento. `X''
transpuesta conjugada de `X'
`X.''
transpuesta de `X'
Sea resolver el sistema de ecuaciones `a * x = b'
. La forma más
evidente de hacerlo es usando la función `inv()'
que retorna la
inversa de la matriz. El uso del operador `\'
es mucho más
eficiente ya que non invierte la matriz `a'
sino que la factoriza
tipo `L*U'
y luego elimina. Esto es de tener en cuenta
especialmente para matrices grandes.
octave> a=[2 1; 1 2]; b=[1;0];
octave> x=inv(a)*b
x =
0.66667
-0.33333
octave> x=a\b
x =
0.66667
-0.33333
octave>
Tal vez uno de los elementos más útiles de Octave es el uso de los
operadores "elemento a elemento". Si `a= b.*c '
esto es
equivalente a un doble lazo en los `i,j'
sobre la operación:
`a(i,j)=b(i,j)*c(i,j)'
. Por ejemplo podemos calcular el producto
escalar de dos vectores de la siguiente forma. La función
`sum()'
retorna la suma de los elementos del vector argumento.
octave> a=rand(20,1); b=rand(20,1);
octave> p=sum(a.*b)
p = 5.1900
octave>
El uso de estas operaciones no sólo significa una notación más
compacta sino que es mucho más eficiente. Por ejemplo, si `a'
y
`b'
son matrices de 500x500, entonces la siguiente operación es
equivalente a la versión compacta `b=a.ˆ0.5'
. Sin embargo la
opción con lazos `for'
tarda unas 70 veces m'as lo que la versión
compacta.
for k=1:500
for l=1:500
b(k,l)=a(k,l)^0.5;
endfor
endfor
`X < Y'
Verdadero si `X'
es menor que `Y'
`X <= Y'
Verdadero si `X'
es menor que `Y'
`X == Y'
Verdadero si `X'
es igual a `Y'
`X >= Y'
Verdadero si `X'
es mayor o igual que `Y'
`X > Y'
Verdadero si `X'
es myor que `Y'
`X != Y'
, `X ~= Y'
, `X <
> Y'
Verdadero si `X'
no es igual a `Y'
Todos los operadores de comparación retornan 1 si el resultado es verdadero y 0 si es falso. La compración se hace siempre elemento a alemento:
octave> a
a =
1 1 2
4 0 4
4 4 0
octave> a>2
ans =
0 0 0
1 0 1
1 1 0
octave> a>=2
ans =
0 0 1
1 0 1
1 1 0
octave> a==0
ans =
0 0 0
0 1 0
0 0 1
octave>
Combinado con las expresiones de indexación "cero-uno" explicadas
anteriormente esto permite extraer convenientemente columnas y filas
de una matriz. En el ejemplo siguiente usamos esta combinación para
extraer primero todos los elementos del vector `a'
que son
menores o iguales que 3 y después el complemento.
octave> a
a =
4 4 2 4 4 4 3 3 5 1
octave> a<=3
ans =
0 0 1 0 0 0 1 1 0 1
octave> a(a<=3)
ans =
2 3 3 1
octave> a(a>3)
ans =
4 4 4 4 4 5
octave>
Puede evitarse el uso de indexación "cero-uno" utilizando la función
`find()'
.
Existen los siguientes operadores lógicos:
`X & Y'
operador lógico "AND"`X | Y'
operador lógico "OR"`!X', `~X'
operador lógico "NOT"Un elemento 0 es interpretado como falso y en caso contrario como verdadero.
Existen las versiones "corto-circuitadas" de estos
operadores `X && Y'
y `X || Y'
. La idea es que el segundo
argumento es evaluado sólo si el primero cumple con la condición.
Por ejemplo, la expresión `a>0 && b=sqrt(a)'
sólo evalúa la
expresión `b=sqrt(a)'
si `a>0'
. Esto puede hacerse también
con la construcción `if
endif'/, pero es más largo.
octave> a=rand
a = 0.29860
octave> a>0 && b=sqrt(a) ; b
b = 0.54644
octave> a=-rand
a = -0.25817
octave> a>0 && b=sqrt(a) ; b
b = 0.54644
octave>
Son expresiones que guardan un nuevo valor en una variable. Las variables no tienen un tipo definido por ejemplo:
octave> foo = 1
foo = 1
octave> foo = "bar"
foo = bar
Asignar una matriz vacía `[]'
equivale muchas veces a eliminar
esa fila o columna. Sin embargo los elementos eliminados deben ser
tales que la matriz resultante sea rectangular, sino los resultados
son impredecibles.
octave> a(:,1)=[]
a =
7 9
2 3
6 4
octave> a(1,1)=[]
a =
7 9
2 3
6 4
octave>
Estos operadores incrementan el valor de una variable en la más o
menos la unidad. Existen versiones "pre" y "post" dependiendo de si la
operación se produce antes o después de retornar el resultado. Son muy
utilizados en lazos y son más eficientes que la operación
`X=X+1'
.
`++X'
pre-incremento en 1 de `X'
`--X'
pre-incremento en -1 de `X'
`X+
+'
post-incremento en 1 de `X'
`X--'
post-incremento en -1 de `X'
Por ejemplo:
octave> x=5; x++
ans = 5
octave> x
x = 6
octave> x=5; ++x
x = 6
octave> x
x = 6
octave>
La precedencia de los operadores indica cuales son las operaciones que
se realizan primero al evaluar una expresión. Por ejemplo,
`-xˆ2'
se reduce a `-(xˆ2)'
ya que el operador
`ˆ'
tiene precedencia sobre `-'
.
`;'
, `,'
`='
`||'
, `&&'
`|'
, `&'
`<'
, `<='
, `=='
, `>='
,
`>'
, `!='
, `~='
, `<
>'
`:'
`+'
, `-'
`*'
, `/'
, `\'
, `.\'
,
`.*'
, `./'
`''
, `.''
`+'
, `-'
, `++'
, `--'
,
`!'
, `~'
`ˆ'
, `**'
, `.ˆ'
, `.**'
Next Chapter, Previous Chapter
Table of contents of this chapter, General table of contents
Top of the document, Beginning of this Chapter