Tải bản đầy đủ (.pdf) (35 trang)

Xây dựng chương trình thống kê kí tự đơn và kí tự đôi trong file văn bản tiếng Việt mã ABC

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (248.85 KB, 35 trang )


Luận văn Xây dựng chương trình
thống kê kí tự đơn và kí tự đôi trong
file văn bản tiếng Việt mã ABC

I. Đặt vấn đề

*Mục đích của đề tài

Từ khi máy vi tính trở nên phổ biến và rộng rãi trong nước, nhu cầu sử dụng
chữ Việt trên máy vi tính đã trở thành nhu cầu cấp thiết đối với người sử dụng.
Việc đưa tiếng Việt vào máy tính là một vấn đề có tầm quan trọng trong việc tin
học hóa, phổ cập tin học và phát triển công nghệ phần mềm trong nước. Do vậy đã
có nhiều cá nhân, tập thể trong và ngoài nước đã tập trung nhiều công sức trong
việc đưa tiếng Việt vào các máy tính nói chung và máy vi tính nói riêng.

Hiện nay có rất nhiều phần mềm đưa tiếng Việt vào máy tính, số lượng các
phần mềm này lớn cho ta thấy được ý nghĩa của việc đưa tiếng Việt vào máy tính.
Tuy nhiên mỗi phần mềm này lại có một cách thức thể hiện khác nhau (cách mã
hóa ký tự tiếng Việt khác nhau). Có rất nhiều cách mã hóa ký tự Tiếng Việt. Mặc
dù khác nhau, không thống nhất theo những quy tắc chung, các giải pháp tiếng
Việt đã phần nào đáp ứng được nhu cầu thực tiễn, ít nhất cũng trong phạm vi của
một tổ chức, cơ quan và góp phần vào việc “Việt hóa” các ứng dụng cho máy
tính.

ở cấp độ các cơ quan nhà nước, một bộ mã tiếng Việt duy nhất được thống
nhất sử dụng, đó là bộ mã TCVN3 hay còn gọi là mã ABC. Mặc dù vẫn còn nhiều
khiếm khuyết nhưng trên thực tế, bộ mã tiếng Việt 8 bit này đã được chấp nhận và
lưu hành rộng rãi tại Hà Nội và các tỉnh phía Bắc sau một số năm áp dụng. Việc
xử lý các văn bản sử dụng bảng mã ABC vẫn có ý nghĩa quan trọng trong thời
điểm hiện tại.



*Giói thiệu bài toán cụ thể của đề tài


Chuyên đề thực tập này có nội dung như sau:
“Xây dựng chương trình thống kê kí tự đơn và kí tự đôi trong file văn bản tiếng
Việt mã ABC”.

Đề tài gồm hai phần:

Phần thứ nhất thống kê tất cả các kí tự đơn của file văn bản
Phần thứ hai liệt kê số lần xuất hiện của một kí tự đơn sau một kí tự đơn
khác là bao nhiêu lần trong toàn bộ file văn bản (ví dụ ta có kí tự đôi “ca”, vậy kí
tự “a” sẽ đứng sau kí tự “c” là bao nhiêu lần trong văn bản), sau đó tính tần suất
xuất hiện kí tự đôi.




II. Cơ sở lý thuyết của đề tài thực tập

Trình bầy tổng quan các nghiên cứu trong lý thuyết

1) File văn bản

Để giải bài toán đặt ra, trước hết ta phải xác định kiểu file đầu vào cho chương
trình. Trong Turbo Pascal, file văn bản được hiểu theo nghĩa chuẩn, các dữ liệu
được lưu trữ dưới dạng các ký tự trong bảng mã ASCII. Các ký tự được liên kết
thành từng dòng, dấu hết dòng được ghi nhận bởi cặp ký tự điều khiển có mã 13
(ký tự trở về đầu dòng - CR) và ký tự có mã 10 (ký tự xuống dòng - Lf). Không có

quy định về chiều dài dòng cho file văn bản.


Turbo Pascal dùng tên chuẩn Text để đặt cho file văn bản. Vì thế lời khai báo
biến file văn bản sẽ là:
Var
f: text;

