|
| 1 | +program ConvexHull; |
| 2 | + |
| 3 | +{$mode objfpc}{$H+} |
| 4 | + |
| 5 | +uses |
| 6 | + Classes, |
| 7 | + Generics.Collections, |
| 8 | + Sysutils; |
| 9 | + |
| 10 | +type |
| 11 | + TPoint = record |
| 12 | + X, Y: integer; |
| 13 | + end; |
| 14 | + |
| 15 | + TPointList = specialize TList<TPoint>; |
| 16 | + TIntegerList = specialize TList<integer>; |
| 17 | + |
| 18 | +procedure ShowUsage; |
| 19 | +begin |
| 20 | + Writeln('Usage: please provide at least 3 x and y coordinates as separate lists (e.g. "100, 440, 210")'); |
| 21 | + Halt(1); |
| 22 | +end; |
| 23 | + |
| 24 | +function ParseIntegerList(const S: string): TIntegerList; |
| 25 | +var |
| 26 | + Tokens: TStringArray; |
| 27 | + Token: string; |
| 28 | + Value: integer; |
| 29 | +begin |
| 30 | + if S.Trim.IsEmpty then |
| 31 | + ShowUsage; |
| 32 | + |
| 33 | + Tokens := S.Split([',']); |
| 34 | + Result := TIntegerList.Create; |
| 35 | + for Token in Tokens do |
| 36 | + begin |
| 37 | + if not TryStrToInt(Trim(Token), Value) then |
| 38 | + begin |
| 39 | + Result.Free; |
| 40 | + ShowUsage; |
| 41 | + end; |
| 42 | + Result.Add(Value); |
| 43 | + end; |
| 44 | + |
| 45 | + if Result.Count < 3 then |
| 46 | + begin |
| 47 | + Result.Free; |
| 48 | + ShowUsage; |
| 49 | + end; |
| 50 | +end; |
| 51 | + |
| 52 | +function CheckListLengths(const Xs, Ys: TIntegerList): boolean; |
| 53 | +begin |
| 54 | + if Xs.Count <> Ys.Count then |
| 55 | + Exit(False); |
| 56 | + |
| 57 | + if Xs.Count < 3 then |
| 58 | + Exit(false); |
| 59 | + |
| 60 | + Result := True; |
| 61 | +end; |
| 62 | + |
| 63 | +procedure QuickSortPoints(List: TPointList; Lo, Hi: integer); |
| 64 | +var |
| 65 | + i, j: integer; |
| 66 | + Pivot, Temp: TPoint; |
| 67 | + |
| 68 | + procedure SwapElements(Index1, Index2: integer); |
| 69 | + var |
| 70 | + Temp: TPoint; |
| 71 | + begin |
| 72 | + Temp := List[Index1]; |
| 73 | + List[Index1] := List[Index2]; |
| 74 | + List[Index2] := Temp; |
| 75 | + end; |
| 76 | + |
| 77 | +begin |
| 78 | + while Lo < Hi do |
| 79 | + begin |
| 80 | + i := Lo; |
| 81 | + j := Hi; |
| 82 | + Pivot := List[Lo + (Hi - Lo) div 2]; |
| 83 | + |
| 84 | + repeat |
| 85 | + while (List[i].X < Pivot.X) or ((List[i].X = Pivot.X) and |
| 86 | + (List[i].Y < Pivot.Y)) do |
| 87 | + Inc(i); |
| 88 | + while (List[j].X > Pivot.X) or ((List[j].X = Pivot.X) and |
| 89 | + (List[j].Y > Pivot.Y)) do |
| 90 | + Dec(j); |
| 91 | + |
| 92 | + if i <= j then |
| 93 | + begin |
| 94 | + SwapElements(i, j); |
| 95 | + Inc(i); |
| 96 | + Dec(j); |
| 97 | + end; |
| 98 | + until i > j; |
| 99 | + |
| 100 | + if (j - Lo) < (Hi - i) then |
| 101 | + begin |
| 102 | + if Lo < j then QuickSortPoints(List, Lo, j); |
| 103 | + Lo := i; |
| 104 | + end |
| 105 | + else |
| 106 | + begin |
| 107 | + if i < Hi then QuickSortPoints(List, i, Hi); |
| 108 | + Hi := j; |
| 109 | + end; |
| 110 | + end; |
| 111 | +end; |
| 112 | + |
| 113 | +function Cross(const O, A, B: TPoint): int64; |
| 114 | +begin |
| 115 | + Result := int64(A.X - O.X) * (B.Y - O.Y) - int64(A.Y - O.Y) * (B.X - O.X); |
| 116 | +end; |
| 117 | + |
| 118 | +procedure BuildHull(const Points: TPointList; Hull: TPointList); |
| 119 | +var |
| 120 | + PointCount, i: integer; |
| 121 | + CurrentIndex, NextIndex, LeftmostIndex: integer; |
| 122 | +begin |
| 123 | + PointCount := Points.Count; |
| 124 | + if PointCount < 3 then |
| 125 | + begin |
| 126 | + Hull.AddRange(Points); |
| 127 | + Exit; |
| 128 | + end; |
| 129 | + |
| 130 | + // Find the leftmost point (with lowest X; if tie, highest Y) |
| 131 | + LeftmostIndex := 0; |
| 132 | + for i := 1 to PointCount - 1 do |
| 133 | + if (Points[i].X < Points[LeftmostIndex].X) or |
| 134 | + ((Points[i].X = Points[LeftmostIndex].X) and |
| 135 | + (Points[i].Y > Points[LeftmostIndex].Y)) then |
| 136 | + LeftmostIndex := i; |
| 137 | + |
| 138 | + CurrentIndex := LeftmostIndex; |
| 139 | + repeat |
| 140 | + Hull.Add(Points[CurrentIndex]); |
| 141 | + |
| 142 | + NextIndex := (CurrentIndex + 1) mod PointCount; |
| 143 | + for i := 0 to PointCount - 1 do |
| 144 | + if Cross(Points[CurrentIndex], Points[i], Points[NextIndex]) < 0 then |
| 145 | + NextIndex := i; |
| 146 | + |
| 147 | + CurrentIndex := NextIndex; |
| 148 | + until CurrentIndex = LeftmostIndex; |
| 149 | +end; |
| 150 | + |
| 151 | +var |
| 152 | + Xs, Ys: TIntegerList; |
| 153 | + Points, Hull: TPointList; |
| 154 | + i: integer; |
| 155 | + Pt: TPoint; |
| 156 | +begin |
| 157 | + if ParamCount <> 2 then |
| 158 | + ShowUsage; |
| 159 | + |
| 160 | + Xs := ParseIntegerList(ParamStr(1)); |
| 161 | + Ys := ParseIntegerList(ParamStr(2)); |
| 162 | + |
| 163 | + if not CheckListLengths(Xs, Ys) then |
| 164 | + begin |
| 165 | + Xs.Free; |
| 166 | + Ys.Free; |
| 167 | + ShowUsage; |
| 168 | + end; |
| 169 | + |
| 170 | + Points := TPointList.Create; |
| 171 | + Hull := TPointList.Create; |
| 172 | + try |
| 173 | + for i := 0 to Xs.Count - 1 do |
| 174 | + begin |
| 175 | + Pt.X := Xs[i]; |
| 176 | + Pt.Y := Ys[i]; |
| 177 | + Points.Add(Pt); |
| 178 | + end; |
| 179 | + |
| 180 | + QuickSortPoints(Points, 0, Points.Count - 1); |
| 181 | + BuildHull(Points, Hull); |
| 182 | + |
| 183 | + for i := 0 to Hull.Count - 1 do |
| 184 | + Writeln('(', Hull[i].X, ', ', Hull[i].Y, ')'); |
| 185 | + |
| 186 | + finally |
| 187 | + Xs.Free; |
| 188 | + Ys.Free; |
| 189 | + Points.Free; |
| 190 | + Hull.Free; |
| 191 | + end; |
| 192 | +end. |
0 commit comments