Delete cruft
[worg.git] / org-tutorials / org-spreadsheet-lisp-formulas.org
blobd868e6f6eaf9611ee2e755e1c917c53cb94c7eb6
1 #+TITLE:      Org as a spreadsheet system: using Emacs lisp as formulas
2 #+AUTHOR:     Worg people
3 #+EMAIL:      bzg AT altern DOT org
4 #+OPTIONS:    H:3 num:nil toc:t \n:nil ::t |:t ^:t -:t f:t *:t tex:t d:(HIDE) tags:not-in-toc
5 #+STARTUP:    align fold nodlcheck hidestars oddeven lognotestate
6 #+SEQ_TODO:   TODO(t) INPROGRESS(i) WAITING(w@) | DONE(d) CANCELED(c@)
7 #+TAGS:       Write(w) Update(u) Fix(f) Check(c)
8 #+LANGUAGE:   en
9 #+PRIORITIES: A C B
10 #+CATEGORY:   worg
12 [[file:../index.org][{Back to Worg's index}]]
14 * Introduction
16 This tutorial explains how to use Emacs Lisp as formulas in Org tables.  If
17 you want a general tutorial on how to use Org as a spreadsheet system, read
18 [[file:org-spreadsheet-intro.org][this tutorial]].  You can also check the complete [[https://orgmode.org/manual/The-spreadsheet.html#The-spreadsheet][Org documentation]] on this
19 topic[fn:1].
21 * Example 1: manipulate cells
23 Here is a simple table:
25 | First name | Last Name | Email                |
26 |------------+-----------+----------------------|
27 | John       | Doe       | john.doe@emacs.edu   |
28 | Jennie     | Duh       | jennie.duh@emacs.edu |
30 You easily notice the pattern =[firstname].[lastname]@emacs.edu= for the
31 third column.  Given a first name and a last name, it should be easy to
32 compute the result for the "Email" column.
34 Okay, let's do it then.  First put your cursor in the third column:
36 # | First name | Last Name | Email                |
37 # |------------+-----------+----------------------|
38 # | John       | Doe       | john.doe@emacs.edu   |
39 # | Jennie     | Duh       | jennie.duh@emacs.edu |
40 # | Jack       | Goody     |  <= [cursor is here] |
42 #+ATTR_HTML: width="400px"
43 [[file:../images/bzg/org-spreadsheet-table1.jpg]]
45 Now type =C-c }= to display the table's coordinates.
47 For each row, you want to concatenate the content of the first column
48 (accessed with =$1=) to a string with a dot (".") to the cell of the second
49 column (accessed with =$2=) and to the string "@emacs.edu".  In Emacs Lisp,
50 you would write this:
52 #+BEGIN_SRC emacs-lisp
53 '(concat $1 "." $2 "@emacs.edu")
54 #+END_SRC
56 Now copy this formula, type =C-c == in the bottom right field to insert a
57 /column formula/[fn:2] and copy the formula here.  Hitting =RET= will
58 immediately insert the result in this field (=Jack.Goody@emacs.edu=) and
59 add the =#+TBLFM= line at the bottom of the table.
61 *Caveat*: beware of the initial quote: the formula is the /expression
62 itself/, not its value.  This expression will only have a meaning when the
63 =$1= and =$2= references will be replaced by the right strings, an the
64 expression will then by applied as a formula by pressing =C-c C-c= on
65 =#+TBLFM=.
67 So you now have this table:
69 # | First name | Last Name | Email                |
70 # |------------+-----------+----------------------|
71 # | John       | Doe       | John.Doe@emacs.edu   |
72 # | Jennie     | Duh       | Jennie.Duh@emacs.edu |
73 # | Jack       | Goody     | Jack.Goody@emacs.edu |
74 # #+TBLFM: $3='(concat $1 "." $2 "@emacs.edu")
76 #+ATTR_HTML: width="400px"
77 [[file:../images/bzg/org-spreadsheet-table2.jpg]]
79 At the time the formula is evaluated, the references =$1= and =$2= are
80 interpreted and replaced by the values of these cells /as strings/: you
81 don't need to enclose =$1= with quotes ("$1").
83 If you want to force the references (=$1= and =$2=) to be interpreted as
84 numbers, add the flag =;N= at the end of the Emacs lisp expression.  See
85 for example this table
87 | First name | Last Name | Maths | French | Mean |
88 |------------+-----------+-------+--------+------|
89 | John       | Doe       |    12 |     16 |   14 |
90 | Jennie     | Duh       |    15 |      9 |   12 |
91 #+TBLFM: $5='(/ (+ $3 $4) 2);N
93 where we used this formula for the fifth column:
95 #+BEGIN_SRC org
96   ,#+TBLFM: $5='(/ (+ $3 $4) 2);N
97 #+END_SRC
99 As an exercise, try to find Emacs lisp formula for the fifth column in this
100 table:
102 | First name | Last Name | Maths | French | Mean       |
103 |------------+-----------+-------+--------+------------|
104 | John       | Doe       |    12 |     16 | John: 14   |
105 | Jennie     | Duh       |    15 |      9 | Jennie: 12 |
106 #+TBLFM: $5='(concat "$1" ": " (number-to-string (/ (+ $3 $4) 2)));L
107 #+TBLFM: $5='(concat "$1" ": " (number-to-string (/ (+ $3 $4) 2)));N
108 #+TBLFM: $5='(concat $1 ": " (number-to-string (/ (+ (string-to-number $3) (string-to-number $4)) 2)))
110 All you have is the values of the first four columns.  (Hint: check the
111 Emacs lisp functions =string-to-number= and =number-to-string=.)
113 *Solution*: we cannot use the =;N= flag because it will force
114 interpretation of the cells as numbers, and if we do so, we will not be
115 able to access the value of cells in the first row.  So one idea is to
116 use =string-to-number= and =number-to-string= this way:
118 #+BEGIN_SRC org
119   ,#+TBLFM: $5='(concat $1 ": " (number-to-string (/ (+ (string-to-number $3) (string-to-number $4)) 2)))
120 #+END_SRC
122 Another idea is to use the =;L= flag: instead of using interpreting cells
123 as strings or numbers, it inserts their content literally in the Emacs lisp
124 expression.  So the formula above can safely be replaced by this more
125 condensed one:
127 #+BEGIN_SRC org
128   ,#+TBLFM: $5='(concat "$1" ": " (number-to-string (/ (+ $3 $4) 2)));L
129 #+END_SRC
131 Note the double-quotes around "$1": that's because inserting a first name
132 literally would mean "it is an Emacs lisp symbol".  So, when using the =;L=
133 flag, adding double-quotes makes sure the reference is interpreted as a
134 string.
136 We're done!
138 * Example 2: manipulating ranges
140 Suppose now we have this table
142 | Col1 | Col2 | Col3                             | Col4         | Col5         |
143 |------+------+----------------------------------+--------------+--------------|
144 | ?    | ?    | in Col1 and Col2 (no duplicates) | only in Col1 | only in Col2 |
145 | ?    | ?    | ...                              | ...          | ...          |
146 | ?    | ?    | ...                              | ...          | ...          |
148 =Col1= and =Col2= contain strings.
150 We want the first cell of the third column to contain a string with all the
151 strings that are in both =Col1= and =Col2=, with no duplicates.  =Col4=
152 contains strings that are only in =Col1= (and not in =Col2=) whereas =Col5=
153 contains strings that are only in =Col2= (and not in =Col1=).
155 How to automate this with an Emacs lisp formula?
157 Let's first figure out the result we want for a simple table:
159 | Col1 | Col2 | Col3    | Col4 | Col5 |
160 |------+------+---------+------+------|
161 | a    | a    | a b c d | c    | d    |
162 | a    | b    |         |      |      |
163 | b    | a    |         |      |      |
164 | c    | d    |         |      |      |
166 Now let's get the values of the first column starting from the second row.
168 The "a" on the top left cell can be accessed through the reference
169 =@2$1=.  The "c" on the bottom left cell can be accessed through the
170 reference =@5$1=.  The range of cells can then be accessed with
171 =@2$1..@5$1=.
173 Let's add this range in the first cell of =Col3=:
175 | Col1 | Col2 | Col3    | Col4 | Col5 |
176 |------+------+---------+------+------|
177 | a    | a    | a a b c | c    | d    |
178 | a    | b    |         |      |      |
179 | b    | a    |         |      |      |
180 | c    | d    |         |      |      |
181 #+TBLFM: @2$3='(mapconcat 'identity (list @2$1..@5$1) " ")
183 Here is the formula:
185 #+BEGIN_SRC org
186   ,#+TBLFM: @2$3='(mapconcat 'identity (list @2$1..@5$1) " ")
187 #+END_SRC
189 How to read it?
191 When interpreted, the range =@2$1..@5$1= is replaced by the values of the
192 cells, separated with a space.  So =(list @2$1..@5$1)= becomes =(list "a"
193 "a" "b" "c")= and the whole formula becomes
195 #+BEGIN_SRC emacs-lisp
196   '(mapconcat 'identity (list "a" "a" "b" "c") " ")
197 #+END_SRC
199 which basically means "Concatenate elements of ("a" "a" "b" "c") and add a
200 single space between each of them".
202 Let's generalize and say that we don't know how many rows our table
203 contains.  The range =@2$1..@5$1= becomes =@2$1..@>$1= where =@>= means
204 "the last row" and =@>$1= "the last row in the first column".
206 Remember: we want the third column to contain a string with all the strings
207 that are in both =Col1= and =Col2=, with no duplicates.  Let's first list
208 all values from =Col1= and =Col2= with =(list @2$1..@>$1 @2$2..@>$2)=, then
209 delete duplicates like this =(delete-dups (list @2$1..@>$1 @2$2..@>$2))=
210 then put this expression in the one we already have above.
212 #+BEGIN_SRC org
213   ,#+TBLFM: @2$3='(mapconcat 'identity (delete-dups (list @2$1..@>$1 @2$2..@>$2)) " ")
214 #+END_SRC
216 | Col1 | Col2 | Col3    | Col4 | Col5 |
217 |------+------+---------+------+------|
218 | a    | a    | a b c d | ?    | ?    |
219 | a    | b    |         |      |      |
220 | b    | a    |         |      |      |
221 | c    | d    |         |      |      |
222 #+TBLFM: @2$3='(mapconcat 'identity (delete-dups (list @2$1..@>$1 @2$2..@>$2)) " ")
224 Okay.  Now that you know how to manipulate ranges, you can replace the "?"
225 with the right formulas...  remember: =Col4= contains strings that are only
226 in =Col1= and not in =Col2=, whereas =Col5= contains strings that are only
227 in =Col2= and not in =Col1=.  (Hint: you can write your own functions and
228 use them in an Emacs lisp formula.)
230 Don't forget that you can edit a table's formulas with by hitting C-c '
231 anywhere on the table: it will open the formulas editor, which highlights
232 references that the cursor is on (both in the formulas editor and in the
233 table).  The formulas editor is really handy when you need to check that
234 your references are correct.  Also, hitting =TAB= on a formulas in this
235 editor will pretty-print the formula, which helps editing a lot !
237 * Conclusion
239 Please check the Org manual for (condensed but accurate and up to date)
240 information on using Lisp as formulas: see the [[https://orgmode.org/manual/Formula-syntax-for-Lisp.html][manual online]] or as an [[info:org#Formula%20syntax%20for%20Lisp][Info
241 page]].
243 * Footnotes
245 [fn:1] If you are reading this tutorial from within Emacs, go to the
246   spreadsheet section of the manual by clicking on this link:
247   [[info:org#The spreadsheet][the spreadsheet]].
249 [fn:2] Column formulas apply to the whole column, whereas Field formulas
250   only apply to the current field.  Field formulas can be inserted by
251   pressing =C-u C-c == in a field.