2

I have searched without success to a similar situation as follows.

I have two lists, list A and list B.
List A is composed of 10 objects created from ClassA which contains only strings.
List B is composed of 100 objects created from ClassB which only contains decimals.

List A is the header information.
List B is the data information.

The relationship between the two lists is:

Row 1 of list A corresponds to rows 1-10 of list B.
Row 2 of list A corresponds to rows 11-20 of list B.
Row 3 of list A corresponds to rows 21-30 of list B.

etc.........

How can I combine these two lists so that when I display them on the console the user will see a header row followed immediately by the corresponding 10 data rows.

I apologize if this has been answered before.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
greg
  • 39
  • 1
  • 5
  • So list A will *always* contain 10 objects and List B will *always* contain 100 objects? – Erik Philips Sep 16 '13 at 23:10
  • I am reading from a text file and the number of objects in A could vary. – greg Sep 16 '13 at 23:33
  • @greg did you mean 10 objects in list B for every 1 object in list A? – Erik Philips Sep 16 '13 at 23:42
  • I apologize for any confusion. For every 1 object in List A there are 10 corresponding objects in List B. The first object in List A corresponds to the first 10 objects in List B and so on..... – greg Sep 17 '13 at 02:47

4 Answers4

1

Here is some code that should fulfill your request - I am going to find a link for the partition extension as I can't find it in my code anymore:

void Main()
{
    List<string> strings = Enumerable.Range(1,10).Select(x=>x.ToString()).ToList();
    List<decimal> decimals = Enumerable.Range(1,100).Select(x=>(Decimal)x).ToList();

    var detailsRows = decimals.Partition(10)
                              .Select((details, row) => new {HeaderRow = row, DetailsRows = details});

    var headerRows = strings.Select((header, row) => new {HeaderRow = row, Header = header});

    var final = headerRows.Join(detailsRows, x=>x.HeaderRow, x=>x.HeaderRow, (header, details) => new {Header = header.Header, Details = details.DetailsRows});
}

public static class Extensions
{
    public static IEnumerable<List<T>> Partition<T>(this IEnumerable<T> source, Int32 size)
    {
        for (int i = 0; i < Math.Ceiling(source.Count() / (Double)size); i++)
            yield return new List<T>(source.Skip(size * i).Take(size));
    }
}    

That Partition method is the one that does the grunt work...

And here is the link to the article - LINK

EDIT 2

Here is better code for the Main() method... Rushed to answer and forgot brain:

void Main()
{
    List<string> strings = Enumerable.Range(1,10).Select(x=>x.ToString()).ToList();
    List<decimal> decimals = Enumerable.Range(1,100).Select(x=>(Decimal)x).ToList();

    var detailsRows = decimals.Partition(10);

    var headerRows = strings; //just renamed for clarity from other code

    var final = headerRows.Zip(detailsRows, (header, details) => new {Header = header, Details = details});
}
Community
  • 1
  • 1
tostringtheory
  • 877
  • 8
  • 19
1

Ok, that should work. Let me know in case I got anything wrong.

List<ClassA> listA = GetListA()// ...
List<ClassB> listB = GetListA()// ...


if(listB.Count % listA.Count != 0)     
      throw new Exception("Unable to match listA to listB");

var datasPerHeader = listB.Count / listA.Count;

for(int i = 0; i < listA.Count;i++)
{
    ClassA header = listA[i];
    IEnumerable<ListB> datas = listB.Skip(datasPerHeader*i).Take(datasPerHeader);
    Console.WriteLine(header.ToString());
    foreach(var data in datas)
    {
          Console.WriteLine("\t{0}", data.ToString());
    } 
}
alex.b
  • 4,547
  • 1
  • 31
  • 52
  • +1 on your mod check... I left it as an exercise to the poster, but basically verbatim. – tostringtheory Sep 16 '13 at 23:40
  • Thanks for your help it works nicely. My programming experience is limited and I was best able to understand your example. The IEnumerable interface is something I have not used before but soon will. I am still searching for ways to combine lists that contain different types. – greg Sep 18 '13 at 18:07
  • cool, glad to hear that. `IEnumerable` is interface that generalizes all the collections, e.g. all the arrays, lists, sequences, hashtables, dictionaries in .net are implement IEnumerable, which means they are sequence/collection of some items and can be enumerated. – alex.b Sep 18 '13 at 19:00
1

This should be pretty straight forward unless I'm missing something.

var grouped = ListA.Select((value, index) => 
  new {
    ListAItem = value,
    ListBItems = ListB.Skip(index * 10).Take(10)
  })
  .ToList();

Returns back an anonymous type you can loop through.

foreach (var group in grouped)
{
  Console.WriteLine("List A: {0}", group.Name);
  foreach (var listBItem in group.ListBItems)
  {
    Console.WriteLine("List B: {0}", listBItem.Name);
  {
}
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
0

The easiest way may be something like this:

var listA = new List<string>() { "A", "B", "C", ... }
var listB = new List<decimal>() { 1m, 2m, 3m, ... }
double ratio = ((double)listA.Count) / listB.Count;
var results = 
    from i in Enumerable.Range(0, listB.Count)
    select new { A = listA[(int)Math.Truncate(i * ratio)], B = listB[i] };

Or in fluent syntax:

double ratio = ((double)listA.Count) / listB.Count;
var results = Enumerable.Range(0, listB.Count)
    .Select(i => new { A = listA[(int)Math.Truncate(i * ratio)], B = listB[i] });

Of course if you know you will always have 10 items in listB for each item in listA, you can simplify this to:

var results = 
    from i in Enumerable.Range(0, listB.Count)
    select new { A = listA[i / 10], B = listB[i] };

Or in fluent syntax:

var results = Enumerable.Range(0, listB.Count)
    .Select(i => new { A = listA[i / 10], B = listB[i] });

This will return a result set like

{ { "A", 1 }, 
  { "A", 2 }, 
  { "A", 3 }
  ..,
  { "A", 10 },
  { "B", 11 },
  { "B", 12 },
  { "B", 13 },
  ...
  { "B", 20 },
  { "C", 21 },
  ...
  { "J", 100 }
}
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331