Tema 11 Funciones

Una función permite escribir un fragmento de código parametrizado. De esta forma, es posible escribir un bloque de código y ejecutarlo para distintos datos. Una función puede considerarse un subprograma que resuelve una subtarea. Un motivo para utilizar funciones es que permiten estructurar u organizar el código de un programa. Cuando hay que resolver un problema complejo, en lugar de intentar solucionarlo mediante un programa muy extenso es mejor descomponerlo en subproblemas. Los subproblemas deben tener una complejidad moderada, de forma que sean resueltos por subprogramas (como las funciones) sencillos. Así, en lugar de utilizar un programa muy grande para resolver un problema complejo se emplean distintos subprogramas que resuelven tareas sencillas y que se combinan para producir una solución final más simple.

En temas previos ya hemos usado muchas funciones de R, en este tema vamos a aprender a crear nuestras propias funciones.

11.1 Creación de funciones

Vamos a empezar a estudiar las funciones creando una función que calcula el máximo de sus dos argumentos:

maximo <- function(a, b) {
  if (a > b) {
    m <- a
  } else {
    m <- b
  }
  m
}
maximo(6, 2)
## [1] 6
y <- 18
maximo(20, y + 7)
## [1] 25

Para crear una función se usa la palabra reservada function. A continuación se enumeran los parámetros formales de la función separados por comas y encerrados entre paréntesis (en el ejemplo los parámetros formales son a y b). Después viene el código de la función. Puesto que el código del ejemplo tiene más de una instrucción, hay que encerrarlo entre llaves. Con esto se crea un objeto función que se asigna a la variable maximo.

Una vez creado el objeto función y haber sido asignado a maximo, en el ejemplo aparecen dos invocaciones a la función. En la primera se usan los parámetros reales 6 y 2, y en la segunda 20 y la expresión y + 7. Al invocar a una función se evalúan las expresiones que conforman los parámetros reales y se emparejan con los parámetros formales, es decir, a cada parámetro formal se le asigna el resultado de evaluar la expresión usada como parámetro real. Los parámetros formales son variables que reciben la información de entrada de la función (en el ejemplo, a y b). Una vez emparejados parámetros reales con formales se ejecutan las instrucciones de la función. Una función devuelve como salida el resultado de evaluar la última instrucción que ejecuta. En el ejemplo, la última instrucción ejecutada es m, que es una expresión que produce el máximo de los dos valores de entrada de la función (almacenados en a y b). Veamos otros ejemplos de invocación a la función maximo:

m <- maximo(2, 3)  # se asigna la salida de la función a m
2 + maximo(4, 5.2) # la invocación a la función forma parte de una expresión
## [1] 7.2

11.2 La función return

En la sección anterior hemos comentado que una función devuelve el resultado de evaluar la última instrucción que ejecuta. Esto es cierto si el código de la función no invoca a la función return. La función return tiene un parámetro y produce el siguiente efecto: termina la ejecución de la función en la que se ejecuta y el valor devuelto por la función es el parámetro con que es invocada return. Por ejemplo, la función maximo previa puede codificarse así:

maximo <- function(a, b) {
  if (a > b) {
    m <- a
  } else {
    m <- b
  }
  return(m)
}
maximo(6, 2)
## [1] 6

Otra posibilidad es:

maximo <- function(a, b) {
  if (a > b) {
    return(a)
  } else {
    return(b)
  }
}
maximo(6, 7)
## [1] 7

O incluso:

maximo <- function(a, b) {
  if (a > b) {
    return(a)
  }
  return(b)
}
maximo(6, 2)
## [1] 6

En este último ejemplo, la instrucción return(b) se ejecuta sólamente si a no es mayor que b. Si a fuera mayor que b entonces se ejecutaría return(a), lo que terminaría la ejecución de la función e impediría que se ejecutara cualquier instrucción tras el if.

11.3 Variables locales

Una función puede realizar cálculos complejos, por lo que a veces necesita usar variables para almacenar los resultados intermedios de sus cálculos. Por ejemplo, la siguiente función calcula la suma de los elementos de un vector:

