0

I want to get all numbers multiple of 3 or 5 below given number in the fastest way. So I did the below code:

double limit = number / 5;
for (var i = 0; i <= number / 3; i++)
{
    list.Add(i * 3);
    if (i <= limit && ((i * 5) % 3 != 0))
    {
        list.Add(i * 5);
    }
}

But the resulted list will not be sorted in ascending order. If number = 11, list contains

0, 3, 5, 6, 10, 9

instead of

0, 3, 5, 6, 9, 10

To get sorted list, I could use a simple iteration like:

for (var i = 0; i <= number; i++)
{
    if (i % 3 == 0 || i % 5 == 0)
    {
        list.Add(i);
    }
}

But the main purpose of the exercise is to right the fastest algorithm.

Am I missing something in the first algorithm or should I keep the second one?

Mhd
  • 2,222
  • 1
  • 15
  • 45
  • 3
    Addition is faster than multiplication or division (modulo). And integer data types are faster, and more appropriate, than floating point. You don't make clear whether a sorted result is a requirement, or just a pleasantry. A repeating pattern of deltas can be used to add to a running value to generate the next number in the series. – HABO Apr 28 '17 at 20:05
  • 1
    Sounds like some got **Fizz Buzz** – Mad Myche Apr 28 '17 at 20:22
  • 10 is not a number smaller than 10 that is divisible by 3 or 5. – Eric Lippert Apr 28 '17 at 20:48
  • I gonna change 10 to 11 @EricLippert – Mhd Apr 28 '17 at 20:52
  • @HABO That was the point: addition is faster than modulo. – Mhd Apr 29 '17 at 13:27

4 Answers4

3

Here is my implementation although it might not be the fastest in terms of BigO notation.

    static void Main(string[] args)
    {
        int number = 35;
        List<int> list = new List<int>();
        int count3 = 0;
        int count5 = 0;
        int step3 = 3;
        int step5 = 5;
        while (count3 < number || count5<number )
        {
            if ((count3 + step3) <= (count5 + step5))
            {
                count3 += step3;
                if (count3 <= number)
                {
                    list.Add(count3);
                }
            }
            else
            {

                count5 += step5;
                if (count5 <= number)
                {
                    list.Add(count5);
                }
            }

        }
        foreach (var l in list)
        {
            Console.WriteLine(l.ToString());
        }
        Console.ReadLine();

    }
