Reputation: 84590
Is there any way to extract the highest (or lowest) value in a set? For example, if I have the following "set of byte":
[0, 1, 2, 4, 5, 28, 199]
is there any function I could run on that and get back 199 as the result?
EDIT: There's an obvious brute-force solution involving a for..in loop. If possible, I'd like to find a better way than that.
Upvotes: 2
Views: 6558
Reputation: 11
Highest and lowest, not use of Math unit:
type
TCharSet = set of Char;
function MaxOfSet(aSet: TCharSet):Char;
var
Data:array[0..SizeOf(TCharSet)-1] of Byte absolute aSet;
i,r:Byte;
begin
if aSet<>[] then begin
i:=SizeOf(TCharSet)-1;
while (i>0) and (Data[i]=0) do
Dec(i);
r:=i*8;
i:=Data[i];
while (i and $80)=0 do begin
i:=i shl 1;
Dec(r)
end;
Result:=Chr(r+7)
end
else
raise Exception.Create('Unable to extract max value from an empty set');
end;
function MinOfSet(aSet: TCharSet):Char;
var
Data:array[0..SizeOf(TCharSet)-1] of Byte absolute aSet;
i,r:Byte;
begin
if aSet<>[] then begin
i:=0;
while (i<SizeOf(TCharSet)-1) and (Data[i]=0) do
Inc(i);
r:=i*8;
i:=Data[i];
while (i and 1)=0 do begin
i:=i shr 1;
Inc(r)
end;
Result:=Chr(r)
end
else
raise Exception.Create('Unable to extract min value from an empty set');
end;
Upvotes: 1
Reputation: 11
Nearly the same, but shorter:
type
TByteSet = set of Byte;
function MaxSet(S: TByteSet): Byte;
var
CardArr: Array [0..7] of Cardinal absolute S;
i: Byte;
begin
i := 7;
while (i > 0) AND (CardArr[i] = 0) do
Dec(i);
Result := i + Floor(Log2(CardArr[i]));
end;
Upvotes: 1
Reputation: 16862
Here is a little quicker version that uses knowledge of the internal structure of the byte set.
type
TByteSet = set of Byte;
function HighestElement(const ByteSet: TByteSet): Byte;
type
TSetBytes = array[0..SizeOf(TByteSet) - 1] of Byte;
var
I, J: Integer;
B: Byte;
SetBytes: TSetBytes;
begin
if ByteSet <> [] then
begin
SetBytes := TSetBytes(ByteSet);
// Start at the top and work down, one byte at a time
for I := SizeOf(TByteSet) - 1 downto 0 do
begin
// Any bits set here
B := SetBytes[I];
if B <> 0 then
begin
Result := I * 8;
for J := 0 to 7 do
if (B shr J) and 1 <> 0 then
begin
Result := Result + J;
Exit;
end;
end;
end;
end else
// No elements set
end;
You can change the type of the set, TByteSet, to nearly any set type, and this function should still work. Just replace TByteSet within the function decl and body with the type of your set. You can also modify it to return the actual element type if using a set of AnsiChar, or a set of some enumeration type. To get the lowest value, change the "I" for loop to "0 to SizeOf(TByteSet) - 1" and the if test in the "J" loop to "if (B shl J) and $80 <> 0"
Upvotes: 2
Reputation: 81526
Sets are unordered, so you're not going to get much better than a loop. If you're constantly looking up the min/max value of a set, then use a heap data structure, which provides O(1) lookup to get the min/max value and O(log n) lookup on any other value.
Upvotes: 6
Reputation: 163317
A loop really is the formally correct way to do it.
type
TSetType = set of TEnumType;
function HighestMember(const s: TSetType): TEnumType;
begin
for Result := High(Result) downto Low(Result) do
if Result in s then
exit;
raise Exception.Create('empty sets have no highest member');
end;
Any other kind of solution will require type-casting or assembler, both of which force you to lose type-safety — they go "outside the language," so to speak.
If you can guarantee that your set has no more than 32 possible elements, then the set can be overlaid with an ordinary integer, and your question is equivalent to asking for the position of the highest bit set in a 32-bit integer. That's been asked here before, pretty much:
If you don't have a 32-element restriction on the set type, then you have Delphi's 256-element limit, and any bit-twiddling solution will need to handle a 32-byte input.
Upvotes: 15