# Función que suma los elementos de un vector (recibido en v)
suma <- function(v) {
  s <- 0
  for (x in v) {
    s <- s + x
  }
  s
}
suma(c(2, 5, -3))
## [1] 4

Las variables creadas en una función se llaman variables locales. En el ejemplo, la variable s es una variable local que se usa para acumular los elementos del vector. Las variables locales, junto con los parámetros formales, se crean al ejecutarse la función y desaparecen al terminar su ejecución. Las variables locales y parámetros formales son independientes de otras variables que puedan existir fuera de la función con el mismo nombre. Por ejemplo:

v <- 5
s <- 10
suma <- function(v) {
  s <- 0
  for (x in v) {
    s <- s + x
  }
  s
}
suma(c(2, 5, -3))
## [1] 4
v
## [1] 5
s
## [1] 10

Observa que el parámetro formal v y la variable local s coinciden en nombre con otras variables externas a la función. Al invocar a la función suma se crean las variables locales v y s y al terminar su ejecución desaparecen, siendo independientes de las variables v y s que conservan sus valores.

11.4 Variables globales

Una variable global de un programa es aquella que se ha creado o definido fuera del cuerpo de cualquier función. Una función también puede usar variables globales. Por ejemplo:

w <- 10
f <- function(x) {
  x + w
}
f(4)
## [1] 14

La función f usa el valor de la variable w que no es una variable local, sino que es global a la función (ha sido creada fuera). En general, el uso de variables globales no es considerada una buena práctica de programación, por lo que no las usaremos. No obstante, es conveniente saber de su existencia, pues hay muchos programas en R que las utilizan.

El operador de superasignación: <<-, permite modificar variables globales:

w <- 10
f <- function() {
  w <<- w + 2
}
w
## [1] 10
f()
w
## [1] 12

Puesto que nosotros no usaremos variables globales, no usaremos este operador.

11.5 Funciones que devuelven varios valores

Las funciones que hemos implementado hasta ahora devuelven un escalar. No obstante, algunas funciones tienen que devolver más de una valor. Sin embargo, las funciones de R sólo pueden devolver un objeto, resultado de evaluar una expresión. Por lo tanto, cuando queramos devolver más de un dato habrá que devolver un objeto que almacene una colección de datos, como un vector, factor, matriz, lista o data frame. Habrá que elegir el tipo de objeto que creamos que sea más adecuado a nuestros cálculos. Por ejemplo, la función range devuelve el mínimo y el máximo de los elementos de un vector. Vamos a implementar nuestra propia versión:

# rango de los valores de un vector
# Parámetro de entrada: v, el vector
rango <- function(v) {
  c(min(v), max(v))
}
(v <- sample(10, 4))
## [1]  7  8  3 10
rango(v)
## [1]  3 10
range(v) # para comparar si funciona igual que range
## [1]  3 10

Hemos decidido devolver un vector con dos elementos: el mínimo y el máximo. Sin embargo, también podíamos haber optado por devolver una lista con el mínimo y el máximo:

# rango de los valores de un vector
rango2 <- function(v) {
  list(minimo = min(v), maximo = max(v))
}
rango2(v)
## $minimo
## [1] 3
## 
## $maximo
## [1] 10
range(v)
## [1]  3 10

En general, las funciones que devuelven datos de distinto tipo usan una lista como valor de retorno, pues las listas permiten incluir datos de distintos tipos.

11.6 Parámetros por defecto e invocación por nombre

En el tema de variables y expresiones ya comentamos que las funciones pueden tener parámetros por defecto. Un parámetro por defecto es un parámetro formal que se define terminándolo con un igual y una expresión. Vamos a ver su utilidad con un ejemplo. La siguiente función sirve para calcular la distancia euclídea entre dos puntos en un espacio bidimensional y no usa parámetros por defecto:

# distancia entre los puntos (x1, y1) y (x2, y2)
distancia_eu <- function(x1, y1, x2, y2) {
  sqrt((x1 - x2)^2 + (y1 - y2)^2)
}
distancia_eu(1, 1, 2, 2) # distancia entre (1, 1) y (2, 2)
## [1] 1.414214

Como es muy común calcular la distancia de un punto al origen, se decide que el segundo punto sea, por defecto, el origen:

