How to shuffle multiple related arrays? - c#

I have some unusual I need to do. I am wondering if anyone can think of an easy
way to make the change that I need. What I have is a
public class Report
{
public string[] Text { get; set; }
public string[] Image { get; set; }
public string[] Explanation { get; set; }
}
The report class can have any number of Texts, Images and Explanations and the size of each array is always the consistent but maybe be different for each report instance.
What I need to do is to be able to sort the array elements in a random order. So for example I might have
Report.Text[0] = "text0";
Report.Text[1] = "text1";
Report.Text[2] = "text2";
Report.Image[0] = "img0";
Report.Image[1] = "img1";
Report.Image[2] = "img2";
Report.Explanation[0] = "exp0";
Report.Explanation[1] = "exp1";
Report.Explanation[2] = "exp2";
then after sorting
Report.Text[0] = "text2";
Report.Text[1] = "text0";
Report.Text[2] = "text1";
Report.Image[0] = "img2";
Report.Image[1] = "img0";
Report.Image[2] = "img1";
Report.Explanation[0] = "exp2";
Report.Explanation[1] = "exp0";
Report.Explanation[2] = "exp1";
Can anyone think of a simple way to do this? All I can think of is that I need to create a
new temporary object of the same size and do some kind of swapping. But I am not sure how
to randomize. The reason I am asking is just in case someone has had this need in the past.

