Castle Paradox Forum Index Castle Paradox

 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
 Gamelist   Review List   Song List   All Journals   Site Stats   Search Gamelist   IRC Chat Room

Area Cohesion Algorithms

 
Post new topic   Reply to topic    Castle Paradox Forum Index -> The Arcade
View previous topic :: View next topic  
Author Message
Mr B




Joined: 20 Mar 2003
Posts: 382

PostPosted: Wed Apr 26, 2006 1:25 pm    Post subject: Area Cohesion Algorithms Reply with quote

This question was brought up by a couple of projects I had been mulling over, and finalized by my PoliSci teacher's lecture on representative districts.

The question is this: how can I determine whether adding or removing a segment from a group (for the purposes of simplicity, assume I'm talking about a tile on a grid) cam be done without breaking it up into two groups?

For example, let's say that I am programming next year's hot game, Circulatory System Tycoon. Cells can only recieve the benefit of a blood supply if the blood vessel they are adjacent to is also connected to the heart. For adding vessel segments, this wouldn't be very difficult -- segments can only be added if they are adjacent to pre-existant segments. If the heart is the first segment created, then no problem exists.

However, what about removing vessel segments? What if removing a segment causes other segments to be divorced from the heart? (we'll assume that we don't need to worry about creating functional loops so that the blood can actually cycle...) What if removing one segment causes the circulatory system to break into two circulatory systems, one of which does not have a heart?

I thought of a few different solutions, all but one of which ended up being really poor. This last one seems to work, but I am not sure about it and I don't know if it's the best way.

I'll start out by defining what I think a cohesive area is.

Cohesive Area Definition #1: A cohesive area is an area such that a path may be drawn from any point within the area to any other point within the area such that the path does not need to cross any points not within the area.

Cohesive Area Definition #2: A cohesive area is an area such that all points are contained within a single edge.

Definition #2 is simpler and probably easier to work with, but I don't know if it's actually true... Even if it is, how can you get a computer to define "inside of an edge?"

I'll work with def #2 for the time being.

Anyways, my addition algorithm is pretty simple. A segment may be added only if it will be in contact with a pre-existant segment.

The subtraction algorithm is giving me a little trouble. How to tell when removing a point will not make the area uncohesive? The solution I found was to have a little bug (er, process) that runs around the edge of the point to be removed, such that it is always hugging an edge. For each connection on the point to other points of the same time (for a tile, this would be the four sides) a bug is generated. Each bug runs runs with the same handedness (i.e., they will all hug edges to their left, or all to their right).

If a bug reaches an edge of its area type it pursues that edge until it meets up with the point to be removed again.

When a bug reaches the "birthplace" of another bug, it is successful and dies. If a bug reaches its own birthplace without touching the birthplace of any other bug, then it is in an area separate from the other bugs and thus the removal would be invalid (since it would form two separate areas). (this is only true if the point to be removed is adjacent to two or more other points of its own kind -- if it were at the tip of a peninsula it would only spawn one bug; this problem could be easily solved by straight-out permitting the removal of any point that is adjacent to only one other point of its own kind).

Note that bugs will only die successfully if they recross their own birthplace in the same orientation as they left it. This prevents them from falsely dying if they happen to hit their birthplace coming back from a peninsula or somesuch.

Of course, if bug Alfred hits the birthplace of bug Bethany, then they can both immediately die knowing that they succeeded in their mission, since if Alfred is in the same area as Bethany, then Bethany is in the same area as Alfred.

Let me give an example:

Code:

ooooooooooo
ooxxxxxxooo
oxxx###*xoo
oxxx##xxxoo
ooxxxxxxooo
oooxxxxxooo
ooooooooooo


There are three different territories here; the o's, the x's, and the #'s. the '*' is an 'x' tile that is requesting to be removed.

The * is adjacent to three tiles of its own kind -- one to the north, one to the south, and one to the east. One bug will be created and placed on each of these three tiles. Each bug will hug the edge on its left.

The east bug immediately hits an edge and starts heading south.

The south bug turns a corner and immediately hits the birthplace of the east bug. Since these two bugs are therefore certainly in the same area, they both die knowing that theirs is a job well done.

The north bug also immediately hits an edge; one created by the isolated island of #'s. It follows this edge west, then south, then east, takes a quick corner, then hits the birthplace of the south bug and successfully dies. All of the bugs have successfully died, so the tile can be removed.

Code:

ooooooooooo
ooxx*xxxooo
oxxx####xoo
oxxx##xxxoo
ooxxxxxxooo
oooxxxxxooo
ooooooooooo


Now let's say that another 'x' tile is requesting removal after the previous scenario. This tile is also represented by an '*'. Since it is adjacent to two tiles of its own kind, it creates two bugs, one on each.

The west bug immediately hits an edge and heads south along the island of #'s. It will continue to do so and will then follow the outside edge of the o's.

The east bug will walk east for three points, then west, and will finally end up on its birthplace without having hit the birthplace of the other bug. This means that it was on an area that would become isolated if the * were removed. The removal fails.

This seems to work pretty well except for in one circumstance:

Code:

oooooo
oooxxx
oxx*xx
oxxxoo


In this scenario, the north and east bugs will bump into each other, signalling a success. The west and south bugs will do likewise. However, two separate and discontinuous areas will be created!

The only solution I can think of is that all the bugs have to hit the birthplace of the next bug (N-E-S-W). At least, all but one. If you A=B=C=D, then D=A.

I am not at all certain that I am doing this correctly. Any advice or suggestions? Am I barking up the complete wrong tree?

*just realized that adding a segment to one area involves removing a segment from another area, thus the removal algorithm must be done for both addition and subtraction*
Back to top
View user's profile Send private message
TMC
On the Verge of Insanity




Joined: 05 Apr 2003
Posts: 3240
Location: Matakana

PostPosted: Thu Apr 27, 2006 3:54 am    Post subject: Reply with quote

Sorry, short answer and I prehaps didn't read over very well, going to bed.

I'm not sure Cohesive Area Definition #2 is bullet proof, it depends on which edge you choose. I don't know if it actually is a problem for the method you describe below, but:

Code:

choose the outer edge and fail to detect a discontinuity?
 #####
 #   #
 # # #
 #   #
 #####



What you seem to be doing with the "bugs" is check that all 4 bordering tiles to the starting tile, if they exist, are connected. This definitely means that they are connected. But what is the best way to do such a test? Checking that all the connecting tiles are on the same edge by itself will not work, but your method doesn't do quite this?

Quote:
this problem could be easily solved by straight-out permitting the removal of any point that is adjacent to only one other point of its own kind


Why wouldn't that be legal?

Quote:
If a bug reaches its own birthplace without touching the birthplace of any other bug


By touching, do you mean step next to? I think this is needed.
_________________
"It is so great it is insanely great."
Back to top
View user's profile Send private message Send e-mail
msw188




Joined: 02 Jul 2003
Posts: 1041

PostPosted: Thu Apr 27, 2006 4:19 am    Post subject: Reply with quote

I don't like Topology.

Okay, I haven't read your entire post yet, but I have to go and I want to point out quickly before I forget:

Your definition #2 is okay only under certain conditions. The main one I can think of now is that the edge cannot cross itself (like a figure eight). You also are not running an if and only if here. I mean that there exist fully cohesive areas that have more than one 'edge'. For example, a flat donut would be such an area. Inside-ness and outside-ness is a problem, too. I'll be back in a few minutes, I'll type more about it then.

Okay, I'm back, so the rest of this is an edit.

Shoot, I can't 'review' the post while I edit. I will start a new post. Sorry about the soon-to-be double post.
Back to top
View user's profile Send private message Visit poster's website
msw188




Joined: 02 Jul 2003
Posts: 1041

PostPosted: Thu Apr 27, 2006 4:52 am    Post subject: Reply with quote

Okay, your bug algorithm seems to get around some of the problems I was pointing out in your definition of a cohesive area (you will notice that your algorithm does not really count edges at all, but rather asks the question 'can I travel from one spot on this edge to another spot on this edge, without leaving the area?'). This seems all well and good, and the only thing I can think of to add is this:
You do not need to have the bugs hit each other's birthplaces in order necessarily. You can set it so that if bug 1 meets bug 2, then ONE of the bugs (along with its birthplace) dies, and then let the other bug continue. If all but one of the bugs die in this way, then you are connected. This may be important for trying to use this algorithm for things other than OHR maps, where your 'area' may be more than two dimensional (and thus your 'edges' more than one dimensional lines, so that they need not be so simply cyclic; I don't think it has any real effect on your current situation).

