4

The function (output the power set of a given input)

p() { [ $# -eq 0 ] && echo || (shift; p "$@") |
        while read r ; do echo -e "$1 $r\n$r"; done }

Test Input

p $(echo -e "1 2 3")

Test Output

1 2 3
2 3
1 3
3
1 2
2
1

I have difficulty grasping the recursion in the following code. I tried to understand it by placing some variables inside of the code to denote the level of recursion and the order of execution, but I am still puzzled.

Here are the things I can tell so far:

  1. The subshell's output will not be shown on the final output, as it gets redirected to the read command via pipe
  2. The echo command appends new line for all of its output

The order of execution I see is:

  1. p (1 2 3) -> 1 followed by all combination of output below\n all combination of output below
  2. p (2 3) -> 2 3\n3\n
  3. p (3) -> 3
  4. p () ->

So I think I should have p(2) instead of p(3) on execution #3, but how does that happen? Since shift only goes in one direction.

If I were to use "p(1 2 3 4)" as the input, it is the part that shows "1 2 3" in the output that confuses me.

2
  • 1
    $(echo -e "1\n2\n3") is equivalent to 1 2 3. Perhaps it will be simpler to follow just p 1 2 3? Commented Apr 1, 2013 at 11:29
  • I changed my question accordingly. It certainly improves readability that way. Commented Apr 1, 2013 at 20:21

1 Answer 1

4

The use of -e in the echo command seems to me pure obfuscation, since it could have been written:

p() { [ $# -eq 0 ] && echo || (shift; p "$@") |
      while read r ; do
        echo $1 $r
        echo $r
      done
    }

In other words, "for every set in the power set of all but the first argument (shift; p "$@"), output both that set with and without the first argument."

The bash function works by setting up a chain of subshells, each one reading from the next one, something like this, where each box is a subshell and below it, I've shown its output as it reads each line of input: (I used "" to make "nothing" visible. => means "call"; <- means "read".)

+---------+     +-------+     +-------+     +-------+
| p 1 2 3 | ==> | p 2 3 | ==> |  p 3  | ==> |   p   |
+---------+     +-------+     +-------+     +-------+
  1 2 3 "" <--+-- 2 3 "" <---+-- 3 "" <-----+-- ""
  2 3 ""   <-/              /              /
  1 3 ""   <--+-- 3 ""   <-/              /
  3 ""     <-/                           /
  1 2 ""   <--+-- 2 ""   <---+-- ""   <-/
  2 ""     <-/              /
  1 ""     <--+-- ""     <-/
  ""       <-/
Sign up to request clarification or add additional context in comments.

7 Comments

I do think this really answers the part where I am asking.
@Prometheus: maybe you meant that you don't think that? In case that's true, remember that every subshell has its own stdin and its own argument list, and that | creates a subshell.
I think I understand your post and also I knew what you just wrote in the comment. (My understanding #2). It is just that when I get to the aforementioned part of the execution in my question, I fail to follow where in the tree the script is executing and how the script got there. i.e the part where the last element of argument gets excluded from computation in comparison to another previous branch of execution.
@prometheus: maybe my ascii art helps.
@prometheus, the call to p 1 2 3 reads 2 from p 2 3, and puts a 1 before it. The call to p 2 3 output 2 after reading a blank line from p 3; it put a 2 before the blank. The call to p 3 output a blank line after reading a blank line from p, and did not put anything before it. You can see that from my diagram, I hope. (I changed some arrows which might make it clearer.)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.