
Why it’s hard for programmers to write a program to flatten a list? - shekhargulati
https://shekhargulati.com/2017/02/24/why-its-hard-for-programmers-to-write-a-program-to-flatten-a-list/
======
njd
I'm a retired data architect and C developer with 36 years of professional
experience. In my opinion, this is not how you find an appropriate candidate.
It would be better to ask substantive questions that are related to the thing
you are making. What does this person bring to the team? How will the team
receive this person? Has this person delivered something of substance in the
recent past that would convince you that he/she would succeed on your project?
How was this person an asset on their previous job?

~~~
fiedzia
Those are questions for juniors. Most likely they had no previous job at all.
The only thing they can bring is potential, and this question tests it rather
well.

~~~
brudgers
It does not really test potential because a junior trained in Java has poor
tools for flattening a list because idiomatic Java would not represent
hierarchical data structure as a nested list. Idiomatic Java would use tree
and node objects. Idiomatic Java also prefers arrays over lists for sequential
data.

An experienced programmer might look at the problem and choose a better tool:
a different language or call a service or rewrite the offending code that
produces a nested list or any one of a dozen things that are not bashing away
with the hammer of Java.

~~~
fiedzia
> An experienced programmer might look at the problem and choose a better
> tool: a different language

Come on, you are not going to call Haskell or Python to flatten a list in your
Java.

> or call a service

I know we have "micro" services now, but really? Sending serialized data to
ListFlatteningService to get deserialized FlattenedList?

> or rewrite the offending code

Something may produce it because it makes sense it its context, while it
doesn't for you. No offence here.

~~~
brudgers
I make no claim to being a good programmer or having high potential (of any
sort but the wasted), just the ability to occasionaly mimic those who are.
Charged with flattening lists, I'd write my Java in Clojure.

    
    
      (flatten [1 [[2] [[3 4] [5 [6]]]]])
    

as much of it as I could because it would be less work and easier to read and
maintain and debug.

Since nested Java lists are isomorphic with trees, there's more than one way
to deserialize them: inorder, preorder and postorder. Flattening deserializes
in preorder. Expecting different consumers of the nested list to deserialize
it in different ways might be a reason that producing a nested list makes
sense in a particular context.

Once we start talking about "consumers of the nested list" we are using the
language of services. That, for better or worse, is a road that currently
tends to lead to micro-services.

~~~
pcmonk
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?

~~~
cestith
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 determines the final order of
your leaves. Are you pre-flattening all the more deeply nested lists and then
building your flat list, or are you walking along a certain nesting level and
deferring anything that's not a leaf to later flattening?

I gave two examples. One produces a flattened list like this:

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

The other products this:

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

Are either of those outside the specification set forth?

~~~
pcmonk
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).

~~~
cestith
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.

~~~
dllthomas
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 =<< flatten m) == flatten (f =<< m)
        pure x = flatten (pure x)

~~~
cestith
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...](http://search.cpan.org/~obradovic/List-
Flatten-0.01/lib/List/Flatten.pm) (which does not handle arbitrary depths)

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

[http://search.cpan.org/~rsavage/Set-
Array-0.30/lib/Set/Array...](http://search.cpan.org/~rsavage/Set-
Array-0.30/lib/Set/Array.pm#flatten\(\)) (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/](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/~bbc/Hash-
Flatten-1.19/lib/Hash/Flatten.pm)

[http://search.cpan.org/~chocolate/Hash-
Fold-0.1.2/lib/Hash/F...](http://search.cpan.org/~chocolate/Hash-
Fold-0.1.2/lib/Hash/Fold.pm)

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

I agree. I said as much. I just thought it was interesting.

> I'm not sure what you think is so simple about your solution compared to
> [...]

