Grouping Arbitrary Number of Polygons by Intersection - C# - c#

I have multiple lists of polygons that each represent an physical object. For example:
List<CurveLoop> A could represent a rectangle with a hole in it. One curve within this list would be the outline of the rectangle, and another curve would be the hole.
I want a method that will return a list of lists, where each list contains all the objects that intersect.
I already have a method that will return whether two of the objects intersect:
bool _CurveLoopsIntersect(List<CurveLoop> curveLoopsA, List<CurveLoop> curveLoopsB) {...}
will return true if any any two curves within the two lists touch.
Below is the code I have so far, but it just gives me a single pass. I think I need multiple passes, so that if object A and B intersect, and B and C intersect, then they would form set { A, B, C }. I need an arbitrary number of passes though, and sometimes the objects won't intersect at all, or be part of different sets, such as {A, B, C} and {D, E} and {F}.
public List<CurveLoop> _MergeCurveLoops(List<List<CurveLoop>> elementCurveLoops, View view)
{
// ...
// Preprocessing
var listOfLists = new List<List<CurveLoop>>();
foreach (var elementCurveLoop in elementCurveLoops)
{
var newList = elementCurveLoops.FindAll(x => _CurveLoopsIntersect(x, elementCurveLoop));
listOfLists.Add(newList);
}
}
private bool _CurveLoopsIntersect(List<CurveLoop> curveLoopsA, List<CurveLoop> curveLoopsB)
{
foreach (var curveLoopA in curveLoopsA)
{
foreach (var curveA in curveLoopA)
{
foreach (var curveLoopB in curveLoopsB)
{
foreach (var curveB in curveLoopB)
{
var result = curveA.Intersect(curveB);
if (result == SetComparisonResult.Overlap ||
result == SetComparisonResult.Subset ||
result == SetComparisonResult.Superset ||
result == SetComparisonResult.Equal)
{
return true;
}
}
}
}
}
return false;
}

