Error when pulling from database to create objects after successful creation - c#

Let me start by apologizing for the complexity of this post. hopefully I am missing something simple but in order to find out I have to make a lengthy explanation.
I'm building a staff tracking app that allows users to draw polygons on a map. The polygon is used as a zone. If the device location is inside the zone it will set them to one status such as "In" and then set them to another status like "Out" when they leave. I'm using several nugets; SQLite, TK.CustomMap, PopupPlugin, to accomplish as much of this as possible in the Shared Project. The SQLite data model is based on the structure of the remote database for the solution which also has a desktop application, web app, and many other interfaces so that structure must be maintained.
The tables involved in this operation are Zones, ZonePoints and StatusClass. When the Zone is saved the Points of the TK.Polygon are saved into the points table. The Statuses assigned to the in and out status of the Zone are assigned to the zone as is the zone name.
The process works this way - first the user clicks on a button to add the zone. This creates a new zone, saves it to the database and gets its id from the table. For now, the ZoneSys (Primary Key in the remote db) is also set to the id although when the API is ready this will be Auto Incremented in the remote db. Here is that command and the objects it references. I did not include the database method definitions given that they work on the first time through, but if anyone thinks they will help solve this riddle please let me know -
public static List<Position> ActiveZonePositions = new List<Position>();
public static int ActiveZoneID;
public static Zone ActiveZone;
public static int PointCount;
public static bool NewZoneOpen = false;
public Command<EventArgs> OpenNewZone
{
get
{
return new Command<EventArgs>(async e =>
{
NewZoneOpen = true;
PointCount = 0;
ActiveZonePositions.Clear();
Zone ZoneToAdd = new Zone
{
Contactsys = MobileUser.ContactSys,
ZoneTypeSys = 1,
OrganizationSys = MobileUser.OrganizationSys,
ZoneName = null
};
ActiveZoneID = await AddZoneToDBAsync(ZoneToAdd);
ZoneToAdd.ID = ActiveZoneID;
ZoneToAdd.ZoneSys = ActiveZoneID;
await AddZoneToDBAsync(ZoneToAdd);
ActiveZone = await App.Database.GetZoneAsync(ActiveZoneID);
});
}
}
As the user click points in the map the polygon is drawn using those positions and those positions are also used to create points which are added to a static list. Here is the MapClicked_Command -
public Command<Position> MapClickedCommand
{
get
{
return new Command<Position>(async position =>
{
if (NewZoneOpen)
{
bool isPointInPolygon = IsPointInAnyPolygon(position);
if (isPointInPolygon)
{
var action = await Application.Current.MainPage.DisplayActionSheet(
"Region Collides with Another Region",
"Cancel",
null,
"Try Again",
"Close Zone Editor");
if (action == "Close Zone Editor")
{
await RemoveZoneAsync(ActiveZoneID);
}
if (action == "Try Again")
{
return;
}
}
else if (!isPointInPolygon)
{
ActiveZonePositions.Add(position);
}
if (ActiveZonePositions.Count == 2)
{
ZonePolyLine.LineCoordinates = ActiveZonePositions;
_lines.Remove(ZonePolyLine);
_lines.Add(ZonePolyLine);
}
else if (ActiveZonePositions.Count == 3)
{
ActiveZonePositions.Add(position);
_lines.Remove(ZonePolyLine);
TKPolygon poly = new TKPolygon
{
StrokeColor = System.Drawing.Color.CornflowerBlue,
StrokeWidth = 2f,
Color = System.Drawing.Color.CornflowerBlue
};
foreach (Position pos in ActiveZonePositions)
{
poly.Coordinates.Add(pos);
}
_polygons.Add(poly);
_currentPolygon.Clear();
_currentPolygon.Add(poly);
currentPolygonRendering = true;
}
else if (ActiveZonePositions.Count > 3)
{
ActiveZonePositions.Add(position);
TKPolygon poly = new TKPolygon
{
StrokeColor = System.Drawing.Color.CornflowerBlue,
StrokeWidth = 2f,
Color = System.Drawing.Color.CornflowerBlue
};
foreach (Position pos in ActiveZonePositions)
{
poly.Coordinates.Add(pos);
}
_polygons.Remove(_polygons.Last());
_polygons.Add(poly);
_currentPolygon.Clear();
_currentPolygon.Add(poly);
}
var pin = new TKCustomMapPin
{
Position = new TK.CustomMap.Position(position.Latitude, position.Longitude),
Title = string.Format("Pin {0}, {1}", position.Latitude, position.Longitude),
IsVisible = true,
IsDraggable = true,
ShowCallout = true
};
_pins.Add(pin);
await CreatePointAsync(position);
PointCount++;
}
else if (EditZoneOpen)
{
ActiveZonePositions.Add(position);
var poly = _polygons[0];
poly.Coordinates.Clear();
foreach (Position pos in ActiveZonePositions)
{
poly.Coordinates.Add(pos);
}
_polygons.Remove(_polygons.Last());
_polygons.Add(poly);
_currentPolygon.Clear();
_currentPolygon.Add(poly);
var pin = new TKCustomMapPin
{
Position = new TK.CustomMap.Position(position.Latitude, position.Longitude),
Title = string.Format("Pin {0}, {1}", position.Latitude, position.Longitude),
IsVisible = true,
IsDraggable = true,
ShowCallout = true
};
_pins.Add(pin);
await CreatePointAsync(position);
PointCount++;
}
});
}
}
Here is the CreatePointAsyncMethod -
public async Task CreatePointAsync(TK.CustomMap.Position position)
{
var zone = await RetrieveZoneAsync(ActiveZoneID);
Model.Point PointToAdd = new Model.Point
{
ZoneSys = zone.ZoneSys,
PointName = "",
Latitude = position.Latitude,
Longitude = position.Longitude,
PointOrder = PointCount + 1
};
ActiveZonePoints.Add(PointToAdd);
}
Here is the IsPointInAnyPolygon method that checks against the list of polygons to ensure the point clicked is not inside any of them as well as its supporting methods.
private bool IsPointInAnyPolygon(Position position)
{
bool inBounds = false;
for (var i = 0; i < ZonePolygons.Count(); i++)
foreach (ZonePolygon zpoly in ZonePolygons)
{
TKPolygon tkpoly = zpoly.Zpolygon;
inBounds = IsPointInPolygon(position, tkpoly.Coordinates);
if (inBounds)
{
ActiveZoneID = zpoly.ID;
return inBounds;
}
}
return inBounds;
}
private bool IsPointInPolygon(TK.CustomMap.Position position, List<Position> coords)
{
int intersectCount = 0;
for (int j = 0; j < coords.Count() - 1; j++)
{
if (j+1 >= coords.Count())
{
if (rayCastIntersect(position, coords[j], coords[0]))
{
intersectCount++;
}
} else if (rayCastIntersect(position, coords[j], coords[j + 1]))
{
intersectCount++;
}
}
return ((intersectCount % 2) == 1); // odd = inside, even = outside;
}
private bool rayCastIntersect(TK.CustomMap.Position position, TK.CustomMap.Position vertA, TK.CustomMap.Position vertB)
{
double aY = vertA.Latitude;
double bY = vertB.Latitude;
double aX = vertA.Longitude;
double bX = vertB.Longitude;
double pY = position.Latitude;
double pX = position.Longitude;
if ((aY > pY && bY > pY) | (aY < pY && bY < pY)
| (aX < pX && bX < pX))
{
return false; // a and b can't both be above or below pt.y, and a or
// b must be east of pt.x
}
double m = (aY - bY) / (aX - bX); // Rise over run
double bee = (-aX) * m + aY; // y = mx + b
double x = (pY - bee) / m; // algebra is neat!
return x > pX;
}
Upon clicking the save button a popup opens that allows the user to give the Zone a name, define the statuses it will be assigned and the points are added to the database. There is a ZoneSys column in the Points table which allows the points to be matched to their respective zones when retrieved. This is done withe the UpdateZone command
public Command<EventArgs> UpdateZone
{
get
{
return new Command<EventArgs>(async e =>
{
Zone zone = await App.Database.GetZoneAsync(ActiveZoneID);
zone.ZoneName = ZoneParameters.ZoneName;
zone.StatusSys = ZoneParameters.InStatus.StatusSys;
zone.OutOfZoneStatusSys = ZoneParameters.OutStatus.StatusSys;
await AddZoneToDBAsync(zone);
if (MapPage.SaveZoneInfoPopupPageOpen)
{
SavePointsOnExit();
MapPage.SaveZoneInfoPopupPageOpen = false;
}
});
}
}
The UpdateZone command calls the SavePointsOnExit method
private async void SavePointsOnExit()
{
ActiveZonePoints.OrderBy(o => o.PointOrder);
for (var i = 0; i < ActiveZonePoints.Count(); i++)
{
Model.Point PointToAdd = new Model.Point();
PointToAdd = ActiveZonePoints[i];
ActivePointID = await AddPointToDBAsync(PointToAdd);
PointToAdd.ID = ActivePointID;
PointToAdd.PointSys = ActivePointID;
await AddPointToDBAsync(PointToAdd);
}
try
{
Zone zone = await RetrieveZoneAsync(ActiveZoneID);
}
catch
{
await Application.Current.MainPage.DisplayActionSheet("no zone returned", "database error", "cancel");
}
try
{
Zone zone = await RetrieveZoneAsync(ActiveZoneID);
await CreateZonedPolygonAsync(zone);
}
catch
{
await Application.Current.MainPage.DisplayActionSheet("Could not create ZonePolygon", "object error", "cancel");
}
ActiveZonePoints.Clear();
ActiveZonePositions.Clear();
NewZoneOpen = false;
ClearPins();
PointCount = 0;
PopulatePoints();
}
In addition to saving the points to the db the SaveZonePointsOnExit method also creates the ZonePolygon and adds it to an observable collection using the CreateZonedPolygonAsync method -
private async Task<ZonePolygon> CreateZonedPolygonAsync(Zone zone)
{
int StatusSys = zone.StatusSys;
var status = await App.Database.GetStatusBySysAsync(StatusSys);
int OutStatusSys = zone.OutOfZoneStatusSys;
var outStatus = await App.Database.GetStatusBySysAsync(OutStatusSys);
var points = await App.Database.GetZonePointsAsync(zone.ZoneSys);
ZonePolygon zonePolygon = new ZonePolygon
{
ID = zone.ID
};
TKPolygon poly = new TKPolygon();
foreach (Model.Point point in points)
{
poly.Coordinates.Add(new Position(point.Latitude, point.Longitude));
}
poly.Color = Color.FromHex(status.ColorCode);
poly.StrokeColor = Color.Firebrick;
poly.StrokeWidth = 5f;
_polygons.Add(poly);
ZonePolygons.Add(zonePolygon);
return zonePolygon;
}
So far all of this works to a point. I have been successful in creating the first Polygon. I don't run into a problem until I attempt to create a second Zone. When I click on the AddZone button a second time that works fine but when I click on the map to begin creating the second zone a nullreference exception occurs.
Given that the first zone is created without issue I think the problem must be arising from something that occurs when the IsPointInAnyPolygon method no longer immediately returns false because the ZonePolygons list is no longer empty. So something about the retrieving of zones from the database to check against is the problem or possibly adding coordinates when the TKPolygon is created. I don't know what has a null reference. I would think that since I am creating the Zones directly from the database that all the objects would be saved properly and their previous references wouldn't matter. I'm very stuck on this.
TL;DR there is an issue with either the CreateZonedPolygonAsync Method or the IsPointInAnyPolygon method