File văn bản chỉ có thể truy cập tuần tự chứ không thể truy cập trực tiếp như file
định kiểu. Và cũng không thể vừa đọc vừa ghi trên file văn bản như file định kiểu.

Tuy nhiên vì file văn bản không phụ thuộc vào định nghĩa kiểu thành phần nên
nó là công cụ giao tiếp chung giữa các chương trình. Bất cứ chương trình nào cũng
có thể đọc dữ liệu từ file văn bản và kết xuất dữ liệu dưới dạng văn bản. Việc soạn
thảo file văn bản có thể dùng bất cứ một hệ soạn thảo nào (là những chương trình
tiện ích vốn rất phong phú hiện nay). Chẳng hạn, có thể dùng ngay hệ soạn thảo
của chính Turbo Pascal để xây dựng và hiệu chỉnh file văn bản.

Các bước thao tác trên file văn bản tuân theo quy trình như sau: đầu tiên cần
đăng ký biến file văn bản (f) với tên file trên đĩa (name) bằng thủ tục Assign (f,
name). Sau đó mở file bằng thủ tục Reset (f) nếu nó đã có để chuẩn bị đọc, hoặc
mở bằng thủ tục Rewrite (f) nếu muốn tạo mới để chuẩn bị ghi. Sau Reset chỉ có
thể đọc và sau Rewrite chỉ có thể ghi. Mỗi lần đọc ghi xong con trỏ lại định vị đến
vị trí kế tiếp. Việc đọc dữ liệu từ file văn bản tương tự như đọc từ bàn phím, cũng
dùng các thủ tục Read (f, var1, var2, ), Readln nếu muốn đọc từng dòng. Các
biến ký tự được đọc theo từng đơn vị ký tự, các biến xâu ký tự được đọc đúng độ
dài của nó hoặc cho đến khi gặp dấu xuống dòng. Việc ghi ra file văn bản cũng
tương tự như ghi ra màn hình (cũng dùng các thủ tục write và writeln). Sau khi
thao tác xong cần đóng file nhờ thủ tục Close (f) để đảm bảo an toàn dữ liệu.


Ngoài ra còn có thủ tục Append (f) để mở một văn bản có sẵn để ghi kế tiếp,
hàm Eof (f) để kiểm tra đã định vị đến cuối file chưa



2) Kiểu con trỏ

Trong Pascal, kiểu con trỏ là một kiểu dữ liệu đặc biệt, dùng để biểu diễn
những giá trị địa chỉ. Như thế kiểu con trỏ là những biến dùng để lưu những giá trị
địa chỉ của bộ nhớ. Nhờ các biến con trỏ, ta có thể thao tác trên các giá trị địa chỉ
cũng như truy cập dữ liệu ở các vùng nhớ một cách linh hoạt. Ngoài ra, biến con
trỏ còn là công cụ để xin cấp phát trên vùng Heap (cấp phát động) và xây dựng
kiểu dữ liệu động, cho phép chương trình có thể khai thác tối đa và hiệu quả bộ
nhớ.

Có hai kiểu con trỏ là con trỏ định kiểu và con trỏ không định kiểu.

Với bài toán đã cho, con trỏ định kiểu được sử dụng. Con trỏ định kiểu cần
xác định kiểu dữ kiệu mà nó trỏ đến. Turbo Pascal dùng ký hiệu ^ trước tên kiểu
dữ liệu để khai báo con trỏ định kiểu:

Type
PtrType = ^DataType;
trong đó: PtrType là tên kiểu con trỏ
DataType là tên kiểu dữ liệu xác định kiểu dữ liệu được trỏ.

Vì kiểu con trỏ chỉ lưu địa chỉ nên nó có thể khai báo trước kiểu dữ liệu được
trỏ. Chẳng hạn, có quyền khai báo:

PtrType = ^ DataType;

DataType = record


end;

Hai biến con trỏ có thể gán giá trị cho nhau trong các trường hợp tương thích.
Khi đó chúng cùng trỏ tới một địa chỉ. tuy nhiên, hai con trỏ định kiểu mà trỏ tới
các kiểu dữ liệu khác nhau là không tương thích.