Actually, your algorithm is so based on the idea of traveling along a linear wall that it will not work in more than two dimensions in any case. So I'm pretty sure either your cyclic method or my method will work equally well. I'm not sure which would be easier to program.

In addition, I thought I'd mention that there is an easy way for a computer to tell whether it is on the inside or outside of a simple closed curve, that you are probably familiar with - go out toward 'infinity' (or the furthest border of the map) and count how may times you pass through an edge. If the answer is odd, you were 'inside', if even, you were 'outside'. Note that this only works if you know beforehand that there is only one edge that you are dealing with.
Back to top
View user's profile Send private message Visit poster's website
Mike Caron
Technomancer




Joined: 26 Jul 2003
Posts: 889
Location: Why do you keep asking?

PostPosted: Thu Apr 27, 2006 5:48 am    Post subject: Reply with quote

The Mad Cacti wrote:
Sorry, short answer and I prehaps didn't read over very well, going to bed.

I'm not sure Cohesive Area Definition #2 is bullet proof, it depends on which edge you choose. I don't know if it actually is a problem for the method you describe below, but:

Code:

choose the outer edge and fail to detect a discontinuity?
 #####
 #   #
 # # #
 #   #
 #####




I don't think that situation could ever arise in the first place underneath Mr. B's "Tiles can only be added touching an existing tile"

