OJ’s rants What would OJ do?

14Jun/0866

An Interesting Little Problem

This post was inspired by a recent interview question that was posted over at fsharp.it. It's one of those neat little questions which looks really simple on the surface but is quite tricky.

The question apparently originates from an interview that someone had with Google, and goes something like this:

There is an array A[N] of N integers. You have to compose an array Output[N] such that Output[i] will be equal to the product of all the elements of A[] except A[i].

Example:
    INPUT:[4, 3, 2, 1, 2]
    OUTPUT:[12, 16, 24, 48, 24]

Note: Solve it without the division operator and in O(n).

Since I had a spare 10 minutes, I decided to give it a shot ... in Haskell.

I'll cut to the chase, here's the source to my solution:

vals :: [Int]
vals = [ 4, 3, 2, 1, 2 ]
 
answers :: [Int]
answers = [ front !! (x-1) * back !! (x+1) | x <- [1..length vals] ]
  where
    front = scanl1 (*) temp
    back = scanr1 (*) temp
    temp = [1] ++ vals ++ [1]

I'm hoping that this is quite self-explanatory. But in case it's not, I'll cover some of the gory bits.

The core of the problem is coming up with a way of determining the value of the product of numbers from the start of the list up to a given index, and to do the same at the other end of the list from that given index.

I thought that the easiest way would be to create two lists: both of them containing the compounded products of the numbers in the list, but each of them in different directions. To generate those lists, I thought that I'd add the value of 1 to the list, both at the start and at the end, as it would allow me to do two things:

  1. Generate the lists using the scanl1 and scanr1 functions.
  2. Index into the list using a counter that's based on the size of the original values without having to worry about going past the bounds of the list.

Yup, quite lazy, but very handy.

Here's the output when I execute answers in GHCi:

Prelude> :l google.hs
[1 of 1] Compiling Main             ( google.hs, interpreted )
Ok, modules loaded: Main.
*Main> answers
[12,16,24,48,24]
*Main>

Problem solved in O(n). Neato! After feeling rather chuffed with myself I thought I'd go back to Fsharp.it and check out the answer posted there. The principle was similar, but the implementation listed was a little longer.

So I thought I'd have a go at writing up my solution using F#. It didn't seem like a stretch until I realised how little of the language I know (I'm currently reading through Expert F#, but I'm still far from being one myself). Here's what I came up with:

#light
 
let vals = [ 4; 3; 2; 1; 2; ]
 
let mul a b = a * b
let (++) = List.append
 
let answers =
  let temp = [1] ++ vals ++ [1]
  let front = List.scan1_left mul temp |> List.to_array
  let back = List.scan1_right mul temp |> List.to_array
  seq { for x in [1 .. vals.Length] -> front.[x-1] * back.[x+1] }

A few things you might notice:

  1. My syntax highlighter plugin doesn't currently support F# :)
  2. I use a similar method to the Haskell solution, but ended up having to convert the front and back lists to arrays. The reason was because I need to be able to index into the resulting integer set, and I can't do that with lists (if I'm wrong, please let me know!)
  3. I defined a function called mul which does a simple multiplication. I wanted to pass (*) as the first parameter to the scan1_* functions, but the interpreter took that as the start of a comment instead! So I had to resort to a dodgey hack. If you know a way around this, please let me know.
  4. I wrote my own (++) operator because I didn't want to have to write List.append more than once :)

In other words, my F# version smells like n00b. I'm sure there are so many better ways to implement this using the built-in features of the language and supporting libraries, but I'm yet to get to the level when I can write it. I'd love for someone to show me how :)

I did enjoy having a dabble with F# for the first time in ages, though I have to admit I much prefer using VIM and fsi.exe instead of Visual Studio and the interactive F# add-in.

As always, feedback and criticism welcomed (and needed).

Update

After some great feedback (see below), I've come to realise that the !! operator in Haskell is actually O(n) itself. Hence it was a bad choice for inclusion. Back to the drawing board for me!

Here are a couple of submitted Haskell solutions.

-- by lf
scanm f z xs = zipWith f (scanl f z xs) (tail $ scanr f z xs)
main = print $ scanm (*) 1 [4,3,2,1,2]
 
-- by Henning
answers :: [Int] -> [Int]
answers vals = zipWith (*) front (drop 1 back)
	where
	front = scanl (*) 1 vals
	back = scanr (*) 1 vals
 
-- by desp
problem input = zipWith (*) front back where
  front = init (scanl (*) 1 input)
  back = tail (scanr (*) 1 input)
 