I would strongly recommend that you refactor this to create a single class to encapsulate the { Text, Image, Explanation } tuple. At that point, the code will be cleaner and it'll be trivial to reorder the values. Heck, you may not even need a Report type at that point... you may just be able to have a List<ReportItem> or whatever. You'd only need a separate Report type if you wanted to add extra behaviour or data to tie things together.
(As an aside, I hope you don't really have public fields for these to start with...)
If you then have a question around shuffling a single collection, a modified Fisher-Yates shuffle is probably the easiest approach. You could do this with the multiple arrays as well, but it wouldn't be nice - and would have to be specific to Report... whereas you could easily write a generic Fisher-Yates implementation based on IList<T>. If you search on Stack Overflow, you should easily be able to find a few existing implementations :)

If you choose to change your class to the following:
public class Report
{
public string Text { get; set; }
public string Image { get; set; }
public string Explanation { get; set; }
}
You could then do this using an extension method:
(See answer on this SO question)
Then call it this way:
List<Report> reports = new List<Report> { /* create list of reports */ }
Random rnd = new Random();
foreach (Report r in reports.Shuffle(rnd)) {
/* do something with each report */
}

Why don't you create a class
public class Report
{
public string Text { get; set; }
public string Image { get; set; }
public string Explanation { get; set; }
}
and then create a List of those objects and manage it through the list properties:
IList<Report> yourList = new List<Report>()

Here is my solution
class StringWrapper
{
public int Index;
public string Str;
}
public string[] MixArray(string[] array)
{
Random random = new Random();
StringWrapper[] wrappedArray = WrapArray(array);
for (int i = 0; i < wrappedArray.Length; i++)
{
int randomIndex = random.Next(0, wrappedArray.Length - 1);
wrappedArray[i].Index = randomIndex;
}
Array.Sort(wrappedArray, (str1, str2) => str1.Index.CompareTo(str2.Index));
return wrappedArray.Select(wrappedStr => wrappedStr.Str).ToArray();
}
private StringWrapper[] WrapArray(string[] array)
{
int i = 0;
return array.Select(str => new StringWrapper {Index = ++i, Str = str}).ToArray();
}
Then you can call MixArray for each Report object for each property you wand to randomize.

I am not sure I am fond of this direction, but ...
To do exactly what you ask (the law, not the spirit of the law), you will have to add additional arrays and pull items over. In addition, for each array, you will need a List or similar to store the items you have already randomly pulled over. After that, things are simple. Use the Random class to create random numbers, check if the item has already been moved (using the List), if not store the result in the new array/list, add the value to your List to make sure you do not move the same item twice. Once everything is moved, set this new array to the old array.
Now, what is the business reason for randomizing? That might affect whether or not this is a good idea.
ADDED:
After examination of skeet's response, here is a way to solve this if you can use the following type of class:
public class Report {
public string Text { get; set; }
public string Image { get; set; }
public string Explanation { get; set; }
}
Here is one "down and dirty" type of sort:
private static SortedList<int, Report> SortRandomly(List<Report> reports)
{
Random rnd = new Random((int)DateTime.Now.Ticks);
List<int> usedNumbers = new List<int>();
SortedList<int, Report> sortedReports = new SortedList<int, Report>();
int maxValue = reports.Count;
foreach(Report report in reports)
{
bool finished = false;
int randomNumber = 0;
//Get unique random (refactor out?)
while(!finished)
{
randomNumber = rnd.Next(0, maxValue);
if(!usedNumbers.Contains(randomNumber))
{
finished = true;
usedNumbers.Add(randomNumber);
}
}
sortedReports.Add(randomNumber, report);
}
return sortedReports;
}
Note, you can also work to keep the sort in order and randomly picking from the original list, which means you can, in theory, keep it as a list.
private static List<Report> SortRandomly(List<Report> reports)
{
Random rnd = new Random((int)DateTime.Now.Ticks);
List<Report> outputList = new List<Report>();
List<int> usedNumbers = new List<int>();
int maxValue = reports.Count-1;
while(outputList.Count < reports.Count)
{
int randomNumber = rnd.Next(0, maxValue);
if(!usedNumbers.Contains(randomNumber))
{
outputList.Add(reports[randomNumber]);
}
}
return outputList;
}
Even better, consider sorting the list of numbers first and then grabbing the reports, in an order manner. Once again, the above are down and dirty implementations and using the specific requirements will certainly refine the algorithms.

Related

How to create a Class List with different numbers of inputs in C#

I'm working on my first real c# project and I have faced a problem with my way of creating List based on a Class, which I have no idea how to solve.
I’m trying to write some code, which takes an input file (txt/csv) of multiple constructions with multiple layers, put it into my program, and later write the constructions into a new txt/csv file.
When having the same numbers of layers, it works fine. But when the constructions have different numbers of layers it causes trouble and I get a “System.IndexOutOfRangeException”.
My question is: Can I make the Class which I’m basing my List on, dynamic (I don’t know if it is the technical term), so it work with different numbers of inputs? Both when Adding the construction to the program and when I write it to a new file?
My code is:
class Program
{
static void Main(string[] args)
{
// Filepath for the input and output file
string filePathIn_constructions = #"C:\Library\Constructions.txt";
string filePathOut = #"C:\Library\EPlus_Inputfile.txt";
// Creating a list of constructions based on the class. The list is made from the file "filePathIn_constructions"
List<Construction> allConstructions = new List<Construction>();
List<string> lines_constructions = File.ReadAllLines(filePathIn_constructions).ToList(); // add it to a list
// Adding all the data from the fil to the variable "allConstructions"
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
newConstruction.Layer1 = entries[2];
newConstruction.Layer2 = entries[3];
newConstruction.Layer3 = entries[4];
newConstruction.Layer4 = entries[5];
newConstruction.Layer5 = entries[6];
allConstructions.Add(newConstruction); // Add it to our list of constructions
}
List<string> output = new List<string>();
foreach (var x in allConstructions) // Printing the new
{
output.Add($"{x.EIndex}, {x.Name}, {x.Layer1}, {x.Layer2}, {x.Layer3}, {x.Layer4}, {x.Layer5}");
}
File.WriteAllLines(txtFilePathOut, output);
}
}
My Class for the Constructions is
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public string Layer1 { get; set; }
public string Layer2 { get; set; }
public string Layer3 { get; set; }
public string Layer4 { get; set; }
public string Layer5 { get; set; }
}
An example of a input/output file could be
Construction,ConcreteWall,Concrete;
Construction,Brickwall1,Birck,Isulation,Brick;
Construction,Brickwall2,Birck,AirGap,Isulation,Brick;
Construction,Wood/Concrete Wall,Wood,Isulation,Concrete,Gypson;
Construction,Wood Wall,Wood,AirGap,Gypson,Isulaiton,Gypson;
I hope someone can help. Thanks.
Edit: I have to be able to excess the construction Name seperatly, because i'm using it to do some sorting of the.
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public List<string> Layers { get; set; } = new List<string>();
}
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
for (int i=2; i < entries.Length; i++) {
newConstruction.Layers.Add(entries[i]);
}
allConstructions.Add(newConstruction);
}
foreach(var x in allConstuctions) {
File.AppendAllText(output, $"{x.EIndex}, {x.Name}, {string.Join(", ", x.Layers)}");
}
It is because you are trying to reach a cell of an array that doesn't exist (documentation)
In your input/output file you have lines that have between 3 and 7 values, and you are building an array entries out of those values. This means that you will have arrays with between 3 and 7 cells
The problem is that right after creating those arrays you try to access on every array the cells 0, 1, 2... up to the 7th, even for arrays that have only 3 cells!
What you could do to fix this in a simple way is to add columns to have the same number of separator on each lines (you defined the separator of your lines as column with line.Split(',')). This way, every arrays that you will create will always have 7 cells, even if the value inside is null