Để truy cập dữ liệu mà con trỏ p đang trỏ, Turbo Pascal dùng ký hiệu p^. Ký
hiệu này đóng vai trò như một biến, có nội dung là nội dung vùng dữ liệu mà p
đang trỏ. Dùng p^ có thể thay cho các mảng Mem và các biến tuyệt đối trong việc
truy cập bộ nhớ.

Khi p là con trỏ định kiểu. Khi đó p^ là biến định kiểu có địa chỉ đựợc p lưu
giữ. Kiểu của p^ được xác định là kiểu mà con trỏ p trỏ tới. Mọi thao tác trên biến
dịnh kiểu thông thường đều được áp dụng cho p^. Tuy nhiên, p^ là một biến định
kiểu đặc biệt, nó có thể thay đổi địa chỉ trong quá trình hoạt động bằng cách gán
địa chỉ tương ứng cho p, vì thế p^ có thể truy cập vào bất cứ vùng nhớ nào theo
kiểu dữ liệu của nó. Có thể xem p^ như một biến tuyệt đối có khả năng thay đổi
địa chỉ nhờ phép gán.
3) Cấp phát động

Việc dùng p^ bằng cách gán cho p một giá trị địa chỉ không phải là việc cung
cấp một biến p^ mới, vì thế phải hết sức thận trọng khi truy cập p^. Muốn p^ thực
sự được cấp phát như một biến, phải dùng kỹ thuật cấp phát động cho p. Cấp phát
động được thực hiện bằng câu lệnh trong thân chương trình (chứ không phải bằng
khai báo) và được thu hồi nếu cần (cũng bằng câu lệnh trong chương trình). Vùng
cấp phát động bao giờ cũng là vùng nhớ tự do (Heap). Địa chỉ vùng cấp phát động
được quản lý bởi các biến con trỏ. Việc cấp phát động cho phép người lập trình sử

dụng bộ nhớ linh hoạt và tiết kiệm đặc biệt, nhờ cấp phát động, người ta có thể
xây dựng được những kiểu dữ liệu động, cho phép khai thác tối đa bộ nhớ.


Cấp phát động là một ứng dụng quan trọng của con trỏ. Việc dùng con trỏ
thường được gắn liền với cấp phát động. Nhứng biến được xin trong vùng Heap
nhờ cấp phát động, được gọi là những biến động (dynamic variable) để phân biệt
với những biến thông thường được xin từ khai báo.

Pascal tổ chức hai cách cấp phát động: Một, dành cho các con trỏ định kiểu và
hai, dành cho con trỏ bất kỳ (không quan tâm đến kiểu). ở đây chúng ta chỉ quan
tâm đến cấp phát cho con trỏ định kiểu - được gọi là cấp phát định kiểu.
Giả sử p là một con trỏ định kiểu. thủ tục New (p) sẽ cấp một vùng nhớ trên
Heap cho con trỏ p với kích thước bằng kích thước của kiểu dữ liệu và gán địa chỉ
cua vùng này cho p. Khi đó biến động định kiểu p^ sẽ truy cập vùng được cấp phát
chừng nào p còn giữ địa chỉ vùng này. Mọi thao tác trên biến thông thường đều
được áp dụng cho p^.
Vùng nhớ đã cấp phát cho con trỏ p bằng thủ tục New(p) sẽ được thu hồi nhờ
thủ tục Dispose (p). Vùng nhớ đã cấp phát sẽ được hệ thống bảo vệ cho đến khi
được thu hồi.

4) Danh sách liên kết

Một trong những ứng dụng quan trọng nhất của cấp phát động là tạo ra những
cấu trúc dữ liệu động, cho phép khai thác linh hoạt và tối đa bộ nhớ. Các cài đặt
dùng cấu trúc dữ liệu động không bị hạn chế (về lôgic) bởi kích thước của dữ liệu,
và đặc biệt thuận lợi khi phải dùng thường xuyên các thao tác chèn hoặc xóa.

Danh sách liên kết là một dãy các phần tử có cùng kiểu dữ liệu, trong đó cần
chỉ rõ mối liên kết trước-sau của các phần tử trong danh sách. Một dạng cài đặt