# distancia entre los puntos (x1, y1) y (x2, y2)
distancia_eu2 <- function(x1, y1, x2 = 0, y2 = 0) {
  sqrt((x1 - x2)^2 + (y1 - y2)^2)
}
distancia_eu2(1, 1, 2, 2) # distancia entre (1, 1) y (2, 2)
## [1] 1.414214
distancia_eu2(4, 4)       # distancia entre (4, 4) y (0, 0)
## [1] 5.656854
distancia_eu2(4, 4, 0, 0) # igual efecto que la llamada anterior
## [1] 5.656854

En esta versión de la función con parámetros por defecto, cuando un punto sea el origen no es preciso especificarlo al invocar a la función. El efecto de un parámetro por defecto es que, si no se especifica al ser invocada la función, el parámetro formal toma el valor por defecto, es decir, el especificado tras el operador igual en la definición de la función.

En todas las invocaciones a funciones realizadas hasta ahora en este tema se ha usado la invocación posicional, es decir, parámetros reales y formales se emparejan según su posición (el primer parámetro real se empareja con el primer parámetro formal, el segundo con el segundo, …). Recordemos que al invocar una función en R los parámetros también se pueden especificar por nombre:

# Invocación posicional: distancia entre (1, 1) y (2, 2)
distancia_eu2(1, 1, 2, 2)
## [1] 1.414214
# Invocación por nombre: distancia entre (1, 1) y (2, 2)
distancia_eu2(x1 = 1, x2 = 2, y1 = 1, y2 = 2)
## [1] 1.414214

11.7 Las funciones son objetos

En R una función es un objeto más, como un vector o un data frame. Esto implica que se puede operar con funciones como con otros tipos de objetos. En el siguiente ejemplo, se asigna una función:

f <- function(x) {
  x^2
}
f(8)
## [1] 64
g <- f # se asigna a g la función asociada a f
g(3)   # invocamos a la función a través de g
## [1] 9

Otro ejemplo es crear una lista cuyos elementos son funciones:

suma   <- function(a, b) a + b
resta  <- function(a, b) a - b
operaciones <- list(s = suma, r = resta)
operaciones[[1]](2, 4)
## [1] 6
operaciones[[2]](2, 4)
## [1] -2
operaciones$s(5, 6)
## [1] 11

Una función también puede ser el parámetro de otra función. Ya hemos visto ejemplos de esto en funciones como apply o lapply. Vamos a crear nuestra versión simplificada de lapply. Recordemos que esta función aplica una función, recibida como parámetro, a los elementos de una lista y devuelve una lista con el resultado.

# Aplica una función a los elementos de una lista
# Parámetros de entrada:
#  - l, la lista
#  - f, la función a aplicar a los elementos de l
# Parámetros de salida: una lista con el resultado de aplicar f
#                       a los elementos de l
mi_lapply <- function(l, f) {
  salida <- list()
  for (ind in seq_along(l)) {
    salida[[ind]] <- f(l[[ind]])
  }
  return(salida)
}
l <- list(1, 2, 3)
f <- function(x) x^3
mi_lapply(l, f)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 8
## 
## [[3]]
## [1] 27
lapply(l, f)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 8
## 
## [[3]]
## [1] 27

Aunque todo esto es bastante útil, especialmente los parámetros de tipo función, en este libro no vamos a profundizar en su uso.

11.8 Funciones anónimas

Hasta ahora, al definir las funciones siempre hemos asignado el objeto función creado a un identificador. Una función anónima es aquella que no tiene un identificador asociado. En R hay ocasiones en que sólo necesitamos una función temporalmente y podemos evitarnos el darle un nombre si usamos una función anónima. Por ejemplo, vamos a usar sapply para ver cuántos elementos distintos tiene cada variable (columna) del data frame mtcars:

f <- function(variable) {
  length(unique(variable))
}
sapply(mtcars, f)
##  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
##   25    3   27   22   22   29   30    2    2    3    6

Si observas la salida verás que sólo hay tres valores de cilindros distintos (variable cyl). La función f calcula el número de valores distintos de un vector. Para ello usa la función unique que elimina repetidos y después cuenta cuántos elementos (sin repetidos) hay. Si la función f sólo es usada para este cálculo con sapply, podemos evitarnos el darle un nombre:

