Suppose there is some text from a file:
(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 2" "#2")
("1.2 Illustrative Examples 4" "#4")
("1.3 Guidelines for Model Construction 26" "#26")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
How can I add 11 to the last number in each line if there is one, ie
(bookmarks
("Chapter 1 Introduction 1" "#12"
("1.1 Proble开发者_如何学JAVAm Statement and Basic Definitions 2" "#13")
("1.2 Illustrative Examples 4" "#15")
("1.3 Guidelines for Model Construction 26" "#37")
("Exercises 30" "#41")
("Notes and References 34" "#45"))
)
by using sed, awk, python, perl, regex ....
Thanks and regards!
awk -F'#' 'NF>1{split($2,a,"[0-9]+");print $1 FS $2+11 a[2];next}1' infile
Proof of Concept
$ awk -F'#' 'NF>1{split($2,a,"[0-9]+");print $1 FS $2+11 a[2];next}1' infile
(bookmarks
("Chapter 1 Introduction 1" "#12"
("1.1 Problem Statement and Basic Definitions 2" "#13")
("1.2 Illustrative Examples 4" "#15")
("1.3 Guidelines for Model Construction 26" "#37")
("Exercises 30" "#41")
("Notes and References 34" "#45"))
)
use strict;
use warnings;
while(my $line = <DATA>){
$line =~ s/#(\d+)/'#'.($1 + 11)/e;
}
__DATA__
(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 2" "#2")
("1.2 Illustrative Examples 4" "#4")
("1.3 Guidelines for Model Construction 26" "#26")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
Output:
(bookmarks
("Chapter 1 Introduction 1" "#12"
("1.1 Problem Statement and Basic Definitions 2" "#13")
("1.2 Illustrative Examples 4" "#15")
("1.3 Guidelines for Model Construction 26" "#37")
("Exercises 30" "#41")
("Notes and References 34" "#45"))
)
In Python, try:
import re
m = re.search(r'(?<=#)([0-9]+)',txt)
to find the next number. Then set:
txt = txt[:m.start()] + str(int(m.group())+11) + txt[m.end():]
Repeat that (e.g. in a while-loop) as long as search
doesnt find any further matches.
Note: The regExp (?<=#)([0-9]+)
matches any sequence of digits which follow the #-character. start()
yields the start-position of the next match; end()
yields the end-Position and group()
yields the actual match. The expression str(int(m.group()) +11)
converts the matched number to an int-value, adds 11 and re-converts in to a string.
If you can use Ruby(1.9+)
$ ruby -ne 'puts $_=/#/?$_.gsub(/(.*#)(\d+)(.*)/){"#{$1}"+($2.to_i+11).to_s+"#{$3}"}:$_' file
(bookmarks
("Chapter 1 Introduction 1" "#12"
("1.1 Problem Statement and Basic Definitions 2" "#13")
("1.2 Illustrative Examples 4" "#15")
("1.3 Guidelines for Model Construction 26" "#37")
("Exercises 30" "#41")
("Notes and References 34" "#45"))
)
In Python
dh = '''"Chapter 1 Introduction 1" "#1"
"1.1 Problem Statement and Basic Definitions 2" "#2"
"1.2 Illustrative Examples 4" "#4"
"1.3 Guidelines for Model Construction 26" "#26"
"Exercises 30" "#30"
"Notes and References 34" "#34"'''
pat = re.compile('^(".+?(\d+)" *"#)\\2" *$',re.M)
def zoo(mat):
return '%s%s"' % (mat.group(1),str(int(mat.group(2))+11))
print dh
print
print pat.sub(zoo,dh)
result
"Chapter 1 Introduction 1" "#1"
"1.1 Problem Statement and Basic Definitions 2" "#2"
"1.2 Illustrative Examples 4" "#4"
"1.3 Guidelines for Model Construction 26" "#26"
"Exercises 30" "#30"
"Notes and References 34" "#34"
"Chapter 1 Introduction 1" "#12"
"1.1 Problem Statement and Basic Definitions 2" "#13"
"1.2 Illustrative Examples 4" "#15"
"1.3 Guidelines for Model Construction 26" "#37"
"Exercises 30" "#41"
"Notes and References 34" "#45"
.
But beginning from the preceding string as exposed in your other message:
eh = '''Chapter 3 Convex Functions 97
3.1 Definitions 98
3.2 Basic Properties 103'''
pat = re.compile('^(.+?(\d+)) *$',re.M)
def zaa(mat):
return '"%s" "%s"' % (mat.group(1),str(int(mat.group(2))+11))
print eh
print
print pat.sub(zaa,eh)
result
Chapter 3 Convex Functions 97
3.1 Definitions 98
3.2 Basic Properties 103
"Chapter 3 Convex Functions 97" "108"
"3.1 Definitions 98" "109"
"3.2 Basic Properties 103" "114"
Is all that a homework ?
.
EDIT :
I corrected the first above code
dh = '''(bookmarks
("Chapter 1 Introduction 1" "#1")
("1.1 Problem Statement and Basic Definitions 2" "#2")
("1.2 Illustrative Examples 4" "#4")
("1.3 Guidelines for Model Construction 26" "#26")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)'''
pat = re.compile('^(\(".+?(\d+)" *"#)\\2" *(\)\)?)$',re.M)
def zoo(mat):
return '%s%s"%s' % (mat.group(1),str(int(mat.group(2))+11),mat.group(3))
print dh
print
print pat.sub(zoo,dh)
result
(bookmarks
("Chapter 1 Introduction 1" "#1")
("1.1 Problem Statement and Basic Definitions 2" "#2")
("1.2 Illustrative Examples 4" "#4")
("1.3 Guidelines for Model Construction 26" "#26")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
(bookmarks
("Chapter 1 Introduction 1" "#12")
("1.1 Problem Statement and Basic Definitions 2" "#13")
("1.2 Illustrative Examples 4" "#15")
("1.3 Guidelines for Model Construction 26" "#37")
("Exercises 30" "#41")
("Notes and References 34" "#45"))
)
From my answer to your earlier question:
awk '{n = $NF + 11; print "(\"" $0 "\" \"#" n "\")"}' inputfile
or
awk 'BEGIN {q="\x22"} {n = $NF + 11; print "(" q $0 q " " q "#" n q ")"}' inputfile
This works on the data as you presented in the previous question. I can't determine how you're getting from that to the example you posted in this question since there's a difference in the way the parentheses are nested. You also don't say whether the (bookmarks )
wrapper already exists in the original input or if some code we don't see is adding it while other things are being added.
What you're doing is starting to look a little bit like XML. Perhaps you should use the real thing and use proper tools to manipulate it.
Python:
import re
file_name="bin/SO/bookmarks.txt"
print "unmodified file:"
with open(file_name) as f:
for line in f:
print line.rstrip()
print
print "modified file:"
i=11
with open(file_name) as f:
for line in f:
m=re.match(r'(^.*"#)(\d+)(.*$)',line)
if m:
new_line=m.group(1)+str(int(m.group(2))+i)+m.group(3)
print new_line
else:
print line.rstrip()
Output:
unmodified file:
(bookmarks
("Chapter 1 Introduction 1" "#1"
("1.1 Problem Statement and Basic Definitions 2" "#2")
("1.2 Illustrative Examples 4" "#4")
("1.3 Guidelines for Model Construction 26" "#26")
("Exercises 30" "#30")
("Notes and References 34" "#34"))
)
modified file:
(bookmarks
("Chapter 1 Introduction 1" "#12"
("1.1 Problem Statement and Basic Definitions 2" "#13")
("1.2 Illustrative Examples 4" "#15")
("1.3 Guidelines for Model Construction 26" "#37")
("Exercises 30" "#41")
("Notes and References 34" "#45"))
)
This syntax is s-expressions (sexps for short), easiest to manipulate in Lisp and related languages such as Scheme. Easiest for complex tasks, that is; if you can assume that your input is sufficiently tame (e.g. no "#
inside chapter titles, newlines where you illustrate them, etc.), then for this task a text processing tool (as shown by other answers) is preferable.
In Lisp or Scheme, reading and writing the data as structured data is as simple as (read)
and (write data)
. Other things aren't so easy, for example there's no standard way to read the command line arguments in Lisp or Scheme.
Here's a Lisp program that does the desired transformation. It treats the data as structured data, so you don't have to worry about the presentation. The first line, to obtain the first command line argument, is for CLisp; the rest is portable Common Lisp.
(setq delta (parse-integer (car ext:*args*)))
(defun shift-page (page)
(format nil "#~D" (+ delta (parse-integer page :start 1))))
(defun shift-pages (entry)
(let ((title (car entry))
(page (cadr entry))
(subentries (cddr entry)))
(cons title (cons (shift-page page) (mapcar #'shift-pages subentries)))))
(let ((toc (read)))
(write (cons 'bookmarks (mapcar #'shift-pages (cdr toc)))))
Emacs Lisp
Preliminaries
Here we gonna use functions from dash
and s
third-party libraries, that you can install in Emacs from MELPA, using Emacs's package system. How to install packages in Emacs. dash
is a list and tree manipulation library, that also contains various functions that make code more concise and functional. s
is a string manipulation library. When you write code in Elisp often, I heavily recommend installing these packages to make coding easy.
-map
is the same as mapcar
, it traverses a list, calls a function for each element, and returns a list with all elements changed. E.g. (-map '1+ '(1 2 3)) ; returns (2 3 4)
. However, -map
has an anaphoric macro version, which allows to write a concise code instead of passing lambdas. Anaphoric versions start with 2 dashes. E.g. (--map (+ 10 it) '(1 2 3))
is equivalent to (-map (lambda (x) (+ 10 x)) '(1 2 3))
.
->>
is a threading macro from dash
that is like function composition, but with reverse order. E.g. (number-to-string (-sum (-map '1+ '(1 2 3))))
, which returns "9"
, is equivalent to (->> '(1 2 3) (-map '1+) -sum number-to-string)
.
String approach
Suppose you store your whole structure in a string s
. Then you must find every occurrence of sequence of characters of the form #[some_number]
, using regex maybe, and replace it with a number increased by 11.
(let* ((old (-map 'car (s-match-strings-all "#[0-9]\\{1,3\\}" s)))
(new (--map (->> it
(s-chop-prefix "#")
string-to-number
(+ 11)
number-to-string
(s-prepend "#"))
old)))
(s-replace-all (-zip old new) s))
Tree approach
But wait a second, your structure is recursively enclosed in parentheses, it's an s-expression! We could traverse it as a tree and replace every occurrence of string starting from # and containing a numeric value with a new value increased by 11. -tree-map-nodes
is like a -map
for trees. It applies a function only when predicate returns true. IOW, it skips some elements unchanged, if predicate doesn't hold for them.
-tree-map-nodes
recurses in 2 ways, both in breadth and depth. That means, that it treats lists as ordinary elements, and the first element is the whole list. E.g. (-tree-map-nodes 'zerop '1+ '(0 (1 (0) 1) 0))
is not correct, it will throw this error: *** Eval error *** Wrong type argument: numberp, (0 (1 (0) 1) 0)
. Instead you should check first if the element is a number. E.g. (--tree-map-nodes (and (numberp it) (zerop it)) (1+ it) '(0 (1 (0) 1) 0))
would return (1 (1 (1) 1) 1)
. Suppose your tree is in variable q
. Then the solution is below and it will return a new modified s-expression:
(--tree-map-nodes (and (stringp it)
(eq (elt it 0) ?#)
(s-numeric? (s-chop-prefix "#" it)))
(->> it
(s-chop-prefix "#")
string-to-number
(+ 11)
number-to-string
(s-prepend "#"))
q)
精彩评论