2013-09-06 28 views
6

Mam następujące klasy, który jest wypełniany danymidołączyć kilka IEnumerable <> użyciu LINQ

public class cntrydata 
{ 
    public string countryid { get; set; } 
    public string countryname { get; set; } 

    public IEnumerable<Data> data { get; set; } 

} 
public class Data 
{ 
     public int year { get; set; } 
     public float value { get; set; } 
} 

Mam IEnumerable rezultatu, który ma dane jak poniżej:

IEnumerable<cntryData> result 

USA 
United States 
    2000 12 
    2001 22 
    2004 32 

CAN 
Canada 
    2001 29 
    2003 22 
    2004 24 

chcę oceniać " wynik "obiekt za pomocą LINQ, aby uzyskać następujący wynik:

2000 USA 12 CAN null 
2001 USA 22 CAN 29 
2003 USA null CAN 22 
2004 USA 32 CAN 24 

Również jeśli wynik ma więcej krajów (np. Ch ina z wartością z 1995 roku 12), wynik powinien wyglądać następująco:

1995 USA null CAN null CHN 12 
2000 USA 12  CAN null CHN null 
2001 USA 22  CAN 29 CHN null 
2003 USA null CAN 22 CHN null 
2004 USA 32  CAN 24 CHN null 

Czy można tego dokonać za pomocą LINQ? Dziękuję Ci.

+0

chcesz spojrzeć na GroupBy() – Jonesopolis

+0

