emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [babel] Code for simple set-operations on two tables. Asking for some input.
@ 2011-12-26 11:00 Marc-Oliver Ihm
  2011-12-27 20:53 ` Eric Schulte
  0 siblings, 1 reply; 5+ messages in thread
From: Marc-Oliver Ihm @ 2011-12-26 11:00 UTC (permalink / raw)
  To: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 1360 bytes --]

Hello,

Please find attached the elisp-file and a tutorial (as an org-file) for org-babel-table-proc.

It provides some simple set-operations (mostly merge and intersect), which treat org-mode tables as sets.

An example for merging two tables would be:

> #+name: lower
> | 2 | b |
> | 4 | d |
> | 5 | e |
> | 6 | h |
>
> #+name: upper
> |  1 | A |
> |  3 | C |
> |  4 | D |
> | 10 | J |
> |  2 | B |
>
> #+begin_src emacs-lisp :var t1=lower :var t2=upper
>    (babel-table-proc-merge t1 t2)
> #+end_src
>
> #+results:
> |  1 |   | A |
> |  2 | b | B |
> |  3 |   | C |
> |  4 | d | D |
> |  5 | e |   |
> |  6 | h |   |
> | 10 |   | J |

which merges the two input tables lower and upper into a single table, even, if they do not have all
keys (from the first column) in common.
In general this merges information from two different sources without loosing anything.

The package org-babel-table-proc provides these and a few other operations, but definitely still needs
some debugging and polishing.

However, before finishing this work, I would like to ask, if something like this is already present
in babel or the library of babel ?

Also, I am not sure, which prefix to use for packages and functions; is org-babel-table-proc the right name or can
it be shortened to ob-table-proc.

Any input would be very welcome !

With kind regards, Marc-Oliver Ihm

[-- Attachment #2: org-babel-table-proc.el --]
[-- Type: text/plain, Size: 6848 bytes --]

;;; org-babel-table-proc.el --- Common operations on tables for use with orgmode and lob

;; Copyright (C) 2010-2011
;;   Free Software Foundation, Inc.

;; Author: Marc-Oliver Ihm
;; Keywords: tables library of babel orgmode
;; Version: 0.01

;;; License:

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; Purpose:
;;  
;;  Common operations on tables for use with orgmode and lob:
;;   - simple set operations (intersection and merge)
;;   - filtering one table according to another
;; 
;;
;; Setup:
;;
;;  (require 'org-babel-table-proc)
;;
;; Further reading:
;;
;;  See the file org-babel-table-proc.org for complete examples.

;;

;;; Code:


(defun babel-table-proc-keep (t1 t2)
  "Keep only those keys from the second table, that appear within the first"
  (lob-tbl-filter 'keep t1 t2)
  )

(defun babel-table-proc-remove (t1 t2)
  "Remove those keys from the second table, that appear within the first"
  (lob-tbl-filter 'remove t1 t2)
  )

(defun lob-tbl-filter (what t1 t2)
  "Internal function to do the work of babel-table-proc-keep and -remove"
  (let (keys
        result)
    (setq keys (mapcar 'car t1))
    (dolist (line t2) 
      (if (equal (member (car line) keys)
                 (equal what 'keep))
          (setq result (cons line result))
        )
      )
    (nreverse result)
    )
)

(defun babel-table-proc-merge (&rest tables)
  "Merge two tables by first column; sort the result"
  (babel-table-proc-two-tables-to-one 'merge tables))

(defun babel-table-proc-intersect (&rest tables)
  "Intersect two tables by first column; sort the result"
  (babel-table-proc-two-tables-to-one 'intersect tables))

(defun babel-table-proc-two-tables-to-one (what tables)
  "Internal function to do the work of babel-table-proc-merge and -intersect"
  (let (is-all-numbers          
        less-than-function 
        equal-function
        conversion-function
        format-specifier
        rests-of-tables
        rest-of-rests-of-tables
        rest-of-table
        widths-of-tables
        current-key
        current-key-in-intersection
        result-table
        result-line
        i)

    ;; Find out, if all keys in all tables are numbers or if there are strings among them
    (setq is-all-numbers
          (catch 'not-a-number
            (dolist (table tables) (dolist (line table) (unless (numberp (car line)) (throw 'not-a-number 'nil))))
            't))

    ;; prepare functions to treat table contents in a unified way
    (setq format-specifier (if is-all-numbers "%g" "%s"))
    (setq conversion-function (if is-all-numbers
                                  (lambda (x) x)
                                (lambda (x) (if (numberp x) (number-to-string x) x))
                                ))
    (setq less-than-function (lambda (x y) (if is-all-numbers (< x y) (string< (funcall conversion-function x) (funcall conversion-function y)))))
    (setq equal-function (lambda (x y) (if is-all-numbers (= x y) (string= (funcall conversion-function x) (funcall conversion-function y)))))
    

    ;; sort tables
    (setq tables (mapcar (lambda (table) (sort table (lambda (x y) (funcall less-than-function (car x) (car y))))) tables))

    ;; compute and remember table widths
    (setq widths-of-tables (mapcar (lambda (x) (length (car x))) tables))

    (setq rests-of-tables (copy-list tables))

    ;; loop as long as the rest of table still contains lines
    (while (progn 
             ;; find lowest key among all tables, which is the key for the next line of the result
             (setq current-key nil)
             (setq current-key-in-intersection 't)
             (dolist (rest-of-table rests-of-tables) (when (and rest-of-table
                                                                (or (null current-key) 
                                                                    (funcall less-than-function (caar rest-of-table) current-key)))
                                                       (setq current-key (caar rest-of-table))))
             current-key)

      (progn

        (setq result-line (list current-key))
        
        ;; go through all tables and collect one line for the result table ...
        (setq i 0) ; table-count
        ;; cannot use dolist like above, because we need to modify the cons-cells
        (setq rest-of-rests-of-tables rests-of-tables)
        (while (progn
                 (setq rest-of-table (car rest-of-rests-of-tables))
                 (setq i (1+ i))
                 ;; if table contains current key 
                 (if (and rest-of-table
                          (funcall equal-function current-key (caar rest-of-table)))
                     ;; then copy rest of line
                     (progn (nconc result-line (cdar rest-of-table))
                            ;; and shorten rest
                            (setcar rest-of-rests-of-tables (cdar rest-of-rests-of-tables))
                            ;; and check, if current-key appears again
                            (when (and (caadr rest-of-table)
                                       (funcall equal-function current-key (caadr rest-of-table))
                                       )
                              (error (concat "Key '" format-specifier "'appears twice within input table %i") (funcall conversion-function current-key) i)
                              )
                            )
                   ;; otherwise fill with nil and do not shorten rest of table
                   (progn 
                     (setq current-key-in-intersection nil)
                     (nconc result-line (make-list (1- (elt widths-of-tables (1- i))) ""))
                     )
                   )
                 
                 (setq rest-of-rests-of-tables (cdr rest-of-rests-of-tables))
                 ;; condition for while-loop
                 rest-of-rests-of-tables 
                 )
          )
        (if (or (eq what 'merge) current-key-in-intersection)
          (setq result-table (cons result-line result-table)) ; store away line
          )
        )
      )
    (nreverse result-table)
    )
  )

(provide 'org-babel-table-proc)

[-- Attachment #3: org-babel-table-proc.org --]
[-- Type: text/plain, Size: 4796 bytes --]



* Introduction

  This file (org-babel-table-proc.org) contains the documentation and living examples for
  org-babel-table-proc.el, which is a libary of functions, which in turn makes it easier
  to process tables with babel, which is part of orgmode.

  The functions within this library take two tables and apply simple filter or set
  operations on them. The first column within each table plays the role of a key, which
  determines how the table-lines is treated; the remaining columns are payload.

* Installation

  To install the library org-babel-table-proc.el within emacs you need to do two things:

  First: Move the file into any directory within your emacs load-path
  (see [[id:b3ec5718-8a32-4b1e-8203-f620aa819d82][Finding out, what your load-path is]] for details)

  Second: Put the following line

  (require 'org-babel-table-proc)

  into your emacs-startup file (".emacs" in most cases). For now, it is even
  enough to execute the following source-code-block (see [[id:170aa925-e8e1-4937-9ed7-c889b83e8419][Executing code blocks]])

#+begin_src emacs-lisp
  (require 'org-babel-table-proc)
#+end_src

* Some input tables, that are used within the examples

  For the examples in the rest of this document, we will use two input tables: "lower" and
  "upper" (see below). Each of these tables associates a position within the alphabet with
  a letter (either lower or upper case).
  
  For example the table line "| 2 | b |" below simply expresses the fact, that the letter "b"
  comes at position 2 within the alphabet.

#+name: lower
| 2 | b |
| 4 | d |
| 5 | e |
| 6 | h |

#+name: upper
|  1 | A |
|  3 | C |
|  4 | D |
| 10 | J |
|  2 | B |

  Please note further, that those tables have names
  (given by the line "#+name: lower").

  A third table, named "keys", has only one column, which will be used as keys to select rows from
  the other tables.

#+name: keys
| 1 |
| 2 |
| 4 |

* Set operations

#+begin_src emacs-lisp :var t1=lower :var t2=upper
   (babel-table-proc-merge t1 t2)
#+end_src

#+results:
|  1 |   | A |
|  2 | b | B |
|  3 |   | C |
|  4 | d | D |
|  5 | e |   |
|  6 | h |   |
| 10 |   | J |


#+begin_src emacs-lisp :var t1=keys :var t2=upper
   (babel-table-proc-remove t1 t2)
#+end_src

#+results:
|  3 | C |
| 10 | J |




* Appendices
** Executing code blocks
   :PROPERTIES:
   :ID:       170aa925-e8e1-4937-9ed7-c889b83e8419
   :END:

   Throughout this document you will be urged to execute code blocks.

   This refers to orgmode's feature of beeing able to run code that
   is embedded within the document; such code blocks begin with 
   a line "#+begin_src" and end with "#+end_src".

   Throughout this document you will find many such code blocks, just see
   below for an example.

   For a complete description of the many great things, that can be done
   with such code blocks, please refer to the orgmode manual, chapter 14
   ("Working with source code").

   For the moment however, it is enough to know, how to execute those
   blocks: Just move the cursor to the line below (the one starting with
   "#+begin_src") and press C-c C-c (this means: Hold down the control-key
   an press the key "c" twice).

#+begin_src emacs-lisp
   (current-time-string)
#+end_src

   The result of executing this block of code can be seen above, following
   the line starting with "#+results:". It is simply the current time.

   If you execute this code block again, the old result is overwritten with a new
   timestamp. If you want to remove the whole result to restore the "clean" state of this
   buffer, please check out [[id:224b0c4a-6b2c-495c-afc1-8a52c9dea778][Cleaning up results]].

   For the purpose of this document, you do not need to know much more about executing
   code blocks and you may read on.
   
** Finding out, what your load-path is
   :PROPERTIES:
   :ID:       b3ec5718-8a32-4b1e-8203-f620aa819d82
   :END:

#+begin_src emacs-lisp
   (mapcar 'list load-path)
#+end_src

** Cleaning up results
   :PROPERTIES:
   :ID:       224b0c4a-6b2c-495c-afc1-8a52c9dea778
   :END:

   Execute the code block below (see [[id:170aa925-e8e1-4937-9ed7-c889b83e8419][Executing code blocks]]) to clear any
   results that have been generated previously from executing other code
   blocks within this document.

   However, as each execution of a code block overwrites any results of
   previous executions, there is no real need to clean up anything.
   If at all, than only to tidy up the file, before passing it along.

#+begin_src emacs-lisp
(save-excursion
(beginning-of-buffer)
     (while (re-search-forward "^#\\+results:\n\\(^\|\\|:.*\n\\)*\n" nil t)
       (replace-match ""))
)
#+end_src

#+results:

   Note, that the only remaining result (introduced by the line "#+results:") ist the one
   immediately above.


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [babel] Code for simple set-operations on two tables. Asking for some input.
  2011-12-26 11:00 [babel] Code for simple set-operations on two tables. Asking for some input Marc-Oliver Ihm
@ 2011-12-27 20:53 ` Eric Schulte
  2011-12-28  8:56   ` Marc-Oliver Ihm
  2012-01-14  9:27   ` Marc-Oliver Ihm
  0 siblings, 2 replies; 5+ messages in thread
