I'm having a hard time geting access to properties in the var output of the query (I know I can use foreach on the result that I've got but I'm not sure which elements values I'm accessing). I think my query is just plain wrong, so I'd appreciate some help.

I have a class which contains a Point struct (X,Y). I want to group all of the objects by their X value, and then arrange each of those groups in descending order by the Y values within them. I can get the grouping just fine, but placing the values into the group seems to block me from doing any further computations with them.

Here is my LINQ query:

var col = from le in mylist
	          group le by le.X into g         
			  orderby g.Key descending
			  select g;

at the end of the "group.... into g" line, le goes out of scope so I can't get access to the Y component anymore.
This code currently gives me a group of collections sorted by their X value. I've even tried joining it with the original data set so I could just get a set of (X,Y) values back.

So my question is twofold, how can I "get" my Y values out of the the "g" list or how could I reformulate my query better in the first place...

Thanks much for any help!

Recommended Answers

All 12 Replies

Try to group first then order second

I thought that's what I did... could you be more specific? Do you mean to try forgoing the "into g" step?

Thanks!

var col = from le in mylist
group le by le.X into g         
orderby g.Key descending
select g;

On two phases

var colGrouped = from le in mylist
group le by le.X select le;

var colOrdered = from colGrouped ..... orderby...
commented: Excellent. +16
commented: Some long overdue rep +1


On two phases

var colGrouped = from le in mylist
group le by le.X select le;   <-----*  
var colOrdered = from colGrouped ..... orderby...

Unfortunately this(*) doesn't want to compile without the into

So please send your class code you want to group and order it; the class has this struct to play with this problem.

Here's the class (it's a game of space invaders from Head First C#)

public class Invader
    {  //see directionandtypeenum.cs for Type definition
        private const int HorizontalInterval = 10;
        private const int VerticalInterval = 25; //40;

        private Bitmap image;
        private Point location;
        public Point Location { get { return location; } }  //changed this to make life easier
        public Type InvaderType { get; private set; }

        public Rectangle Area { get { return new Rectangle(location, image.Size); } }

        public int Score { get; private set; }
        public Invader(Type InvaderType,Point location,int score)
        {
            this.InvaderType = InvaderType;
            this.location = location;
            this.Score = score;
            image = InvaderImage(0);
        }

        public void Draw(Graphics g,int animationCell)
        {
            
            g.DrawImage(InvaderImage(animationCell), Location);
        }

        public void Move(Direction direction)
        {
            switch (direction)
            {
                case Direction.Down:
                    location.Y += VerticalInterval;
                    break;
                case Direction.Left:
                    location.X -= HorizontalInterval;
                    break;
                case Direction.Right:
                    location.X += HorizontalInterval;
                    break;
                default: break;
                
            }
        }

        private Bitmap InvaderImage(int animationCell)
        {
            return (Bitmap)Properties.Resources.ResourceManager.GetObject(InvaderType.ToString().ToLower() + (animationCell+1).ToString());
                        
        }
    }

Here's where the query happens in another class called Game (there's a bit more to the method but it's not relevant). The second query as it is doesn't compile...

private void FireInvaderShots()
        {
            if (invaderShots.Count <=2)
            {
                
                var Column = from inv in invaders
                             group inv by inv.Location.X into g
                             select g;
                //into invadergroup             
                //orderby invadergroup.Key descending
                             
                
                
                
                var cols = from v in Column
                            where v.ElementAt(v.Key).Location.Y == v.ElementAt(v.Key).Location.Y.Max()
                            select v;
                
                List<Invader> col = new List<Invader>();

Take a look at this code,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

class MainApp
{
    static void Main()
    {
        List<MyData> items = new List<MyData>();
        items.Add(new MyData("A", "1"));
        items.Add(new MyData("A", "2"));
        items.Add(new MyData("B", "3"));
        items.Add(new MyData("A", "4"));

        var result = from all in items group all by all.State into grpResult orderby grpResult.Key descending  select grpResult;

        foreach (var t in result){
            Console.WriteLine(t.Key + " " + t.Count());
        }
    }
}
public class MyData{
    public string State { get; set; }
    public string City { get; set; }
    public MyData() { }
    public MyData(string _state, string _city){
        State = _state;
        City = _city;
    }
}

Nice! Thanks so much. Here's what I ended up with(I had to make some minor adjustments, but otherwise worked like a charm):

var Column = from all in invaders group all by all.Location.X into grpResult orderby grpResult.ElementAt(0).Location.Y descending select grpResult;

foreach (var v in Column)
Console.WriteLine(v.ElementAt(v.Count()-1).Location.ToString());

Thanks to Ramy, also.

var Column = from all in invaders group all by all.Location.X into grpResult orderby grpResult.ElementAt(0).Location.Y descending select grpResult;

foreach (var v in Column)
Console.WriteLine(v.ElementAt(v.Count()-1).Location.ToString());

You can and should use v.Last() instead of v.ElementAt(v.Count()-1) . Using v.Last() also only iterates through the collection once, which is sometimes something to be aware of (and sometimes a needless microoptimization) and anyway it's less code.

Edit: And I think you have it all wrong. Your code groups by the X value and sorts the collection of groups by the groups' first Y values. That's what you want?

commented: Some long overdue rep +1

You can and should use v.Last() instead of v.ElementAt(v.Count()-1) . Using v.Last() also only iterates through the collection once, which is sometimes something to be aware of (and sometimes a needless microoptimization) and anyway it's less code.

No, I wasn't aware of that, it's good to know.

Edit: And I think you have it all wrong. Your code groups by the X value and sorts the collection of groups by the groups' first Y values. That's what you want?

Er, actually not in theory. The current query seemed to work but maybe it was coincidence. I wanted to sort the Y values (descending) for each group of X values, then choose the first X,Y value (therefore the furthest down on the screen) to feed to another method. Perhaps I'm still confused about the IEnumerables that emerge from the LINQ query and how to access them properly.

Er, actually not in theory. The current query seemed to work but maybe it was coincidence. I wanted to sort the Y values (descending) for each group of X values, then choose the first X,Y value (therefore the furthest down on the screen) to feed to another method. Perhaps I'm still confused about the IEnumerables that emerge from the LINQ query and how to access them properly.

Except when doing joins, I prefer to avoid the LINQ syntax and just use the Enumerable extension methods directly. It makes the behavior more clear, sometimes.

If you want things grouped by X and then each individual group sorted, you should do this:

var groups = invaders
    .GroupBy(invader => invader.Location.X)
    .Select(group => group.OrderByDescending(invader => invader.Location.Y));

If invaders looks like this: [(1,2), (1, 3), (1,1), (3, 5), (2, 4), (3, 4)] , then groups should look like this: [[(1, 3), (1, 2), (1, 1)], [(3, 5), (3, 4)], [(2, 4)]] (but you might want to double check that I'm right).

If you want LINQ syntax for that, you'd write

var groups = from invader in invaders
             group invader by invader.Location.X into g
             select g.OrderByDescending(invader => invader.Location.Y)

but I don't have a C# compiler on this computer so maybe I forgot something. This is equivalent:

var groups = from invader in invaders
             group invader by invader.Location.X into g
             select (from invader in g orderby invader.Location.Y descending select invader);

I'll have to put it through some further testing to make sure, but it looks really good. Thanks for the extra assistance.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.