Comparing all values within a List against each other - c#

I am a bit stuck here and can't think further.
public struct CandidateDetail
{
public int CellX { get; set; }
public int CellY { get; set; }
public int CellId { get; set; }
}
var dic = new Dictionary<int, List<CandidateDetail>>();
How can I compare each CandidateDetail item against other CandidateDetail items within the same dictionary in the most efficient way?
Example: There are three keys for the dictionary: 5, 6 and 1. Therefore we have three entries. now each of these key entries would have a List associated with. In this case let say each of these three numbers has exactly two CandidateDetails items within the list associated to each key. This means in other words we have two 5, two 6 and two 1 in different or in the same cells. I would like to know:
if[5].1stItem.CellId == [6].1stItem.CellId => we got a hit. That means we have a 5 and a 6 within the same Cell
if[5].2ndItem.CellId == [6].2ndItem.CellId => perfect. We found out that the other 5 and 6
are together within a different cell.
if[1].1stItem.CellId == ...
Now I need to check the 1 also against the other 5 and 6 to see if the one exists within the previous same two cells or not.
Could a Linq expression help perhaps? I am quite stuck here...
I don't know...Maybe I am taking the wrong approach. I am trying to solve the "Hidden pair" of the game Sudoku. :)
http://www.sudokusolver.eu/ExplainSolveMethodD.aspx
Many Thanks,
Kave

Process every pair in a sequence shows how you can process every pair within a list

from kvp1 in dic
from i1 in Enumerable.Range(0, kvp1.Value.Count)
let candidate1 = kvp1.Value[i2]
from kvp2 in dic
where kvp2.Key >= kvp1.Key
from i2 in Enumerable.Range(0, kvp2.Value.Count)
let candidate2 = kvp2.Value[i2]
where kvp1.Key != kvp2.Key || i2 > i1
where candidate1.CellId == candidate2.CellId
select new {
Key1 = kvp1.Key,
Key2 = kvp2.Key,
Candidate1 = candidate1,
Candidate2 = candidate2
}
You may want to modify the select clause for only the information you want or add another where clause if you want only matching candidates from different keys.

Related

How to find if 3 elements in list are same in Unity3D

