Sorting Gameobjects and set their SiblingIndex in Hierarchy - c#

i have a friendsystem and want to sort it by the Users Worldrank (via PlayFab).
I want the best at the Top and the worst at the bottom.
I tried it with the following Script
private void sortfriendlist()
{
GameObject[] Friendcolumns = GameObject.FindGameObjectsWithTag("Friendcolumn");
for (int i = 0; i < Friendcolumns.Length; i++)
{
//If ranked
if (Friendcolumns[i].name.Substring(0, 1) != "F")
{
//Set Sibling (Rank)
int rank = int.Parse(Friendcolumns[i].name.Substring(0, 1));
Friendcolumns[i].transform.SetSiblingIndex(rank);
}
//If unranked
else
{
Friendswithoutrank[i] = Friendcolumns[i];
}
}
Debug.Log(Friendswithoutrank.Length + " Friends without Rank");
for(int i = 0; i < Friendswithoutrank.Length; i++)
{
Debug.Log(Friendswithoutrank[i].name + " has no Rank");
Friendcolumns[i].transform.SetAsLastSibling();
}
}
But that doesnt work as you can see here
First Im getting all Friends and store them in an Array. After that Im checking if the Player even has a Rank by checking if there is an "F" at the beginning of the GameObjects Name (Friends without a Rank have no Ranknumber in front of the Gameobject Name so the Name start with an "F"). Than Im getting the Rank of the Friend and set the SiblingIndex to the Rank. So normally it should be sorted now as i think. But that idea doesn´t seem to work.
If you have any idea how to fix it or make it better please let me know!

Sorting can actually be done much easier using Linq (also see this thread):
using System.Linq;
using UnityEngine;
private void sortfirendlist()
{
var Friendcolumns = GameObject.FindGameObjectsWithTag("Friendcolumn");
// Use linq to get the Objects with and without Rank (not starting / starting with "F")
// This works a bit similar to sql code
// -> if the "Where" returns true -> object stays in the list
// using string.StartsWidth which is a
// better choice for what you are doing with Substring(0,1)== ...
var withoutRank = Friendcolumns.Where(obj => obj.name.StartsWith("F"));
var withRank = Friendcolumns.Where(obj => !obj.name.StartsWith("F"));
// Sort the ranked by name (again using Linq)
var rankedSorted = withRank.OrderBy(go => go.name);
// Now we have our ordered arrays -> we just have to apply that to the scene
// set sibling index for the ranked
foreach (var t in rankedSorted)
{
// I simply send them one by one to the bottom
// => when we are finished they are all sorted at the bottom
t.transform.SetAsLastSibling();
}
// Do the same for the unranked to also send them to the bottom
Debug.Log(withoutRank.Length + " Friends without Rank");
foreach (var t in withoutRank)
{
Debug.Log(t.name + " has no rank");
t.transform.SetAsLastSibling();
}
// Now the object we sent to the bottom first
// (first element in withRank) should be on top and everything sorted below it
}

Related

c# FlaUi get all the Values from DataGridView of another program

I'm trying to pull all the values from another program's DataGridBox. For that I'm using FlaUi. I made a code that does what I want. However, it is very slow. Is there a faster way to pull up all the values from another program's DataGridView using FlaUi?
my code:
var desktop = automation.GetDesktop();
var window = desktop.FindFirstDescendant(cf => cf.ByName("History: NEWLIFE")).AsWindow();
var table = window.FindFirstDescendant(cf => cf.ByName("DataGridView")).AsDataGridView();
int rowscount = (table.FindAllChildren(cf => cf.ByProcessId(30572)).Length) - 2;
// Remove the last row if we have the "add" row
for (int i = 0; i < rowscount; i++)
{
string string1 = "Row " + i;
string string2 = "Symbol Row " + i;
var RowX = table.FindFirstDescendant(cf => cf.ByName(string1));
var SymbolRowX = RowX.FindFirstDescendant(cf => cf.ByName(string2));
SCAN.Add("" + SymbolRowX.Patterns.LegacyIAccessible.Pattern.Value);
}
var message = string.Join(Environment.NewLine, SCAN);
MessageBox.Show(message);
Thank you in-advance
Searching for descendants is pretty slow as it will go thru all objects in the tree until it finds the desired control (or there are no controls left). It might be much faster to use the grid pattern to find the desired cells or get all rows at once and loop thru them.
Alternatively you could try caching as UIA uses inter process calls which are generally slow. So each Find method or value property does such a call. If you have a large grid, that can sum up pretty badly. For that exact case, using UIA Caching could make sense.
For that, you would get everything you need (all descendants of the table and the LegacyIAccessible pattern) in one go inside a cache request and then loop thru those elements in the code with CachedChildren and such.
A simple example for this can be found at the FlaUI wiki at https://github.com/FlaUI/FlaUI/wiki/Caching:
var grid = <FindGrid>.AsGrid();
var cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Descendants;
cacheRequest.Add(Automation.PropertyLibrary.Element.Name);
using (cacheRequest.Activate())
{
var rows = _grid.Rows;
foreach (var row in rows)
{
foreach (var cell in row.CachedChildren)
{
Console.WriteLine(cell.Name);
}
}
}

