0

I am trying to work with Random array list and here is the code

private static string getSearchEngine()
{
    ArrayList url = new ArrayList();
    url.Add("www.google.com");
    url.Add("www.bing.com");
    url.Add("www.yahoo.com"); 
    Random rnd = new Random();
    int i = rnd.Next(0, url.Count);
    return url[i].ToString();
}

private static void DoMore_Functionality_Using_engName()
{
    for(int i = 0; i < 300; i++)
        string engName = getSearchEngine();
}

I want each search engine to be used only 100 times. If I use the above code, the randomizer could pick any one of them more than 100 times. How can I modify the code to do this?

Thanks Rashmi

aloisdg
  • 22,270
  • 6
  • 85
  • 105
Rashmi
  • 121
  • 2
  • 14
  • 1
    So in short: a random search engine should be used and it should be used 100 times in total so for example: google 50, bing 25 and yahoo 25. Or they should all execute 100 times, so a total of 300? – Max Mar 18 '14 at 15:08
  • @MaxMommersteeg: My reading would be the later. They want each search engine to be used only 100 times. – Matt Burland Mar 18 '14 at 15:09
  • You have 3 engines, from which you want to randomly generate 300 engines, at most 100 of each? I think I have a non-random solution for you... – decPL Mar 18 '14 at 15:09
  • What if you used the `i % 3`th engine each time you need one? This would be well distributed, albeit not random. – Matthias Meid Mar 18 '14 at 15:09
  • @MaxMommersteeg, after execution of DoMore_Functionality_Using_engName(), google =100, bing=100 and yahoo=100 – Rashmi Mar 18 '14 at 15:10
  • First, don't use `ArrayList`. The generic `List` is much more useful. – Matt Burland Mar 18 '14 at 15:10
  • You could simply use a Dictionary Where String is the url of the engine and int is the times being called... – Max Mar 18 '14 at 15:11
  • 4
    Create a list of 300 `int`s with 100 0's, 100 1's and 100 2's. Then shuffle that list (use Fisher-Yates, for example). Now iterate over that list taking the engine at the index on each iteration. – Matt Burland Mar 18 '14 at 15:12
  • @MaxMommersteeg: That was my first thought, but it won't be guaranteed to exit if you have to check whether the count is 100 (or 0 if you count backwards) and try again if it is. – Matt Burland Mar 18 '14 at 15:13
  • @MattBurland You could iterate through the Dictionary values(integers) and Sum them, if total is equal to 300, then quit the for loop. – Max Mar 18 '14 at 15:14
  • @MaxMommersteeg: I think you are missing my point. Let's say engine 1 is 100, engine 2 is 100 and engine 3 is 99. You pick a random engine, and it's engine 1, it's already at 100, so you try again. This time you get engine 2, your try again and get engine 1 again. This can continue forever in theory - for a truly random number generator. In practice, it could take a lot longer than expected. – Matt Burland Mar 18 '14 at 15:18
  • @MattBurland Ah okay, thanks for the explanation I get your point now. If one hit 100 already it should be excluded and the random range should be variable, so it will always pick the one left over at the end. – Max Mar 18 '14 at 15:21
  • @MattBurland, I did this List engines = new List(Enumerable.Repeat("www.google.com", 2) .Concat(Enumerable.Repeat("www.bing.com", 2))); string[] array = engines.ToArray(); Random random = new Random(); for (int i = array.Length; i > 1; i--) { int j = random.Next(i); T tmp = array[j]; array[j] = array[i - 1]; array[i - 1] = tmp; } how can I make this aynchronous? – Rashmi Mar 18 '14 at 18:41
  • @Rashmi: That's an entirely different question. So start a new question and explain what you mean by asynchronous. How do you want to be able to call this and in what context. And don't post code in comments, it's completely unreadable. – Matt Burland Mar 18 '14 at 19:03

3 Answers3

3

I think you can do this by defining your collection outside of your method.Add each search engine for 100 times to your list, then each time you pick a random engine, remove that item from your collection.Something like this:

static List<string> engines = new List<string> (Enumerable.Repeat("www.google.com", 100)
        .Concat(Enumerable.Repeat("www.bing.com", 100))
        .Concat(Enumerable.Repeat("www.yahoo.com", 100)));

