开发者

How to add to the number near the end of each line

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)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