wygląda chcesz [ "lewy outów -join "] (http://msdn.microsoft.com/en-us/library/vstudio/bb397895.aspx) w LINQ. Łącz wszystkie lata ze wszystkimi krajami. Otrzymasz zasięg w następujący sposób: 'int minYear = result.Min (c => c.data.Min (d => d.rok)); int maxYear = result.Max (c => c.data.Max (d => d.rok)); var range = Enumerable.Range (minYear, maxYear-min Year + 1); ' –

+0

@TimSchmelter Jeśli pominiesz wiele lat, nie jest to zbyt dobre podejście. Jest to możliwe tylko wtedy, gdy wszystkie mieszczą się w dość niewielkim zakresie. – Servy

Odpowiedz

4

znalazłem to zaskakująco trudno wymyślić czystą odpowiedź na to jedno, a ja nadal nie jestem naprawdę zadowolony, więc opinia jest mile widziany :

var countries = result.Select(x => x.countryid).Distinct(); 
var years = result.SelectMany(x => x.data).Select(x => x.year).Distinct(); 
var data = result.SelectMany(x => x.data 
            .Select(y => new { Country = x.countryid, 
                 Data = y })) 
       .ToDictionary(x => Tuple.Create(x.Country, x.Data.year), 
           x => x.Data.value); 

var pivot = (from c in countries 
      from y in years 
      select new { Country = c, Year = y, Value = GetValue(c, y, data) }) 
      .GroupBy(x => x.Year) 
      .OrderBy(x => x.Key); 



public float? GetValue(string country, int year, 
         IDictionary<Tuple<string, int>, float> data) 
{ 
    float result; 
    if(!data.TryGetValue(Tuple.Create(country, year), out result)) 
     return null; 
    return result; 
} 

pivot będzie zawierać jeden przedmiot rocznie. Każdy z tych produktów będzie zawierał po jednym elemencie na kraj.

Jeśli chcesz sformatować każdą linię jako ciąg, można zrobić coś takiego:

pivot.Select(g => string.Format("{0} {1}", g.Key, string.Join("\t", g.Select(x => string.Format("{0} {1}", x.Country, x.Value))))); 
+0

Ostatni wynik ma 3 właściwości: 'Kraj',' Rok', 'Wartość', która tak naprawdę nie jest potrzebna PO. To, czego chce, to coś, co ma własności 'n + 1', podczas gdy' n' to liczba krajów. Jest bardzo dynamiczny. –

+0

@KingKing: Niepoprawnie. Przeczytaj opis zawartości pliku 'pivot'. –

+0

@ Daniel Hilgarth - myślisz, że moja "sztuczka" przy użyciu 'DataTable' jest" czystsza "? Nie wymaga funkcji pomocniczej. – Hogan

4

Aktualizacja

Tu jest teraz użyć poniższy kod aby uczynić tabelę danych:

var newresult = result.SelectMany(cntry => cntry.data.Select(d => new { id = cntry.countryid, name = cntry.countryname, year = d.year, value = d.value })) 
         .GroupBy(f => f.year) 
         .Select(g => new { year = g.Key, placeList = g.Select(p => new { p.id, p.value })}); 


    DataTable table = new DataTable(); 

    table.Columns.Add("Year"); 
    foreach(string name in result.Select(x => x.countryid).Distinct()) 
    table.Columns.Add(name); 

    foreach(var item in newresult) 
    { 
    DataRow nr = table.NewRow(); 

    nr["Year"] = item.year; 

    foreach(var l in item.placeList) 
     nr[l.id] = l.value; 

    table.Rows.Add(nr); 
    } 

    table.Dump(); 

A jak to wygląda:

table


ten jest to, co możesz zrobić linq, ty może łatwo przekształcić to w tabelę danych, listę według roku lokalizacji i ich wartości.

Spłaszczaj dane wejściowe, a następnie grupuj według. Wybierz, co chcesz. Podobnie jak ten

Oto, jak wygląda zrzut w LinqPadzie.

dump

Oto pełny kod testowy

void Main() 
{ 
    List<cntrydata> result = new List<cntrydata>() 
    { 
    new cntrydata() { countryid = "USA", countryname = "United States", 
     data = new List<Data>() { 
     new Data() { year = 2000, value = 12 }, 
     new Data() { year = 2001, value = 22 }, 
     new Data() { year = 2004, value = 32 } 
     } 
    }, 
    new cntrydata() { countryid = "CAN", countryname = "Canada", 
     data = new List<Data>() { 
      new Data() { year = 2001, value = 29 }, 
      new Data() { year = 2003, value = 22 }, 
      new Data() { year = 2004, value = 24 } 
     } 
    } 
    }; 

    var newresult = result.SelectMany(cntry => cntry.data.Select(d => new { id = cntry.countryid, name = cntry.countryname, year = d.year, value = d.value })) 
         .GroupBy(f => f.year) 
         .Select(g => new { year = g.Key, placeList = g.Select(p => new { p.id, p.value })}); 

    newresult.Dump(); 

} 

public class cntrydata 
{ 
    public string countryid { get; set; } 
    public string countryname { get; set; } 

    public IEnumerable<Data> data { get; set; } 

} 

public class Data 
{ 
     public int year { get; set; } 
     public float value { get; set; } 
} 
+0

To wygląda tak samo jak istniejąca odpowiedź i ma dokładnie ten sam problem. – Servy

+0

@Servy - przykładowy kod do dodania tabeli: D – Hogan

3
//group things up as required 
var mainLookup = result 
    .SelectMany(
    country => country.data, 
    (country, data) => new { 
     Name = country.Name, 
     Year = data.Year, 
     Val = data.Val 
    } 
) 
    .ToLookup(row => new {Name= row.Name, Year = row.Year} 

List<string> names = mainLookup 
    .Select(g => g.Key.Name) 
    .Distinct() 
    .ToList(); 
List<string> years = mainLookup 
    .Select(g => g.Key.Year) 
    .Distinct() 
    .ToList(); 

// generate all possible pairs of names and years 
var yearGroups = names 
    .SelectMany(years, (name, year) => new { 
    Name = name, 
    Year = year 
    }) 
    .GroupBy(x => x.Year) 
    .OrderBy(g => g.Key); 

IEnumerable<string> results = 
    (
    from yearGroup in yearGroups 
    let year = yearGroup.Key 
    //establish consistent order of processing 
    let pairKeys = yearGroup.OrderBy(x => x.Name) 
    let data = string.Join("\t", 
    from pairKey in pairKeys 
    //probe original groups with each possible pair 
    let val = mainLookup[pairKey].FirstOrDefault() 
    let valString = val == null ? "null" : val.ToString() 
    select pairKey.Name + " " + valString 
    ) 
    select year.ToString() + "\t" + data; //resultItem 
Powiązane problemy