# usando una función anónima
sapply(mtcars, function(x) length(unique(x)))
##  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
##   25    3   27   22   22   29   30    2    2    3    6

Las funciones anónimas suelen ser funciones cortas que se pueden escribir en una única línea. Como segundo ejemplo de función anónima vamos a usar la función integrate, que integra a la función que le pasemos como primer parámetro en el intervalo especificado con los siguientes dos parámetros. Por ejemplo, una aproximación a la integral de \(2x^2 + 7\) en el intervalo [0, 5] es:

f <- function(x) {
  2*x^2 + 7
}
integrate(f, 0, 5)
## 118.3333 with absolute error < 1.3e-12

De nuevo, podemos evitar dar nombre a la función si no la precisamos para otros cálculos:

integrate(function(x) 2*x^2 + 7, 0, 5)
## 118.3333 with absolute error < 1.3e-12

Otro ejemplo en el que se podría usar las funciones anónimas es en el código previo en el que se creaba una lista de funciones, éstas pueden ser anónimas:

operaciones <- list(suma  = function(a, b) a + b,
                    resta = function(a, b) a - b
)
operaciones[[1]](2, 4)
## [1] 6
operaciones[[2]](2, 4)
## [1] -2
operaciones$suma(5, 6)
## [1] 11

Ejercicio. Dada la matriz:

set.seed(5)
(m <- matrix(sample(1000, size = 30), nrow = 3))
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,]  834  207  809   71  387  943  528  539  842   474
## [2,]  875  715  725  503  806  821  922  457   85   819
## [3,]  697  889  222  403  826  844  128  732  122   625

genera un vector que indique cuántos elementos de cada fila son mayores que la media de la fila. Usa una función anónima con la función apply: apply(m, 1, function(f) ...). La función anónima recibirá una fila (vector) de la matriz y debe devolver cuántos elementos de la fila (vector) son mayores que la media.

# Solución
apply(m, 1, function(f) sum(f > mean(f)))
## [1] 4 7 6

11.9 Funciones con un número variable de parámetros

Hay funciones, como sum, que toman un número variable de parámetros. En concreto, sum suma todos los valores que le pasemos:

sum(1, 2)
## [1] 3
sum(1, 2, 3)
## [1] 6
sum(1, c(3, 8))
## [1] 12

Para implementar funciones con un número variable de parámetros hay que usar un parámetro especial que viene dado por tres puntos. Dentro del código de la función podemos utilizar list(...) para obtener una lista con los parámetros con que se ha invocado la función. Vamos a implementar una versión simplificada de sum que sólo suma escalares:

# Función que suma un número variable de escalares
suma <- function(...) {
  l <- list(...)
  s <- 0
  for (x in l) {
    s <- s + x
  }
  return(s)
}
suma(1, 2)
## [1] 3
suma(1, 2, 3)
## [1] 6

