mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Cleaned up some code in HashMap and added a getReference method
This commit is contained in:
parent
ea34593ae4
commit
4c6b5f0a8b
3 changed files with 346 additions and 46 deletions
|
|
@ -130,9 +130,9 @@ public:
|
|||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
|
||||
for (int i = hashSlots.size(); --i >= 0;)
|
||||
for (auto i = hashSlots.size(); --i >= 0;)
|
||||
{
|
||||
HashEntry* h = hashSlots.getUnchecked(i);
|
||||
auto* h = hashSlots.getUnchecked(i);
|
||||
|
||||
while (h != nullptr)
|
||||
{
|
||||
|
|
@ -161,24 +161,44 @@ public:
|
|||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
|
||||
for (const HashEntry* entry = hashSlots.getUnchecked (generateHashFor (keyToLookFor)); entry != nullptr; entry = entry->nextEntry)
|
||||
if (entry->key == keyToLookFor)
|
||||
return entry->value;
|
||||
if (auto* entry = getEntry (getSlot (keyToLookFor), keyToLookFor))
|
||||
return entry->value;
|
||||
|
||||
return ValueType();
|
||||
}
|
||||
|
||||
/** Returns a reference to the value corresponding to a given key.
|
||||
If the map doesn't contain the key, a default instance of the value type is
|
||||
added to the map and a reference to this is returned.
|
||||
@param keyToLookFor the key of the item being requested
|
||||
*/
|
||||
inline ValueType& getReference (KeyTypeParameter keyToLookFor)
|
||||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
auto hashIndex = generateHashFor (keyToLookFor, getNumSlots());
|
||||
|
||||
auto* firstEntry = hashSlots.getUnchecked (hashIndex);
|
||||
|
||||
if (auto* entry = getEntry (firstEntry, keyToLookFor))
|
||||
return entry->value;
|
||||
|
||||
auto* entry = new HashEntry (keyToLookFor, ValueType(), firstEntry);
|
||||
hashSlots.set (hashIndex, entry);
|
||||
++totalNumItems;
|
||||
|
||||
if (totalNumItems > (getNumSlots() * 3) / 2)
|
||||
remapTable (getNumSlots() * 2);
|
||||
|
||||
return entry->value;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the map contains an item with the specied key. */
|
||||
bool contains (KeyTypeParameter keyToLookFor) const
|
||||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
|
||||
for (const HashEntry* entry = hashSlots.getUnchecked (generateHashFor (keyToLookFor)); entry != nullptr; entry = entry->nextEntry)
|
||||
if (entry->key == keyToLookFor)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return (getEntry (getSlot (keyToLookFor), keyToLookFor) != nullptr);
|
||||
}
|
||||
|
||||
/** Returns true if the hash contains at least one occurrence of a given value. */
|
||||
|
|
@ -186,8 +206,8 @@ public:
|
|||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
|
||||
for (int i = getNumSlots(); --i >= 0;)
|
||||
for (const HashEntry* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry)
|
||||
for (auto i = getNumSlots(); --i >= 0;)
|
||||
for (auto* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry)
|
||||
if (entry->value == valueToLookFor)
|
||||
return true;
|
||||
|
||||
|
|
@ -199,35 +219,14 @@ public:
|
|||
If there's already an item with the given key, this will replace its value. Otherwise, a new item
|
||||
will be added to the map.
|
||||
*/
|
||||
void set (KeyTypeParameter newKey, ValueTypeParameter newValue)
|
||||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
const int hashIndex = generateHashFor (newKey);
|
||||
|
||||
HashEntry* const firstEntry = hashSlots.getUnchecked (hashIndex);
|
||||
|
||||
for (HashEntry* entry = firstEntry; entry != nullptr; entry = entry->nextEntry)
|
||||
{
|
||||
if (entry->key == newKey)
|
||||
{
|
||||
entry->value = newValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
hashSlots.set (hashIndex, new HashEntry (newKey, newValue, firstEntry));
|
||||
++totalNumItems;
|
||||
|
||||
if (totalNumItems > (getNumSlots() * 3) / 2)
|
||||
remapTable (getNumSlots() * 2);
|
||||
}
|
||||
void set (KeyTypeParameter newKey, ValueTypeParameter newValue) { getReference (newKey) = newValue; }
|
||||
|
||||
/** Removes an item with the given key. */
|
||||
void remove (KeyTypeParameter keyToRemove)
|
||||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
const int hashIndex = generateHashFor (keyToRemove);
|
||||
HashEntry* entry = hashSlots.getUnchecked (hashIndex);
|
||||
auto hashIndex = generateHashFor (keyToRemove, getNumSlots());
|
||||
auto* entry = hashSlots.getUnchecked (hashIndex);
|
||||
HashEntry* previous = nullptr;
|
||||
|
||||
while (entry != nullptr)
|
||||
|
|
@ -258,9 +257,9 @@ public:
|
|||
{
|
||||
const ScopedLockType sl (getLock());
|
||||
|
||||
for (int i = getNumSlots(); --i >= 0;)
|
||||
for (auto i = getNumSlots(); --i >= 0;)
|
||||
{
|
||||
HashEntry* entry = hashSlots.getUnchecked(i);
|
||||
auto* entry = hashSlots.getUnchecked(i);
|
||||
HashEntry* previous = nullptr;
|
||||
|
||||
while (entry != nullptr)
|
||||
|
|
@ -293,13 +292,27 @@ public:
|
|||
*/
|
||||
void remapTable (int newNumberOfSlots)
|
||||
{
|
||||
HashMap newTable (newNumberOfSlots);
|
||||
const ScopedLockType sl (getLock());
|
||||
|
||||
for (int i = getNumSlots(); --i >= 0;)
|
||||
for (const HashEntry* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry)
|
||||
newTable.set (entry->key, entry->value);
|
||||
Array<HashEntry*> newSlots;
|
||||
newSlots.insertMultiple (0, nullptr, newNumberOfSlots);
|
||||
|
||||
swapWith (newTable);
|
||||
for (auto i = getNumSlots(); --i >= 0;)
|
||||
{
|
||||
HashEntry* nextEntry = nullptr;
|
||||
|
||||
for (auto* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = nextEntry)
|
||||
{
|
||||
auto hashIndex = generateHashFor (entry->key, newNumberOfSlots);
|
||||
|
||||
nextEntry = entry->nextEntry;
|
||||
entry->nextEntry = newSlots.getUnchecked (hashIndex);
|
||||
|
||||
newSlots.set (hashIndex, entry);
|
||||
}
|
||||
}
|
||||
|
||||
hashSlots.swapWith (newSlots);
|
||||
}
|
||||
|
||||
/** Returns the number of slots which are available for hashing.
|
||||
|
|
@ -459,12 +472,23 @@ private:
|
|||
int totalNumItems;
|
||||
TypeOfCriticalSectionToUse lock;
|
||||
|
||||
int generateHashFor (KeyTypeParameter key) const
|
||||
int generateHashFor (KeyTypeParameter key, int numSlots) const
|
||||
{
|
||||
const int hash = hashFunctionToUse.generateHash (key, getNumSlots());
|
||||
jassert (isPositiveAndBelow (hash, getNumSlots())); // your hash function is generating out-of-range numbers!
|
||||
const int hash = hashFunctionToUse.generateHash (key, numSlots);
|
||||
jassert (isPositiveAndBelow (hash, numSlots)); // your hash function is generating out-of-range numbers!
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline HashEntry* getEntry (HashEntry* firstEntry, KeyType keyToLookFor) noexcept
|
||||
{
|
||||
for (auto* entry = firstEntry; entry != nullptr; entry = entry->nextEntry)
|
||||
if (entry->key == keyToLookFor)
|
||||
return entry;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline HashEntry* getSlot (KeyType key) const noexcept { return hashSlots.getUnchecked (generateHashFor (key, getNumSlots())); }
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashMap)
|
||||
};
|
||||
|
|
|
|||
271
modules/juce_core/containers/juce_HashMap_test.cpp
Normal file
271
modules/juce_core/containers/juce_HashMap_test.cpp
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
struct HashMapTest : public UnitTest
|
||||
{
|
||||
HashMapTest() : UnitTest ("HashMap") {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
doTest<AddElementsTest> ("AddElementsTest");
|
||||
doTest<AccessTest> ("AccessTest");
|
||||
doTest<RemoveTest> ("RemoveTest");
|
||||
doTest<PersistantMemoryLocationOfValues> ("PersistantMemoryLocationOfValues");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct AddElementsTest
|
||||
{
|
||||
template <typename KeyType>
|
||||
static void run (UnitTest& u)
|
||||
{
|
||||
AssociativeMap<KeyType, int> groundTruth;
|
||||
HashMap<KeyType, int> hashMap;
|
||||
|
||||
RandomKeys<KeyType> keyOracle (300, 3827829);
|
||||
Random valueOracle (48735);
|
||||
|
||||
int totalValues = 0;
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
auto key = keyOracle.next();
|
||||
auto value = valueOracle.nextInt();
|
||||
|
||||
bool contains = (groundTruth.find (key) != nullptr);
|
||||
u.expectEquals (contains, hashMap.contains (key));
|
||||
|
||||
groundTruth.add (key, value);
|
||||
hashMap.set (key, value);
|
||||
|
||||
if (! contains) totalValues++;
|
||||
|
||||
u.expectEquals (hashMap.size(), totalValues);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AccessTest
|
||||
{
|
||||
template <typename KeyType>
|
||||
static void run (UnitTest& u)
|
||||
{
|
||||
AssociativeMap<KeyType, int> groundTruth;
|
||||
HashMap<KeyType, int> hashMap;
|
||||
|
||||
fillWithRandomValues (hashMap, groundTruth);
|
||||
|
||||
for (auto pair : groundTruth.pairs)
|
||||
u.expectEquals (hashMap[pair.key], pair.value);
|
||||
}
|
||||
};
|
||||
|
||||
struct RemoveTest
|
||||
{
|
||||
template <typename KeyType>
|
||||
static void run (UnitTest& u)
|
||||
{
|
||||
AssociativeMap<KeyType, int> groundTruth;
|
||||
HashMap<KeyType, int> hashMap;
|
||||
|
||||
fillWithRandomValues (hashMap, groundTruth);
|
||||
auto n = groundTruth.size();
|
||||
|
||||
Random r (3827387);
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
auto idx = r.nextInt (n-- - 1);
|
||||
auto key = groundTruth.pairs.getReference (idx).key;
|
||||
|
||||
groundTruth.pairs.remove (idx);
|
||||
hashMap.remove (key);
|
||||
|
||||
u.expect (! hashMap.contains (key));
|
||||
|
||||
for (auto pair : groundTruth.pairs)
|
||||
u.expectEquals (hashMap[pair.key], pair.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ensure that the addresses of object references don't change
|
||||
struct PersistantMemoryLocationOfValues
|
||||
{
|
||||
struct AddressAndValue { int value; const int* valueAddress; };
|
||||
|
||||
template <typename KeyType>
|
||||
static void run (UnitTest& u)
|
||||
{
|
||||
AssociativeMap<KeyType, AddressAndValue> groundTruth;
|
||||
HashMap<KeyType, int> hashMap;
|
||||
|
||||
RandomKeys<KeyType> keyOracle (300, 3827829);
|
||||
Random valueOracle (48735);
|
||||
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
auto key = keyOracle.next();
|
||||
auto value = valueOracle.nextInt();
|
||||
|
||||
hashMap.set (key, value);
|
||||
|
||||
if (auto* existing = groundTruth.find (key))
|
||||
{
|
||||
// don't change the address: only the value
|
||||
existing->value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
groundTruth.add (key, { value, &hashMap.getReference (key) });
|
||||
}
|
||||
|
||||
for (auto pair : groundTruth.pairs)
|
||||
{
|
||||
const auto& hashMapValue = hashMap.getReference (pair.key);
|
||||
|
||||
u.expectEquals (hashMapValue, pair.value.value);
|
||||
u.expect (&hashMapValue == pair.value.valueAddress);
|
||||
}
|
||||
}
|
||||
|
||||
auto n = groundTruth.size();
|
||||
Random r (3827387);
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
auto idx = r.nextInt (n-- - 1);
|
||||
auto key = groundTruth.pairs.getReference (idx).key;
|
||||
|
||||
groundTruth.pairs.remove (idx);
|
||||
hashMap.remove (key);
|
||||
|
||||
for (auto pair : groundTruth.pairs)
|
||||
{
|
||||
const auto& hashMapValue = hashMap.getReference (pair.key);
|
||||
|
||||
u.expectEquals (hashMapValue, pair.value.value);
|
||||
u.expect (&hashMapValue == pair.value.valueAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <class Test>
|
||||
void doTest (const String& testName)
|
||||
{
|
||||
beginTest (testName);
|
||||
|
||||
Test::template run<int> (*this);
|
||||
Test::template run<void*> (*this);
|
||||
Test::template run<String> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename KeyType, typename ValueType>
|
||||
struct AssociativeMap
|
||||
{
|
||||
struct KeyValuePair { KeyType key; ValueType value; };
|
||||
|
||||
ValueType* find (KeyType key)
|
||||
{
|
||||
auto n = pairs.size();
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
auto& pair = pairs.getReference (i);
|
||||
|
||||
if (pair.key == key)
|
||||
return &pair.value;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void add (KeyType key, ValueType value)
|
||||
{
|
||||
if (ValueType* v = find (key))
|
||||
*v = value;
|
||||
else
|
||||
pairs.add ({key, value});
|
||||
}
|
||||
|
||||
int size() const { return pairs.size(); }
|
||||
|
||||
Array<KeyValuePair> pairs;
|
||||
};
|
||||
|
||||
template <typename KeyType, typename ValueType>
|
||||
static void fillWithRandomValues (HashMap<KeyType, int>& hashMap, AssociativeMap<KeyType, ValueType>& groundTruth)
|
||||
{
|
||||
RandomKeys<KeyType> keyOracle (300, 3827829);
|
||||
Random valueOracle (48735);
|
||||
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
auto key = keyOracle.next();
|
||||
auto value = valueOracle.nextInt();
|
||||
|
||||
groundTruth.add (key, value);
|
||||
hashMap.set (key, value);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename KeyType>
|
||||
class RandomKeys
|
||||
{
|
||||
public:
|
||||
RandomKeys (int maxUniqueKeys, int seed) : r (seed)
|
||||
{
|
||||
for (int i = 0; i < maxUniqueKeys; ++i)
|
||||
keys.add (generateRandomKey (r));
|
||||
}
|
||||
|
||||
const KeyType& next()
|
||||
{
|
||||
int i = r.nextInt (keys.size() - 1);
|
||||
return keys.getReference (i);
|
||||
}
|
||||
private:
|
||||
static KeyType generateRandomKey (Random&);
|
||||
|
||||
Random r;
|
||||
Array<KeyType> keys;
|
||||
};
|
||||
};
|
||||
|
||||
template <> int HashMapTest::RandomKeys<int> ::generateRandomKey (Random& rnd) { return rnd.nextInt(); }
|
||||
template <> void* HashMapTest::RandomKeys<void*>::generateRandomKey (Random& rnd) { return reinterpret_cast<void*> (rnd.nextInt64()); }
|
||||
|
||||
template <> String HashMapTest::RandomKeys<String>::generateRandomKey (Random& rnd)
|
||||
{
|
||||
String str;
|
||||
|
||||
int len = rnd.nextInt (8)+1;
|
||||
for (int i = 0; i < len; ++i)
|
||||
str += static_cast<char> (rnd.nextInt (95) + 32);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static HashMapTest hashMapTest;
|
||||
|
|
@ -232,6 +232,11 @@ namespace juce
|
|||
#include "network/juce_URL.cpp"
|
||||
#include "network/juce_WebInputStream.cpp"
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
#include "containers/juce_HashMap_test.cpp"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/*
|
||||
As the very long class names here try to explain, the purpose of this code is to cause
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue