1

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.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Jennifer82
  • 151
  • 1
  • 1
  • 6
  • I am hearing sorting at random order for the first time. Also for report, I would prefer to use named attribute and the library like StringTemplate is very good for reporting. Don't store the manipulative data on the report object/class –  Jun 13 '11 at 14:31

5 Answers5

5

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 :)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Agreed. It'll likely make all of your code cleaner, not just the reordering. – Tim Jun 13 '11 at 14:06
  • 1
    Although it's an excellent suggestion, it does not answer OP's question. – Adriano Carneiro Jun 13 '11 at 14:07
  • 1
    @Adrian: It's the approach I'd take to get to where the OP needs to go. – Jon Skeet Jun 13 '11 at 14:08
  • I agree. However, we are assuming there's a relation between `Text[0]`, `Image[0]` and `Explanation[0]`. But there might not be, specially because OP wants to randomize those individually. Maybe OP should clarify. – Adriano Carneiro Jun 13 '11 at 14:10
  • 2
    @Adrian: I'm assuming so based on the sample given. Whenever you have several collections which are always the same length within a class, alarm bells should ring. – Jon Skeet Jun 13 '11 at 14:12
  • @Adrian - notice that the OP's example has the three lists randomized in the same way (2-0-1). Can't be sure without the OP's input, but that made me think that there's definitely a relationship. – Tim Jun 13 '11 at 14:14
  • Yes they all need to randomize in the same way in sync with each other. Sorry if I wasn't so clear on this. I like the idea of a class but the code is already written to include the arrays :-( One of the reasons for the use of arrays is that the data is displayed on web forms. If there is an array of size 4 then there would be something like four rows of data in a table with each column being for one of the above elements. – Jennifer82 Jun 13 '11 at 14:17
  • 1
    @Jennifer82: What's stopping you from *changing* the code that's already there? – Jon Skeet Jun 13 '11 at 14:18
  • @Tim & @Jon Agree with you both. Although it's inherent from our jobs to try to figure out what the "user" really means, I'd still like to see OP's clarification. – Adriano Carneiro Jun 13 '11 at 14:19
1

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 */
}
Community
  • 1
  • 1
Ken Pespisa
  • 21,989
  • 3
  • 55
  • 63
  • 1
    Creating a new `Random` on each call is a bad idea IMO - and I'm personally not a fan of this sort of shuffle, which is inefficient and depends on the key only being extracted once. A Fisher-Yates shuffle is easy to implement, and will perform better. I'd make the `Random` value a parameter and use one of the approaches in http://csharpindepth.com/Articles/Chapter12/Random.aspx to supply it. – Jon Skeet Jun 13 '11 at 14:11
0

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>()
ub1k
  • 1,674
  • 10
  • 14
0

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.

Disposer
  • 667
  • 1
  • 7
  • 23
  • But the problem for me is that all the arrays need to be randomized in the same order as in my example. I am not sure how I could do this with your method. – Jennifer82 Jun 13 '11 at 14:31
0

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.

Gregory A Beamer
  • 16,870
  • 3
  • 25
  • 32
  • This is pretty much what I was think also. The business would like to have the report information presented in different ways each time a user views it. Something like rows of a table that appear in random order on each view. – Jennifer82 Jun 13 '11 at 14:21
  • Are the items the same, meaning text1 relates to image1 and exp1? If so, consider creating a single class and then you can set up a List for the class. There are a couple of ways to randomly sort then. – Gregory A Beamer Jun 13 '11 at 14:43