I'm working on a Card game in unity. where I have 5 different cards (A,B,C,D,E) and cards i can have at my hand is maximum 6. And I have a button. So clicking on button once I draw one card out. So all these functions are ready. But i want to perform an action when out of those cards that are drawn. if any three cards are similar.
For eg:- I draw out 5 cards And the drawn cards are in this order: A,A,A,B,C now as you see i have 3-A I want to perform A Action. Or if cards are like this A,B,C,C,C,E if want to perform C function because there are 3 C's. So if 3 cards are same type I want to perform an action related to it.
So to brief more I'll explain the flow once. Card-Draw Button Click -> Keep getting cards per click -> Add the cards to a list called _CardList & Sort the cards in List -> If 3 Same Cards are there perform an action. Im Facing Problem at the last one of checking the card.
I have already done a method but its super huge and not advisable. So if you guys can help me in this that will help me a lot. Thanks and Ill post all my doubts and question below with what i tried.
Try#1
Have multiple list like:
List<Card> Alist;
List<Card> BList; //and so on until And keep creating list as long as card type
.
.
List<Card> Elist;
void DrawButton() // Function thats passed to the button. Meaning what to happen when the button is clicked
{
//Here we instantiate the card randomly from a list given from inspector.
GameObject card = Instantiate(cards._cardModel, _playerHandPoints[clicks].localPosition, _playerHandPoints[clicks].localRotation, mCardHolderParent.transform);
//We access the card script attached to card and give its type and everything
card.GetComponent<Cards>()._cardType = cards._cardType;
//Here we pass the instantiated card
AddNewCard(card.GetComponent<Cards>());
//Every time after adding we check if any list count is 3 and perform related action
CardMatchThreeChecker();
}
AddNewCard(Card inNewCard)
{
//in this function we check the card type and add it to their specific list & also add it to _cardList.
switch (inNewCard._cardType)
{
case CardType.A: AList.Add(inNewCard);
break;
case CardType.B: BList.Add(inNewCard);
break;
case CardType.C: CList.Add(inNewCard);
break;
case CardType.D: DList.Add(inNewCard);
break;
case CardType.E: EList.Add(inNewCard);
break;
default:
break;
}
_CardList.Add(inNewCard);
}
void CardMatchThreeChecker()
{
if (AList.Count == 3)
{
SceneManager.LoadScene(1);
}
if (BList.Count == 3)
{
SceneManager.LoadScene(2);
}
if (CList.Count == 3)
{
SceneManager.LoadScene(3);
}
if (DList.Count == 3)
{
SceneManager.LoadScene(4);
}
if (EList.Count == 3)
{
SceneManager.LoadScene(5);
}
}
Problem - 1: with the above method is if I add any new cards in future i need to keep adding many lists on and on which is not advisable as the cards playable at a time can also be 20 or more
Problem -2 : There is no use of the List "_cardList" that contains all the cards instantiated.
Question - 1 Instead I want to check if any 3 things are similar in the _CardList List itself. Like every time I draw a card they all go and add themself to cardlist's List and I want to check if any three elements are similar in the list then perform the related action. I searched in internet but dint find or get an idea for what to do or how to try.
But Yet I tried a method but now im stuck in that too.Method metioned Below
These lines below come under a function called CardChecking() That is called in DrawButton(). Here Im saying it to check the list only if card count is more than 2 because we have a match only if 3 cards are there and we cant have a match if cards are less that 3 as we are checking if there are 3 similar cards. And as we want to check the elements of the list we have a for loop with (i,i+1,i+2) making it checking elements of the cardlist. Problem of this outside the snippet.
for (int i = 0; i < _CardList.Count; i++)
{
if (_CardList.Count > 2)
{
if(_CardList[i]._cardType == _CardList[i+1]._cardType && _CardList[i + 1]._cardType == _CardList[i+2]._cardType)
{
SceneManager.LoadScene(_CardList[i]._cardType.ToString());
}
else
{
continue;
}
}
}
Problem that I faced is: for example if i draw 3 cards my cardlist count would be 3, which will true my condition and check the function. If my cards are not of same type it goes to next for iteration at that time my i values will be cardList[1] || cardList[2] && cardList[2] || cardList[3] meaning my card values 1,2 & 3 on second iteration for my for-loop but when it checks for cardList[3] it throws Index out of range as my cardlist count is 3 and it doesnt have the 4th element the for loop is checking for. So Dont know How to overcome this.
To solve Problem - 1,you can use Dictionary<CardType,List> :
Dictionary<CardType,List<Card>> cardListDictionary = new Dictionary<CardType, List<Card>>();
AddNewCard(Card inNewCard)
{
if (!cardListDictionary.TryGetValue(inNewCard._cardType,out var list))
{
list = new List<Card>();
cardListDictionary.Add(inNewCard._cardType,list);
}
list.Add(inNewCard);
}
To solve Problem - 2,implement CardChecking like this :
void CardChecking()
{
if (_CardList.Count > 2)
{
var type = _CardList[0]._cardType;
var count = 1;
for (int i = 1; i < _CardList.Count; i++)
{
if(_CardList[i]._cardType == type)
{
count++;
if (count == 3)
{
SceneManager.LoadScene(type.ToString());
}
}
else
{
type = _CardList[i]._cardType;
count = 1;
}
}
}
}
I assume the order of the cards doesn't matter and think you are looking for Linq GroupBy
// Just a simplified example since you didn't show your Card class
class Card
{
public CardType _cardType;
public Card(CardType type)
{
_cardType = type;
}
}
var list = new List<Card>
{
new Card(CardType.A),
new Card(CardType.B),
new Card(CardType.C),
new Card(CardType.C),
new Card(CardType.B),
new Card(CardType.B),
new Card(CardType.A),
new Card(CardType.A),
new Card(CardType.B)
};
// This creates separated groups according to the value of the card
var groups = list.GroupBy(c => c._cardType)
// Then it filters out only those groups that have 3 or more items
.Where(g => g.Count() >= 3);
// Then you can simply iterate through them
foreach (var #group in groups)
{
// The Key is the value the grouping was based on
// so in this example the Card._cardType
Debug.Log($"There are {#group.Count()} cads with cardType = {#group.Key}");
// you can also get all the according cards if needed
//var cards = #group.ToArray();
}
will print
There are 3 cards with cardType = A
There are 4 cards with cardType = B
Further if you just want to get the first group that has 3 items and anyway already know that there will never be two or more at the same time you could go
// again first create separate groups based on the value
var group = list.GroupBy(c => c._cardType)
// Pick the first group that has at least 3 items
// or NULL if there is none
.FirstOrDefault(g => g.Count() >= 3);
if (group != null)
{
Debug.Log($"Found a group of {group.Count()} for the value = {group.Key}!");
}
else
{
Debu.Log("There is no group with 3 elements yet...");
}
Well, not sure if I get the whole picture right. Let me simplify the question, and if it works, then there is a solution.
Question: I have a list of cards, and I need to perform an action if there are 3 instances of the card of the same type in the list.
Answer: Use Linq to group the list by the type. Then, filter the result to find the "3-cards cases".
Here you go:
var answer = _CardList
.GroupBy(c => c.GetCardType())
.ToDictionary(g => g.Key, g => g.Count()) // Count by the card type.
.Where(x => x.Value >= 3) // Three or more cards of the same type.
.Select(x => x.Key) // Fetch the card type.
.FirstOrDefault(); // Only one match is needed.
if (answer != null) {
// answer is the type of the card that is counted 3 or more times.
}
If you need to detect all the "3-cards cases" remove FirstOrDefault and deal with the collection.

Fastest way of updating items of one list from another using StartsWith

I have a scenario where I need to update few items based on the data from another list. I have already gone through various questions over here but none helped.
Scenario
listA: Total Count around 88000
public class CDRs
{
public string cld { get; set; }
public string prefix2 { get; set; }
public string country { get; set; }
public string city { get; set; }
}
listB: Total Count : 3000.
public class RatesVM
{
public string prefix { get; set; }
public string Country { get; set; }
public string City { get; set; }
}
Now in listB there can be multiple matches of listA field that is cld
for eg.
listA.cld = "8801123232";
Matched prefixes from ListB I get is
880 BGD Proper
8801 BGD Mobile
88011 BGD Dhaka Mobile
88017 BGD Dhaka Mobile
88018 BGD Dhaka Mobile
88019 BGD Dhaka Mobile
Now I want the closest match in this case it would be
88011 BGD Dhaka Mobile
Approach I am following right now.
foreach (var x in listA)
{
var tempObj = listB.FirstOrDefault(y => x.cld.StartsWith(y.prefix));
if (tempObj != null)
{
x.prefix2 = tempObj.prefix;
x.country = tempObj.Country;
x.city = tempObj.City;
}
else
{
x.prefix2 = "InBound";
x.country = "Unknown";
x.city = "Unknown";
}
}
It works fine but takes a lot of time. Around 2-3 minutes for this case.
There are few scenarios where ListA will have around 1 million records. I am worried it will take forever.
Many Thanks in advance
I would suggest the below code. The key difference is using orderedListB to ensure that you get the most specific match possible (i.e. start with the longest prefixes first), as well as a Dictionary to cache results. *
Dictionary<string, RatesVM> cache = new Dictionary<string, RatesVM>();
var orderedListB = listB.OrderByDescending(z => z.prefix.Length).ToList();
foreach (var x in listA)
{
RatesVM cached;
cache.TryGetValue(x.cld, out cached);
var tempObj = cached ?? orderedListB.FirstOrDefault(z => x.cld.StartsWith(z.prefix));
if (tempObj != null)
{
if (cached == null)
{
cache.Add(x.cld, tempObj);
}
x.prefix2 = tempObj.prefix;
x.country = tempObj.Country;
x.city = tempObj.City;
}
else
{
x.prefix2 = "InBound";
x.country = "Unknown";
x.city = "Unknown";
}
}
You may also want to consider using Parallel.ForEach rather than just foreach.
Your problem is hard, because you need the 'closest' solution rather than any solution at all. This forces you to iterate of every record in listB, for each element in listA.
Since you need an answer for every element in listA you are forced to check every element in it.
You can however preprocess listB by creating a tree structure. You create a node for each different first number of all strings in B. Then that node will be the parent of all records in listB that start with that number. The nodes below that node will hold the second number in the string, and so on.
Went ahead and drew you a visual idea of what such a tree might look like:
Now if you search in listB, you don't have to iterate over the entire listB but can just traverse down the list which will increase your time per iteration from O(n) to O(log n).
You would take the first letter in a record in listA and compare it to the tree, and traverse in that branch (instantly eliminating a huge amount of records you otherwise would need to compare against, increasing your performance). Then compare the second letter, etc untill no more letters can be found in the tree. When you stop, you have found the longest matching record in listB guaranteeing the 'closest' match, which FirstOrDefault(\x -> x.StartsWith()) doesn't do at all! (It finds the first match only, which is almost always just the first letter!).
You only have to create this tree once for all searches in listA, and if there are changes in listB you can easily update the tree as well.
If you're running this on a decent machine with more than one core, you can also parallelize this search. It increases the complexity of the program you're writing because you need to manage which thread searches which record in listA though it will help out greatly with the performance and will greatly lower the amount of time needed.

using string.split in entity to traverse tree depth

i have the following self-referencing table
public partial class products_category
{
public long id { get; set; }
public string category_name { get; set; }
public string category_description { get; set; }
//self referencing to table id
public Nullable<long> Parent_Id { get; set; }
public string navPath {get; set; }
}
here string navpath contains all the leading parents for a child categroy, say:
"Clothes" = 1 Parent_id=null, navpath=""
"Silk" = 2 Parent_id=1 navpath="1"
"Silk Suit"=3 parent_id=2 navpath="1-2"
"Saree" =4 parent_id=3 navpath="1-2-3"
"Dress Material"=5 parent_id=1 navpath="1" and so on....
now as per this scenario i want to access the flattend tree for frther processing for a certain depth only say to level 2 or until level 4 depth of children associated with navpath.
my idea regarding this issue was to approach using linq to ef this way:
var catTrees = db.products_category.Where(pc => pc.navpath.Split('-').Length < 4).ToList();
i am using the following link to do further traversing and tree generation:
https://bitlush.com/blog/recursive-hierarchical-joins-in-c-sharp-and-linq
and it is doing a great work so far, the only issue is i dont want to pre select whole table for processing. i want to achieve paging and a certain level of depth for first iteration, so i can maintain performance in case of thousand of records. [think of this as a category hierarchy or blog/youtube comments hierarchy].
but using the above ef linq command is giving the following error:
The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities.
i checked with ef docs and other places in SO to know that string.split doesn't work with EF implicitly. but can we apply it using extension methods or can this tree selection have alternate approach without using string.split and hitting DB only ones?
please advice.
This looks like an issues with building SQL code out of your LINQ mpre specifically SQL which takes a string splits it on dash and counts the elements.
if you dont hate the idea of loading into memory then you can force anything :)
var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Split('-').Length < 4).ToList();
The trick here is to force the execution of the SQL by adding the .ToList() when we want the data from the database. This is called realizing the data.
Even with that realization trick the count is faster
var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Count(a => a == '-') < 3).ToList();
these solutions are essentially the same as
List<Result> filter() {
List<Result> r = new List<Result>();
foreach(var a in db.products_category) {
if(a.navpath.Count(a => a == '-') < 3) {
r.add(a);
}
}
return r;
}
When thinking about it the filter method is somewhat less memory intensive as it reads one and one and never stores everything in memory. (in theory at least, only a few really knows what the .NET compiler does in the shadows)
I would advice you against using the navpath for checking depth.
If you can change your model, you could add an additional numeric Depth field for each category and populate it according its navpath, then you could select them from your code in this way:
var catTrees = db.products_category.Where(pc => pc.Depth < 3).ToList();
There are many ways to populate that new column, but the bottom line is that you will have to do it just once (given that you keep track of it every time you modify the navpath of a category).
One possible way of populating it would be looping through all categories, something like:
var allCategories = db.products_category.ToList();
foreach(var category in allCategories)
{
var depth = category.navpath == "" ? 0 : category.navpath.Split('-').Length + 1;
category.Depth = depth;
}
allCategories.SubmitChanges();

