miércoles, 29 de octubre de 2014

Nueva aplicación en el Google Play Store. Paletris




Recientemente he publicado en el Google Play Store mi nueva aplicación. Esta vez se trata de un juego, que tiene parte de puzzle y parte de agilidad y que a algunos podrá recordarles al famoso Tetris pero en realidad funciona de manera muy distinta.

En el mismo, deberás de acomodar bloques con letras dentro que van cayendo del cielo de manera que cuando los tengas en tierra puedas formar palabras uniendo algunos de ellos y ganando puntos, siguiendo hasta cierto punto para esto la lógica del Scrabble (formar una palabra con la ñ es mucho mas complicado que con la a, y en consecuencia deben de ser los puntos que se otorguen). Toda la acción la guía un gracioso Rinoceronte, fruto del trabajo del diseñador gráfico Abdel de la Campa.

Tecnológicamente hablando esta creado usando Android nativo y apoyándose en la librería RoboGuice. Además, hace uso del listado de palabras obtenidas con el método descrito aqui, desde el código fuente de Chrome.




Inyectando valores en las vistas en Android al estilo Roboguice o ButterKnife.

A la hora de desarrollar aplicaciones Android, soy un gran fanático de Roboguice. Este fue el que nos permitió pasar de

class EjemploActivity extends Activity {
TextView nombre;
 
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        nombre      = (TextView) findViewById(R.id.nombre); 

        //Utilizar findViewById con cast incluido para obtener el TextView
    }
}


a

class EjemploActivity extends RoboActivity {
    //Sencilla anotación que realiza la misma función

