LINUX.ORG.RU

python скрипт: подсчитать частоту перехода состояний

 ,


1

1

Disclaimer: я только начал изучать питон.

Имеется текстовый файл следующего содержания (в качестве примера):

A-B-C-D
A-B-D-E
A-C-E
A-B-C-E
A-B
B-E
A-D-E
B-C
...

В каждой строке A,B,C,D,E — это некие состояния, разделенные делимитером '-'. Я пишу скрипт на питоне который должен подсчитать частоту перехода состояний равно как и количество входных и выходных состояний, например:

A входное  6 раз
E выходное 5 раз
A->B  4 раза
B->C  3 раза
C->D  1 раз
и т.д.

Подсчитать частоту A,B,C,D несложно, но мне нужно именно переходы и одного состояния в другое.

Вот что я соорудил — входной файл читается в строку, далее эта строка режется splitlines() построчно и кладется в список. Данный список подается на вход вот этой функции которая все считает:

def count_states(lines):
    states = dict()
    for l in lines:
        words = l.split('-')
        if not words[0] in states:
            states[words[0]] = 1
        else:
            states[words[0]] += 1
        for w in zip(words, words[1:]):
            if not w in states:
                states[w] = 1
            else:
                states[w] += 1

        if not w[-1] in states:
            states[w[-1]] = 1
        else:
            states[w[-1]] += 1

    return states

Вроде бы все считается правильно. Но можно ли написать компактнее и более «в духе» питона?

Спасибо.

★★

Не могу ничего сказать про «дух питона», но код так себе. Из-за того что у этой процедуры нет никакого практического приминения, у нее нет никакой структуры. За это я не люблю все эти «абстрактные задания». Что такое массив states? Что в нем содержится? Как им пользоваться? Почему там половина ключей - элементы, а половина - таплы? Если бы ты впоследствии пытался сделать с ним хоть что-нибудь полезное, то сразу бы понял что так делать не надо.

А вообще, код рабочий — и сойдет. На джуна сгодится, а там тебя уже ревьюверы сношать будут.

morse ★★★★★ ()

Три if’а можно убрать, если использовать defaultdict.

>>> from collections import defaultdict
>>> 
>>> a = defaultdict(lambda: 1)
>>> dict(a)
{}
>>> a[1] += 1
>>> a[2] += 2
>>> dict(a)
{1: 2, 2: 3}
>>> 
i-rinat ★★★★★ ()
Последнее исправление: i-rinat (всего исправлений: 2)

На логику не смотрел (но по-моему у тебя там не различаются входные и выходные состояния), а насчёт компактности - открой для себя collections.

from collections import defaultdict 

states = defaultdict(int)
states['A', 'B'] += 1
states['B', 'C'] += 1
states['C', None] += 1

for (from_, to), count in states.items():
    print(from_, '→', to, ':', count)
A → B : 1
B → C : 1
C → None : 1

Ещё лучше тут подходит collections.Counter.

slovazap ★★★★★ ()
Последнее исправление: slovazap (всего исправлений: 1)
Ответ на: комментарий от WitcherGeralt

c[‘A-B’] += 1

Вот эта часть выглядит кривой, потому что считатель (Counter) и сами данные смешаны в одно. Код поймут, потому что знают, что Counter это наследник dict, и вести себя будет похожим образом. Но криво же.

i-rinat ★★★★★ ()

Строки всегда упорядоченные? Статистика нужна только по всему тексту или по каждой «ноде» тоже?

vvn_black ★★★★★ ()

Но можно ли написать компактнее и более «в духе» питона?

Так... Питон же у нас не про оптимизацию? ) Тогда можно так:

text = '''
A-B-C-D
A-B-D-E
A-C-E
A-B-C-E
A-B
B-E
A-D-E
B-C
'''

from collections import Counter
from itertools import chain

data = list(map(lambda x: x.split('-'), text.split()))
nodes = sorted(set(chain(*data)))

def node_count_first(node, data):
    return len(list(filter(lambda x: x[0] == node, data)))

def node_count_last(node, data):
    return len(list(filter(lambda x: x[-1] == node, data)))

def node_transitions(node, data):
    return filter(
        len,
        map(lambda x: node in x and f'{node}{x[x.index(node) + 1]}' or '',
            filter(lambda x: x[-1] != node, data) ))


for node in nodes:
    print(f'{node}, first: {node_count_first(node, data)}, '
          f'last: {node_count_last(node, data)}')