Not all empty lists are removed from ObservableCollection?

I have code to create a grouped list for a ListView in Xamarin Forms, which for some reason only sometimes removes a group from the list if it is empty.
char[] alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
var animals = new List<string>() { "Jaguar", "Elephant", "Nemo", "Cat", "Dog", "Iguana", "Puma", "Crow", "Hawk", "Owl", "Badger", "Meerkat", "Lion", "Tiger", "Rabbit", "Pig" };
var groups = new ObservableCollection<GroupedItemModel>();
for (int i = 0; i < alpha.Length; i++)
{
groups.Add(new GroupedItemModel(alpha[i].ToString()));
}
foreach (var _group in groups)
{
foreach (var _animal in animals)
{
if (_animal[0].ToString().ToUpper() == _group.GroupName.ToUpper())
{
_group.Add(_animal);
}
}
}
for (int i = 0; i < groups.Count; i++)
{
if (groups[i].Count == 0)
{
groups.RemoveAt(i);
}
}
ListSource = groups;
However, this happens:
Why do these groups not get removed? Any solutions?
When removing from a list you need to work backwards.
such as:
if you have 1,2,3,4,5,6,7,8,9,10 and you say remove even numbers as its 1-10,
on 1 thats ok, on 2, you remove it, so 3 moves into its place.. so now you arent checking 3...you skip to 4.. so in this example you'd get away withit, but what if your list was already missing say 3, it would have moved number 4 to the place where 2 had been, and move on, 4 would be missed
Change your for loop to be decremental
As described by BugFinder above, the problem you are getting is caused by modifying the list as you iterate through it. Problem solved. But it might also be worth taking a look at the overall approach. Currently we:
Create list of all possible groups (A-Z)
Iterate though the list of animals and add each to a group based upon the first letter
Iterate though the groups and throw away any which are empty.
This can all be done with a few lines of Linq code and a tweak to the GroupedItemModel
public class GroupedItemModel
{
public GroupedItemModel(string name, IEnumerable<string> values){
Name = name;
Values = new List<string>(values);
}
public string Name { get; }
public List<string> Values { get; }
}
We can now populate an ObservableCollection<GroupedItem> with
new ObservableCollection<GroupedItem>(animals.GroupBy(a => char.ToUpper(a[0]).ToString()).OrderBy(g => g.Key).Select(g => new GroupedItem(g.Key, g)));
Very minor afterword: Prefixing variable names with underscores is usually used for member variables of a class, not for local variables (_group, _animal). It doesn't change how the code works but when sharing code with others, using the general conventions helps speed up reading and understanding the code.

