1 package PseudoHeap; 2 # 3 # File: PseudoHeap.pm 4 # Author: Manish Sud <msud@san.rr.com> 5 # 6 # Copyright (C) 2024 Manish Sud. All rights reserved. 7 # 8 # This file is part of MayaChemTools. 9 # 10 # MayaChemTools is free software; you can redistribute it and/or modify it under 11 # the terms of the GNU Lesser General Public License as published by the Free 12 # Software Foundation; either version 3 of the License, or (at your option) any 13 # later version. 14 # 15 # MayaChemTools is distributed in the hope that it will be useful, but without 16 # any warranty; without even the implied warranty of merchantability of fitness 17 # for a particular purpose. See the GNU Lesser General Public License for more 18 # details. 19 # 20 # You should have received a copy of the GNU Lesser General Public License 21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or 22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, 23 # Boston, MA, 02111-1307, USA. 24 # 25 26 use strict; 27 use Carp; 28 use Exporter; 29 use TextUtil (); 30 31 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 32 33 @ISA = qw(Exporter); 34 @EXPORT = qw(); 35 @EXPORT_OK = qw(); 36 37 %EXPORT_TAGS = ( 38 all => [@EXPORT, @EXPORT_OK] 39 ); 40 41 # Setup class variables... 42 my($ClassName); 43 _InitializeClass(); 44 45 use overload '""' => 'StringifyPseudoHeap'; 46 47 # PseudoHeap is designed to support tracking of a specific number of largest or smallest key/value 48 # pairs with numeric or alphanumeric keys along with corresponding scalar or reference values. 49 # 50 # Although PseudoHeap is similar to a heap, it lacks number of key properties of a traditional heap data 51 # structure: no concept of root, parent and child nodes; no ordering of keys in any particular order; no 52 # specific localtion greatest or smallest key. 53 # 54 # The keys are simply stored in a hash with each key poining to an array containing specified values. 55 # The min/max keys are updated during addition and deletion of key/value pairs; these can be retrieved 56 # by accessing corresponding hash. 57 # 58 # Addition and deletion of key/value is also straightforward using hashes. However, min/max keys 59 # need to be identified which is done using Perl sort on the keys. 60 # 61 # 62 # Class constructor... 63 # 64 sub new { 65 my($Class, %NamesAndValues) = @_; 66 67 # Initialize object... 68 my $This = {}; 69 bless $This, ref($Class) || $Class; 70 $This->_InitializePseudoHeap(); 71 72 $This->_InitializePseudoHeapProperties(%NamesAndValues); 73 74 return $This; 75 } 76 77 # Initialize object data... 78 # 79 sub _InitializePseudoHeap { 80 my($This) = @_; 81 82 # Type of pseudo heap: 83 # 84 # KeepTopN - Keep track of a specified number largest of key/value pairs 85 # KeepBottomN - Keep track of a specified number smallest of key/value pairs 86 # 87 $This->{Type} = undef; 88 89 # Type of keys: Numeric or Alphanumeric 90 # 91 # The value of KeyType determines comparison function used to sort and 92 # and compare keys for a specific heap type as shown below: 93 # 94 # Type KeyType Comp Sorting 95 # 96 # KeepTopN Numeric < Descending 97 # KeepTopN AlphaNumeric lt Descending 98 # KeepBottomN Numeric > Ascending 99 # KeepBottomN AlphaNumeric gt Ascending 100 # 101 $This->{KeyType} = undef; 102 103 # Maximum number of largest or smallest key/value pairs to keep... 104 # 105 $This->{MaxSize} = 10; 106 107 # Keys and values associated with each key as an array... 108 %{$This->{Keys}} = (); 109 110 # Min and max keys... 111 $This->{MinKey} = undef; 112 $This->{MaxKey} = undef; 113 114 # Number of key/valur pairs currently present... 115 $This->{CurrentSize} = 0; 116 117 # Number of keys currently present where each key correspond to multiple values... 118 $This->{KeysCount} = 0; 119 } 120 121 # Initialize class ... 122 sub _InitializeClass { 123 #Class name... 124 $ClassName = __PACKAGE__; 125 126 } 127 128 # Initialize object properties.... 129 # 130 sub _InitializePseudoHeapProperties { 131 my($This, %NamesAndValues) = @_; 132 my($Name, $Value, $MethodName); 133 134 while (($Name, $Value) = each %NamesAndValues) { 135 $MethodName = "Set${Name}"; 136 $This->$MethodName($Value); 137 } 138 139 if (!exists $NamesAndValues{Type}) { 140 croak "Error: ${ClassName}->New: Object can't be instantiated without specifying Type..."; 141 } 142 143 if (!exists $NamesAndValues{KeyType}) { 144 croak "Error: ${ClassName}->New: Object can't be instantiated without specifying KeyType..."; 145 } 146 } 147 148 # Set heap type... 149 # 150 sub SetType { 151 my($This, $Type) = @_; 152 153 if (defined $This->{Type}) { 154 croak "Error: ${ClassName}->SetType: Can't change Type..."; 155 } 156 157 if ($Type !~ /^(KeepTopN|KeepBottomN)$/i) { 158 croak "Error: ${ClassName}->SetType: Unknown PseudoHeap type: $Type; Supported types: KeepTopN or KeepBottomN..."; 159 } 160 $This->{Type} = $Type; 161 162 return $This; 163 } 164 165 # Get heap type.. 166 # 167 sub GetType { 168 my($This) = @_; 169 170 return defined $This->{Type} ? $This->{Type} : 'None'; 171 } 172 173 # Set key type... 174 # 175 sub SetKeyType { 176 my($This, $KeyType) = @_; 177 178 if (defined $This->{KeyType}) { 179 croak "Error: ${ClassName}->SetType: Can't change KeyType..."; 180 } 181 182 if ($KeyType !~ /^(Numeric|Alphanumeric)$/i) { 183 croak "Error: ${ClassName}->SetType: Unknown PseudoHeap key type: $KeyType; Supported key types: Numeric or Alphanumeric..."; 184 } 185 $This->{KeyType} = $KeyType; 186 187 return $This; 188 } 189 190 # Get key type.. 191 # 192 sub GetKeyType { 193 my($This) = @_; 194 195 return defined $This->{KeyType} ? $This->{KeyType} : 'None'; 196 } 197 198 # Add a key/value pair... 199 # 200 sub AddKeyValuePair { 201 my($This, $Key, $Value) = @_; 202 203 if (!(defined($Key) && defined($Value))) { 204 carp "Warning: ${ClassName}->AddKeyValuePair: No key added: Both key and value must be defined..."; 205 return undef; 206 } 207 208 $This->_AddKeyValuePair($Key, $Value); 209 210 return $This; 211 } 212 213 # Add multiple key/value pairs... 214 # 215 sub AddKeyValuePairs { 216 my($This, @KeyValuePairs) = @_; 217 218 if (!@KeyValuePairs) { 219 carp "Warning: ${ClassName}->AddKeyValuePairs: No keys added: Key/Value pairs list is empty..."; 220 return undef; 221 } 222 if (@KeyValuePairs % 2) { 223 carp "Warning: ${ClassName}->AddKeyValuePairs: No keys pairs added: Invalid key/value pairs data: Input list must contain even number of values..."; 224 return undef; 225 } 226 227 my($Key, $Value, $Index); 228 for ($Index = 0; $Index < $#KeyValuePairs; $Index += 2) { 229 $Key = $KeyValuePairs[$Index]; $Value = $KeyValuePairs[$Index + 1]; 230 $This->AddKeyValuePair($Key, $Value); 231 } 232 233 return $This; 234 } 235 236 # Delete specified keys along with all associated values for each key... 237 # 238 sub DeleteKeys { 239 my($This, @Keys) = @_; 240 241 if (!@Keys) { 242 carp "Warning: ${ClassName}->DeleteKeys: No keys deleted: Keys list is empty..."; 243 return undef; 244 } 245 my($Key); 246 for $Key (@Keys) { 247 $This->DeleteKey($Key); 248 } 249 250 return $This; 251 } 252 253 # Delete a sepcified key along with all of its associated values... 254 # 255 sub DeleteKey { 256 my($This, $Key) = @_; 257 258 if (!defined $Key ) { 259 carp "Warning: ${ClassName}->DeleteKey: No key deleted: Key must be specified..."; 260 return undef; 261 } 262 263 return $This->_DeleteKey($Key); 264 } 265 266 # Delete min key along with all of its associated values... 267 # 268 sub DeleteMinKey { 269 my($This) = @_; 270 271 return $This->DeleteKey($This->{MinKey}); 272 } 273 274 # Delete max key along with all of its associated values... 275 # 276 sub DeleteMaxKey { 277 my($This) = @_; 278 279 return $This->DeleteKey($This->{MaxKey}); 280 } 281 282 # Set max size... 283 # 284 sub SetMaxSize { 285 my($This, $Size) = @_; 286 287 if (!TextUtil::IsPositiveInteger($Size)) { 288 croak "Error: ${ClassName}->SetMaxSize: Max size value, $Size, is not valid: It must be a positive integer..."; 289 } 290 291 if (defined($This->{MinKey}) || defined($This->{MaxKey})) { 292 croak "Error: ${ClassName}->SetMaxSize: Can't change max size: Keys are already present..."; 293 } 294 295 $This->{MaxSize} = $Size; 296 297 return $This; 298 } 299 300 # Get max size... 301 # 302 sub GetMaxSize { 303 my($This) = @_; 304 305 return $This->{MaxMaxSize}; 306 } 307 308 # Get current size... 309 # 310 sub GetCurrentSize { 311 my($This) = @_; 312 313 return $This->{CurrentSize}; 314 } 315 316 # Get min key... 317 # 318 sub GetMinKey { 319 my($This) = @_; 320 321 return defined $This->{MinKey} ? $This->{MinKey} : 'None'; 322 } 323 324 # Get max key... 325 # 326 sub GetMaxKey { 327 my($This) = @_; 328 329 return defined $This->{MaxKey} ? $This->{MaxKey} : 'None'; 330 } 331 332 # Get keys... 333 # 334 sub GetKeys { 335 my($This) = @_; 336 337 return wantarray ? keys %{$This->{Keys}} : scalar keys %{$This->{Keys}}; 338 } 339 340 # Get sorted keys... 341 # 342 sub GetSortedKeys { 343 my($This) = @_; 344 my(@SortedKeys); 345 346 @SortedKeys = (); 347 if ($This->{Type} =~ /^KeepTopN$/i) { 348 @SortedKeys = ($This->{KeyType} =~ /^Numeric$/i) ? (sort { $b <=> $a } keys %{$This->{Keys}}) : (sort { $b cmp $a } keys %{$This->{Keys}}); 349 } 350 elsif ($This->{Type} =~ /^KeepBottomN$/i) { 351 @SortedKeys = ($This->{KeyType} =~ /^Numeric$/i) ? (sort { $a <=> $b } keys %{$This->{Keys}}) : (sort { $a cmp $b } keys %{$This->{Keys}}); 352 } 353 354 return wantarray ? @SortedKeys : scalar @SortedKeys; 355 } 356 357 # Get values associated with a specified key... 358 sub GetKeyValues { 359 my($This, $Key) = @_; 360 my(@KeyValues); 361 362 @KeyValues = (); 363 if (defined($Key) && exists($This->{Keys}{$Key})) { 364 @KeyValues = @{$This->{Keys}{$Key}}; 365 } 366 return wantarray ? @KeyValues : scalar @KeyValues; 367 } 368 369 # Add key/value pair... 370 # 371 sub _AddKeyValuePair{ 372 my($This, $Key, $Value) = @_; 373 374 if ($This->{CurrentSize} < $This->{MaxSize}) { 375 return $This->_AppendKeyValuePair($Key, $Value); 376 } 377 else { 378 return $This->_InsertKeyValuePair($Key, $Value); 379 } 380 } 381 382 # Append key/value pair... 383 # 384 sub _AppendKeyValuePair { 385 my($This, $Key, $Value) = @_; 386 387 if (!exists $This->{Keys}{$Key}) { 388 @{$This->{Keys}{$Key}} = (); 389 $This->{KeysCount} += 1; 390 391 $This->_CompareAndSetMinKey($Key); 392 $This->_CompareAndSetMaxKey($Key); 393 } 394 395 push @{$This->{Keys}{$Key}}, $Value; 396 $This->{CurrentSize} += 1; 397 398 return $This; 399 } 400 401 # Insert key/value pair... 402 # 403 sub _InsertKeyValuePair { 404 my($This, $Key, $Value) = @_; 405 406 # Is this key need to be inserted? 407 if (!$This->_IsKeyNeedToBeInserted($Key)) { 408 return $This; 409 } 410 411 # Insert key/value pair... 412 if (!exists $This->{Keys}{$Key}) { 413 @{$This->{Keys}{$Key}} = (); 414 $This->{KeysCount} += 1; 415 } 416 push @{$This->{Keys}{$Key}}, $Value; 417 $This->{CurrentSize} += 1; 418 419 # Remove min or max key/value pair along with its update... 420 my($KeyToDetele); 421 422 $KeyToDetele = ($This->{Type} =~ /^KeepTopN$/i) ? $This->{MinKey} : $This->{MaxKey}; 423 $This->_DeleteKeyValuePair($KeyToDetele); 424 425 return $This; 426 } 427 428 # Check whether it makes sense to insert specified key... 429 # 430 sub _IsKeyNeedToBeInserted { 431 my($This, $Key) = @_; 432 433 if ($This->{Type} =~ /^KeepTopN$/i) { 434 if ($This->{KeyType} =~ /^Numeric$/i) { 435 return ($Key < $This->{MinKey}) ? 0 : ((($This->{KeysCount} == 1) && ($This->{MinKey} == $Key)) ? 0 : 1); 436 } 437 else { 438 return ($Key lt $This->{MinKey}) ? 0 : ((($This->{KeysCount} == 1) && ($This->{MinKey} eq $Key)) ? 0 : 1); 439 } 440 } 441 elsif ($This->{Type} =~ /^KeepBottomN$/i) { 442 if ($This->{KeyType} =~ /^Numeric$/i) { 443 return ($Key > $This->{MaxKey}) ? 0 : ((($This->{KeysCount} == 1) && ($This->{MaxKey} == $Key)) ? 0 : 1); 444 } 445 else { 446 return ($Key gt $This->{MaxKey}) ? 0 : ((($This->{KeysCount} == 1) && ($This->{MaxKey} eq $Key)) ? 0 : 1); 447 } 448 } 449 450 return 1; 451 } 452 453 # Set min key... 454 # 455 sub _CompareAndSetMinKey { 456 my($This, $Key) = @_; 457 458 if (!defined $This->{MinKey}) { 459 $This->{MinKey} = $Key; 460 return $This; 461 } 462 463 if ($This->{KeyType} =~ /^Numeric$/i) { 464 if ($Key < $This->{MinKey}) { 465 $This->{MinKey} = $Key; 466 } 467 } 468 else { 469 if ($Key lt $This->{MinKey}) { 470 $This->{MinKey} = $Key; 471 } 472 } 473 474 return $This; 475 } 476 477 # Set max key... 478 # 479 sub _CompareAndSetMaxKey { 480 my($This, $Key) = @_; 481 482 if (!defined $This->{MaxKey}) { 483 $This->{MaxKey} = $Key; 484 return $This; 485 } 486 487 if ($This->{KeyType} =~ /^Numeric$/i) { 488 if ($Key > $This->{MaxKey}) { 489 $This->{MaxKey} = $Key; 490 } 491 } 492 else { 493 if ($Key gt $This->{MaxKey}) { 494 $This->{MaxKey} = $Key; 495 } 496 } 497 498 return $This; 499 } 500 501 # Delete a sepcified key along with all of its values added to the list... 502 # 503 sub _DeleteKey { 504 my($This, $Key) = @_; 505 my($NumOfValues); 506 507 if (!exists $This->{Keys}{$Key}) { 508 return undef; 509 } 510 511 # Delete all key values... 512 $NumOfValues = scalar @{$This->{Keys}{$Key}}; 513 @{$This->{Keys}{$Key}} = (); 514 $This->{CurrentSize} -= $NumOfValues; 515 516 # Delete key... 517 delete $This->{Keys}{$Key}; 518 $This->{KeysCount} -= 1; 519 520 # Set min and max keys... 521 $This->_FindAndSetMinAndMaxKeys(); 522 523 return $This; 524 } 525 526 # Delete a sepcified key along with its most recent value added to the list... 527 # 528 sub _DeleteKeyValuePair { 529 my($This, $Key) = @_; 530 531 if (!exists $This->{Keys}{$Key}) { 532 return undef; 533 } 534 535 # Delete value... 536 pop @{$This->{Keys}{$Key}}; 537 $This->{CurrentSize} -= 1; 538 539 # Delete key... 540 if (!@{$This->{Keys}{$Key}}) { 541 delete $This->{Keys}{$Key}; 542 $This->{KeysCount} -= 1; 543 } 544 545 # Set min and max keys... 546 $This->_FindAndSetMinAndMaxKeys(); 547 548 return $This; 549 } 550 551 # Set min and max key... 552 # 553 sub _FindAndSetMinAndMaxKeys { 554 my($This) = @_; 555 my(@SortedKeys); 556 557 @SortedKeys = ($This->{KeyType} =~ /^Numeric$/i) ? (sort { $a <=> $b } keys %{$This->{Keys}}) : (sort { $a cmp $b } keys %{$This->{Keys}}); 558 559 if (@SortedKeys) { 560 $This->{MinKey} = $SortedKeys[0]; 561 $This->{MaxKey} = $SortedKeys[$#SortedKeys]; 562 } 563 else { 564 $This->{MinKey} = undef; 565 $This->{MaxKey} = undef; 566 } 567 568 return $This; 569 } 570 571 # Return a string containing vector values... 572 sub StringifyPseudoHeap { 573 my($This) = @_; 574 my($PseudoHeapString, $Key, $Value, $KeyValuesString, @KeysAndValues); 575 576 $PseudoHeapString = "PseudoHeap: Type: " . $This->GetType() . "; KeyType: " . $This->GetKeyType() . "; MaxSize: $This->{MaxSize}; CurrentSize: $This->{CurrentSize}; MinKey: " . $This->GetMinKey() . "; MaxKey: " . $This->GetMaxKey() . "; NumOfUniqueKeys: $This->{KeysCount}"; 577 578 @KeysAndValues = (); 579 for $Key ($This->GetSortedKeys()) { 580 for $Value ($This->GetKeyValues($Key)) { 581 push @KeysAndValues, "$Key - $Value"; 582 } 583 } 584 if (@KeysAndValues) { 585 $KeyValuesString = TextUtil::JoinWords(\@KeysAndValues, "; ", 0); 586 } 587 else { 588 $KeyValuesString = "None"; 589 } 590 591 $PseudoHeapString .= "; Sorted Key - Value pairs: [$KeyValuesString]"; 592 593 return $PseudoHeapString; 594 } 595