InnerAlien

InnerAlien

Hardware|Software

02 Feb 2026

Flatten a Nested List Structure

№7 · 99 Picat Problems
myFlatten([]) = [].
myFlatten([X|Xs]) = Res, list(X) => Res = myFlatten(X) ++ myFlatten(Xs).
myFlatten([X|Xs]) = [X] ++ myFlatten(Xs).
myFlatten(X) = [X].

Explanation

This function transforms a nested list structure like [1,[2,[3,4],5]] into a flat list [1,2,3,4,5]. The challenge is handling elements that might themselves be lists.

Base case for empty list (line 1):

myFlatten([]) = [].
  • An empty list flattens to an empty list

Recursive case for list head (line 2):

myFlatten([X|Xs]) = Res, list(X) => Res = myFlatten(X) ++ myFlatten(Xs).

This line is dense, so let's break it down piece by piece:

  • myFlatten([X|Xs]) = Res - The function returns a value that will be bound to variable Res
  • , list(X) - Guard condition: the comma means "and also check this condition". The function clause only matches if X is a list
  • => - Separates the guard from the function body (this is different from = which is for assignment/unification)
  • Res = myFlatten(X) ++ myFlatten(Xs) - The body: flatten X (a nested list), flatten Xs (the tail), concatenate with ++, and bind the result to Res

Why use Res here? Because we need a place to put the guard condition. We can't write:

myFlatten([X|Xs]) = myFlatten(X) ++ myFlatten(Xs), list(X).  % Syntax error!

The guard must come before the =>, so we introduce an intermediate variable Res:

myFlatten([X|Xs]) = Res, list(X) => Res = myFlatten(X) ++ myFlatten(Xs).

Think of it as: "This function returns Res, but only if X is a list, and here's how to compute Res..."

Recursive case for non-list head (line 3):

myFlatten([X|Xs]) = [X] ++ myFlatten(Xs).
  • Pattern matches a list with head X and tail Xs
  • This clause only matches when the previous one doesn't (when X is not a list)
  • Wrap X in a single-element list [X] and concatenate with the flattened tail

Base case for non-list atom (line 4):

myFlatten(X) = [X].
  • Handles the case where the input is a single element (not a list at all)
  • Returns it wrapped in a list

Complete Example

Here's the full runnable code:

main =>
  go_07.

go_07 =>
  A = [1,[2,[3,4],5]],
  B = myFlatten(A),
  println(B).

myFlatten([]) = [].
myFlatten([X|Xs]) = Res, list(X) => Res = myFlatten(X) ++ myFlatten(Xs).
myFlatten([X|Xs]) = [X] ++ myFlatten(Xs).
myFlatten(X) = [X].

How it works:

  • A = [1,[2,[3,4],5]] is a nested list with multiple levels
  • myFlatten(A) recursively descends into nested lists
  • The list(X) guard determines whether to recurse into X or treat it as an atom
  • println(B) outputs [1,2,3,4,5]

Example Execution

With A = [1,[2,[3,4],5]]:

myFlatten([1,[2,[3,4],5]])
→ [1] ++ myFlatten([[2,[3,4],5]])                             (1 is not a list)
→ [1] ++ (myFlatten([2,[3,4],5]) ++ myFlatten([]))            ([2,[3,4],5] is a list, flatten it)
→ [1] ++ (myFlatten([2,[3,4],5]) ++ [])                       (empty list base case)
→ [1] ++ ([2] ++ myFlatten([[3,4],5]))                        (2 is not a list)
→ [1] ++ ([2] ++ (myFlatten([3,4],5) ++ myFlatten([5])))      ([3,4] is a list)
→ [1] ++ ([2] ++ (([3] ++ myFlatten([4])) ++ myFlatten([5]))) (flatten [3,4])
→ [1] ++ ([2] ++ (([3] ++ [4]) ++ ([5] ++ [])))               (base cases resolve)
→ [1,2,3,4,5]                                                 (all concatenations complete)

The recursion explores the nested structure depth-first, collecting atoms into a single flat list.

Takeaways

  1. Guard syntax in functions: When you need a guard condition in a function, the syntax is:

    functionName(Pattern) = Result, guard_condition => Result = expression.

    Breaking it down:

    • = Result introduces a variable to hold the return value
    • , guard_condition adds the condition (comma means "and")
    • => separates the guard from the body
    • Result = expression computes the actual return value

    Compare this to simpler functions without guards:

    myFlatten([X|Xs]) = [X] ++ myFlatten(Xs).  % No guard, direct = expression
  2. Type checking with guards: The list(X) built-in predicate checks whether X is a list. This allows different clauses to handle lists vs atoms differently. Picat evaluates clauses in order, so the guarded clause (line 2) is tried before the unguarded clause (line 3).

  3. List concatenation: The ++ operator concatenates two lists. Each recursive call builds up the result by concatenating smaller flattened pieces. While ++ is O(n) for the left list, this approach is clean and readable for moderate-sized lists.

  4. Multiple base cases: Unlike earlier problems with a single base case, myFlatten has two: one for empty lists [] and one for non-list atoms. This handles both the recursion termination (when we've consumed all elements) and leaf nodes (individual atoms that need to be wrapped in a list).

  5. Clause ordering matters: Picat tries clauses in the order they're written. The guarded clause list(X) must come before the unguarded clause, or else the unguarded one would always match first.