Remove random entry until collection is empty - c#

I'm pretty sure this is something basic but for some reason I just can't seem to get it work. First I'm not sure which Collection Type I should be using. I'm using C# for my Unity game and I'd like to store info like this:
# Name Number Value
1 Jim 15
2 Bob 21
3 Tim 31
4 Ron 26
Numbers 1-4 would be indexes so if I removed entry #3 with Tim, the table would look like:
# Name Number Value
1 Jim 15
2 Bob 21
3 Ron 26
I assume I should be using Dictionaries and that's what I tried. What I want to do with my code is to pick a random element, print it and then remove it. Rinse and repeat until the list is empty.
This is what I've been using for my code:
Dictionary<String, int> myDictionary = new Dictionary<String, int> ();
void Start()
{
myDictionary.Add ("Jim", 15);
myDictionary.Add ("Bob", 21);
myDictionary.Add ("Tim", 31);
myDictionary.Add ("Ron", 26);
}
void Update()
{
int totalEntries = myDictionary.Count;
int randNumber = Random.Range(1,totalEntries);
print(myDictionary[randNumber]);
myDictionary.Remove(randNumber);
}
It's not working. I've seen some suggestions about using RemoveAt or using HashTable but I haven't been able to get those working either. RemoveAt for some reason isn't recognized as a command and HashTable just kind of had the same issue. I am using "using System.Collections.Generic;" btw so that's not the problem. I'd like to mention that I'm pretty new to C# so what would help me the most would be actual working code that I can then edit to fit my game.

A Dictionary would not be ideal in this case.
I'd use a List of Tuples (or a List of a custom class).
List<Tuple<int, String, int>> myItems = new List<Tuple<int, String, int>>();
void Start()
{
myItems.Add(Tuple.Create(1, "Jim", 15));
myItems.Add(Tuple.Create(2, "Bob", 21));
myItems.Add(Tuple.Create(3, "Tim", 31));
myItems.Add(Tuple.Create(4, "Ron", 26));
}
Now there are a couple of ways to identify the object you wish to remove. First your initial approach (by index in the list):
void Update()
{
var rnd = new Random();
var randNumber = rnd.Next(0, myItem.Count);
myItems.Remove(myItems.ElementAt(randNumber));
}
Then by the actual value of the object:
void Update2()
{
var minValue = myItems.Min(i => i.Item1); // Gets the minimum value in first column (so to speak);
var maxValue = myItems.Max(i => i.Item1); // Gets the maximum value.
var rnd = new Random();
var randNumber = rnd.Next(minValue, maxValue + 1); // maximum value in Random.Next is exclusive.
var myObject = myItems.SingleOrDefault(o => o.Item1 == randNumber);
if (myObject != null)
myItems.Remove(myObject);
}
Which method you prefer is up to you and the requirements to solve the task. You might prefer by index but on the other hand you might prefer removing by value.
The drawback by the second method is that you might not remove an object at all when the random value is already removed.

Try this:
using System.Linq;
var r = new Random();
var i = r.Next(0, dic.Count);
var e = dic.ElementAt(i)
dic.Remove(e);

create an array with the same length, and out in each cell a number 1 to N. and shuffle the array. then delete each row by index

I ended up finding this link: http://wiki.unity3d.com/index.php/Choosing_the_right_collection_type
And have been able to solve my issue with ArrayLists. This is the part of the link that really helped me:
ArrayList myArrayList = new ArrayList(); // declaration
myArrayList.Add(anItem); // add an item to the end of the array
myArrayList[i] = newValue; // change the value stored at position i
TheType thisItem = (TheType) myArray[i]; // retrieve an item from position i
myArray.RemoveAt(i); // remove an item from position i
var howBig = myArray.Count; // get the number of items in the ArrayList
Thanks everyone for all the sugestions!

Related

Constantly generating unique and random values in a specific range in C#

