Live data from Hacker News

Why it’s hard for programmers to write a program to flatten a list?

shekhargulati.com

111–120 of 128 posts

Re: Why it’s hard for programmers to write a program to flatten a list?

#111

Earlier quoted context omitted.

I am not an enthusiast of this approach to recruiting, but to be fair, the purpose of the question is not to find someone to flatten lists. It is reasonable to ask, if a programmer cannot flatten a list, in what sense is that person a programmer? Putting aside the question of whether this is a good approach to recruiting, if this question is being failed with any regularity, it raises some interesting and important q…

If the person flattens a list with the: Google Copy Paste Algorithm, are they a programmer? Are they more or less of a programmer if instead they use racket or clojure's built-in =flatten=?

It is the cases of failure that are interesting and informative, and if the methods you mention are the only ways a person can solve the problem, that counts as a failure - as I mentioned, this is not actually about flattening lists.

Re: Why it’s hard for programmers to write a program to flatten a list?

#112
post #44

Earlier quoted context omitted.

Aren't list-of-lists more like trees where values are only stored on the leaves? In this case, aren't {pre,in,post}order all the same?

For the trivial case in which the nesting always gets deeper at the end like in the OP, perhaps. Take a look at nodes and leaves from my Perl5 example elsewhere in the thread. my $nested = [ 1, [ 2, 3 ], [ 4, [ 5, 6, [ 7, [ 8, [ 9, 10 ] ], 11, 12, [ 13, [ 14, [ 15, 16 ], 17 ], 18 ], 19 ], 20 ] ] ]; What the code does when there are leaves and nodes intermixed at arbitrary depths in arbitrary order from left to right…

Generally, "flatten" means the first of those two orders, but I see that the problem as given doesn't disambiguate those. If an interviewer wants to only accept one of those, he should clarify.

Incidentally, the first of those is preorder and postorder traversal of the list-of-lists (inorder only exists for binary trees), while the second is none of those. I would describe that difference as depth-first (first) vs breadth-first (second).

Re: Why it’s hard for programmers to write a program to flatten a list?

#113
post #44

Earlier quoted context omitted.

Aren't list-of-lists more like trees where values are only stored on the leaves? In this case, aren't {pre,in,post}order all the same?

The example input in the article is: [1,[2,3], [4, [5,6]]] It happens to be representable as a binary tree. Indeterminacy regarding whether or not that is accidental or intentional is due to underspecification of the interview problem. The first time I read a description of Linkedin using logs as the fundamental data structure (for what turns out to be Kafka I later learned) I had an epiphany that there is no such th…

To me, "nested list structure" means list-of-lists, which implies a certain natural tree representation, where all elements in a list are siblings.

But maybe I've just been poisoned by Lisp, which tries to use lists as the elusive generic simple data structure.

