Tabla de contenidos:
GroupJoin
El operador GroupJoin nos permite establecer una relación entre 2 secuencias y agrupar los resultados. Básicamente agrupa un elemento de la secuencia externa a una colección de elementos de la secuencia interna.
GroupJoin estándar:
Código necesario:
Una clase Provincia:
public class Provincia
{
public string Nombre { get; set; }
public Provincia(string nombre)
{
Nombre = nombre;
}
}
Una clase Ciudad:
public class Ciudad
{
public string Nombre { get; set; }
public Provincia Localizacion { get; set; }
public Ciudad(string nombre, Provincia localizacion)
{
Nombre = nombre;
Localizacion = localizacion;
}
}
Creamos varias provincias y varias ciudades:
Provincia cadiz = new Provincia("Cádiz");
Provincia malaga = new Provincia("Málaga");
Provincia madrid = new Provincia("Madrid");
Provincia barcelona = new Provincia("Barcelona");
Ciudad algeciras = new Ciudad("Algeciras", cadiz);
Ciudad jerez = new Ciudad("Jerez", cadiz);
Ciudad ronda = new Ciudad("Ronda", malaga);
Ciudad churriana = new Ciudad("Churriana", malaga);
Ciudad alcobendas = new Ciudad("Alcobendas", madrid);
Ciudad fuenlabrada = new Ciudad("Fuenlabrada", madrid);
Y las metemos en listas:
List<Provincia> listaProvincias = new List<Provincia> {cadiz, malaga, madrid, barcelona};
List<Ciudad> listaCiudades = new List<Ciudad> {algeciras, jerez, ronda, churriana, alcobendas, fuenlabrada};
Volver al operador GroupJoin estándar
Definición:
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector )
El operador recibe varios parámetros:
Recibe una secuencia la cual llamaremos secuencia interna (La externa es en la que aplicamos el operador).
Recibe un delegado que usaremos para extraer la clave de la secuencia externa.
Recibe un delegado que usaremos para extraer la clave de la secuencia interna.
El ultimo delegado lo usaremos para crear la enumeración que saldrá como resultado al combinar un elemento de la secuencia externa con una colección de elementos de la secuencia interna.
El operador devuelve la enumeración resultante.
Vamos a verlo con un ejemplo. Queremos crear una enumeración donde agruparemos las provincias con una colección de ciudades que están dentro de dicha provincia:
var query = listaProvincias.GroupJoin(listaCiudades,
provincia => provincia,
ciudades => ciudades.Localizacion,
(provincia, ciudades) =>
new {provincia, ciudades});
Por pasos:
El primer parámetro es la secuencia interna, en este caso la lista de ciudades (La externa es la lista de provincias).
La clave externa será el elemento en si (de la secuencia externa).
La clave interna será la propiedad Localizacion de cada ciudad.
Luego creamos un tipo anonimo que consistirá en una provincia mas una lista de ciudades.
Parece complicado pero no lo es, simplemente colocamos como clave los 2 elementos que harán la unión, en este caso un objeto Provincia con otro objeto Provincia dentro de cada ciudad. Así que simplemente cada provincia estará asociada a una lista de ciudades que la tengan como provincia.
Como alternativa, podemos usar la sintaxis de los query expression:
var query = from ciudad in listaCiudades
group ciudad by ciudad.Localizacion
into ciudades
select new {ciudades.Key.Nombre, ciudades};
Por cada ciudad de la lista, meterlas en un grupo que tengan la Localización en común. Llamar a ese grupo ciudades. De ahí creamos un tipo anónimo con el nombre de la clave (es el elemento que hemos usado para hacer la unión, en este caso la provincia) y la lista de ciudades de dicho grupo.
Es lo mismo pero con la sintaxis alternativa que nos da algunos operadores de LINQ.
En ambos casos el resultado podría ser así:
Cádiz - (Algeciras, Jerez)
Málaga - (Ronda, Churriana)
Madrid - (Alcobendas, Fuenlabrada)
Barcelona - ()
GroupJoin + comparador personalizado:
Código necesario:
Una lista de iniciales y otra de palabras:
List<string> listaIniciales = new List<string> {"A", "B", "C", "D"};
List<string> listaPalabras = new List<string> {"Arroz", "Antena", "Barco", "Caballo", "Decir"};
Un comparador personalizado:
public class ComparadorIniciales : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x[0] == y[0];
}
public int GetHashCode(string obj)
{
return obj[0].GetHashCode();
}
}
Volver al operador GroupJoin + comparador personalizado
Definición:
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga hace lo mismo que el operador estándar, simplemente nos da la posibilidad de usar un comparador personalizado.
En el anterior ejemplo, habiamos simplemente comparado 2 instancias de por si iguales, ahora vamos a poner un ejemplo donde necesitaremos un comparador personalizado.
Tenemos una lista con iniciales y una lista de palabras. Queremos hacer una unión donde cada inicial estará asociada a una colección de palabras que empiecen por dicha inicial.
El código sería:
var query = listaIniciales.GroupJoin(listaPalabras,
inicial => inicial,
palabras => palabras,
(inicial, palabras) =>
new {inicial, palabras},
new ComparadorIniciales());
La forma de usarlo es la misma que con el operador estándar, simplemente añadimos una instancia de nuestro comparador personalizado que hará el trabajo de comprobar si una inicial y una palabra están relacionadas.
Por ser la primera vez que usamos un comparador personalizado, lo explicaré un poco.
El comparador ha de implementar IEqualityComparer
Por cada elemento comprobará el hashcode y cuando 2 elementos tienen el mismo hashcode llamará a equals para comprobar si esos dos elementos son iguales.
Por otro lado, cabe decir que jamás de los jamases usaríamos esta sobrecarga para hacer un trabajo como este puesto que el operador estandar es capaz de hacer una unión de este tipo o directamente usar otro operador como SelectMany. Simplemente esto es un ejemplo para que veais como implementar un comparador personalizado y usarlo.
Finalmente el resultado es algo tipo:
A - (Arroz, Antena)
B - (Barco)
C - (Caballo)
D - (Decir)
Volver a la tabla de contenidos
Join
El operador Join sirve para establecer una relación entre 2 secuencias comparando sus claves.
Join estándar:
Código necesario:
Una clase Provincia:
public class Provincia
{
public string Nombre { get; set; }
public Provincia(string nombre)
{
Nombre = nombre;
}
}
Una clase Ciudad:
public class Ciudad
{
public string Nombre { get; set; }
public Provincia Localizacion { get; set; }
public Ciudad(string nombre, Provincia localizacion)
{
Nombre = nombre;
Localizacion = localizacion;
}
}
Creamos varias provincias y varias ciudades:
Provincia cadiz = new Provincia("Cádiz");
Provincia malaga = new Provincia("Málaga");
Provincia madrid = new Provincia("Madrid");
Provincia barcelona = new Provincia("Barcelona");
Ciudad algeciras = new Ciudad("Algeciras", cadiz);
Ciudad jerez = new Ciudad("Jerez", cadiz);
Ciudad ronda = new Ciudad("Ronda", malaga);
Ciudad churriana = new Ciudad("Churriana", malaga);
Ciudad alcobendas = new Ciudad("Alcobendas", madrid);
Ciudad fuenlabrada = new Ciudad("Fuenlabrada", madrid);
Y las metemos en listas:
List<Provincia> listaProvincias = new List<Provincia> {cadiz, malaga, madrid, barcelona};
List<Ciudad> listaCiudades = new List<Ciudad> {algeciras, jerez, ronda, churriana, alcobendas, fuenlabrada};
Volver al operador Join estándar
Definición:
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector )
El operador recibe varios parámetros:
Recibe una secuencia la cual llamaremos secuencia interna (La externa es en la que aplicamos el operador).
Recibe un delegado que usaremos para extraer la clave de la secuencia externa.
Recibe un delegado que usaremos para extraer la clave de la secuencia interna.
El ultimo delegado lo usaremos para crear la enumeración que saldrá como resultado al combinar un elemento de la secuencia externa con un elemento de la secuencia interna.
El operador devuelve la enumeración resultante.
Por ejemplo, queremos una secuencia compuesta por una provincia y una ciudad que pertenezca a esa provincia:
var query = listaProvincias.Join(listaCiudades,
provincia => provincia,
ciudad => ciudad.Localizacion,
(provincia, ciudad) =>
new {provincia, ciudad});
A diferencia del operador GroupJoin aquí se creará un elemento por cada relación. Eso quiere decir, que si tenemos 2 ciudades de Cádiz, saldrán 2 entradas en vez de 1 con varios elementos.
Esto es lo que comunmente se llama un Inner Join que básicamente es encontrar la relación entre 2 secuencias, y cada elemento de las 2 secuencias que se relacionen irán a parar a una nueva enumeración.
Podemos representar un Inner Join usando la sintaxis de un query expression:
var query = from provincia in listaProvincias
join ciudad in listaCiudades on provincia equals ciudad.Localizacion
select new {provincia, ciudad};
Tiene un extraño parecido a un Inner Join en SQL ¿Verdad?
En ambos casos el resultado podría ser del tipo:
Cádiz - Algeciras
Cádiz - Jerez
Málaga - Ronda
Málaga - Churriana
Madrid - Alcobendas
Madrid - Fuenlabrada
Join + comparador personalizado:
Código necesario:
Una lista de iniciales y otra de palabras:
List<string> listaIniciales = new List<string> {"A", "B", "C", "D"};
List<string> listaPalabras = new List<string> {"Arroz", "Antena", "Barco", "Caballo", "Decir"};
Un comparador personalizado:
public class ComparadorIniciales : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x[0] == y[0];
}
public int GetHashCode(string obj)
{
return obj[0].GetHashCode();
}
}
Volver al operador Join + comparador personalizado
Definición:
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga hace lo mismo que el operador estándar, simplemente nos da la posibilidad de usar un comparador personalizado.
En el anterior ejemplo, habiamos simplemente comparado 2 instancias de por si iguales, ahora vamos a poner un ejemplo donde necesitaremos un comparador personalizado.
Tenemos una lista con iniciales y una lista de palabras. Queremos hacer una unión donde cada inicial estará asociada a una palabras que empiecen po dicha inicial:
var query = listaIniciales.Join(listaPalabras,
inicial => inicial,
palabra => palabra,
(inicial, palabra) =>
new {inicial, palabra},
new ComparadorIniciales());
Si necesitas saber como crear y usar un comparador revisa la definición del operador GroupJoin.
El resultado es:
A - Arroz
A - Antena
B - Barco
C - Caballo
D - Decir
Volver a la tabla de contenidos
Tags: LINQ

