I have a type Card with an int property called Value where Ace = 14, Five = 5, etc.
If I have a list of Cards (5), ie. a hand. What I would like to do is count the number cards where the Value is equal to another card, I.e. Finding 4 of a kind, 3 of a kind, a pair, two pair etc. I'm fairly new to C#/programming, but I believe this is a case for LINQ/Lambda expression? Can someone help me out?
class Card : IComparable<Card>
{
private string name;
private int value;
private string suit;
public int Value
{
get
{
return value;
}
}
<.....>
//Constructor accepts string ex. "AH" and builds value 14, suit "Hearts"
public int CompareTo(Card that)
{
if (this.value > that.value) return -1;
if (this.value == that.value) return 0;
return 1;
}
}
List<Card> HandBuilder = new List<Card>();
HandBuilder.Add(...); //5 Cards
HandBuilder.Count ?? //Help
It's pretty trivial using GroupBy.
var cards = HandBuilder.GroupBy(card => card.Value)
.OrderByDescending(group => group.Count());
To check for four of a kind just see if the first group has four items; to check for three of a kind see if the first group has three items. To check for two pair just see if the first two groups each have two items.
Related
I need to return a value that corresponds to some weighting for a calculation based on age.
Here's the age ranges and weights:-
21-30: 1.2
31-40: 1.8
41-50: 1.9
and so on (there's no real pattern)
The program needs to take an age as input and then return the weighting (e.g. if age = 35, the return value would be 1.8.
How would this be best achieved?
I could use switch but I'm not sure if it's the best way around this. Is there some other construct or technique I could apply in C# to achieve this that would be more effective and portable/scalable should the weightings change?
One other thing - I can't use a database to store any weightings data - just adding this for info.
Create a Dictionary to define your age ranges and weights. The key should be a Tuple with the min-age and max-age for this range and the value should be your weight:
Dictionary<Tuple<int,int>, double> // minAge, MaxAge -> weight
Then you may loop through all keys to find your weight.
You may create this dictionary from the contents of a table, a XML file, a database, whatever.
We have done something similar in a system here, and we use the concept of storing in a database table the weighting and the lower threshold. Thus all we need to do is to find the record with the highest lower threshold less than the value entered and read the weight.
This simplifies the process and allows for editing and adding and removing the values.
No, as far as I know, there is nothing like a range-structure.
You could use a switch, either in this way, if the ranges are always from x1 to y0
switch((value-1) / 10)
{
case 1: ... break;
case 2: ... break;
}
or, if needed:
switch(value)
{
case 11:
case 12:
case 20: ... break;
case 21: ...
}
Depending on the number of groups you need, you could do checks like
if(value > 10 && <= 20) ...
I don't know any more elegant approach.
If the ranges do not overlap then the best thing to use would be a SortedList where the key is the upper value of the range, and the value is the weight. Additionally you can make the weigth nullable to distinguish the case of not finding an entry. I've added the entry of {20, null} so that if the age is <= 20 you'll get null instead of 1.2.
var rangedWeights = new SortedList<int, float?>
{
{ 20, null }
{ 30, 1.2f },
{ 40, 1.8f },
{ 50, 1.9f }
};
int age = 44;
float? weight = null;
foreach (var kvp in rangedWeights)
{
if (age <= kvp.Key)
{
weight = kvp.Value;
break;
}
}
You can dynamically add new entries and still be sure they are sorted.
rangedWeights.Add(60, 2.1f);
You can use dictionary with composite key, which you will use to check the user input and get the respected value for the matching key.
Here is example;
Dictionary<Tuple<int, int>, double> t = new Dictionary<Tuple<int, int>, double>();
t.Add(new Tuple<int,int>(11,20),1f);
t.Add(new Tuple<int, int>(21, 30), 2f);
t.Add(new Tuple<int, int>(31, 40), 3f);
int weight = 34;
double rr = (from d in t where d.Key.Item1 <= weight && d.Key.Item2 >= weight select d.Value).FirstOrDefault();
Console.WriteLine(rr); // rr will print 3.0
Hope it helps..!!
If the ranges are consecutive as they appear to be in your example, you only need the upper value and the ranges sorted in order to be able to query it, so you can do something like this:
public class RangeEntry
{
public RangeEntry(int upperValue, float weight)
{
UpperValue = uperValue;
Weight = weight;
}
public int UpperValue { get; set; }
public float Weight { get; set; }
}
public class RangeWeights
{
private List<RangeEntry> _ranges = new List<RangeEntry>
{
new RangeEntry(30, 1.2f),
new RangeEntry(40, 1.8f),
new RangeEntry(50, 1.9f),
}
public float GetWeight(int value)
{
// If you pre-sort the ranges then you won't need the below OrderBy
foreach (var r in _ranges.OrderBy(o => o.UpperValue))
{
if (value <= r.UpperValue)
return r.Weight;
}
// Range not found, do whatever you want here
throw new InvalidOperationException("value not within in any valid range");
}
}
The value of this approach is that adding a new range means adding just 1 line of code in the instantiation of the ranges.
If you are looking for a shorter way of writing it, I suggest you use ?: operator.
double getWeight(int age){
return (age <= 30) ? 1.2 :
(age <= 40) ? 1.8 : 1.9;
}
It will be the same as using switch. Only shorter way of putting it. You can replace the digits and weights with variables if you don't want them to be hard coded.
It's not very clear what is the structure of the age ranges and weights data,
but I would probably do something like this:
Class AgeRangeAndWeight {
int FromAge {get;set;}
int ToAge {get;set;}
double Weight {get;set;}
}
Class AgeRangeAndWeight Collection() : List<AgeRangeAndWeight> {
AgeRangeAndWeight FindByAge(int age) {
foreach(AgeRangeAndWeight araw in this) {
if(age >= araw.FromAge && age <= araw.ToAge) {
return araw;
}
}
return null;
}
}
then all you have to do is call the FindByAge method. remember to check that it doesn't return null.
Update
Five years after I've posted this answer it was upvoted.
Today I wouldn't recommend inheriting a List<T> - but simply use it's Find method like this:
var list = new List<AgeRangeAndWeight>() {/* populate here */}
var age = 35;
var ageRangeAndWeight = list.Find(a => d.FromAge >= age && d.ToAge <= age);
I have, for instance, 10 teams and I need to match them.
The criteria for matching are:
Two teams are matched only one time.
Teams with closest score must be matched (Score is property of Team class, type is double).
It could be iterated 9 (n-1) times max and this is what I try to do, go further as I can.
At the end of each iteration, points of teams increase randomly.
I populated my list with random data.
List<Team> _teams = new List<Team>();
for (int i = 1; i <= 10; i++)
{
_teams.Add(new Team("Team "+i,MyExtensions.RandomNumber(31)));
}
private static readonly Random Rand = new Random();
public static int RandomNumber(int max)
{
return Rand.Next(max);
}
My team class:
public class Team
{
private string _name;
private double _score;
private List<Team> _matchedTeams;
private Team _currentMatch;
public Team CurrentMatch
{
get { return _currentMatch; }
set { _currentMatch = value; }
}
public List<Team> MatchedList
{
get { return _matchedTeams; }
set { _matchedTeams = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public double Score
{
get { return _score; }
set { _score = value; }
}
public Team(string name, double score)
{
_matchedTeams = new List<Team>();
_name = name;
_score = score;
}
public override string ToString()
{
return _name + " - (" + _score + ")";
}
}
And here's my extension method to get closest match;
public static Team FindClosest(this List<Team> list, Team team)
{
var orderedItems =
list.Where(p => p != team && !p.MatchedList.Contains(team)).OrderBy(x => Math.Abs(x.Score - team.Score));
return orderedItems.FirstOrDefault();
}
var nearestMatch = _teams.FindClosest(_teams.ElementAt(0));
Actually, I am trying to make a bridge game fixture calculator. For each round different teams must be matched and matched teams should be equal (or close) strength. But for next rounds, because of team match uniqueness is first criterion and we must arrange a match for people, we must bend the rules of score (point) closeness.
So it will result something like that;
Algorithm runs...
First Round;
Team1-Team2 ; Team3-Team4 ; Team5-Team6 .....
scores are updated by user at the end of the round1
Algorithm runs...
Second Round;
Team1-Team7 ; Team3-Team8 ; Team4-Team9 .....
.....
scores are updated by user at the end of the round8
Algorithm runs...
Ninth Round;
Team1-Team9; Team2-Team7; Team3-Team5 .....
scores are updated by user at the end of the round9
Algorithm runs...
No more match.
I prefer an algorithm like backtracking instead of brute force or random trial-and-error.
Can you suggest me an algorithm, a method to solve my problem?
Any help would be much appreciated.
Sort the teams by number of points, and then take pairs of teams starting from the beginning of the sorted list. Clearly the second-place team is the only possible team that is closest in points to the first-place team. If you don't pair the first-place team with the second-place team, but instead pair the second-place team with the third-place team, then the best match for the first-place team will be the fourth-place team. Pairing teams out of order in the sorted list will only increase the points disparity.
To illustrate, assume that there are 4 teams named A,B,C,D. The difference in points between the teams is d1,d2,d3
A B C D
d1 d2 d3
If the pairings are AB and CD, then the set of differences is { d1, d3 }.
If the pairings are BC and AD, then the set of differences is { d2, (d1+d2+d3) }.
I don't know of any metric where the second set is preferable to the first.
You need to explicitly state an objective function you are trying to optimize. If it's to find a minimum of sum of absolute values of differences between points for pairs, then I agree with another answer, you simply sort the teams by points and then pair them off two teams at a time from left to right and you can prove this gives the optimal pairing. If your objective function is different, then it may be harder to optimize, maybe even NP-hard depending on what your objective function is. But the point is we can't help if you if you just say "teams closest in points must be paired together" because it's possible to have a team with two neighbors on opposite sides that are equally close in points, such that the other neighbors of the two neighbors are far away, and then you can only pair up the team with one of its close neighbors.
You will need this course.
The dynamic programming may be too slow. Maybe the Local search, or other method would be more efficient. You could read the videos about Local Search first.
Given several sets such as {1,2,3,4},{1,2},{1},{1,2,3,},..., and a number k such that the combination that ought to be formed is exactly of length k, how would one go about generating a unique combination of length k if I can only choose one number from each set?
There are exactly k number of sets to choose from. I'm looking for the most efficient way to do this in c#.
Intuitively i tried(though i'm not sure it is correct) generating all possible combinations for each set then concatenating each i'th combination from each set with the corresponding i'th combination from subsequent sets to form a k long unique combination but I'm sure there is a general case for this type of question.
Can anyone please offer some tips or advise? Specifically which math or computer science topic this falls under and how these questions are typically solved?
in the snippet below a string in the array theSets would be for example "31"
this means that k = 2 (length of a string in theSets) and there are two sets one of which is {1,2,3} and {1}. with these sets, generate all the unique combinations and provide a count
private int combicount(string[] theSets)
{
int size = theSets[0].Length;
int count = 0;
List<HashSet<int>> l = new List<HashSet<int>>();
foreach (string s in theSets)
{
foreach (char c in s)
{
HashSet<int> h = new HashSet<int>();
for (int n = 1; n <= int.Parse(c.ToString()); n++)
{
h.Add(n);
}
l.Add(h);
}
//toDO - generate all unique combinations by picking exactly 1 from each set in l
//toDO - count the number of unique combinations generated
}
return count;
}
You can use LINQ to solve it like this:
private void CombineSets()
{
var mySets = new List<HashSet<int>>();
mySets.Add(new HashSet<int>() { 1, 2, 3, 4 });
mySets.Add(new HashSet<int>() { 1, 2 });
mySets.Add(new HashSet<int>() { 1 });
mySets.Add(new HashSet<int>() { 1, 2, 3 });
var result = new HashSet<int>();
while (mySets.Count > 0)
{
//get the smallest set
var set = mySets.OrderBy(x => x.Count).First();
//remove the set from the collection as we do not need it anymore (we got our value)
mySets.Remove(set);
//remove what is in the result from the set (as we only want unique values)
set.ExceptWith(result);
//then add the first value from the remaining values to our result set
result.Add(set.First());
}
}
To make this more efficient you could also sort the list before the while loop. This at least solves the first few lines of your question.
Hope this helps :)
I'm writing a card game in C#, a game of blackjack, where the user presses a button and it deals a card, the value of that card gets added to a text box. If the value of the text box goes over 21 the user has lost.
I'm having a problems with the dealing of the cards, i generate a random, say 5 gets generated show the 5 of diamonds, add the value of the 5 of diamonds to the score text box and display a picture box(the 5 of diamonds playing card). However once 5 has been generated i'm having problems not generating that card again, i have the logic down of what needs to happen, i'm just not sure on the actual code itself as i'm a beginner.
So far iv tried 2 different ways, a List and a array of Boolean values and i'm still struggling, could anybody point me in the right direction in terms of code.
List<int> Diamonds = new List<int>();
Random random = new Random();
genRandom = random.Next(0, 5);
while (Diamonds.Contains(genRandom))
{
genRandom = random.Next(0, 5);
break;
}
while (!Diamonds.Contains(genRandom))
if (genRandom == 0)
{
Diamonds.add(0);
score = score += 2;
scoreTextBox.Text = score.ToString();
diamonds2.Show();
}
Thanks in advance, sorry about the bad grammar!
I would take the reverse approach, by creating a collection that holds all the possible cards, and then draw them from the collection randomly.
Say you have a class called Deck, which represents a card deck. Fill it with Card classes. Now when you start drawing cards, randomly pick a number in the collection and remove that card from the Deck. The next time the same random number is drawn, it will draw a different card from the deck, since you remove the used cards.
Just remember to generate a random number that is within bounds of the size of the deck, which will decrease after each draw.
The problem you have right now is that you don't have a card pool. You should have a list of card you can draw from and once a card is picked, it is removed from the available choices and cannot be drawn again.
List<Card> deck = new List<Card>();
deck.Add(new Card(1, CardType.Diamonds));
deck.Add(new Card(2, CardType.Diamonds));
...
Card nextCard = deck[Random.Next(0, deck.Count - 1)];
deck.Remove(nextCard);
where:
struct Card
{
public int number;
public CardType type;
public Card(int number, CardType type)
{
this.number = number;
this.type = type;
}
}
enum CardType
{
Diamonds,
Spades,
Hearts,
Clubs
}
It's a very simplistic, object oriented approach where each card is clearly defined as a unique container. It might not be the most optimal way, but probably much easier to understand.
There are many ways to do that the most i use it is as follows:
List<int> available = new List<int>(5000);
for(int i=1; i<=5; i++)
available.Add(i);
The above code will generate all random numbers.
Now you can choose from them as follows:
List<int> result = new List<int>(5000);
while(available.Count > 0)
{
int index = random.Next(availableCount);
result.Add(available[index]);
available.RemoveAt(index);
}
return result;
Since you are removing after getting they will never repeat.
You could do something like this:
List<int> Diamonds = new List<int>();
for(int i = 1; i <= 10; i++) //10 is just an example..dk how many cards :P
{
Diamonds.Add(i);
}
Random random = new Random();
int index = random.Next(0, Diamonds.Count - 1);
int nr = Diamonds[index];
Diamonds.Remove(index);
Easiest thing to do would be to sort the entire list randomly (by sorting by Rand() - 0.5), then each time you want a 'random' card, take the first one and remove it from the list. The cards are in a random order so it's the same effect statistically as picking a random one each time, and you won't pick the same card twice in a given game.
The easiest way to do this conceptually is to have an array which represents a deck of cards, and then deal cards from it.
Ideally you would use a stack for this, but unfortunately you can't shuffle a stack!
Therefore, your best bet is to use a List<Card>, where 'Card' is a class that represents a card. The Card class would have two properties: a Suite and a rank from 1 (the Ace) to 13 where 11 is Jack, 12 is Queen and 13 is King.
You would populate your List<Card> deck with all 52 possible cards, and then you could shuffle it using an implementation of the Fisher-Yates shuffle.
Here's a complete sample:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var deck = new List<Card>();
for (int rank = 1; rank <= 13; ++rank)
foreach (Suite suite in Enum.GetValues(typeof(Suite)))
deck.Add(new Card {Rank = rank, Suite = suite});
var shuffler = new Shuffler();
shuffler.Shuffle(deck);
// Deal the top 10 cards.
for (int i = 0; i < 10; ++i)
Console.WriteLine(deck[i]);
}
}
public enum Suite
{
Clubs,
Diamonds,
Hearts,
Spades
}
public sealed class Card
{
public Suite Suite;
public int Rank;
public override string ToString()
{
string rank;
switch (Rank)
{
case 1: rank = "Ace"; break;
case 11: rank = "Jack"; break;
case 12: rank = "Queen"; break;
case 13: rank = "King"; break;
default: rank = Rank.ToString(); break;
}
return string.Format("{0} of {1}", rank, Suite);
}
}
public sealed class Shuffler
{
public Shuffler()
{
_rng = new Random();
}
public void Shuffle<T>(IList<T> array)
{
for (int n = array.Count; n > 1; )
{
int k = _rng.Next(n);
--n;
T temp = array[n];
array[n] = array[k];
array[k] = temp;
}
}
private readonly Random _rng;
}
}
Real code should validate Rank and Suite, of course. Also, you'd be better off writing a Deck class which encapsulated shuffling and dealing (it could remove cards from the end of the List<Card> when dealing).
Lets assume:
List<element> which element is:
public class Element {
int Weight { get; set; }
}
What I want to achieve is, select an element randomly by the weight.
For example:
Element_1.Weight = 100;
Element_2.Weight = 50;
Element_3.Weight = 200;
So
the chance Element_1 got selected is 100/(100+50+200)=28.57%
the chance Element_2 got selected is 50/(100+50+200)=14.29%
the chance Element_3 got selected is 200/(100+50+200)=57.14%
I know I can create a loop, calculate total, etc...
What I want to learn is, whats the best way to do this by Linq in ONE line (or as short as possible), thanks.
UPDATE
I found my answer below. First thing I learn is: Linq is NOT magic, it's slower then well-designed loop.
So my question becomes find a random element by weight, (without as short as possible stuff :)
If you want a generic version (useful for using with a (singleton) randomize helper, consider whether you need a constant seed or not)
usage:
randomizer.GetRandomItem(items, x => x.Weight)
code:
public T GetRandomItem<T>(IEnumerable<T> itemsEnumerable, Func<T, int> weightKey)
{
var items = itemsEnumerable.ToList();
var totalWeight = items.Sum(x => weightKey(x));
var randomWeightedIndex = _random.Next(totalWeight);
var itemWeightedIndex = 0;
foreach(var item in items)
{
itemWeightedIndex += weightKey(item);
if(randomWeightedIndex < itemWeightedIndex)
return item;
}
throw new ArgumentException("Collection count and weights must be greater than 0");
}
// assuming rnd is an already instantiated instance of the Random class
var max = list.Sum(y => y.Weight);
var rand = rnd.Next(max);
var res = list
.FirstOrDefault(x => rand >= (max -= x.Weight));
This is a fast solution with precomputation. The precomputation takes O(n), the search O(log(n)).
Precompute:
int[] lookup=new int[elements.Length];
lookup[0]=elements[0].Weight-1;
for(int i=1;i<lookup.Length;i++)
{
lookup[i]=lookup[i-1]+elements[i].Weight;
}
To generate:
int total=lookup[lookup.Length-1];
int chosen=random.GetNext(total);
int index=Array.BinarySearch(lookup,chosen);
if(index<0)
index=~index;
return elements[index];
But if the list changes between each search, you can instead use a simple O(n) linear search:
int total=elements.Sum(e=>e.Weight);
int chosen=random.GetNext(total);
int runningSum=0;
foreach(var element in elements)
{
runningSum+=element.Weight;
if(chosen<runningSum)
return element;
}
This could work:
int weightsSum = list.Sum(element => element.Weight);
int start = 1;
var partitions = list.Select(element =>
{
var oldStart = start;
start += element.Weight;
return new { Element = element, End = oldStart + element.Weight - 1};
});
var randomWeight = random.Next(weightsSum);
var randomElement = partitions.First(partition => (partition.End > randomWeight)).
Select(partition => partition.Element);
Basically, for each element a partition is created with an end weight.
In your example, Element1 would associated to (1-->100), Element2 associated to (101-->151) and so on...
Then a random weight sum is calculated and we look for the range which is associated to it.
You could also compute the sum in the method group but that would introduce another side effect...
Note that I'm not saying this is elegant or fast. But it does use linq (not in one line...)
.Net 6 introduced .MaxBy making this much easier.
This could now be simplified to the following one-liner:
list.MaxBy(x => rng.GetNext(x.weight));
This works best if the weights are large or floating point numbers, otherwise there will be collisions, which can be prevented by multiplying the weight by some factor.