Adaptagrams
util.h
1 /*
2  * vim: ts=4 sw=4 et tw=0 wm=0
3  *
4  * libdialect - A library for computing DiAlEcT layouts:
5  * D = Decompose/Distribute
6  * A = Arrange
7  * E = Expand/Emend
8  * T = Transform
9  *
10  * Copyright (C) 2018 Monash University
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2.1 of the License, or (at your option) any later version.
16  * See the file LICENSE.LGPL distributed with the library.
17  *
18  * This library is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21  *
22  * Author(s): Steve Kieffer <http://skieffer.info>
23 */
24 
25 #ifndef DIALECT_UTIL_H
26 #define DIALECT_UTIL_H
27 
28 #include <cmath>
29 #include <string>
30 #include <memory>
31 #include <cstdio>
32 #include <map>
33 #include <vector>
34 #include <utility>
35 #include <algorithm>
36 #include <functional>
37 #include <math.h>
38 
39 #include "libavoid/geomtypes.h"
40 
41 namespace dialect {
42 
47 inline bool logically_equal(double a, double b, double error_factor=1.0)
48 {
49  return a==b || std::abs(a-b) < std::abs(std::min(a,b))*std::numeric_limits<double>::epsilon()*error_factor;
50 }
51 
53 inline bool approx_equal(double a, double b, double tol=0.000001)
54 {
55  return a==b || std::abs(a-b) < tol;
56 }
57 
61 template<typename ... Args>
62 std::string string_format( const std::string& format, Args ... args )
63 {
64  size_t size = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
65  std::unique_ptr<char[]> buf( new char[ size ] );
66  std::snprintf( buf.get(), size, format.c_str(), args ... );
67  return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
68 }
69 
71 template<typename T>
72 struct Matrix2d {
73  int rows, cols;
74  std::vector<T> data;
75  Matrix2d() : rows(0), cols(0) {}
76  Matrix2d(int rows, int cols) : rows(rows), cols(cols), data(rows*cols)
77  { }
78 
79  T operator()(int i, int j) const
80  {
81  assert(i < rows);
82  assert(j < cols);
83  return data[i*cols+j];
84  }
85  T& operator()(int i, int j)
86  {
87  assert(i < rows);
88  assert(j < cols);
89  return data[i*cols+j];
90  }
91 
92  std::string toString() const {
93  std::string s = "";
94  s += "\n ";
95  char buffer [10];
96  for (int j=0; j<cols; j++) {
97  sprintf(buffer," %2d",j);
98  s += std::string(buffer);
99  }
100  for (int i=0; i<rows; i++) {
101  s += "\n";
102  sprintf(buffer,"%2d",i);
103  s += std::string(buffer);
104  for (int j=0; j<cols; j++) {
105  sprintf(buffer," %2d",data[i*cols+j]);
106  s += std::string(buffer);
107  }
108  }
109  return s;
110  }
111 
112 };
113 
128 template <typename T>
130 public:
131 
135  NearbyObjectFinder(double threshold) : m_thresh(threshold) {}
136 
142  void addObject(double x, double y, T obj) {
143  // Round coords to integers in order to choose a bucket.
144  int x0 = (int) std::round(x),
145  y0 = (int) std::round(y);
146  // Store the point and the object in the bucket.
147  m_objects[x0][y0].push_back({Avoid::Point(x, y), obj});
148  }
149 
157  T findObject(double x, double y) {
158  // Consider the range of all integer coords under which a nearby
159  // point could have been stored, according to the threshold.
160  int u0 = (int) std::round(x - m_thresh),
161  u1 = (int) std::round(x + m_thresh),
162  v0 = (int) std::round(y - m_thresh),
163  v1 = (int) std::round(y + m_thresh);
164  // Iterate over the range, looking for a match, and returning first one found, if any.
165  for (int u = u0; u <= u1; ++u) {
166  for (int v = v0; v <= v1; ++v) {
167  for (auto pair : m_objects[u][v]) {
168  Avoid::Point p = pair.first;
169  if (fabs(x - p.x) < m_thresh && fabs(y - p.y) < m_thresh) return pair.second;
170  }
171  }
172  }
173  // If we didn't find any match, then we return a value-initialised object.
174  return T();
175  }
176 
177 private:
178  double m_thresh;
179  // Object storage format:
180  // We will be given an object of type T to store, under a point (x, y).
181  // We will round the coordinates to integers x0, y0, and store the object
182  // under a "bucket" for these integer coords. Thus under [x0][y0] we have a vector
183  // of pairs, giving the original point (x, y), and the object of type T.
184  // So it looks like:
185  // (int, int) --> [ {(x,y), T}, {(x,y), T}, ... ]
186  std::map<int, std::map<int, std::vector<std::pair<Avoid::Point, T>>>> m_objects;
187 };
188 
199 template <typename T>
200 std::vector<std::vector<T>> partition(std::vector<T> items, std::function<double(T)> key, double tolerance=0) {
201  // Prepare the return value
202  std::vector<std::vector<T>> parts;
203  // If there are no items, there is nothing to do.
204  if (items.size() == 0) return parts;
205  // Else sort by the given key.
206  std::sort(items.begin(), items.end(),
207  [&key](const T &a, const T &b) -> bool {
208  return key(a) < key(b);
209  }
210  );
211  // Initialise first part with first item.
212  auto it = items.cbegin();
213  T firstItem = *it;
214  std::vector<T> part{firstItem};
215  // Initialise the average key value for the part.
216  double avg = key(firstItem);
217  // Let n record how many keys are in the average.
218  size_t n = 1;
219  // Consider the remaining items.
220  for (auto jt = ++it; jt != items.cend(); ++jt) {
221  T item = *jt;
222  double k = key(item);
223  double dk = k - avg;
224  // NB: Using <= (not strict <) in this test is necessary so that the case
225  // where tolerance == 0 works!
226  if (fabs(dk) <= tolerance) {
227  // Difference of new key with current avg is within
228  // tolerance. Add item to current part.
229  part.push_back(item);
230  // Update n and avg. Note that when tolerance is 0, then k == avg, so avg stays constant.
231  avg = (n*avg + k)/(n+1);
232  ++n;
233  } else {
234  // Difference is too much. Record the current part
235  // and begin a new one for this item.
236  parts.push_back(part);
237  part = {item};
238  avg = k;
239  n = 1;
240  }
241  }
242  // At this point there is always a nonempty part that has not
243  // yet been appended to the part list.
244  parts.push_back(part);
245  return parts;
246 }
247 
248 } // namespace dialect
249 
250 #endif // DIALECT_UTIL_H
double y
The y position.
Definition: geomtypes.h:109
std::vector< std::vector< T > > partition(std::vector< T > items, std::function< double(T)> key, double tolerance=0)
Partition a vector of items according to a key value.
Definition: util.h:200
libdialect: A library for computing human-like orthogonal network (DiAlEcT) layouts.
Definition: cola.h:44
void addObject(double x, double y, T obj)
Add a new object, and say what its x,y-coords are.
Definition: util.h:142
double x
The x position.
Definition: geomtypes.h:107
Dense 2d array, with integer indices.
Definition: util.h:72
std::string string_format(const std::string &format, Args ... args)
String formatting.
Definition: util.h:62
Definition: util.h:129
bool approx_equal(double a, double b, double tol=0.000001)
Tolerant equality test for doubles. Uses arbitrary tolerance.
Definition: util.h:53
Contains the interface for various geometry types and classes.
NearbyObjectFinder(double threshold)
Construct a NearbyObjectFinder that looks for objects within a given threshold.
Definition: util.h:135
The Point class defines a point in the plane.
Definition: geomtypes.h:52
T findObject(double x, double y)
Check to see if any object has been stored yet in the open neighbourhood centred at the given coordin...
Definition: util.h:157
bool logically_equal(double a, double b, double error_factor=1.0)
Tolerant equality test for doubles. Generates principled value for tolerance.
Definition: util.h:47