私はついにこれをいじる時間ができました。少し夢中になりました。長いですが、とりあえず貼っておきます。、、、、およびメソッドと、カーソルの抽象化を破る低レベルの操作を可能にするいくつかのプライベート メソッドを追加set_item
しました。また、範囲外または非トップレベル オブジェクトを指す for インデックス タプルをスローするメソッドも追加しました。insert
delete
find
find_left
move_cursor
IndexError
基本的に、パブリック関数のみを使用する限り、カーソルは常にトップレベルのオブジェクトを指し、挿入と削除はすべてトップレベルで行われることが (保証されるべき) です。__getitem__
ここから、__setitem__
、__delitem__
、 などを安全に実装できるはず__getslice__
です__setslice__
。
ただし、いくつかのしわがあります。カーソルが常にトップレベルのオブジェクトを指すという制限により、ネストされたリストをフラット リストであるかのように反復処理することが非常に簡単になります。insert
ただし、カーソルが下位レベルのオブジェクトを指すことができないことも意味するため、単独では使用できない種類の挿入があります。たとえば、次の 3 つのリストがあるとします。
>>> l1 = [1, 2, 3, 4]
>>> l2 = [5, 6, 7, 8]
>>> l3 = [l1, l2]
>>> l3
[[1, 2, 3, 4], [5, 6, 7, 8]]
このネストされた構造を NLI に入れ、 に移動して5
、挿入を試みます。
>>> nli = NestedListIter(l3)
>>> nli.find(5)
>>> nli.insert(9)
>>> nli.nested_list
[[1, 2, 3, 4], [9, 5, 6, 7, 8]]
ご覧のとおり、 に何かを挿入することはできl2
ますが、 に何かを簡単に挿入することはできませんl3
。実際、今すぐそうするには、private 関数を使用する必要があります。これは、カーソルの抽象化を不愉快な方法で壊します。
>>> nli._insert_at(nli.stack[:-1], 10)
>>> nli.nested_list
[[1, 2, 3, 4], 10, [9, 5, 6, 7, 8]]
>>> nli.get_item()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "nestedlistiterator.py", line 130, in get_item
return self._get_item_at(self.stack)
File "nestedlistiterator.py", line 39, in _get_item_at
item = item[i]
TypeError: 'int' object is unsubscriptable
安全なパブリック メソッドを実装する方法は確かに存在しますがinsert_between_branches
、それらの方法には、今私が気にかけている以上に複雑さが伴います。
の後に値を挿入しようとすると、別の問題が発生します4
。l2
これまで見てきたように、 beforeに値を挿入できます5
が、カーソルを4
andに移動すると、 inside のinsert
後に何かを挿入できないことがすぐにわかります。4
l1
>>> nli.go_to_head()
>>> nli.find(4)
>>> nli.insert(11)
>>> nli.nested_list
[[1, 2, 3, 11, 4], 10, [9, 5, 6, 7, 8]]
フラット アクセスの観点からは、4 の後に挿入することと 5 の前に挿入することは同じことですが、ネストされたリストの観点からは異なります。insert
は事実上 であるため、left_insert
この問題はメソッドで部分的に修正できますright_insert
(その結果、l1 の先頭に挿入できなくなります)。
これらの問題は、カーソルが下位レベルのオブジェクトを指すようにすることで、より一般的に対処できる可能性がありますが、フラット アクセスがより複雑になります。要するに、これらの問題を修正しようとすると、インターフェイスのフラット側またはネストされた側のいずれかで複雑さが増します。
(これが、私がまだ単純な方法を好む理由enumerate_nested
です! すべてのノード (最上位ノードだけでなく) に値を持つ適切なツリー構造も、より単純で優れている可能性があります。それでも、これはコーディングするのが楽しかったです。)
import collections
class NestedListIter(object):
'''A mutable container that enables flat traversal of a nested tree of
lists. nested_list should contain only a list-like mutable sequence.
To preserve a clear demarcation between 'leaves' and 'branches', empty
sequences are not allowed as toplevel objects.'''
def __init__(self, nested_list):
if not nested_list:
raise ValueError, 'nested_list must be a non-empty sequence'
self.nested_list = nested_list # at some point, vet this to make sure
self.go_to_head() # it contains no empty sequences
def _is_sequence(self, item=None):
'''Private method to test whether an item is a non-string sequence.
If item is None, test current item.'''
if item is None:
item = self._get_item_at(self.stack)
return isinstance(item, collections.Sequence) and not isinstance(item, basestring)
def _is_in_range(self, index_tuple=None):
'''Private method to test whether an index is in range.
If index is None, test current index.'''
if index_tuple is None:
index_tuple = self.stack
if any(x < 0 for x in index_tuple):
return False
try:
self._get_item_at(index_tuple)
except IndexError:
return False
else:
return True
def _get_item_at(self, index_tuple):
'''Private method to get item at an arbitrary index, with no bounds checking.'''
item = self.nested_list
for i in index_tuple:
item = item[i]
return item
def _set_item_at(self, index_tuple, value):
'''Private method to set item at an arbitrary index, with no bounds checking.
Throws a ValueError if value is an empty non-string sequence.'''
if self._is_sequence(value) and not value:
raise ValueError, "Cannot set an empty list!"
containing_list = self._get_item_at(index_tuple[:-1])
containing_list[index_tuple[-1]] = value
def _insert_at(self, index_tuple, value):
'''Private method to insert item at an arbitrary index, with no bounds checking.
Throws a ValueError if value is an empty non-string sequence.'''
if self._is_sequence(value) and not value:
raise ValueError, "Cannot insert an empty list!"
containing_list = self._get_item_at(index_tuple[:-1])
containing_list.insert(index_tuple[-1], value)
def _delete_at(self, index_tuple):
'''Private method to delete item at an arbitrary index, with no bounds checking.
Recursively deletes a resulting branch of empty lists.'''
containing_list = self._get_item_at(index_tuple[:-1])
del containing_list[index_tuple[-1]]
if not self._get_item_at(index_tuple[:-1]):
self._delete_at(index_tuple[:-1])
def _increment_stack(self):
'''Private method that tires to increment the top value of the stack.
Returns True on success, False on failure (empty stack).'''
try:
self.stack[-1] += 1
except IndexError:
return False
else:
return True
def _decrement_stack(self):
'''Private method that tries to decrement the top value of the stack.
Returns True on success, False on failure (empty stack).'''
try:
self.stack[-1] -= 1
except IndexError:
return False
else:
return True
def go_to_head(self):
'''Move the cursor to the head of the nested list.'''
self.stack = []
while self._is_sequence():
self.stack.append(0)
def go_to_tail(self):
self.stack = []
'''Move the cursor to the tail of the nested list.'''
while self._is_sequence():
self.stack.append(len(self.get_item()) - 1)
def right(self):
'''Move cursor one step right in the nested list.'''
while self._increment_stack() and not self._is_in_range():
self.stack.pop()
if not self.stack:
self.go_to_tail()
return False
while self._is_sequence():
self.stack.append(0)
return True
def left(self):
'''Move cursor one step left in the nested list.'''
while self._decrement_stack() and not self._is_in_range():
self.stack.pop()
if not self.stack:
self.go_to_head()
return False
while self._is_sequence():
self.stack.append(len(self.get_item()) - 1)
return True
def move_cursor(self, index_tuple):
'''Move cursor to the location indicated by index_tuple.
Raises an error if index_tuple is out of range or doesn't correspond
to a toplevel object.'''
item = self._get_item_at(index_tuple)
if self._is_sequence(item):
raise IndexError, 'index_tuple must point to a toplevel object'
def get_item(self):
'''Get the item at the cursor location.'''
return self._get_item_at(self.stack)
def set_item(self, value):
'''Set the item a the cursor locaiton.'''
return self._set_item_at(self.stack, value)
def insert(self, value):
'''Insert an item at the cursor location. If value is a sequence,
cursor moves to the first toplevel object in value after insertion.
Otherwise, cursor does not move.'''
temp_stack = self.stack[:]
self.left()
self._insert_at(temp_stack, value)
self.right()
def delete(self):
'''Deete an item at the cursor location. Cursor does not move.'''
temp_stack = self.stack[:]
self.left()
self._delete_at(temp_stack)
self.right()
def iterate(self):
'''Iterate over the values in nested_list in sequence'''
self.go_to_head()
yield self.get_item()
while self.right():
yield self.get_item()
def iterate_left(self):
'''Iterate over the values in nested_list in reverse.'''
self.go_to_tail()
yield self.get_item()
while self.left():
yield self.get_item()
def find(self, value):
'''Search for value in nested_list; move cursor to first location of value.'''
for i in self.iterate():
if i == value:
break
def find_left(self, value):
'''Search for value backwards in nested_list; move cursor to last location of value.'''
for i in self.iterate_left():
if i == value:
break
def _NLI_Test():
l = [1, 2, 3, ['a', 'b', 'c'], 4, ['d', 'e', [100, 200, 300]], 5, ['a', 'b', 'c'], 6]
nli = NestedListIter(l)
print nli.nested_list
for i in nli.iterate():
print i,
print
for i in nli.iterate_left():
print i,
print
nli.go_to_head()
for i in range(5):
nli.right()
nli.insert('cow')
nli.insert(['c', ['o', 'w']])
print nli.nested_list
nli.find('cow')
print nli.get_item()
nli.delete()
print nli.nested_list
nli.find('c')
nli.delete()
print nli.nested_list
nli.find_left('w')
nli.delete()
nli.find('o')
nli.delete()
print nli.nested_list
print nli.nested_list == l
nli.find(100)
nli.set_item(100.1)
print nli.nested_list
if __name__ == '__main__':
_NLI_Test()