3

I'm having issues trying to form code for a problem I want to resolve. It goes like this:

~ Goal: flatten a nested list into one number

  1. If the object is a list, replace the list with the sum of its atoms.
  2. With nested lists, flatten the innermost lists first and work from there.

Example:

  (CONDENSE '(2 3 4 (3 1 1 1) (2 3 (1 2)) 5))

       (2 3 4 (6) (2 3 (3)) 5)

       (2 3 4 (6) (8) 5)

       (28) 

  => 28 

I've tried to implement the flatten list function for this problem and I ended up with this:

(defun condense (lst)
  (cond
    ((null lst) nil)
    ((atom lst) (list lst)))
    (t (append  (flatten (apply #'+ (cdr lst))))))

But it gives me errors :(

Could anyone explain to me what is wrong with my processing/code? How can I improve it?


UPDATE: JUNE 5 2012

(defun condense(lxt)
  (typecase lxt
    (number (abs lxt))
    (list
        (if (all-atoms lxt)
           (calculate lxt)
           (condense (mapcar #'condense lxt))))))

So here, in this code, my true intent is shown. I have a function calculate that performs a calculation based off the values in the list. It is not necessarily the same operation each time. Also, I am aware that I am returning the absolute value of the number; I did this because I couldn't find another way to return the number itself. I need to find a way to return the number if the lxt is a number. And I had it recurse two times at the bottom, because this is one way that it loops on itself infinitely until it computes a single number. NOTE: this function doesn't implement a flatten function anymore nor does it use anything from it.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Zchpyvr
  • 1,119
  • 3
  • 12
  • 26

5 Answers5

2

Is this homework? If so, please mark it as such. Some hints:

  1. are you sure the 'condensation' of the empty list in nil? (maybe you should return a number?)
  2. are you sure the condensation of one element is a list? (maybe you should return a number?)
  3. are you sure the condensation of the last case is a list? (shouldn't you return a number)?

In short, how is your condense ever going to return 28 if all your returned values are lists?

Miron Brezuleanu
  • 2,989
  • 2
  • 22
  • 33
  • hmmm... you make a good point, but I'm trying to implement the flatten method (which turns all atoms into lists, by which, the parens are automatically removed so there remain no nested lists). You're right in the fact that this method won't return a number, but a list with one element. I will have to consider that in the code. – Zchpyvr Jun 05 '12 at 19:31
  • 1
    It seems you're a bit confused about what you're trying to do. If your `condense` is in fact `flatten`, what is `#'+` doing in there? What's the type of the value returned by your function? Why don't you call your function `flatten`? Computers amplify everything, including indecision ;-) – Miron Brezuleanu Jun 05 '12 at 20:23
  • ok, so what I'm trying to do is create a function in my project that will compute based on values from a list and replace that list with a calculation based on the nested list values. It is not necessarily adding up the numbers, as I have introduced in this problem. My goal is to perform different list calculations with one giant nested list and have it all reduce down to one number. At first, I thought using the concept from the lisp flatten function would help me reduce the giant nested list (the parameter) but after looking and absorbing ideas from the other answers, I found this was wrong. – Zchpyvr Jun 05 '12 at 23:38
  • From your last comment, I think you're thinking of a `reduce` (or condense) function that works on trees. There are two ways of implementing such a function: using `flatten` and then using `reduce` on the result, or doing the reducing in place (which you seem to be attempting with condense). Some more hints: you need to pass some extra parameters to your tree reducer: a value for the 'neutral term' of the reduction (0 in case of addition) and a function to reduce two values (`#'+` in case of addition). Look at `reduce` in the hyperspec (http://clhs.lisp.se/Body/f_reduce.htm). – Miron Brezuleanu Jun 06 '12 at 04:28
  • One more thing: if this thing feels hard (as in taking more than a half hour to solve), this may mean that you need to read more about the subject before attempting to solve this (your toolbox of basic functional concepts/techniques/tricks doesn't seem to be filled yet, and it's hard to reinvent those things; reading is an effective way to obtain that knowledge). What book are you using to learn these things? (obviously, using a good book to focus your study in the beginning is a great time saver) – Miron Brezuleanu Jun 06 '12 at 04:33
  • I been thinking about that too recently, but I don't know which resources to start with. Do you have any suggestions? – Zchpyvr Jun 08 '12 at 00:24
  • Have you read SICP? (full text at http://mitpress.mit.edu/sicp/full-text/book/book.html, videos at http://groups.csail.mit.edu/mac/classes/6.001/abelson-sussman-lectures/) It's intensive; I only read the book and did the exercises in 2002 (didn't know about the videos then); there was no measurement of the time it required, but I'd say between 100 and 200 hours (reading, thinking about it and doing the exercises). It's a big time investment, but it provides a very solid foundation for functional programming. There are other FP introductory books - maybe there are some SO questions about this? – Miron Brezuleanu Jun 08 '12 at 16:00
2

Imagine you have your function already. What does it get? What must it produce?

Given an atom, what does it return? Given a simple list of atoms, what should it return?

(defun condense (x)
  (typecase x
    (number  
       ; then what?
       (condense-number x))
    (list
       ; then what?
       (if (all-atoms x)
         (condense-list-of-atoms x) ; how to do that?
         (process-further-somehow
            (condense-lists-inside x))))
    ; what other clauses, if any, must be here?
    ))

What must condense-lists-inside do? According to your description, it is to condense the nested lists inside - each into a number, and leave the atoms intact. So it will leave a list of numbers. To process that further somehow, we already "have" a function, condense-list-of-atoms, right?

Now, how to implement condense-lists-inside? That's easy,

(defun condense-lists-inside (xs)
  (mapcar #'dowhat xs))

Do what? Why, condense, of course! Remember, we imagine we have it already. As long as it gets what it's meant to get, it shall produce what it is designed to produce. Namely, given an atom or a list (with possibly nested lists inside), it will produce a number.

So now, fill in the blanks, and simplify. In particular, see whether you really need the all-atoms check.

edit: actually, using typecase was an unfortunate choice, as it treats NIL as LIST. We need to treat NIL differently, to return a "zero value" instead. So it's better to use the usual (cond ((null x) ...) ((numberp x) ...) ((listp x) ...) ... ) construct.

About your new code: you've erred: to process the list of atoms returned after (mapcar #'condense x), we have a function calculate that does that, no need to go so far back as to condense itself. When you substitute calculate there, it will become evident that the check for all-atoms is not needed at all; it was only a pedagogical device, to ease the development of the code. :) It is OK to make superfluous choices when we develop, if we then simplify them away, after we've achieved the goal of correctness!

But, removing the all-atoms check will break your requirement #2. The calculation will then proceed as follows

(CONDENSE '(2 3 4 (3 1 1 1) (2 3 (1 2)) 5))
==
(calculate (mapcar #'condense '(2 3 4 (3 1 1 1) (2 3 (1 2)) 5)))
== 
(calculate (list 2 3 4 (condense '(3 1 1 1)) (condense '(2 3 (1 2))) 5))
== 
(calculate (list 2 3 4 (calculate '(3 1 1 1)) 
                         (calculate (list 2 3 (calculate '(1 2)))) 5))
== 
(calculate (list 2 3 4 6 (calculate '(2 3 3)) 5))
== 
(calculate (list 2 3 4 6 8 5))
==
28

I.e. it'll proceed in left-to-right fashion instead of the from the deepest-nested level out. Imagining the nested list as a tree (which it is), this would "munch" on the tree from its deepest left corner up and to the right; the code with all-atoms check would proceed strictly by the levels up.

So the final simplified code is:

(defun condense (x)
  (if (listp x)
    (reduce #'+ (mapcar #'condense x))
    (abs x)))

a remark: Looking at that last illustration of reduction sequence, a clear picture emerges - of replacing each node in the argument tree with a calculate application. That is a clear case of folding, just such that is done over a tree instead of a plain list, as reduce is.

This can be directly coded with what's known as "car-cdr recursion", replacing each cons cell with an application of a combining function f on two results of recursive calls into car and cdr components of the cell:

(defun condense (x) (reduce-tree x #'+ 0))
(defun reduce-tree (x f z)
  (labels ((g (x)
            (cond
             ((consp x) (funcall f (g (car x)) (g (cdr x))))
             ((numberp x) x)
             ((null x) z)
             (T (error "not a number")))))
    (g x)))

As you can see this version is highly recursive, which is not that good.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • As always, your logic has been the most lucid. I've updated the question to include the skeleton of your code, namely I merged the two methods together ( is that a good idea? ) and I've found a makeshift solution to infinitely process nested lists. Please comment on my solution. ** I still can't figure out what you mean by do "you really need the `all-atoms` check" – Zchpyvr Jun 06 '12 at 00:07
  • @Zchpyvr why, thanks! But you've made an error in your new code: `condense` returns a number; `mapcar condense` returns a list of numbers; we process these with `calculate`, not with `condense`. About `all-atoms`: what will happen if you call `mapcar condense` on list with both atoms and nested lists? Won't it just work, if we separate out just numbers and lists in general? -- You haven't shown `calculate` yet. You say it'll "be different" - that means to make it a funcional argument and funcall it, `(funcall calc-func a-list-of-numbers)`. – Will Ness Jun 06 '12 at 05:41
  • @Zchpyvr also `typecase` was an unfortunate - wrong - choice, as it treats NIL as LIST. We need to treat NIL separately, to return a "zero value" instead of it. I'll add this remark to the answer. – Will Ness Jun 06 '12 at 05:45
  • @Zchpyvr i've added the simplified final code here. Hopefully you had enough time to arrive at it yourself by now. :) – Will Ness Jun 06 '12 at 23:04
  • ARRGGG.... I came so close. I had reached a cond tree similar to your construction, but it gave me undesired results, so I returned to the pedagogical version. But now looking at your simplified version, I see that that `all-atoms` check was unneeded in the end. I implemented this version, and it works! – Zchpyvr Jun 08 '12 at 00:30
  • @Zchpyvr no worries, you'll get there. The key to top-down approach in recursive programming is to imagine you have the function already, and just use it on reduced cases (then *simplify!*). Another thing is to have a clear contract for the function - its "type" - what it gets, what it produces. Sometimes it's clear upfront, sometimes you clarify it while writing/using the function. The next step is converting a recursive code into an equivalent iterative one, perhaps building the result list [in top-down manner](http://stackoverflow.com/a/10584256/849891) as well. – Will Ness Jun 08 '12 at 05:57
0

Task: With nested lists, flatten the innermost lists first and work from there

sum
   flatten lists

For sum use REDUCE, not APPLY.

For flatten lists you need a loop. Lisp already provides specialized mapping functions.

Slightly more advanced: both the sum and the flatten can be done by a call to REDUCE.

You can also write down the recursion without using a higher-order function like APPLY, REDUCE, ... That's a bit more work.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
0

Here's added the explanation of the errors you were having, actually you were close to solving your problem, just a bit more effort and you would get it right.

; compiling (DEFUN CONDENSE ...)

; file: /tmp/file8dCll3
; in: DEFUN CONDENSE
;     (T (APPEND (FLATTEN (APPLY #'+ (CDR LST)))))
; 
; caught WARNING:
;   The function T is undefined, and its name is reserved 
;   by ANSI CL so that even
;   if it were defined later, the code doing so would not be portable.
; 
; compilation unit finished
;   Undefined function:
;     T
;   caught 1 WARNING condition
;STYLE-WARNING: redefining CONDENSE in DEFUN

(defun condense (lst)
  (cond
    ((null lst) nil)
    ((atom lst) (list lst)))
  ;.------- this is a function call, not a condition
  ;|         (you closed the parens too early)
  (t (append  (flatten (apply #'+ (cdr lst))))))

;; Argument Y is not a NUMBER: (3 1 1 1)
;;    [Condition of type SIMPLE-TYPE-ERROR]

(defun condense (lst)
  (cond
    ((null lst) nil)
    ((atom lst) (list lst));             .-- not a number!
    ;You are calling #'+ -------.        |
    ;on something, which        |  '(3 4 (3 1 1 1) (2 3 (1 2)) 5)
    ; is not a number.          |   |
    (t (append  (flatten (apply #'+ (cdr lst)))))))

;; You probably wanted to flatten first, and then sum

(defun condense (lst)
  (cond
    ((null lst) nil);          .--- returns just the 
    ((atom lst) (list lst));  /     atom 28, you can
    ;  .---------------------/      just remove it.
    (t (append (apply #'+ (flatten lst))))))

;; Now, you are lucky that append would just return the 
;; atom if it's not a list

(defun condense (lst)
  (cond
    ((null lst) nil)
    ((atom lst) (list lst))
    (t (apply #'+ (flatten lst)))))

;; Again, you are lucky because (apply can take enough arguments
;; while your list is reasonably small - this will not always be
;; the case, that is why you need to use something more durable,
;; for example, reduce.

(defun condense (lst)
  (cond
    ((null lst) nil)
    ((atom lst) (list lst))
    (t (reduce #'+ (flatten lst)))))

;; Whoa!

(condense '(2 3 4 (3 1 1 1) (2 3 (1 2)) 5))

This is all given the flatten function actually works.

  • I don't know why, maybe because I don't have a proper `flatten` function, but this code doesn't work on my installation. Based on your reasoning, I'm sure this code will work for adding up all the numbers in the nested list. Based off your code, I can extend it to include more operations. – Zchpyvr Jun 06 '12 at 00:20
0

If your lisp already implements flatten and reduce functions (such as Clojure, which I will use here), you can just do something like:

user=> (defn condense [l] (reduce + 0 (flatten l)))
#'user/condense
user=> (condense [1 [2 [[3 4] 5]]])
15
user=> 

Failing that, a naive implementation of those functions might be:

(defn flatten [l]
  (cond (nil?  l) l
        (coll? l) (let [[h & t] l]
                    (concat (flatten h) (flatten t)))
        true      [l]))

and:

(defn reduce [op initial-value [h & t]]
  (if (nil? t)
    (op initial-value h)
    (op initial-value (reduce op h t))))

But make sure to check the semantics of the particular Lisp you are using. Also, if you are implementing reduce and flatten, you may want to make them tail recursive which I didn't so as to maintain clarity.

In Common Lisp you would do something like:

(defun flatten (l)
    (cond ((null l) l)
          ((atom l) (list l))
          (t        (append (flatten (car l))
                            (flatten (cdr l))))))

and use apply instead of reduce:

(defun condense (l) (apply #'+ (flatten l)))
dsm
  • 10,263
  • 1
  • 38
  • 72
  • I'm not familiar with other dialects, I'm a beginner using emacs Lisp, so unfortunately I don't understand your code. But what I can glean from your answer, I believe your code is superior to what I could envisioned. And looking at your one-line code to the function, I can't help but be intrigued by Clojure...... interesting stuff. – Zchpyvr Jun 05 '12 at 19:38
  • why should I use `apply` instead of `reduce`? – Zchpyvr Jun 06 '12 at 22:45
  • You don't have to, it's just more idiomatic – dsm Jun 07 '12 at 10:49
  • 1
    `apply` is less preferable because the function call may have a built-in limit to the number of arguments: `(apply #'+ xs)` is equivalent to `(+ x1 x2 ... xn)` but there might be a limit to `n` in a given implementation. [This](http://www.lispworks.com/kb/4fbc798cb17237ca852566da005fcc79.html) says "ANSI Common Lisp states that the value of CALL-ARGUMENTS-LIMIT, a positive integer one greater that the maximum number of arguments in a function call, is implementation dependent but must not be smaller than 50." CLISP e.g. has a limit of 4096. – Will Ness Jun 07 '12 at 15:08
  • Seems like using reduce is good for heavy-duty lists, so is there a reason that one would use apply instead of reduce? Perhaps a difference in speed? – Zchpyvr Jun 08 '12 at 00:26
  • @Zchpyvr Common LISP has `reduce` but `apply` is in *any* Lisp. It is fundamental. For anyone who used to work with *those* lisps which have no `reduce`, applying a function to reduce a list is idiomatic. But what's really meant, is `reduce`. – Will Ness Jun 08 '12 at 05:10