thường gặp của danh sách liên kết là dùng mảng, trong đó mối liên kết được ẩn
dưới các giá trị kề nhau của chỉ số. Việc dùng mảng cho phép truy cập nhanh đến

các phần tử của danh sách nhờ các giá trị chỉ số. Tuy nhiên các hạn chế của việc
dùng mảng là:

 Khai thác bộ nhớ không linh hoạt bộ nhớ do phải khai báo trước.
 Không thể làm việc với những danh sách lớn hơn 64 Kb vì mảng cần được
cấp một vùng nhớ liên tục.
 Các thao tác chèn, xóa đòi hỏi phải dồn chỉ số cho các phần tử mảng.

Việc cấp phát động cho các phần tử của danh sách cho phép khai thác bộ nhớ
tốt hơn. Một mặt, nó không phụ thuộc vào kích thước danh sách vì không phải
khai báo trước. Mặt khác, do các phần tử của danh sách được cấp phát riêng rẽ nên
danh sách không bị hạn chế bởi giới hạn 64 Kb. Ngoài ra, các thao tác chèn, xóa
đòi hỏi không phải dồn phần tử.

Để xây dựng danh sách liên kết bằng cấp phát dộng mỗi phần tử của nó (mà ta
sẽ gọi là nút - node) cần phải xác định hai thành phần: Một là nội dung dữ liệu mà
nút lưu trữ, hai là địa chỉ của nút kế tiếp. Ta dùng kiểu bản ghi để định nghĩa một
nút như vậy:

Type
DataType = {kiểu dữ liệu}
PtrType = ^Node;
Node = record
Data: DataType; {lưu trữ dữ liệu}
Next: PtrType; {trỏ đến nút kế tiếp}
End;


Khai báo trên định nghĩa kiểu PtrType trỏ tới Node, trong đó Node là kiểu bản
ghi mô tả một nút gồm hai trường. Trường Data dùng để lưu với giả thiết tên kiểu

là DataType (là một kiểu dữ liệu nào đấy đã được định nghĩa, có thể gồm nhiều
thành phần) còn trường Next thuộc kiểu PtrType, dùng để lưu địa chỉ của nút kế
tiếp. Trong những khai báo như ở trên thì PtrType cần được định nghĩa trước
Node.

Để quản lý danh sách, cần có một biến toàn thể thuộc kiểu PtrType lưu địa chỉ
của nút đầu tiên trong danh sách. Ta sẽ đặt tên cho biến này là First:

Var
First: PtrType;

Từ giá trị của First ta có thể truy cập đến nút đầu tiên, sau đó từ trường Next
của nút này, ta có thể truy cập tới nút thứ hai, Để báo hết danh sách, trường
Next của nút cuối cần trỏ vào Nil. Bằng cách như vậy, ta có thể truy cập được toàn
bộ các nút của danh sách.

- Để khởi tạo danh sách rỗng (chưa có nút nào), ta gán Nil cho con trỏ First:
First:=Nil;

- Duyệt danh sách: Để lần lượt đi qua các nút của danh sách, cần phải khởi
động một con trỏ CurrPtr trỏ đến nút đầu tiên, rồi đi tiếp qua danh sách theo các
trường liên kết và xử lý dữ liệu trong mỗi nút.

- Xây dựng danh sách: Đây là công việc sẽ gặp trong bài toán của đề tài.
Trong bài toán thường xảy ra tình huống chèn một nút mới vào cuối danh sách.
Công việc này đòi hỏi phải tổ chức thêm con trỏ Last luôn trỏ tới cuối danh sách
nhằm không phải tìm kiếm lại. Như thế sau mỗi lần nối, phải cập nhập lại giá trị

cho con trỏ này. Ngoài ra việc nối vào một danh sách rỗng phải được xử lý riêng
vì khi đó không có nút đi trước.


First := Nil;
While not Stop do
Begin
New (TempPtr);
TempPtr^.Data:= Item;
If First = Nil then
First:= TempPtr;
else
Last^.Next:=Tempptr;
Last:=Tempptr;
Last^.Next:=Nil;
End;