This can be implemented using some code like this psuedu
set = a,b,c, ...
While(set not empty) {
Create newSet
Add set.first to new list
Remove set.first from set // this line isnt necessary if a curve doesnt intersect with self
For (i = 0 , i < newset.length , i++)
{
newSet.add(set.FindAll(x => _CurveLoopsIntersect(x, newSet[i]));
set.removeRange(newSet); // this line may have error that the first element doesnt exist in set
}
Add newSet to set of sets
}

Thanks, you put me in the right direction. You were right, using a Set was the right approach. I used a set in combination with a recursive function (similar to your while loop).
The code I wrote is below:
static List<Polygon> _RecursiveMergePolygons(List<Polygon> polygons, View view)
{
HashSet<Polygon> initialSet = new HashSet<Polygon>(polygons);
HashSet<Polygon> finalSet = new HashSet<Polygon>(polygons);
foreach (var polygon in initialSet)
{
// Should always return at least 1 instance
var polys = polygons.FindAll(x => _PolygonsIntersect(x, polygon));
// if it's greater than 1, then merge them and restart the recursion, otherwise continue
if (polys.Count > 1)
{
foreach (var poly in polys)
{
finalSet.Remove(poly);
}
var mergedPolygon = new Polygon(polys, view);
finalSet.Add(mergedPolygon);
break;
}
}
if (finalSet.Count == initialSet.Count)
{
return finalSet.ToList();
}
return _RecursiveMergePolygons(finalSet.ToList(), view);
}

Related

Merge data from two arrays or something else

How to combine Id from the list I get from file /test.json and id from list ourOrders[i].id?
Or if there is another way?
private RegionModel FilterByOurOrders(RegionModel region, List<OurOrderModel> ourOrders, MarketSettings market, bool byOurOrders)
{
var result = new RegionModel
{
updatedTs = region.updatedTs,
orders = new List<OrderModel>(region.orders.Count)
};
var json = File.ReadAllText("/test.json");
var otherBotOrders = JsonSerializer.Deserialize<OrdersTimesModel>(json);
OtherBotOrders = new Dictionary<string, OrderTimesInfoModel>();
foreach (var otherBotOrder in otherBotOrders.OrdersTimesInfo)
{
//OtherBotOrders.Add(otherBotOrder.Id, otherBotOrder);
BotController.WriteLine($"{otherBotOrder.Id}"); //Output ID orders to the console works
}
foreach (var order in region.orders)
{
if (ConvertToDecimal(order.price) < 1 || !byOurOrders)
{
int i = 0;
var isOurOrder = false;
while (i < ourOrders.Count && !isOurOrder)
{
if (ourOrders[i].id.Equals(order.id, StringComparison.InvariantCultureIgnoreCase))
{
isOurOrder = true;
}
++i;
}
if (!isOurOrder)
{
result.orders.Add(order);
}
}
}
return result;
}
OrdersTimesModel Looks like that:
public class OrdersTimesModel
{
public List<OrderTimesInfoModel> OrdersTimesInfo { get; set; }
}
test.json:
{"OrdersTimesInfo":[{"Id":"1"},{"Id":"2"}]}
Added:
I'll try to clarify the question:
There are three lists with ID:
First (all orders): region.orders, as order.id
Second (our orders): ourOrders, as ourOrders[i].id in a while loop
Third (our orders 2): from the /test.json file, as an array {"Orders":[{"Id":"12345..."...},{"Id":"12345..." ...}...]}
There is a foreach in which there is a while, where the First (all orders) list and the Second (our orders) list are compared. If the id's match, then these are our orders: isOurOrder = true;
Accordingly, those orders that isOurOrder = false; will be added to the result: result.orders.Add(order)
I need:
So that if (ourOrders[i].id.Equals(order.id, StringComparison.InvariantCultureIgnoreCase)) would include more Id's from the Third (our orders 2) list.
Or any other way to do it?
You should be able to completely avoid writing loops if you use LINQ (there will be loops running in the background, but it's way easier to read)
You can access some documentation here: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/introduction-to-linq-queries
and you have some pretty cool extension methods for arrays: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-6.0 (these are great to get your code easy to read)
Solution
unsing System.Linq;
private RegionModel FilterByOurOrders(RegionModel region, List<OurOrderModel> ourOrders, MarketSettings market, bool byOurOrders)
{
var result = new RegionModel
{
updatedTs = region.updatedTs,
orders = new List<OrderModel>(region.orders.Count)
};
var json = File.ReadAllText("/test.json");
var otherBotOrders = JsonSerializer.Deserialize<OrdersTimesModel>(json);
// This line should get you an array containing
// JUST the ids in the JSON file
var idsFromJsonFile = otherBotOrders.Select(x => x.Id);
// Here you'll get an array with the ids for your orders
var idsFromOurOrders = ourOrders.Select(x => x.id);
// Union will only take unique values,
// so you avoid repetition.
var mergedArrays = idsFromJsonFile.Union(idsFromOurOrders);
// Now we just need to query the region orders
// We'll get every element that has an id contained in the arrays we created earlier
var filteredRegionOrders = region.orders.Where(x => !mergedArrays.Contains(x.id));
result.orders.AddRange(filteredRegionOrders );
return result;
}
You can add conditions to any of those actions (like checking for order price or the boolean flag you get as a parameter), and of course you can do it without assigning so many variables, I did it that way just to make it easier to explain.

Algorithm to implement a JOIN with some limitation

Let me explain my matching problem with a real example (the problem is generic). Assume having 2 lists: of "selections" loaded from different sources. The list don't have duplicates.
Let's say mkTPL.Selections and mkDB.Selections come from SQL Tables each with an unique index on the id and the selection's name. The problem is that sometimes IdSelectionType is null (in the selection from mkTPL.Selections)
foreach (var selTPL in mkTPL.Selections)
{
foreach (var selDB in mkDB.Selections)
{
if (selTPL.IsTheSame(selDB))
selTPL.OddOrResultValue = selDB.OddOrResultValue;
}
}
public bool IsStessaSelezione(SelectionPrints selDb)
{
if (selDb.IdSelectionType == this.IdSelectionType)
return true;
else
{
bool isSameName = selDb.Name == this.Name;
bool isSimilarName = false;
if (!isSameName)
{
isSimilarName = RegexReplace(selDb.Name, #"\([\d.]+\)") == RegexReplace(this.Name, #"\([\d.]+\)");
}
return isSameName || isSimilarName;
}
}
The match alghtoritm that I have implemented is not efficient. Once a selection is matched I shouldn't try to match it further with others (because of the unique index on the id and on the selection name).
Linq could provide me an easy solution?
First of all, you should break when you found a match:
foreach (var selTPL in mkTPL.Selections)
{
foreach (var selDB in mkDB.Selections)
{
if (selTPL.IsTheSame(selDB))
{
selTPL.OddOrResultValue = selDB.OddOrResultValue;
break; // <--
}
}
}
Second, I would make a dictionary of mkDB.Selections, where you store the regexed value so you don't have to make that calculation over and over again, on every iteration.
Something like:
var mkDBDictionary = mkDB.Selections.ToDictionary(s => RegexReplace(s.Name, #"\([\d.]+\)"), s => s);
foreach (var selTPL in mkTPL.Selections)
{
string selTPLName = RegexReplace(selTPL.Name, #"\([\d.]+\)");
if (mkDBDictionary.TryGetValue(selTPLName, out var selDB))
{
selTPL.OddOrResultValue = selDB.OddOrResultValue;
}
}

A function that gets two variables and their order doesn't matter

Sorry that I couldn't explain better in the question header. I'll try to define my question better here.
That's what I am doing - in my game, there is an option to use items on one another. For each pair of items, the game performs an action.
Currently, there are two variables that game uses for this: "ItemUsed" and "ItemUsedOn". First, the game chooses first item - its id goes to "ItemUsed", then he chooses second item, it's id goes to "ItemUsedOn". Then, there is a void that defines a specific action.
A short example of code:
if (ItemUsed == "itm_cable")
{
if (ItemUsedOn == "itm_towel")
SubMain.ItemsMergedText = "To achieve what?";
else if (ItemUsedOn == "itm_wet_towel")
SubMain.ItemsMergedText = "No, water will damage it";
else if (ItemUsedOn == "itm_glass")
SubMain.ItemsMergedText = "I don't need to cut the cable";
}
if (ItemUsed == "itm_book_electronics")
{
if (ItemUsedOn == "itm_towel")
SubMain.ItemsMergedText = "Why?";
if (ItemUsedOn == "itm_wet_towel")
SubMain.ItemsMergedText = "No, water will damage the book";
else if (ItemUsedOn == "itm_soap")
SubMain.ItemsMergedText = "Wrong plan";
else if (ItemUsedOn == "itm_hair")
SubMain.ItemsMergedText = "It won't help";
}
And there are many such pairs.
However, there is a problem with this approach. When two items are combined, their order doesn't matter, for example "itm_toolbox" can be "ItemUsed" and "itm_cable" can be "ItemUsedOn", but also can be the other way around, the result will be the same. How can this be achieved?
I did try using this in every "larger" if:
else
CombineItems(ItemUsedOn, "itm_book_edda");
But this doesn't always work, and I couldn't find why.
So, what I am looking for is function that gets 2 variables:
void CombineItems(string Item1, string Item2)
And then give same result in those cases:
if (Item1="Tomato")&&(Item2="Cucumber")
Item3="Salad"
if (Item1="Cucumber")&&(Item2="Tomato")
Item3="Salad"
My question: is there an easier way for this, without using so many "if's"?
Thank you in advance,
Evgenie
I'd recommend that you create yourself an eg UnorderedPair type, which overrides the behaviour of .Equals and/or ==, such that:
new UnorderedPair("Tomato", "Cumcumber") == new UnorderedPair("Cucumber", "Tomato");
Then you can reduce all of your if statements down to a simple dictionary:
combinedItems = new Dictionary<UnderedPair, string>
{
[new UnorderedPair("Tomato", "Cumcumber")] = "Salad",
[new UnorderedPair("Bread", "Filling")] = "Sandwich",
...
};
and your code for determining the text can then just be:
SubMain.ItemsMergedText = combinedItems[new UnorderedPair(ItemUsed, ItemUsedOn)];
You can use params(to treat them as array) and LINQ. Store the salad-items in an array too:
private string[] SaladItems = new string[] { "Tomato", "Cucumber" };
string CombineItems(params string[] Items)
{
bool isSalad = SaladItems.Length == Items.Length && !SaladItems.Except(Items).Any();
if (isSalad) return "Salad";
// other types ...
return null; // no match, exception?
}
Side-Note: if you want to accept "tomato"(so ignore the case) use:
!SaladItems.Except(Items, StringComparer.InvariantCultureIgnoreCase).Any()
You could make this endless list of comparisons a lot smaller if you structure the logic into separate parts.
First, I would create a list or so to keep the items and their result:
var items = new[] { new { Item1 = "tomato", Item2 = "Cucumber", Result = "Salad" }
, new { Item1 = "tomato2", Item2 = "Cucumber2", Result = "Salad2" }
};
Then find the matching item:
var match = items.FirstOrDefault
(itm => (itm.Item1 == Item1 && itm.Item2 == Item2)
|| (itm.Item1 == Item2 && itm.Item2 == Item1)
);
There are two approaches that can keep down the amount of duplication throughout the code:
Sort the items so they're alphabetical order:
if(Item1 > Item2) {
var tmp = Item2;
Item2 = Item1;
Item1 = tmp;
}
now all of your remaining code can assume that Item1 values will always be earlier alphabetically than Item2, so you'd only write:
if (Item1="Cucumber")&&(Item2="Tomato")
Item3="Salad"
Or, you can, after exhausting all of your options (again, written only once), call your method recursively with the parameters swapped:
void TakeAction(Item1, Item2) {
.
.
.
/* No matches */
else {
TakeAction(Item2,Item1);
}
}
var items= new HashSet<string>();
items.Add("tomato");
items.Add("Cucumber");
if(items.Contains("tomato") && items.Contains("Cucumber")) //Order does **NOT** matter
Item3="Salad";

selecting an individual element from a list within a list using .where and .select

I have a List<> of type world and within each world element is a List<> of type item which in itself contains a Rectangle and a string
heres the structure of world
`class world
public Items[] items { get; set; }
public world(int nooflevels, int noofitems)
{
//when creating a world make the items
items = new Items[noofitems];
for (int i = 0; i < noofitems; i++)
{
items[i] = new Items();
}
}
}`
and item
class Items
{
public new Rectangle spriterect;
public string type { get; set; }
public Items()
{
spriterect.X = 0;
spriterect.Y = 0;
spriterect.Width = 0;
spriterect.Height = 0;
type = "default";
}
}
this list of worlds was created like this
List<world> Worlds = new List<world>();
i was trying to get a specific rectangle out of the list of items based on the type
void getitem(string thetype, int world)
{
Rectangle a = Worlds[world].
items.Where(f=> f.type == thetype).
Select(g => g.spriterect);
}
so i was hoping this would select the item[].spriterect that contained the .type thetype
and i want it to return the rectangle in that item but it returns an IEnumerable
how do i get it to return the single rectangle based on the type?
You should select single item from items. If there should be single rectangle of specified type, then use SingleOrDefault:
var item = Worlds[world].items.SingleOrDefault(i => i.type == thetype);
if (item != null)
a = item.spriterect;
If you completely sure that there is always exist rectangle of specified type, then simply use Single:
Rectangle a = Worlds[world].items.Single(i => i.type == thetype).spriterect;
You would want to use .Single after you .Select.
Single will throw an Exception if there is not exactly one match.
Rectangle a = Worlds[world]
.items.Where(f=> f.type == thetype)
Select(g => g.spriterect).Single();
Instead of where use FirstOrDefault. If it doesn't find the item, it will return null.
var primary = Worlds[world].FirstOrDefault(f=> f.type == thetype);
if (primary != null)
return primary.spriterect;
return null;
The Where function returns a collection (eveything that meets the criteria), rather than just a single item. You would want to use either First or Single, noting that Single will throw an exception if there is more than one matching the criteria (and both will throw if there are none).
If you only know you will only ever get one value you can use Single, or SingleOrDefault if you know the item may not exist.
//use if you know the rectangle will be there, and there will be only 1 that matches the criteria
Rectangle a = Worlds[world].items.Single(f => f.type == thetype).spriterect;
//use if the rectangle may not be there, and if it is there will be only 1 that matches the criteria
var item = Worlds[world].items.SingleOrDefault(f => f.type == thetype);
if (item != null)
Rectangle a = item.spriterect;

Compare adjacent list items

I'm writing a duplicate file detector. To determine if two files are duplicates I calculate a CRC32 checksum. Since this can be an expensive operation, I only want to calculate checksums for files that have another file with matching size. I have sorted my list of files by size, and am looping through to compare each element to the ones above and below it. Unfortunately, there is an issue at the beginning and end since there will be no previous or next file, respectively. I can fix this using if statements, but it feels clunky. Here is my code:
public void GetCRCs(List<DupInfo> dupInfos)
{
var crc = new Crc32();
for (int i = 0; i < dupInfos.Count(); i++)
{
if (dupInfos[i].Size == dupInfos[i - 1].Size || dupInfos[i].Size == dupInfos[i + 1].Size)
{
dupInfos[i].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i].FullName));
}
}
}
My question is:
How can I compare each entry to its neighbors without the out of bounds error?
Should I be using a loop for this, or is there a better LINQ or other function?
Note: I did not include the rest of my code to avoid clutter. If you want to see it, I can include it.
Compute the Crcs first:
// It is assumed that DupInfo.CheckSum is nullable
public void GetCRCs(List<DupInfo> dupInfos)
{
dupInfos[0].CheckSum = null ;
for (int i = 1; i < dupInfos.Count(); i++)
{
dupInfos[i].CheckSum = null ;
if (dupInfos[i].Size == dupInfos[i - 1].Size)
{
if (dupInfos[i-1].Checksum==null) dupInfos[i-1].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i-1].FullName));
dupInfos[i].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i].FullName));
}
}
}
After having sorted your files by size and crc, identify duplicates:
public void GetDuplicates(List<DupInfo> dupInfos)
{
for (int i = dupInfos.Count();i>0 i++)
{ // loop is inverted to allow list items deletion
if (dupInfos[i].Size == dupInfos[i - 1].Size &&
dupInfos[i].CheckSum != null &&
dupInfos[i].CheckSum == dupInfos[i - 1].Checksum)
{ // i is duplicated with i-1
... // your code here
... // eventually, dupInfos.RemoveAt(i) ;
}
}
}
I have sorted my list of files by size, and am looping through to
compare each element to the ones above and below it.
The next logical step is to actually group your files by size. Comparing consecutive files will not always be sufficient if you have more than two files of the same size. Instead, you will need to compare every file to every other same-sized file.
I suggest taking this approach
Use LINQ's .GroupBy to create a collection of files sizes. Then .Where to only keep the groups with more than one file.
Within those groups, calculate the CRC32 checksum and add it to a collection of known checksums. Compare with previously calculated checksums. If you need to know which files specifically are duplicates you could use a dictionary keyed by this checksum (you can achieve this with another GroupBy. Otherwise a simple list will suffice to detect any duplicates.
The code might look something like this:
var filesSetsWithPossibleDupes = files.GroupBy(f => f.Length)
.Where(group => group.Count() > 1);
foreach (var grp in filesSetsWithPossibleDupes)
{
var checksums = new List<CRC32CheckSum>(); //or whatever type
foreach (var file in grp)
{
var currentCheckSum = crc.ComputeChecksum(file);
if (checksums.Contains(currentCheckSum))
{
//Found a duplicate
}
else
{
checksums.Add(currentCheckSum);
}
}
}
Or if you need the specific objects that could be duplicates, the inner foreach loop might look like
var filesSetsWithPossibleDupes = files.GroupBy(f => f.FileSize)
.Where(grp => grp.Count() > 1);
var masterDuplicateDict = new Dictionary<DupStats, IEnumerable<DupInfo>>();
//A dictionary keyed by the basic duplicate stats
//, and whose value is a collection of the possible duplicates
foreach (var grp in filesSetsWithPossibleDupes)
{
var likelyDuplicates = grp.GroupBy(dup => dup.Checksum)
.Where(g => g.Count() > 1);
//Same GroupBy logic, but applied to the checksum (instead of file size)
foreach(var dupGrp in likelyDuplicates)
{
//Create the key for the dictionary (your code is likely different)
var sample = dupGrp.First();
var key = new DupStats() {FileSize = sample.FileSize, Checksum = sample.Checksum};
masterDuplicateDict.Add(key, dupGrp);
}
}
A demo of this idea.
I think the for loop should be : for (int i = 1; i < dupInfos.Count()-1; i++)
var grps= dupInfos.GroupBy(d=>d.Size);
grps.Where(g=>g.Count>1).ToList().ForEach(g=>
{
...
});
Can you do a union between your two lists? If you have a list of filenames and do a union it should result in only a list of the overlapping files. I can write out an example if you want but this link should give you the general idea.
https://stackoverflow.com/a/13505715/1856992
Edit: Sorry for some reason I thought you were comparing file name not size.
So here is an actual answer for you.
using System;
using System.Collections.Generic;
using System.Linq;
public class ObjectWithSize
{
public int Size {get; set;}
public ObjectWithSize(int size)
{
Size = size;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("start");
var list = new List<ObjectWithSize>();
list.Add(new ObjectWithSize(12));
list.Add(new ObjectWithSize(13));
list.Add(new ObjectWithSize(14));
list.Add(new ObjectWithSize(14));
list.Add(new ObjectWithSize(18));
list.Add(new ObjectWithSize(15));
list.Add(new ObjectWithSize(15));
var duplicates = list.GroupBy(x=>x.Size)
.Where(g=>g.Count()>1);
foreach (var dup in duplicates)
foreach (var objWithSize in dup)
Console.WriteLine(objWithSize.Size);
}
}
This will print out
14
14
15
15
Here is a netFiddle for that.
https://dotnetfiddle.net/0ub6Bs
Final note. I actually think your answer looks better and will run faster. This was just an implementation in Linq.

Categories

Resources