states = Counter(chain(*map(lambda x: node_transitions(x, data), nodes)))
print(states)
A, first: 6, last: 0
B, first: 2, last: 1
C, first: 0, last: 1
D, first: 0, last: 1
E, first: 0, last: 5
Counter({'AB': 4, 'BC': 3, 'CE': 2, 'DE': 2, 'AC': 1, 'AD': 1, 'BD': 1, 'BE': 1, 'CD': 1})
vvn_black ★★★★★ ()

zip(words, words[1:])

Так, полпрограммы ты написал. Осталось отшелушить все остальное и будет

collections.Counter(zip(words, words[1:]) для одной строки или

seqs = [line.split('-') for line in ['A-B-C-A-B', 'A-B']]
transitions = [zip(seq, seq[1:]) for seq in seqs]
collections.Counter(itertools.chain(*transitions))

для нескольких

t184256 ★★★★★ ()
from collections import Counter

def count_states(lines):
	states = Counter()
	for line in lines:
		pr_line = line.replace('-', '')
		states.update(pr_line)
		states.update([f'{p}-{с}' for p, с in zip(pr_line[:-1], pr_line[1:])])
	return states

Как-то так.

sekekeke ()
Ответ на: комментарий от vvn_black

ну такое себе. понапхал куда надо и не надо этих своих фильтров с лямбдами и в итоге нихрена непонятно, надо вчитываться.

anonymous ()
Ответ на: комментарий от t184256

Так, полпрограммы ты написал. Осталось отшелушить все остальное и будет

Спасибо, элегантно и кратко! Только интерпретатор ругался, я немного поменял код:

from collections import Counter
from itertools import chain

def xstates(lines):
    seqs = [l.split('-') for l in lines]
    x = [zip(seq, seq[1:]) for seq in seqs]
    p = Counter(chain(*x))
...

А вот как подсчитать еще начальные и конечные состояния и добавить их в начало `p` и в конце. Пример:

A-B-C-D
A-B-D-E
A-C-E
A-B-C-E
A-B
B-E
A-D-E
A-C

состояние A -- 7 раз
состояние E -- 4 раза
состояние D -- 1 раз
и пр.
cruz7 ★★ ()
Ответ на: комментарий от sekekeke
states.update([f'{p}-{с}' for p, с in zip(pr_line[:-1], pr_line[1:])])

Питон ругается на эту строчку:

  states.update([f'{p}-{c}' for p, c in zip(pr_line[:-1], pr_line[1:])])
                          ^
SyntaxError: invalid syntax
cruz7 ★★ ()
Ответ на: комментарий от cruz7
states.update(['{0}-{1}'.format(p, c) for p, c in zip(pr_line[:-1], pr_line[1:])])

Анонимус прав. Поправил, сейчас должно работать.

sekekeke ()

Если задача не учебная, а хоть сколько то боевая, затолкай данные в реляционную СУБД и играйся с ними сколько влезет.

olelookoe ★★ ()
Ответ на: комментарий от cruz7

Я не очень понимаю, как их добавить в p, если p - переходы. Вместе с остальным как (None, 'A') и ('D', None)? Тогда (навскидку):

from collections import Counter
from itertools import chain

def xstates(lines):
    seqs = [[None] + l.split('-') + [None] for l in lines]
    x = [zip(seq, seq[1:]) for seq in seqs]
    p = Counter(chain(*x))

Отдельно? Тогда Counter([seq[0] for seq in seqs]), Counter([seq[-1] for seq in seqs])

t184256 ★★★★★ ()

компактнее
«в духе»

Тебе либо одно, либо другое. Все питонисты, которых я видел, пишут в столбик, много и «понятно», а двойной map их повергает в ужас.

anonymous ()
Ответ на: комментарий от anonymous

плюсую вот этого анонимуса.

Это и есть «дух питона» - вот такой длинный, минимально абстрактный, и даже тупой, код.

lovesan ★★ ()
Ответ на: комментарий от t184256
from collections import Counter
from itertools import chain

def xstates(lines):
    seqs = [l.split('-') for l in lines]
    x = [zip(seq, seq[1:]) for seq in seqs]
    p = Counter(chain(*x))
...

Для входного массива:

A-B-C-D
A-B-D-E
A-C-E
A-B-C-E
A-B
B-E
A-D-E
A-C

на выходе получается:

Counter({('A', 'B'): 4, ('D', 'E'): 2, ('B', 'C'): 2, ('C', 'E'): 2, ('A', 'C'): 2, ('C', 'D'): 1, ('A', 'D'): 1, ('B', 'E'): 1, ('B', 'D'): 1})

Не пойму, по какой системе формируется этот выходной словарь? Или же он уже отсортирован по value? Можно ли получить несортированный?

cruz7 ★★ ()
Ответ на: комментарий от cruz7

Предположим, что он несортированный. Что хотел-то?

t184256 ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.