- Tìm kiếm một nút trong danh sách: Giả sử cần tìm một nút trong danh sách
có nội dung dữ liệu là Item (có kiểu DataType). Việc tìm kiếm được tiến hành
tuần tự theo chiều duyệt danh sách bắt đầu từ First nhờ con trỏ CurrPtr. Mỗi khi đi
đến một nút, vùng dữ liệu của Currptr lại được so sánh với Item. Nếu tìm thấy, con
trỏ Currptr sẽ xác định nút cần tìm, trái lại con trỏ Currptr sẽ có giá trị Nil.






III.Xây dựng phương án giải quyết bài toán



Chương trình để giải quyết bài toán sẽ thực hiện 2 công việc chính, thống kê
kí tự đơn (liệt kê các kí tự có trong văn bản, số lần xuất hiện của mỗi kí tự) và
thống kê số lần xuất hiện của kí tự đơn sau một kí tự đơn khác trong kí tự đôi (xét
xem kí tự này đứng sau kí tự kia bao nhiêu lần trong toàn bộ văn bản).

1) Mô tả phương án giải quyết bài toán

Để làm được các việc đó, chương trình cần phải trải qua 3 bước chính:

- Bước 1: Lập danh sách liên kết chứa toàn bộ kí tự đơn của văn bản. Mỗi nút
của danh sách chứa một kí tự đơn:
Đầu tiên tiến hành đọc file văn bản đầu vào theo từng ký tự, dùng thủ tục Read(f,
ch); nhận các ký tự chuẩn (các ký tự tạo thành từ). Sau mỗi lần có được một kí tự
thì tiến hành tạo nút mới có phần nội dung chứa kí tự đó và tiến hành chèn nút
này vào cuối danh sách.

Như vậy sau bước này ta có một danh sách liên kết chứa các kí tự đơn của file
văn bản nguồn. Đây chỉ là danh sách trung chuyển để tiện cho việc xử lý dữ liệu
sau này mà chưa phải là danh sách thống kê kí tự chuẩn.

Thực ra ngay từ khi đọc file văn bản nguồn như ở bước này ta đã có thể tạo ra
danh sách thống kê kí tự đơn hoàn chỉnh. Danh sách này sẽ có phần nội dung gồm
2 trường:

Trường 1: Chứa nội dung kí tự
Trường 2: Chứa số lần xuất hiện kí tự trong file văn bản


Tuy nhiên, bài toán còn liên quan đến việc thống kê kí tự đôi. Vì vậy ta cần có

một danh sách chứa kí tự đơn của văn bản mà vẫn giữ nguyên trật kí tự như trong
văn bản, kí tự nào đứng trước trong văn bản thì vẫn đứng trước trong danh sách.

Sau khi có được một danh sách chứa các kí tự đôi của văn bản được xếp đúng
trật tự, ta chuyển sang bước 2.

- Bước 2: Lập danh sách thống kí tự từ đơn và đưa kết quả ra file văn
bản.

Từ danh sách đã thu được từ bước 1, ta tiến hành thực hiện công việc thứ nhất
của bài toán: thống kê kí tự đơn trong văn bản.

Để thống kê kí tự đơn rồi đưa kết quả ra một file văn bản, trước hết cần phải
lập một danh sách thống kê kí tự đơn hoàn chỉnh với phần nội dung gồm hai
trường:
Trường 1: Term: char - chứa kí tự
Trường 2: lanxh: integer; chứa số lần xuất hiện của kí tự đó.

Tiến hành duyệt từng nút của danh sách thu được từ bước 1 để lấy ra lần lượt
từng kí tự đơn của file văn bản. Các kí tự này sẽ qua một thủ tục để kiểm tra đã có
trong danh sách thống kê hay chưa, nếu chưa có thì thêm một nút có nội dung term
là kí tự đó và đưa vào cuối danh sách, số lần xuất hiện của kí tự lúc này được đưa
vào là 1. Nếu kí tự đã có trong danh sách thì tìm đến vị trí nút chứa kí tự đó, tăng
trường số lần xuất hiện của kí tự lên 1.