From: Eric Schulte @ 2011-12-27 20:53 UTC (permalink / raw)
  To: Marc-Oliver Ihm; +Cc: emacs-orgmode

Marc-Oliver Ihm <marc-oliver.ihm@online.de> writes:

> Hello,
>
> Please find attached the elisp-file and a tutorial (as an org-file) for org-babel-table-proc.
>
> It provides some simple set-operations (mostly merge and intersect),
> which treat org-mode tables as sets.
>
> An example for merging two tables would be:
>
>> #+name: lower
>> | 2 | b |
>> | 4 | d |
>> | 5 | e |
>> | 6 | h |
>>
>> #+name: upper
>> |  1 | A |
>> |  3 | C |
>> |  4 | D |
>> | 10 | J |
>> |  2 | B |
>>
>> #+begin_src emacs-lisp :var t1=lower :var t2=upper
>>    (babel-table-proc-merge t1 t2)
>> #+end_src
>>
>> #+results:
>> |  1 |   | A |
>> |  2 | b | B |
>> |  3 |   | C |
>> |  4 | d | D |
>> |  5 | e |   |
>> |  6 | h |   |
>> | 10 |   | J |
>
> which merges the two input tables lower and upper into a single table,
> even, if they do not have all keys (from the first column) in common.
> In general this merges information from two different sources without
> loosing anything.
>
> The package org-babel-table-proc provides these and a few other
> operations, but definitely still needs some debugging and polishing.
>
> However, before finishing this work, I would like to ask, if something
> like this is already present in babel or the library of babel ?
>