msw188 wrote:
You do not need to have the bugs hit each other's birthplaces in order necessarily. You can set it so that if bug 1 meets bug 2, then ONE of the bugs (along with its birthplace) dies, and then let the other bug continue. If all but one of the bugs die in this way, then you are connected.


But, will the bugs ever meet?

With two bugs (tiles), it's easy to ensure that they meet at some point (send them off in two directions), but with 3 or 4, then one or more will be going clockwise, and 1-"one or more" will be going counter clock wise. When they meet, depending on which one dies, it could work, or you could have two bugs that chase eachother around until they get back home.

I'm not sure how you could determine which one is ok to kill. Maybe CW, CCW, CW, CCW, or some such pattern, starting with whichever direction is greater.
_________________
I stand corrected. No rivers ran blood today. At least, none that were caused by us.

Final Fantasy Q
OHR Developer BLOG
Official OHRRPGCE Wiki and FAQ
Back to top
View user's profile Send private message Send e-mail Visit poster's website MSN Messenger
msw188




Joined: 02 Jul 2003
Posts: 1041

PostPosted: Thu Apr 27, 2006 7:41 am    Post subject: Reply with quote

Sorry, when I said that bugs meet, I meant either they meet or one hits the other's birthplace.
Back to top
View user's profile Send private message Visit poster's website
Mr B




Joined: 20 Mar 2003
Posts: 382

PostPosted: Thu Apr 27, 2006 12:04 pm    Post subject: Reply with quote

It should probably be that the bug that steps on the birthplace is the one that dies. If the other one dies then the first one will have to cover all of its path over again for no reason.

Mike Caron wrote:
With two bugs (tiles), it's easy to ensure that they meet at some point (send them off in two directions), but with 3 or 4, then one or more will be going clockwise, and 1-"one or more" will be going counter clock wise. When they meet, depending on which one dies, it could work, or you could have two bugs that chase eachother around until they get back home.

I'm not sure how you could determine which one is ok to kill. Maybe CW, CCW, CW, CCW, or some such pattern, starting with whichever direction is greater.


Mmmm...it's probably best not to think in terms of (|counter)clockwise-ness. Think in terms of "each bug hugs the wall to its left." Or right. Just as long as they all follow the same rule.

Everything below (and including this line) is an edit.

As far as I see it, each bug should connect to the birthplace of the bug on the side of the point to be removed that it would connect to if there were no surfaces to detain it. For example, if the bugs hug the walls to their lefts, then the west bug will need to land on the birthplace of the south bug in the same orientation as the south bug had when it was first created. If the west bug first hits the birthplace of the east bug, then the north bug will have been in an independent area. (assuming that the north bug was created)