I figured this out. I feel rather silly because I have been stuck on this for several hours spread out over a couple of weeks. Kept coming back to it and couldn't figure it out. The issue was that in the CreateZonedPolygonAsync method I never assigned the TKPolygon created via points to the ZonePolygon object being created. So when I tried to reference it it didn't exist. All that existed was the ID. Can't believe I missed this for this long.
Of course now I'm having brand new problems but at least this is fixed.
All I had to do was add zonePolygon.Zpolygon = poly; as shown here and it works now
private async Task<ZonePolygon> CreateZonedPolygonAsync(Zone zone)
{
int StatusSys = zone.StatusSys;
var status = await App.Database.GetStatusBySysAsync(StatusSys);
int OutStatusSys = zone.OutOfZoneStatusSys;
var outStatus = await App.Database.GetStatusBySysAsync(OutStatusSys);
var points = await App.Database.GetZonePointsAsync(zone.ZoneSys);
ZonePolygon zonePolygon = new ZonePolygon
{
ID = zone.ID
};
TKPolygon poly = new TKPolygon();
foreach (Model.Point point in points)
{
poly.Coordinates.Add(new Position(point.Latitude, point.Longitude));
}
poly.Color = Color.FromHex(status.ColorCode);
poly.StrokeColor = Color.Firebrick;
poly.StrokeWidth = 5f;
zonePolygon.Zpolygon = poly;
_polygons.Add(poly);
ZonePolygons.Add(zonePolygon);
return zonePolygon;
}