static Random rnd = new Random();
private static string getSearchEngine()
{
    int i = rnd.Next(0, engines.Count);
    var temp = engines[i];
    engines.RemoveAt(i);
    return temp;
}
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • 1
    You should really use a proper shuffling algorithm instead, as much more efficient options are not much more work. – Servy Mar 18 '14 at 15:19
  • I really like your proposition. I would write something like you. Fill the list with 300 item and [shuffle it](http://stackoverflow.com/questions/2301015/shuffle-listt). – aloisdg Mar 18 '14 at 15:19
  • 2
    Removal from `List` is O(n), you do it *n* times, so overall you are O(n2). Versus an O(n) shuffle and then iterating through that list. – Matt Burland Mar 18 '14 at 15:23
1

Here's a way to do it with a shuffle:

List<string> urls = new List<string> { "www.google.com", "www.bing.com", "www.yahoo.com" };
List<int> randomIdx = new List<int> (Enumerable.Repeat(0, 100)
                                     .Concat(Enumerable.Repeat(1, 100))
                                     .Concat(Enumerable.Repeat(2, 100)));

Random r = new Random();
// This is the Fisher-Yates Shuffle
// see: http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
for (int i = randomIdx.Count - 1; i > 0; i--) 
{
    int j = r.Next(0,i);
    int tmp = randomIdx[i];
    randomIdx[i] = randomIdx[j];
    randomIdx[j] = tmp;
}

// Iterate through our random list and write each one out
for (int i = 0; i < randomIdx.Count; i++)
{
    Console.WriteLine(urls[randomIdx[i]]);
}

So to work this into your DoMore_Functionality_Using_engName() function, you would have an initialization in your class that sets up the list and shuffles it and then sets an index property that will keep track of where you are in the list. Then in DoMore_Functionality_Using_engName(), you would get the next result and then increment the index.

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
0

You need to associate each search engine with a counter of how many times it was used. To do so, you'll need to keep the collection in memory and not create it each time.

First, create a class to represent your data:

class SearchEngine
{
    public SearchEngine(string url, int counter)
    {
        Url = url;
        Counter = counter;
    }

    public string Url { get; set; }
    public int Counter { get; set; }
}

Second, create a collection of your engines as a private field somewhere:

private Random _random = new Random();
private IEnumerable<SearchEngine> _engines = new[]
{
    new SearchEngine("www.google.com", 0),
    new SearchEngine("www.bing.com", 0),
    new SearchEngine("www.yahoo.com", 0)
};

And the method to get a random search engine should be like this:

private string GetRandomSearchEngine()
{
    var searchEngine = _engines
    // randomize the collection
        .OrderBy(x => _random.Next())
    // skip items with invalid counter
        .SkipWhile(t => t.Counter >= 100)
        .First();
    // update the counter
    searchEngine.Counter++;
    return searchEngine.Url;
}

EDIT

As @Matt Burland suggests using _random.Next() is a better approach to randomizing the collection. He also raises another valid point: what happens when all the counters reach 100?

In such case, the code above will throw an exception (namely, the First() method). Assuming you don't want that you can use FirstOrDefault() and check for null. If the returned item is null then all counters have reached 100.

private bool TryGetRandomSearchEngine(out string url)
{
    var searchEngine = _engines
    // randomize the collection
        .OrderBy(x => _random.Next())
    // skip items with invalid counter
        .SkipWhile(t => t.Counter >= 100)
        .FirstOrDefault();
    if(searchEngine != null)
    {        
        // update the counter
        searchEngine.Counter++;
        url = searchEngine.Url;
        return true;
    }
    url = String.Empty;
    return false;
} 

And somewhere in your code you can use the method above like this:

string searchEngineUrl;
while(TryGetRandomSearchEngine(out searchEngineUrl))
{
    PerformSearch(searchEngineUrl, searchTerms);
}
RePierre
  • 9,358
  • 2
  • 20
  • 37
  • 1
    Guids are unique, not necessarily random, and should not be used in place of random numbers. – Servy Mar 18 '14 at 15:20
  • @Servy, `Guid`s aren't used as random numbers but rather for ensuring `random order`. – RePierre Mar 18 '14 at 15:25
  • 1
    Which is a task it is not capable of ensuring. You're using it as if it were a random number, which you're using to try and generate a random number. Since your "random" number is not in fact random, your "random" ordering is not in fact random. – Servy Mar 18 '14 at 15:26
  • I also fail to see why you are using a `Guid` versus just `Random.Next()`. Otherwise, it's kind of a clever solution, but you should probably use `FirstOrDefault` and check for null which would indicate that all the counters are `>= 100`. – Matt Burland Mar 18 '14 at 19:06