Nothing like this is currently available in the library of Babel.  I do
think that such a library of "indexed set operations" would be a useful
addition to the library.  As for packaging, although it may be more
comfortable to develop these functions in the manner you are currently
using (with side-by-side .el and .org files), when it comes time to add
these code blocks to the library of babel, I would recommend *combining*
the elisp source and the explanatory prose as a single new subtree in
the existing org-babel-library-of-babel.org file.

Rather than exposing these functions as elisp functions they could be
exposed as Babel code blocks, e.g., 

    #+begin_src emacs-lisp :var t1=lower :var t2=upper
       (babel-table-proc-merge t1 t2)
    #+end_src

would instead become

    #+call: proc-merge(lower, upper)

>
> Also, I am not sure, which prefix to use for packages and functions;
> is org-babel-table-proc the right name or can it be shortened to
> ob-table-proc.
>

If you package these as code blocks in the library of babel then there
is no issue of prefixing, just pick meaningful names for the code blocks
and don't define any global elisp functions.

>
> Any input would be very welcome !
>

I hope the above helps, please let me know if I have been unclear or if
you have any other questions.

I look forward to your patch!

Cheers,

>
> With kind regards, Marc-Oliver Ihm
>
>

-- 
Eric Schulte
http://cs.unm.edu/~eschulte/

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [babel] Code for simple set-operations on two tables. Asking for some input.
  2011-12-27 20:53 ` Eric Schulte
@ 2011-12-28  8:56   ` Marc-Oliver Ihm
  2012-01-14  9:27   ` Marc-Oliver Ihm
  1 sibling, 0 replies; 5+ messages in thread
From: Marc-Oliver Ihm @ 2011-12-28  8:56 UTC (permalink / raw)
  Cc: emacs-orgmode

Am 27.12.2011 21:53, schrieb Eric Schulte:
> Marc-Oliver Ihm<marc-oliver.ihm@online.de>  writes:
>
>> Hello,
>>
>> Please find attached the elisp-file and a tutorial (as an org-file) for org-babel-table-proc.
>>
>> It provides some simple set-operations (mostly merge and intersect),
>> which treat org-mode tables as sets.
>>
>> An example for merging two tables would be:
>>
>>> #+name: lower
>>> | 2 | b |
>>> | 4 | d |
>>> | 5 | e |
>>> | 6 | h |
>>>
>>> #+name: upper
>>> |  1 | A |
>>> |  3 | C |
>>> |  4 | D |
>>> | 10 | J |
>>> |  2 | B |
>>>
>>> #+begin_src emacs-lisp :var t1=lower :var t2=upper
>>>     (babel-table-proc-merge t1 t2)
>>> #+end_src
>>>
>>> #+results:
>>> |  1 |   | A |
>>> |  2 | b | B |
>>> |  3 |   | C |
>>> |  4 | d | D |
>>> |  5 | e |   |
>>> |  6 | h |   |
>>> | 10 |   | J |
>>
>> which merges the two input tables lower and upper into a single table,
>> even, if they do not have all keys (from the first column) in common.
>> In general this merges information from two different sources without
>> loosing anything.
>>
>> The package org-babel-table-proc provides these and a few other
>> operations, but definitely still needs some debugging and polishing.
>>
>> However, before finishing this work, I would like to ask, if something
>> like this is already present in babel or the library of babel ?
>>
>
> Nothing like this is currently available in the library of Babel.  I do
> think that such a library of "indexed set operations" would be a useful
> addition to the library.  As for packaging, although it may be more
> comfortable to develop these functions in the manner you are currently
> using (with side-by-side .el and .org files), when it comes time to add
> these code blocks to the library of babel, I would recommend *combining*
> the elisp source and the explanatory prose as a single new subtree in
> the existing org-babel-library-of-babel.org file.
>
> Rather than exposing these functions as elisp functions they could be
> exposed as Babel code blocks, e.g.,
>
>      #+begin_src emacs-lisp :var t1=lower :var t2=upper
>         (babel-table-proc-merge t1 t2)
>      #+end_src
>
> would instead become
>
>      #+call: proc-merge(lower, upper)
>
>>
>> Also, I am not sure, which prefix to use for packages and functions;
>> is org-babel-table-proc the right name or can it be shortened to
>> ob-table-proc.
>>
>
> If you package these as code blocks in the library of babel then there
> is no issue of prefixing, just pick meaningful names for the code blocks
> and don't define any global elisp functions.
>
>>
>> Any input would be very welcome !
>>
>
> I hope the above helps, please let me know if I have been unclear or if
> you have any other questions.
>
> I look forward to your patch!
>
> Cheers,
>
>>
>> With kind regards, Marc-Oliver Ihm
>>
>>
>

Hi Eric,

Thanx, that was exactly the input I was looking for.
Now I have something nice to code over the vacations :-)

I will try to merge it all into the existing library without using global functions and come back with a patch ...

with kind regards, Marc-Oliver Ihm

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [babel] Code for simple set-operations on two tables. Asking for some input.
  2011-12-27 20:53 ` Eric Schulte
  2011-12-28  8:56   ` Marc-Oliver Ihm
@ 2012-01-14  9:27   ` Marc-Oliver Ihm
  2012-01-15 16:10     ` Eric Schulte
  1 sibling, 1 reply; 5+ messages in thread
From: Marc-Oliver Ihm @ 2012-01-14  9:27 UTC (permalink / raw)
  To: Eric Schulte; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 688 bytes --]

Hello,

please find attached an early draft of lob-table-operations.org.

It already has a reasonable documentation and working examples, so it should be easy to play with.

Some features are still missing (e.g. handling of column names and hlines)
and the coding needs some improvement (using the cl-package ?).
So it is probably not yet fit for official inclusion into the library of babel.

with kind regards,
Marc-Oliver Ihm

As a side note: I am very pleased and fascinated, how easily babel and org have made the task of keeping together
all aspects of development; from user documentation to implementation and (of course !) organisation.

This has made my coding even more fun !


[-- Attachment #2: lob-table-operations.org --]
[-- Type: text/plain, Size: 11584 bytes --]


* Table Operations
  :PROPERTIES:
  :ID:       1f8371eb-65e8-416d-ac22-b77431a7df3f
  :END:

** Documentation
   :PROPERTIES:
   :ID:       90a0c9e2-6092-492e-bd4b-c1c737087ac5
   :END:

*** Introduction

    This section within the library of babel implements some simple operations, that act on
    one or more tables to produce other tables.
    
    The known operations are grouped in two categories:
    
    - Filtering the rows of a single table
    - Merging two tables into one
     
*** Example tables

    To demonstrate we need three tables: upper, lower and keys.

    Please note, that column-names are currently not supported !

#+name: upper
|  1 | A |
|  3 | C |
|  4 | D |
| 10 | J |
|  2 | B |

#+name: lower
| 2 | b |
| 4 | d |
| 5 | e |
| 6 | h |

#+name: keys
| 1 |
| 2 |
| 4 |

    The tables upper and lower both have two columns and associate a numerical position
    within the alphabet with the matching letter. E.g. the row "| 1 | A |" within table
    upper, just states that the letter "A" comes at position 1 within the alphabet.

    Nearly the same is true for table lower, only that it contains lower case letters only
    and deliberatly not quite the same ones as table upper.

    The table keys finally, contains keys (i.e. positions within the alphabet), that can be
    used to select rows from either table upper or lower.

*** Filtering a table

**** Keeping rows

     Let's say, we want to select the upper-case letters (i.e. rows from the table upper),
     that are given in table keys (i.e. the first, second and fourth letter).

     This can be described as filtering table upper and keeping only those rows, that are
     specified in table keys.

     As a babel-call, this reads:

#+call: table-operations-filter-keep(upper,keys)

#+results: table-operations-filter-keep(upper,keys)
| 1 | A |
| 4 | D |
| 2 | B |

     Which gives exactly those rows from table upper, that are specified in keys.

**** Removing rows

     Now if on the contrary you want to filter table upper to remove any rows, which are given
     in table keys:

#+call: table-operations-filter-remove(upper,keys)

#+results: table-operations-filter-remove(upper,keys)
|  3 | C |
| 10 | J |

*** Combining tables

    Now, if we have a look at tables upper and lower (and drop table keys for the moment),
    it comes to combining tables.

    (Here we only look at combining two tables for simplicity, however, all examples can
    be easily scaled up to seven tables.)

**** Merging rows

     We have two table, one with upper-case letter and one with lower-case. What now if you
     want to have only one table, which contains both, upper- and lower-case ?
     
     Probably you want to merge them:

#+call: table-operations-combine-merge(upper,lower)

#+results: table-operations-combine-merge(upper,lower)
|  1 | A |   |
|  2 | B | b |
|  3 | C |   |
|  4 | D | d |
|  5 |   | e |
|  6 |   | h |
| 10 | J |   |

     This results-table combines both upper- and lower-case letters and lists them by
     their position within the alphabet.

     Speaking more abstract, the result is a single table. Its rows are gained by
     combining rows from tables upper and lower with the same key.

**** Intersecting rows

     If you only want the rows, that are complete (i.e. have both lower- and upper-case
     letters, you should compute the intersection:

#+call: table-operations-combine-intersect(upper,lower)

#+results: table-operations-combine-intersect(upper,lower)
| 2 | B | b |
| 4 | D | d |

     which has only those keys, that apear in both tables.

** Internals

   This section is not required reading for normal users of these table operations. Only
   if you are curious about its implementation or development, you might want to have a
   look.

*** Implementation
   
   Here is the actual lisp code, that implements the functionality of [[id:1f8371eb-65e8-416d-ac22-b77431a7df3f][Table Operations]].

**** table-operations-filter
***** Directly callable blocks

#+name: table-operations-filter-keep
#+begin_src emacs-lisp :noweb yes :var table=() :var filter=() 
<<lob-table-operations-filter-defun>>
(lob-table-operations-filter 'keep table filter)
#+end_src

#+name: table-operations-filter-remove
#+begin_src emacs-lisp :noweb yes :var table=() :var filter=() 
  <<lob-table-operations-filter-defun>>
  (lob-table-operations-filter 'remove table filter)
#+end_src

***** Included defuns

#+name: lob-table-operations-filter-defun
#+begin_src emacs-lisp
  (defun lob-table-operations-filter (what table filter)
    "Internal function for table-operations in orgmodes library of babel"
    (let (keys
          result)
      (setq keys (mapcar 'car filter))
      (dolist (line table) 
        (if (equal (not (not (member (car line) keys)))
                   (equal what 'keep))
            (setq result (cons line result))
          )
        )
      (nreverse result)
      )
    )
#+end_src

**** table-operations-combine
***** Directly callable blocks

#+name: table-operations-combine-merge 
#+begin_src emacs-lisp :noweb yes :var t1=() :var t2=() :var t3=() :var t4=() :var t5=() :var t6=() :var t7=()
  <<lob-table-operations-combine-defun>>
  (let ((tables (list t1 t2 t3 t4 t5 t6 t7)))
  (lob-table-operations-combine 'merge tables)
  )
#+end_src

#+name: table-operations-combine-intersect
#+begin_src emacs-lisp :noweb yes :var t1=() :var t2=() :var t3=() :var t4=() :var t5=() :var t6=() :var t7=()
  <<lob-table-operations-combine-defun>>
  (let ((tables (list t1 t2 t3 t4 t5 t6 t7)))
    (lob-table-operations-combine 'intersect tables)
    )
#+end_src

***** Included defuns

#+name: lob-table-operations-combine-defun
#+begin_src emacs-lisp
  (defun lob-table-operations-combine (what tables)
    "Internal function for table-operations in orgmode library of babel"
    (let (is-all-numbers          
          less-than-function 
          equal-function
          conversion-function
          format-specifier
          rest-of-tables
          rests-of-tables
          rest-of-rests-of-tables
          rest-of-table
          widths-of-tables
          current-key
          current-key-in-intersection
          result-table
          result-line
          i
          )
  
      ;; remove possible empty trailing tables
      (setq rest-of-tables tables)
      (while (cadr rest-of-tables) (setq rest-of-tables (cdr rest-of-tables)))
      (setcdr rest-of-tables nil)
  
      ;; Find out, if all keys in all tables are numbers or if there are strings among them
      (setq is-all-numbers
            (catch 'not-a-number
              (dolist (table tables) (dolist (line table) (unless (numberp (car line)) (throw 'not-a-number 'nil))))
              't))
      
      ;; Prepare functions to treat table contents in a unified way
      (setq format-specifier (if is-all-numbers "%g" "%s"))
      (setq conversion-function (if is-all-numbers
                                    (lambda (x) x)
                                  (lambda (x) (if (numberp x) (number-to-string x) x))
                                  ))
      (setq less-than-function (lambda (x y) (if is-all-numbers (< x y) (string< (funcall conversion-function x) (funcall conversion-function y)))))
      (setq equal-function (lambda (x y) (if is-all-numbers (= x y) (string= (funcall conversion-function x) (funcall conversion-function y)))))
      
      
      ;; sort tables
      (setq tables (mapcar (lambda (table) (sort table (lambda (x y) (funcall less-than-function (car x) (car y))))) tables))
      
      ;; compute and remember table widths
      (setq widths-of-tables (mapcar (lambda (x) (length (car x))) tables))
      
      (setq rests-of-tables (copy-list tables))
      
      ;; loop as long as the rest of table still contains lines
      (while (progn 
               ;; find lowest key among all tables, which is the key for the next line of the result
               (setq current-key nil)
               (setq current-key-in-intersection 't)
               (dolist (rest-of-table rests-of-tables) (when (and rest-of-table
                                                                  (or (null current-key) 
                                                                      (funcall less-than-function (caar rest-of-table) current-key)))
                                                         (setq current-key (caar rest-of-table))))
               current-key)
        
        (progn
          
          (setq result-line (list current-key))
          
          ;; go through all tables and collect one line for the result table ...
          (setq i 0) ; table-count
          ;; cannot use dolist like above, because we need to modify the cons-cells
          (setq rest-of-rests-of-tables rests-of-tables)
          (while (progn
                   (setq rest-of-table (car rest-of-rests-of-tables))
                   (setq i (1+ i))
                   ;; if table contains current key 
                   (if (and rest-of-table
                            (funcall equal-function current-key (caar rest-of-table)))
                       ;; then copy rest of line
                       (progn (nconc result-line (cdar rest-of-table))
                              ;; and shorten rest
                              (setcar rest-of-rests-of-tables (cdar rest-of-rests-of-tables))
                              ;; and check, if current-key appears again
                              (when (and (caadr rest-of-table)
                                         (funcall equal-function current-key (caadr rest-of-table))
                                         )
                                (error (concat "Key '" format-specifier "'appears twice within input table %i") (funcall conversion-function current-key) i)
                                )
                              )
                     ;; otherwise fill with nil and do not shorten rest of table
                     (progn 
                       (setq current-key-in-intersection nil)
                       (nconc result-line (make-list (1- (elt widths-of-tables (1- i))) ""))
                       )
                     )
                   
                   (setq rest-of-rests-of-tables (cdr rest-of-rests-of-tables))
                   ;; condition for while-loop
                   rest-of-rests-of-tables 
                   )
            )
          (if (or (eq what 'merge) current-key-in-intersection)
              (setq result-table (cons result-line result-table)) ; store away line
            )
          )
        )
      (nreverse result-table)
      )
    )
#+end_src

: lob-table-operations-combine

**** Debugging and testing
***** Clean up
#+begin_src emacs-lisp
  (save-excursion
    (beginning-of-buffer)
    (while (re-search-forward "^#\\+results:.*\n\\(^\|.+\n\\)*\n" nil t)
      (replace-match ""))
    )
#+end_src

#+results:

***** Byte Compilation

   (byte-compile 'lob-table-operations-combine)
   (byte-compile 'lob-table-operations-filter)

*** Development
**** Versions and history

     - [2012-01-07 Sa] Version 0.01 which comes as a single org-file (no special .el-file
       needed any more). Combines and restructures documentation and implementation.

**** Bugs and Todos

     - [ ] Brush up documentation
     - [ ] Tests with more than two columns per table
     - [ ] Tests with more than two tables for merging
     - [ ] Handle optional table captions
     - [ ] Handle hlines
     - [ ] Error messages as result of block
     - [ ] Restructure code to make use of cl 

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [babel] Code for simple set-operations on two tables. Asking for some input.
  2012-01-14  9:27   ` Marc-Oliver Ihm