Related

How to Update or Add to a Collection properly in Parallel processing (ForEach)

I am trying to process a list of addresses into 2 collections; a list of addresses that meets requirements; a list of scores for each of those addresses that meets requirements.
I have several different criteria to slice and dice the addresses, but I am running into an incorrect implementation of modifying a collection during parallel processing.
For instance, I take the zip code and for each (+388k records) I determine if the Levenshtein Distance is greater or equal to a filtering value. If it does, then add the address to the compiled list of addresses. Then using the SET method, determine if the score for the address already exists. If is does not, then add. Else use the score currently in the collection and update the appropriate property.
I am hitting the "Collection Has Been Modified" error and I can not think of another way to implement the solution. I am looking for alternative ways to accomplish this.
private LevenshteinDistance _ld = new LevenshteinDistance();
private int _filter_zip = int.Parse(ConfigHelper.GetAppSettingByKey("filter_zip"));
public Factory ScoreAddresses(Factory model)
{
Factory result = model;
IList<Address> _compiledList = new List<Address>();
IList<Scores> _compiledScores = new List<Scores>();
ParallelLoopResult MailLoopResult = Parallel.ForEach(
result.ALL_Addresses, factory =>
{
if (factory.MAIL_ZIP_CODE_NBR != null
&& factory.MAIL_ZIP_CODE_NBR.Length >= 1)
{
int _zipDistance = _ld.Compute(
result.Target_Address.MAIL_ZIP_CODE_NBR,
factory.MAIL_ZIP_CODE_NBR);
if (_zipDistance <= _filter_zip)
{
_compiledList.Add(factory);
Scores _score = new Scores();
_compiledScores = _score.Set(_compiledScores,
factory.INDIVIDUAL_ID, "Levenshtein_MAIL_ZIP_CODE_NBR",
_zipDistance, string.Empty, null);
}
}
});
return result;
}
public IList<Scores> Set(IList<Scores> current, int Individual_ID,
string Score_Type, int Levenshtein, string Methaphone, decimal? other)
{
int currentcount = current.Count();
bool updating = false;
IList<Scores> result = current;
try
{
Scores newscore = new Scores();
//Check if scoreing for individual already present
Scores lookingforIndv = current.AsParallel().Where(
p => p.INDIVIDUAL_ID == Individual_ID).FirstOrDefault();
if (lookingforIndv != null)
{
newscore = lookingforIndv;
updating = true;
}
else
{
newscore.INDIVIDUAL_ID = Individual_ID;
updating = false;
}
//Add score based upon indicated score type
switch (Score_Type)
{
case "Levenshtein_MAIL_ZIP_CODE_NBR":
newscore.Levenshtein_MAIL_ZIP_CODE_NBR = Levenshtein;
result.Add(newscore);
break;
//More and More Cases
}
}
catch (Exception e)
{
}
return result;
}