ThomW
  • 121
  • 5
  • Liked the Algorithm, which jumps the numbers and process only the numbers which divide by 3 or 5, though still O(N), but N is fairly reduced as numbers are omitted, +1. In real applications though using Computing power or look up data structures are more widely harnessed – Mrinal Kamboj Apr 28 '17 at 21:16
  • You can optimize more the algorithm by changing `while (count3 < number || count5 – Mhd Apr 28 '17 at 23:32
  • @Mhd Tthank you for the improvement. That cleans it up a lot. – ThomW May 01 '17 at 14:03
2

For 3 and 5 there is circular dependence every 15 (3*5=15) that is why we can just do something like this:

static void Main()
{
    int[] deltas= { 3,2,1,3,1,2,3 };

    int number = 30;

    List<int> result = new List<int>();

    int j = 1;
    for(int i = deltas[0]; i<=number; i+=deltas[j++%deltas.Length])
    {
        result.Add(i);
    }

    foreach(int i in result)
        Console.Write(i+", ");
}

Update:

To find circular point we need to calculate Least Common Multiple. Now to find all deltas to that point we just need resolves original problem in less optimal way and subtracts elements from each other.

More generic version:

static void Main()
{
     foreach(int i in multi(30, new []{2,3,4,5,7} ))
        Console.Write(i+", ");
}

static List<int> multi(int max, int[] divs)
{
    int[] deltas = calcDeltas(divs);

    List<int> result = new List<int>();

    int j = 1;
    for(int i = deltas[0]; i<=max; i+=deltas[j++%deltas.Length])
    {
        result.Add(i);
    }

    return result;
}

static int[] calcDeltas(int[] divs)
{
    long max = 1;
    foreach(int div in divs)
        max = lcm(max,div);

    List<long> ret = new List<long>();

    foreach(int div in divs)
    {
        for(int i=div; i<=max; i+=div)
        {
            int idx = ret.BinarySearch(i);
            if (idx < 0)
            {
                ret.Insert(~idx, i);
            }
        }
    }

    for(int i=ret.Count-1; i>0; i--)
        ret[i]-=ret[i-1];

    return ret.ConvertAll(x => (int)x).ToArray();
}

static long gcf(long a, long b)
{
    while (b != 0)
    {
        long temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

static long lcm(long a, long b)
{
    return (a / gcf(a, b)) * b;
}

Update 2:

I do some tests for provided solutions (without Eric's solution). I modify some functions to give exact same result (more in description). Tested on i7-3770K@3.5GH.

Username - description
Time 1 - Average time, loops = 100, number = 10000000
Time 2 - Total time, loops = 100000, number = 10000
Time 3 - Total time, loops = 1000000, number = 100
CPU Usage - in percentage

Mrinal Kamboj - with OrderBy as suggested to preserve correct order
434ms, 85447ms, 348600ms, 70%

Mrinal Kamboj - with OrderBy as suggested to preserve correct order, without ToList() instead to materialize query done ForAll(x=>{})
136ms, 51266ms, 273409ms, 80%

Mrinal Kamboj - without AsParallel
154ms, 14559ms, 1985ms, 13%

Mhd - second solution
69ms, 5791ms, 879ms, 13%

ThomW - added condition to prevent duplicates
34ms, 2398ms, 521ms, 13%

Logman - with precalculated deltas for 3,5 as original answer
43ms, 3498ms, 654ms, 13%

Logman - generic solution
47ms, 3529ms, 1270ms, 13%

Logman + HABO - with precalculated deltas for 3,5
37ms, 2655ms, 501ms, 13%

Logman + HABO - generic solution
37ms, 2701ms, 1149ms, 13%

HABO - Unrolled solution (see the comment)
32ms, 2072ms, 464ms, 13%

Test code

Logman
  • 3,474
  • 1
  • 22
  • 32
  • Good idea. It's like @Me.Name answer. Could you please explain to me how to find `deltas` list? – Mhd Apr 28 '17 at 22:58
  • @Mhd : Logman may have a smarter way, but you can pick your two numbers p & q, then keep a running counter and reset the counter everytime you hit valid multiple. So, 1,2,3 (3), 1,2 (5), 1 (6), 1,2,3 (9). Kinda hard to write out. Deltas is the number you need to add to the previous to get a new multiple. Do this until you hit p * q; – MrZander Apr 28 '17 at 23:09
  • Removing the modulo from the loop is possible, e.g. `do { foreach ( int delta in deltas ) { i += delta; if ( i > max ) break; result.Add( i ); } } while ( i < max );`. That might be slightly faster for larger values of `max`. Without examining the IL (and resulting machine code) it's hard to say. And may depend on the underlying hardware. – HABO Apr 29 '17 at 14:54
  • @HABO Base on [this answer](http://stackoverflow.com/a/365658/2483065) I disagree but as you said there is no way of actually telling without checking IL (or assembly). – Logman Apr 29 '17 at 16:19
  • 1
    @HABO I do tests and your optimization is 20% faster then my code. And it proven me been wrong. – Logman Apr 29 '17 at 16:49
  • Have you tried unrolling the inner loop? `while ( true ) { i += 3; if ( i > max ) break; result.Add( i ); i += 2; if ( i > max ) break; ... }` – HABO Apr 30 '17 at 13:52
  • @HABO tested it's the fastest solution. – Logman Apr 30 '17 at 14:39
1

I want to get all numbers multiple of 3 or 5 below given number in the fastest way.

Easily done.

private static readonly int [][] lookup = { 
  {}, // 0
  {0}, // 1
  {0}, // 2
  {0}, // 3
  {0, 3}, // 4
  {0, 3}, // 5
  {0, 3, 5}, // 6
  {0, 3, 5, 6}, // 7
  {0, 3, 5, 6}, // 8
  {0, 3, 5, 6}, // 9
  {0, 3, 5, 6, 9}, // 10
  ... and so on; fill out the table as far as you like
};

Now your algorithm is:

static int[] Blah(int x) { return lookup[x]; }

The fastest algorithm is almost always a precomputed lookup table. Solve the problem ahead of time and save the results; your program then just looks up the precomputed result. It does not typically get faster than that; how could it?

Now is it clear that you are not actually looking for the fastest algorithm? What algorithm are you actually looking for?

Eric Lippert
  • 612,321
  • 166
  • 1,175
  • 2,033
  • 1
    Maybe the fastest that can give the answer for **any** number ? – Rafalon Apr 28 '17 at 20:56
  • I'm not looking for a particular algorithm. I just want a confirmation that my second algorithm will be the fastest option in case I want to preserve the order. – Mhd Apr 28 '17 at 20:58
  • @Rafalon Absolutely, that's what I'm looking for. If `numer=1000000...`, i should keep building my lookup table!!! – Mhd Apr 28 '17 at 21:00
  • @Rafalon: What number do you want? Just extend the table to cover that number. – Eric Lippert Apr 28 '17 at 21:05
  • 1
    @Mhd: We have just seen that your second algorithm cannot possibly be the fastest; I promise that mine is faster. – Eric Lippert Apr 28 '17 at 21:06
  • @Mhd: If you want to know which of two things is faster, *run them* and you'll know. – Eric Lippert Apr 28 '17 at 21:07
  • But in order to cover *any* `int` number, you would need to fill your table up to 2,147,483,647 and this is not something you would do manually. – Rafalon Apr 28 '17 at 21:09
  • 3
    @Rafalon: So you don't actually want the fastest algorithm. You want a slower algorithm that is *more convenient for you*. The point of this silly answer is to make you think about what you really want when you ask for the "fastest" something. You never want the fastest thing. You want a thing that is *adequately fast with a low implementation cost*. In line-of-business programming we have to constantly make tradeoffs between performance and implementation cost; internalize that fact now. – Eric Lippert Apr 28 '17 at 21:12
  • 1
    @Rafalon: Also, I encourage you to see what happens when you try two billion as an argument to your algorithm. Does it perform adequately fast? Or does it crash and die? What are the performance implications of *allocating a list with eight billion bytes of integers in it*? If this is a realistic requirement of your algorithm then you will have to consider the costs of memory management; you might want to build a custom memory manager that handles your case efficiently. – Eric Lippert Apr 28 '17 at 21:14
  • Indeed the difference between practical an theory, since in reality word fast is perceptional in nature, for some 5 s is fine, others are not even happy with 50 ms, optimization is always based on wide variety of factors – Mrinal Kamboj Apr 28 '17 at 21:21
  • 1
    @EricLippert calculating result for that simple task actually may be faster then looking to lookup table as you need to load whole table from memory. – Logman Apr 28 '17 at 22:09
  • 1
    @Logman: Good point; we're going to need to write a custom memory manager if we want *as fast as possible*. – Eric Lippert Apr 28 '17 at 23:06
0

Looking at the results the values seems to contain a repeating pattern every 14 items, which is probably the key to find the optimal algorithm (short of an extended lookup table)

More optimizations may always be possible (such as precalculating expected size), but the following delivered the expected results and should perform well:

var list=new List<int>(); 
int[] seq = {0,3,5,6,9,10,12,15,18,20,21,24,25,27};
for(int i = 0;;){
    int val = seq[i % 14] + (i++/14) * 30;
    if(val > number)break;
    list.Add(val);
}

(Side note: in general one hard to measure factor is the branch prediction ifs cause, so when doing these puzzles it's always a good idea to get as few as those as possible)

Me.Name
  • 11,334
  • 3
  • 25
  • 41
  • Could you please elaborate more. Where this `seq[i % 14] + (i++/14) * 30` come from? – Mhd Apr 28 '17 at 22:49
  • 1
    Because the pattern repeats after 14 values (30,33,35,36,39,40,42 etc), the base pattern can be used to determine each next value. The modulo `i%14` will get the index within the array. The division `i/14` will get the number of times the pattern has been completed (since i is an integer, C# will floor the outcome). So for the first 14 values the sequence is used directly. For the 15th value the first index is used, but the outcome is increased with 30. The same happens for the next repeat (`60,63,65`) and the next, etc. – Me.Name Apr 29 '17 at 07:55