    @InjectView(R.id.nombre)             
    TextView nombre;
 
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

o sea, eliminar un montón de código de inicialización de nuestros vistas por medio de una simple anotación que le indica al framework que debe de hacerlo por nosotros. Lo malo de todo es que me gustó tanto, que al final acabé queriendo aplicar su filosofía a todo. Si no podía hacerlo como RoboGuice ya no estaba tan bien. Por ejemplo muchas veces me encontré queriendo cambiar la fuente de una vista y para esto tenía que hacer:

Typeface fuenteDeseada=Typeface.createFromAsset(getAssets(),"roboto");
nombre.setTypeface(fuenteDeseada);


Para esto me cree este proyecto. Con esta mini-librería es posible definir una anotación y en adición a esta definir una clase que implemente la funcionalidad esperada al encontrar declarada la anotación. De esta forma, después de haber declarado TypefaceInjector, podía tranquilamente hacer:

@InjectView(R.id.nombre)
 @InjectTypeface(typefaceName="roboto")
 private TextView nombre;

Y de manera similar con eventos y con muchas cosas más.

Tiempo después de desarrollar esto descubrí ButterKnife, cuyo código fuente es mucho mas sencillo de extender que RoboGuice y además tiene la particularidad que para el rellenado de las vistas utiliza generación de código en la compilación en lugar de Reflection, con la consecuente mejora de rendimiento. De haber conocido ButterKnife un poco antes, mi decisión hubiese sido extender este en lugar de crear SantaGuice, porque creo que este framework es realmente muy prometedor.

lunes, 20 de octubre de 2014

Creando e inicializando objetos complejos con datos de prueba

Te has encontrado alguna vez ante una clase como la siguiente:

public class Container {

List<ContentA> list1;
ContentB[] list2;
ContentC c3;
}

public class ContentA {

String strContent;
ContentB contentB;
}

public class ContentB {

int intContent;
ContentC cc;
}

public class ContentC {

Date dateContent;
}

Y te ves en la necesidad de instanciar un objeto de tipo Container con todos sus miembros con un cierto valor (puede ser aleatorio) para usarlo con cualquier fin, desde pasar un test hasta mockear la salida de un WebService que devuelve un POJO similar.

Yo he debido de hacerlo muchas veces, al principio solo tenía la opción de la "fuerza bruta", o sea

Container container=new Container();
container.setList1(new ArrayList());
container.setList2(new ContentB[5]);
container.setContentC(new ContentC());

container.getList1.add(...)
....

Tedioso no? Al tiempo me implemente una clase mía basada en la clase PropertyUtils de Apache Commons con las cuales inicializaba dinamicamente los objetos uno a uno, que represento un cierto avance con respecto a la solucion inicial pero todavia no era lo ideal.

Lo ideal lo descubrí en la libreria Podam la cual, a pesar de tener un interminable numero de configuraciones posibles para inicializar un objeto de la manera que mas nos guste, con dos sencillas lineas nos crea un nuevo objeto Container con todas sus propiedades rellenas tal y como queríamos.

PodamFactory factory = new PodamFactoryImpl();
//Usando la estrategia aleatoria de creación de objetos

Container container= factory.manufacturePojo(Container.class);

Y ya está. Simple no?

Comprobar si un número es impar, ¿Cual es la manera mas rápida?

Mi lectura de turno es el aunque medio añejo, muy recomendable libro Java Puzzlers . En este, los autores, que en su mayoría han estado ligados al desarrollo del lenguaje Java en alguna de sus fases nos plantean ejercicios que siempre parece que terminan de una forma y lo hacen de otra, sorprendiéndonos gratamente, desvelándonos alguna de las interioridades del lenguaje y haciéndonos conocerlos un poco mejor.

Uno de los ejercicios gira en torno a conocer en Java si un número es impar. La manera usual de obtener esto es mediante el operador % usando la expresión siguiente:

numero%2!=0

Este operador, en realidad calcula el resto de la división de numero entre 2 y por consiguiente de acuerdo a la especificación del lenguaje seria parte de la siguiente igualdad

numero/2+resto=división

a partir de la cual es calculado.

En Java Puzzlers se nos plantea la alternativa de saber si un número es impar si al aplicarle una operación Bitwise And con 1 como segundo operando devuelve algo distinto de 0

numero & 1!=0

El fundamento de esto es que el operador And Bitwise realiza un And entre las representaciones binarias de ambos operandos. El 1 al representarse en binario solo podrá dar como resultado un numero distinto de 0 al aplicarle un & Bitwise al ser comparado con un numero que también tenga un uno en la ultima posición en binario y estos numero tienen la forma 2n+1 o sea son impares.

000000001
&
________1
=
000000001

Para comprobar la veracidad del planteamiento se ha elaborado un mini programa que dado una entrada de 1 000 000 de números pone en practica ambos métodos para comprobar si son impares y el resultado como podrá verse comprueba lo planteado por los autores.

Ejecutando comprobacion con el mismo valor, 1000000 times
Via usual
Tardo 6 milisegundos
Via Bitwise
Tardo 0 milisegundos
Ejecutando comprobacion con 1000000 valores diferentes
Via usual
Tardo 5 milisegundos
Via Bitwise
Tardo 1 milisegundos



La ligera diferencia que existe entre el caso en el que los números son todos distintos y cuando son iguales no parece repetirse en sucesivos ejecuciones del programa por lo que no debería ser significativa. De esto puede extraerse que aunque usar el bitwise and pueda significar confuso a primera vista para posibles terceros que lean nuestro código, en ocasiones puede significar una mejora importante en el rendimiento de nuestras aplicaciones y es sin duda una cuestión que merece ser conocida por todos los amantes de un código eficiente y optimo.

lunes, 4 de agosto de 2014

Creando un Realm personalizado para Shiro que permita leer los usuarios de una fuente externa


Como ya hemos vistos en alguno de los post anteriores Apache Shiro es una buena herramienta para las tareas de seguridad mas comunes dentro de una aplicación J2EE. Una de las formas que tiene este framework para permitirnos definir toda la configuración de la seguridad de nuestra aplicación de una manera declarativa y externa a la misma es mediante la creación de un archivo .ini con la siguiente estructura.

[main]
authc.loginUrl = /jsp/login.xhtml
authc.successUrl = /jsp/index.xhtml

[users]
admin=LECTOR
admin=EDITOR

[roles]
LECTOR=nota:leer
EDITOR=nota:crear,modificar

[urls]
/jsp/listaNotas.xhtml=perms["nota:leer"]
/jsp/insertarNota.xhtml=roles["EDITOR"]
/jsp/** = authc

Sin embargo, con frecuencia es posible rellenar de antemano todas las secciones anteriores excepto la de los usuarios. Es muy común que en las aplicaciones de hoy en día obtengamos la información de los usuarios a partir de otro tipo de recursos tales como una Base de Datos LDAP que sin duda nos brindan una mayor flexibilidad para el manejo de los cambios en los mismos (altas y bajas), así como mayor seguridad que un simple archivo de texto. ¿Pero que pasaría si no quisiésemos renunciar a la idea de definir nuestra configuración de seguridad en un archivo .ini pero necesitásemos obtener los usuarios de una fuente externa?

Excavando en la muy bien diseñada arquitectura de Shiro encontramos el concepto de Realm que es una interfaz para dado el nombre y contraseña del usuario actual recuperar la información de autenticación o autorización del mismo, dada que se cuenta con que esta almacenada en un sitio externo. Shiro posee varias clases que implementan esta interfaz para los casos mas comunes tales como :

  • AbstractLdapRealm
  • ActiveDirectoryRealm
  • JdbcRealm
  • JndiLdapRealm
  • IniRealm


Este ultimo es el Realm autorizado en el caso en que usemos un archivo ini. Mirando en el código fuente vemos que su funcionamiento interno consiste en parsear el archivo y extraer cada una de las secciones. Es de esta forma que nos es posible reutilizar esta clase a través de la herencia, pero sustituir en nuestra clase hija el fragmento en el que se cargan los usuarios por nuestra propia forma de obtenerlos. Un ejemplo sería como el que sigue:

...
public class IniConUsuarioExternoRealm extends IniRealm {

private static final String INI_FILE = "/WEB-INF/shiro.ini";

@Override
protected AuthenticationInfo doGetAuthenticationInfo

(AuthenticationToken token) throws AuthenticationException {
actualizarDatosDeUsuario();
return super.doGetAuthenticationInfo(token);
}

@Override
protected void onInit() {
this.setResourcePath("classpath:" + INI_FILE);

super.onInit();
}

public void actualizarDatosDeUsuario

(AuthenticationToken token) {
Map<String, Object> map = FacesContext.getCurrentInstance().

getExternalContext().getSessionMap();

String user = token.getUser();
String pass = token.getPs();
String[] roles = //Aqui obten los roles para tu usuario 

//de donde quiera que los tengas

if (StringUtils.isBlank(user)) {
throw new AuthorizationException("No hay usuarios ni roles");
}

construirCuenta(user, "", roles);
}

private SimpleAccount construirCuenta

(String username, String password, String[] roles) {
SimpleAccount account = getUser(username);
if (account == null) {
account = new SimpleAccount(username, password, getName());
add(account);
}

account.setCredentials(password);

if (roles != null) {
for (int i = 0; i < roles.length; i++) {
String rolename = roles[i];
account.addRole(rolename);

SimpleRole role = getRole(rolename);
if (role != null) {
account.addObjectPermissions(role.getPermissions());
}
}
} else {
account.setRoles(null);
}

return account;
}
}


domingo, 29 de junio de 2014

Utilizando Apache Shiro para la seguridad en una aplicacion Web en Java

Como hemos visto en el post anterior, Apache Shiro es un  framework potente, ligero y fácil de usar que nos permite incorporar aspectos de seguridad a nuestras aplicaciones Java.


Para comenzar a utilizarlo deberás añadir las siguientes dependencias en el pom de tu proyecto.

<dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.2.2</version>
</dependency>

Un ServletFilter y un listener deben de ser registrados en el archivo web.xml. Estos serán usado por Shiro para interceptar todas las peticiones http a la aplicación y hacer una verificación de los permisos para el usuario actual. 


<listener>
<listener-class>
        org.apache.shiro.web.env.EnvironmentLoaderListener
</listener-class>
</listener>

 <filter>

    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>

<filter-mapping>

    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/jsp/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

Aunque Shiro nos ofrece diversas formas de definir nuestras reglas de auntentificación y autorización, en este ejemplo utilizaremos un archivo .ini. Esta variante es muy flexible pues como ya se ha dicho, permite declarar las reglas en un archivo externo a la aplicación, de una forma declarativa, haciendo que su modificación futura pueda ser realizada incluso sin realizar una recompilación.

En el archivo .ini. definiremos los usuarios, roles y permisos. También podremos controlar el acceso a cada una de las URL que referencien a páginas de nuestra aplicación, ya sea mediante roles o permisos que tambien pueden ser declarados aquí. Deberá de estar ubicado en algún lugar dentro del classpath de nuestra aplicación

A continuación veremos un definición de cada una de las secciones del mismo

[main]Configuración de los aspectos principales. 
Por ejemplo: La URL por defecto para las peticiones no autorizadas 
y la pagina a navegar por defecto.
authc.loginUrl = /jsp/login.xhtml
authc.successUrl = /jsp/index.xhtml

[users]-->Sección usada para asignar roles a los usuarios
admin=LECTOR
admin=EDITOR

[roles]----> Seccion para asignar permisos a los roles
LECTOR=nota:leer
EDITOR=nota:crear,modificar

[urls]----> Aqui se definen los permisos de cada URL de acuerdo a roles

que tenga el usuario actual, o a permisos que tengan uno de estos roles,
entre muchas otras posibilidades
/jsp/listaNotas.xhtml=perms["nota:leer"]
/jsp/insertarNota.xhtml=roles["EDITOR"]
/jsp/** = authc

Aunque la mayoría de los casos posibles de autorización quedan cubiertos en el archivo ini, algunas veces es necesario ir un poco mas lejos y restringir el acceso solo a determinados componentes dentro de una pagina y no a esta como un todo. Para conseguir esto Shiro posee la clase Subject que brinda detalles sobre el usuario actual y sus permisos a traves de métodos tales como isPermitted(String permiso) o isUserInRole(String role).

Armado con este pequeño mini tutorial es posible configurar nuestra primera aplicación para comenzar a usar el framework Apache Shiro. En proximas entradas contare como beneficiarse de su condición de framework open source y modificar su funcionalidad adaptandola a posibles situaciones, a traves de los multiples puntos de extension que posee.



domingo, 25 de mayo de 2014

Introducción a Apache Shiro

Apache Shiro es un framework escrito en el lenguaje Java pensado para ser usado en las tareas mas comunes relacionadas con la seguridad. Estas vendrían a ser Autenticación, Autorización, Encriptación y manejo de sesiones. Es un proyecto bastante maduro y estable y fue aceptado por la Apache Software Foundation en 2008 y ha sido un proyecto de primer nivel dentro de dicha fundación desde 2010.

Las características mas valorables que posee Shiro que lo hacen ser mas popular que los frameworks con los que compite en el panorama de seguridad (por ejemplo JAAS) para aplicaciones Java son:


  • Es simple y fácil de usar.
  • Fue concebido pensando en ser usado en aplicaciones Webs desde el inicio (JAAS por ejemplo comprende áreas tan especificas como la seguridad a nivel de maquina virtual lo que lo hacen mucho mas complejo )
  • Casi todos los aspectos de seguridad pueden ser definidos de manera declarativa en un archivo externo haciéndolo modificable incluso en aplicaciones desplegadas. Cuando la autorización se realiza programaticamente es basada en anotaciones en POJOS lo que hace que se necesite menos código de configuración
  • Las posibilidades que ofrece para declarar los aspectos de la autorización son muy especificas y flexibles a la vez. Permite Autorización basada en roles y permisos.
  • Brinda una capa de Manejo de Sesiones propia, solapando la que pudiese tener el contenedor de la aplicación. De igual forma Shiro soporta la clusterización de sesiones, de manera que si la sesión fuese propagada entre clusteres, la persistencia de los datos de sesión es manejada por el framework.
Hasta aquí esta corta introducción a Shiro. En un próximo post explicaré como comenzar a utilizarlo en un proyecto desde cero.


viernes, 9 de mayo de 2014

Usando Hibernate para acceder a DB2. Lecciones aprendidas

A la hora de desarrollar una aplicación Java con Hibernate y que acceda a una base de datos DB2 se deben tener en cuenta ciertas características de esta Base de Datos que la hacen especial y pueden ocasionar ciertos problemas.

1)DB2 siempre añade espacios al final de las cadenas.

Solución:
Se puede crear un UserType de Hibernate que actue como "traductor" entre las cadenas recién extraídas mediante la consulta SQL y el String final que se obtendrá en Java. Mas detalles de este tema pueden ser vistos en el articulo previo.

2)Se obtiene un 

THE STRING REPRESENTATION OF A DATETIME VALUE IS NOT A VALID DATETIME VALUE

DB2 SQL Error: SQLCODE=-181, SQLSTATE=22007, SQLERRMC=null, DRIVER=3.62.56 

cuando se intenta guardar una fecha de tipo java.util.Date

Solución:
Hibernate y su dialecto para DB2 mapean los objetos de tipo java.util.Date como Sql Timestamp. Esto tiene como consecuencia que se produce un desbordamiento al almacenar la fecha en una columna de tipo Date debido a que los Timestamp poseen mayor precision que Date. La solución es indicar de alguna forma en la entidad de Java que no se desea que implicitamente se mapee a Timestamp. Esto puede ser conseguido de varias maneras. 

Una de ellas sería cambiar el tipo de las fechas en la entidad a java.sql.Date, aunque esto implicaría que nuestro modelo de datos fuese dependiente de las fechas del package sql. 

La otra y mas recomendable es añadir @Type(type="date") a la fecha declarada en la entidad. Esto modificaría la conversión por defecto haciendo que en lugar de enviar un Timestamp al Sql se envie un Date.

viernes, 18 de abril de 2014

Solución del problema de los Lucky Tickets

Entre muchas aficiones que tengo, una de ellas es resolver problemas de algoritmia. Soy usuario activo de jurados en línea que no son mas que sitios con bancos de problemas en los cuales el usuario puede registrarse y enviar sus soluciones para que un evaluador automático las califique y de esa forma poder competir con programadores de todo el mundo.

A pesar de haber resuelto mucho ejercicios de este tipo, a menudo me encuentro con que a pesar de haber pasado mucho tiempo pensando en una solución y finalmente de haberla encontrado, al poco tiempo no la recuerdo. Es por esto que me he propuesto la meta de publicar aquí cada solución que envié con una breve explicación de manera que el algoritmo quede descrito para el futuro.

En esta ocasión he resuelto el problema de los Lucky Tickets en lenguaje C#.

using System;
using System.Collections;

class Program
    {
        static void Main(string[] args)
        {
            int x = int.Parse(Console.ReadLine());
            Ticket(x);
         }
        public static void Ticket(int cifras)
        {
            int mitad = cifras / 2;
            int result = 0;
         
            for (int i = 0; i <= 9 * mitad; i++)
            {
                int sum=SumanK(i,mitad);
                result += sum * sum;
            }
            Console.WriteLine(result);
        }

        private static int SumanK(int k, int cifras)
        {
            if (cifras == 1 && k<10)
            {
                return 1;
            }
            int min = Math.Min(k, 9);
            int max = Math.Max(0, k - 9 * (cifras - 1));
            int count = 0;

            for (int i = max; i <= min; i++)
            {
                count=count+SumanK(k - i, cifras - 1);  
            }
            return count;
        }     
    }

Solución:
La solución puede dividirse en 2 partes muy simples. La primera es detectar cuantas distintas sumas existen donde los sumandos sean los dígitos de números de n/2 cifras. De esta forma para por ejemplo n=6, n/2=3--> 9*9*9=27. Las posibles sumas de los dígitos de números de 3 dígitos vas desde 0 hasta 27.

Dicho, esto solo tendríamos que encontrar para cada uno de estos valores de posible suma, que combinaciones de digitos hacen que se pueda obtener la misma. Para esto se usa el algoritmo recursivo implementado mediante la función SumanK, que funciona de esta forma:

Dadas la cantidad de cifras c y el numero a obtener como suma k:

  • Si c=1 y k<10 se devuelve 1
  • Si c>1 
  • El mínimo dígito posible para la ultima posición sería el Máximo entre 0 y k-9*Todos los dígitos restantes
  • El máximo dígito posible sería el mínimo entre k y 9
  • Iterando desde mínimo hasta máximo (valor i) y se calcula recursivamente cuantos números suman k-i y tienen una cifra menos.

lunes, 24 de febrero de 2014

Creando un UserType de hibernate que se aplique a todas las entidades.

Hibernate posee un mecanismo muy potente para controlar la manera en que ocurre la conversion de valores obtenidos de la fuente de datos a valores Java. Se trata de los UserTypes. Por ejemplo, si utilizas una Base de Datos muy antigua que mantiene siempre al final de cada cadena espacios en blanco hasta rellenar la longitud de la columna, mediante un UserType puedes redefinir la manera en que la cadena es leida de la base de datos y escrita en el String de destino de Java. Para manipular dicho proceso basta con implementar una clase heredera que redefina algunos métodos y dejar que el framework realice el resto

package es.hib.usertype;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;

public class TrimmedString implements UserType {

public TrimmedString() {
super();
}

@Override
public int[] sqlTypes() {
return new int[] { Types.VARCHAR };
}

@Override
public Class returnedClass() {
return String.class;
}

@Override
public boolean equals(Object x, Object y) throws HibernateException {
return (x == y) || (x != null && y != null && (x.equals(y)));
}

@Override
public Object deepCopy(Object value) throws HibernateException {
if (value == null)
return null;
return new String((String) value);
}

@Override
public boolean isMutable() {
return false;
}

@Override
public Object assemble(Serializable cached, Object owner) throws

 HibernateException {
return cached;
}

@Override
public Serializable disassemble(Object obje) throws 

HibernateException {
return (Serializable) obje;
}

@Override
public int hashCode(Object obje) throws HibernateException {
return obje.hashCode();
}

@Override
public Object nullSafeGet(ResultSet rs, String[] names, 

SessionImplementor arg2, Object owner) throws HibernateException,
SQLException {

String val = rs.getString(names[0]);

if (null == val) {

return (null);
}
return val.trim();
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, 

int index, SessionImplementor arg3) throws HibernateException,
SQLException {
st.setString(index, (String) value);
}

@Override
public Object replace(Object original, Object target, Object owner) 

throws HibernateException {

return original;
}
}

Ya teniendo el UserType implementado puede decidirse si usarse localmente

@Type(type="es.hib.usertype.TrimmedString")
@Column(name = "DESCRIPCION")
private String descripcion;

o globalmente, o sea, aplicarlo a la conversion de todos los VARCHAR en String de la aplicación. Para esto se debe crear en el paquete que contiene las entidades el siguiente archivo package-info.java. El mismo es una anotación a nivel de paquetes que surtirá el mismo efecto que si se anotaran una a una todas las clases del paquete dado.

@org.hibernate.annotations.TypeDef(name = "trimmedString", 
defaultForType = String.class, typeClass = TrimmedString.class)
package es.hib.entities;

import es.hib.usertype.TrimmedString;

Después de creado el package-info.java, ya solo se necesita que hibernate lo note. Para esto debe de agregarse,  programaticamente cuando se esten cargando las configuraciones añadir la siguiente sentencia.

configuration.addPackage("es.hib.entities")


domingo, 9 de febrero de 2014

Concept Net

ConceptNet es una base de conocimiento de sentido común integrada con un paquete de herramientas de lenguaje natural, concebido en el Instituto tecnológico de Massachusetts por [Liu y Singh, 2004]. El término sentido común se utiliza para expresar la manera en que fue y es construido dicho recurso. El mismo se basa en el proyecto Open Mind Common Sense que posee un sitio en Internet en el que cualquier persona puede definir en su propio idioma, hechos o relaciones, sobre los conceptos que desee, en forma de rellenado la línea, por ejemplo: “The effect of Eating food is_______”. Como puede notarse, el sitio brinda a los usuarios una oración con una estructura definida por ellos y la persona debe llenar el espacio aportando su conocimiento en particular de algún tema.


Aprovechando que en el idioma inglés existen mas de un millón de hechos definidos en Open Mind Common Sense, lo autores construyeron de manera automática esta red semántica, mediante la extracción de las relaciones reflejadas en los hechos por intermedio de reglas definidas, así como la inferencia de nuevas reglas dadas algunas muy generalizadas, como por ejemplo:

[(IsA “apple” “fruit”); (IsA “banana” “fruit”); (IsA “peach” “fruit”)]
AND
[(PropertyOf “apple” “sweet”); (PropertyOf “banana” “sweet”);
PropertyOf “peach” “sweet”)]
IMPLIES (PropertyOf “fruit” “sweet”)


O sea, si varias frutas como la manzana, el banano o el melocotón son dulces, esto implica que las frutas son dulces en general. Estas técnicas utilizadas evitaron tener que utilizar a varios expertos en lingüística para etiquetar a mano sus datos.


Para tener una idea mejor, comparándola con WordNet, se puede decir que la misma, presenta casi la totalidad de las funcionalidades que el anterior, pero se diferencia en que está mucho mas orientada al concepto que a los aspectos lingüísticos debido a que presenta un número mayor de conexiones conceptuales, lo que permite que sea usada para hacer inferencias de sentido común sobre textos reales, descubrimiento de afecto y resolución de analogías entre muchas otras.
Fragmento de la red semántica de ConceptNet

 Como ya se ha mencionado la estructura de ConceptNet está almacenada en forma de ontología conceptual, incluyendo un gran número de relaciones. A continuación puede ser visto un mapa de dicha ontología donde se señala cada relación en particular, la clase a la que pertenece y además el tamaño que abarca cada una dentro de la red semántica.

Mapa de la ontología conceptual de ConceptNet con los tipos de relaciones y el tamaño aproximado de las mismas incluídos

Introduccion a WordNet

WordNet es una sistema de referencia léxico para el idioma inglés. Su diseño, a diferencia de la mayoría de los diccionarios actuales que organizan las palabras de acuerdo a la manera en que se escriben, esta inspirado en las teorías que sostienen los psico-lingüistas sobre la organización de la memoria léxica en los humanos. En la medida en que se comenzaron a utilizar las computadoras para realizar tareas de los diccionarios, se hizo evidente que con la gran capacidad de cálculo de las mismas, sería un desperdicio contemplar solo la posibilidad de que fueran meras buscadoras por índice, sino que debía incorporársele más funcionalidad y es a esto lo que se orienta esta herramienta.


WordNet fue concebido alrededor del año 1985 cuando un grupo de investigadores del Laboratorio de Ciencias Cognitivas de la Universidad de Princenton se dieron a la tarea de crear una base de datos léxica, que a diferencia de los diccionarios convencionales presenta una organización subdividida en sustantivos, verbos, adjetivos y adverbios. Uno de sus aportes principales fue la creación de los synsets que constituyen conjuntos de palabras cuyo significado es el mismo, o sea, sinónimos. Es necesario aclarar que como una palabra puede tener varios significados, así también puede estar contenida en más de un synset. WordNet almacena además para cada synset una pequeña glosa, texto explicativo del significado del mismo, así como diversas relaciones semánticas.

Sustantivos:

  • Hiperónimos: Y es un hiperonimo de X si cada X es un tipo de Y (Ejemplo: Y=animal, X=perro)
  • Hipónimos: Y es un hipónimo de X si Y es un tipo de X (Ejemplo: Y=perro, X=animal)
  • Terminos Coordinados: X y Z son términos coordinados si comparten un Hiperonimo en común (X=perro, Z=gato, Y=animal)
  • Holónimo: Y es un holónimo de X si X es parte de Y (Ejemplo X=neumático, Y=coche)
  • Merónimo: Y es un jerónimo de X si Y es parte de X (Ejemplo X=coche, Y=neumático)
Verbos:
  • Hiperónimo: Y es un hiperónimo de X si la actividad de X es un tipo de la actividad de Y (Ejemplo X=percibir,Y=oir)
  • Tropónimo: Y es un tropónimo de X si la actividad Y realiza X de alguna forma (Ejemplo: X=balbucear,Y=hablar)
  • Vinculación(entailment): Y esta vinculado con X si para hacer X obligatoriamente se deberá hacer Y (Ejemplo: X=roncar, Y=dormir)
  • Términos coordinados: Y y X son coordinados si tienen un Hiperónimo en común. (Ejemplo: X=susurrar, Y=vocear)

Adjetivos:
  • Sustantivo al que modifican
  • Adjetivos similares
  • Participio del verbo

Adverbios:
  • Adjetivos que lo originan
Por relaciones semánticas en este caso, se entiende una conexión synset-synset con una etiqueta que define el tipo de la relación. Cada synset puede tener varias relaciones de acuerdo a la categoría del mismo, de aquí las relaciones contempladas por WordNet. La estructura del conocimiento almacenado en wordnet varía según la categoría. Los sustantivos y verbos al poseer la relación de hiperonmia o EsUn() pueden ser organizados jerárquicamente. La jerarquía va tendiendo hacia conceptos cada vez más primitivos hasta llegar al nodo raíz.

 dog, domestic dog, Canis familiaris
    => canine, canid
       => carnivore
         => placental, placental mammal, eutherian, eutherian mammal
           => mammal
             => vertebrate, craniate
               => chordate
                 => animal, animate being, beast, brute, creature, fauna
                   => ...

Los que se consideran sinónimos aparecen en el mismo nivel de la jerarquía. Es conocido que el árbol de los sustantivos es bastante profundo pues contiene gran cantidad de términos intermedios cuyo uso práctico es bien limitado, pero que son útiles para la categorización de otros mas usados. Por su parte la de los verbos tiene menos niveles de profundidad.

Para los adjetivos es imposible crear una estructura del conocimiento arbórea debido a que los mismos no poseen la mencionada relación. En lugar de eso, se forman diversos clústeres bipolares en donde son organizados sinónimos y antónimos con respecto a un atributo que los mismos modifican. El ejemplo siguiente corresponde al cluster Wetness y contiene los cúmulos de adjetivos contrarios húmedo/seco.

WET1,DRY1,! bedewed,& boggy,& clammy,& damp,& drenched,&
drizzling,&hydrated,&muggy,& perspiring,& saturated2,&
showery,&tacky,&tearful,& watery2,& WET2,&  
 bedewed,dewy,wet1,& 
 boggy,marshy,miry,mucky, muddy, quaggy, swampy, wet1,&  
 clammy,dank,humid1,wet1,&  
 damp,moist,wet1,& 
 drenched,saturated1,soaked, soaking, soppy, soused, wet1,&  
 drizzling,drizzly,misting, misty, wet1,&  
 hydrated,hydrous,wet1,& ((chem) combined with water molecules)  
 muggy,humid2,steamy,sticky1, sultry, wet1,&  
 perspiring,sweaty,wet1,& 
 saturated2,sodden,soggy,waterlogged, wet1,&  
 showery,rainy,wet1,& 
 sticky2,tacky,undried,wet1,& ("wet varnish")  
 tearful,teary,watery1,wet1,&  
 watery2,wet1,&(filledwith water; "watery soil")    DRY1,WET1,! anhydrous,&arid,&
dehydrated,& dried,& dried-up1,&
dried-up2,&DRY2,&rainless,& thirsty,&  
anhydrous,dry1,&((chem) with all water removed)  
arid,waterless,dry1,& 
dehydrated,desiccated,parched, dry1,&  
dried,dry1,&("theinkis dry")  
dried-up1,dry1,&("adry water hole")  
dried-up2,sere,shriveled, withered, wizened, dry1,&
(usedofvegetation) 
rainless,dry1,& 
thirsty,dry1,&  

Visto todo lo anterior se puede mencionar que una de las principales desventajas atribuidas a WordNet es que a pesar de que presenta una gran cantidad de significados de dominio general, muchas veces es insuficiente cuando trata de aplicarse a un dominio específico debido a que no tiene bien cubierto el mismo. En base a esto se ha apreciado en diversas ocasiones como se ha extendido de cierta manera WordNet mediante la creación de ontologías que la abarcan y añaden conocimientos extra de algún dominio en particular tal es el caso de SENSUS.

Resumiendo al decir de creadores de la herramienta que será detallada a continuación. Word Net es posiblemente el recurso semántico de uso mas extendido por la comunidad de Lingüística computacional en el día de hoy, y todo probablemente por la buena organización de su estructura y su facilidad de uso.

Probar WordNet Online