From c43b70b783f54d23213157722e42fe45af88fdec Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 29 Mar 2024 21:47:37 +0100 Subject: [PATCH] Shape#hash and Shape#< allow using Shape objects as hash keys --- Changelog | 1 + src/db/db/dbShape.cc | 19 +++++++++++ src/db/db/dbShape.h | 20 +++++++++++ src/db/db/gsiDeclDbShape.cc | 15 +++++++++ src/pya/unit_tests/pyaTests.cc | 1 + testdata/python/dbShapesTest.py | 59 +++++++++++++++++++++++++++++++++ testdata/ruby/dbShapesTest.rb | 29 ++++++++++++++++ 7 files changed, 144 insertions(+) create mode 100644 testdata/python/dbShapesTest.py diff --git a/Changelog b/Changelog index 323f705d4..57b377c30 100644 --- a/Changelog +++ b/Changelog @@ -43,6 +43,7 @@ using them to select edges. * Enhancement: New RBA/pya Features - Main window title: MainWindow#title (property) + - MainWindow#synchronous (getter added) - LayoutView#is_dirty? - Triangulation: Region#delaunay - Quality rasterizer: Region#rasterize diff --git a/src/db/db/dbShape.cc b/src/db/db/dbShape.cc index e5c1d7820..648221066 100644 --- a/src/db/db/dbShape.cc +++ b/src/db/db/dbShape.cc @@ -24,6 +24,7 @@ #include "dbShape.h" #include "dbBoxConvert.h" #include "dbPolygonTools.h" +#include "dbHash.h" #include "tlCpp.h" namespace db @@ -862,6 +863,24 @@ Shape::box_type Shape::rectangle () const return box_type (); } +size_t +Shape::hash_value () const +{ + size_t h = size_t (m_type); + h = std::hcombine (h, std::hfunc (m_trans)); + + if (m_stable) { + // Use the bytes of the iterator binary pattern (see operator<) + for (unsigned int i = 0; i < sizeof (tl::reuse_vector::const_iterator); ++i) { + h = std::hcombine (h, size_t (m_generic.iter[i])); + } + } else { + h = std::hcombine (h, size_t (m_generic.any)); + } + + return h; +} + std::string Shape::to_string () const { diff --git a/src/db/db/dbShape.h b/src/db/db/dbShape.h index dd003b47e..469351376 100644 --- a/src/db/db/dbShape.h +++ b/src/db/db/dbShape.h @@ -2769,6 +2769,11 @@ public: return m_trans < d.m_trans; } + /** + * @brief Hash value + */ + size_t hash_value () const; + /** * @brief Convert to a string */ @@ -2837,6 +2842,21 @@ public: }; } // namespace db + +namespace std +{ + + // provide a template specialization for std::hash + template <> + struct hash + { + size_t operator() (const db::Shape &s) const + { + return s.hash_value (); + } + }; + +} // namespace std #endif diff --git a/src/db/db/gsiDeclDbShape.cc b/src/db/db/gsiDeclDbShape.cc index 68f6ff4fb..6d03cf26c 100644 --- a/src/db/db/gsiDeclDbShape.cc +++ b/src/db/db/gsiDeclDbShape.cc @@ -2131,6 +2131,21 @@ Class decl_Shape ("db", "Shape", "Equality of shapes is not specified by the identity of the objects but by the\n" "identity of the pointers - both shapes must refer to the same object.\n" ) + + gsi::method ("<", &db::Shape::operator<, gsi::arg ("other"), + "@brief Less operator\n" + "\n" + "The less operator implementation is based on pointers and not strictly reproducible." + "However, it is good enough so Shape objects can serve as keys in hashes (see also \\hash).\n" + "\n" + "This method has been introduced in version 0.29." + ) + + gsi::method ("hash", &db::Shape::hash_value, + "@brief Hash function\n" + "\n" + "The hash function enables Shape objects as keys in hashes.\n" + "\n" + "This method has been introduced in version 0.29." + ) + gsi::method ("to_s", &db::Shape::to_string, "@brief Create a string showing the contents of the reference\n" "\n" diff --git a/src/pya/unit_tests/pyaTests.cc b/src/pya/unit_tests/pyaTests.cc index d14c393cd..7fb7d0366 100644 --- a/src/pya/unit_tests/pyaTests.cc +++ b/src/pya/unit_tests/pyaTests.cc @@ -99,6 +99,7 @@ void run_pythontest (tl::TestBase *_this, const std::string &fn) PYTHONTEST (kwargs, "kwargs.py") PYTHONTEST (dbLayoutTest, "dbLayoutTest.py") PYTHONTEST (dbRegionTest, "dbRegionTest.py") +PYTHONTEST (dbShapesTest, "dbShapesTest.py") PYTHONTEST (dbReaders, "dbReaders.py") PYTHONTEST (dbPCellsTest, "dbPCells.py") PYTHONTEST (dbPolygonTest, "dbPolygonTest.py") diff --git a/testdata/python/dbShapesTest.py b/testdata/python/dbShapesTest.py new file mode 100644 index 000000000..f5a2482e8 --- /dev/null +++ b/testdata/python/dbShapesTest.py @@ -0,0 +1,59 @@ +# KLayout Layout Viewer +# Copyright (C) 2006-2024 Matthias Koefferlein +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import pya +import unittest +import sys +import os + +class DBShapesTest(unittest.TestCase): + + # Shape objects as hashes + def test_12(self): + + s = pya.Shapes() + s1 = s.insert(pya.Box(1, 2, 3, 4)) + s2 = s.insert(pya.Polygon(pya.Box(1, 2, 3, 4))) + s3 = s.insert(pya.SimplePolygon(pya.Box(1, 2, 3, 4))) + + self.assertEqual(s1.hash != s2.hash, True) # let's hope so ... + self.assertEqual(s1.hash != s3.hash, True) + self.assertEqual(s2.hash != s3.hash, True) + + self.assertEqual(s1 < s2 or s2 < s1, True) + self.assertEqual(s1 < s3 or s3 < s1, True) + self.assertEqual(s2 < s3 or s3 < s2, True) + + h = {} + h[s1] = 1 + h[s2] = 2 + h[s3] = 3 + + self.assertEqual(len(h), 3) + + self.assertEqual(h[s1], 1) + self.assertEqual(h[s2], 2) + self.assertEqual(h[s3], 3) + +# run unit tests +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(DBShapesTest) + + if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful(): + sys.exit(1) + diff --git a/testdata/ruby/dbShapesTest.rb b/testdata/ruby/dbShapesTest.rb index bca7672cf..61b8c9243 100644 --- a/testdata/ruby/dbShapesTest.rb +++ b/testdata/ruby/dbShapesTest.rb @@ -1627,6 +1627,35 @@ class DBShapes_TestClass < TestBase end + # Shape objects as hashes + def test_12 + + s = RBA::Shapes::new + s1 = s.insert(RBA::Box::new(1, 2, 3, 4)) + s2 = s.insert(RBA::Polygon::new(RBA::Box::new(1, 2, 3, 4))) + s3 = s.insert(RBA::SimplePolygon::new(RBA::Box::new(1, 2, 3, 4))) + + assert_equal(s1.hash != s2.hash, true) # let's hope so ... + assert_equal(s1.hash != s3.hash, true) + assert_equal(s2.hash != s3.hash, true) + + assert_equal(s1 < s2 || s2 < s1, true) + assert_equal(s1 < s3 || s3 < s1, true) + assert_equal(s2 < s3 || s3 < s2, true) + + h = {} + h[s1] = 1 + h[s2] = 2 + h[s3] = 3 + + assert_equal(h.size, 3) + + assert_equal(h[s1], 1) + assert_equal(h[s2], 2) + assert_equal(h[s3], 3) + + end + end load("test_epilogue.rb")