Sau khi đã có một danh sách thống kê kí tự đơn hoàn chỉnh, cung cấp đầy đủ
thông tin về nội dung và số lần xuất hiện của kí tự , ta sẽ tiến hành duyệt danh sách

này để đưa ra file kết quả. File kết quả cũng là file văn bản, có tên do người dùng
tự đặt, tên file được nhập từ bàn phím.


- Bước 3: Thống kê số lần xuất hiện của một kí tự liền sau một kí tự
(thống kê từ đôi).

Với danh sách thu được từ bước 1, tổ chức 2 con trỏ currptr và back. Ban đầu
currptr chỉ vào nút đầu tiên của danh sách, back chỉ vào nút liền sau nút trỏ bởi
currptr. Như vậy khi duyệt danh sách bằng 2 con trỏ này ta lần lượt nhận được từ
đứng trước (nhờ con trỏ currptr) và từ đứng sau (nhờ con trỏ back). Lập 2 con trỏ
khác là currptr2 và back2 với kiểu như 2 con trỏ currptr và back để tìm số lần lặp
lại của 2 từ đơn liền nhau.

Mỗi khi có được một kết quả hoàn chỉnh (kí tự đơn này đứng sau kí tự đơn kia
bao nhiêu lần), tiến hành ghi luôn kết quả này ra một file văn bản chứa kết quả, tên
cũng do người dùng đặt và nhập từ bàn phím.

2) Mô tả chương trình

Chương trình giải quyết bài toán (có tên là THONG_KE_ky_tu) có định nghĩa
kiểu danh sách và khai báo biến như sau:

Type
ptrtype = ^node; {kiểu con trỏ trỏ vào danh sách}
node = record {định nghĩa danh sách theo kiểu bản ghi}
data: char;
next: ptrtype;
end;


var
first: ptrtype; {con trỏ trỏ vào nút đầu tiên của danh sách, là biến toàn cục}

name1, name2, name3: string; {các tên file văn bản}

Chương trình gồm 3 thủ tục chính:

procedure Creatlist (name: string);
procedure Thongkekytudon (name: string);
procedure Thongkekytudoi (name: string);

+) Thủ tục procedure Creatlist (name: string); thực hiện bước 1: tạo danh sách
chứa toàn bộ ký tự đơn của file văn bản có tên name, vẫn giữ nguyên trật tự như
trong file văn bản. Thủ tục này gồm một số hàm và thủ tục sau:

- Hàm function Getchar: char; , trả lại một ký tự đơn hợp lệ (khác các dấu và
ký tự đặc biệt) lấy được từ file văn bản, nếu không có trả lại rỗng. Nội dung của
hàm này như sau:

const {khai báo mảng chứa các ký tự không hợp lệ}
delimitset: set of char = [chr(0), chr(13), chr(10), chr(0). . Chr(64),
chr(91). . Chr(96), chr(123). . Chr(127)];
Var
w ,ch: char;
more: boolean;
begin
ch: =chr(0);
more: =true;
while not eof(f) do
begin

read(f, ch); {đọc từng ký tự của file văn bản vào biến ch}
more: = ch in delimitset;

if not more then {nếu ký tự là hợp lệ}
begin
getchar: =ch;
break; {thoát ngay ra khỏi vòng lặp để có getchar:=ch hợp lệ}
end;
end;
end;

- Thủ tục procedure Insertlist (Item: char; var last: ptrtype); chèn một nút mới
có nội dung Item vào cuối danh sách, nút cuối danh sách được định vị bởi tham
biến last kiểu con trỏ. Nội dung của thủ tục này như sau:

var
tempptr: ptrtype;
begin
new (tempptr); {xin cấp phát nút mới}
tempptr^.data: = Item; {nội dung nút mới là Item}
if first=nil then {trường hợp danh sách rỗng}
first: =tempptr;
else
last^.next: =tempptr; {gắn nút mới vào cuối danh sách}
last: =tempptr; {xác định lại con trỏ last}
last^.next: =nil;
end;

Và đây là phần thân chương trình của thủ tục procedure Creatlist (name: string);


begin
assign(f, name); reset(f); {mở file văn bản nguồn}