11.10 Ejercicios

  1. Realiza una función que reciba como parámetro un número y devuelva su valor absoluto. Nota: en R existe una versión más general de esta función llamada abs.

  2. Escribe una función que reciba un vector de números y devuelva una lista con los elementos positivos y los elementos negativos del vector.

  3. Realiza una versión de la función sign. Esta función recibe un vector numérico y devuelve un vector del mismo tamaño que el vector original, pero con los valores 1 (valores positivos), 0 (valores 0) y -1 (valores negativos). Por ejemplo, sign(c(-4, 2, 0, 3)) devuelve el vector c(-1, 1, 0, 1).

  4. La función choose permite calcular un número combinatorio. Por ejemplo, choose(5, 2) calcula \(\binom{5}{2}\). Escribe una versión de choose. En la implementación puedes usar la función factorial.

    \[ \binom{n}{k} = \frac{n!}{k!(n-k)!} \]

  5. Realiza una versión de la función any que recibe como parámetro un vector de valores lógicos y devuelve un valor lógico indicando si al menos uno de los elementos del vector es TRUE.

  6. R contiene una serie de funciones que implementan las operaciones entre conjuntos: unión, intersección, diferencia e igualdad:

    x <- c(1, 2, 5)
    y <- c(5, 6, 1, 3)
    union(x, y)      # unión
    ## [1] 1 2 5 6 3
    intersect(x, y)  # intersección
    ## [1] 1 5
    setdiff(x, y)    # Diferencia: elementos de x que no están en y  
    ## [1] 2
    setdiff(y, x)
    ## [1] 6 3
    setequal(x, y)   # igualdad
    ## [1] FALSE
    setequal(x, c(5, 2, 1))
    ## [1] TRUE

    Implementa versiones de estas funciones. En la implementación puedes usar el operador %in% que comprueba si un elemento está en un vector:

    2 %in% x
    ## [1] TRUE
    c(2, 4) %in% x
    ## [1]  TRUE FALSE
  7. Siguiendo con el ejercicio previo, implementa una función que calcule si un conjunto es un subconjunto de otro y una función que calcule la diferencia simétrica, es decir, aquellos elementos que pertenecen a uno de los conjuntos, pero no a ambos a la vez.

  8. Escribe una función que devuelva el valor asociado a una función matemática definida por intervalos (mira la solución para ver una representación gráfica de la función):

    \[ f(x) = \left\{ \begin{array}{ll} x^2 & \mbox{si $x < 0$} \\ seno(x) & \mbox{si $x \in [0, 2\pi]$} \\ x - 2\pi & \mbox{si $x > 2\pi$} \end{array} \right. \]

  9. Escribe una función que dada una matriz permute sus filas. Es decir, la primera fila se debe intercambiar con la última, la segunda fila con al penúltima y así sucesivamente. Como ejemplo, dada la matriz:

    \[ \left( \begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ \end{array} \right) \]

    se debe devolver la matriz:

    \[ \left( \begin{array}{ccc} 7 & 8 & 9 \\ 4 & 5 & 6 \\ 1 & 2 & 3 \\ \end{array} \right) \]

  10. Escribe una función que dado un valor y un vector devuelva cuántas veces se encuentra el valor en el vector.

  11. Realiza una función que dado un vector de cadenas de caracteres devuelva la cadena más larga y la más corta (si hay varias igual de largas o cortas se puede elegir cualquiera). Para resolver este ejercicio pueden resultar útiles las funciones which.max y which.min. La función nchar devuelve el número de caracteres de una cadena de caracteres.

  12. Una matriz es simétrica si es cuadrada y es igual a su traspuesta. Escribe una función que devuelva un valor lógico indicando si una matriz es simétrica.

  13. Realiza una función que dado un vector numérico indique si existe algún elemento del vector que es igual a la suma del resto de elementos del vector.

  14. Una función recibe como parámetro una matriz. La matriz representa los tiempos empleados por un ciclista en varias etapas. Cada fila representa una etapa. La primera columna de la matriz almacena el número de horas, la segunda columna el número de minutos y la tercera columna el número de segundos que tardó en completar la etapa. Por ejemplo, si se recibe:

    \[ \left( \begin{array}{ccc} 2 & 30 & 50 \\ 1 & 55 & 20 \\ \end{array} \right) \]

    El ciclista ha completado dos etapas. En la primera etapa ha tardado 2 horas, 30 minutos y 50 segundos. La función debe devolver una lista con el número total de horas, minutos y segundos empleados por el ciclista en cubrir el total de etapas. Para los datos de ejemplo se devolvería 4 horas, 26 minutos y 10 segundos.

  15. Realiza una función que reciba como parámetro una matriz y devuelva las posiciones de los elementos que sean a la vez mínimos de su columna y máximos de su fila. Por ejemplo, dada la matriz:

    \[ \left( \begin{array}{ccc} 7 & 8 & 9 \\ 4 & 5 & 6 \\ 1 & 2 & 3 \\ \end{array} \right) \]

    devolvería la posición (3, 3).

  16. Realiza un programa que permita gestionar las reservas de una sala de cine para una sesión. La sala tiene 9 filas con 9 asientos por fila. Los asientos se numeran del 11 al 19 para la primera fila, del 21 al 29 para la segunda fila y así sucesivamente. La sala puede representarse como una matriz. Crea las siguientes funciones:

    • Función que inicie la matriz. Inicialmente todos los asientos deben estar libres.
    • Función que muestre en la pantalla la ocupación de la sala.
    • Función que devuelva cuántos asientos libres hay en la sala.
    • Función que dado un número de entradas devuelva un vector con los números de las filas en que existe, como mínimo, ese número de asientos libres.
    • Función que dado un número de fila y un entero positivo \(n\) reserve \(n\) asientos en la fila especificada. Devuelve un vector con los números de los asientos reservados. Puedes suponer que el número de fila es correcto y que la fila contiene esa cantidad de asientos libres.
    • Función que dado un entero positivo \(n\) reserve \(n\) asientos en la sala. Si no existen \(n\) asientos en la sala no se debe reservar ninguno. Devuelve un vector con los números de los asientos reservados.

    Escribe un programa que solicite cantidades de entradas. Si la cantidad de entradas es mayor a la cantidad de asientos libres indíquelo en la pantalla. Si es menor o igual reserve esas entradas. Proceda solicitando cantidades de entradas y reservando hasta que no haya entradas libres en la sala o se introduzca una cantidad de entradas negativa. Reserve entradas de la siguiente forma:

    • Si es posible albergar todas las entradas en la misma fila, reserve la fila de mayor numeración con capacidad para albergar el número de entradas solicitadas.
    • En otro caso obtenga los asientos de cualquier lugar disponible.

    Cada vez que se reserven entradas muestre los números de entradas reservadas y la ocupación global de la sala. Por ejemplo:

    1: L L L L L L L L L
    2: L L L L L L L L L
    3: L L L L L L L L L
    4: L L L L L L L L L
    5: L L L L L L L L L
    6: L L L L L L L L L
    7: L L L L L L L L L
    8: L L L L L L L L L
    9: L L L L L L L L L

    Cantidad de entradas: 8
    Asientos reservados: [91, 92, 93, 94, 95, 96, 97, 98]

    1: L L L L L L L L L
    2: L L L L L L L L L
    3: L L L L L L L L L
    4: L L L L L L L L L
    5: L L L L L L L L L
    6: L L L L L L L L L
    7: L L L L L L L L L
    8: L L L L L L L L L
    9: O O O O O O O O L

    Cantidad de entradas: 11
    Asientos reservados: [99, 81, 82, 83, 84, 85, 86, 87, 88, 89, 71]

    1: L L L L L L L L L
    2: L L L L L L L L L
    3: L L L L L L L L L
    4: L L L L L L L L L
    5: L L L L L L L L L
    6: L L L L L L L L L
    7: O L L L L L L L L
    8: O O O O O O O O O
    9: O O O O O O O O O

    Cantidad de entradas:

11.11 Soluciones a los ejercicios

# Función valor absoluto
absoluto <- function(x) {
  if (x < 0) {
    x <- -x
  }
  x
}
absoluto(-3)
## [1] 3
absoluto(7.5)
## [1] 7.5
# Dividir elementos positivos y negativos de un vector
posNeg <- function(v) {
  list(positivos = v[v > 0], negativos = v[v < 0])
}

(v <- rnorm(10))
##  [1] -1.86417944 -0.03216801  0.24779384 -0.26155985 -0.72183242  0.08401044
##  [7]  0.12514274 -1.48698214  0.33928752  0.16689248
l <- posNeg(v)
l$positivos
## [1] 0.24779384 0.08401044 0.12514274 0.33928752 0.16689248
l$negativos
## [1] -1.86417944 -0.03216801 -0.26155985 -0.72183242 -1.48698214
# Función signo
signo <- function(v) {
  for (ind in seq_along(v)) {
    if (v[ind] > 0) {
      v[ind] <- 1
    } else if (v[ind] < 0) {
      v[ind] <- -1
    }
  }
  v
}
v <- c(2, -2, 0, 0, 3.9)
signo(v)
## [1]  1 -1  0  0  1

# Versión usando la función ifelse
signo2 <- function(v) {
  v <- ifelse(v > 0, 1, v)
  ifelse(v < 0, -1, v)
}
signo2(v)
## [1]  1 -1  0  0  1
# Versión de choose (número combinatorio)
nc <- function(n, k) {
  factorial(n) / (factorial(k) * factorial(n-k))
}

nc(5, 2)
## [1] 10
choose(5, 2)
## [1] 10

# Versión más eficiente
nc2 <- function(n, k) {
  prod((n-k):n) / factorial(n-k)
}

nc2(5, 2)
## [1] 10
# Versión de la función any
cualquiera <- function (v) {
  for (x in v)
    if (x)          # equivale a if (x == TRUE)
      return(TRUE)
  return(FALSE)
}
cualquiera(1:5 > 2)
## [1] TRUE
cualquiera(1:5 < 0)
## [1] FALSE
# Operaciones con conjuntos
mi_union <- function(x, y) c(x, y[!(y %in% x)])
mi_union2 <- function(x, y) {
  r <- x
  for (v in y) {
    if (!(v %in% x)) {
      r <- c(r, v)
    }
  }
  r
}
interseccion <- function(x, y) x[x %in% y]
diferencia <- function(x, y) x[!(x %in% y)]
igual <- function(x, y) length(x) == length(y) && all(sort(x) == sort(y))

x <- c(1, 2, 5)
y <- c(5, 6, 1, 3)
mi_union(x, y)
## [1] 1 2 5 6 3
mi_union2(x, y)
## [1] 1 2 5 6 3
interseccion(x, y)
## [1] 1 5
diferencia(x, y)    
## [1] 2
diferencia(y, x)
## [1] 6 3
igual(x, y)
## [1] FALSE
igual(x, c(5, 2, 1))
## [1] TRUE
# Subconjunto y diferencia simétrica
subconjunto <- function(x, y) all(x %in% y)
diferencia_simetrica <- function(x, y) c(setdiff(x, y), setdiff(y, x))

subconjunto(1:3, 2:4)
## [1] FALSE
subconjunto(1:3, 1:4)
## [1] TRUE
x <- c(1, 2, 5)
y <- c(5, 6, 1, 3)
diferencia_simetrica(x, y)
## [1] 2 6 3
# Función definida por intervalos
f <- function(x) {
  if (x < 0) {
    return(x^2)
  }
  if (x <= 2*pi) {
    return(sin(x))
  }
  return(x - 2*pi)
}

intervalo_x <- seq(-3, 10, by = 0.1)
plot(intervalo_x, sapply(intervalo_x, f), type = 'l')

# Permuta las filas de una matriz
permuta_filas <- function(m) m[nrow(m):1,]
(m <- matrix(1:9, nrow = 3, byrow = TRUE))
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
## [3,]    7    8    9
permuta_filas(m)
##      [,1] [,2] [,3]
## [1,]    7    8    9
## [2,]    4    5    6
## [3,]    1    2    3
# Cuenta ocurrencias de un valor en un vector (v)
n_ocurrencias <- function(valor, v) sum(valor == v)
# versión con ciclos
n_ocurrencias2 <- function(valor, v) {
  ocu <- 0
  for (x in v) {
    if (x == valor) {
      ocu <- ocu + 1
    }
  }
  ocu
}

n_ocurrencias("la", c("oh", "la", "la"))
## [1] 2
n_ocurrencias2("la", c("oh", "la", "la"))
## [1] 2
# Cadena más corta y más larga
corta_larga <- function(v) {
  long <- sapply(v, nchar)
  list(corta = v[which.min(long)],
       larga = v[which.max(long)]
  )
}
corta_larga(c('A', 'Pepe', 'le', 'gustan', 'la', 'paella'))  
## $corta
## [1] "A"
## 
## $larga
## [1] "gustan"
# Matriz simétrica
simetrica <- function(m) {
  nrow(m) == ncol(m) && all(m == t(m))
}

(m <- matrix(c(9, 2, 4, 2, 6, 1, 4, 1, -2), nrow = 3))
##      [,1] [,2] [,3]
## [1,]    9    2    4
## [2,]    2    6    1
## [3,]    4    1   -2
simetrica(m)
## [1] TRUE
m[2, 1] <-  100
simetrica(m)
## [1] FALSE
simetrica(matrix(0, nrow = 2, ncol = 3))
## [1] FALSE
# Elemento del vector igual a la suma del resto
propiedad <- function(v) {
  s <- sum(v)
  for (x in v) {
    if (x == s-x)
      return(TRUE)
  }
  return(FALSE)
}

propiedad(c(1, 2, 6, 3))
## [1] TRUE
propiedad(1:5)
## [1] FALSE
# Ciclista
tiempo_total <- function(m) {
  total <- colSums(m)
  seg <- total[3] %% 60
  min <- total[2] + total[3] %/% 60
  hor <- total[1] + min %/% 60
  min <- min %% 60
  list(horas = hor, minutos = min, segundos = seg)
  
}
tiempos <- matrix(c(2, 30, 50, 1, 55, 20), nrow = 2, byrow = TRUE)
tiempo_total(tiempos)
## $horas
## [1] 4
## 
## $minutos
## [1] 26
## 
## $segundos
## [1] 10
# Mínimo de su columna y máximo de su fila
# Devuelve una lista de pares fila, columna
minc_maxf <- function(m) {
  r <- list()
  for (f in 1:nrow(m)) {
    maxf <- max(m[f, ])
    for (c in 1:ncol(m)) {
      minc <- min(m[, c])
      if (m[f, c] == maxf && m[f, c] == minc) {
        r[[length(r) + 1]] <- c(f, c)
      }
    }
  }
  r
}

(m <- matrix(c(7, 4, 1, 8, 5, 2, 9, 6, 3), nrow = 3))
##      [,1] [,2] [,3]
## [1,]    7    8    9
## [2,]    4    5    6
## [3,]    1    2    3
minc_maxf(m)
## [[1]]
## [1] 3 3
# Sala de cine

# Inicia la sala de cine
inicia_matriz <- function() matrix(0, nrow = 9, ncol = 9)

# Muestra en la pantalla la ocupación de la sala
muestra <- function(m) {
  for (f in 1:nrow(m)) {
    cat(f, ": ", sep = "")
    for (c in 1:ncol(m)) {
      cat(ifelse(m[f, c] == 0, "L ", "O "))
    }
    cat("\n")
  }
}

# Número de asientos libres
libres <- function(m) sum(m == 0)

# Posibles filas para reservar un número de asientos
posibles_filas <- function(m, n) {
  filas <- numeric()
  for (f in 9:1) {
    if (sum(m[f, ] == 0) >= n) filas[length(filas) + 1] <- f
  }
  filas
}

# Reserva asientos en fila
reserva_en_fila <- function(m, f, n) {
  r <- numeric()
  reservados <- 0
  col <- 1
  while (reservados < n) {
    if (m[f, col] == 0) {
      m[f, col] <- 1
      reservados <- reservados + 1
      r <- c(r, f*10 + col)
    }
    col <- col + 1
  }
  list(reservados = r, cine = m)
}

# Reserva asientos en la sala
reserva <- function(m, n) {
  r <- numeric()
  if (n > libres(m)) {
    return(list(reservados = r, cine = m))
  }
  reservados <- 0
  col <- 1
  fila <- 9
  while (reservados < n) {
    if (m[fila, col] == 0) {
      m[fila, col] <- 1
      reservados <- reservados + 1
      r <- c(r, fila*10 + col)
    }
    if (col < 9) {
      col <- col + 1
    } else {
      fila <- fila - 1
      col <- 1
    }
  }
  list(reservados = r, cine = m)
}

cine <- inicia_matriz()
muestra(cine)
n <- as.integer(readline('Número de entradas: '))
while (n > 0 && libres(cine) > 0) {
  filas <- posibles_filas(cine, n)
  if (length(filas) > 0) {
    r <- reserva_en_fila(cine, filas[1], n)
    cine <- r$cine
    cat("Asientos reservados: ")
    print(r$reservados)
  } else {
    r <- reserva(cine, n)
    if (length(r$reservados) > 0) {
      cine <- r$cine
      cat("Asientos reservados: ")
      print(r$reservados)
    }
  }
  muestra(cine)
  if (libres(cine) > 0) {
    n <- as.integer(readline('Número de entradas: '))
  }
}