Hi, just for the records: I've came up myself with an alternative solution to store an ordered list. The basic idea is to calculate order keys from a set of characters such that I can always insert items in front of the list and at the end of the list and also between any two items. I never need to change any existing ordering key. You only need the keys for the two neighbors (or just the first or last key in the list when inserting at the beginning or end). The key creation is fairly efficient to tasks like always inserting items at the end of the list or at the beginning of the list or for example always at the second position in the list. A random insertion is fine too. The efficiency in all those very different situations is based on some randomness in the new key creation processes, but this is the whole interesting thing here.
André -------- order_key.py ----- (the beef) ---------------- # -*- encoding: utf-8 -*- # The MIT License # # Copyright (c) 2009 André Wobst <wob...@users.sourceforge.net> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """This module generates strings to be used to define an order when sorted as strings. We call those strings 'keys'. All keys are composed of printable ascii chars. (The alphabet is actually defined in key_chars.) It is ensured, that you can always create a 'smaller' or 'larger' key than any key generated by the functions in this module. Additionally, you can always generate a key, which is in between two given keys.""" import random # Note that we use some randomization to get good performance not only when # generating middle_keys but also when generating before_keys and after_keys # frequently. The randomization is fairly "auto-adjusting" by depending on the # key length. key_chars = "".join(chr(i) for i in range(33,127)) middle_char = key_chars[len(key_chars)//2] key_char_index = dict((c, i) for i, c in enumerate(key_chars)) def init_key(): """Returns an initial order key to be used for the first item.""" return middle_char def before_key(key): """Returns an order key which is before the passed key.""" new_key = [] for i, c in enumerate(key): if c is not key_chars[0] and (i == len(key)-1 or not random.randint(0, len(key))): new_key.append(key_chars[key_char_index[c]-1]) if i == len(key)-1: new_key.append(middle_char) break else: new_key.append(c) else: raise RuntimeError("could not insert a key before '%s'" % key) new_key = "".join(new_key) assert new_key < key return new_key def after_key(key): """Returns an order key which is after the passed key.""" new_key = [] for c in key: if c is not key_chars[-1] and not random.randint(0, len(key)): new_key.append(key_chars[key_char_index[c]+1]) break else: new_key.append(c) else: new_key.append(middle_char) new_key = "".join(new_key) assert key < new_key return new_key def middle_key(key1, key2): """Returns an order key which is between the two passed keys. The two keys passed to this function must be ordered.""" assert key1 < key2 new_key = [] for i, (c1, c2) in enumerate(zip(key1, key2)): if c1 == c2: new_key.append(c1) elif key_char_index[c1]+1 == key_char_index[c2]: new_key.append(c1) new_key.extend(after_key(key1[i+1:])) # note that after_key doesn't fail for an empty key break else: new_key.append(key_chars[(key_char_index[c1] + key_char_index[c2])//2]) break else: if len(key1) > len(key2): new_key.extend(after_key(key1[len(key2):])) elif len(key2) > len(key1): new_key.extend(before_key(key2[len(key1):])) else: raise RuntimeError("identical keys?!") new_key = "".join(new_key) assert key1 < new_key < key2 return new_key if __name__ == "__main__": l = [init_key()] for i in range(10000): p = random.randint(0, len(l)) if p == 0: l.insert(0, before_key(l[0])) elif p == len(l): l.append(after_key(l[-1])) else: l.insert(p, middle_key(l[p-1], l[p])) assert l == sorted(l) -------- order_key.py ----- (example) ---------------- # -*- encoding: utf-8 -*- import random from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, Unicode, ForeignKey, UniqueConstraint from sqlalchemy.orm import sessionmaker, mapper, relation import order_key metadata = MetaData() list_table = Table("list", metadata, Column("id", Integer, primary_key=True), Column("name", Unicode)) item_table = Table("item", metadata, Column("id", Integer, primary_key=True), Column("name", Unicode), Column("list_order", String, nullable=False), Column("list_id", Integer, ForeignKey("list.id"), nullable=False), UniqueConstraint("list_id", "list_order")) class List(object): def __init__(self, name): self.name = name def __repr__(self): return "<List %s>" % self.name def insert_item(self, pos, item): if self.items: if p == 0: item.list_order = order_key.before_key(l.items [0].list_order) elif p == len(l.items): item.list_order = order_key.after_key(l.items [-1].list_order) else: item.list_order = order_key.middle_key(l.items [p-1].list_order, l.items[p].list_order) else: item.list_order = order_key.init_key() self.items.insert(pos, item) class Item(object): def __init__(self, name): self.name = name def __repr__(self): return "<Item %s part of %s>" % (self.name, self.list) mapper(List, list_table, properties={"items": relation(Item, backref="list", order_by= [item_table.c.list_order])}) mapper(Item, item_table) engine = create_engine("postgres:///list") metadata.bind = engine list_table.create() item_table.create() l = List(u"list") for i in range(1000): p = random.randint(0, len(l.items)) i = Item(u"%05i" % i) l.insert_item(p, i) Session = sessionmaker(engine) session = Session() session.add(l) session.commit() session.close() engine.dispose() --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---