View Full Version : [LINQ] analisi di matrici
^TiGeRShArK^
16-07-2008, 20:53
Finora con linq ho lavorato prettamente con dati sequenziali, ma come dovrebbe funzionare in caso di matrici n-dimensionali?
E' possibile utilizzarlo efficacemente o è limitato solo a collection uni-dimensionali?
Ho fatto una rapida prova con una matrice di interi con questo codice:
int[][] matrix = {
new int[]{1,2,3},
new int[]{4,5,6},
new int[]{7,8,9}};
var result = matrix.Select(row => row.Where(value => value > 5));
foreach (var r in result) r.ForEach(Console.WriteLine);
Che da il risultato voluto, ovvero restituisce i numeri 6, 7, 8 e 9, però ho dovuto usare una Select sulla matrice.
Così facendo in pratica ho ricevuto 3 oggetti Enumerable, mentre ad esempio del primo non saprei che farmene dato che non contiene nessun oggetto maggiore di 5.
Avevo provato inizialmente ad usare un altro Where al posto del Select ma non buildava...
Quale sarebbe quindi la soluzione migliore (se esiste) per lavorare su dati di tipo matriciale con Linq?
cdimauro
17-07-2008, 07:13
A naso sembra che lavori sempre con dati sequenziali, come anche il tuo esempio dimostra.
Non mi sembra un limite. Tra l'altro finora non m'è mai capitato di lavorare con matrici (omogenee) multidimensionali.
Si', lavora su elementi monodimensionali, ma a tutto c'e' una soluzione.
Ti passo qui 3 modi di risolvere il problema, con l'extension a corredo
Il primo e' piu' simile al tuo, ma con la SelectMany eviti di dover usare un terzo enumeratore.
In pratica la SelectMany e' la UNION ALL dell'SQL, che unisce insieme gli elementi di una sottoenumerazione.
Il secondo fa uso di una Matrice Multidimensionale, con a corredo un AsEnumerable per poter ciclare sugli elementi.
Dovrebbe essere la soluzione migliore per le matrici in C#, dato che rango della matrice e lunghezza di ciascuna dimensione sono fissi e il compilatore lo sa a compile-time.
Il terzo e' di nuovo simile al primo, ma usa una doppia enumerazione creata al volo per accedere ai singoli elementi.
Ovviamente non funziona se la matrice e' atipica (con lunghezza di righe diversa)
In pratica mette in prodotto cartesiano le due enumerazioni, costruendo quindi ogni coppia possibile di X e Y.
Sembra essere la soluzione preferita in caso di computo di grosse moli di lavoro per ciascun elemento, dato che puo' essere resa parallelizzabile semplicemente se al posto della prima Enumerable usi una ParallelEnumerable. Ma non ho fatto studi di fattibilita' in tal senso sugli altri esempi.
static class Program
{
static void Main(string[] args)
{
int[][] matrix = {
new int[]{1,2,3},
new int[]{4,5,6},
new int[]{7,8,9}};
var result = matrix.SelectMany(row => row.Where(value => value > 5));
result.ForEach(Console.WriteLine);
Console.ReadKey();
}
static void Main2(string[] args)
{
int[,] matrix = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
var result = matrix.AsEnumerable().Where(t => t > 5);
result.ForEach(Console.WriteLine);
Console.ReadKey();
}
static void Main3(string[] args)
{
int[][] matrix = {
new int[]{1,2,3},
new int[]{4,5,6},
new int[]{7,8,9}};
int rank = matrix.Rank;
int dim1 = matrix.Length;
int dim2 = matrix[0].Length;
var result = from y in Enumerable.Range(0, dim1)
from x in Enumerable.Range(0, dim2)
let val= matrix[y][x]
where val>5
select val;
result.ForEach(Console.WriteLine);
Console.ReadKey();
}
static IEnumerable<T> AsEnumerable<T>(this T[,] domain)
{
foreach (T elem in domain)
yield return elem;
}
static void ForEach<T>(this IEnumerable<T> domain, Action<T> action)
{
foreach (T t in domain) action(t);
}
}
Piu' interessante potrebbe essere costruire un metodo tipo:
Data una matrice bidimensionale, restituire una matrice bidimensionale di pari geometria, ma con valorizzate solo le celle che soddisfano un determinato filtro.
Es: elemento>5
Con anche il filtro passato come parametro, diverso di volta in volta.
Ho provato, possono venire fuori cose interessanti.
^TiGeRShArK^
17-07-2008, 13:24
Il metodo + interessante mi sembra così ad occhio il secondo. :p
Nel terzo metodo ho scoperto le doppie enumerazioni che ancora non avevo mai visto :mbe:
ora vedo se riesco a restituire la matrice originale con valorizzati i parametri che soddisfano la condizione espressa nel filtro :p
^TiGeRShArK^
17-07-2008, 14:02
l'implementazione + semplice che mi è venuta in mente è questa:
class Program
{
static void Main(string[] args)
{
int[][] matrix = {
new int[]{1,2,3},
new int[]{4,5,6},
new int[]{7,8,9}};
var result = matrix.Select(row => row.Select(value => value > 5 ? value : 0));
foreach (var row in result)
{
foreach (var value in row)
{
Console.Write(value);
}
Console.WriteLine();
}
Console.ReadLine();
}
ce ne sono di migliori che mi sono sfuggite? :p
Il requisito che avevo in mente era una funzione che, dato in ingresso una matrice tipo int [][]
restituisse di nuovo un oggetto matrice, con la stessa geometria, applicando il filtro agli elementi.
Intendo una matrice vera, non solo a video :D
^TiGeRShArK^
17-07-2008, 14:13
Il requisito che avevo in mente era una funzione che, dato in ingresso una matrice tipo int [][]
restituisse di nuovo un oggetto matrice, con la stessa geometria, applicando il filtro agli elementi.
Intendo una matrice vera, non solo a video :D
ah ok :asd:
ora non ho tempo che ho chiuso parallels e devo scappare al lavoro :p
gli do un occhiatina stasera :D
^TiGeRShArK^
17-07-2008, 23:16
non riesco a fare andare il predicate :fagiano:
class Program
{
static void Main(string[] args)
{
int[][] matrix = {
new int[]{1,2,3},
new int[]{4,5,6},
new int[]{7,8,9}};
int[][] result = matrix.Filter(value => value > 5);
foreach (int[] row in result) {
foreach (int value in row)
{
Console.Write(value);
}
Console.WriteLine();
}
Console.ReadLine();
}
}
mi da errore nella riga in bold dicendo:
"Error 1 The type arguments for method 'ProvaLinq.Extensions.Filter<TSource>(int[][], System.Func<int,bool>)' cannot be inferred from the usage. Try specifying the type arguments explicitly. C:\Visual Studio 2008\Projects\ProvaLinq\ProvaLinq\Program.cs 18 30 ProvaLinq"
l'extension method è il seguente:
public static int[][] Filter<TSource>(this int[][] matrix, Func<int, bool> predicate)
{
return matrix.Select(row => row.Select(value => predicate(value) ? value : 0).ToArray<int>()).ToArray<int[]>();
}
Ovviamente senza predicate riesco a farla funzionare, ma ancora come utilizzare i delegates durante l'implementazione devo capirlo... :stordita:
^TiGeRShArK^
18-07-2008, 09:58
ce l'ho fatta..
a mente libera ho risolto il problema :D
public static int[][] Filter(this int[][] matrix, Func<int, bool> predicate)
{
return matrix.Select(row => row.Select(value => predicate(value) ? value : 0).ToArray<int>()).ToArray<int[]>();
}
certo che però ancora non ho capito proprio tutto di queste funzioni implementate in questo modo... :stordita:
Giusto, provo a postare le mie 2 versioni di Generics Extension, che si possono applicare a tutte le matrici bidimensionali.
La sostanza principale era quella di dotare la funzione di un funzionale di filtro (il tuo Func<int,bool>) da poter applciare dinamicamente, e che puo' essere risolto dall'utilizzatore mediante una comodissima lambda function.
Per matrice bidimensionale ho inteso proprio la matrice C# con la sintassi [y,x] e non quella tipica del C e C++ come la [y][x]
Ovvio che si possono portare anche per la versione piu' vecchia.
La prima versione e' iterativa, la seconda e' funzionale.
Per questo caso penso che avrei scelto la iterativa.
public static T[,] GetFiltered<T>(this T[,] domain, Func<T, bool> pass)
{
int dy = domain.GetLength(0); //0 e 1 sono le dimensioni.
int dx = domain.GetLength(1);
T[,] ret = new T[dy, dx];
for (int y = 0; y < dy; y++)
{
for (int x = 0; x < dx; x++)
{
T test = domain[y,x];
ret[y, x] = pass(test) ? test : default(T);
}
}
return ret;
}
public static T[,] GetFiltered2<T>(this T[,] domain, Func<T, bool> pass)
{
int dy = domain.GetLength(0);
int dx = domain.GetLength(1);
T[,] ret = new T[dy, dx];
var universe = from y in Enumerable.Range(0, dy)
from x in Enumerable.Range(0, dx)
let test = domain[y, x]
let res = pass(test) ? test : default(T)
select new {y=y,x=x,res=res};
universe.ForEach(an => ret[an.y, an.x] = an.res);
return ret;
}
public static void ForEach<T>(this IEnumerable<T> domain, Action<T> action)
{
foreach (T t in domain) action(t);
}
E l'uso che se ne puo' fare e' p.es.:
static void Main(string[] args)
{
int[,] Mat1 = { { 1, 2, 3 }, { 4, 5, 6 } };
int[,] Mat1Filt = Mat1.GetFiltered(t => t > 4);
double[,] Mat2 = { { 1.0, 2.0, 3.0 }, { 4.0, 5.0, 6.0 } };
double[,] Mat2Filt = Mat2.GetFiltered2(u => u > 4.5);
}
Tutto fortemente tipizzato e lamentato a compile time se qualche tipo esce fuori dai ranghi.
Ora, sto cercando di capire se e' possibile avere davvero un enumeratore doppio. Intendo, un'enumerazione che abbia alla fine non un sola dimensione ma piu' di una.
L'enumerazione di cui sopra restituisce una serie di oggetti. Ogni oggetto contiene le 2 coordinate (e il risultato del filtro), e su questo ho giocato.
Mi sarebbe invece piaciuto ottenere un'enumerazione doppia, con 2 parametri interi (u,v) invece che uno solo tipizzato (t)
E' una questione di lana caprina, ma mi sarebbe piacuto scrivere qualcosa come la prima, invece che la seconda.
universe.ForEach((t, u) => ret[t, u] = qualcosa);
universe.ForEach(an => ret[an.y, an.x] = qualocosa);
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.