Create Intersect of HashSet and remove Items from class structure with extracted ID's

lets assume I have the following classes:
public class ServiceStatistics
{
public string LocalId { get; set; }
public string OrganizationId { get; set; }
public List<StatisticElements> Elements { get; } = new List<StatisticElements>();
}
public class StatisticElements
{
public string StatisticId { get; set; }
public string Type { get; set; }
public string ServiceName { get; set; }
}
I retrieve such ServiceStatistics by a soap service and I use serialization/deserialization.
Each ServiceStatistics contains a set of StatisticElements. I also have a static list of StatisticElements-ID's which are relevant for calculation. All other incoming StatisticElements-ID's can be dropped. I need to do this on my side
because the SOAP Service does not support selecting specific StatisticElements-ID's
So I have generated a static Class with a HashSet:
public static class RelevantDutyPlans
{
private static HashSet<int> relevantDutyPlans;
static RelevantDutyPlans()
{
// only a subset of the original ID's
relevantDutyPlans = new HashSet<int>()
{
530,
1150,
1095,
};
}
public static HashSet<int> GetRelevantDutyPlans()
{
return relevantDutyPlans;
}
public static bool Contains(int planId)
{
return relevantDutyPlans.Contains(planId);
}
// Extracts all DutyPlans which are relevant (HashSet) for validation from
// the incoming data
public static List<int> ExtractRelevantDutyPlans(List<int> planIds)
{
var relevantPlans = new HashSet<int>(planIds);
relevantPlans.IntersectWith(relevantDutyPlans);
return relevantDutyPlans.ToList();
}
}
So my thought was, to create an Intersect like this:
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
List<int> incomingIds = new List<int>();
foreach(var item in statistics)
{
foreach(var element in item.Statistic)
{
incomingIds.Add(int.Parse(element.StatisticId));
}
}
List<int> extract = RelevantDutyPlans.ExtractRelevantDutyPlans(incomingIds);
So now I have a List of ID's which are relevant for further processing. What I want to achieve is to remove all class elements "StatisticElements" with "StatisticId" not contained in the the extract list generated above.
Any ideas?
Any help is very appreciated
How about a little bit different approach. Simply remove irrelevant plans right away!
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
foreach(var item in statistics)
{
item.Elements.RemoveAll(x => !RelevantDutyPlans.Contains(int.Parse(x.StatisticId)));
}
Now you are only left with the relevant once.
Hope you can use selectMany to flatten the collection and proceed the filter.
var filteredItems = statistics.SelectMany(s => s.Elements)
.Where(s => extract.Contains(Convert.ToInt32(s.StatisticId)))
.ToList();
You could also use LINQ to create a new List<> if you need to keep the original statistcs intact - e.g. if you might run multiple plans against it.
var relevantStatistics = statistics.Select(s => new {
LocalId = s.LocalId,
OrganizationId = s.OrganizationId,
Elements = s.Elements.Where(e => !RelevantDutyPlans.Contains(Convert.ToInt32(e.StatisticId))).ToList()
});
Since ServiceStatistics doesn't provide for construction, I return an anonymous object instead, but you could create an appropriate DTO class.

c# array looping with itself