I am implementing this code in my discord bot, where I want to be able to generate unique numbers from 1 to 10 (as suggested above) whenever I input a single command.
Turns out that the values sometimes are repeated. Therefore I was suggested to add an array (used[ ]) and a loop in order to check every time if the value has been generated already.
Random random = new Random();
int[] used = new int[10];
int rng = 0;
while (!used.Contains(rng))
{
rng = random.Next(1, 10);
}
/*
I wish to store the generated value "rng" to the "used" array.
e.g.
used[0] = rng
used[1] = rng
used[2] = rng
etc.
*/
Console.WriteLine("The number generated is " + Convert.ToString(rng));
However, I don't know how to constantly add values to an array in an arranged order. (as seen by the commentations above)
In a more simple way, For 10 times I want the system to generate a number, and those numbers are randomly picked out of [1,10] and only once. If all the numbers have been generated once, all are free to be generated again.
Sorry for my bad interpretation. I have refined it with the comments that everyone has contributed.
Here is a solution, that shuffles the values and returns each value until the list is exhausted, then starts all over:
int[] GenerateNewArray(int n)
{
// Define an array of values we wish to return and populate it
int[] baseValues = Enumerable.Range(1, n).ToArray();
Random rnd=new Random();
// Shuffle the array randomly using Linq
return baseValues.OrderBy(x => rnd.Next()).ToArray();
}
void Main()
{
int nNumbers = 10;
while (true)
{
// Generate a new randomized array
var values = GenerateNewArray(nNumbers);
// Print each value
for (int i=0;i<nNumbers;i++)
{
Console.WriteLine($"The number generated is {values[i]}");
}
}
}
EDIT
Parameterized the number of unique values.
Rather than storing in an array, looking for duplicates etc., just use a HashSet
This only accepts unique values, and if you try to add a duplicate it will be simply ignored.
Or you can just do a check to see if it exists
HashSet<int> integerSet = new HashSet<int>();
if (hashSet.Contains(rng ))
// element already exists in set
else
//Doesn't exist
Of course, if you just keep generating each number randomly until you have generated everything in the range, then you might as well just simply generate an array/list of elements with every number and save yourself processing time!
Using a list is a good way to start, but before continuing the comment go check out the list documentation
In small talk a list is an IEnumerable of a type you choose, in your case to declare and instanciate a list you should write like this List<int> listName = new List<int>(); and the main difference between an array and a list is that you can populate the list whenever you want without the length declaration, to do that you can add listName.Add(2); (2 : the integer value that you want to add) or remove values listName.Remove(0) (0 : the position of the list that need to be removed, ex: 0 is the first value inside the list because position 0 is the start)
Random random = new Random();
List<int> listName = new List<int>();
int rng = 0, counterOfValues = 0;
while (counterOfValues < 10)
{
rng = random.Next(1, 10);
If(!listName.Contains(rng))
{
listName.Add(rng);
Console.WriteLine("The number generated is " + listName.Last().ToString());
If(counterOfValues < 10)
{
counterOfValues++;
}
else
{
counterOfValues = 0;
}
}
}
This should probably do the work ("should" because it's some code I wrote out of my head, so sorry about caps ecc)
Let me know if it works as intended or we need to correct something!
P.S: If you want the user to select the range of the randomic values just change the value of counterOfValues in the lowest value and the 10's with the higher value
Using a HashSet is better to know the numbers that you are use. But in your case, I think it's better use a List and remove elements:
var list = new List<int>();
for (int i = 1; i <= 10; i++)
list.Add(i);
// List is 0-index
// list = 1 2 3 4 5 6 7 8 9 10
int rng = random.Next(0, list.Count - 1);
// Suppose rgn=3: You get 3 value and remove it from the list
var value = list[rgn];
list.RemoveAt(rgn);
// list = 1 2 4 5 6 7 8 9 10
// Now you get a value between 0...8 (list.Count is 9)
rng = random.Next(0, list.Count - 1);
// Suppose rgn=3: You get 4 value and remove it from the list
var value = list[rgn];
list.RemoveAt(rgn);
// list = 1 2 5 6 7 8 9 10
// Now you get a value between 0...7 (list.Count is 8)
rng = random.Next(0, list.Count - 1);
// Suppose rgn=5: You get 7 value and remove it from the list
var value = list[rgn];
list.RemoveAt(rgn);
// list = 1 2 5 6 8 9 10
...
When list has one element you don't need use random.
When your list is empty, fill again and repeat the whole process.
In this form you always get an element with each random call. Using HashSet you may try a lot of random calls to get a nonused number.
A class for that maybe:
public class RandomClass
{
private readonly List<int> _list;
private readonly Random _random;
public RandomClass()
{
this._list = new List<int>();
this._random = new Random();
}
private void PopulateList()
{
for (int i = 1; i <= 10; i++)
this._list.Add(i);
}
public int GetNumber()
{
if (this._list.Count == 0)
{
this.PopulateList();
}
if (this._list.Count > 1)
{
int rng = this._random.Next(0, this._list.Count);
var value = this._list[rgn];
this._list.RemoveAt(rgn);
return value;
}
else
{
var value = this._list[0];
this._list.RemoveAt(0);
return value;
}
}
}
UPDATE: Some about List, Arrays and other collections
Array and List are very similar. The main difference is that an array is a continuous block of memory while List (in memory) it's not a block. Array is faster to iterate (address of first element + multiply for sizeof each element) but slower when you want to add/remove elements (you must create other array and copy the elements). From the point of view of an user, both of them are used to store a list of items and iterate them.
Other collections very interesting are HashSet and Dictionary. These collections are sets, you can't access their elements using an index. You saw an example for HashSet in this page: Add a number to the set and query it if you want know if that number appear before. The key with this sets are that you access to their elements using a hash. Instead of an index, you use whatever you want as a key and an algorithm get quickly the position of the element in the set. Dictionary is like a HashSet but you can store addicional data asociated to the key.
In many cases, you'll use both of them: a list to iterate sequencially and a dictionary to direct access to elements. For example, you have a lot of Persons that you show in some order (name, surname...). You store them in a List an allow user to change the order (using Sort of the list). You use a list because you show and use that order many times: you need ordered Persons.
But you have many, many Persons. Suppose that your program allow search Persons by Id and Name. Each time a user search for Persons you must iterate a very long list. It's not efficient. So you define two Dictionary:
Dictionary<int, List<Person>> personsById;
Dictionary<string, List<Person>> personsByName;
Also, you have a List:
List<Person> allPersons;
And we need some operations:
public void AddPerson(Person person)
{
Person existingPerson;
if (personsById.TryGetValue(person.Id, out existingPerson))
{
// Already exists: don't add duplicated persons
return;
}
personsById.Add(person.Id, person);
// Maybe more than one person with the same name. It's the reason why use a List here
List<Person> list;
if (personsByName.TryGetValue(person.Name, out list))
{
// There are other persons with this name, so the list exists. Simply add person to list
list.Add(person);
}
else
{
// Is the first person with this name: create the list and associate to the name
personsByName.Add(person.Name, new List<Person> { person });
}
// Always add to main list
allPersons.Add(person);
}
public void RemovePerson(Person person)
{
if (!personsById.TryGetValue(person.Id, out _))
{
// Not exists: do nothing
return;
}
personsById.Remove(person.Id);
// NOTE: Here we are removing person assuming that you never have different instances
// of person. If you can create different instances for a person, here we must iterate
// the lists to find the person
List<Person> list;
if (personsByName.TryGetValue(person.Name, out list))
{
list.Remove(person);
}
allPersons.Remove(person);
}
public Person GetPerson(int id)
{
return this.personsById.TryGetValue(id, out Person person) ? person : null;
}
public List<Person> GetPersons(string name)
{
return this.personsByName.TryGetValue(name, out List<Person> list) ? list : null;
}
It's a very basic example but I think maybe a good one to learn about this classes.

Randomly selects an item from one of the two lists

I am working with generating Random items from either one or another list. I am kind of struggling how to do that.
Basically I have two lists:
List<string> names = new List<string>();
List<string> surnames = new List<string>();
I know how to get an item from one list randomly, but I am struggling how to do so there will be a possibility of taking an item from either names or surnames.
I know there is possibly an easy solution for that but couldn't find it.
Any help would be appreciated.
I know how to get an item from one list randomly
Leverage the technique for taking a random item from a single list to build a simple approach that works with two lists.
Imagine that you have a list of length N = names.Count + surnames.Count
Pick a random position p between 0, inclusive, and N, exclusive
If the position p is less than names.Count, use names[p]
Otherwise, use surnames[p - names.Length]
Effectively, the above approach picks an item form a merged list without performing an actual merge.
Edit: It turns out that you wanted a random combination of names[] and surnames[]. This is a simpler task, which is achieved by picking a random element from an array twice - once from names[], and then separately from surnames[].
This should do the job:
Random r = new Random();
Int32 nameIdx = r.Next(names.Count);
Int32 surnameIdx = r.Next(surnames.Count);
String randFullname = names[nameIdx] + " " + surnames[surnameIdx];
This is just an example to show you how to work with random array accesses. If you need to select only one name or one surname (the question was not really clear on that point "but I am struggling how to do so there will be a possibility of taking an item from either names or surnames"), just throw another random [0 1] and pick the first or the second list basing your choice on the output value:
List<String> currentList;
String result;
Random r = new Random();
if (rand.Next(0, 2) == 0)
currentList = names;
else
currentList = surnames;
Int32 idx = r.Next(currentList.Count);
String result = currentList[idx];
Otherwise, just pick a single random entry from a concatenation:
List<String> con = names.Concat(surnames).ToList();
You can merge two lists and access random element as follows,
var newList = names.Concat(surnames).ToList();
Random r = new Random();
string rand = newList[r.Next(newList.Count)];
If you want to do it in a one-liner you could try the following:
var r = new Random();
var randomName = names.Concat(surnames).OrderBy(n => r.Next()).First();
It's not very efficient memory wise, but it should work.

How can I always have a different random phrase selected when using a random? [duplicate]

This question already has answers here:
Randomize a List<T>
(28 answers)
Closed 5 years ago.
I have this code:
public async Task ShowTimedCard()
{
phrase = phrases[(int)AS.rand.Next(phrases.Count)];
It gets a random number and then selects a phrase from phrases which is a List of phrase rows. The List has items added and removed from it by a background process.
Each phrase has an Id field. I cannot remove phrases that I have used from the List for other reasons.
Sometimes ShowTimedCard picks the same phrase twice in a row so I decided to store the lastPhrase information in a static variable like this:
AS.phrase.id = phrase.id
How can I make it so if there are more than 1 items in the phrases List then it will not pick the same phrase twice in a row? I was thinking of some kind of while loop or until loop but not sure how to implement that. I guess it needs to compare the phrase.Id with the AS.phrase.id
Show where you create rand
You should create it just once and reuse it
I bet you are creating new and not enough tick to get a new seed
OK I may have missed the question
Just shuffle the List using Yates shuffle
This is byte but you get the idea
for (byte i = (byte)(count - 1); i >= 1; i--)
{
byte k = (byte)rand.Next(i + 1);
if (k != i)
{ // exchange the values
curVal = deck[i];
deck[i] = deck[k];
deck[k] = curVal;
}
}
Or store the used id in a hashset
Random rand = new Random();
HashSet<int> hs = new HashSet<int>();
int next;
while (hs.Contains(next = rand.Next(12))) { }
hs.Add(next)
Moving target
while (next = rand.Next(12) == lastNext) { }
lastNext = next;
It sounds like what you really want to do is sort the list randomly and then you can just take consecutive items. This will ensure that you don't see any item before all the others have been displayed, but they won't be displayed in the order they were added:
// Shuffle our list of phrases, so they're in a random order
var rnd = new Random();
phrases = phrases.OrderBy(p => rnd.NextDouble()).ToList();
You can then re-shuffle the list anytime you want, like if you add new items or when you've reached the end of the list.
Here's a quick demo using integers:
// Create a list of consecutive integers and display it
var numbers = Enumerable.Range(1, 30).ToList();
Console.WriteLine("Original list:");
Console.WriteLine(string.Join(", ", numbers));
// Shuffle the list and display it again
var rnd = new Random();
numbers = numbers.OrderBy(n => rnd.NextDouble()).ToList();
Console.WriteLine("\nShuffled list:");
Console.WriteLine(string.Join(", ", numbers));
Output

picking random name from text file in c# windows forms non repeatable

I was wondering how get on with this code, currently working on a tournament bracket system.
Currently I have created a comboBox that fetches all the lines from "log.txt" there are 16 lines in the txt file; then I created a assign button that is supposed to assign all the names into 16 textboxes called User1 --> User16, however the same name cant be repeated.
I looked at "Array of list" & "Array of string", but I seem to be stuck since I cant really figure out what to put in the code.
my random button looks like this at the moment:
private void assign_Click(object sender, EventArgs e)
{
int x;
Random rnd = new Random();
x = rnd.Next(0, 16);
User1.Text = comboBox2.Items[x].ToString();
x = rnd.Next(0, 16);
User2.Text = comboBox2.Items[x].ToString();
x = rnd.Next(0, 16);
User3.Text = comboBox2.Items[x].ToString();
x = rnd.Next(0, 16);
User4.Text = comboBox2.Items[x].ToString();
and so on untill i hit
x = rnd.Next(0, 16);
User16.Text = comboBox2.Items[x].ToString();
}
One of the simplest, but not necessarily most efficient, way to do this is to put all your strings into a List<string> and remove them randomly one-by-one. This would work a lot better if you put all your textboxes into a collection as well. For example, given a list of strings called myStrings and a collection of textboxes called myTextboxes, you could:
for (var i=0; i < myStrings.Count; i++)
{
var idx = rnd.Next(0, myStrings.Count);
myTextboxes[i].Text = myStrings[idx]; // Note: we are assuming the two collections have
// the same length
myStrings.RemoveAt(idx);
}
This is very easy to implement and very easy to get right, but it's not terribly efficient (for 16 items, it probably doesn't matter) because your collection is repeatedly resized. For a more efficient approach, first shuffle your strings using the Fisher-Yates shuffle and then just assign the first entry from your shuffled strings to the first textbox, the second to the second, and so on.
You could use a List, and after each assignment you could remove the assigned item from the list. This would prevent duplicates.
http://msdn.microsoft.com/en-us/library/cd666k3e(v=vs.110).aspx
How about removing each item after selecting it?
Try something like
comboBox1.Items.RemoveAt(x);
After adding it and each time your
x = rnd.Next(0, 16);
code will reduce to
x = rnd.Next(0, 15);
until it reaches zero.
A different approach would be after selecting one randomly loop through all the filled ones (or all in general for simpler code) and check if it is already selected. If already selected get a new one until it's different.
For that you could use an array of textboxes (store what you have in an array) and loop through them like so
for(int i=0;i<16;i++)
if(textBoxArray[i].Text==comboBox2.Items[x].toString()){
chosen=true;
}
But removing them from the combobox is much simpler and much faster as code. If you want them to still appear in your combobox you could simultaneously in a List, get your items from that List and remove it from there.
The user will not see anything.
To accomplish this, is fairly simple.
First, you know there are 16 items in total. You don't need to randomize the list but rather, randomize the index that you use to access the item of the list. This part you know.
In order to avoid repeating items, you need to keep a list of indexes that have already been used. Once you have determined an unused index, that's when you need to access your list.
Example:
class Sample
{
List<int> _usedIndexes;
public Sample()
{
_usedIndexes = new List<int>();
}
public int GetRandomIndex(int s, e)
{
Random rnd = new Random();
//Initialize with a random number
int x = rnd.Next(s, e);
//While the index exists in the list of used indexes, get another random number.
while(_usedIndexes.Exists(index => index == x))
{
x = rnd.Next(s, e);
}
//Add the number to the list of used indexes
_usedIndexes.Add(x);
return x;
}
}
Then you simply access the List of names you have with the index you have acquired as follows:
int unusedIndex = GetRandomIndex(0, 16);
User1.Text = comboBox2.Items[unusedIndex].ToString();

Creating a text parse for certain numbers

I have a textbox that I want to use to detect certain numbers (1 - 65) that have a value attached to them for doing math with the value.
Example: When a user types in the numerals "50" I want to associate that with the value 4500 (50, 4500).
So for each number 1 - 65 I want to assign a specific value, then when a user types a number 1 - 65 the program takes the associated value and assigns that to a variable so I can do math.
int lvl50 = 4500;
lvl50 = clvl;
tolvl = clvl - currentexp;
int ttlvl = (tlvl / ptexp) +1;
I'm looking for something like this.
I think you can use a Dictionary<int,int>
var values = new Dictionary<int,int> { { 1, 1000 }, { 50, 4500 } ... };
Then you can get the corresponding value of a number
values[50] // returns 4500
With user input:
var input = int.Parse(textBox1.Text);
var value = values[input];
Or use TryParse and ContainsKey methods to avoid possible exceptions
int input = -1;
if(int.TryParse(textBox1.Text, out input) && values.ContainsKey(input))
{
var value = values[input];
}
One of the methods I can think of is the use of a Dictionary<TKey, TValue> found under System.Collections.Generic;
values.Add(50, 4500);:
var values = new Dictionary<int, int>();
values.Add(50, 4500);
// ... etc
Create a formula/function:
If the numbers 1-65 values have any formula, than you can use create a method that will implement that formula value in order to use it.
Example:
public static int myFormula(int number)
{
return number * 90;
}
Dictionary:
If the following number matched values don't have any visible formula than you can use the Dictionary<int, int> to match them a specific value.
Example:
var myDic = Dictionary<int, int>() {
new { 1, 1 },
new { 50, 4500 },
};
From your code snippet, it looks (to me) like you may be calculating the value in the dictionary based on the key. You could place the logic for calculating the value in a separate method:
private int CalculateValue(int x)
{
// calculate value and return it
}
Then create a dictionary for a particular range of numbers like this:
var dict = Enumerable.Range(1, 65).ToDictionary(x => x, CalculateValue);
To use an element from the dictionary, just reference it using the key:
var matchingValue = dict[50]; // Lookup key 50
If it's possible the key may not exist, you can test for it:
var value = dict.ContainsKey(72) ? dict[72] : -1; // Assign some default value
Using a dictionary like the other answers suggest will work for you, but if your numbers 1-65 have no gaps, you can use a list (or even a simple array). The index of the list will be your input. That will be easier and more efficient than a dictionary.
var values = new List<int> { 1000 , 4500 };
Then you can get the corresponding value of user input:
int input, value;
if(int.TryParse(textBox1.Text, out input)){
if(values.Contains(input){
value = values[input];
}
}

Categories

Resources