@ 2012-01-15 16:10     ` Eric Schulte
  0 siblings, 0 replies; 5+ messages in thread
From: Eric Schulte @ 2012-01-15 16:10 UTC (permalink / raw)
  To: Marc-Oliver Ihm; +Cc: emacs-orgmode

Hi Marc-Oliver,

I would recommend two small coding style points for writing lisp code
(especially for inclusion into the library of babel).

1. You *never* want to leave trailing ")"'s on a line by themselves,
   but rather you should always stack these, indentation is used to
   visually identify nesting in lisp. e.g., this

    (defun lob-table-operations-filter (what table filter)
      "Internal function for table-operations in orgmodes library of babel"
      (let (keys
            result)
        (setq keys (mapcar 'car filter))
        (dolist (line table) 
          (if (equal (not (not (member (car line) keys)))
                     (equal what 'keep))
              (setq result (cons line result))
            )
          )
        (nreverse result)
        )
      )

   should be

     (defun lob-table-operations-filter (what table filter)
       "Internal function for table-operations in orgmodes library of babel"
       (let (keys result)
         (setq keys (mapcar 'car filter))
         (dolist (line table) 
           (when (equal (not (not (member (car line) keys)))
                        (equal what 'keep))
             (setq result (cons line result))))
         (nreverse result)))

   Using `paredit-mode' makes this behavior very easy to maintain.  This
   point is true for any lisp coding (not just in the library of babel).