first: =nil; last: =first; {khởi tạo danh sách}
while not eof(f) do
begin
Item: =getchar; {lấy từng ký tự đơn vào biến Item}
if (Item<>chr(0)) and ((Item<>chr(11)) {lọc để đảm bảo Item là hợp lệ}
then insertlist(Item, last); {chèn vào danh sách}
end;
close(f); {đóng file văn bản nguồn để bảo vệ dữ liệu}
end;

+) Thủ tục procedure thongkekytudon (name: string); thống kê từ đơn và ghi
kết quả ra file có tên name. Thủ tục này có phần khai báo như sau:

type
ptrrec = ^rec; {định nghĩa một kiểu con trỏ mới, trỏ vào danh sách thống kê từ}
rec = record {định nghĩa từng nút của danh sách}
term: char; {chứa từ}
lanxh: integer; {chứa số lần xuất hiện của từ}
nextrec: ptrrec; {con trỏ trỏ tới nút kế tiếp}
end;

var
f: text; {biến gắn với tên file chứa kết quả thống kê từ đơn}
firstrec, lastrec: ptrrec; {2 con trỏ trỏ vào đầu và cuối danh sách thống kê}

Thủ tục này bao gồm các hàm và thủ tục như sau:


- Hàm function InRec (w: char): boolean;
{tìm trong danh sách rec nút có nội dung w}

var
currrec: ptrrec;
begin
currrec: = firstrec; {bắt đầu tìm từ nút đầu tiên của danh sách}
inrec: =false;
while (currrec<>nil) do
begin
if currrec^.term=w then
begin
inrec: =true;
break; {ngắt ra khỏi vòng lặp nếu đã tìm thấy}
end;
currrec: = currrec^. Nextrec; {không tìm thấy, tiếp tục chuyển tới
nút sau}
end;
end;

- Thủ tục procedure InsertRec (L: char; N: integer; var lastrec: ptrrec); thêm
một nút mới vào cuối danh sách:
Var
tempptr: ptrrec;
begin
new (tempptr); {xin cấp phát một nút mới}
tempptr^.term: = L; {nạp nội dung cho nút mới}
tempptr^.lanxh: =N;
if firstrec=nil then {nếu danh sách rỗng}
firstrec:=tempptr

else
lastrec^.nextrec: =tempptr; {nối vào cuối danh sách}

lastrec: =tempptr; {định vị lại con trỏ last}
lastrec^.nextrec: =nil;
end;

- Thủ tục procedure Taodanhsachkytudon; tạo danh sách thống kê ký tự đơn
hoàn chỉnh để chuẩn bị đưa ra file kết quả.

var
currptr, currptr2: ptrtype;
ch: char;
count: integer;
begin
currptr: =first;
while currptr<>nil do {duyệt danh sách ở bước 1 để lấy từ}
begin
ch: =currptr^.data; {lấy từng từ vào biến ch}
currptr2: =first;
count: =0;
while currptr2<>nil do {tìm số lần lặp lại của từ}
begin
if (currptr2^.data=ch) then count: =count+1;
currptr2: =currptr2^.next;
end;
if not inrec(ch) then insertrec(ch, count, lastrec); {kiểm tra từ đã có
trong danh sách thống kê chưa, nếu chưa có thì đưa vào cuối danh sách}
currptr: =currptr^.next;
end;

end;


- Thủ tục procedure Ghirafile; duyệt danh sách thống kê ký tự đơn (rec) để ghi
ra file kết quả.
var
currptr: ptrrec;
begin
assign (f, name); rewrite(f); {mở file kết quả để ghi}
currptr: =firstrec; {bắt đầu duyệt từ nút đầu tiên}
while currptr<>nil do
begin {duyệt danh sách để ghi lên file f}
writeln(f, currptr^.term, ' ': 16-length(currptr^.term), 'xuat hien: ',
currptr^.lanxh: 10, ' lan');
currptr:=currptr^.nextrec;
end;
close(f);
end;

- Phần thân chương trình của thủ tục procedure thongkekytudon (name:
string);là


begin
firstrec: =nil;
lastrec: =firstrec;
taodanhsachkytudon;
ghirafile;
end;


