I'm looking for advices to speed up my implementation of Dijkstra Shortest Path Search on a weighted graph which is a square matrix N x N. The weight on horizontal vertice is called H (resp. V on vertical ones).
A picture is worth a thousand words:
(source: free.fr)
Of course, this is part of a bigger application, but I've extracted the relevant bit here:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
N = 200; //Working on a grid of N x N, here for a quick test, in practice, it's more 10000
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
end;
TNode = class
public
ID, //Number of the Node
origin, //From which Node did I came?
weight : integer; //The total weight of the path to Node ID
done : boolean; //Is the Node already explored?
constructor Create(myID, myOrigin, myweight: integer);
end;
var
Form1: TForm1;
implementation
var
H, V : array of integer;
{$R *.dfm}
constructor TNode.Create(myID, myOrigin, myweight: integer);
begin
ID:=MyID;
origin:=MyOrigin;
weight:=MyWeight;
end;
{------------------------------------------------------------------------------}
Function GetNodeFromID(ID: integer; NodeList: TList) : TNode; overload;
var
I: Integer;
Node: TNode;
begin
result:=nil;
for I := 0 to NodeList.count-1 do
begin
Node := NodeList[i];
if Node.ID=ID then
begin
result:=Node;
break;
end;
end;
end;
{------------------------------------------------------------------------------}
Function GetNodeOfMiniWeight(NodeList: TList) : TNode; overload;
var
I, min: Integer;
Node: TNode;
begin
result:=nil;
min :=maxint;
for I := 0 to NodeList.count-1 do
begin
Node := NodeList[i];
if Node.done then continue;
if Node.weight < min then
begin
result:=Node;
min := Node.weight;
end;
end;
end;
{------------------------------------------------------------------------------}
procedure SearchShortestPath(origin,arrival: integer);
var
NewWeight: integer;
NodeList : Tlist;
NodeFrom, //The Node currently being examined
NodeTo :TNode; //The Node where it is intented to go
s : string;
begin
NodeList := Tlist.Create;
NodeFrom := TNode.Create(origin,MaxInt,0);
NodeList.Add(NodeFrom);
while not (NodeFrom.ID = arrival) do //Arrived?
begin
//Path toward the top
if (NodeFrom.ID > N-1) //Already at the top of the grid
and not(NodeFrom.origin-NodeFrom.ID = N) then //Coming from the top
begin
NewWeight:=NodeFrom.weight + H[NodeFrom.ID-N];
NodeTo := GetNodeFromID(NodeFrom.ID-N, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID-N,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
//Path toward the bottom
if (NodeFrom.ID < N*N-N) //Already at the bottom of the grid
and not(NodeFrom.Origin- NodeFrom.ID = N) then //Coming from the bottom
begin
NewWeight:=NodeFrom.weight + H[NodeFrom.ID];
NodeTo := GetNodeFromID(NodeFrom.ID+N, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID+N,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
//Path toward the right
if not(NodeFrom.ID mod N = N-1) //Already at the extrem right of the grid
and not(NodeFrom.Origin - NodeFrom.ID = 1) then //Coming from the right
begin
NewWeight:=NodeFrom.weight + V[NodeFrom.ID];
NodeTo := GetNodeFromID(NodeFrom.ID+1, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID+1,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
//Path toward the left
if not (NodeFrom.ID mod N = 0) //Already at the extrem right of the grid
and not(NodeFrom.Origin - NodeFrom.ID = -1) then //Coming from the left
begin
NewWeight:=NodeFrom.weight + V[NodeFrom.ID-1];
NodeTo := GetNodeFromID(NodeFrom.ID-1, NodeList);
if NodeTo <> nil then
begin
if NodeTo.weight > NewWeight then
begin
NodeTo.Origin:=NodeFrom.ID;
NodeTo.weight:=NewWeight;
end;
end
else
begin
NodeTo := TNode.Create(NodeFrom.ID-1,NodeFrom.ID,NewWeight);
NodeList.Add(NodeTo);
end;
end;
NodeFrom.done :=true;
NodeFrom:=GetNodeOfMiniWeight(NodeList);
end;
s:='The shortest path from '
+ inttostr(arrival) + ' to '
+ inttostr(origin) + ' is : ';
//Get the path
while (NodeFrom.ID <> origin) do
begin
s:= s + inttostr(NodeFrom.ID) + ', ';
NodeFrom:=GetNodeFromID(NodeFrom.Origin, NodeList);
end;
s:= s + inttostr(NodeFrom.ID);
ShowMessage(s);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SearchShortestPath(Random(N*N),Random(N*N));
end;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
//Initialisation
randomize;
SetLength(V,N*N);
SetLength(H,N*N);
for I := 0 to N*N-1 do
begin
V[I]:=random(100);
H[I]:=random(100);
end;
end;
end.
The code spend most of the time in the routines: GetNodeFromID and GetNodeOfMiniWeight, and a substantial time to create nodes.
I thought that I could use a binary search, but since it requires the list to be sorted, I think that I'll loose the time in sorting the list. Any advice is welcome.
First of all, use a profiler! For instance, see http://www.delphitools.info/samplingprofiler
Your current code has several weaknesses:
It leaks memory (TNode/TNodeList instances);
You may use dynamic arrays of records instead of individual class instances for nodes (with a count stored outside);
I was not able to recognize your algorithm just by reading the code - so I guess you may enhance the code design.
The pseudo-code of this algorithm is as followed:
for all vertices v,
dist(v) = infinity;
dist(first) = 0;
place all vertices in set toBeChecked;
while toBeChecked is not empty
{in this version, also stop when shortest path to a specific destination is found}
select v: min(dist(v)) in toBeChecked;
remove v from toBeChecked;
for u in toBeChecked, and path from v to u exists
{i.e. for unchecked adjacents to v}
do
if dist(u) > dist(v) + weight({u,v}),
then
dist(u) = dist(v) + weight({u,v});
set predecessor of u to v
save minimum distance to u in array "d"
endif
enddo
endwhile
Did you try this library from DelphiForFun ? Sounds like something already proven, updated recently, and well written. May be improved (e.g. using an array of bits instead array of boolean), but sounds pretty correct for a start.
I've implemented modification of Dijkstra Shortest Path algorithm for sparsed graphs. Your graph is very sparsed (E << V^2). This code uses priority queue based on binary heap, that contains (VerticeNum, DistanceFromSource) pairs as TPoints, ordered by Distance (Point.Y). It reveals loglinear (close to linear) asymptotic behavior. Example for small graph:
Times for i5-4670
N V time, ms
100 10^4 ~15
200 4*10^4 ~50-60 //about 8000 for your implementation
400 1.6*10^5 100
1600 2.5*10^6 1300
6400 4*10^7 24000
10000 10^8 63000
//~max size in 32-bit OS due to H,V arrays memory consumption
code:
function SparseDijkstra(Src, Dest: integer): string;
var
Dist, PredV: array of integer;
I, j, vert, CurDist, toVert, len: integer;
q: TBinaryHeap;
top: TPoint;
procedure CheckAndChange;
begin
if Dist[vert] + len < Dist[toVert] then begin
Dist[toVert] := Dist[vert] + len;
PredV[toVert] := vert;
q.Push(Point(toVert, Dist[toVert]));
//old pair is still stored but has bad (higher) distance value
end;
end;
begin
SetLength(Dist, N * N);
SetLength(PredV, N * N);
for I := 0 to N * N - 1 do
Dist[I] := maxint;
Dist[Src] := 0;
q := TBinaryHeap.Create(N * N);
q.Cmp := ComparePointsByY;
q.Push(Point(Src, 0));
while not q.isempty do begin
top := q.pop;
vert := top.X;
CurDist := top.Y;
if CurDist > Dist[vert] then
continue; //out-of-date pair (bad distance value)
if (vert mod N) <> 0 then begin // step left
toVert := vert - 1;
len := H[toVert];
CheckAndChange;
end;
if (vert div N) <> 0 then begin // step up
toVert := vert - N;
len := V[toVert];
CheckAndChange;
end;
if (vert mod N) <> N - 1 then begin // step right
toVert := vert + 1;
len := H[vert];
CheckAndChange;
end;
if (vert div N) <> N - 1 then begin // step down
toVert := vert + N;
len := V[vert];
CheckAndChange;
end;
end;
q.Free;
// calculated data may be used with miltiple destination points
result := '';
vert := Dest;
while vert <> Src do begin
result := Format(', %d', [vert]) + result;
vert := PredV[vert];
end;
result := Format('%d', [vert]) + result;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
t: Dword;
I, row, col: integer;
begin
t := GetTickCount;
if N < 6 then // visual checker
for I := 0 to N * N - 1 do begin
col := I mod N;
row := I div N;
Canvas.Font.Color := clBlack;
Canvas.Font.Style := [fsBold];
Canvas.TextOut(20 + col * 70, row * 70, inttostr(I));
Canvas.Font.Style := [];
Canvas.Font.Color := clRed;
if col < N - 1 then
Canvas.TextOut(20 + col * 70 + 30, row * 70, inttostr(H[I]));
Canvas.Font.Color := clBlue;
if row < N - 1 then
Canvas.TextOut(20 + col * 70, row * 70 + 30, inttostr(V[I]));
end;
Memo1.Lines.Add(SparseDijkstra({0, n*n-1}random(N * N), random(N * N)));
Memo1.Lines.Add('time ' + inttostr(GetTickCount - t));
end;
Edit: TQPriorityQueue is class for internal use, but you can try any implementation of heap-based priority queue. For example, this one. You have to change Pointer to TPoint, Word to Integer in this module.
Edit2:
I've replaced proprietary queue method names in my procedure by BinaryHeap methods.
Related
I'm programming concurrency in Pascal-FC using Eclipse Gavab 2.0. I haven't had any problems so far using it, as it always inform me of which the errors are when it's unable to execute a program.
I did the producer-consumer problem using semaphores and it worked alright. Now I've done it using a monitor, but when I run it, it launches for a second then stops, and does nothing else. It shows no errors at all, and I can't find anything wrong with the code. Is it a compiler's problem?
Producer-Consumer using semaphores:
program ProdCons;
const MAXDATOS = 10;
{ Creamos el buffer de comunicación: }
type tBuffer = record
datos : array [1..MAXDATOS] of integer;
posInser, posSacar : integer;
nProductos, nHuecos, em : semaphore;
end;
var buffer : tBuffer;
{ METODOS O PROCEDIMIENTOS: }
procedure inicializar(var buffer : tBuffer);
begin
buffer.posInser := 1;
buffer.posSacar := 1;
initial(buffer.nProductos,0);
initial(buffer.nHuecos,MAXDATOS);
initial(buffer.em,1);
end;
procedure insertar(dato : integer; var buffer : tBuffer);
begin
wait(buffer.nHuecos); {En num de huecos debe ser >0}
wait(buffer.em);
buffer.datos[buffer.posInser] := dato;
writeln('Inserta dato ',dato,' en datos[',buffer.posInser,']');
buffer.posInser := buffer.posInser MOD MAXDATOS + 1;
signal(buffer.em);
signal(buffer.nProductos); {Confirmamos que num. productos >0}
end;
procedure sacar(dato : integer; var buffer : tBuffer);
begin
wait(buffer.nProductos); {Esperamos a que num. productos >0}
wait(buffer.em);
dato:=buffer.datos[buffer.posSacar];
writeln('Consume dato ',dato,' en datos[',buffer.posSacar,']');
buffer.posSacar := buffer.posSacar MOD MAXDATOS + 1;
signal(buffer.em);
signal(buffer.nHuecos);
end;
{ PROCESOS TIPO: }
process type tProductor(var buffer : tBuffer);
var dato : integer;
begin
repeat
dato := random(200);
insertar(dato,buffer);
forever
end;
process type tConsumidor(var buffer : tBuffer);
var dato : integer;
begin
repeat
sacar(dato,buffer);
forever
end;
{ Variables: }
var i : integer;
prod : array [1..5] of tProductor;
cons : array [1..3] of tConsumidor;
begin
inicializar(buffer);
cobegin
for i:=1 to 5 do
prod[i](buffer);
for i:=1 to 3 do
cons[i](buffer);
coend;
end.
Producer-Consumer with semaphore's output
Producer-Consumer using a monitor:
program ProdConsMONITORES;
const N = 5;
monitor ProdCons;
export produce, consume;
{Variables}
var posProd, posCons, cont : integer;
obj : array [1..N] of integer;
vacio, lleno : condition;
{Procedimientos/metodos}
procedure produce(dato,i : integer);
begin
if cont=N then delay(lleno);
obj[posProd] := dato;
writeln('Productor',i,' produce dato ',dato,' en obj[',posProd,']');
posProd := posProd MOD N + 1;
cont := cont + 1;
writeln(' [',cont,' objetos disponibles]');
resume(vacio);
end;
procedure consume(j : integer);
var dato := integer;
begin
if cont=0 then delay(vacio);
dato := obj[posCons];
writeln('Consumidor',j,' consume dato ',dato,' en obj[',posCons,']');
posCons := posCons MOD N + 1;
cont := cont - 1;
writeln(' [',cont,' objetos disponibles]');
resume(lleno);
end;
{Procesos}
process type Productor(i : integer);
var dato : integer;
begin
dato := random(10);
ProdCons.produce(dato,i);
end;
process type Consumidor(j : integer);
begin
ProdCons.consume(j);
end;
{Variables locales}
var p : array [1..5] of Productor;
var c : array [1..3] of Consumidor;
var i,j : integer;
begin
cont := 0;
posProd := 1; posCons := 1;
cobegin
writeln('hola');
for i:=1 to 5 do
p[i](i);
for j:=1 to 3 do
c[j](j);
coend;
end.
Producer-Consumer with monitor's output
Just in case someone else has this problem: the error was I didn't create the body of the monitor, which is a "begin-end" where variables are initialized. It must be placed right after the procedures and goes like this:
begin
cont := 0;
posProd := 1; posCons := 1;
end;
Obviously it's no longer necesary to initialize vars. at the last begin-end.
I am starting to learn programming.
I have to do (HW) program where I can add in example name and surname, add it to single linked list and than display this single linke list.
I tried to do it, program is compiling and it is even working. I can add data and it is displayed. But I propably dont understan something and make mistake. I am adding first person, it is displayed, than I am adding second person, it is also displayed but the first person data is override by the second person I added. So I have two the same records. Listbox is only showing data from the single linked list so I suppose there is no problem. Each pointer should point different data so why each record is the same as the last one I added?
Here is my code:
type
wskaznik = ^Lista;
Lista = record
lp : string;
dane : string;
wsk : wskaznik;
end;
var
Form1 : TForm1;
First, current : wskaznik;
tekst : string;
liczba : string;
i, k : integer;
implementation
{$R *.lfm}
{ TForm1 }
procedure AddToList(dane : string; lp : string; var current : wskaznik);
var
prev, Next : wskaznik;
begin
if current <> nil then
begin
prev := current;
Next := current^.wsk;
end
else
begin
prev := nil;
Next := nil;
end;
new(current);
current^.dane := dane;
current^.lp := lp;
current^.wsk := Next;
if prev <> nil then
prev^.wsk := current;
end;
procedure GetAddr(dane : string; var First, current : wskaznik);
var
Next : wskaznik;
begin
if First <> nil then
begin
Next := First;
repeat
if Next^.wsk <> nil then
Next := Next^.wsk
until (Next^.wsk = nil) or (Next^.dane = dane);
current := Next;
end;
end;
procedure GetNum(n : integer; var First, current : wskaznik);
var
Next : wskaznik;
begin
if First <> nil then
if n = 1 then
current := First
else
if (n = 2) and (First^.wsk = nil) then
n := 0
else
begin
Next := First;
i := 1;
repeat
Inc(i);
if Next^.wsk <> nil then
Next := Next^.wsk
until (i = n) or (Next^.wsk = nil);
if (Next^.wsk = nil) and (i < n) then
n := 0
else
current := Next;
end;
end;
procedure List;
var
l : integer;
begin
form1.listbox1.Clear;
form1.listbox2.Clear;
for l := 1 to i do
begin
Getnum(l, First, current);
if l > 1 then
form1.listbox1.items.add(current^.dane);
form1.listbox2.items.add(current^.lp);
end;
end;
procedure findLess(dane : string; lp : string; var First, current : wskaznik);
var
tmp, Next : wskaznik;
begin
if First <> nil then
begin
Next := First;
repeat
if (Next^.wsk <> nil) then
begin
tmp := Next;
Next := Next^.wsk;
end;
until (Next^.wsk = nil) or (Next^.dane > dane);
if Next^.dane > dane then
current := tmp
else
current := Next;
if Next^.lp > lp then
current := tmp
else
current := Next;
end;
end;
procedure TForm1.Button1Click(Sender : TObject);
begin
Inc(i);
findLess(edit1.Text, edit2.Text, First, current);
addtolist(edit1.Text, edit2.Text, current);
label3.Caption := 'Elementów: ' + IntToStr(i - 1);
//edit1.SetFocus;
list;
end;
end.
You never assign anything to First (assuming Firstis to be the beginning of the list).
The first time you call AddToList you should assign Current to First
I have problem. If condition in for loop is checking only once. What's the problem? Here is my code:
program Planas;
function skaiciuoti() : integer;
var z, zz, d, dt, dp, i, sk : integer;
Fr, Fw : text;
begin
Assign(Fr, 'Duomenys.txt');
Reset(Fr);
ReadLn(Fr, d, z);
zz := 0;
dt := d;
for i := 1 to d do
begin
Read(Fr, sk);
zz := zz + sk;
if sk >= z then
dt := d - 1;
end;
z := d * z - zz;
dp := z div d;
if z mod d <> 0
then dp := dp + 1;
Close(Fr);
WriteLn(dt);
WriteLn(z);
WriteLn(dp);
end;
begin
skaiciuoti();
Readln;
end.
P.S Thank you for your answers in advance! :)
Perhaps you have a typo in your code in dt := d - 1. The value of d seems to be constant within the loop, so dt would not change after the first successful check. I guess you might have wanted to decrement dt by using dt := dt - 1.
I'm trying compile Doubly linked list but I have one small problem with this part of code :
It's a "insert after"
When I insert data to the list : 1 2 3 4 5 and want insert after number 4 for example number 9, number 9 is inserted like this : 1 9 2 3 4 5. I really dont know where I have wrong code:(
everytime i insert new number to second position..
procedure insertAfter(var L:Plist; n_p:integer);
var novy_za:Plist;
begin
new(novy_za);
novy_za^.info:=n_p;
novy_za^.next:=L^.next;
novy_za^.prev:=L;
L^.next:=novy_za;
if novy_za^.next<>nil
then novy_za^.next^.prev:=novy_za;
end;
procedure call :
begin
writeln;
write('which number insert : '); readln(x);
writeln;
write('insert after : '); readln(Y);
InsertAfter(P,x);
end;
Here's a corrected version of your routine. Your original failed to accept the value you want to insert after as an argument. I made it a function so that it returns the pointer to the new added element if it was able to find the one you wanted to insert after. Otherwise, it returns nil. You call it with a second argument which tells it what value you want to insert after.
function insertAfter(var L: Plist; n_a, n_p: integer): Plist;
var novy_za, cur_za: Plist;
begin
cur_za := L;
novy_za := nil;
{ Traverse the list, looking for value n_a and, if found, insert the new
element n_p after it }
while cur_za <> nil do begin
{ Insert the new element after the one with value n_a }
if cur_za^.info = n_a then begin
new(novy_za);
novy_za^.info := n_p;
novy_za^.next := cur_za^.next;
novy_za^.prev := cur_za;
cur_za^.next := novy_za;
if novy_za^.next <> nil then
novy_za^.next^.prev := novy_za;
break; { Exit the loop }
end;
cur_za := cur_za^.next;
end;
insertAfter := novy_za;
end;
Function call (this assumes P is defined somewhere as a valid Plist, and x and y are defined as info type):
begin
writeln;
write('which number insert : '); readln(x);
writeln;
write('insert after : '); readln(y);
{ Insert the value x after the number y }
if insertAfter(P, y, x) = nil then
writeln('The value ', y, ' was not found')
else
writeln('The value ', x, ' was successfully inserted after ', y);
end;
In PL/SQL, is there any way to calculate next serial number from another one like 'A1B0010C'. Next serial no will be A1B0011C (+1). My idea is retrieve number part, increase it get return string back.
I can do this in java, but passing more than 1000 elements to Oracle will cause problems in IN clause.
So please help, any suggestion is appreciated.
Try to write some recursive function like this
This Function returns next character. ex: D after C. 0 after Z.
create or replace function get_next_character(ch char)
return char
is
result_ch char(1) := ch;
begin
IF UPPER(ch) = 'Z' or ch = '9' THEN
result_ch := 'A';
ELSE
result_ch := chr(ascii(ch) + 1);
END IF;
return upper(result_ch);
end get_next_character;
and this is your actual function which returns the next serial number
So, it generates 'A1B0010D' if your input is 'A1B0010C'
create or replace function get_next_serial(p_serial IN varchar2) -- PASS THE REQUIRED ARGUMENTS
return varchar2
IS
v_ret_serial varchar2(100);
v_in_serial varchar2(100) := p_serial;
tmp varchar2(100);
last_char char(1);
begin
tmp := v_in_serial;
for i in reverse 1..length(v_in_serial) loop
last_char := substr(tmp, length(tmp));
last_char := get_next_character(last_char);
tmp := substr(v_in_serial, 1, length(tmp)-1);
v_in_serial := substr(v_in_serial, 1, i-1) || last_char || substr(v_in_serial, i+1);
IF last_char <> 'A' then
exit;
END IF;
end loop;
IF last_char = 'A' THEN
v_in_serial:= 'A'||v_in_serial;
END IF;
return UPPER(v_in_serial);
exception
when NO_DATA_FOUND then
return 'AA';
when others then
return null;
end get_next_serial;
you can call this function (get_next_serial('abc')) where ever you want;
select get_next_serial('ALK0989KJ') from dual
You can place these two functions in a package and use at your convenience.
You can achieve it by using the following mix of Regexp_* functions:
SQL> with t1 as(
2 select 'A1B0010C' col from dual
3 )
4 select regexp_replace(col, '[[:digit:]]+'
5 , to_char(to_number(regexp_substr(col, '[[:digit:]]+',1,2) + 1), 'fm0000')
6 , 1
7 , 2
8 ) as Res
9 from t1
10 ;
RES
------------
A1B0011C
UPDATE In response to the comment.
SQL> with t1 as(
2 select 'A1B0010C' col from dual union all
3 select 'A1B0010C2' col from dual union all
4 select 'A1B0012C001' col from dual union all
5 select 'S000001' col from dual
6 )
7 select col
8 , regexp_replace(col
9 , '([[:digit:]]+)([[:alpha:]]+$|$)'
10 , LPad(to_char(to_number(num) + 1), length(num), '0') || '\2'
11 ) as Res
12 from (select col
13 , regexp_substr(col, '([[:digit:]]+)([[:alpha:]]+$|$)', 1, 1, 'i', 1) as num
14 from t1
15 )
16 ;
COL RES
----------- -----------------
A1B0010C A1B0011C
A1B0010C2 A1B0010C3
A1B0012C001 A1B0012C002
S000001 S000002
create or replace function nextSerial(s in varchar2) return varchar2 as
i integer;
done boolean := false;
c char;
newserial varchar2(4000);
begin
newserial := s;
i := length(newserial);
while i>0 and not done loop
c := substr(newserial, i, 1);
if c='9' then
newserial := substr(newserial, 1, i-1) || '0' || substr(newserial, i+1);
elsif c>='0' and c<='8' then
c := chr(ascii(c)+1);
newserial := substr(newserial, 1, i-1) || c || substr(newserial, i+1);
done := true;
end if;
i := i-1;
end loop;
if not done then
newserial := '1' || newserial;
end if;
return newserial;
end;