Why I'm doing this:
So I'm trying to make an application for a game called clash royale, after winning the games there you get a "random" chest, which is actually not random... When you create your account you get a digit assigned to you from 0 to 239, and after that it follows a pattern for the chest drops. The applciation I'm making would take a user's entries and compare it to the pattern, thus being able to predict how soon the next chests of a higher quality would drop.
The help I need with the code:
Is it possible to make an array kind of... loop within itself.. So for example when going through the array in a loop, if "i" is 239, then adding +1 would take it back to the beginning, or #0 (239 not necessarily being the limit).
The class (and it's container that I want to loop):
class Chest
{
public int ID { get; set; }
public string Type { get; set; }
public Chest()
{
}
public Chest(int id, string type)
{
ID = id;
Type = type;
}
}
class ChestContainer
{
private Chest[] ChestList = new Chest[240];
public int Count { get; set; }
public ChestContainer(int size)
{
ChestList = new Chest[size];
}
public void Add(Chest chest)
{
ChestList[Count++] = chest;
}
public Chest Get(int index)
{
return ChestList[index];
}
}
Also wouldn't mind any tips to improve my class / container class, at the moment this is what I've been doing for pretty much my entire "career" as this is what we were thought in uni (minus the string override for the class).
You could use Modulo % in order to get a loop kind of thing.
If you replace the Container.Add method with the one below, the index will be "reset" (for lack of better words).
public void Add(Chest chest)
{
ChestList[Count++%(ChestList.Length)] = chest;
}
After updating the method, if you want an example, you can try the code below:
var container = new ChestContainer(240);
for (int i = 0; i < 1000; i++)
container.Add(new Chest(i, $"{i}"));
Edit In order to have the Get method working as well, modifying it as mentioned below will ensure your container works as expected:
public Chest Get(int index)
{
return ChestList[index%(ChestList.Length)];
}
To test it out, you can use the code below:
var container = new ChestContainer(240);
for (int i = 0; i < 1000; i++)
{
container.Add(new Chest(i, $"{i}"));
var value = container.Get(i);
}
You can overload the [] operator to define it's behaviour.
Something like this:
public static Chest operator [] (int index) {
return ChestList[index%240];
}
public Chest Get(int index)
{
return ChestList[index%240]; //put your limit here
}
How it works: % is the modulo operator.
It returns the remainder of a devision.
Example:
5/2 = 2, remaining 1
=> 5%2 = 1
In your case, when numbers higher than 239 are entered, with modulo it just wraps around.

Pull values from two different array lists?

I need help on how to make a for loop. Then using the i value as both arrayList index numbers.
My directions say
-Create a loop that uses either the length of the ArrayList or the element to dictate the number of times the loop will run.
-Each time the loop runs, pull another element of the ArrayList and output it in a
meaningful fashion to the console.
static void Main(string[] args)
{
nameArrayLists();
}
public static void nameArrayLists() {
ArrayList teamLists = new ArrayList() {"Cloud 9"};
teamLists.Add("Fnatic");
teamLists.Add("SKT T1");
teamLists.Add("Flash Wolves");
teamLists.Add("EDG");
ArrayList region = new ArrayList() { "North America" };
region.Add("Europe");
region.Add("Korea");
region.Add("Taiwan");
region.Add("China");
So after this, how do I make a for loop using i as both arrayList index numbers? The end result should be like "Fnatic is in the Europe region, cloud 9 is the in north america region" etc.
If i understand correctly, you are looking for something like this:
if (teamLists.Count == region.Count)
{
for (int i = 0; i < teamLists.Count; i++)
{
Console.WriteLine("{0} is in the {1} region", teamLists[i], region[i]);
}
}
else
{
Console.WriteLine("Items in the collections are not matching");
}
Keep in mind: Both the list are in the same order. which means for any i the value at corresponding index should be matching.
There is a best option for you:
Create a simple class with two properties(more if needed), then an overrided ToString() method which is for displaying the text as you described. and then use a List as like the following;
Class definition:
public class TeamNRegion
{
public string TeamName { get; set; }
public string RegionName { get; set; }
public int Id { get; set; }
public override string ToString()
{
return String.Format("{0} is in the {1} region", this.TeamName, this.RegionName);
}
}
Then you can create the list of this class objects like this:
List<TeamNRegion> teamRegionList = new List<TeamNRegion>()
{
new TeamNRegion(){Id=1,TeamName="Fnatic",RegionName="Europe"},
new TeamNRegion(){Id=10,TeamName="SKT T1",RegionName="Korea"},
new TeamNRegion(){Id=11,TeamName="Flash Wolves",RegionName="Taiwan"},
new TeamNRegion(){Id=12,TeamName="EDG",RegionName="China"},
};
// Print the result like this
foreach (TeamNRegion team in teamRegionList)
{
Console.WriteLine(team.ToString());
}

Variable not returning actual values

I want correctly return some variables (arrays)
kazkas.Ads[n]; (n = how many ads are)
kazkas.Ads[n].id;
kazkas.Ads[n].Days[m].Stats.Clicks; // every day have his own clicks
kazkas.Ads[n].Days[m].Stats.Impresons; // every day have his own impresions
from this method and use these variables in other class.
public static void GetAdsStats(string Ticket, DateTime start, DateTime end, int CamId)
{
var client = new CampaignStatsServiceClient();
var id = new CampaignIdFilter();
id.CampaignId = CamId;
var statsdata = new GetAdStatsData();
var kazkas = new Campaign();
kazkas = client.GetAdStats(Ticket, new GetAdStatsData
{
IdFilter = id,
StartDate = start,
EndDate = end
});
long AllClicks = 0;
long AllImpresions = 0;
int reklamos = kazkas.Ads.Length;
long[] statistikaClikai = new long[reklamos];
long[] statistikaImpresions = new long[reklamos];
for (int i = 0; i < reklamos; i++)
{
int dienos = kazkas.Ads[i].Days.Length;
for (int lop = 0; lop < dienos; lop++)
{
AllClicks = AllClicks + kazkas.Ads[i].Days[lop].Stats.Clicks;
AllImpresions = AllImpresions + kazkas.Ads[i].Days[lop].Stats.Impressions;
}
statistikaClikai[i] = AllClicks;
statistikaImpresions[i] = AllImpresions;
}
}
I know that void type can't return anything, but this how I know that my method works ( from debugging). Like you see I was trying do that with for loop. Here i have 9 Ads and every ad have one day.
Like I says I want return every Ads id[in array], and every days.stats.impresions and days.stats.click
how can I do that ? Ore how return more variables/arrays from method to other class, I am using webservises, so i cant use database ore something like that.
As can be seen by the downvotes of the question, you need to design the return value and then code against it.
Your query almost does it (now):
kazkas.Ads[n]; (n = how many ads are)
kazkas.Ads[n].id;
kazkas.Ads[n].Days[m].Stats.Clicks; // every day have his own clicks
kazkas.Ads[n].Days[m].Stats.Impressions; // every day have his own impressions
Your existing code show this should be expanded to include:
kazkas.Ads[n].Total.Clicks;
kazkas.Ads[n].Total.Impressions;
So now you're ready to design. First you want a Stat Class that just contains CLicks and Impressions:
public class Stat
{
public long Impressions { get; set; }
public long Clicks { get; set; }
}
An optimisation here may be to use a struct, but I won't go into that.
As you currently have defined it each Day has just a Stats property:
public class DayStat
{
public Stat Stats { get; set; }
}
Now finally we can define the top level AdStat:
public class AdStat
{
public int id { get; set; }
public DayStat Day[];
public Stat Total { get; set; }
}
Etc... There's further issues here, such as ensuring arrays are created and Stat instances are never null (which is why making some of these classes structs is an option). But I'm really a VB programmer so I'll stop here before I get caught typing crap into the SO IDE :-)
Create a class or struct with members you need
public class Stat
{
public int Id { get; set; }
public long Clicks { get; set; }
...
}
Change the signature of your method from void GetAdsStats to IEnumberable<Stat> GetAdsStats and either return a collection of stats or use yield keyword to return the stat object.
Also if you do not want your method to return anything (return type void) do not use a name starting with Get.
Example:
public static IEnumerable<Stat> GetAdsStats(...)
{
...
var statList = new List<Stat>();
for (int i = 0; i < reklamos; i++)
{
var stat = new Stat();
statList.Add(stat);
int dienos = kazkas.Ads[i].Days.Length;
for (int lop = 0; lop < dienos; lop++)
{
AllClicks = AllClicks + kazkas.Ads[i].Days[lop].Stats.Clicks;
AllImpresions = AllImpresions + kazkas.Ads[i].Days[lop].Stats.Impressions;
}
stat.Clicks = AllClicks;
stat.Impression = AllImpresions;
}
return statList;
}
Change your void to the type you want to return, say Campaign, and return the appropriate variable. The variables you define in your method, only live in your method and are not accessible from another method or class.

Categories