Best way to compare variables between instances of the same class?

This is a game based on certain elemental combos, I'm trying to find a way to have two elements compare to each other in order to reduce hard coding. I do have a working solution provided at the end, but I'm trying to learn any ways that would be more simple, straight forward, and readable.
//Wizard is a class
Wizard player1;
Wizard player2;
player1.health = 3;
player2.health = 3;
//Elements is an enum that allows fire/water/air to be selected
player1.elementSelected = Elements.fire;
player2.elementSelected = Elements.water;
Wizard[] bothPlayers = { player1, player2 };
I want to search for the active element in bothPlayers so I can effect the Player's health. I know this doesn't work, but I was wondering if I could do something like:
if (bothPlayers.Contains(Wizard.elementSelected.Elements.fire) && bothPlayers.Contains(Wizard.elementSelected.Elements.water))
Alternatively I was thinking of just setting it to a new array but that wont let me call back to effect player health unless I set them up to a new variable like:
Elements[] bothSelectedElements = { player1.elementSelected, player2.elementSelected };
if (bothSelectedElements.Contains(Elements.fire) && bothSelectedElements.Contains(Elements.water))
{
Wizard playerWithFire; // = player who selected fire. Can't set without hardcoding
Wizard playerWithWater; // = player who selected water. Can't set without hardcoding
playerWithFire.health--;
playerWithWater.waterStrength++;
}
//CURRENT WORKING SOLUTION
//set each Wizard container to null at the start of each check
Wizard fire = null;
Wizard water = null;
Wizard air = null;
//add check to make sure same elements aren't selected. Then assign the players to the containers
foreach (Wizard player in bothPlayers)
{
if (player.elementSelected == Elements.fire)
{
fire = player;
}
if (player.elementSelected == Elements.water)
{
water = player;
}
if (player.elementSelected == Elements.air)
{
air = player;
}
}
//then do the actual check
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
//repeat with other if statement comparisons
This is a fiddly problem, but thankfully you are working with a language that has all the tools you need to hide the complexity away.
var elementWizards = wizards.GroupBy(w => w.elementSelected).ToDictionary(g => g.Key);
var elements = elementWizards.Keys.ToHashSet(); // gives us access to SetEquals
if (elements.SetEquals(new[] { Elements.Fire, Elements.Water }))
{
foreach (var wizard in elementWizards[Elements.Fire]) wizard.health--;
foreach (var wizard in elementWizards[Elements.Water]) wizard.waterStrength++;
}
else if (elements.SetEquals(new[] { Elements.Earth, Elements.Fire }))
{
// more effects...
}
Note that SetEquals doesn't care about the order of items, so you don't need to worry about handling water/fire instead of fire/water.
Footnote: in the real world I would define some static HashSet<Element> objects and call if(foo.SetEquals(elementWizards.Keys)), but I kept things simple for this answer.
Solution with Linq:
Check if there are water and fire wizard (with Linq)
Foreach to search and add health etc...
if (bothPlayers.Count(wizard => wizard.elementSelected == Elements.fire) > 0 &&
bothPlayers.Count(wizard => wizard.elementSelected == Elements.water) > 0)
{
bothPlayers.ForEach(wizard =>
{
var _ = wizard.elementSelected == Elements.water ? wizard.waterStrength++ :
wizard.elementSelected == Elements.fire ? wizard.health-- : 1;
});
}
you can change the wizards you are searching for and the properties that depends on it
There are multiple ways of finding item in a collection. For example :
// using Array.Find method
Wizard fire = Array.Find(bothPlayers, player => player.elementSelected == Elements.fire);
// or using System.Linq FirstOrDefault extension
Wizard water = bothPlayers.FirstOrDefault(player => player.elementSelected == Elements.water);
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
For more than two players, a Lookup can be used to separate the players into groups :
var lookup = bothPlayers.ToLookup(player => player.elementSelected);
if (lookup.Contains(Elements.fire) && lookup.Contains(Elements.water))
{
foreach (Wizard fire in lookup[Elements.fire] ) fire.health--;
foreach (Wizard water in lookup[Elements.water]) water.waterStrength++;
}

