Перемещайтесь вручную с помощью курсора по вложенным спискам, предоставляя только «left ()» и «right ()» в качестве команд? - PullRequest
5 голосов
/ 01 июля 2011

Несмотря на то, что я пишу на python, я думаю, что абстрактная концепция более интересна для меня и других.Так что, пожалуйста, псевдокод:)

У меня есть список предметов из одного из моих классов.Давайте сделаем это со строками и числами здесь, это действительно не имеет значения.Его вкладывают на любую глубину.(На самом деле это не список, а класс контейнера, основанный на списке.)

Пример : [1, 2, 3, ['a', 'b', 'c'] 4 ['d', 'e', ​​[100, 200, 300]] 5, ['a', 'b', 'c'], 6]

Обратите внимание, что оба ['a', 'b', 'c'] на самом деле один и тот же контейнер.Если вы меняете одно, вы меняете другое.Контейнеры и элементы можно редактировать, вставлять элементы и использовать наиболее важные контейнеры несколько раз.Чтобы избежать избыточности, невозможно сгладить список (я думаю!), Потому что вы теряете возможность вставлять элементы в один контейнер, и он автоматически появляется во всех других контейнерах.

Проблема: Для интерфейса (просто командная строка с модулем Python «cmd») я хочу перемещаться по этой структуре с помощью курсора, который всегда указывает на текущий элемент, чтобы его можно было читать или редактировать.Курсор может перемещаться влево и вправо (с точки зрения пользователя) и должен вести себя так, как если бы список был не вложенным, а плоским.

Для человека это очень легко сделать.Вы просто делаете вид, что в этом списке выше подсписки не существуют, и просто идете слева направо и назад.

Например, если вы находитесь в позиции «3» в списке выше и идете направо, выполучите «a» в качестве следующего элемента, затем «b», «c», а затем «4» и т. д. Или, если вы идете прямо от «300», вы получите «5» следующим.

И обратно: Если вы идете влево от «6», то следующим будет «c».Если вы идете влево от «5», то до «300».

Так как же мне это сделать в принципе?У меня есть один подход, но он неправильный, и вопрос уже настолько длинный, что я боюсь, что большинство людей его не прочтут :(. Я могу опубликовать его позже.

PS Даже если трудно сопротивляться: Ответ навопрос не в том, «Почему вы хотите это сделать, почему вы организовываете свои данные таким образом, почему бы вам сначала не [сгладить список | что-то из моего воображения]? Проблема именно в том, что я описал здесь».больше ничего. Данные структурированы по характеру проблемы таким образом.

Ответы [ 4 ]

3 голосов
/ 01 июля 2011

Одним из решений будет сохранение текущего индекса и / или информации о глубине и использование ее для обхода вложенного списка.Но это похоже на решение, которое сделало бы много сложного разветвления - тестирование концов списков и так далее.Вместо этого я пришел к компромиссу.Вместо того, чтобы сгладить список списков, я создал генератор, который создает плоский список индексов в списке списков:

def enumerate_nested(nested, indices):
    for i, item in enumerate(nested):
        if isinstance(item, collections.Iterable) and not isinstance(item, basestring):
            for new_indices in enumerate_nested(item, indices + (i,)):
                yield new_indices
        else:
            yield indices + (i,)

Затем простая функция, которая извлекает самый внутренний элемент из списка списков на основекортеж индекса:

def tuple_index(nested_list, index_tuple):
    for i in index_tuple:
        nested_list = nested_list[i]
    return nested_list

Теперь все, что вам нужно сделать, - это просмотреть плоский список индексов любым удобным вам способом.

>>> indices = list(enumerate_nested(l, tuple()))
>>> print l
[1, 2, 3, ['a', 'b', 'c'], 4, ['d', 'e', [100, 200, 300]], 5, ['a', 'b', 'c'], 6]
>>> for i in indices:
...     print tuple_index(l, i),
... 
1 2 3 a b c 4 d e 100 200 300 5 a b c 6

Поскольку этот ответ был принят дляоснованное на стеке решение, которое я разместил на ideone в комментариях, и поскольку желательно не использовать внешние вставки для кода ответа, обратите внимание, что этот ответ также содержит мое основанное на стеке решение.

3 голосов
/ 01 июля 2011

Я бы позволил курсору иметь стек индексов массивов.

Примеры:

[1, 2, 3, ['a', 'b', 'c'], 4 ]

Если курсор находится на 1 (с индексом 0),позиция курсора - [0].

Если курсор находится на 2 (с индексом 1), позиция курсора - [1].

Если курсор находится на «a» (с индексом 3 на крайнем уровне и индексом 0 на втором уровне) позиция курсора будет [3, 0].

Если курсор находится на «b» (индекс 3 на крайнем уровне)и индекс 1 на втором уровне), позиция курсора будет [3, 1].