I stipulate that a bug has to be in the same orientation as the other bug was when its birthplace was registered, because a certain tortured map design could allow a bug to step on the birthplace of another bug before hitting the birthplace of the bug it is supposed to hit.

(I am assuming that each bug is created such that its original orientation is with the wall of the tile requesting deletion is to the bug's wall-seeking side)

For example: (these bugs hug the wall to their rights)

Code:

oooooooooo
oxxxxxxxxo
oxxxxxxxxo
oxx#####xo
ox###xx#xo
oxxxx*xxxo
oxxxxxxxxo
oxxxxxxxxo
oxxxxxxxxo


The * is the 'x' tile that is requesting removal.

(ignore the fact that the north, south, and east bugs would all connect up with the west bug before it gets all the way around -- it would be easy to make a map that doesn't allow that; that just happens to be peripheral)

The west bug, hugging the wall to its right, would immediately hit the island of x's and make a long detour. It would come up to the * on the east side, touching the birthplace of the east bug before reaching the birthplace of the north bug. However, the removal would still be valid (though I don't know what tile type would take its place...).

As a result, the bug needs to check for not only birthplace but also creation orientation of the bug whose birthplace it steps on.
Back to top
View user's profile Send private message
Bob the Hamster
OHRRPGCE Developer




Joined: 22 Feb 2003
Posts: 2526
Location: Hamster Republic (Southern California Enclave)

PostPosted: Thu Apr 27, 2006 2:41 pm    Post subject: Reply with quote

I find this question fascinating, but it was a little over my head.. However, I showed it to Brian and he had lots of interesting things to say about this. I quote his e-mail here:

Brian Fisher wrote:
well the connectivity problem is, in the VAST majority of cases, a
super-easy and super-well known one to solve - the biggest problem
newbies have is that they worry about doing what they should do - a
brute force search of objects (brute force meaning go ahead and look
at every object, whenever you feel like) because usually the game will
run plenty fast with that approach.

So to find out what things aren't/won't be connected to the root(s),
there are two basic approachs - first is search from the object to
find a root, second is to search from the roots to find the objects,
marking all objects as connected as you find them.

Most games only need to know if objects are/aren't connected, rather
than having to determine the effect of deleting a link beforehand
(easier to ask forgiveness than permission?) so they therefore end up
searching for all objects from the root once a frame, or every time
the game changes state. Also, game objects are usually set up to have
some kind of "I am connected flag", and then every time the game needs
to determine connectivity, it clears every objects's flag, then does
searches from the root(s), marking objects as connected as it goes,
making sure not to bother to search through objects that are already
marked as connected. In the end, the cost of all that work is still
proportional to the number of objects you have, which basically means
you're not doing significantly more work than it takes to enumerate
each object, like the game has to do to draw and update and all that
stuff anyways
Note that the poster's "bug" algorithm is a different way of
describing classic brute force search, and seems to be a lot like
"breadth first search".

If however you need to know if a link will break connections, the
simplest way is to break the connection and test for connectivity for
everybody again, which is what the developer should probably try
first, even if it seems like it would be slow. However if that ends up
being too costly (which usually won't be the case, even if you are
doing this test on every link once a frame), then the only solution I
can think of is to code up an efficient point-point search algorithm
(i.e. a not brute force that is trying to find a specific destination)
and then perform your test by breaking the link, then doing a search
from each object on either side of the broken link to the root(s), and
if either endpoint fails to find the root(s), then you would have
broken connectivity. Doing point-point searches efficiently is usually
accomplished byheuristic search methods, most notable A*

Another possible situation is that you need to know whether deleting a
link makes seperate segments, and you don't have a root (or roots) -
again the issue of whether you need to know about links before
breaking, or whether it's okay to simply break the link and then
detect and deal with the change in connectivity, makes a difference.
If you just need to detect the number and size of distinct blobs, it's
pretty easy - once per frame, clear some flag, then for each object,
if it's not flagged, search from the object flagging as you go,
counting up whatever properties you need to know about that blob you
just searched. If everything is connected, you'll just do one search
on the first object and mark everything, meaning you got one group. If
nothing is connected, then you'll search on every object, knowing each
is in it's own group.

If you need to know if a link will break connectivity before hand,
and need to know efficiently, then you can test-break the link, then
do a point to point search from each side of the link. If they can't
find another path to each other, there ya go

so why were you curious?


Brian Fisher wrote:
Two more things:

1. while the idea having edges doesn't even apply to all graphs - 2D
where links can't cross is the only area I can think of where you can
even really say you have an edge.

2. trying to find and describe edges of "Cohesive Area"s in a
meaningful way (test for membership, know whether an edge is an inner
outer edge, etc.) is not an easier problem than determining
connectivity, in my opinion.
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Mike Caron
Technomancer




Joined: 26 Jul 2003
Posts: 889
Location: Why do you keep asking?

PostPosted: Thu Apr 27, 2006 9:50 pm    Post subject: Reply with quote

Hmm, he raises a valid point: Why do we need to care about whether it's connected or not when we're deleting a tile?

In SimCity, for example, every time the sim is updated, the engine does such a brute force check from every Water Pump to determine whether there's water there. If you break the link, then it'll be reflected in the next cycle.

Of course, there's other factors as well, in that case, such as water supply vs watering area which might make a pipe unwatered, but it's the same idea.

I can't imagine many situations in which you cannot allow the chain to be broken without adverse effects.
_________________
I stand corrected. No rivers ran blood today. At least, none that were caused by us.

Final Fantasy Q
OHR Developer BLOG
Official OHRRPGCE Wiki and FAQ
Back to top
View user's profile Send private message Send e-mail Visit poster's website MSN Messenger
Mr B




Joined: 20 Mar 2003
Posts: 382

PostPosted: Fri Apr 28, 2006 12:17 pm    Post subject: Reply with quote

Mike Caron wrote:
Hmm, he raises a valid point: Why do we need to care about whether it's connected or not when we're deleting a tile?

Maintain circulation.

I was thinking about this in terms of a growing mass that must stay connected for whatever reason (mold colony, space alien slime infestation). It is most important in the context of representative districts.

In the representative branch of Congress, each member is elected by a representative district in his/her/its home state. The districts have no fixed location -- they can be reapportioned whenever the state legislature so decides -- but they most contain roughly equal amounts of population in comparison with the other districts in that state. Districts must each be a contiguous area.

Since districts can't have multiple areas, the practice of gerrymandering has developed. Gerrymandering is the act of redistricting a district such that it still has a roughly equal headcount, but stretches to cover as much area as possible containing voters that support the representative, and as little area as possible containing voters that don't support the representative. Gerrymandering tends to make districts non-competative -- that is, the incumbant has an almost certain chance of winning the next election because he/she/it practically mail-ordered his/her/its constituent base.

I was thinking of making a gerrymander-ish game where the player has to optimize the tiles contained in the player's zone. It probably wouldn't have been a political game, but the restrictions and possibilities could have made an interesting stat/attribute bonus table (think of each character as having a certain-sized district on the attribute board that can be shifted around to maximize that character's abilities).

Brian Fisher's method seems to be identical in application to the "fill tool" used in a lot of graphic manipulation programs.

I don't see why he described the bug method as being brute force -- it requires the testing of relatively few tiles, certainly none that are not on an edge. Perhaps my definition of a brute force method is incorrect?

I looked up the Breadth-first search on Wikipedia. I don't see any simularity to the bug at all. I don't think the bug method uses any root node, unless if the tile to be removed could be considered some kind of root node.

I guess what I need is some kind of gnarly path-finding algorithm.
Back to top
View user's profile Send private message
Bob the Hamster
OHRRPGCE Developer




Joined: 22 Feb 2003
Posts: 2526
Location: Hamster Republic (Southern California Enclave)

PostPosted: Fri Apr 28, 2006 1:27 pm    Post subject: Reply with quote

Mr B wrote:
Brian Fisher's method seems to be identical in application to the "fill tool" used in a lot of graphic manipulation programs.


Exactly. When removing a tile, chose one tile next to it, and then do a "fill tool" search from there. When that is finished, check to see if there are any "unfilled" tiles of the same type next to the tile you removed, and boom! You know if you split the region.

Mr B wrote:
I don't see why he described the bug method as being brute force -- it requires the testing of relatively few tiles, certainly none that are not on an edge. Perhaps my definition of a brute force method is incorrect?


As I understand "brute force", it includes anything that is not constant-time. That is to say, anything that takes longer depending on the size of the set you are dealing with. Your method is a brute-force search of the edges, rather than a brute force search of the area. Whether or not it will be faster depends a lot on the shape.

Naturally, some brute force searches are less brutal than others ;)

(Though I am not positive whether or not my definition of a brute force method is correct either)
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Mike Caron
Technomancer




Joined: 26 Jul 2003
Posts: 889
Location: Why do you keep asking?

PostPosted: Fri Apr 28, 2006 2:57 pm    Post subject: Reply with quote

James Paige wrote:
Mr B wrote:
I don't see why he described the bug method as being brute force -- it requires the testing of relatively few tiles, certainly none that are not on an edge. Perhaps my definition of a brute force method is incorrect?


As I understand "brute force", it includes anything that is not constant-time. That is to say, anything that takes longer depending on the size of the set you are dealing with. Your method is a brute-force search of the edges, rather than a brute force search of the area. Whether or not it will be faster depends a lot on the shape.

Naturally, some brute force searches are less brutal than others Wink

(Though I am not positive whether or not my definition of a brute force method is correct either)


Well, even using a pathing algorithm to see if you can get from A to B isn't constant time. Granted, it's close, but not constant.
_________________
I stand corrected. No rivers ran blood today. At least, none that were caused by us.

Final Fantasy Q
OHR Developer BLOG
Official OHRRPGCE Wiki and FAQ
Back to top
View user's profile Send private message Send e-mail Visit poster's website MSN Messenger
Mr B




Joined: 20 Mar 2003
Posts: 382

PostPosted: Mon May 01, 2006 11:45 am    Post subject: Reply with quote

James Paige wrote:
As I understand "brute force", it includes anything that is not constant-time. That is to say, anything that takes longer depending on the size of the set you are dealing with. Your method is a brute-force search of the edges, rather than a brute force search of the area. Whether or not it will be faster depends a lot on the shape.

Naturally, some brute force searches are less brutal than others Wink


Ah, okay. Hmm...are there any constant-time algorithms for such things? I somehow doubt.

Oh yes; I forgot to respond to this:
msw188 wrote:
In addition, I thought I'd mention that there is an easy way for a computer to tell whether it is on the inside or outside of a simple closed curve, that you are probably familiar with - go out toward 'infinity' (or the furthest border of the map) and count how may times you pass through an edge. If the answer is odd, you were 'inside', if even, you were 'outside'. Note that this only works if you know beforehand that there is only one edge that you are dealing with.


Huh..! I never know about that. It seems quite elegant. I don't see why it would work for only one edge -- as long as you counted only the edges between "my area" and "everyone else's areas" it should work for any number of "my areas"...right?
Back to top
View user's profile Send private message
msw188




Joined: 02 Jul 2003
Posts: 1041

PostPosted: Wed May 03, 2006 5:40 am    Post subject: Reply with quote

You're right. I guess I really meant 'only one type of edge', rather than just one edge. You also need to make sure that you're 'infinity' is not a member of 'my area', otherwise this will be getting it backwards.

The main reason we are having these sorts of misunderstandings, I think, is that I am thinking in terms of 'inside' and 'outside', whereas you seem to be thinking more in terms of 'area A' versus 'area B'. The idea is still the same though. The only other problem I can think of is corners, or traveling along an edge. This may not be easy for a computer to recognize. Do you see what I mean? I have to go for now...
Back to top
View user's profile Send private message Visit poster's website
Mr B




Joined: 20 Mar 2003
Posts: 382

PostPosted: Fri May 05, 2006 11:42 am    Post subject: Reply with quote

Yeah; I was thinking in terms of areas, not edges. I was using edges as a means of defining areas.

And as was mentioned, the usefulness of an edge algorithm is probably restricted to a 2-D field.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Castle Paradox Forum Index -> The Arcade All times are GMT - 8 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group