Build up List from another List

I've got a list of Players. Each Player has a Marketvalue. I need to build up a second list which iterates through the player list and builds up a team. The tricky thing is the new team should have at least 15 players and a maximum Marketvalue of 100 Mio +/- 1%.
Does anyone know how to do that elegantly?
private Result<List<Player>> CreateRandomTeam(List<Player> players, int startTeamValue)
{
// start formation 4-4-2
// Threshold tw 20 mio defender 40 mio Midfielder 40 Mio Striker 50 Mio
var playerKeeperList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Keeper);
var playerDefenderList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Defender);
var playerMidfieldList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Midfield);
var playerStrikerList = players.FindAll(p => p.PlayerPosition == PlayerPosition.Striker);
List<Player> keeperPlayers = AddRandomPlayers(playerKeeperList, 2, 0, 20000000);
List<Player> defenderPlayers = AddRandomPlayers(playerDefenderList, 4, 0, 40000000);
List<Player> midfieldPlayers = AddRandomPlayers(playerMidfieldList, 4, 0, 40000000);
List<Player> strikerPlayers = AddRandomPlayers(playerStrikerList, 2, 0, 50000000);
List<Player> team = new List<Player>();
team.AddRange(keeperPlayers);
team.AddRange(defenderPlayers);
team.AddRange(midfieldPlayers);
team.AddRange(strikerPlayers);
var currentTeamValue = team.Sum(s => s.MarketValue);
var budgetLeft = startTeamValue - currentTeamValue;
players.RemoveAll(p => team.Contains(p));
var player1 = AddRandomPlayers(players, 2, 0, budgetLeft);
team.AddRange(player1);
players.RemoveAll(p => player1.Contains(p));
currentTeamValue = team.Sum(t => t.MarketValue);
budgetLeft = startTeamValue - currentTeamValue;
var player2 = players.Aggregate((x, y) => Math.Abs(x.MarketValue - budgetLeft) < Math.Abs(y.MarketValue - budgetLeft) ? x : y);
team.Add(player2);
players.Remove(player2);
return Result<List<Player>>.Ok(team);
}
private static List<Player> AddRandomPlayers(List<Player> players, int playerCount, double minMarketValue, double threshold)
{
// TODO: AYI Implement Random TeamName assign logic
Random rnd = new Random();
var team = new List<Player>();
double assignedTeamValue = 0;
while (team.Count < playerCount)
{
var index = rnd.Next(players.Count);
var player = players[index];
if ((assignedTeamValue + player.MarketValue) <= threshold)
{
team.Add(player);
players.RemoveAt(index);
assignedTeamValue += player.MarketValue;
}
}
return team;
}`
This isn't really a C# question so much as an algorithm question, so there may be a better place for it. AIUI, you want to pick 15 numbers from a list, such that the total adds up to 99-101.
It's likely that there are many solutions, all equally valid.
I think you could do it like this:
Build a list of the 14 cheapest items.
Pick the highest value, so long as the remaining space is greater than the total of the 14 cheapest.
Repeat the above, skipping any players that won't fit.
Fill the remaining places with players from the 'cheapest' list.
This will probably give you a team containing the best and worst players, and one middle-ranking player that just fits.
If you want to do some more research, this sounds like a variant of the coin change problem.
Just to show my solution if someone need's it.
var selection = new EliteSelection();
var crossover = new OnePointCrossover(0);
var mutation = new UniformMutation(true);
var fitness = new TeamFitness(players, startTeamValue);
var chromosome = new TeamChromosome(15, players.Count);
var population = new Population(players.Count, players.Count, chromosome);
var ga = new GeneticAlgorithm(population, fitness, selection, crossover, mutation)
{
Termination = new GenerationNumberTermination(100)
};
ga.Start();
var bestChromosome = ga.BestChromosome as TeamChromosome;
var team = new List<Player>();
if (bestChromosome != null)
{
for (int i = 0; i < bestChromosome.Length; i++)
{
team.Add(players[(int) bestChromosome.GetGene(i).Value]);
}
// Remove assigned player to avoid duplicate assignment
players.RemoveAll(p => team.Contains(p));
return Result<List<Player>>.Ok(team);
}
return Result<List<Player>>.Error("Chromosome was null!");
There is a fitness method which handles the logic to get the best result.
class TeamFitness : IFitness
{
private readonly List<Player> _players;
private readonly int _startTeamValue;
private List<Player> _selected;
public TeamFitness(List<Player> players, int startTeamValue)
{
_players = players;
_startTeamValue = startTeamValue;
}
public double Evaluate(IChromosome chromosome)
{
double f1 = 9;
_selected = new List<Player>();
var indexes = new List<int>();
foreach (var gene in chromosome.GetGenes())
{
indexes.Add((int)gene.Value);
_selected.Add(_players[(int)gene.Value]);
}
if (indexes.Distinct().Count() < chromosome.Length)
{
return int.MinValue;
}
var sumMarketValue = _selected.Sum(s => s.MarketValue);
var targetValue = _startTeamValue;
if (sumMarketValue < targetValue)
{
f1 = targetValue - sumMarketValue;
}else if (sumMarketValue > targetValue)
{
f1 = sumMarketValue - targetValue;
}
else
{
f1 = 0;
}
var keeperCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Keeper);
var strikerCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Striker);
var defCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Defender);
var middleCount = _selected.Count(s => s.PlayerPosition == PlayerPosition.Midfield);
var factor = 0;
var penaltyMoney = 10000000;
if (keeperCount > 2)
{
factor += (keeperCount - 2) * penaltyMoney;
}
if (keeperCount == 0)
{
factor += penaltyMoney;
}
if (strikerCount < 2)
{
factor += (2 - strikerCount) * penaltyMoney;
}
if (middleCount < 4)
{
factor += (4 - middleCount) * penaltyMoney;
}
if (defCount < 4)
{
factor += (4 - defCount) * penaltyMoney;
}
return 1.0 - (f1 + factor);
}
}

Waiting for a parse.com async task to finish in Unity3D

As part of my school project I'm trying to link two tables to decrease the amount of data stored in one table so I wanted to link my "Scores" class with my "CorrectAnswers" class via the ObjectID. However since the tasks are asynchronous, by the time one task is done saving, the other task has already begun or also finished saving and so the ObjectID returns as null.
Here's the code I'm using:
public void SaveScore()
{
ParseObject SendScore = new ParseObject("Scores");
SendScore["Score"] = CheckAnswer.score;
SendScore["user"] = ParseObject.CreateWithoutData("_User", ParseUser.CurrentUser.ObjectId);
SendScore["TestMode"] = MainMenu.testmode;
SendScore["TotalQuestions"] = QuestionCreation.TotalQuestions;
SendScore["CorrectQuestions"] = CheckAnswer.CorrectQuestions;
SendScore.SaveAsync().ContinueWith(t =>
{
ScoreObjectId = SendScore.ObjectId;
});
ParseObject SendCorrectTopics = new ParseObject("CorrectAnswers");
SendCorrectTopics["Score"] = SendScore.ObjectId;
for (int i = 0; i <= 9; i++)
{
string Topic = "Topic" + (i + 1).ToString();
SendCorrectTopics[Topic] = CheckAnswer.CorrectTopics[i];
}
SendCorrectTopics.SaveAsync();
SceneManager.LoadScene(0);
}
How would I be able to make the second save hold until the first save has finished? I'm somewhat new to C# and so don't quite know all it's features yet. I've looked into "await" but unity doesn't seem to like that. Any help would be greatly appreciated!
Thanks in advance,
EDIT: Okay, after a bit more reading on Unity's coroutines, I found a much better way of checking that only relies on checking when needed:
IEnumerator CheckSave()
{
while(ScoreObjectId == null & !DoneSave))
{
print("Running");
yield return new WaitForSeconds(0.5f);
}
DoneSave = false;
SaveTotalTopics();
}
This seems like a much better way of doing it.
Well, it seems the answer was something I've already done before, even if it is a little ugly.
Using Unity's update function I created a check to make sure the ObjectID is not null and the previous save had completed, as so:
void Update () {
if (ScoreObjectId != null & DoneSave)
{
DoneSave = false;
SaveTotalTopics();
}
Thus splitting it into two saves and creating:
public void SaveScore()
{
ParseObject SendScore = new ParseObject("Scores");
SendScore["Score"] = CheckAnswer.score;
SendScore["user"] = ParseObject.CreateWithoutData("_User", ParseUser.CurrentUser.ObjectId);
SendScore["TestMode"] = MainMenu.testmode;
SendScore["TotalQuestions"] = QuestionCreation.TotalQuestions;
SendScore["CorrectQuestions"] = CheckAnswer.CorrectQuestions;
Task SendingScores = SendScore.SaveAsync().ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
DoneSave = false;
print(t.Exception);
}
else
{
DoneSave = true;
print("Setting object ID!");
ScoreObjectId = SendScore.ObjectId;
print(ScoreObjectId);
}
});
}
void SaveTotalTopics()
{
for (int i = 0; i <= 9; i++)
{
string Topic = "Topic" + (i + 1).ToString();
SendCorrectTopics[Topic] = CheckAnswer.CorrectTopics[i];
}
SendCorrectTopics["UserScore"] = ParseObject.CreateWithoutData("Scores", ScoreObjectId);
SendCorrectTopics.SaveAsync().ContinueWith(t =>
{
if(t.IsFaulted || t.IsCanceled)
{
print(t.Exception);
}
else
{
print("Saved!");
}
});
}
I'd also forgotten to use ParseObject.CreateWithoutData() so my first code snippet wouldn't have worked even if I'd found a better method...
So, although I'm not happy with the final result, at least it works and I don't think running an if statement every frame should significantly impact on my game's performance.
Why not use a bool and a while loop?
public IEnumerator SaveScore()
{
bool canContinue = false;
ParseObject SendScore = new ParseObject("Scores");
SendScore["Score"] = CheckAnswer.score;
SendScore["user"] = ParseObject.CreateWithoutData("_User", ParseUser.CurrentUser.ObjectId);
SendScore["TestMode"] = MainMenu.testmode;
SendScore["TotalQuestions"] = QuestionCreation.TotalQuestions;
SendScore["CorrectQuestions"] = CheckAnswer.CorrectQuestions;
SendScore.SaveAsync().ContinueWith(t =>
{
ScoreObjectId = SendScore.ObjectId;
//set the bool canContinue to true because the first portion of code has finished running
canContinue = true;
});
//wait while the canContinue bool is false
while(!canContinue){
yield return null;
}
//continue your parse code
ParseObject SendCorrectTopics = new ParseObject("CorrectAnswers");
SendCorrectTopics["Score"] = SendScore.ObjectId;
for (int i = 0; i <= 9; i++)
{
string Topic = "Topic" + (i + 1).ToString();
SendCorrectTopics[Topic] = CheckAnswer.CorrectTopics[i];
}
SendCorrectTopics.SaveAsync();
SceneManager.LoadScene(0);
return null;
}

Word.Shape.Name taking maximum 18 characters

I am using Word.Interop library. If I assign less than 18 characters to 'Word.Shape.Name' then it work perfect but when I assign more than 18 characters then 'Word.Shape.Name' throw exception.
e.g
Word.Shape.Name = "This is a test value to assign";
throw exception
"System.UnauthorizedAccessException: Access is denied.
(Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))".
What I should do to resolve this problem?
Whole method is
objTargetDocument, ref Scripting.Dictionary dicMarkers, ref Alias.Document objSourceDocument)
{
bool blnRetVal = false;
string strModel = ndModel.Name;
int lngModel = 0;
int lngNextModel = 0;
int lngStart;
int lngEnd;
Alias.Document objTemp;
Microsoft.Office.Interop.Word.Range objRange;
Shape objShape;
Object[] astr;
int n;
bool bLastModel;
/*'--------------------------------------------------------------------------------------------------
' 1. Find model's model marker and the next marker (if any)
'--------------------------------------------------------------------------------------------------*/
astr = dicMarkers.Keys();
for (n = astr.GetLowerBound(0); n <= astr.GetUpperBound(0); n++)
{
if (string.Compare(astr[n].ToString(), strModel, true) == 0)
{
lngModel = (int)dicMarkers.get_Item(astr[n]); //PNDC //dicMarkers.Item(astr(n))
if (n < astr.GetUpperBound(0))
{
if (string.Compare(astr[n + 1].ToString(), "#end", true) == 0)
{
lngNextModel = 0;
bLastModel = true;
}
else
{
lngNextModel = (int)dicMarkers.get_Item(astr[n + 1]);
bLastModel = false;
}
}
else
{
lngNextModel = 0;
}
break;
}
}
/*'--------------------------------------------------------------------------------------------------
' 2. Copy model from original document to new document
'--------------------------------------------------------------------------------------------------*/
if (lngModel > 0)
{
lngStart = objSourceDocument.Sections[lngModel].Range.Start;
if (lngNextModel == 0)
{
var key = "#end";
var value = dicMarkers.get_Item(key);
lngEnd = value;
}
else
lngEnd = objSourceDocument.Sections[lngNextModel].Range.Start; //objSourceDocument.Sections.Last.Index;
//--------------------------------------------------------------------------------------------------
//copy original
objSourceDocument.ActiveWindow.Selection.SetRange(lngStart, lngEnd);
objSourceDocument.ActiveWindow.Selection.Copy();
bool bInsertSection = false;
//paste (append) copied model to the document
if (objTargetDocument.Sections.First.Index == objTargetDocument.Sections.Last.Index)
{
//Target document only has 1 (default) section
bInsertSection = true;
}
else
{
if (objTargetDocument.Sections.Last.PageSetup.SectionStart == WdSectionStart.wdSectionNewPage)
{
//Last section is a nextpage section
if ((objTargetDocument.Sections.Last.Range.End - (objTargetDocument.Sections.Last.Range.Start) <= 1))
//Empty section
bInsertSection = false;
else
bInsertSection = true;
}
else
{
//Last section isn't a nextpage
bInsertSection = true;
}
}
objTargetDocument.ActiveWindow.Selection.Start = objTargetDocument.Range().End;
if (bInsertSection)
{
objTargetDocument.ActiveWindow.Selection.InsertBreak(WdBreakType.wdSectionBreakNextPage);
objTargetDocument.ActiveWindow.Selection.Start = objTargetDocument.Range().End;
}
objTargetDocument.ActiveWindow.Selection.Collapse();
objRange = objTargetDocument.ActiveWindow.Selection.Range.Duplicate; //remember range for model marker anchor
objTargetDocument.ActiveWindow.Selection.Paste();
objTargetDocument.Variables.Add(m_strModelMarker + strModel);
// .TextFrame.ContainingRange
//place model marker (so that we can find our model again)
objShape = objTargetDocument.Shapes.AddTextbox(Microsoft.Office.Core.MsoTextOrientation.msoTextOrientationUpward, 0, 0, 0, 0, objRange);
objShape.Name = m_strModelMarker + strModel; // This Line Trowing Exception
objShape.Visible = Microsoft.Office.Core.MsoTriState.msoFalse;
UpdateFields(ref objTargetDocument, ref ndModel);
blnRetVal = true;
}
else
new Modules.Globals().MsgBoxEx("Kan het bestaande model '" + strModel + "' niet kopieren.", MessageBoxButtons.OK);
return blnRetVal;
}
I haven't been able to find any reference to a 18-character limitation in the Name property. Also, after a quick test, it seems to work fine (even by inputting a much longer length):
Word.Shape oShape = oDoc.Shapes.AddLine(0, 0, 0, 0);
oShape.Name = "This is a test value to assign - This is a test value to assign";
Thus, the error you are mentioning is most likely provoked by a different part of your code.
In any case, I am not sure that you understand the exact meaning of the Name property. This is not something you would be seeing in the Word document at all; this is something for "internal reference purposes". The Shapes array is actually a Dictionary which can be accessed by typing either the index or the name of the given shape; that is, if oShape, as defined above, is the first shape in the document, I can access it by using any of the folowing options:
Word.Shape oShape2 = oDoc.Shapes[1];
Word.Shape oShape2 = oDoc.Shapes["This is a test value to assign - This is a test value to assign"];
For this reason, writing too long names is not necessary anyway.
There is an old discussion here about Word 2003 which seems to be similar.
In that discussion, it suggests storing the name in a document variable object (as in Document.Variables).
Something like:
Variables vars = Doc.Variables;
var = vars.Add("myShapeName", "This is a test value to assign");
and then assign var.Value to Word.Shape.Name somehow.
Edit
On re-reading that linked discussion, there is no way of directly assigning a Doc.Variable to the Name property. What you can set the shape's Name to a short unique identifier and use it to retrieve your long string from the Variables wherever you need it.
void SetLongName(Shape shape, string uniqueId, string longName)
{
shape.Name = uniqueId;
Variables vars = Doc.Variables;
var = vars.Add(uniqueId, longName);
}
string GetLongNameOfShape(Shape shape)
{
return GetLongNameById(shape.Name);
}
string GetLongNameById(string uniqueId)
{
Variables vars = Doc.Variables;
return vars.get_Item(uniqueId).Value;
}

Determining value jumps in List<T>

I have a class:
public class ShipmentInformation
{
public string OuterNo { get; set; }
public long Start { get; set; }
public long End { get; set; }
}
I have a List<ShipmentInformation> variable called Results.
I then do:
List<ShipmentInformation> FinalResults = new List<ShipmentInformation>();
var OuterNumbers = Results.GroupBy(x => x.OuterNo);
foreach(var item in OuterNumbers)
{
var orderedData = item.OrderBy(x => x.Start);
ShipmentInformation shipment = new ShipmentInformation();
shipment.OuterNo = item.Key;
shipment.Start = orderedData.First().Start;
shipment.End = orderedData.Last().End;
FinalResults.Add(shipment);
}
The issue I have now is that within each grouped item I have various ShipmentInformation but the Start number may not be sequential by x. x can be 300 or 200 based on a incoming parameter. To illustrate I could have
Start = 1, End = 300
Start = 301, End = 600
Start = 601, End = 900
Start = 1201, End = 1500
Start = 1501, End = 1800
Because I have this jump I cannot use the above loop to create an instance of ShipmentInformation and take the first and last item in orderedData to use their data to populate that instance.
I would like some way of identifying a jump by 300 or 200 and creating an instance of ShipmentInformation to add to FinalResults where the data is sequnetial.
Using the above example I would have 2 instances of ShipmentInformation with a Start of 1 and an End of 900 and another with a Start of 1201 and End of 1800
Try the following:
private static IEnumerable<ShipmentInformation> Compress(IEnumerable<ShipmentInformation> shipments)
{
var orderedData = shipments.OrderBy(s => s.OuterNo).ThenBy(s => s.Start);
using (var enumerator = orderedData.GetEnumerator())
{
ShipmentInformation compressed = null;
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (compressed == null)
{
compressed = current;
continue;
}
if (compressed.OuterNo != current.OuterNo || compressed.End < current.Start - 1)
{
yield return compressed;
compressed = current;
continue;
}
compressed.End = current.End;
}
if (compressed != null)
{
yield return compressed;
}
}
}
Useable like so:
var finalResults = Results.SelectMany(Compress).ToList();
If you want something that probably has terrible performance and is impossible to understand, but only uses out-of-the box LINQ, I think this might do it.
var orderedData = item.OrderBy(x => x.Start);
orderedData
.SelectMany(x =>
Enumerable
.Range(x.Start, 1 + x.End - x.Start)
.Select(n => new { time = n, info = x))
.Select((x, i) => new { index = i, time = x.time, info = x.info } )
.GroupBy(t => t.time - t.info)
.Select(g => new ShipmentInformation {
OuterNo = g.First().Key,
Start = g.First().Start(),
End = g.Last().End });
My brain hurts.
(Edit for clarity: this just replaces what goes inside your foreach loop. You can make it even more horrible by putting this inside a Select statement to replace the foreach loop, like in rich's answer.)
How about this?
List<ShipmentInfo> si = new List<ShipmentInfo>();
si.Add(new ShipmentInfo(orderedData.First()));
for (int index = 1; index < orderedData.Count(); ++index)
{
if (orderedData.ElementAt(index).Start ==
(si.ElementAt(si.Count() - 1).End + 1))
{
si[si.Count() - 1].End = orderedData.ElementAt(index).End;
}
else
{
si.Add(new ShipmentInfo(orderedData.ElementAt(index)));
}
}
FinalResults.AddRange(si);
Another LINQ solution would be to use the Except extension method.
EDIT: Rewritten in C#, includes composing the missing points back into Ranges:
class Program
{
static void Main(string[] args)
{
Range[] l_ranges = new Range[] {
new Range() { Start = 10, End = 19 },
new Range() { Start = 20, End = 29 },
new Range() { Start = 40, End = 49 },
new Range() { Start = 50, End = 59 }
};
var l_flattenedRanges =
from l_range in l_ranges
from l_point in Enumerable.Range(l_range.Start, 1 + l_range.End - l_range.Start)
select l_point;
var l_min = 0;
var l_max = l_flattenedRanges.Max();
var l_allPoints =
Enumerable.Range(l_min, 1 + l_max - l_min);
var l_missingPoints =
l_allPoints.Except(l_flattenedRanges);
var l_lastRange = new Range() { Start = l_missingPoints.Min(), End = l_missingPoints.Min() };
var l_missingRanges = new List<Range>();
l_missingPoints.ToList<int>().ForEach(delegate(int i)
{
if (i > l_lastRange.End + 1)
{
l_missingRanges.Add(l_lastRange);
l_lastRange = new Range() { Start = i, End = i };
}
else
{
l_lastRange.End = i;
}
});
l_missingRanges.Add(l_lastRange);
foreach (Range l_missingRange in l_missingRanges) {
Console.WriteLine("Start = " + l_missingRange.Start + " End = " + l_missingRange.End);
}
Console.ReadKey(true);
}
}
class Range
{
public int Start { get; set; }
public int End { get; set; }
}

Categories