makeCacheMatrix <- function(x = matrix()) {
    i <- NULL
    ## set takes a matrix y and sets value of the matrix y to the given matrix
    set <- function(y) {
        x <<- y
        i <<- NULL
    }
    ## get returns the value of the matrix
    get <- function(){
        return(x)
    }
    ## setinv sets the inverse matrix 'i' to matrix inv
    setinv <- function(inv) i<<-inv
    ## getinv returns the inverse matrix
    getinv <- function(){
        return(i)
    }
    ## We return a list containing the definition of the set get setinv and
    ## getinv functions
    list(set = set, get = get, setinv = setinv, getinv = getinv)

}

cacheSolve <- function(x, ...) {
    ## Return a matrix that is the inverse of 'x'
    library("MASS")
    ## We import the library "MASS" because, we are making use of the ginv()
    ## function, which is a generalized inverse function which computes the
    ## inverse of a matrix
    i <- x$getinv()
    if(!is.null(i)) { ## checks if a cached copy exists
        message("Getting cached data")
        return(i)
    }
    data <- x$get()
    i <- ginv(data) ## computes the inverse of the matrix
    x$setinv(i) ## saves a cached copy of the inverse of matrix
    i
}