Best way of building a collection of unique ID's as well as their counts

I've looked into various different ways of array's, arraylist's, dictionaries... but as I'm used to PHP I'm not entirely sure on the best way I could collect the following information.
My program loops through each user, and if their is a location ID, I want to add that to some sort of collection / array. It's expected that different users will have the same location ID.
If the location ID is the same, I need to increase an integer of how many occurrence for that location ID.
Example:
User1 - Location1
User2 - Location3
User3 - Location3
Location1 = 1
Location3 = 2
Also I need to somehow append each user ID to this collection. So Location3 / 2 occurrences / user2/user3
I've been trying to figure out the best way of doing this for about two hours now, and all the different methods of multidimensional arrays, arraylists, dictionaries is all a little confusing as it all seems abstract to my PHP knowledge. I think C# handles arrays in an entirely different way.
Essentially, the collection with unique location ID's / occurrences / and users collection needs to be stored in something that can be passed to somewhere else in my program as an argument.
I've made a PHP script which does exactly what I'm after
foreach($call["data"] as $v)
{
// Foreach USER ($v containing their unique ID and location ID.)
$user_id = $v["id"];
$location_id = $v["location"]["id"];
// This adds the location ID as the key within the array, followed by every user who has it. I don't need a count in this case, as I could just count the number of users.
$collection[$location_id][$user_id] = null;
}
This in return creates this array when printed using print_r
[106078429431815] => Array
(
[620790873] =>
[626276302] =>
[100000152470577] =>
)
(Small part of the output). - Added PHP Example.
Anyone know how I can get C# to collect the same information in the same way my PHP array does?
using System.Linq;
var grouppingByLocation = users.GroupBy(u => u.LocationID);
foreach (var g in grouppingByLocation)
{
Console.WriteLine("Location id: {0}", g.Key);
foreach (var u in g)
{
Console.WriteLine("User id: {0}", u.ID);
}
}
See Enumerable.GroupBy() for more details.
This is an Extension Method over IEnumerable<T> interface implemented by any built-in collection (such as Array T[], List<T>, Dictionary<K,V>, etc.) which accepts a lambda expression pointing to a property of class collection of which you're grouping by.
If you want to build the list looping through initial data, you can create object like this:
var list = new Dictionary<int, Tuple<int, List<int>>();
And fill it in the loop
if(list[locationID]==null) list[locationID] = Tuple.Create(0,new List<int>());
//..
list[locationId].Item1++; // counter
list[locationId].Item2.Add(userId); //list of users
Create an object to hold each item of data.
public Class Model{
public int LocationId {get;set;}
public int Occurences{get;set;}
public IList<User> Users{get;set;}
}
Initialize the container as a list of items.
var container = List<Model>();
Process you list of users.
foreach(var user in userList){
var model = container.SingleOrDefault(x=> x.LocationId == user.LocationId);
if(model != null){
model.Users.Add(user);
} else{
model = new Model{
model.Users = new List<User>.Add(user);
model.LocationId = user.LocationId;
container.Add(model)
}
model.Occruences ++;
}
}
var byLocation = users.Where(u => !string.IsNullOrEmpty(u.Location))
.GroupBy(u => u.Location);
var stats = byLocation.Select(l => string.Format("{0} / {1} occurrences / {2}",
l.Key, l.Count(), string.Join("/", l.Select(u => u.User)));
// And just to print the result
foreach (var location in stats)
Console.WriteLine(location);

Categories