Optimize or Shard Large set of permutations

I have data set that I have generate every permutation, then check some properties on it to see if is an object that I want to keep and use. The number of permutations is staggering, in the quadrillions. Is there anything that you can see in the code below that I can use to speed this up? I suspect that I can't speed it up to a reasonable amount of time, so I'm also looking at possibly sharding it onto multiple servers to process, but I'm having a hard time deciding where to shard it.
Any opinions or ideas is appreciated.
var boats = _warMachineRepository.AllBoats();
var marines = _warMachineRepository.AllMarines();
var bombers = _warMachineRepository.AllBombers().ToList();
var carriers = _warMachineRepository.AllCarriers().ToList();
var tanks = _warMachineRepository.AllTanks().ToList();
var submarines = _warMachineRepository.AllSubmarines();
var armies = new List<Army>();
int processed = 0;
Console.WriteLine((long)boats.Count*marines.Count*bombers.Count*carriers.Count*tanks.Count*submarines.Count);
// 70k of these
Parallel.ForEach(boats, new ParallelOptions(){MaxDegreeOfParallelism = Environment.ProcessorCount},boat =>
{
// 7500 of these
foreach (var marine in marines)
{
// 200 of these
foreach (var bomber in bombers)
{
// 200 of these
foreach (var carrier in carriers)
{
// 400 of these
foreach (var tank in tanks)
{
// 50 of these
foreach (var submarine in submarines)
{
var lineup = new Army()
{
Tank = tank,
Submarine = submarine,
Carrier = carrier,
Marine = marine,
Bomber = bomber,
Boats = boat
};
if (army.Hitpoints > 50000)
{
lock (lockObject)
{
armies.Add(lineup);
}
}
processed++;
if (processed%10000000 == 0)
{
Console.WriteLine("Processed: {0}, valid: {1}, DateTime: {2}", processed, armies.Count, DateTime.Now);
}
}
}
}
}
}
});
return armies;
If this code is referring to a simulation you might want to add some optimizations by:
Mark an object as changed (put it in a list) when it changes so there is no need to search multiple times
Decrease/throttle/tune the object update frequency
Use other information available to filter objects: are objects close to one another so they might affect/hurt/heal each other -> only then investigate changes
Change the data structure; by putting all attributes of all objects in a smartly setup matrix you might be able to use simple matrix multiplication to have the object interact. You might even be able to offload the multiplication to the GPU
You might be asking too much: so scale out by using more nodes/machines.

List<> Contains Issue