Which solution? I didn't present an implementation in this thread. I did
elsewhere
([https://news.ycombinator.com/item?id=13726564](https://news.ycombinator.com/item?id=13726564)),
but I don't think that's what you're talking about? I was discussing
specification, and the code fragment in my comment was a property, not a
definition.

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

Yes, though Tree is a slightly less natural choice for this than a rose tree
(aka `Free []`). Of course, you still have it (in the form of toList).

> 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.

... yes?

You seem to be desperately trying to defend perl against an attack you imagine
me to have made. I have nothing against perl (at least, nothing beyond a
strong desire for static types on large projects, but that applies equally to
a great many languages).

~~~
cestith
Oh, I'm not imagining you making an attack on Perl. It happens commonly
enough, though. I was commenting that your property had a lot of punctuation
and bemoaned that Perl with the same amount often gets criticized for that
very thing. Meanwhile many of the same people are fine with Python's invisible
punctuation, Lisp's parentheses, or Haskell's syntax which likewise includes a
lot of punctuation.

I also was pointing out that Haskell has Tree shipping with it, and Perl's
CPAN, which is usually a stellar place to look, has what appear to be some
false starts. I'd sort of expect one of the many List:: modules like
List::Utils, List::MoreUtils, etc. to have the functionality, but as far as I
saw when looking, no. It's easy enough to do in the base language, though. I
assumed Haskell from your language and the syntax of your notation.

Your Bash and sed solution appears simple on the surface, but it is using a
trick of the data format and bringing together two languages. It's even using
a syntax that will confuse some people on first look in that you're putting
square brackets within square brackets starting with the right bracket then
the left. Many people are going to look at that and at first think it's two
empty character classes then do a double take. It's clever, but any simplicity
in it is rather baked into some, let's say interesting assumptions. I like it
as a snarky response to the problem, but it's not something I'd hire a
programmer for proposing as a serious solution.

~~~
dllthomas
> Oh, I'm not imagining you making an attack on Perl.

'k :-P

> It happens commonly enough, though. I was commenting that your property had
> a lot of punctuation and bemoaned that Perl with the same amount often gets
> criticized for that very thing. Meanwhile many of the same people are fine
> with Python's invisible punctuation, Lisp's parentheses, or Haskell's syntax
> which likewise includes a lot of punctuation.

It certainly happens commonly enough, often unfairly. That said, I don't think
_Haskellers_ usually complain about Perl's punctuation.

> I also was pointing out [...] of your notation

Ah, I seem to have misread you entirely there.

> Your Bash and sed solution appears simple on the surface, but it is using a
> trick of the data format and bringing together two languages.

Well, it's true that we're lucky that a relatively simple transformation of
the input format gives us the desired output. But I'm not sure I'd call it a
"trick".

> It's even using a syntax that will confuse some people on first look in that
> you're putting square brackets within square brackets starting with the
> right bracket then the left. Many people are going to look at that and at
> first think it's two empty character classes then do a double take.

I mean, you've kinda gotta assume people speak the language...

> I like it as a snarky response to the problem, but it's not something I'd
> hire a programmer for proposing as a serious solution.

Well, it was a _snarky_ response, but I think that actually depends on the
context. If it was "You have these particular 20 files, that need to be
transformed this way just this once", it's a great "serious" solution and I'd
totally hire someone who would propose it (or something equivalent in another
language) in for that purpose. It's incredibly brittle to some particular
changes in the input format (especially if you might find square brackets
internal to items) and probably not something that should be built atop.

------
hibikir
The first problem I see is that your rubric is a complete shibboleth: The
candidate has to guess what you are evaluating. In some interviews, all they
want is working code. Others want performance. Others care about testing:
Based on the question, I'd not be sure of what you want. There's places where
writing the test first will, if anything, be detrimental. Others will love it.
You have to be clear on expectations.

The second issue is that a question like that is not even remotely fair across
programming languages. What about types? Is a list of lists a sensible,
idiomatic practice in that language? It's a trivial question that makes sense
in Lisp, and it won't faze a Scala programmer. People are probably going to
come up with a good, concise solution in Python or Ruby. In Java, lists of
lists are often considered awful things, the generics syntax is torture, and
you'll find candidates that will actually think less of you for asking them to
write that: If a list of lists happens, it's normally an intermediate step in
a computation, and you'd not like to pass, or return, anything with that type
signature to anything that isn't a private method.

So compare the very short, boring program that you need in a lisp, with the
array of questions a professional Java programmer would have to ask you to
make sure that you don't ding them with your list of questions, along with the
extra knowledge of how to build generic functions.

Instead, why not ask a simple, yet realistic problem that can be solved in
more than one way? A question where you can give someone points for doing good
things, instead of subtracting points for not doing exactly what you consider
the right solution to be. For extra success, make your rubric public.

I do a lot of interviews as part of my work, and I have noticed that, even for
the same problem, little things like rubric clarity and tailoring requirements
to the language make big difference in pass percentage on screens. Moreover,
they also make a difference when they get to on site interviews if the rubrics
all come from the same basic principles (and if they don't, you should change
that).

Do yourself and your candidates a favor and abandon this question if you are
interviewing people across languages.

~~~
whorleater
> The second issue is that a question like that is not even remotely fair
> across programming languages. What about types?

I wonder if part of this test _is_ to see if a programmer will choose a
language that's appropriate for it. Picking the right tool for the job and
whatnot.

------
klibertp
The word "Java" really should be in the title.

Whatever else you say about it, Java is one of the entry-level languages. It's
no wonder there are many entry-level programmers among its users. It's
obviously a trade-off, as you get that many more candidates to choose from,
compared to for example OCaml, Clojure or Erlang programmers. On the other
hand, a percentage of people who _can_ flatten a list is greater in users of
the more "advanced" languages.

If you want to have a good signal to noise ratio: avoid languages used in
"Programming 101" like a plague and languages which are currently fashionable.
This way you'll get candidates who learned the language by themselves, in most
cases after learning and mastering their previous language.

Becoming a programmer is a long process. Depending on the person, the time
required to learn a single programming language may be shorter than the time
needed to really master all the essential programming skills. Which I think
explains why there is a correlation between good programmers and people who
know more than one programming language.

~~~
brudgers
One of the problems is that for Java the task is underspecified: Flatten a
list of _what_? Sure this one is integers, is that always the case or should
the list take <object>?

Leaving out that information from the question is going to disorient novices
who in a high stress situation at the short end of an asymmetrical
relationship. I suppose if the interviewer lets them Google, then it is a fair
test (and objectively, not Googling before writing code is probably the best
criterion for failing miserably).

What the question really measures is how lucky the candidate was in their
education. If they were taught in Racket rather than Java:

    
    
      (flatten '((1) ((2 3 ((4) (5 6)))))
    

and if they were taught the JVM rather than Java

    
    
      (flatten [[1] [[2 3 [[4] [5 6]]]]]
    

and with J

    
    
      flatten =: [: ; <S:0
    

I'd bet that the interviewer would not know if it was wrong or right. Which is
the same issue of open endedness that the candidate faces.

~~~
dragonwriter
> One of the problems is that for Java the task is underspecified: Flatten a
> list of what? Sure this one is integers, is that always the case or should
> the list take <object>?

Well, I don't Java much, but I think that even if the result List is just
integers, the source list has to be (invalid syntax) List<Integer |
List<Integer>>, which is somewhat problematic to type as anything but
List<Object> given Java's lack of sum types.

~~~
bushin
More precisely List<Integer | List<Integer | List <Integer | List<Integer |
...> :) That would be trivial in functional languages.

~~~
dragonwriter
It would be trivial in languages with sum types and support for recursive
signatures/constructors, which doesn't describe all statically-types
functional languages. And there's no reason such a language would have to be
functional, though most are.

------
gumby
> I am not sure what makes this problem tough for candidates.

Most people aren't programmers, and sad to say, most people employed writing
code are not programmers.

I find trivial problems like this are good interview questions. What the
person asks, says and does tells me a lot about what they would be like to
work with. And it's not like you're using some bizarre datastructure or
obscure CS case: this is an ordinary workaday problem. So it's not a "trick"
or one-upsmanship, just "do you know your tools?"

------
fiedzia
Some reasons:

* Nobody required this from them before.

* Recursion is rarely used to write business logic, most common thing programmers write and learn. Same for static methods and being generic.

* Type checking is done by compiler for them, so no surprise that they don't know how to do it.

* Relatively few people go beyond minimal, so adoption of is java 8 is low

Overall, it looks like a good question to pick up the best developers -
assuming this is what you want.

~~~
dethswatch
To me, this smells like: * Required before: I only ever code at work. *
Recursion: I didn't get any/much theory in school * Type checking: I don't
know my language very well. * Going beyond minimal: agreed- try not to hire
them.

------
ghshephard
I think a lot of this is based on your experience with languages. If you have
exposure to a list/iterator native language like python, you come up with
solution in a few seconds, even if you aren't even remotely a programmer.
Other languages might not lend themselves to so obvious a solution.

The perl example cited here kind of blows my mind compare to the trivial
python approach:

    
    
      def flatten(lst):
          rlst=[]
          for x in lst:
              if type(x)==list:
                  for y in(flatten(x)):
                      rlst.append(y)  
              else:
                  rlst.append(x)
          return (rlst)

~~~
jsnell
You perhaps missed that there were two solutions (+ test infrastructure) in
that Perl post. You Python code is the moral equivalent of the three-line
"flatten" function in the Perl example. The long "flatten2" function was doing
it without recursion.

~~~
ghshephard
The part that was/is challenging/mind blowing for me to grok was:

    
    
       sub flatten {
            my @f;
            push @f, (ref $_ ? flatten( $_ ) : $_ ) for @{ $_[0] };
            return @f;
        }
    
    

The implicit $_, and whatever @{ $_[0]} does, is challenging enough that I bet
I could find people who had been using perl to do work for years who wouldn't
be able to explain what was happening here at first glance, even if they could
eventually work it out/write it themselves with a bit of effort.

That's what I meant by native iterative/sequence languages like python lend
themselves to easy solutions for this type of question, even for people who
_aren 't_ programmers, whereas you really need to be a programmer to come up
with something as powerful as the above.

Put another way, doing a complex joined query might be a trivial question for
someone who knows even the slightest amount of SQL, but would be beyond most
novice python developers (without of course just using a SQL select
statement...)

I will admit the (dense) perl solution certainly has its own beauty that the
more drawn out python solution is lacking, though I'm guessing it could be
written in a pythonic way itself...

~~~
zzzcpan
> I bet I could find people who had been using perl to do work for years who
> wouldn't be able to explain what was happening here

Don't jump to conclusions. You will not find such people, because those are
very basics. Understanding of $_[0] is necessary to use arguments in Perl
functions and understanding of @{...} is necessary to use array references as
lists.

~~~
ghshephard
Challenge accepted.

------
catdog
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<Object> 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<?> nested) {
              return nested.stream()
                      .flatMap(x -> x instanceof List ? flatten((List<?>) x).stream() : Stream.of(x))
                      .collect(Collectors.toList());
          }
      }
    
    

I would say it's a bit of an odd question if you mainly target Java as it's
very uncommon to encounter such a construct there, alone because the type
system can't handle it properly.

~~~
SwellJoe
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.

------
contravariant
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.

~~~
amyjess
I found this on Stack Overflow a while back, and I've been using it in my
Python code since then:

    
    
        [item for sublist in l for item in sublist]

~~~
klibertp
That wouldn't work in this case: it only works with lists where every element
is also a list and it only flattens one level deep.

With these constraints, there are quite a few interesting techniques, for
example in Scheme you can apply append:

    
    
        (apply append list-of-lists)
    

or in Python you can use reduce with add operator:

    
    
        reduce(op.add, list_of_lists) # op == operator module
    

And anyway, even if it's not the constrained version, the solution is indeed
trivial, for example in Erlang:

    
    
        flatten(L) -> flatten(L, []).
    
        flatten([], Acc)            -> Acc; 
        flatten([[] | T], Acc)      -> flatten(T, Acc); 
        flatten([[_|_]=H | T], Acc) -> flatten(T, Acc ++ H);
        flatten([X | T], Acc)       -> flatten(T, Acc ++ [X]).
    

(please don't mind the inefficient use of ++, it's a toy example...)

It can get a bit more complex in a statically typed language, especially if
you want to have static guarantees (no down-casting from Object), but it's
still doable in a couple of minutes...

...is what this post - and discussion - is not about.

It's trivial once you get it, of course. But it's also true that it's almost
impossible to figure it out on your own during a stressful interview if you
_didn 't get it_ beforehand. It's so easy to forget that. We all once
struggled with some concepts, and we all went through many discoveries of
facts, techniques, and skills we didn't know existed, much less that we needed
them.

What I want to say is that the beginners who fail to answer this question are
not bad, they are just beginners. It's ok to reject them right now, but it's
not ok to post an article suggesting that "programmers find it hard to solve
trivial problems even with my help". It's not programmers, but beginner
programmers; it's not trivial problem, just relatively widely known one; and
it may well be the help was insufficient.

~~~
amyjess
Thank you for the correction!

------
mmcconnell1618
Am I the only one who saw the string representation of the nested array and
said "you could just walk it character by character and ignore the brackets
and commas?"

~~~
franciscop
That is indeed, depending on the context (you know the strings won't contain
commas OR that it should be split in the commas), one way to do it in
Javascript (
[https://github.com/franciscop/umbrella/blob/master/src/plugi...](https://github.com/franciscop/umbrella/blob/master/src/plugins/args/args.js#L17)
):

    
    
        return args.toString().split(/[\s,]+/).filter(/* ... */);

------
scotttrinh
When reading this, my immediate instinct was to say: "Easy!"

    
    
      import { flattenDeep } from 'lodash';
    
      const flat = flattenDeep([1,2[3], [4, [5,6]]);
    

Not sure why employers care about developers being able to write utility
functions from scratch, when that is not (typically) the job developers are
hired for.

Having said that, I lament the issues that the OP brought up: poor naming,
unfamiliarity with their language's data structures, etc. Those are issues
that _will_ come up. Especially naming.

~~~
dpark
> _Not sure why employers care about developers being able to write utility
> functions from scratch, when that is not (typically) the job developers are
> hired for._

Because of the reasons you listed in your next paragraph:

> _poor naming, unfamiliarity with their language 's data structures, etc.
> Those are issues that will come up. Especially naming._

No one actually wants a candidate to write "flatten". They want the candidate
to demonstrate that they can work through a small problem and write something
sane and functional. Utility functions tend to be small and reasonable to put
together in about an hour.

With that said, I think this is a mediocre question because no one would
create this list of lists-or-ints in Java. If presented well, the problem
might be decent. If presented poorly, a lot of junior candidates would likely
fail even if they're good candidates.

------
skybrian
A slight rewrite makes this a reasonable question in Java. A list of lists has
a tree structure and asking candidates how to print the items in a tree in
depth-first order is reasonable.

The main issue is that you'd never use a list as a tree node in Java. There
would be an explicit Node class of some sort.

I do ask a question where one possible solution is to define your own Node
class, build a tree, and print it out. But you can also solve it without even
realizing you're working with a tree.

------
cestith
This is pretty trivial. It doesn't require recursion, although the recursive
implementation is much simpler. The spec doesn't specify breadth-first or
depth-first. The example input offered comes out the example output offered
either way. The spec in the article also doesn't mention making it generic, so
I don't see how that's points away for programmers who tackle the input and
output given as integers only.

Here's some pretty trivial Perl5 to do recursive or iterative versions.:

    
    
        use strict;
        use warnings;
        use Data::Dumper ();
        my $nested = [ 1, [ 2, 3 ], [ 4, [ 5, 6, [ 7, [ 8, [ 9, 10 ] ], 11, 12, [ 13, [ 14, [ 15, 16 ], 17 ], 18 ], 19 ], 20 ] ] ];
    
        sub flatten {
            my @f;
            push @f, (ref $_ ? flatten( $_ ) : $_ ) for @{ $_[0] };
            return @f;
        }
    
        sub flatten2 {
            my $n = shift;
            my ( @f, @queue1, @queue2 );
            my $pass = 0;
    
            for ( @$n ) {
                if ( ref $_ ) {
                    push @queue1, $_;
                } else {
                    push @f, $_;
                }
            }
            until ( $pass > 0 && scalar @queue1 == 0 && scalar @queue2 == 0 ) {
                for ( @queue1 ) {
                    if ( ref $_ ) {
                        push @queue2, @{ $_ };
                    } else {
                        push @f, $_;
                    }
                }
                @queue1 = @queue2;
                @queue2 = ();
                $pass++;
            }
    
            return @f;
        }
    
        print STDOUT (join ' ', flatten( $nested )) . "\n";
        my @flat = flatten( $nested );
        print Data::Dumper::Dumper \@flat;
    
        print STDOUT (join ' ', flatten2( $nested )) . "\n";
        my @flat2 = flatten2( $nested );
        print Data::Dumper::Dumper \@flat2;

~~~
zzzcpan
You can make it nicer and more reliable with Perl:

    
    
        sub flatten {
           map { ref $_ eq 'ARRAY' ? flatten(@$_) : $_ } @_ 
        }
    

However, this is not something a Java/OO programmer would be comfortable with,
especially under the stress of the interview. As it's a bit higher level and
closer to a functional way of thinking, than OO.

~~~
cestith
I started to use map, but I've been told time and again anything that does is
not a trivial example because you start by explaining what map does.

Anyway, I'm glad the first response to "this is trivial in Perl" is another,
simpler Perl response rather than "Perl is dead".

------
franciscop
I fail to see how some people see this as a _contrieved_ test. It's an issue
many programmers have probably found at some point as opposed to fizzbuzz or
other artificial tests. Probably other questions related to your business
would also be good, but using this as a first and quick filter sounds about
right IMO.

In Javascript, depending on your context, it could be as easy as:

    
    
         // Only if we're using numbers or other variables without commas on them
         // divide them, remove empty things and make them numbers again
         const flatten = arr => arr.toString().split(/[\s,]+/).filter(e => e).map(n => 0+n);
    

Or a slightly more complex and "proper" one as:

    
    
        // Recursively flatten an array
        const flatten = arr => arr.reduce((all, one) => all.concat(one instanceof Array ? flatten(one) : one), []);

~~~
throwaway729
The complaint isn't that it's contrived in general. The complaint is that it's
a bad question if you're interviewing Java developers (because Java's type
system is impoverished, but that's really beside the point).

If you're going to ask this question of a Java developer, you should carefully
indicate what form the input takes. Heterogeneous collections are canonical in
dynamically typed languages, and also in reasonable typed languages as
recursive sum types. But in Java you're stuck between awkward subtyping /
dynamic casting, or coding up your own definition of a heterogeneous
collections. And in particular, the wording of the question suggests
List<Object>, which is terrible style in Java land.

The real take-away is that Java's type system stinks for this sort of thing,
and the standard libraries don't offer much help. But if you're interviewing
Java developers, you shouldn't start off by asking them to writing terribly
non-idiomatic Java code.

------
dllthomas
{ echo [; sed s/[][]//g; echo ]; }

~~~
valbaca
This is the TYPE of answer I like to see.

------
alkonaut
The fact that "int or list of int" isn't sanely representable in any concise
way in the mainstream OO languages is a sign that perhaps this isn't a great
example problem for a Java interview.

Obviously you can do this with brute forcing the sum type with some class
hierarchy (ugh) or make the algorithm accept some List<object> or Java "List"
interface.

Both of those options are pretty terrible.

I think the intents of the question are good - see if the candidates
understand the importance of naming, signature, tests.

There has to be a question that does this and doesn't immediately have the
subject crying over the type system.

~~~
dragonwriter
> The fact that "int or list of int" isn't sanely representable in any concise
> way in the mainstream OO languages is a sign that perhaps this isn't a great
> example problem for a Java interview.

OTOH, if you are going to be using Java dealing with real world problems, many
of which have aspects that are not neatly representable in Java's type system,
some test of how you deal with the mismatch of that type system with a problem
statement may be appropriate.

A lot of whether this is appropriate in an interview goes beyond the language
used to what the job entails and what you are trying to learn about the
candidate from the response.

~~~
alkonaut
Yes obviously Java is going to be the tool at hand, but to keep focus on the
good aspects of this exercise (naming, testing, etc) perhaps it would be best
to have a problem that has a good solution in Java.

If the subject does this property with generics that simple sum type alone
requires writing several classes just to represent the sum type.

It's not clear from the article what the input _is_. What signature I can use
depends on what the input data is. Is it a string rep of the nested list? Can
I assume it's any type I want such as my own NestedList?

------
kazinator
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](http://www.kylheku.com/cgit/txr/tree/lib.c)

functions lazy_flatten_scan, lazy_flatten_func and lazy_flatten.

------
wz1000
I don't really write Java, but I fail to see any easy and robust way to assign
a type to the flatten function in Java.

Is this the signature you would expect in a solution to this?

    
    
        List<Integer> flatten(List<Object>)
    

Or something like

    
    
        List<Integer> flatten(NestedList<Integer>)
    

with a definition for NestedList?

Is there even a way to define something like NestedList without coercing back
and forth from Object? Is there a way you can do this so that you don't have
to basically reimplement List?

~~~
ng12
I would either use Object to be quick, or implement a NestedListItem with
isNumber(), getNumber(), getList().

The one thing I do like about this problem is it shows when TDD is useful. If
you try to write a test case first the representation problem will be the very
first thing you run into, and the code will mostly follow from there.

~~~
wz1000
Personally, I think this problem shows why sane type systems are useful.

In Haskell

    
    
        data NestedList a = Single a | Nested [NestedList a]
        -- Or equivalently, type NestedList = Free []
    
        flatten :: NestedList a -> [a]
        -- This is pretty much the only reasonable definition of flatten the compiler will accept given this type
        flatten (Single a) = [a]
        flatten (Nested xs) = concatMap flatten xs
    
        -- or given the Free variation of NestedList
        -- flatten = retract

~~~
dllthomas
Not necessarily. See my bash solution in another comment :-D

------
alkonaut
This is pretty easy actually: since the question is _horribly_ vague on what
the input type actually IS, I'm going to just assume it's a string.

So flatttening the list is just a string split on all delimiters. Done.

My Java is rusty but something like input.split("\\[\\],"). Done.

It's an answer of exactly the same quality as the question...

------
ScottBurson
It's trivial, of course, in Common Lisp:

    
    
      (defun flatten (x)
        (if (listp x)
            (reduce #'append (mapcar #'flatten x))
          (list x)))

------
alkonaut
> No one thinks about generic program so that solution will work across all
> types.

Is this even possible to do generically in Java or C#?

You can do it for objects so that it works for ALL types (i.e "object"), but
you can't make it work generically for a type T for any T.

That's why this is such an excellent example of why sum types really are
useful.

Writing the flatten interface without generics (Java List or c# IEnumerable)
is not generic. If you do that it will accept a heterogeneous list - which we
probably don't want.

~~~
throwaway729
Yes you can code up sum types in the object system of Java or C#.

But are you allowed to do that? The problem write-up makes it sound like
you're supposed to be ingesting the interviewer's input. So, how did the
interviewer code up sum types? With tons of nasty casting out of List<Object>,
or with a slightly less terrible object hierarchy mimicking sum types?

That's what makes the question terrible. It implies you're supposed to be
coding against an interface, but in the allowed language (Java) it's totally
non-obvious what the interface is.

I'll submit this criteria for coding question: if a big chunk of your
candidates can't write down the friggen _type_ of the program you want them to
implement -- and if two reasonable people could write down _extremely
different types_ for the same question -- then your question is bad and you
should throw it out.

~~~
alkonaut
Agree 100%.

Title of blog post could be "How do I keep failing to convey the basic
constraints and requirements of a programming interview question?"

------
QML
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.

------
valine
It took me about 5 minutes to solve this in Swift. My first thought was to use
a recursive function that takes an array with a generic type. I'm not sure
that would have been my first thought under the stress of an interview. Its
very likely I would have frozen and wouldn't have produced anything at all.

~~~
collyw
It took me an embarrassingly long 15 minutes in Python. I had already seen the
mention of recursion in the description, so I hope I would have realized that
it was needed. (I am guessing I probably would have looked at recursion at the
point when I realized that there were multiple levels of nesting, and not just
one).

------
relics443
After reading the problem, I spent 5 minutes writing a performant, optimized,
generic, recursive solution in Java (technically Kotlin). I don't consider
that to be one the reasons that I'm a good engineer.

------
artmageddon
I haven't done any interview prep in quite awhile and I feel like even a
question like this would leave me dead in the water, given what the post laid
out.

------
jasonkostempski
If you give the option of any language and they choose Java to work on a
single instance of an untyped array, they should be disqualified.

------
silverwind

      $ node
      > [1, [2, 3], [4, [5, 6]]].toString().split(',').map(n => Number(n))
      [ 1, 2, 3, 4, 5, 6 ]

------
linkedlist007
#python code

def flat(x):

    
    
      if type(x) == type([]) and len(x)==1:
    
        return flat(x[0])
    
      if type(x) == type([]) and len(x)>1:
    
        return flat(x[0]) + flat(x[1:])
    
      else:
    
        return [x]

------
nolite
$ irb 2.3.1 :001 > [1,[2,3], [4, [5,6]]].flatten

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

BOOM!

