Suppose I have a simple class with properties of a vertical line on a 2D Cartesian coordinate plane such as this:
public class VerticalLine
{
int x, y1, y2;
...
}
Assume that Y1 can be smaller or larger than Y2, but Y1 and Y2 never contain the same value.
In runtime, I will gather a collection(List) of vertical lines in similar format:
List<VerticalLine> lines = new List<VerticalLine>
{
new VerticalLine() { X=12, Y1=3, Y2=15 },
new VerticalLine() { X=23, Y1=23, Y2=5 },
new VerticalLine() { X=32, Y1=12, Y2=10 },
new VerticalLine() { X=37, Y1=15, Y2=12 },
...
};
Note that there are no specific order of the lines in the list.
By drawing imaginary horizontal lines from one end of vertical line to an end of another line, I need to check that if the lines in the above list will be able to connect and form a loop. In other words, pick a line in list, connect one end to an end of another line using a horizontal line, and then to next line and so on... until the last line connect back to the original line. See image for example.
Note that:
ALL the lines in the list must be used.
The order of the lines list is not important in finding the loop. Any line can be the first line.
The directions of lines are not important in finding the loop, meaning Y2 of one line may try to connect to Y1 or Y2 of the next line, as long as they share the same Y value.
In simple words, my problem is how to write a method to check if lines can form a loop.
My first thought of doing so will be using a recursive method to exhaustively try connecting the next line. But this way will involve a large amount of list manipulations, which can be slow. I wonder if there are 'better' way to achieve the same goal.
This is a working solution I have so far. It is simple but probably not the most efficient way of doing so. It is just a recursive and exhaustive method.
public bool CheckForLoop (List<VerticalLine> lines)
{
if (lines == null || lines.Count < 2)
return false;
VerticalLine head = lines[0];
return CheckForLoop(head.Y1, head.Y2, lines.GetRange(1, lines.Count - 1));
}
// helper method for recursion
private bool CheckForLoop(int start, int last, List<VerticalLine> remaining)
{
if (remaining.Count == 1)
{
return remaining[0].Y1 == start && remaining[0].Y2 == last || remaining[0].Y2 == start && remaining[0].Y1 == last;
}
foreach (var next in remaining)
{
bool foundMatching = false;
int newLast = 0;
if (next.Y1 == last)
{
foundMatching = true;
newLast = next.Y2;
} else if (next.Y2 == last)
{
foundMatching = true;
newLast = next.Y1;
}
if (foundMatching)
{
List<VerticalLine> remaining2 = new List<VerticalLine>(remaining);
remaining2.Remove(next);
if (CheckForLoop(start, newLast, remaining2))
{
return true;
}
}
}
return false;
}
What I don't like about this is that it requires a separate helper method for the recursion. I would be best if everything is done within the one method itself.
Related
I'm new to c#. I have a task to make a type of minesweeper, but which immediately opens a solution.
static void Main(string[] args)
{
Console.Write("Enter the width of the field: ");
int q = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter the length of the field: ");
int w = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter the number of bombs: ");
int c = Convert.ToInt32(Console.ReadLine());
Random rand = new Random();
var charArray = new char[q, w];
var intArray = new int[q, w];
for (int i = 0; i < q; i++)
{
for (int j = 0; j < w; j++)
{
intArray[i, j] = rand.Next(2);
charArray[i, j] = intArray[i, j] == 0 ? '_' : '*';
Console.Write(charArray[i, j]);
}
Console.WriteLine();
}
}
}
}
Two arrays should be output. Everything should be closed on the first one, that is, there should be only the characters: _ and *
0 - these are places without mines, I replaced them with a symbol _
1 - these are places with mines, I replaced them with an asterisk symbol, but they do not accept the number of mines entered by the user. And it is necessary that there are as many "*" characters as there are mines.
And in the second array there should be an already open solution of the game. That is, the cells next to which there are mines should take a number meaning the number of mines next to this cell.
Please help me..
Compiling the current code
Random random = new Random();
while(c > 0)
{
var rq = random.Next(q);
var rw = random.Next(w);
if(intArray[rq,rw] == 0)
{
intArray[rq, rw] = 1;
c--;
}
}
I would suggest dividing the problem in smaller manageable chunks. For instance, you can place the bombs in a initial step, and on a second step build the solution. You can build the solution at the same time you place the bombs, although for clarity you can do it after.
Naming of variables is also important. If you prefer using single letter variable names, I believe that's fine for the problem limits, however I would use meaningful letters easier to remember. eg: W and H for the width and height of the board, and B for the total number of bombs.
The first part of the problem then can be described as placing B bombs in a WxH board. So instead of having nested for statements that enumerate WxH times, it's better to have a while loop that repeats the bomb placing logic as long as you have remaining bombs.
Once you generate a new random location on the board, you have to check you haven't placed a bomb there already. You can have an auxiliary function HasBomb that checks that:
bool HasBomb(char[,] charArray, int x, int y)
{
return charArray[x,y] == '*';
}
I'll leave error checking out, this function being private can rely on the caller sending valid coordinates.
Then the bomb placing procedure can be something like:
int remainingBombs = B;
while (remainingBombs > 0)
{
int x = rand.Next(W);
int y = rand.Next(H);
if (!HasBomb(charArray, x, y)
{
charArray[x,y] = '*';
remainingBombs--;
}
}
At this point you may figure out another concern. If the number B of bombs to place is larger than the available positions on the board WxH, then you wont be able to place the bombs on the board. You'll have to check for that restriction when requesting the values for W, H and B.
Then in order to create the array with the number of bombs next to each position, you'll need some way to check for all the neighbouring positions to a given one. If the position is in the middle of the board it has 8 neighbour positions, if it's on an edge it has 5, and if it's on a corner it has 3. Having a helper function return all the valid neighbour positions can be handy.
IEnumerable<(int X, int Y)> NeighbourPositions(int x, int y, int W, int H)
{
bool leftEdge = x == 0;
bool topEdge = y == 0;
bool rightEdge = x == W - 1;
bool bottomEdge = y == H - 1;
if (!leftEdge && !topEdge)
yield return (x-1, y-1);
if (!topEdge)
yield return (x, y-1);
if (!rightEdge && !topEdge)
yield return (x+1, y-1);
if (!leftEdge)
yield return (x-1, y);
if (!rightEdge)
yield return (x+1, y);
if (!leftEdge && !bottomEdge)
yield return (x-1, y+1);
if (!bottomEdge)
yield return (x, y+1);
if (!rightEdge && !bottomEdge)
yield return (x+1, y+1)
}
This function uses Iterators and touples. If you feel those concepts are too complex as you said are new to C#, you can make the function return a list with coordinates instead.
Now the only thing left is to iterate over the whole intArray and increment the value on each position for each neighbour bomb you find.
for (int x = 0; x < W; x++)
{
for (int y = 0; y < H; y++)
{
foreach (var n in NeighbourPositions(x, y, W, H))
{
if (HasBomb(charArray, n.X, n.Y))
intArray[x,y]++;
}
}
}
The answers here are mostly about generating random x and random y put in loop and trying to put the mine into empty cells. It is ok solution, but if you think of it, it is not that sufficient. Every time you try to find a new random cell, there is chance that cell is already a mine. This is pretty much alright, if you don't put too many mines into your field, but if you put some greater number of mines, this event can occur quite often. This means that the loop might take longer than usually. Or, theoretically, if you wanted to put 999 mines into 1000 cell field, it would be really hard for the loop to fill all the necessary cells, especially for the last mine. Now, I am not saying that the solutions here are bad, I think, it's really alright solution for many people. But if someone wanted a little bit efficient solution, I have tried to crate it.
Solution
In this solution, you iterate each cell and try to calculate a probability of the mine to be placed there. I have come up with this easy formula, which calculates the probability:
Every time you try to place a mine into one cell, you calculate this formula and compare it to random generated number.
bool isMine = random.NextDouble() < calcProbability();
A simple line class is define as two PointF members containing the start and end coordinates:
public class Line {
PointF s, e;
}
I have two lists containing all horizontal and vertical lines that appear on a drawing canvas and form one or more tables.
List<Line> AllHorizontalLines;
List<Line> AllVerticalLines;
I need to group these lines so that lines belonging to one table are captured in a single group, thus the grouping function would have a signature like this:
List<List<Line>> GroupLines(List<Line> hor, List<Line> ver)
{
}
For simplicity we are assuming that there are only "simple" tables on the page, i.e. no nested table are there. However there can be merged cells, so we have to ignore small horizontal and vertical lines that are smaller than the full height of the parent table. For further simplicity, assume that both input lists are sorted (horizontal lines w.r.t. Y-axis and vertical lines w.r.t. X-axis).
Is there any known algorithm to solve this problem? Or can anyone help me devise one?
Here is my suggested approach:
Clean both lists so there aren't any fully contained 'small' lines.
Pick any line.
Take all the lines that touch (intersect) this line.
For each of these lines take all the lines that touch them.
Continue until you can't find any more touching lines.
You now have a group.
Pick a line from those that remain and repeat until there are no more lines left.
Code:
public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
// Clean the input lists here. I'll leave the implementation up to you.
var ungroupedLines = new HashSet<Line>(horizontalLines.Concat(verticalLines));
var groupedLines = new List<List<Line>>();
while (ungroupedLines.Count > 0)
{
var group = new List<Line>();
var unprocessedLines = new HashSet<Line>();
unprocessedLines.Add(ungroupedLines.TakeFirst());
while (unprocessedLines.Count > 0)
{
var line = unprocessedLines.TakeFirst();
group.Add(line);
unprocessedLines.AddRange(ungroupedLines.TakeIntersectingLines(line));
}
groupedLines.Add(group);
}
return groupedLines;
}
public static class GroupingExtensions
{
public static T TakeFirst<T>(this HashSet<T> set)
{
var item = set.First();
set.Remove(item);
return item;
}
public static IEnumerable<Line> TakeIntersectingLines(this HashSet<Line> lines, Line line)
{
var intersectedLines = lines.Where(l => l.Intersects(line)).ToList();
lines.RemoveRange(intersectedLines);
return intersectedLines;
}
public static void RemoveRange<T>(this HashSet<T> set, IEnumerable<T> itemsToRemove)
{
foreach(var item in itemsToRemove)
{
set.Remove(item);
}
}
public static void AddRange<T>(this HashSet<T> set, IEnumerable<T> itemsToAdd)
{
foreach(var item in itemsToAdd)
{
set.Add(item);
}
}
}
Add this method to Line
public bool Intersects(Line other)
{
// Whether this line intersects the other line or not.
// I'll leave the implementation up to you.
}
Notes:
If this code runs too slowly you might need to scan horizontally, picking up connected lines as you go. Might also be worth looking at this.
Specialised:
public static IEnumerable<IEnumerable<Line>> Group(IEnumerable<Line> horizontalLines, IEnumerable<Line> verticalLines)
{
// Clean the input lists here. I'll leave the implementation up to you.
var ungroupedHorizontalLines = new HashSet<Line>(horizontalLines);
var ungroupedVerticalLines = new HashSet<Line>(verticalLines);
var groupedLines = new List<List<Line>>();
while (ungroupedHorizontalLines.Count + ungroupedVerticalLines.Count > 0)
{
var group = new List<Line>();
var unprocessedHorizontalLines = new HashSet<Line>();
var unprocessedVerticalLines = new HashSet<Line>();
if (ungroupedHorizontalLines.Count > 0)
{
unprocessedHorizontalLines.Add(ungroupedHorizontalLines.TakeFirst());
}
else
{
unprocessedVerticalLines.Add(ungroupedVerticalLines.TakeFirst());
}
while (unprocessedHorizontalLines.Count + unprocessedVerticalLines.Count > 0)
{
while (unprocessedHorizontalLines.Count > 0)
{
var line = unprocessedHorizontalLines.TakeFirst();
group.Add(line);
unprocessedVerticalLines.AddRange(ungroupedVerticalLines.TakeIntersectingLines(line));
}
while (unprocessedVerticalLines.Count > 0)
{
var line = unprocessedVerticalLines.TakeFirst();
group.Add(line);
unprocessedHorizontalLines.AddRange(ungroupedHorizontalLines.TakeIntersectingLines(line));
}
}
groupedLines.Add(group);
}
return groupedLines;
}
This assumes no lines overlap as it doesn't check if horizontal lines touch other horizontal lines (same for vertical).
You can probably remove the if-else. That's just there in case there are vertical lines not attached to horizontal lines.
The following seems to work:
Set up a dictionary mapping bounding rectangles to a list of lines in each rectangle.
For each line in both your input lists (we don't care about the direction)
Create a bounding rectangle out of the line
Check if the line crosses any existing bounding rectangle(s).
If so, merge the lines from those rectangle(s), add the current line, delete the touched rectangles, calculate a new bounding rectangle, and check again.
Otherwise, add this new rectangle to the dictionary and delete the old one(s).
Return the list of lines from each rectangle.
Here's the code I've got:
public static IEnumerable<IEnumerable<Line>> GroupLines(IEnumerable<Line> lines)
{
var grouped = new Dictionary<Rectangle, IEnumerable<Line>>();
foreach (var line in lines)
{
var boundedLines = new List<Line>(new[] { line });
IEnumerable<Rectangle> crossedRectangles;
var boundingRectangle = CalculateRectangle(boundedLines);
while (
(crossedRectangles = grouped.Keys
.Where(r => Crosses(boundingRectangle, r))
.ToList()
).Any())
{
foreach (var crossedRectangle in crossedRectangles)
{
boundedLines.AddRange(grouped[crossedRectangle]);
grouped.Remove(crossedRectangle);
}
boundingRectangle = CalculateRectangle(boundedLines);
}
grouped.Add(boundingRectangle, boundedLines);
}
return grouped.Values;
}
public static bool Crosses(Rectangle r1, Rectangle r2)
{
return !(r2.Left > r1.Right ||
r2.Right < r1.Left ||
r2.Top > r1.Bottom ||
r2.Bottom < r1.Top);
}
public static Rectangle CalculateRectangle(IEnumerable<Line> lines)
{
Rectangle rtn = new Rectangle
{
Left = int.MaxValue,
Right = int.MinValue,
Top = int.MaxValue,
Bottom = int.MinValue
};
foreach (var line in lines)
{
if (line.P1.X < rtn.Left) rtn.Left = line.P1.X;
if (line.P2.X < rtn.Left) rtn.Left = line.P2.X;
if (line.P1.X > rtn.Right) rtn.Right = line.P1.X;
if (line.P2.X > rtn.Right) rtn.Right = line.P2.X;
if (line.P1.Y < rtn.Top) rtn.Top = line.P1.Y;
if (line.P2.Y < rtn.Top) rtn.Top = line.P2.Y;
if (line.P1.Y > rtn.Bottom) rtn.Bottom = line.P1.Y;
if (line.P2.Y > rtn.Bottom) rtn.Bottom = line.P2.Y;
}
return rtn;
}
OK. I finally devised an efficient algorithm myself. Here are the steps for any future reader.
Define a grand collection of Tuple<List<Line>, List<Line>>. Each tuple in this collection will represent one table (all horizontal and vertical lines of that table).
Find all vertical lines that touch a horizontal line's starting point at one end and another horizontal line's starting point at the other end. These will be the left-most lines of each table.
Now for each left-line (call it LV):
a. locate all the vertical lines that have same starting and ending Y as LV and thus belong to the same table. Add these "sister" lines to a list called MyVSisters.
b. Locate the right-most vertical sister line's X-coordinate (call it RVX).
c. Now locate all the horizontal lines that stretch from the LV's X to RVX and have their Y between LV's Y1 and Y2. Add these lines to a collection called MyHSisters.
d. Add the tuple MyHSisters and MyVSisters to the grand collection.
Return grand collection.
I'm not marking it as answer yet. I'll wait for response from both of the guys and compare performance and accuracy of all 3 before deciding which one is the best answer.
I am having some trouble correctly implementing the Bentley-Ottmann algorithm in C#. I am trying to implement it according to the pseudocode here. I have posted my main code below. Assuming my BST and PriorityQueue classes are implemented correctly, do you see any problems with the code?
There are no errors, but not all intersection points are found, only some. My guess is that there's an error in the else part of the code (when the current event is an intersection point). I'm not sure what the pseudocode means by swapping the position of two segments in the BST. Is the way I do it fine? Because in the end, the two aren't really swapped in the BST. I can't just change their positions either, because that could break the BST properties.
Also, am I right in assuming that segments are ordered in the BST by the Y-coordinate of their left endpoint?
Another error I've noticed that I can't seem to be able to track down is that sometimes the point (0, 0) gets into eventList. (0, 0) is outputted by Geometry.Intersects in case there is no intersection, but in that case the if conditions should stop it from getting added. I have no idea how it gets in. If I print the contents of eventList after adding a point in, (0, 0) never shows up. If I print the contents after extracting and popping an element, (0, 0) sometimes shows up. Could this have anything to do with the Pop() method messing with the references, or is it definitely a problem in my PriorityQueue implementation?
If needed I can show my implementations for the BST and priority queue as well.
static class BentleyOttman
{
private static void AddIntersectionEvent(PriorityQueue eventList, Segment segEv, Segment segA, SegPoint i)
{
i.IntersectingSegments = new Tuple<Segment, Segment>(segEv, segA);
i.Type = SegmentPointType.IntersectionPoint;
eventList.Add(i);
}
public static void Solve(Panel surface, TextBox debug)
{
debug.Clear();
var segList = Generator.SegList;
PriorityQueue eventList = new PriorityQueue();
foreach (Segment s in segList)
{
eventList.Add(new SegPoint(s.A, s, SegmentPointType.LeftEndpoint));
eventList.Add(new SegPoint(s.B, s, SegmentPointType.RightEndpoint));
}
BST sweepLine = new BST();
while (!eventList.Empty)
{
SegPoint ev = eventList.Top();
eventList.Pop();
if (ev.Type == SegmentPointType.LeftEndpoint)
{
Segment segEv = ev.Segment;
sweepLine.Insert(segEv);
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(segEv, segA, out i.Point))
{
AddIntersectionEvent(eventList, segA, segEv, i);
}
if (segB != null && Geometry.Intersects(segEv, segB, out i.Point))
{
AddIntersectionEvent(eventList, segEv, segB, i);
}
}
else if (ev.Type == SegmentPointType.RightEndpoint)
{
Segment segEv = ev.Segment;
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
sweepLine.Remove(segEv);
SegPoint i = new SegPoint();
if (segA != null && segB != null && Geometry.Intersects(segA, segB, out i.Point))
{
AddIntersectionEvent(eventList, segA, segB, i);
}
}
else
{
Generator.DrawPoint(ev.Point, surface, Brushes.Red);
Segment seg1 = ev.IntersectingSegments.Item1;
Segment seg2 = ev.IntersectingSegments.Item2;
sweepLine.Remove(seg1);
sweepLine.Remove(seg2);
Segment t = new Segment(seg1);
seg1 = new Segment(seg2);
seg2 = new Segment(t);
sweepLine.Insert(seg1);
sweepLine.Insert(seg2);
Segment segA = sweepLine.InorderPre(seg2);
Segment segB = sweepLine.InorderSuc(seg1);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(seg2, segA, out i.Point))
AddIntersectionEvent(eventList, segA, seg2, i);
if (segB != null && Geometry.Intersects(seg1, segB, out i.Point))
AddIntersectionEvent(eventList, seg1, segB, i);
}
}
}
}
I really cannot understand your code without some idea of what exactly the other classes do, but I can answer some of your other questions.
The segments are ordered in the BST by the Y coordinate of their intersection with the sweep line. So when we encounter a left endpoint we add the segment to the tree using the y coordinate of the left endpoint of the entering segment (comparing it with the Y coordinate of the intersection of the other segment with the sweep line). When we encounter a right endpoint we remove the segment from the tree. When we encounter an intersection, then the order of the intersections of the two segments with the sweep line switches, so we swap the two segments in the tree. For example consider the two segments
A = {(-1,1),(1,-1)} and
B = {(-1,-1),(1,1)}
When the X coordinate of the sweep line is less than 0 then the intersection of segment A with the sweep line is greater than the intersection of segment B with the sweep line. and if the sweep line is greater than 0 the reverse is true. (Draw a picture.)
It is probably instructive to draw a simple example, and trace what is going on step by step, drawing the sweep line for each event and labeling the segments in columns between the events, then keeping track of the BST and verifying that the BST keeps the same order as the segments in the region where it is valid. (I'm sorry if that is not as clear as it could be.)
Note: This assumes that your segments are in "general position", i.e. that no segment is vertical, no more than two segments intersect at a given point, etc. Dealing with segments not in general position is outlined on the wikipedia page
I'm developing an application to split an image grid equally and center the images (based on their similarity). So far, I could manage to fix a grid of images with small sizes, but whenever I try a larger "sprite" size (100x100, for instance), I get Stack Overflow error.
Yes I'm using recursion, but whenever a pixel is checked, I set a boolean to deactivate it, copy it to a list and go on checking the others (in all directions), until the list is filled up with an image from the grid. I'm not sure if this is the best way since for each call, I call the same method 7 times (supposing there are 7 adjacent pixels which weren't checked yet)... until there are no pixels left to check, and I can move on to the next image in the grid.
I tried tracing where the error started happening, it was after checking more or less 1600 pixels and adding them to the List. MyPixel is a class which contains 4 variables: x(int), y(int), color (Color), and checked (bool)
public void processSprite(int i, int j)
{
//OOO
//OXO
//OOO
pixeltemp.Add(new MyPixel(imap.pixels[i, j].x, imap.pixels[i, j].y, imap.pixels[i, j].color));
imap.pixels[i, j].read = true;
//OOO
//OOX
//OOO
try
{
if (!imap.pixels[i + 1, j].read)
{
if (imap.pixels[i + 1, j].color.A == 0) //Found a Border
{
imap.pixels[i + 1, j].read = true;
}
else
{
processSprite(i + 1, j);
}
}
}
//... (code goes on)
}
pixeltemp is the temporary list of pixels which holds the image (List<MyPixel>)
imap contains the entire image (List<MyPixel>)
I guess it's not a memory problem since my app just takes about 16mb tops.
My question is, why do I have this "Stack overflow" error if it's not an infinite recursion? Is there an easier way to do this? I do think my code looks ugly, I just have no idea how to make it better.
Thanks in advance !
Stack overflows aren't caused by infinite recursion but by more recursion (or, rather, call stack) than the process can handle. In your case, each recursive call to processSprite is going to do the same number of recursive calls to processSprite. So in the worst-case scenario of 1600 pixels without finding a boundary, your call tree would look like this:
processSprite(0, j)
processSprite(1, j)
processSprite(2, j)
...
processSprite(1599, j) <-- That's 1600 call frames,
enough for an overflow.
You'll want to reorganize your algorithm into a linear loop that does a depth-first search, perhaps in a spiral pattern if you're wanting to to find a pixel that's closest to the starting point. I'm sure there are already other spiffy algorithms others have already developed to solve this problem.
Edit:
I think I understand better now the problem that you're trying to solve. It sounds like you have an image that may contain multiple image tiles surrounded by 0-alpha pixels and you want to find the bounding rectangles for each of these tiles. That looked like an interesting problem to solve, so I implemented it:
IEnumerable<Rectangle> FindImageTiles(Bitmap compositeImage)
{
var result = new List<Rectangle>();
// Scan for a non-empty region that hasn't already been "captured"
for (var x = 0; x < compositeImage.Width; x++)
{
for (var y = 0; y < compositeImage.Height; y++)
{
// Only process the pixel if we don't have a rectangle that
// already contains this and if it's not empty
if (!result.Any(r => r.Contains(x, y))
&& compositeImage.GetPixel(x, y).A != 0)
{
// Now that we've found a point, create a rectangle
// surrounding that point, then expand outward until
// we have a bounding rectangle that doesn't intersect
// with the tile
var rect = new Rectangle(x - 1, y - 1, 2, 2);
bool foundBounds = false;
while (!foundBounds)
{
var xRange = Enumerable.Range(rect.Left, rect.Right)
.Where(px => px >= 0 && px < compositeImage.Width);
var yRange = Enumerable.Range(rect.Top, rect.Bottom)
.Where(py => py >= 0 && py < compositeImage.Height);
// Adjust the top
if (rect.Top >= 0
&& xRange
.Select(bx => compositeImage.GetPixel(bx, rect.Top))
.Any(p => p.A != 0))
{
rect.Y--;
rect.Height++;
}
else if (rect.Bottom < compositeImage.Height
&& xRange
.Select(bx => compositeImage.GetPixel(bx, rect.Bottom))
.Any(p => p.A != 0))
{
rect.Height++;
}
else if (rect.Left >= 0
&& yRange
.Select(by => compositeImage.GetPixel(rect.Left, by))
.Any(p => p.A != 0))
{
rect.X--;
rect.Width++;
}
else if (rect.Right < compositeImage.Width
&& yRange
.Select(by => compositeImage.GetPixel(rect.Right, by))
.Any(p => p.A != 0))
{
rect.Width++;
}
else
{
foundBounds = true;
}
}
result.Add(rect);
}
}
}
return result;
}
what i have is a textbox into which the user types a string. The string will look something like this:
G32:04:20:40
Then the user hits the search button. The program must then open a textfile and search for the "nearest" five strings to the one they entered and display them in a listbox.
I'll define "nearest string" as much as i can (most likely using a very long complicated example).
The data contained in the text file looks like this:
G32:63:58:11 JG01
G32:86:98:30 JG01
G33:50:05:11 JG06
G33:03:84:12 JG05
G34:45:58:11 JG07
G35:45:20:41 JG01
G35:58:20:21 JG03
So if the user types in the string :
G33:89:03:20
The five closest results should display in the list box like this:
G33:50:05:11 JG06
G33:03:84:12 JG05
G32:86:98:30 JG01
G32:63:58:11 JG01
G34:45:58:11 JG07
I should probably point out at this point that strings are coordinates and the value after "JG" represents the value of something at that coordinate.
The way i got to those 5 is by going through the string piece by piece. So the user typed in "G33" so i find all those with G33 at the beginning - if there are none then i find the closest to G33. Then it was "89" so i find all those where the next part is "89" if there are none, then the closest to 89 the better and so on.
What i need to know is how do i go about doing this? I've built the visual components and i also have code in place that deals with similar kinds of things but when it comes to this i'm truely stumped. As you can probably tell by now, i'm rather new to C#, but i'm learning :)
EDIT: Search Code
private void btnSearch_Click(object sender, EventArgs e)
{
lstResult.Items.Clear();
if (txtSearch.Text == String.Empty)
{
MessageBox.Show("The textbox is empty, there is nothing to search.",
"Textbox empty", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
this.CheckFormatting();
}
}
private long GetIndexForCoord(string coord)
{
// gets out a numerical value for each coordinate to make it easier to compare
Regex m_regex = new Regex("\\d\\d:\\d\\d:\\d\\d:\\d\\d");
string cleaned = m_regex.Match(coord).Value;
cleaned = cleaned.Replace(':', '0');
return Convert.ToInt64(cleaned);
}
private List<string> GetResults(string coord)
{
// gets out the 5 closest coordinates
long index = GetIndexForCoord(coord);
// First find the 5 closest indexes to the one we're looking for
List<long> found = new List<long>();
while (found.Count < 5)
{
long closest = long.MaxValue;
long closestAbs = long.MaxValue;
foreach (long i in m_indexes)
{
if (!found.Contains(i))
{
long absIndex = Math.Abs(index - i);
if (absIndex < closestAbs)
{
closest = i;
closestAbs = absIndex;
}
}
}
if (closest != long.MaxValue)
{
found.Add(closest);
}
}
// Then use those indexes to get the coordinates from the dictionary
List<string> s = new List<string>();
foreach (long i in found)
{
s.Add(m_dic[i]);
}
return s;
}
private void CheckFormatting()
{
StringReader objReader = new StringReader(txtSearch.Text);
bool FlagCheck = true;
if (!Regex.IsMatch(txtSearch.Text,
"G3[0-9]{1}:[0-9]{2}:[0-9]{2}:[0-9]{2}"))
{
FlagCheck = false;
}
if (FlagCheck == true)
{
this.CheckAndPopulate();
}
else
{
MessageBox.Show("Your search coordinates are not formatted correctly.",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void CheckAndPopulate()
{
StreamReader objReader = new StreamReader("Jumpgate List.JG");
List<String> v = new List<String>();
do
{
v.Add(objReader.ReadLine());
}
while (objReader.Peek() != -1);
objReader.Close();
foreach (string c in v)
{
long index = GetIndexForCoord(c);
m_dic.Add(index, c);
m_indexes.Add(index);
}
List<string> results = GetResults(txtSearch.Text);
foreach (string c in results)
{
lstResult.Items.Add(c);
}
}
Edit: Added suggested code to read in the file. And also an explanation at the end.
Edit2: Added if check around the adding to the dictionary/list to handle duplicates.
Please note, this code is almost certainly quite inefficient (except possibly by accident) and would need to be cleaned up and error handling would need to be added etc, but it might give you a starting point for writing better code at least.
You probably want to take a look at k-nearest neighbor algorithm to find out the proper way of doing this.
I've assumed that the letter in the beginning is always G and that all 4 parts of the coordinates are always 2 digits each.
Add the following 2 to your class/form:
Dictionary<long, string> m_dic = new Dictionary<long, string>();
List<long> m_indexes = new List<long>();
Then initialize them with the following code (I've assumed you've already read in all the coordinates into a string array called v with one coordinate per item):
foreach (string c in v)
{
long index = GetIndexForCoord(c);
if(!m_dic.ContainsKey(index))
{
m_dic.Add(index, c);
m_indexes.Add(index);
}
}
Then add the following 2 methods:
// gets out a numerical value for each coordinate to make it easier to compare
private long GetIndexForCoord(string coord)
{
Regex m_regex = new Regex("\\d\\d:\\d\\d:\\d\\d:\\d\\d");
string cleaned = m_regex.Match(coord).Value;
cleaned = cleaned.Replace(':', '0');
return Convert.ToInt64(cleaned);
}
// gets out the 5 closest coordinates
private List<string> GetResults(string coord)
{
long index = GetIndexForCoord(coord);
// First find the 5 closest indexes to the one we're looking for
List<long> found = new List<long>();
while (found.Count < 5)
{
long closest = long.MaxValue;
long closestAbs = long.MaxValue;
foreach (long i in m_indexes)
{
if (!found.Contains(i))
{
long absIndex = Math.Abs(index - i);
if (absIndex < closestAbs)
{
closest = i;
closestAbs = absIndex;
}
}
}
if (closest != long.MaxValue)
{
found.Add(closest);
}
}
// Then use those indexes to get the coordinates from the dictionary
List<string> s = new List<string>();
foreach (long i in found)
{
s.Add(m_dic[i]);
}
return s;
}
And finally when the user enter the data you send in that data to the method as:
List<string> results = GetResults(lookingFor);
You can then use the results to populate your listbox.
The code works by converting each coordinate to a numerical value called index (since it's easier to work with) and it then adds all the coordinates to a dictionary with the index as the key.
When it's looking up the closest coordinates it compares the difference in value between the index you're looking for and each of the previously stored indexes to find the 5 closest ones (it uses the Math.Abs method so that it can get the difference without having to worry about negative numbers). It's quite inefficient since it loops through each value once for each coordinate you want to find (so if your list contains 1000 coordinates and you want to find the 5 closest, it'll go through the inner loop 5000 times, I'd assume that could probably be cut down to just 1000 times by improving the code, I suggest looking at the wiki link close to the top of this answer for a better algorithm).