Edit - Check is Moved to The Else, Looping & Multiple Object Changes are Now Prime Issue
I need 5 Teams objects To Play Against each other (Each must play against the Other 4 teams)
The Check had to be in the ELSE statement, Now the Only Problem left is the Scores
add and subtract from ALL the teams same time, and i only play once, i need them to loop
so that they all played against everyone
I Started off like this. Yet i don't know how to go about doing this
//This is the list i check against and add players to when the team
//Played against them
List<string> played = new List<string>();
//Teamlist Holds Objects of type Team
//Contructor - (TeamName,Points,Wins,Losses,Draws,totalpoints)
//string, rest are int type
foreach (var item in Teamlist)
{
if (played.Contains(item.Teamname))
{
//Played against them already or is the same name as their own
}
else
{
//Add own Name on Check
played.Add(item.Teamname);
//Looping here against all Teams Entered to be played against
//How do i go about doing this?
//If Team 1 Wins Team 2 - This is just 2 Randoms between 1
//and 10
if (rand1 > rand2)
{
item.WinGame(); //Increments Team win Points
}
else if (rand1 == rand2)
{
item.DrawGame(); //Draw awards no points
}
else
{
item.LoseGame(); //Lose Points
}
}
}
foreach (var item in Teamlist)
{
//This ToString() looks like this
//return string.Format("Team Name : {0} \t {1} \t {2} \t {3} \t
//{4} \t {5}", teamname, points, win, loss, draw, totalpoints);
Console.WriteLine(item.ToString());
}
Maybe because you made a little error:
First you add item.Teamname
//Add own Name on Check
played.Add(item.Teamname);
Then you check if the previously added item.Teammname is within the list -- always true:
if (played.Contains(item.Teamname))
{
EDIT: This is wrong. It would give you both home and away games.
What about taking the Cartesian Product of the list of teams to give you the list of matches to be played. With Linq it's pretty easy:
static void Main(string[] args)
{
var teams = new[]
{
new { Name = "Team 1"},
new { Name = "Team 2"},
new { Name = "Team 3"},
new { Name = "Team 4"},
new { Name = "Team 5"}
};
var matches =
// here we loop over all the items in the teams collection.
// "teamA" is our loop variable.
from teamA in teams
// here we loop over all the items in the teams collection again.
// like a nested foreach (in)
// "teamB" is our loop variable.
from teamB in teams
// this is like an if(teamA.Name != teamB.Name) within our nested foreach loops
where teamA.Name != teamB.Name
// the select says how we want to create our collection of results.
// Here we're creating a new anonymous object containing the two rival teams.
select new { Team1 = teamA, Team2 = teamB };
foreach (var match in matches)
{
var result = PlayMatch(match.Team1, match.Team2);
Console.WriteLine("{0} played {1} The result was {2}",
match.Team1.Name,
match.Team2.Name,
result);
}
}
private static string PlayMatch(object team1, object team2)
{
// Left as an exercise for the OP.
return "...";
}
here lies your problem:
played.Add(item.Teamname);
if (played.Contains(item.Teamname))
The first line adds the team name to the played list, the second line checks if the team name is in the list. This should always be true, so you never get into the else path...
To fix your code, you need to move played.Add(item.Teamname); inside your else branch, so you're only adding the team name to the played collection if they haven't played them.
foreach (var item in Teamlist)
{
//Add own Name on Check
// played.Add(item.Teamname); // <---- Move from here
if (played.Contains(item.Teamname))
{
//Played against them already or is the same name as their own
}
else
{
//Add own Name on Check
played.Add(item.Teamname); // <---- To here
...
Assuming teamList is IList<Team>:
for(int i = 0; i < teamList.Count() - 1; i++)
for(int j = i + 1; j < teamList.Count(); j++)
{
var team1 = teamList[i];
var team2 = teamList[j];
// will execute for each pair once
}
For 5 teams:
0 plays 1,2,3,4
1 plays 2,3,4
2 plays 3,4
3 plays 4
Edit: Method originally played each team twice. Your notion of a played list is actually required. See below.
Edit: Here's another method, which may prove faster.
void PlayRound(Team[] teams)
{
for (int i = 0; i < teams.Length; i++)
for (int j = i + 1; j < teams.Length; j++)
PlayMatch(teams[i], teams[j]);
}
/// <summary>
/// Plays a full round, one match per team.
/// </summary>
void PlayRound(List<Team> teams)
{
List<Team> played = new List<Team>(); // keep track of teams in the outer loop
foreach (Team teamA in teams)
{
foreach (Team teamB in teams)
{
if (teamA == teamB || played.Contains(teamB))
continue;
else
PlayMatch(teamA, teamB);
}
played.Add(teamA); // add outer loop team to ensure one match per pairing
}
}
/// <summary>
/// Plays a match.
/// </summary>
void PlayMatch(Team teamA, Team teamB)
{
int scoreA = rand.Next();
int scoreB = rand.Next();
if (scoreA > scoreB)
{
teamA.Win();
teamB.Lose();
}
else if (scoreA == scoreB)
{
teamA.Draw();
teamB.Draw();
}
else
{
teamB.Win();
teamA.Lose();
}
}
You are missing one loop in your logic.
Based on the code you provided, here is what happens :
Add current Teamname to played list
Check if current Teamname is in played list
You should add another loop between steps 1 and 2 to loop on all teams and execute the game logic.
foreach (var item in Teamlist)
{
//Add own Name on Check
played.Add(item.Teamname);
if (played.Contains(item.Teamname))
{
//Played against them already or is the same name as their own
}
This is always true? You add the name and next you'll try to find it (you just added it....)
played.Add(item.Teamname);
Should be at the end of the foreach scope i guess

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