(* * Copyright (c) 2008-2010, Lucian Bentea * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *) {$I ../DeHL.Defines.inc} unit DeHL.Collections.SortedList; interface uses SysUtils, DeHL.Base, DeHL.Types, DeHL.Exceptions, DeHL.Arrays, DeHL.StrConsts, DeHL.Serialization, DeHL.Collections.Base; type /// The generic sorted list collection. /// This type uses an internal array to store its values. TSortedList = class(TEnexCollection, IEnexIndexedCollection, IList, IOrderedList, IDynamic) private type {$REGION 'Internal Types'} TEnumerator = class(TEnumerator) private FVer: NativeUInt; FList: TSortedList; FCurrentIndex: NativeUInt; public { Constructor } constructor Create(const AList: TSortedList); { Destructor } destructor Destroy(); override; function GetCurrent(): T; override; function MoveNext(): Boolean; override; end; {$ENDREGION} private var FArray: TArray; FLength: NativeUInt; FVer: NativeUInt; FAscending: Boolean; { Internal insertion } procedure Insert(const AIndex: NativeUInt; const AValue: T); protected /// Called when the serialization process is about to begin. /// The serialization data exposing the context and other serialization options. procedure StartSerializing(const AData: TSerializationData); override; /// Called when the deserialization process is about to begin. /// The deserialization data exposing the context and other deserialization options. /// Default implementation. procedure StartDeserializing(const AData: TDeserializationData); override; /// Called when an element has been deserialized and needs to be inserted into the list. /// The element that was deserialized. /// This method simply adds the element to the list. procedure DeserializeElement(const AElement: T); override; /// Returns the item from a given index. /// The index in the list. /// The element at the specified position. /// is out of bounds. function GetItem(const AIndex: NativeUInt): T; /// Returns the number of elements in the list. /// A positive value specifying the number of elements in the list. function GetCount(): NativeUInt; override; /// Returns the current capacity. /// A positive number that specifies the number of elements that the list can hold before it /// needs to grow again. /// The value of this method is greater or equal to the amount of elements in the list. If this value /// is greater then the number of elements, it means that the list has some extra capacity to operate upon. function GetCapacity(): NativeUInt; public /// Creates a new instance of this class. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// The default type object is requested. constructor Create(const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// Specifies the initial capacity of the list. /// The default type object is requested. constructor Create(const AInitialCapacity: NativeUInt; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// A collection to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// is nil. /// The default type object is requested. constructor Create(const ACollection: IEnumerable; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// An array to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// The default type object is requested. constructor Create(const AArray: array of T; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// An array to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// The default type object is requested. constructor Create(const AArray: TDynamicArray; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// An array to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// The default type object is requested. constructor Create(const AArray: TFixedArray; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// A type object decribing the elements in the list. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// is nil. constructor Create(const AType: IType; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// A type object decribing the elements in the list. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// Specifies the initial capacity of the list. /// is nil. constructor Create(const AType: IType; const AInitialCapacity: NativeUInt; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// A type object decribing the elements in the list. /// A collection to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// is nil. /// is nil. constructor Create(const AType: IType; const ACollection: IEnumerable; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// A type object decribing the elements in the list. /// An array to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// is nil. constructor Create(const AType: IType; const AArray: array of T; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// A type object decribing the elements in the list. /// An array to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// is nil. constructor Create(const AType: IType; const AArray: TDynamicArray; const AAscending: Boolean = true); overload; /// Creates a new instance of this class. /// A type object decribing the elements in the list. /// An array to copy elements from. /// Specifies whether the elements are kept sorted in ascending order. Default is True. /// is nil. constructor Create(const AType: IType; const AArray: TFixedArray; const AAscending: Boolean = true); overload; /// Destroys this instance. /// Do not call this method directly, call Free instead. destructor Destroy(); override; /// Clears the contents of the list. /// This method clears the list and invokes type object's cleaning routines for each element. procedure Clear(); /// Adds an element to the list. /// The value to add. /// The added value is not appended. The list tries to figure out whre to insert it to keep its elements /// ordered at all times. procedure Add(const AValue: T); overload; /// Add the elements from a collection to the list. /// The values to add. /// The added values are not appended. The list tries to figure out where to insert the new values /// to keep its elements ordered at all times. /// is nil. procedure Add(const ACollection: IEnumerable); overload; /// Removes a given value from the list. /// The value to remove. /// If the list does not contain the given value, nothing happens. procedure Remove(const AValue: T); /// Removes an element from the list at a given index. /// The index from which to remove the element. /// This method removes the specified element and moves all following elements to the left by one. /// is out of bounds. procedure RemoveAt(const AIndex: NativeUInt); /// Checks whether the list contains a given value. /// The value to check. /// True if the value was found in the list; False otherwise. /// This method uses binary search beacause the list is always sorted. function Contains(const AValue: T): Boolean; /// Searches for the first appearance of a given element in this list. /// The value to search for. /// The index to from which the search starts. /// The number of elements after the starting one to check against. /// -1 if the value was not found; otherwise a positive value indicating the index of the value. /// This method uses binary search beacause the list is always sorted. /// Parameter combination is incorrect. function IndexOf(const AValue: T; const AStartIndex, ACount: NativeUInt): NativeInt; overload; /// Searches for the first appearance of a given element in this list. /// The value to search for. /// The index to from which the search starts. /// -1 if the value was not found; otherwise a positive value indicating the index of the value. /// This method uses binary search beacause the list is always sorted. /// is out of bounds. function IndexOf(const AValue: T; const AStartIndex: NativeUInt): NativeInt; overload; /// Searches for the first appearance of a given element in this list. /// The value to search for. /// This method uses binary search beacause the list is always sorted. /// -1 if the value was not found; otherwise a positive value indicating the index of the value. function IndexOf(const AValue: T): NativeInt; overload; /// Searches for the last appearance of a given element in this list. /// The value to search for. /// The index to from which the search starts. /// The number of elements after the starting one to check against. /// -1 if the value was not found; otherwise a positive value indicating the index of the value. /// This method uses binary search beacause the list is always sorted. /// Parameter combination is incorrect. function LastIndexOf(const AValue: T; const AStartIndex, ACount: NativeUInt): NativeInt; overload; /// Searches for the last appearance of a given element in this list. /// The value to search for. /// The index to from which the search starts. /// -1 if the value was not found; otherwise a positive value indicating the index of the value. /// This method uses binary search beacause the list is always sorted. /// is out of bounds. function LastIndexOf(const AValue: T; const AStartIndex: NativeUInt): NativeInt; overload; /// Searches for the last appearance of a given element in this list. /// The value to search for. /// -1 if the value was not found; otherwise a positive value indicating the index of the value. /// This method uses binary search beacause the list is always sorted. function LastIndexOf(const AValue: T): NativeInt; overload; /// Specifies the number of elements in the list. /// A positive value specifying the number of elements in the list. property Count: NativeUInt read FLength; /// Specifies the current capacity. /// A positive number that specifies the number of elements that the list can hold before it /// needs to grow again. /// The value of this property is greater or equal to the amount of elements in the list. If this value /// if greater then the number of elements, it means that the list has some extra capacity to operate upon. property Capacity: NativeUInt read GetCapacity; /// Returns the item from a given index. /// The index in the collection. /// The element at the specified position. /// is out of bounds. property Items[const AIndex: NativeUInt]: T read GetItem; default; /// Returns a new enumerator object used to enumerate this list. /// This method is usually called by compiler generated code. Its purpose is to create an enumerator /// object that is used to actually traverse the list. /// An enumerator object. function GetEnumerator(): IEnumerator; override; /// Removes the excess capacity from the list. /// This method can be called manually to force the list to drop the extra capacity it might hold. For example, /// after performing some massive operations of a big list, call this method to ensure that all extra memory held by the /// list is released. procedure Shrink(); /// Forces the list to increase its capacity. /// Call this method to force the list to increase its capacity ahead of time. Manually adjusting the capacity /// can be useful in certain situations. procedure Grow(); /// Copies the specified elements into a new list. /// The index to from which the copy starts. /// The number of elements to copy. /// A new list containing the copied elements. /// Parameter combination is invalid. function Copy(const AStartIndex: NativeUInt; const ACount: NativeUInt): TSortedList; overload; /// Copies the specified elements into a new list. /// The index to from which the copy starts. /// A new list containing the copied elements. /// is out of bounds. function Copy(const AStartIndex: NativeUInt): TSortedList; overload; /// Creates a copy of this list. /// A new list containing the copied elements. function Copy(): TSortedList; overload; /// Copies the values stored in the list to a given array. /// An array where to copy the contents of the list. /// The index into the array at which the copying begins. /// This method assumes that has enough space to hold the contents of the list. /// is out of bounds. /// There array is not long enough. procedure CopyTo(var AArray: array of T; const AStartIndex: NativeUInt); overload; override; /// Checks whether the list is empty. /// True if the list is empty; False otherwise. /// This method is the recommended way of detecting if the list is empty. function Empty(): Boolean; override; /// Returns the biggest element. /// An element from the list considered to have the biggest value. /// The list is empty. function Max(): T; override; /// Returns the smallest element. /// An element from the list considered to have the smallest value. /// The list is empty. function Min(): T; override; /// Returns the first element. /// The first element in the list. /// The list is empty. function First(): T; override; /// Returns the first element or a default if the list is empty. /// The default value returned if the list is empty. /// The first element in list if the list is not empty; otherwise is returned. function FirstOrDefault(const ADefault: T): T; override; /// Returns the last element. /// The last element in the list. /// The list is empty. function Last(): T; override; /// Returns the last element or a default if the list is empty. /// The default value returned if the list is empty. /// The last element in list if the list is not empty; otherwise is returned. function LastOrDefault(const ADefault: T): T; override; /// Returns the single element stored in the list. /// The element in list. /// This method checks if the list contains just one element, in which case it is returned. /// The list is empty. /// There is more than one element in the list. function Single(): T; override; /// Returns the single element stored in the list, or a default value. /// The default value returned if there is less or more elements in the list. /// The element in the list if the condition is satisfied; is returned otherwise. /// This method checks if the list contains just one element, in which case it is returned. Otherwise /// the value in is returned. function SingleOrDefault(const ADefault: T): T; override; /// Aggregates a value based on the list's elements. /// The aggregator method. /// A value that contains the list's aggregated value. /// This method returns the first element if the list only has one element. Otherwise, /// is invoked for each two elements (first and second; then the result of the first two /// and the third, and so on). The simples example of aggregation is the "sum" operation where you can obtain the sum of all /// elements in the value. /// is nil. /// The list is empty. function Aggregate(const AAggregator: TFunc): T; override; /// Aggregates a value based on the list's elements. /// The aggregator method. /// The default value returned if the list is empty. /// A value that contains the list's aggregated value. If the list is empty, is returned. /// This method returns the first element if the list only has one element. Otherwise, /// is invoked for each two elements (first and second; then the result of the first two /// and the third, and so on). The simples example of aggregation is the "sum" operation where you can obtain the sum of all /// elements in the value. /// is nil. function AggregateOrDefault(const AAggregator: TFunc; const ADefault: T): T; override; /// Returns the element at a given position. /// The index from which to return the element. /// The element from the specified position. /// The list is empty. /// is out of bounds. function ElementAt(const AIndex: NativeUInt): T; override; /// Returns the element at a given position. /// The index from which to return the element. /// The default value returned if the list is empty. /// The element from the specified position if the list is not empty and the position is not out of bounds; otherwise /// the value of is returned. function ElementAtOrDefault(const AIndex: NativeUInt; const ADefault: T): T; override; /// Check whether at least one element in the list satisfies a given predicate. /// The predicate to check for each element. /// True if the at least one element satisfies a given predicate; False otherwise. /// This method traverses the whole list and checks the value of the predicate for each element. This method /// stops on the first element for which the predicate returns True. The logical equivalent of this operation is "OR". /// is nil. function Any(const APredicate: TFunc): Boolean; override; /// Checks that all elements in the list satisfy a given predicate. /// The predicate to check for each element. /// True if all elements satisfy a given predicate; False otherwise. /// This method traverses the whole list and checks the value of the predicate for each element. This method /// stops on the first element for which the predicate returns False. The logical equivalent of this operation is "AND". /// is nil. function All(const APredicate: TFunc): Boolean; override; /// Checks whether the elements in this list are equal to the elements in another collection. /// The collection to compare to. /// True if the collections are equal; False if the collections are different. /// This methods checks that each element at position X in this list is equal to an element at position X in /// the provided collection. If the number of elements in both collections are different, then the collections are considered different. /// Note that comparison of element is done using the type object used by this list. This means that comparing this collection /// to another one might yeild a different result than comparing the other collection to this one. /// is nil. function EqualsTo(const ACollection: IEnumerable): Boolean; override; end; /// The generic sorted list collection designed to store objects. /// This type uses an internal array to store its objects. TObjectSortedList = class(TSortedList) private FWrapperType: TObjectWrapperType; { Getters/Setters for OwnsObjects } function GetOwnsObjects: Boolean; procedure SetOwnsObjects(const Value: Boolean); protected /// Installs the type object. /// The type object to install. /// This method installs a custom wrapper designed to suppress the cleanup of objects on request. /// Make sure to call this method in descendant classes. /// is nil. procedure InstallType(const AType: IType); override; public /// Specifies whether this list owns the objects stored in it. /// True if the list owns its objects; False otherwise. /// This property controls the way the list controls the life-time of the stored objects. property OwnsObjects: Boolean read GetOwnsObjects write SetOwnsObjects; end; implementation const DefaultArrayLength = 32; { TSortedList } procedure TSortedList.Insert(const AIndex: NativeUInt; const AValue: T); var I : NativeInt; Cap: NativeUInt; begin if AIndex > FLength then ExceptionHelper.Throw_ArgumentOutOfRangeError('AIndex'); if FLength = NativeUInt(Length(FArray)) then Grow(); { Move the array to the right } if AIndex < FLength then for I := FLength downto (AIndex + 1) do FArray[I] := FArray[I - 1]; Inc(FLength); { Put the element into the new position } FArray[AIndex] := AValue; Inc(FVer); end; procedure TSortedList.Add(const ACollection: IEnumerable); var V: T; begin if (ACollection = nil) then ExceptionHelper.Throw_ArgumentNilError('ACollection'); { Enumerate and add, preserving order} for V in ACollection do Add(V); end; function TSortedList.Aggregate(const AAggregator: TFunc): T; var I: NativeUInt; begin { Check arguments } if not Assigned(AAggregator) then ExceptionHelper.Throw_ArgumentNilError('AAggregator'); if FLength = 0 then ExceptionHelper.Throw_CollectionEmptyError(); { Select the first element as comparison base } Result := FArray[0]; { Iterate over the last N - 1 elements } for I := 1 to FLength - 1 do begin { Aggregate a value } Result := AAggregator(Result, FArray[I]); end; end; function TSortedList.AggregateOrDefault(const AAggregator: TFunc; const ADefault: T): T; var I: NativeUInt; begin { Check arguments } if not Assigned(AAggregator) then ExceptionHelper.Throw_ArgumentNilError('AAggregator'); if FLength = 0 then Exit(ADefault); { Select the first element as comparison base } Result := FArray[0]; { Iterate over the last N - 1 elements } for I := 1 to FLength - 1 do begin { Aggregate a value } Result := AAggregator(Result, FArray[I]); end; end; function TSortedList.All(const APredicate: TFunc): Boolean; var I: NativeUInt; begin if not Assigned(APredicate) then ExceptionHelper.Throw_ArgumentNilError('APredicate'); if FLength > 0 then for I := 0 to FLength - 1 do if not APredicate(FArray[I]) then Exit(false); Result := true; end; function TSortedList.Any(const APredicate: TFunc): Boolean; var I: NativeUInt; begin if not Assigned(APredicate) then ExceptionHelper.Throw_ArgumentNilError('APredicate'); if FLength > 0 then for I := 0 to FLength - 1 do if APredicate(FArray[I]) then Exit(true); Result := false; end; procedure TSortedList.Add(const AValue: T); var I: NativeUInt; Sign: NativeInt; begin if FAscending then Sign := 1 else Sign := -1; I := 0; while I < FLength do begin if ((ElementType.Compare(AValue, FArray[I]) * Sign) < 0) then Break; Inc(I); end; Insert(I, AValue); end; procedure TSortedList.Clear; var I: NativeInt; begin if (ElementType <> nil) and (ElementType.Management() = tmManual) and (FLength > 0) then begin { Should cleanup each element individually } for I := 0 to FLength - 1 do ElementType.Cleanup(FArray[I]); end; { Reset the length } FLength := 0; end; function TSortedList.Contains(const AValue: T): Boolean; begin { Pass the call to AIndex of } Result := (IndexOf(AValue) > -1); end; function TSortedList.Copy(const AStartIndex: NativeUInt): TSortedList; begin { Pass the call down to the more generic function } Copy(AStartIndex, (FLength - AStartIndex)); end; function TSortedList.Copy(const AStartIndex, ACount: NativeUInt): TSortedList; var NewList: TSortedList; begin { Check for zero elements } if (FLength = 0) then begin Result := TSortedList.Create(ElementType); Exit; end; { Check for indexes } if (AStartIndex >= FLength) then ExceptionHelper.Throw_ArgumentOutOfRangeError('AStartIndex'); { Check for indexes } if ((AStartIndex + ACount) > FLength) then ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount'); { Create a new list } NewList := TSortedList.Create(ElementType, ACount); { Copy all elements safely } &Array.SafeMove(FArray, NewList.FArray, AStartIndex, 0, ACount, ElementType); { Set new count } NewList.FLength := ACount; Result := NewList; end; procedure TSortedList.CopyTo(var AArray: array of T; const AStartIndex: NativeUInt); begin { Check for indexes } if AStartIndex >= NativeUInt(Length(AArray)) then ExceptionHelper.Throw_ArgumentOutOfRangeError('AStartIndex'); if (NativeUInt(Length(AArray)) - AStartIndex) < FLength then ExceptionHelper.Throw_ArgumentOutOfSpaceError('AArray'); { Copy all elements safely } &Array.SafeMove(FArray, AArray, 0, AStartIndex, FLength, ElementType); end; constructor TSortedList.Create(const AType: IType; const AAscending: Boolean); begin { Call upper constructor } Create(AType, DefaultArrayLength, AAscending); end; constructor TSortedList.Create(const AType: IType; const ACollection: IEnumerable; const AAscending: Boolean); var V: T; begin { Call upper constructor } Create(AType, DefaultArrayLength, AAscending); { Initialize instance } if (ACollection = nil) then ExceptionHelper.Throw_ArgumentNilError('ACollection'); { Try to copy the given Enumerable } for V in ACollection do begin { Perform a simple push } Add(V); end; end; constructor TSortedList.Create(const AAscending: Boolean); begin Create(TType.Default, AAscending); end; constructor TSortedList.Create(const AInitialCapacity: NativeUInt; const AAscending: Boolean); begin Create(TType.Default, AInitialCapacity, AAscending); end; constructor TSortedList.Create(const ACollection: IEnumerable; const AAscending: Boolean); begin Create(TType.Default, ACollection, AAscending); end; constructor TSortedList.Create(const AType: IType; const AInitialCapacity: NativeUInt; const AAscending: Boolean); begin { Initialize instance } if (AType = nil) then ExceptionHelper.Throw_ArgumentNilError('AType'); InstallType(AType); FLength := 0; FVer := 0; FAscending := AAscending; SetLength(FArray, AInitialCapacity); end; procedure TSortedList.DeserializeElement(const AElement: T); begin { Simple as hell ... } Add(AElement); end; destructor TSortedList.Destroy; begin { Clear list first } Clear(); inherited; end; function TSortedList.ElementAt(const AIndex: NativeUInt): T; begin { Simply use the getter } Result := GetItem(AIndex); end; function TSortedList.ElementAtOrDefault(const AIndex: NativeUInt; const ADefault: T): T; begin { Check range } if (AIndex >= FLength) then Result := ADefault else Result := FArray[AIndex]; end; function TSortedList.Empty: Boolean; begin Result := (FLength = 0); end; function TSortedList.EqualsTo(const ACollection: IEnumerable): Boolean; var V: T; I: NativeUInt; begin I := 0; for V in ACollection do begin if I >= FLength then Exit(false); if not ElementType.AreEqual(FArray[I], V) then Exit(false); Inc(I); end; if I < FLength then Exit(false); Result := true; end; function TSortedList.First: T; begin { Check length } if FLength = 0 then ExceptionHelper.Throw_CollectionEmptyError(); Result := FArray[0]; end; function TSortedList.FirstOrDefault(const ADefault: T): T; begin { Check length } if FLength = 0 then Result := ADefault else Result := FArray[0]; end; function TSortedList.GetCapacity: NativeUInt; begin Result := Length(FArray); end; function TSortedList.GetCount: NativeUInt; begin Result := FLength; end; function TSortedList.GetEnumerator: IEnumerator; begin { Create an enumerator } Result := TEnumerator.Create(Self); end; function TSortedList.GetItem(const AIndex: NativeUInt): T; begin { Check range } if (AIndex >= FLength) then ExceptionHelper.Throw_ArgumentOutOfRangeError('AIndex'); { Get value } Result := FArray[AIndex]; end; procedure TSortedList.Grow; begin { Grow the array } if FLength < DefaultArrayLength then SetLength(FArray, FLength + DefaultArrayLength) else SetLength(FArray, FLength * 2); end; function TSortedList.IndexOf(const AValue: T): NativeInt; begin { Call more generic function } Result := IndexOf(AValue, 0, FLength); end; function TSortedList.IndexOf(const AValue: T; const AStartIndex: NativeUInt): NativeInt; begin { Call more generic function } Result := IndexOf(AValue, AStartIndex, (FLength - AStartIndex)); end; function TSortedList.IndexOf(const AValue: T; const AStartIndex, ACount: NativeUInt): NativeInt; var I, J: NativeInt; begin Result := -1; if FLength = 0 then Exit; { Check for indexes } if (AStartIndex >= FLength) then ExceptionHelper.Throw_ArgumentOutOfRangeError('AStartIndex'); { Check for indexes } if ((AStartIndex + ACount) > FLength) then ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount'); { Search for the value } J := &Array.BinarySearch(FArray, AValue, AStartIndex, ACount, ElementType, FAscending); if J = -1 then Exit(-1) else Inc(J, AStartIndex); for I := J - 1 downto AStartIndex do if not ElementType.AreEqual(AValue, FArray[I]) then begin Result := I + 1; Exit; end; Result := J; end; function TSortedList.LastIndexOf(const AValue: T; const AStartIndex: NativeUInt): NativeInt; begin { Call more generic function } Result := LastIndexOf(AValue, AStartIndex, (FLength - AStartIndex)); end; function TSortedList.Last: T; begin { Check length } if FLength = 0 then ExceptionHelper.Throw_CollectionEmptyError(); Result := FArray[FLength - 1]; end; function TSortedList.LastIndexOf(const AValue: T): NativeInt; begin { Call more generic function } Result := LastIndexOf(AValue, 0, FLength); end; function TSortedList.LastOrDefault(const ADefault: T): T; begin { Check length } if FLength = 0 then Result := ADefault else Result := FArray[FLength - 1]; end; function TSortedList.Max: T; var I: NativeUInt; begin { Check length } if FLength = 0 then ExceptionHelper.Throw_CollectionEmptyError(); { Default one } Result := FArray[0]; for I := 1 to FLength - 1 do if ElementType.Compare(FArray[I], Result) > 0 then Result := FArray[I]; end; function TSortedList.Min: T; var I: NativeUInt; begin { Check length } if FLength = 0 then ExceptionHelper.Throw_CollectionEmptyError(); { Default one } Result := FArray[0]; for I := 1 to FLength - 1 do if ElementType.Compare(FArray[I], Result) < 0 then Result := FArray[I]; end; function TSortedList.LastIndexOf(const AValue: T; const AStartIndex, ACount: NativeUInt): NativeInt; var I, J: NativeInt; begin Result := -1; if FLength = 0 then Exit; { Check for indexes } if (AStartIndex >= FLength) then ExceptionHelper.Throw_ArgumentOutOfRangeError('AStartIndex'); { Check for indexes } if ((AStartIndex + ACount) > FLength) then ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount'); { Search for the value } J := &Array.BinarySearch(FArray, AValue, AStartIndex, ACount, ElementType, FAscending); if J = -1 then Exit(-1) else Inc(J, AStartIndex); for I := J + 1 to AStartIndex + ACount - 1 do if not ElementType.AreEqual(AValue, FArray[I]) then begin Result := I - 1; Exit; end; Result := J; end; procedure TSortedList.Remove(const AValue: T); var I, FoundIndex: NativeInt; begin { Defaults } if (FLength = 0) then Exit; FoundIndex := -1; for I := 0 to FLength - 1 do begin if ElementType.AreEqual(FArray[I], AValue) then begin FoundIndex := I; Break; end; end; if FoundIndex > -1 then begin { Move the list } if FLength > 1 then for I := FoundIndex to FLength - 2 do FArray[I] := FArray[I + 1]; Dec(FLength); Inc(FVer); end; end; procedure TSortedList.RemoveAt(const AIndex: NativeUInt); var I: NativeInt; begin if AIndex >= FLength then ExceptionHelper.Throw_ArgumentOutOfRangeError('AIndex'); if (FLength = 0) then Exit; { Clanup the element at the specified AIndex if required } if ElementType.Management() = tmManual then ElementType.Cleanup(FArray[AIndex]); { Move the list } if FLength > 1 then for I := AIndex to FLength - 2 do FArray[I] := FArray[I + 1]; Dec(FLength); Inc(FVer); end; procedure TSortedList.Shrink; begin { Cut the capacity if required } if FLength < Capacity then begin SetLength(FArray, FLength); end; end; function TSortedList.Single: T; begin { Check length } if FLength = 0 then ExceptionHelper.Throw_CollectionEmptyError() else if FLength > 1 then ExceptionHelper.Throw_CollectionHasMoreThanOneElement() else Result := FArray[0]; end; function TSortedList.SingleOrDefault(const ADefault: T): T; begin { Check length } if FLength = 0 then Result := ADefault else if FLength > 1 then ExceptionHelper.Throw_CollectionHasMoreThanOneElement() else Result := FArray[0]; end; procedure TSortedList.StartDeserializing(const AData: TDeserializationData); var LAsc: Boolean; begin AData.GetValue(SSerAscendingKeys, LAsc); { Call the constructor in this instance to initialize myself first } Create(LAsc); end; procedure TSortedList.StartSerializing(const AData: TSerializationData); begin { Write the AAscending sign } AData.AddValue(SSerAscendingKeys, FAscending); end; function TSortedList.Copy: TSortedList; begin { Call a more generic function } Result := Copy(0, FLength); end; constructor TSortedList.Create(const AArray: array of T; const AAscending: Boolean); begin Create(TType.Default, AArray, AAscending); end; constructor TSortedList.Create(const AType: IType; const AArray: array of T; const AAscending: Boolean); var I: NativeInt; begin { Call upper constructor } Create(AType, DefaultArrayLength, AAscending); { Copy from array } for I := 0 to Length(AArray) - 1 do begin Add(AArray[I]); end; end; constructor TSortedList.Create(const AArray: TFixedArray; const AAscending: Boolean); begin Create(TType.Default, AArray, AAscending); end; constructor TSortedList.Create(const AArray: TDynamicArray; const AAscending: Boolean); begin Create(TType.Default, AArray, AAscending); end; constructor TSortedList.Create(const AType: IType; const AArray: TFixedArray; const AAscending: Boolean); var I: NativeUInt; begin { Call upper constructor } Create(AType, DefaultArrayLength, AAscending); { Copy from array } if AArray.Length > 0 then for I := 0 to AArray.Length - 1 do begin Add(AArray[I]); end; end; constructor TSortedList.Create(const AType: IType; const AArray: TDynamicArray; const AAscending: Boolean); var I: NativeUInt; begin { Call upper constructor } Create(AType, DefaultArrayLength, AAscending); { Copy from array } if AArray.Length > 0 then for I := 0 to AArray.Length - 1 do begin Add(AArray[I]); end; end; { TSortedList.TEnumerator } constructor TSortedList.TEnumerator.Create(const AList: TSortedList); begin { Initialize } FList := AList; KeepObjectAlive(FList); FCurrentIndex := 0; FVer := FList.FVer; end; destructor TSortedList.TEnumerator.Destroy; begin ReleaseObject(FList); inherited; end; function TSortedList.TEnumerator.GetCurrent: T; begin if FVer <> FList.FVer then ExceptionHelper.Throw_CollectionChangedError(); if FCurrentIndex > 0 then Result := FList.FArray[FCurrentIndex - 1] else Result := default(T); end; function TSortedList.TEnumerator.MoveNext: Boolean; begin if FVer <> FList.FVer then ExceptionHelper.Throw_CollectionChangedError(); Result := FCurrentIndex < FList.FLength; Inc(FCurrentIndex); end; { TObjectSortedList } procedure TObjectSortedList.InstallType(const AType: IType); begin { Create a wrapper over the real type class and switch it } FWrapperType := TObjectWrapperType.Create(AType); { Install overridden type } inherited InstallType(FWrapperType); end; function TObjectSortedList.GetOwnsObjects: Boolean; begin Result := FWrapperType.AllowCleanup; end; procedure TObjectSortedList.SetOwnsObjects(const Value: Boolean); begin FWrapperType.AllowCleanup := Value; end; end.