(OT: https://en.wikipedia.org/wiki/List_of_lists_of_lists)

Re: Why it’s hard for programmers to write a program to flatten a list?

#114
post #112

Earlier quoted context omitted.

For the trivial case in which the nesting always gets deeper at the end like in the OP, perhaps. Take a look at nodes and leaves from my Perl5 example elsewhere in the thread. my $nested = [ 1, [ 2, 3 ], [ 4, [ 5, 6, [ 7, [ 8, [ 9, 10 ] ], 11, 12, [ 13, [ 14, [ 15, 16 ], 17 ], 18 ], 19 ], 20 ] ] ]; What the code does when there are leaves and nodes intermixed at arbitrary depths in arbitrary order from left to right…

Generally, "flatten" means the first of those two orders, but I see that the problem as given doesn't disambiguate those. If an interviewer wants to only accept one of those, he should clarify. Incidentally, the first of those is preorder and postorder traversal of the list-of-lists (inorder only exists for binary trees), while the second is none of those. I would describe that difference as depth-first (first) vs br…

I think "flatten" is kind of underdefined.

In many situations, the order of the leaves doesn't matter at all. In others, it matters a great deal. The question should be clear if it does matter.

Depth-first for breadth-first was not specified. Both preorder and postorder are subclasses of depth-first. Neither preorder nor postorder was specified.

Still, it depends on what you consider a node and a leaf and how you build your tree how much the order matters.

Is a deeper nesting level the child node of a value? Are all values leaves and their parent nodes the nesting level? Are nesting levels collapsed into single nodes or are different nested lists at the same level sibling nodes?

     [1,[2,3], [4, [5,6]]]

      []
     / |
    1  [[]].
      / | | \
    2   3 4  [[[]]]
              |  |
              5  6


In preorder you're going to get: [], 1, [[]], 2, 3, 4, [[[]]], 5, 6 In postorder: 1, 2, 3, 4, 5, 6, [[[]]], [[]], []

Let me explode that initial root this time and make some values nodes if they are followed by an increased nesting level.:

    [1,[2,3], [4, [5,6]]]

       1
      /  \
    []    []
    |\     |
    2 3    4
           |
          [[]]
          |  |
          5  6
Preorder? 1, [], 2, 3, [], 4, [[]], 5, 6 Postorder? Aah... 2, 3, [], 5, 6, [[]], 4, [], 1

So even after you splice out empty nodes, your values are suddenly out of sort order. It's still depth-first.

Or maybe we just don't build the nesting levels into our tree because we don't care. But we still build the tree according to it. We're flattening, after all, and the spec doesn't say we need to retain a nesting level as some attribute of the objects in the flattened list. But what becomes a node vs. a leaf is left as an exercise.

       1
     / | \
    2  3  4
         / \
        5   6
Preorder? 1, 2, 3, 4, 5, 6 Postorder? 2, 3, 5, 6, 4, 1

So yeah, it matters. The obvious recursive approach is preorder, especially if you want to maintain sorting. That's what I called "flatten" in my code example earlier. "flatten2" is breadth first as mentioned. Consider this depth-first Perl5 subroutine:

    sub flatten3 {
        my $n = shift;
        my $o;
        my @f;

        for ( @$n ) {
            if ( ref $_ eq 'ARRAY' ) {
                unshift @f, flatten3( $_ );
            } else {
                push @f, $_;
            }
        }
        return @f;
    }
In each level of recursion it's place the children at the beginning of the array (unshift) and the parent to the end of the array (push) even though it's actually considering them each whenever it reaches them.

Given my expanded example input of:

    my $nested = [ 1, [ 2, 3 ], [ 4, [ 5, 6, [ 7, [ 8, [ 9, 10 ] ], 11, 12, [ 13, [ 14, [ 15, 16 ], 17 ], 18 ], 19 ], 20 ] ] ];
This produces output like this, which is definitely the absolute deepest nesting first in the output.:

    15 16 14 17 13 18 9 10 8 7 11 12 19 5 6 20 4 2 3 1

Then there's a true postorder traversal, with an array holding the parent back until after its immediate children.:

    sub flatten4 {
        my $n = shift;
        my @p;
        my @f;

        for my $node ( @$n ) {
            if ( ref $node ) {
                push @f, flatten4( $node );
            } else {
                push @p, $node;
            }
        }

        push @f, @p;
        return @f;
    }

which provides output as such since it's considering the parent node after all its children every time.:

    2 3 9 10 8 15 16 14 17 13 18 7 11 12 19 5 6 20 4 1

Whereas preorder depth-first keeps the numerical order for this input and the breadth-first traversal gives:

   1 2 3 4 5 6 20 7 11 12 19 8 13 18 9 10 14 17 15 16
as previously shown.

So no, preorder and postorder do not necessarily mean the same output just because the input started as a nested list.

A nested list is not simply a list. It is a special representation of a tree. How you consider that tree to be represented by the nested list is important, as is how you traverse it. In fact, in Perl [] is an array reference, which is why this code is testing for references and recursing on them. So it's already traversing an actual tree structure.

Re: Why it’s hard for programmers to write a program to flatten a list?

#115
post #91

As nobody seems to have posted a Java solution yet: import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Arrays.*; public class Flatten { public static void main(String[] args) { List nested = asList(1, asList(2, 3), asList(4, asList(5,6))); List flat = flatten(nested); System.out.println(nested); System.out.println(flat); } public static List flatten(List…

Java never fails to astound in its verbosity, even today, even with much better utility functions for things like lists (flatMap/collect are new to me). But, now that I've taken the time to read through it, I see the actual flatten function isn't actually all that different an implementation from the Perl and Python variants other folks have suggested. But, Java sure does make you work for that list data structure.

I know it's verbose because of type definitions, and because there's no bare functions in Java, but it sure does balloon up small programs with a lot of boilerplate, and tends to hide the point of the program behind stuff that doesn't look like the purpose of the program.

Re: Why it’s hard for programmers to write a program to flatten a list?

#116
A somewhat more interesting challenge is: write code to lazily flatten a list. It must instantly return, and access the original structure lazily. Each atom is fetched from the original list when the lazy list is accessed. No continuations allowed; at most lexical closures.

My C solution is in this file:

http://www.kylheku.com/cgit/txr/tree/lib.c

functions lazy_flatten_scan, lazy_flatten_func and lazy_flatten.

Re: Why it’s hard for programmers to write a program to flatten a list?

#117
post #112

Earlier quoted context omitted.

Generally, "flatten" means the first of those two orders, but I see that the problem as given doesn't disambiguate those. If an interviewer wants to only accept one of those, he should clarify. Incidentally, the first of those is preorder and postorder traversal of the list-of-lists (inorder only exists for binary trees), while the second is none of those. I would describe that difference as depth-first (first) vs br…

I think "flatten" is kind of underdefined. In many situations, the order of the leaves doesn't matter at all. In others, it matters a great deal. The question should be clear if it does matter. Depth-first for breadth-first was not specified. Both preorder and postorder are subclasses of depth-first. Neither preorder nor postorder was specified. Still, it depends on what you consider a node and a leaf and how you bui…

Don't overthink it. But if we're gonna overthink it, one thing we can notice is that a rose tree is a free monad, and like any free structure is initial in its category - meaning there's a unique monad homomorphism from a rose tree to any other monad. Lists form a monad, so we can ask "what is that homomorphism?" It turns out to be the traditional definition of flatten.

That is to say, only flatten solves these equations for lists and rose trees:

    (flatten . f =

Re: Why it’s hard for programmers to write a program to flatten a list?

#118
This is straight out of my homework for an introductory to CS class. If I can recall, we need an IF statement to detect whether the element IS INT, else length of list. Then just add [Int] or list[:] to the new list.

Ok point being: I didn't believe what I was learning was relevant to industry, in particular linked lists.

Re: Why it’s hard for programmers to write a program to flatten a list?

#119

Earlier quoted context omitted.

I think "flatten" is kind of underdefined. In many situations, the order of the leaves doesn't matter at all. In others, it matters a great deal. The question should be clear if it does matter. Depth-first for breadth-first was not specified. Both preorder and postorder are subclasses of depth-first. Neither preorder nor postorder was specified. Still, it depends on what you consider a node and a leaf and how you bui…

Don't overthink it. But if we're gonna overthink it, one thing we can notice is that a rose tree is a free monad, and like any free structure is initial in its category - meaning there's a unique monad homomorphism from a rose tree to any other monad. Lists form a monad, so we can ask "what is that homomorphism?" It turns out to be the traditional definition of flatten. That is to say, only flatten solves these equat…

I believe speaking of monads, monoids, functors, and homomorphism when discussing an interview question for fairly fresh programmers is definitely overthinking it.

I'm not sure what you think is so simple about your solution compared to:

    sub flatten {
        map { ref ? flatten( @$_ ) : $_ } @_;
    }
  
Goodness, and Perl gets a bad reputation for the amount of punctuation in the code.

Of course if you want flatten in Haskell you have it for Tree and Forest.

    import Data.Tree
    tree = Node "A" [Node "B" [], Node "C" [Node "D" [], Node "E" []], Node "F" []]
    main = do
        print $ flatten tree

If a Perl programmer wanted to pull in a CPAN module, there are many from which to choose. Of course, it was just done in a one-line subroutine...:

http://search.cpan.org/~obradovic/List-Flatten-0.01/lib/List... (which does not handle arbitrary depths)

http://search.cpan.org/~rthompson/List-Flatten-Recursive-0.1... (which seems overly complicated)

http://search.cpan.org/~rsavage/Set-Array-0.30/lib/Set/Array... (which tries to flatten hashes as well as lists and seems, well, overly complicated... and pulls in a bunch more methods and functions)

http://search.cpan.org/~satoh/List-Enumerator-0.10/ (which seems about right, including stopping the flattening at an arbitrary depth and comes with other useful array tools)

Of course it's possible to apply the concept of flattening to hashes/dictionaries, too, so there goes the concept of keeping the original sort order.

http://search.cpan.org/~bbc/Hash-Flatten-1.19/lib/Hash/Flatt...

http://search.cpan.org/~chocolate/Hash-Fold-0.1.2/lib/Hash/F...

Re: Why it’s hard for programmers to write a program to flatten a list?

#120

Couldn't you just do something like: def flatten(x): if isIterable(x): for y in x: yield from flatten(y) else: yield x Well, technically this is a generator, but it's easy enough to put its result in a list.

Some python 2 def flatten(x): def flatten_generator(x): if type(x) == list: for y in x: for z in flatten(y): yield z else: yield x return list(flatten_generator(x)) assert(flatten([1, [2,3]]) == [1,2,3]) ary = [1, [2,3], [4, [5,6]]] print flatten(ary)

I'd personally change `if type(x) == list` to `if isinstance(x, (list, tuple, set))` to catch all common iterables and their subclasses.
Post reply on HN