Biến đổi dữ liệu khác
Tìm một xâu con
Tìm một xâu con phụ thuộc vào nơi bạn mất nó. Nếu bạn ngẫu nhiên mất nó bên trong một xâu lớn hơn, thì bạn còn may mắn, vì
index() có thể giúp bạn tìm ra. Sau đây là dáng vẻ của nó:
$x = index ($string, $substring) ;
Perl định vị lần xuất hiện đầu tiên của substring bên trong string, cho lại một số nguyên chỉ vị trí của kí tự đầu tiên. Giá trị chỉ số này
đợc cho lại dựa trên không - tức là nếu tìm đợc substring ở chỗ bắt đầu của string, thì bạn nhận đợc 0. Nếu nó là ở một kí tự sau đó thì
bạn nhận đợc 1, và cứ nh thế. Nếu không tìm thấy substring trong string, thì bạn nhận đợc -1.
Ta hãy xem những điều sau:
$where = index(hello, e); # $where nhận 1
$oerson = barney;
$where = index(fred barney, $person) ; # $where nhận 5
@rockers = (fred, barney) ;
$where = index(join( , @rockers), $person); # cũng thế
Chú ý rằng cả hai xâu này đều đợc tìm kiếm và xâu đợc tìm kiếm thì có thể là một hằng xâu kí hiệu, một biến vô hớng có chứa một
xâu, hay thậm chí một biểu thức có giá trị xâu vô hớng. Sau đây là một số thí dụ nữa:
$which = index(a very long string, long); # $switch nhận 7
$which = index(a very long string, lame); # $switch nhận -1
Nếu xâu có chứa xâu con tại nhiều vị trí thì toán tử index() sẽ cho lại vị trí bên trái nhất. Để tìm ra các vị trí sau, bạn có thể cho
index() tham biến thứ ba. Tham biến này là giá trị tối thiểu mà index() sẽ cho lại, cho phép bạn tìm lần xuất hiện tiếp của xâu con theo
sau một vị trí đã chọn. Nó trông tựa nh thế này:
$x = index($bigtring, $littlestring, $skip);
1
Sau đây là một số thí dụ về cách tham biến thứ ba làm việc:
$where = index(hello world, l); # cho lại 2 (l đầu tiên)
$where = index(hello world, l, 0); # cũng thế
$where = index(hello world, l, 1); # vẫn thế
$where = index(hello world, l,3); # bây giờ cho lại 3
# (3 là vị trí đầu tiên lớn hơn hay bằng 3)
$where = index(hello world, o, 5); # cho lại 7 (0 thứ hai)
$where = index(hello world, o, 8); # cho lại -1 (hết sau 8)
Đi theo lối khác, bạn có thể nhòm từ bên phải để có đợc sự xuất hiện bên phải nhất bằng việc dùng rindex(). Giá trị cho lại vẫn là số
các kí tự giữa đầu bên trái của xâu và chỗ bắt đầu của xâu con, nh trớc, nhng bạn sẽ nhận đợc sự xuất hiện về bên phải nhất thay vì sự
xuất hiện bên trái nhất nếu có nhiều sự xuất hiện. Toán tử rindex() cũng nhận tham biến thứ ba giống nh index() , để cho bạn có thể có đ-
ợc sự xuất hiện ít hơn hay bằng vị trí đã chọn. Sau đây là một số thí dụ về điều bạn nhận đợc:
$w = rindex(hello world, he); # $w nhận 0
$w = rindex(hello world, l); # $w nhận 9 (l bên phải nhất)
$w = rindex(hello world, o); # $w nhận 7
$w = rindex(hello world, o ); # $w nhận 4
$w = rindex(hello world, xx); # $w nhận -1 (không thấy)
$w = rindex(hello world, o, 6); # $w nhận 4 (đầu tiên trớc 6)
$w = rindex(hello world,o, 3); # $w nhận -1 (không thấy trớc 3)
Trích và thay thế một xâu con
Việc lấy ra một mẩu của xâu có thể đợc thực hiện bằng việc áp dụng cẩn thận các biểu thức chính qui, nhng nếu mẩu này bao giờ
cũng ở tại một vị trí kí tự đã biết, thì việc này là không hiệu quả. Thay vì vậy, bạn nên dùng substr(). Toán tử này nhận ba đối: một giá trị
xâu, một vị trí bắt đầu (đợc đo tựa nh nó đã đợc đo cho index()), và một chiều dài, giống nh:
$s = substr ($string, $start, $length);
Vị trí bắt đầu làm việc giống nh index; kí tự đầu tiên là không, kí tự thứ hai là một, và cứ thế. Chiều dài là số các kí tự cần nắm lấy
2
tại điểm đó: chiều dài bằng không có nghĩa là không có kí tự nào, bằng một có nghĩa là lấy kí tự đầu tiên, bằng hai có nghĩa là hai kí tự,
và vân vân. (Nó dừng lại tại cuối xâu, cho nên nếu bạn tìm kiếm quá nhiều, thì cũng không sao cả.) Nó trông giống thế này:
$hello = hello, world!;
$grab = substr($hello, 3, 2); # $grap nhận lo
$grab = substr($hello, 7, 100); # 7 đến cuối, hay world!
Bạn thậm chí có thể tạo ra toán tử nâng lên luỹ thừa mời cho các số mũ nguyên nhỏ, nh trong:
$big = substr(10000000000, 0, $power+1); # 10**$power
Nếu số đếm các kí tự là không hay bé hơn không, thì một xâu rỗng sẽ đợc cho lại. Mặt khác, nếu vị trí bắt đầu là bé hơn không thì
vị trí bắt đầu đợc tính theo số các kí tự từ cuối xâu. Cho nên giá trị -1 đối với vị trí bắt đầu và 1 (hay nhiều) đối với chiều dài sẽ cho bạn
kí tự cuối. Tơng tự, -2 cho vị trí bắt đầu với kí tự thứ hai kể từ cuối. Giống thế:
$stuff = substr(a very long string, -3, 3); # ba kí tự cuối
$stuff = substr(a very long string, -3, 1); # kí tự i
Nếu vị trí bắt đầu là trớc chỗ mở đầu của xâu (giống nh một số âm khổng lồ lớn hơn chiều dài của xâu), thì chỗ mở đầu sẽ là vị trí
bắt đầu (dờng nh bạn đã dùng 0 làm vị trí bắt đầu). Nếu vị trí bắt đầu là một số dơng khổng lồ, thì xâu rỗng bao giờ cũng đợc cho lại.
Nói cách khác, nó có thể làm điều bạn trông đợi nó phải làm, chừng nào bạn còn trông đợi thì nó bao giờ cũng cho lại một cái gì đó
khác hơn là một lỗi.
Bỏ đi đối chiều dài thì cũng hệt nh bạn đã đa một số khổng lồ vào cho đối đó - nắm lấy mọi thứ từ vị trí đã chọn cho tới cuối xâu
*
.
Nếu đối thứ nhất của substr() là một biến (nói cách khác, nó có thể xuất hiện bên vế trái của toán tử gán), thì bản thân substr() cũng
có thể xuất hiện ở vế bên trái của toán tử gán. Điều này trông có vẻ kì lạ nếu bạn bắt nguồn từ nền tảng C, nhng nếu bạn đã chơi với vài
dị bản của BASIC thì nó hoàn toàn là thông thờng.
Điều nhận đợc sự thay đổi nh kết quả của phép gán nh thế là phần của xâu sẽ đợc cho lại, mà có substr() đợc dùng trong một biểu
thức. Nói cách khác, substr($var, 3, 2) cho lại kí tự thứ t và thứ năm (bắt đầu từ 3, vì số đếm 2), cho nên việc gán điều đó làm thay đổi
hai kí tự này cho $var. Giống nh:
*
*
Các bản Perl cổ hơn không cho phép bỏ đi đối thứ ba, dẫn tới việc những lập trình viên Perl tiền phong đã dùng số khổng lồ cho đối đó. Bạn có thể vợt qua điều này trong cuộc hành
trình khảo cổ Perl của mình.
3
$hw = hello world!;
substr($hw, 0, 5) = howdy; # $hw bây giờ là howdy world!
Chiều dài của văn bản thay thế (cái nhận đợc việc gán vào trong substr) không phải là cùng nh văn bản đợc thay thế, nh trong thí dụ
này. Xâu này sẽ tự động tăng trởng hay co lại khi cần để điều hào với văn bản. Sau đây là một thí dụ về việc xâu thành ngắn hơn:
substr($hw, 0, 5) = hi; # $hw bây giờ là hi world!
và đây là một xâu thành dài hơn:
substr($hw, -6, 5) = worldwide news; # thay thế world
Việc co lại hay dãn ra thì khá hiệu quả, cho nene bạ nđừng lo lắng về việc dùng chúng một cách bất kì, mặc dầu việc thay thế một
xâu bằng một xâu chiều dài tơng đơng thì vẫn nhanh hơn nếu bạn có cơ hội.
Định dạng dữ liệu bằng sprintf()
Toán tử printf đôi khi cũng dễ sử dụng khi đợc dùng để lấy một danh sách các giá trị và tạo ra một dòng ra cho hiển thị các giá trị đó
theo cách điều khiển đợc. Toán tử sprintf() là đồng nhất với printf về các đối, nhng cho lại bất kì cái gì đã đợc printf đa ra nh một xâu riêng
biệt. (hãy nghĩ về điều này nh xâu printf.) Chẳng hạn, để tạo ra một xâu bao gồm chữ X theo sau bởi một giá trị năm chữ số lấp đầy
bởi không của $y, cũng dễ dàng nh:
$result = sprintf(X%05d, $y);
Nếu bạn không quen thuộc với xâu định dạng của printf và sprintf, thì hãy tham khảo vào tài liệu printf hay sprintf. (Tên sprintf, trong
thực tế có nguồn gốc từ trình th viện cùng tên.)
Sắp xếp nâng cao
Trớc đây, bạn đã biết rằng bạn có thể lấy một danh sách rồi sắp xếp nó theo thứ tự ASCII tăng dần (nh các xâu), bằng cách dùng
toán tử sort có sẵn. Điều gì sẽ xẩy ra nếu bạn không muốn sắp xếp theo thứ tự ASCII tăng dần, mà thay vì thế là một cái gì đó khác, kiểu
nh sắp xếp số? Đợc, Perl cho bạn công cụ bạn cần để làm việc này. Trong thực tế, bạn sẽ thấy rằng sort của Perl là hoàn toàn tổng quát
và có thể thực hiện bất kì thứ tự sắp xếp nào.
Để định nghĩa việc sắp xếp theo mầu sắc khác, bạn cần định nghĩa một trình so sánh mà mô tả cho cách so sánh hai phần tử. Tại sao
4
điều này lại cần thiết? Đợc, nếu bạn nghĩ về nó, thì sắp xếp là việc đặt một bó các thứ theo một trật tự so sánh tất cả chúng với nhau. Vì
bạn không thể nào so sánh chúng ngay một lúc nên bạn cần so sánh hai phần tử mỗi lúc, để cuối cùng dùng cái bạn phát hiện ra về thứ
tự cho từng cặp mà đặt toàn bộ cả lũ vào hàng.
Trình so sánh đợc định nghĩa nh một trình thông thờng. Trình này sẽ đợc gọi đi gọi lại, mỗi lần lại truyền hai phần tử của danh sách
cần đợc sắp. Trình này phải xác định liệu giá trị thứ nhất là bé hơn, bằng hay lớn hơn giá trị thứ hai, và cho lại một giá trị mã hoá (sẽ đ-
ợc mô tả ngay sau đây). Tiến trình này đợc lặp lại cho tới khi danh sách đợc sắp hoàn toàn.
Để tiết kiệm tốc độ thực hiện một chút, hai giá trị này không đợc truyền trong ảng, mà thay vì thế đợc trao cho một trình con nh giá
trị của biến toàn cục $a và $b. (Bạn đừng lo - giá trị nguyên thuỷ của $a và $b đợc bảo vệ an toàn.) Trình này nên cho lại một giá trị âm
nào đó nếu $a bé hơn $b, bằng không nếu $a bằng $b, và bất kì số dơng nào nếu $a lớn hơn $b. Bây giờ bạn hãy nhớ, bé hơn là
tơng ứng với ý nghĩa của bạn về bé hơn - nó có thể là một so sánh số, tơng ứng với kí tự thứ ba của xâu, hay thậm chí tơng ứng với giá
trị của bất kì mảng nào có dùng các giá trị đợc truyền vào nh khoá. Đấy mới thực sự là mềm dẻo.
Sau đây là một thí dụ về một chơng trình con sắp xếp mà sẽ sắp mọi thứ theo thứ tự số:
sub by_number {
if ($a < $b) {
-1;
} elsif ($a == $b) {
0;
} elsif ($a > $b) {
1;
}
}
Bạn hãy chú ý đến tên by_number. Không có gì đặc biệt về cái tên của trình con này, nhng bạn sẽ thấy tại sao tôi lại thích cái tên bắt
đầu bởi by_ trong giây lát.
Ta hãy nhìn qua trình con này. Nếu giá trị của $a là bé hơn (về mặt số trong trờng hợp này) giá trị của $b, thf ta cho lại giá trị -1.
Nếu các giá trị là bằng nhau về con số thì ta cho lại không, còn ngoài ra cho lại 1. Vậy, theo mô tả của ta cho trình so sánh sắp xếp thì
điều này sẽ làm việc.
5
Làm sao ta dùng đợc nó? Ta hãy thử sắp xếp danh sách sau.
$somelist = (1,2,4,8,16,32,64,128,256);
Nếu ta dùng sort thông thờng không sang sửa lại danh sách thì ta sẽ đợc các số sắp nh chúng là các xâu, và theo trật tự ASCII, tựa
nh:
@wronglist = sort @somelist;
# @wronglist bây giờ là (1,128,16,2,256,32,64,8)
Chắc chắn đấy không phải là sắp xếp số. Thôi đợc, ta sẽ cho sort một trình sắp xếp đợc định nghĩa mới. Tên của trình sắp xếp đi
ngay sau từ khoá sort, nh:
@rightlist = sort by_number @wronglist;
# @rightlist bây giờ là (1,2,4,8,32,64,128,256)
Đây quả là mẹo. Chú ý rằng bạn có thể đọc sort với trình con sắp xếp đi kèm theo kiểu con ngời: sắp xếp theo số. Đó là lí do tại
sao tôi đặt tên cho trình con với tiền tố by_.
Nếu bạn cũng thiên về nh thế và muốn nhấn mạnh rằng cái đi sau sort là một trình con, thì bạn có thể đặt trớc nó bằng một dấu và
(&), khi đó Perl sẽ không bận tâm nữa, nhng nó đã biết rằng cái nằm giữa từ khoá sort và danh sách phải là một tên trình con.
Cái loại giá trị ba ngả kiểu -1, 0, 1 dựa trên cơ sở so sánh số thờng xuất hiện trong trình con so sánh đến mức Perl có một toán tử
đặc biệt để làm điều này trong một lần. nó thờng đợc gọi là toán tử tầu vũ trụ, và trông giống <=>. Dùng toán tử tầu vũ trụ này, trình
con sắp xếp trên đây có thể đợc thay thế bằng:
sub by_number {
$a <=> $b;
}
Bạn hãy chú ý đến con tầu vũ trụ giữa hai biến này. Quả vậy, nó thực là toán tử dài ba kí tự. Con tầu vũ trụ cho lại cùng giá trị nh
dây chuyền if/elsif trong định nghĩa trớc của trình này. Bây giờ điều này là có ngắn gọn, nhng bạn có thể viết tắt lời gọi sắp xếp thêm
nữa, bằng việc thay thế tên của trình sắp xếp bằng toàn bộ trình sắp xếp trong dòng, giống nh:
@rightlist = sort { $a <=> $b } @wronglist;
6
Một số ngời biện minh rằng điều này làm giảm tính dễ đọc. Một số khác thì lại biện minh rằng nó loại bỏ nhu cầu phải đi đâu đó để
tìm ra định nghĩa. Perl chẳng quan tâm đến điều đó. Qui tắc cá nhân của tôi là ở chỗ nếu nó không khớp trên một dòng hay tôi phải
dùng nó nhiều lần, thì nó nên thành một trình con.
Toán tử tầu vũ trụ dành cho so sánh số, có toán tử xâu so sánh gọi là cmp. Toán tử cmp cho lại một trong ba giá trị tuỳ theo việc so
sánh xâu tơng đối của hai đối. Cho nên, một cách khác để viết trật tự mặc định là:
@result = sort { $a cmp $b } @somelist;
Có lẽ bạn còn cha hề viết trình con đích xác này (bắt chớc sắp xếp mặc định có sẵn), chừng nào bạn còn cha viết một cuốn sách về
Perl. Tuy thế, toán tử cmp vẫn có ích lợi của nó, trong việc xây dựng các lợc đồ sắp thứ tự theo tầng. Chẳng hạn, bạn không cần đặt các
phần tử theo thứ tự số chừng nào chúng không bằng nhau về mặt số, và trong trờng hợp đó chúng phải đợc sắp theo trật tự ASCII. (Theo
ngầm định, trình by_number trên sẽ chỉ dùng cho các xâu phi số theo một trật tự ngẫu nhiên nào đó vì không có trật tự số khi so sánh hai
giá trị không.) Sau đây là một cách nói cần so sánh theo số, chừng nào chúng cha bằng nhau về số, còn thì so sánh theo xâu:
sub by_mostly_number {
($a <=> $b) || ($a cmp $b);
}
Điều này làm việc thế nào? Thế này, nếu kết quả của tầu vũ trụ là -1 hay 1 thì phần còn lại của biểu thức bị bỏ qua, và giá trị -1 hay
1 đợc cho lại. Néu tầu vũ trụ tính ra giá trị không thì toán tử cmp trở thành quan trọng, cho lại một giá trị thứ tự thích hợp xem nh giá trị
của xâu.
Giá trị đợc so sánh không nhất thiết là giá trị đợc truyền vào. Chẳng hạn, bạn có một mảng kết hợp trong đó khoá là tên đăng nhập
và các giá trị là tên thật của từng ngời dùng. Giả sử bạn muốn in ra một biểu đồ trong đó tên đăng nhập và tên thật đợc cất giữ theo thứ
tự tên thật. Bạn sẽ làm điều đó nh thế nào?
Thực tại, việc ấy khá dễ dàng. Ta hãy giả sử các giá trị là trong mảng %names. Tên đăng nhập vậy là danh sách của key(%names).
Điều ta muốn đạt tới là một danh sách các tên đăng nhập đợc sắp theo giá trị tơng ứng, vậy với bất kì khoá riêng $a nào, ta cần nhìn vào
$names{$a} và sắp xếp dựa trên điều đó. Nếu bạn nghĩ về điều này theo cách đó thì nó gần nhu đã tự viết ra rồi, nh trong:
@sortedkeys = sort by_names keys(%names);
sub by_names {
7