и т. д.

Чтобы переместить курсор вправо, вы просто увеличите крайний правый индекс в позиции.Поэтому, когда вы переходите от «b» к «c», оно увеличивается с [3, 1] до [3, 2].Если индекс выходит за пределы диапазона, вы извлекаете самое правое значение из стека индексов и увеличиваете самое правое значение.Поэтому, если вы переходите от 'c' к 4, вы переходите от [3, 2] к [4].

При перемещении, если вы сталкиваетесь с позицией с вложенным массивом, вы вводите ее.Поэтому, если вы идете вправо от 3 (по индексу [2]), вы вводите первый элемент в массиве ['a', 'b', 'c'], который находится по индексу [3, 0].

Движение влево - это всего лишь обратное движение вправо.

1 голос
/ 01 июля 2011

Хотя мне нравится идея сглаживания списка индексов, это делает невозможным изменение длины любого подсписка при выполнении итераций по вложенному списку.Если вам не нужна эта функциональность, я бы согласился с этим.

В противном случае я бы также добавил указатель в список в виде набора индексов и использовал бы рекурсию.Для начала, вот класс, который реализует right() и читает значение указателя через deref().(Я использую None для представления указателя за пределами списка.) Я оставлю это в качестве упражнения, как реализовать left() и присвоение элементам.И вам придется решить, какое поведение вы хотите, если замените элемент, на который вы в данный момент указываете, другим списком.Удачи!

def islist(seq):
    return isinstance(seq, (list, tuple))

class nav:
    def __init__(self, seq):
        self.seq = seq
        self.ptr = self.first()
    def __nonzero__(self):
        return bool(self.ptr)
    def right(self):
        """Advance the nav to the next position"""
        self.ptr = self.next()
    def first(self, seq=None):
        """pointer to the first element of a (possibly empty) sequence"""
        if seq is None: seq = self.seq
        if not islist(seq): return ()
        return (0,) + self.first(seq[0]) if seq else None
    def next(self, ptr=None, seq=None):
        """Return the next pointer"""
        if ptr is None: ptr = self.ptr
        if seq is None: seq = self.seq
        subnext = None if len(ptr) == 1 else self.next(ptr[1:], seq[ptr[0]])
        if subnext is not None: return (ptr[0],) + subnext
        ind = ptr[0]+1
        return None if ind >= len(seq) else (ind,) + self.first(seq[ind])
    def deref(self, ptr=None, seq=None):
        """Dereference given pointer"""
        if ptr is None: ptr = self.ptr
        if seq is None: seq = self.seq
        if not ptr: return None
        subseq = seq[ptr[0]]
        return subseq if len(ptr) == 1 else self.deref(ptr[1:], subseq)

abc = ['a', 'b', 'c']
a = [1, 2, 3, abc, 4, ['d', 'e', [100, 200, 300]], 5, abc, 6]
n = nav(a)

while n:
    print n.ptr, n.deref()
    n.right()
1 голос
/ 01 июля 2011