2. When writing code for the library of babel, please try to keep all
   lines <= 79 characters long.  I like to use [1] to identify overlong
   lines by adding the following to my .emacs

      (require 'column-marker)
      (add-hook 'emacs-lisp-mode-hook
        (lambda () (column-marker-3 80)))

Also, two non-style suggestions;

1. If you set "results" to "silent" as a subtree property in your
   "Internals" subtree you won't have to remove empty results.

2. In one function you save many functions into local variables and then
   call them using funcall, it may be simpler to use flet to define
   local functions which can then be called directly.

I'm very pleased to hear that you're enjoying babel, and look forward to
your contribution to the library of babel.

Cheers,

Marc-Oliver Ihm <marc-oliver.ihm@online.de> writes:

> Hello,
>
> please find attached an early draft of lob-table-operations.org.
>
> It already has a reasonable documentation and working examples, so it
> should be easy to play with.
>
> Some features are still missing (e.g. handling of column names and hlines)
> and the coding needs some improvement (using the cl-package ?).
> So it is probably not yet fit for official inclusion into the library of babel.
>
> with kind regards,
> Marc-Oliver Ihm
>
> As a side note: I am very pleased and fascinated, how easily babel and
> org have made the task of keeping together
> all aspects of development; from user documentation to implementation
> and (of course !) organisation.
>
> This has made my coding even more fun !
>
>


Footnotes: 
[1]  http://www.emacswiki.org/emacs/ColumnMarker

-- 
Eric Schulte
http://cs.unm.edu/~eschulte/

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2012-01-15 16:10 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-12-26 11:00 [babel] Code for simple set-operations on two tables. Asking for some input Marc-Oliver Ihm
2011-12-27 20:53 ` Eric Schulte
2011-12-28  8:56   ` Marc-Oliver Ihm
2012-01-14  9:27   ` Marc-Oliver Ihm
2012-01-15 16:10     ` Eric Schulte

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).