-- by foobar
foo [] _ = ([], 1)
foo (x:xs) acc = let (l, m) = foo xs (acc*x)
                 in ((m*acc):l , m*x)

Thanks for the submissions guys :) Sorry for not including the imperative versions in the update.

  • critcizer
    @ Ant K  -- maan, I like your innocence / sarcasm, whichever way you meant tat comment,
    about tat guys soln having a division operator, so technically truth machine has published an incorrect ans [:P]
  • qhfgva
    a = [4, 3, 2, 1, 2]

    foo = [1]+([1] * len(a))
    goo = [1]+([1] * len(a))

    for i, x in enumerate(a):
    foo[i+1] = x * foo[i]

    for i, x in enumerate(reversed(a)):
    goo[i+1] = x * +goo[i]

    goo.reverse()

    print [foo[i] * goo[i+1] for i in range(len(a))]
  • Ant K
    @Truth machine - thanks for the 0n explanation - I've been looking into it.

    Also, doesn't your solution use the division operator?
  • truth machine
    "So what if their solutions are “so complicated”? This is a learning experience for all."

    I was providing some education.

    "I can’t see how your solution is any more or less simple than many others that have been posted."

    On;y if you mix apples and oranges. There are some ridiculously complicated C solution. Your own Haskel solution was ridiculously complicated compared to others.
  • OJ
    Ah yes, finding the time between nappy changes. I remember saying that too ;) It took nearly 4 months to find it! :)
  • This sounds like the kind of problem that could quite neatly be solved using C++ template trickery or even entirely using the pre-processor in some (nasty) macro.  If I get chance between nappy changes and feeds I'll give it a go, just for the fun of it.
  • OJ
    @truth machine: You need to ease up on your tone. People are just having a go at solving the problem themselves. So what if their solutions are "so complicated"? This is a learning experience for all. It's what blogging is all about. I can't see how your solution is any more or less simple than many others that have been posted. Get off your high horse mate :)
  • truth machine
    Simpler yet, per grahamf's "in place" suggestion (except that this sweeps right to left and then left to right, so the results can be output in order without a separate print loop):

    #include <stdio.h>

    static int in[] = { 4,3,2,1,2 };
    #define N (sizeof(in)/sizeof(*in))

    int main(void)
    {
    int out[N];

    for( int p = 1, i = N; --i >= 0; p *= in[i] )
    out[i] = p;

    for( int p = 1, i = 0; i < N; p *= in[i++] )
    printf("out[%d] = %d\n", i, out[i] *= p);

    return 0;
    }
  • truth machine
    "For example, the “sweep left, then sweep right” idea fails when there is a single zero (though it works for 2 or more zeros).

    That's silly. You really need to sharpen your mathematical intuition.
  • truth machine
    "Sorry I’m not too math minded - what does 0n mean?"

    It means that your solution is too slow, because the number of operations it performs is a (constant) multiple of N*N, i.e., O(N^2), but the problem is to do in in a number of operations that is a (constant) multiple of N, i.e., O(N).
  • truth machine
    You people make simple things so complicated:

    #include <stdio.h>

    static int in[] = { 4,3,2,1,2 };
    #define N (sizeof(in)/sizeof(*in))

    int main(void)
    {
    int l[N] = {1};
    int r[N] = {1};

    for( int i = 0; ++i < N; ){
    l[i] = l[i-1] * in[i-1];
    r[i] = r[i-1] * in[N-i];
    }

    for( int i = 0; i < N; i++ )
    printf("out[%d] = %d\n", i, l[i] * r[N-1-i]);

    return 0;
    }
  • Ant K
    There's a load more of these google questions here:-
    http://placementsindia.blogspot.com/2007/09/goo...

    Something to while away the hours/days/weeks!
  • OJ
    OK guys, theme changed for a bunch of reasons. Comments now stop stripping + symbols :) Thanks again for all the great feedback.
  • OJ
    @Antk & @Ankhil: I've fixed up your formatting issues :)


    I have installed a comment editing plugin, can you please let me know if it's working for you?



    @Antk: O(n) is an indication of the computational complexity of the algorithm. Check out this page at Wikipedia for a full run-down on Big O notation.



    Cheers ;)
  • Akhil Ravidas
    MUL = 1 ; for ( i from n to 1 ) OUTPUT[i] = MUL , MUL *= A[i]
    MUL = 1 for ( i frm 1 to N ) OUTPUT[i] = A[i] * MUL , MUL *= A[i]
  • Ant K
    Sorry I'm not too math minded - what does 0n mean?
    C#
    int[] arr = new int[] {4, 3, 2, 1, 2};
    int[] output = new int[arr.Length];
    for (int i = 0; i < arr.Length; i++)
    {
    int tally = 1;
    for (int j = 0; j < arr.Length; j++)
    tally *= j == i ? 1 : arr[j];
    output[i] = tally;
    }
    return output;
  • OJ
    @ashish: You haven't read the question properly. It states that you are not allowed to use division in your answer. Since you're using division, your solution doesn't count.


    Also, you should read the comments in the thread regarding the use of division and the issues around it. You end up with issues in the case where one of the items in the list is zero (among others).
  • implementation in python langauge , it can't get shorter than this.input = [4, 3, 2, 1, 2]output = [ reduce(lambda x,y : x*y , input)/i for i in input],
  • droz
    C#
    int[] A = { 4, 3, 2, 1, 2 };
    int[] L = new int[A.Length], R = new int[A.Length];

    for (int n = 0, m = A.Length - 1; n < A.Length && m >= 0; n++, m-- ) {
    L[n] = n == 0 ? 1 : A[n - 1] * L[n - 1];
    R[m] = m == A.Length - 1 ? 1 : R[m + 1] * A[m + 1];
    }

    for (int n = 0; n < A.Length; n++)
    Console.Write("{0} ", L[n] * R[n]);
    Console.WriteLine();
  • OJ
    @Jason: I just thought I'd point you at the real esoteric languages. I can't see evidence of Haskell or F# in there ;)
  • OJ
    @Josh: Thanks for your post! I am a little intruiged by it :)


    I'm not sure your assumption is correct. The sweep left/right works just fine because the results are accumulated and stored as generated.



    So if you have [4, 3, 0, 1, 2] your "left" and "right" arrays are:

    [4, 12, 0, 0, 0] and [0, 0, 0, 2, 2] respectively.



    For each index you multiply the previous value in the left array, and the next value in the right array (except of course for end-cases where they just have the one value), so your multiplications go: [0, 4 * 0, 12 * 2, 0 * 2, 0], resulting in [0, 0, 24, 0, 0]. This is exactly as expected. It works fine for multiple zeros, and no zeros as well.



    Is there any chance you can point out the flaw in this method? Cheers! :)
  • Josh
    I wasn't able to check every line of every solution, but it seemed like some of these depend on there not being a zero in the original array. For example, the "sweep left, then sweep right" idea fails when there is a single zero (though it works for 2 or more zeros). This is extremely relevant because I just interviewed with a large software company and they asked a variation of this question as part of the interview. The twist was that in their question you were given 2 arrays, the 2nd array being an array of indexes which you were supposed to remove from the result at that position. Example:[1,2,3,4] and [3,1,0,2]gives[6,12,24,8]and [0,2,3,4] and [3,1,0,2] gives[0,0,24,0]Because of who I was interviewing with, I wrote my solution in C#. It centered around first counting the zeros in the first array. If there are none, you sweep left then right as described above. If there is 1 zero, you find the position of that zero, and set it equal to the product of the other numbers, and all other entries are zeros. If you have 2 zeros, you give back a correctly dimensioned all zeros array. My solution looked like this in C#:
    static int[] op(int[] a, int[] b)
    {
    int[] res = new int[a.Length];
    int zeroCount = 0;
    int zeroPosition = -1;
    for (int i = 0; i < a.Length; i++)
    if (a[i] == 0)
    {
    zeroCount++;
    zeroPosition = i;
    }
    for (int i = 0; i < a.Length; i++)
    res[i] = 0;
    if (zeroCount == 0)
    {
    int total = 1;
    for (int i = 0; i < a.Length; i++)
    total *= a[i];
    for (int i = 0; i < a.Length; i++)
    res[i] = total / a[b[i]];
    }
    else if (zeroCount == 1)
    {
    int nonZeroTotal = 1;
    for (int i = 0; i < a.Length; i++)
    if (a[i] != 0)
    nonZeroTotal *= a[i];
    for (int i = 0; i < a.Length; i++)
    if (b[i] == zeroPosition) res[i] = nonZeroTotal;
    }
    return res;
    }

    This is probably not the most elegant solution, but it passed all my test cases.
  • OJ
    @Max: That's gotta be python :)
  • Max
    front,back,n=[1],[1],len(values)for i in range(n-1): front.append(front[i]*values[i]) back.insert(0,back[0]*values[-i-1])output=[front[i]*back[i] for i in range(n)]
  • OJ
    @Kate: I personally believe that it's very hard to make a sweeping statement like "We should move to Functional programming and remove all loops". Anyone brave enough to make that statement better have some really solid arguments to back it up ;) I'm currently in no position to make a statement like that. I hope that whoever said it to you made it clear as to why!


    As far as what I said regarding the use of Functional languages being better for multithreading, I am basing on the the inherent immutability of data in Functional languages. From my experience, most (if not all) of the problems that come out of handling multiple threads is all around access to shared state. Imperative languages are all about shared state and code which has side-effects. Functional programming is all about code that doesn't have side-effects or mutable state, and hence managing that state across threads isn't a problem because if it's shared, it's not writable. Hence you don't have race conditions or collisions. Hence, you don't actually have to use mutexes or semaphores when writing purely Functional code. When you step out of Functional and back into the Imperative world that has side-effects, that's when you have to start worrying about managing your state again -- mutexes and semaphores also come back into play.



    Aside from the issue of threading and managing mutable state, Functional programming is also great as far as testing is concerned. The theory here is that due to the nature of Functional programming, you're able to mathematically prove that your functions are behaving correctly. This is a topic I'll be blogging about down the track as I'm still rather scant on the details, but it's quite exciting! I hope you're still browsing this site when I get round to that post, as I'd be very keen to get your thoughts.



    Having said all that I don't think that Functional is a catch all, and most applications do require some level of mutable state. Right now I personally feel that the ideal solution is a mixture of Imperative and Functional -- where the former is just a high-level layer over the latter. This is why I'm currently quite excited about the prospect of using F# where mixing the two ideas is very easy. Whether or not it's a good thing is still yet to be confirmed in my eyes though :) Cheers!
  • Kate
    @OJ: You wrote, "There are certain things that are easier to write in imperative languages, and same goes for functional." You're absolutely right. That's all I meant by my statement. I have heard some people state that we should all move to functional languages and eliminate all loops from our code. I'm not sure how much I agree with that. :) You mention that functional programming is better for multithreaded applications. I never knew that. Could you explain a little further? Does using functional programming techniques reduce the risks of race conditions and collisions? I would actually think using mutexes and semaphores in a functional language would be as big of a pain as in other iterative languages.
  • OJ
    @Kate: C# and LINQ are pretty nifty hey :) I'm head down in some C# stuff at the moment, but unfortunately we're using .NET 2.0, so I can't use LINQ.


    I'm not sure I agree with your point about using Functional Programming "too much". What is too much? You could use the same argument for Imperative languages, and yet they're used almost everywhere to drive massive systems end-to-end without any Functional coding at all. Is that too much?



    There are certain things that are easier to write in imperative languages, and same goes for functional. But that doesn't mean to say that you shouldn't use one or the other for a certain class of problems.



    I would actually argue that given that the nature of programming is going more towards multithreading due to the number of cores/processors on the desktop increasing, you're better off using Functional wherever possible. It does a much better job of allowing you to write code that is easier to handle across threads than Imperative languages.



    The thing I really like about F# is the ability to do both where it makes sense without having to write code in two different languages.



    Either way, our points are really the same: make sure you use the right tool for the job :)
  • Kate
    @OJ: I do some functional programming. I'm actually working on a big website right now in C# and linq. I like C because it's a very simple language, and I was just interested in what a C solution would look like. :) Functional programming is great for working with large data structures, but you got to be careful when using too much. If you rely on your hammer too much, every problem starts to look like a nail.
  • OJ
    @Graham: That's true! I have seen similar implementations of this kind of thing in quite a few areas of graphics/rendering. For some reason I hadn't made the connection until you mentioned it. Cheers! :)
  • grahamf
    Division won't work since there might be a "0" in the list.  The easiest way is, sweep from left to right, storing the cumulative product in the output array. Then sweep right-to-left with a fresh cumulative product, and multiply the value in the output cell by said cumulative product.  It's easy and it doesn't require any extra storage.  This technique is actually used for computing surface areas of bounding boxes during construction of acceleration data structures for ray tracing, so it's not just an abstract problem :D
  • OJ
    @Boomi: That was a really insightful comment :) The overflow issue has been biting programmers in the butt for yonks and yet they still haven't learned their lesson. Good to see that someone is mindful of it. Cheers!


    @Michael: Thanks for the backup mate. I do get mixed feelings when I read that kind of feedback. On one hand I don't want to make it hard for readers to understand the content, but at the same time I want to post whatever I feel like posting. It's a hard one to nail. At the end of the day I post what I feel like posting in the hope that I can learn from it, and that other people might learn too. If that means I write a Haskell snippet instead of C#, then so be it :) Your comment is greatly appreciated. Cheers!
  • Michael
    OJ, just ignore the people whining about your language choices. You wanted to try your hand at the problem in Haskell and F#. That is your choice to make, not your readers'. If they can't read Haskell or F# (I can read the former and can decode the latter to get the gist) that is their problem. They can either choose to expand their horizons or they can choose to not be a part of your target audience.

    What's next? Blog comments left on a Chinese-language blog complaining about how they can't read all those funny little pictures? Sheesh!
  • boomi
    There is a straightforward recursive solution that works the same way in most languages:

    main = do putStrLn $ show products_of_peers
    where (_, products_of_peers) = multiply_peers 1 [4, 3, 2, 1, 2]
    where multiply_peers _ [] = (1, [])
    multiply_peers l (x:xs) = (r * x, l * r : ps)
    where (r, ps) = multiply_peers (l * x) xs


    (Returns [1] for lists with one element)

    There are actually good reasons not to multiply all numbers first and divide afterwards:
    1. The result would be incorrect for lists containing exactly one zero and at least one non-zero number
    2. The full product may overflow even if the values to be returned are in range
    3. You might have associative multiplication but no division for your datatype
  • OJ
    Hi all, I've just added a new comment plugin which should allow you to at least add
     tags around your code. It doesn't support syntax highlighting, but it's better than nothing!


    I've also added a comment editing plugin which should let you change your comment for up to 30 mins after posting. I hope it helps.



    Thanks all :)
  • OJ
    @Dan: Have added pre tags to your code for you :)


    @Jason: That's true. Functional coders will have no problem reading imperative code, and yes there are some "strange" (for want of a better expression) operators in Haskell/F# that aren't necessarily standard. They can definitely aid in the confusion. I guess with my focus on functional programming I have really not thought too much about imperative examples to stuff that I'm doing. Perhaps in cases such as this where the purpose is a mixture of algorithm and code I should provide both imperative and functional solutions. I could also make more of an effort to cover those things which aren't necessarily obvious when using languages that aren't "mainstream".



    I will take your feedback on board mate. I'll be sure to make more effort to avoid potential confusion and

    provide more examples down the track. Cheers. :)


  • Jason
    @OJ:

    I think any Haskell programmer could understand a C or C# example, but comparatively few people understand a Haskell example. Rather than immediately understanding your approach, the non-Haskell-programmer must research, e.g., what the "!!" operator does.

    Algorithms courses are usually language-neutral, as the algorithm is the lesson, not the implementation of it.
  • Dan
    Here's another C solution using recursion that I've come up with:

    int foo(const int *input, const int size, int index, int lproduct, int *output)
    {
    if (index >= size)
    return 1;
    else
    {
    int rproduct = foo(input, size, index 1, lproduct * input[index], output);
    output[index] = lproduct * rproduct;
    return input[index] * rproduct;
    }
    }


    Called by:
    foo(input, SIZE, 0, 1, output);
  • OJ
    Woah! I just checked the comment moderation queue and found another stack of comments. Sorry for the omissions!

    @web design: Yup, you're right. My bad :) I have learned my lesson.

    @Jason H: I know this will sound like a n00b question, but is that Python?

    @desp: nice and succinct mate :)

    @Buffi: I have fixed up your comment for ya mate.
  • OJ
    @Jason: Interesting feedback. There are a few reasons that I posted what I did. I'm currently playing around with functional languages (Haskell and F# in particular) and was interested in solving the problem using both of those languages. This post was supposed to be a mixture of algorithm and language.

    I personally find psuedo-code horrible. Yes it's great for generalising, but I much prefer concrete examples, and from past experience many other people do too.

    Using the word "esoteric" when referring to functional languages might start another religious war ;) I can certainly understand how the concept of functioal programming can be really tough to grasp if you're an imperative programmer. I can see how examples might be confusing for newcomers to functional programming. I don't agree with functional programming being estoric though :)

    Do you think that a quality imperative example would suffice in a language like C or C#?

    Your feedback is greatly appreciated :)
  • Jason
    My feedback is, if you're going to make a blog post about algorithms, use a common language or common pseudo-code instead of these esoteric languages that are hard for newcomers to follow.
blog comments powered by Disqus