Statistics
| Branch: | Tag: | Revision:

root / lib / utils / algo.py @ 92389be9

History | View | Annotate | Download (7.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Utility functions with algorithms.
22

23
"""
24

    
25
import re
26
import time
27
import itertools
28

    
29
from ganeti import compat
30
from ganeti.utils import text
31

    
32

    
33
_SORTER_GROUPS = 8
34
_SORTER_RE = re.compile("^%s(.*)$" % (_SORTER_GROUPS * r"(\D+|\d+)?"))
35

    
36

    
37
def UniqueSequence(seq):
38
  """Returns a list with unique elements.
39

40
  Element order is preserved.
41

42
  @type seq: sequence
43
  @param seq: the sequence with the source elements
44
  @rtype: list
45
  @return: list of unique elements from seq
46

47
  """
48
  seen = set()
49
  return [i for i in seq if i not in seen and not seen.add(i)]
50

    
51

    
52
def JoinDisjointDicts(dict_a, dict_b):
53
  """Joins dictionaries with no conflicting keys.
54

55
  Enforces the constraint that the two key sets must be disjoint, and then
56
  merges the two dictionaries in a new dictionary that is returned to the
57
  caller.
58

59
  @type dict_a: dict
60
  @param dict_a: the first dictionary
61
  @type dict_b: dict
62
  @param dict_b: the second dictionary
63
  @rtype: dict
64
  @return: a new dictionary containing all the key/value pairs contained in the
65
  two dictionaries.
66

67
  """
68
  assert not (set(dict_a) & set(dict_b)), ("Duplicate keys found while joining"
69
                                           " %s and %s" % (dict_a, dict_b))
70
  result = dict_a.copy()
71
  result.update(dict_b)
72
  return result
73

    
74

    
75
def FindDuplicates(seq):
76
  """Identifies duplicates in a list.
77

78
  Does not preserve element order.
79

80
  @type seq: sequence
81
  @param seq: Sequence with source elements
82
  @rtype: list
83
  @return: List of duplicate elements from seq
84

85
  """
86
  dup = set()
87
  seen = set()
88

    
89
  for item in seq:
90
    if item in seen:
91
      dup.add(item)
92
    else:
93
      seen.add(item)
94

    
95
  return list(dup)
96

    
97

    
98
#pylint: disable=W0142 (use of *-magic in argument list)
99
def GetRepeatedKeys(*dicts):
100
  """Return the set of keys defined multiple times in the given dicts.
101

102
  >>> GetRepeatedKeys({"foo": 1, "bar": 2},
103
  ...                 {"foo": 5, "baz": 7}
104
  ...                )
105
  set("foo")
106

107
  @type dicts: dict
108
  @param dicts: The dictionaries to check for duplicate keys.
109
  @rtype: set
110
  @return: Keys used more than once across all dicts
111

112
  """
113
  if len(dicts) < 2:
114
    return set()
115

    
116
  keys = []
117
  for dictionary in dicts:
118
    keys.extend(dictionary)
119

    
120
  return set(FindDuplicates(keys))
121

    
122

    
123
def _NiceSortTryInt(val):
124
  """Attempts to convert a string to an integer.
125

126
  """
127
  if val and val.isdigit():
128
    return int(val)
129
  else:
130
    return val
131

    
132

    
133
def NiceSortKey(value):
134
  """Extract key for sorting.
135

136
  """
137
  return [_NiceSortTryInt(grp)
138
          for grp in _SORTER_RE.match(value).groups()]
139

    
140

    
141
def NiceSort(values, key=None):
142
  """Sort a list of strings based on digit and non-digit groupings.
143

144
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
145
  will sort the list in the logical order C{['a1', 'a2', 'a10',
146
  'a11']}.
147

148
  The sort algorithm breaks each name in groups of either only-digits
149
  or no-digits. Only the first eight such groups are considered, and
150
  after that we just use what's left of the string.
151

152
  @type values: list
153
  @param values: the names to be sorted
154
  @type key: callable or None
155
  @param key: function of one argument to extract a comparison key from each
156
    list element, must return string
157
  @rtype: list
158
  @return: a copy of the name list sorted with our algorithm
159

160
  """
161
  if key is None:
162
    keyfunc = NiceSortKey
163
  else:
164
    keyfunc = lambda value: NiceSortKey(key(value))
165

    
166
  return sorted(values, key=keyfunc)
167

    
168

    
169
def InvertDict(dict_in):
170
  """Inverts the key/value mapping of a dict.
171

172
  @param dict_in: The dict to invert
173
  @return: the inverted dict
174

175
  """
176
  return dict(zip(dict_in.values(), dict_in.keys()))
177

    
178

    
179
def InsertAtPos(src, pos, other):
180
  """Inserts C{other} at given C{pos} into C{src}.
181

182
  @note: This function does not modify C{src} in place but returns a new copy
183

184
  @type src: list
185
  @param src: The source list in which we want insert elements
186
  @type pos: int
187
  @param pos: The position where we want to start insert C{other}
188
  @type other: list
189
  @param other: The other list to insert into C{src}
190
  @return: A copy of C{src} with C{other} inserted at C{pos}
191

192
  """
193
  new = src[:pos]
194
  new.extend(other)
195
  new.extend(src[pos:])
196

    
197
  return new
198

    
199

    
200
def SequenceToDict(seq, key=compat.fst):
201
  """Converts a sequence to a dictionary with duplicate detection.
202

203
  @type seq: sequen
204
  @param seq: Input sequence
205
  @type key: callable
206
  @param key: Function for retrieving dictionary key from sequence element
207
  @rtype: dict
208

209
  """
210
  keys = map(key, seq)
211

    
212
  duplicates = FindDuplicates(keys)
213
  if duplicates:
214
    raise ValueError("Duplicate keys found: %s" % text.CommaJoin(duplicates))
215

    
216
  assert len(keys) == len(seq)
217

    
218
  return dict(zip(keys, seq))
219

    
220

    
221
def _MakeFlatToDict(data):
222
  """Helper function for C{FlatToDict}.
223

224
  This function is recursively called
225

226
  @param data: The input data as described in C{FlatToDict}, already splitted
227
  @returns: The so far converted dict
228

229
  """
230
  if not compat.fst(compat.fst(data)):
231
    assert len(data) == 1, \
232
      "not bottom most element, found %d elements, expected 1" % len(data)
233
    return compat.snd(compat.fst(data))
234

    
235
  keyfn = lambda e: compat.fst(e).pop(0)
236
  return dict([(k, _MakeFlatToDict(list(g)))
237
               for (k, g) in itertools.groupby(sorted(data), keyfn)])
238

    
239

    
240
def FlatToDict(data, field_sep="/"):
241
  """Converts a flat structure to a fully fledged dict.
242

243
  It accept a list of tuples in the form::
244

245
    [
246
      ("foo/bar", {"key1": "data1", "key2": "data2"}),
247
      ("foo/baz", {"key3" :"data3" }),
248
    ]
249

250
  where the first element is the key separated by C{field_sep}.
251

252
  This would then return::
253

254
    {
255
      "foo": {
256
        "bar": {"key1": "data1", "key2": "data2"},
257
        "baz": {"key3" :"data3" },
258
        },
259
    }
260

261
  @type data: list of tuple
262
  @param data: Input list to convert
263
  @type field_sep: str
264
  @param field_sep: The separator for the first field of the tuple
265
  @returns: A dict based on the input list
266

267
  """
268
  return _MakeFlatToDict([(keys.split(field_sep), value)
269
                          for (keys, value) in data])
270

    
271

    
272
class RunningTimeout(object):
273
  """Class to calculate remaining timeout when doing several operations.
274

275
  """
276
  __slots__ = [
277
    "_allow_negative",
278
    "_start_time",
279
    "_time_fn",
280
    "_timeout",
281
    ]
282

    
283
  def __init__(self, timeout, allow_negative, _time_fn=time.time):
284
    """Initializes this class.
285

286
    @type timeout: float
287
    @param timeout: Timeout duration
288
    @type allow_negative: bool
289
    @param allow_negative: Whether to return values below zero
290
    @param _time_fn: Time function for unittests
291

292
    """
293
    object.__init__(self)
294

    
295
    if timeout is not None and timeout < 0.0:
296
      raise ValueError("Timeout must not be negative")
297

    
298
    self._timeout = timeout
299
    self._allow_negative = allow_negative
300
    self._time_fn = _time_fn
301

    
302
    self._start_time = None
303

    
304
  def Remaining(self):
305
    """Returns the remaining timeout.
306

307
    """
308
    if self._timeout is None:
309
      return None
310

    
311
    # Get start time on first calculation
312
    if self._start_time is None:
313
      self._start_time = self._time_fn()
314

    
315
    # Calculate remaining time
316
    remaining_timeout = self._start_time + self._timeout - self._time_fn()
317

    
318
    if not self._allow_negative:
319
      # Ensure timeout is always >= 0
320
      return max(0.0, remaining_timeout)
321

    
322
    return remaining_timeout