По сути, я бы основывал свое собственное решение на рекурсии. Я бы расширил класс контейнера следующим образом:

  1. cursor_position - Свойство, в котором хранится индекс выделенного элемента (или элемента, который содержит элемент, содержащий выделенный элемент, или любой уровень рекурсии за его пределами).
  2. repr_with_cursor - Этот метод должен возвращать версию содержимого контейнера для печати, уже выделяя элемент, выбранный в данный момент.
  3. mov_right - этот метод должен вызываться, когда курсор перемещается вправо. Возвращает новый индекс курсора в элементе или None, если курсор находится «вне» текущего контейнера (если вы перемещаетесь за последний элемент в контейнере.
  4. mov_left - То же самое, но влево.

Способ, которым должна работать рекурсия, заключается в том, что для каждого метода, в зависимости от типа выделенного метода, у вас должно быть два различных поведения:

  • , если курсор находится на контейнере , он должен вызывать метод "заостренного" контейнера.
  • если курсор находится на неконтейнере , он должен выполнять «реальную вещь».

EDIT У меня было свободное полчаса, поэтому я собрал пример класса, который реализует мою идею. Это не полная функция (например, он не справляется хорошо, когда достигает любого конца самого большого контейнера, и требует, чтобы каждый экземпляр класса использовался только один раз в самой большой последовательности), но он работает достаточно, чтобы продемонстрировать концепцию , Я повторю, прежде чем люди прокомментируют это: это проверочный код, он никоим образом не готов к использованию!

#!/usr/bin/env python
# -*- coding: utf-8  -*-

class C(list):

    def __init__(self, *args):
        self.cursor_position = None
        super(C, self).__init__(*args)

    def _pointed(self):
        '''Return currently pointed item'''
        if self.cursor_position == None:
            return None
        return self[self.cursor_position]

    def _recursable(self):
        '''Return True if pointed item is a container [C class]'''
        return (type(self._pointed()) == C)

    def init_pointer(self, end):
        '''
        Recursively set the pointers of containers in a way to point to the
        first non-container item of the nested hierarchy.
        '''
        assert end in ('left', 'right')
        val = 0 if end == 'left' else len(self)-1
        self.cursor_position = val
        if self._recursable():
            self.pointed._init_pointer(end)

    def repr_with_cursor(self):
        '''
        Return a representation of the container with highlighted item.
        '''
        composite = '['
        for i, elem in enumerate(self):
            if type(elem) == C:
                composite += elem.repr_with_cursor()
            else:
                if i != self.cursor_position:
                    composite += str(elem)
                else:
                    composite += '**' + str(elem) + '**'
            if i != len(self)-1:
                composite += ', '
        composite += ']'
        return composite

    def mov_right(self):
        '''
        Move pointer to the right.
        '''
        if self._recursable():
            if self._pointed().mov_right() == -1:
                if self.cursor_position != len(self)-1:
                    self.cursor_position += 1
        else:
            if self.cursor_position != len(self)-1:
                self.cursor_position += 1
                if self._recursable():
                    self._pointed().init_pointer('left')
            else:
                self.cursor_position = None
                return -1

    def mov_left(self):
        '''
        Move pointer to the left.
        '''
        if self._recursable():
            if self._pointed().mov_left() == -1:
                if self.cursor_position != 0:
                    self.cursor_position -= 1
        else:
            if self.cursor_position != 0:
                self.cursor_position -= 1
                if self._recursable():
                    self._pointed().init_pointer('right')
            else:
                self.cursor_position = None
                return -1

Простой тестовый скрипт:

# Create the nested structure
LevelOne = C(('I say',))
LevelTwo = C(('Hello', 'Bye', 'Ciao'))
LevelOne.append(LevelTwo)
LevelOne.append('!')
LevelOne.init_pointer('left')
# The container's content can be seen as both a regualar list or a
# special container.
print(LevelOne)
print(LevelOne.repr_with_cursor())
print('---')
# Showcase the effect of moving the cursor to right
for i in range(5):
    print(LevelOne.repr_with_cursor())
    LevelOne.mov_right()
print('---')
# Showcase the effect of moving the cursor to left
LevelOne.init_pointer('right')
for i in range(5):
    print(LevelOne.repr_with_cursor())
    LevelOne.mov_left()

Выводит:

['I say', ['Hello', 'Bye', 'Ciao'], '!']
[**I say**, [Hello, Bye, Ciao], !]
---
[**I say**, [Hello, Bye, Ciao], !]
[I say, [**Hello**, Bye, Ciao], !]
[I say, [Hello, **Bye**, Ciao], !]
[I say, [Hello, Bye, **Ciao**], !]
[I say, [Hello, Bye, Ciao], **!**]
---
[I say, [Hello, Bye, Ciao], **!**]
[I say, [Hello, Bye, **Ciao**], !]
[I say, [Hello, **Bye**, Ciao], !]
[I say, [**Hello**, Bye, Ciao], !]
[**I say**, [Hello, Bye, Ciao], !]

Забавная проблема! Мой любимый вопрос ОС сегодня! :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...