+) Thủ tục procedure thongkekytudoi(name: string); thống kê số lần lặp lại
của một ký tự đơn sau một ký tự đơn khác trong ký tự đôi và ghi kết quả ra file

văn bản. Thủ tục này có nội dung như sau:
var
currptr, back, currptr2, back2: ptrtype; {khai báo các con trỏ để duyệt danh sách
thu được từ bước 1}
w1, w2: char;
count: integer;
f: text;
begin
assign (f, name); rewrite(f); {mở file kết quả để ghi}
currptr: =first;
back: =currptr^.next;
while (currptr<>nil) and (back<>nil) do
begin
w1: =currptr^.data; {lấy ký tự đứng trước}
w2: =back^.data; {lấy ký tự đứng sau}
currptr2: =first;
back2: =currptr2^.next;
count: =0;
while ((currptr2<>nil) and (back2<>nil)) do {duyệt danh sách để
tìm số lần lăp lại}
begin
if ((currptr2^.data=w1) and (back2^.data=w2)) then
count: =count+1;
currptr2: =currptr2^.next;
back2: =back2^.next;
end;
writeln (f, w2, ' sau ', w1, ' ', count: 10, ' lan'); {ghi thông tin ra file f}

currptr: =currptr^.next;
back: =back^.next;

end;
close(f);
end;

3) Nội dung chương trình:

Program THONG_KE_ky_tu;

TYPE
ptrtype = ^node;
node = record
data: char;
next: ptrtype;
end;

VAR
first: ptrtype;
name1, name2, name3: string;

procedure Creatlist (name: string);
{tao lap danh sach chua cac tu cua file}
Var
f: text;
Item: char;
last: ptrtype;



Function Getchar: char;
Const

delimitset: set of char = [chr(0), chr(13), chr(10), chr(0). . Chr(64),
chr(91). . Chr(96), chr(123). . Chr(127)];
Var
ch: char;
more: boolean;
Begin
ch: =chr(0);
more: =true;
while not eof(f) do
begin
read(f, ch);
more: = ch in delimitset;
if not more then
begin
getchar: =ch;
break;
end;
end;
End;

Procedure Insertlist (Item: char; var last: ptrtype);
Var
tempptr: ptrtype;
Begin
new (tempptr);
tempptr^. Data: = Item;
if first=nil then

first: =tempptr
else

last^. Next: =tempptr;
last: =tempptr;
last^. Next: =nil;
End;

Begin
assign(f, name); reset(f);
first: =nil; last: =first;
while not eof(f) do
begin
Item: =getchar;
if (item<>chr(0)) and (item<>chr(11)) then

begin
insertlist(Item, last);
end;
end;
close(f);
End;

procedure thongkekytudon(name: string);
{thong ke ky tu don va ghi ket qua ra file co ten name}

Type
ptrrec = ^rec;
rec = record
term: char;

lanxh: integer;
nextrec: ptrrec;

end;

Var
f: text;
firstrec, lastrec: ptrrec;

Function InRec(w: char): boolean;
{tim w trong list}
Var
currrec: ptrrec;
Begin
currrec: = firstrec;
inrec: =false;
while (currrec<>nil) do
begin
if currrec^. Term =w then
begin
inrec: =true;
break;
end;
currrec: = currrec^. Nextrec;
end;
End;

Procedure InsertRec (L: char; N: integer; var lastrec: ptrrec);
Var
tempptr: ptrrec;


Begin

new (tempptr);
tempptr^. Term: = L;
tempptr^. Lanxh: =N;
if firstrec=nil then
firstrec: =tempptr
else
lastrec^. Nextrec: =tempptr;
lastrec: =tempptr;
lastrec^. Nextrec: =nil;
End;

Procedure Taodanhsachkytudon;
Var
currptr, currptr2: ptrtype;
ch: char;
count: integer;
Begin
currptr: =first;
while currptr<>nil do
begin
ch: =currptr^. Data;
currptr2: =first;
count: =0;
while currptr2<>nil do
begin
if (currptr2^. Data=ch) then count: =count+1;
currptr2: =currptr2^. Next;

×