Why it’s hard for programmers to write a program to flatten a list?
shekhargulati.com
shekhargulati.com
I've been asked a barrage of these questions for senior positions, even.
Especially sucks because I don't usually program terribly fast or well in an interview environment, so I look worse than I actually am, and often get put on the defensive in these interviews, which I absolutely hate.
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 questions about the innate difficulty of programming and/or the methods by which it is taught.
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=?I had to write this function to flatten a tree of expressions, so I could sort them by precedence, etc and fold them into a tree. I already had a bunch of higher-order primitives for constructing a parser and that was basically a lazy hack for me to get expressions parsed. I eventually implemented a proper expression parser, that was basically the same thing, but produced a flat list from the beginning.
Came in handy when working with rom archives that liked to put everything in its own folder, and a couple of times when I just had a bunch of crap that I didn't want to have to manually dig up anymore.
Other than that? No, not really.
Yes, multiple times in multiple languages. C, C++, Javascript... sometimes "npm install" is not the answer.
List<List<Blah>> records;
We would send a request off to multiple services, wait for all the responses and then the front end would sometimes need all of the lists and other times need it flatten.
How would I do it?
var allitems = records.SelectMany(b => b).ToList();
OTOH, in functional programming languages and frameworks, there is no need to actually write a method that does that since it is usually a library function call.
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.
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.
(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.
[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 thing as a generic simple data structure living in the wild because data is data because it has semantics, without semantics it is just noise.To me, it looks like the root node is |1|.
But maybe I've just been poisoned by Lisp, which tries to use lists as the elusive generic simple data structure.
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?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).
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, [], 1So 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, 1So 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.
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)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...
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), 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).
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.
'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.
Idiomatic Java as of 1.8 now has flatMap on Streams, which all Collection implementations provide.
One of the difficulties of learning Java is that because it has been around so long, old idioms live on on the internet. For example, the Java examples of flatteing a list (which I recently looked at) do not include flatmapping a stream. It does not surprise me that it is there since I believe Java is an incredible and sophisticated language.
And you have to ask, if they were familiar with Java then why didn't they choose one of the idiomatic approaches you suggested? I mean, the "list" part is not an internal requirement so you could easily build a tree/node structure on input...
The absolute hardest thing about hiring developers is hiring people who I can trust to write code. Most applicants are just not adequate programmers and any other redeeming qualities they have can't overcome that core deficiency. I need people who can look at different ways to solve a problem, implement an approach that makes sense, and explain why it does. I need code reviews to be about architecture decisions and correctness not lessons on how to write code. If someone can't do that I really don't care how well they can sell themselves as an "asset".
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.
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.
If the interviewee guesses whether or not security, speed, memory use, portability, maintainability or anything else are important, maybe they will do the same in production code.
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.
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.> What the question really measures is how lucky the candidate was in their education.
Yes! Exactly! This is the other point I wanted to make[1], thanks for doing it for me :)
> flatten =: [: ; <S:0
Nice, I was playing with J quite a lot but somehow missed the spread word (for the curious: http://code.jsoftware.com/wiki/Vocabulary/scapco)
[1] In my comment here: https://news.ycombinator.com/item?id=13725095
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.
Note that in the genuine sum-type case you're wrapping, too.
If I were responsible for hiring developers into a Python post, I would only consider candidates to could show (ideally substantial) work in some other, strongly-typed, lower-level language. It hardly even matters if they haven't seen Python, a decent developer can become a useful Python programmer in a few hours. Python experience does not teach or demonstrate a decent understanding of software structure.
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?"
* 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.
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) 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...
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.
That's because the Perl version is a two-for-one deal: just for fun, Cestith threw in a non-recursive version too. The 'trivial' recursive approach is more or less equivalent to the Python:
sub flatten { map ref($_)? flatten(@$_) : $_, @_ } sub flatten
{
my @in = @{$_[0]};
my @out;
while (@in) {
my $item = shift @in;
if (ref $item eq 'ARRAY') {
unshift @in, @$item;
} else {
push @out, $item;
}
}
return \@out;
}
Input is a reference to the source list, output is a reference to the flattened list.The main difference is that I opted to manipulate a copy of the source list in place to expand sublists rather than recurse.
As others have noted, a recursive version using map can be written much more concisely.
def flatten(nested_list):
output_list = []
for elem in nested_list:
if type(elem) == list:
output_list.extend(flatten(elem))
else:
output_list.append(elem)
return output_list
I think that's easier to read than the following, and appears to run slightly faster as well? import collections
def flatten(nested_list):
if isinstance(nested_list, collections.Iterable):
return [a for i in nested_list for a in flatten(i)]
else:
return [nested_list] 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.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.
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static junit.framework.TestCase.assertEquals;
public class FlattenList {
@Test
public void test() {
// [1, [2, 3], [4, [5, 6]], [7, [8, [9, 10]]]]
final FlattenableList<Integer> list = new FlattenableList<>();
list.add(new SingleFlattenable<>(1));
list.add(new ListOfSingles<>(Arrays.asList(2, 3)));
list.add(new Flattenable<Integer>(){{
add(new SingleFlattenable<>(4));
add(new ListOfSingles<>(Arrays.asList(5, 6)));
}});
list.add(new Flattenable<Integer>(){{
add(new SingleFlattenable<>(7));
add(new Flattenable<Integer>(){{
add(new SingleFlattenable<>(8));
add(new ListOfSingles<>(Arrays.asList(9, 10)));
}});
}});
assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), list.flatten());
}
public static class FlattenableList<T> {
final List<Flattenable<T>> list = new ArrayList<>();
void add(final Flattenable<T> item) {
list.add(item);
}
List<T> flatten() {
return list.stream().flatMap(Flattenable::flatten).collect(Collectors.toList());
}
}
private static class Flattenable<T> {
final List<Flattenable<T>> contents = new ArrayList<>();
Stream<T> flatten() {
return contents.stream().flatMap(Flattenable::flatten);
}
void add(final Flattenable<T> content) {
contents.add(content);
}
}
private static class ListOfSingles<T> extends Flattenable<T> {
final List<T> list;
ListOfSingles(final List<T> list) {
this.list = list;
}
@Override
Stream<T> flatten() {
return list.stream();
}
}
private static class SingleFlattenable<T> extends Flattenable<T> {
final T item;
SingleFlattenable(final T item) {
this.item = item;
}
@Override
Stream<T> flatten() {
return Stream.of(item);
}
}
} 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. [item for sublist in l for item in sublist]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.
If you do want to do this, the better method is:
itertools.chain.from_iterable()
That has the advantage of being lazy, as opposed to a list comprehension, and has the potential to be more optimised. It also doesn't rely on the somewhat obscure and relatively hard to read nested list comprehension syntax.Docs: https://docs.python.org/3.6/library/itertools.html#itertools...
flatten = (lambda f: lambda *args:
(lambda x: lambda *args2: f(x(x))(*args2))
(lambda x: lambda *args2: f(x(x))(*args2))(*args))(
lambda f: lambda l: [item for sublist in l for item in f(sublist)] if isIterable(l) else [l]
) isinstance(el, collections.Iterable) and not isinstance(el, str)
Is the most common one. The first clause to check if it's an iterable (as per the collections.Iterable abstract base class - for those unfamiliar, basically a check for the `__iter__()` method), the second to check it's not a string, which is also iterable, but generally you don't want to split into individual letters - and if you do, you'll need to add more checks as strings split to other 1-length strings that are still iterable, making an infinite loop.Of course, the original problem did state "lists" explicitly, so I guess you could just type check for lists, but that's really unpythonic and not useful in the real world.
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) return args.toString().split(/[\s,]+/).filter(/* ... */);you fail.
(I don't intend to be mean to you, I intend to be mean to the question ;-) )
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.
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.
Indeed. And those things came up in writing a fairly simple utility function, so they're going to come up later in some large codebase.
While you're seldom hiring programmers to write utility functions like this, this kind of test shows whether the candidates have a basic familiarity with topics that will definitely come into play in their day-to-day work. I regularly run into "professionals" who can't structure their code in sensible ways, who don't even try to name things well, don't think about edge cases, etc. As it turns out, you don't want these people gluing together library calls either, because you'll end up with a mess on your hands.
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.
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; 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.Anyway, I'm glad the first response to "this is trivial in Perl" is another, simpler Perl response rather than "Perl is dead".
def flatten(array):
queue = [array]
result = []
while len(queue) > 0:
item = queue.pop(0)
if isinstance(item, list):
for sub_item in item:
queue.append(sub_item)
else:
result.append(item)
return result
a = [1,[2,3], [4, [5,6]]]
print 'input:', a, '\noutput:', flatten(a)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), []);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.
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.
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.
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?
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.
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?
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.
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 = retractDrawing analogy to lisp, we need to be able to answer "is this a list or an atom?"
Note that the actual structure involved is "really" a rose tree.
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...
(defun flatten (x)
(if (listp x)
(reduce #'append (mapcar #'flatten x))
(list x)))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.
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.
Title of blog post could be "How do I keep failing to convey the basic constraints and requirements of a programming interview question?"
Ok point being: I didn't believe what I was learning was relevant to industry, in particular linked lists.
$ node
> [1, [2, 3], [4, [5, 6]]].toString().split(',').map(n => Number(n))
[ 1, 2, 3, 4, 5, 6 ]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]=> [1, 2, 3, 4, 5, 6]
BOOM!