3
votes

Comment compter les éléments récurrents adjacents dans un tableau?

J'ai un tableau de 0 et de 1 en tant que tel

[0,0,3,0,0,0,0,0,0,2,0,0,0]

Je veux définir une fonction qui prendra ce tableau en entrée et sortira un tableau de même longueur, avec le nombre de adjacents 1 dans l'index où le premier 1 est apparu (et 0 sinon). Donc, la sortie serait

[0,0,1,1,1,0,0,0,0,1,1,0,0]

car 1 est apparu dans le 2ème index 3 fois consécutives et 1 est apparu dans le 9ème index 2 fois consécutives.

Y a-t-il un moyen de faire cela en utilisant numpy? Sinon, y a-t-il un moyen pythonique (efficace) de le faire?


3 commentaires

pouvez-vous partager ce que vous avez essayé jusqu'à présent?


Comme ce que vous avez essayé n'est pas clair, regardez peut-être groupby ?


Quelle est la sortie pour un '1' non adjacent, par ex. [0, 1, 0] ? est-ce [0, 0, 0] ou [0, 1, 0] ? Votre explication implique la première, mais seulement indirectement.


8 Réponses :


1
votes

Vous pouvez utiliser groupby pour regrouper les éléments consécutifs :

def groups(lst):
    for key, group in groupby(lst):
        count = sum(1 for _ in group)
        if key and count > 1:
            yield count
        yield from (0 for _ in range(count - key))


print(list(groups(a)))

Sortie

[0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0]

Un plus court (plus pythonique ?) est ce qui suit:

from itertools import groupby

a = [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]


def groups(lst):
    result = []
    for key, group in groupby(lst):
        if not key:  # is a group of zeroes
            result.extend(list(group))
        else:  # is a group of ones
            count = sum(1 for _ in group)
            if count > 1:  # if more than one
                result.append(count)
                result.extend(0 for _ in range(count - 1))
            else:
                result.append(0)
    return result


print(groups(a))


0 commentaires

1
votes

Voici une façon d'utiliser numpy et une compréhension de liste:

In [63]: def summ_cons(li):
    ...:     for k,g in groupby(li) :
    ...:            if k:
    ...:               s = sum(g)
    ...:               yield s
    ...:               yield from (0 for _ in range(s-1))
    ...:            yield from g
    ...:            


In [65]: list(summ_cons(a))
Out[65]: [0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0]

La logique:

  1. Trouvez les indices de début et de fin où vous avez des conséquences 1
  2. Divisez votre tableau à partir de ces indices
  3. additionnez les sous-listes qui en ont une et laissez les sous-listes avec zéro telles qu'elles sont
  4. aplatir le résultat en utilisant np.hstack .

Si vous voulez remplacer les restants par 0, procédez comme suit:

In [28]: np.hstack([[x.sum(), *[0]*(len(x) -1)]  if x[0] == 1 else x for x in np.split(a, np.where(np.diff(a) != 0)[0]+1)])
Out[28]: array([0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0])

[0] * (len (x) - 1) créera les 0 attendus pour vous et en utilisant un déballage sur place, vous pourrez les placer à côté de la sum(x).

Si vous avez toujours voulu une approche Python pure, voici une façon d'utiliser itertools.groupby:

In [23]: a = np.array([0,0,1,1,1,0,0,0,0,1,1,0,0])
In [24]: np.hstack([x.sum() if x[0] == 1 else x for x in np.split(a, np.where(np.diff(a) != 0)[0]+1)])
Out[24]: array([0, 0, 3, 0, 0, 0, 0, 2, 0, 0])


3 commentaires

Cela produit un tableau plus court que le tableau d'entrée.


@fuglede Je viens d'ajouter ce cas aussi


Votre deuxième exemple est parfait car il fonctionne pour les cas où le tableau commence et se termine par 0 ou 1 ... c'est une excellente solution .... np.hstack ([[x.sum (), * [0] * ( len (x) -1)] si x [0] == 1 sinon x pour x dans np.split (a, np.where (np.diff (a)! = 0) [0] +1)])



0
votes

en utilisant groupby

[0,0,3,0,0,0,0,0,0,2,0,0,0]


0 commentaires

2
votes

Utilisation du module itertools :

from itertools import chain, groupby

A = [0,0,1,1,1,0,0,0,0,1,1,0,0]

def get_lst(x):
    values = list(x[1])
    return [len(values)] + [0]*(len(values) - 1) if x[0] else values

res = list(chain.from_iterable(map(get_lst, groupby(A))))

# [0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0]


2 commentaires

Considérant qu'OP a utilisé numpy et qu'il existe également de nombreuses réponses génératrices / itertools similaires et meilleures ici, celle-ci est très inefficace. Si vous souhaitez utiliser itertools ou en général Python, il existe déjà des moyens meilleurs et plus rapides de le faire.


@ Kasrâmvd, tout à fait d'accord. Je conseillerais à OP de choisir une alternative à NumPy si celle-ci était proposée.



0
votes

TL; DR: cela vous donne le résultat que vous voulez:
0 [0, 0]
1 [1, 1, 1]
0 [0, 0, 0, 0]
1 [1, 1]
0 [0, 0]

vous donnera:

for k, g in itertools.groupby(input):
    print(g, list(k))

explication: h1 >

itertools a la fonction groupby pour diviser les exécutions de "le même"

[0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0]

vous donnera:

import itertools

input = [0,0,1,1,1,0,0,0,0,1,1,0,0]   
result = []

for k, g in itertools.groupby(input):
    if k == 1:
        ll = len(list(g))
        result.extend([ll,] + [0 for _ in range(ll-1)])
    else:
        result.extend(list(g)) 

donc k est la clé, l'élément dans la séquence d'entrée et g est le groupe.

donc les conditions if ajoutent soit (dans le cas d'un 0) la série de 0 à partir de l'entrée, soit la longueur si la série de 1 plus les zéros à remplir pour la longueur de la 1-course.


1 commentaires

La longueur du tableau de sortie ne correspond pas à la longueur du tableau d'entrée.



3
votes

Voici une solution utilisant des opérations vectorisées pures et aucune itération de liste:

import numpy as np

data = np.array([0,0,1,1,1,0,0,0,0,1,1,0,0])
output = np.zeros_like(data)

where = np.where(np.diff(data))[0]
vals = where[1::2] - where[::2]
idx = where[::2] + 1

output[idx] = vals
output
# array([0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0])


2 commentaires

Notez que cela échoue si data se termine par un 1.


Il échoue également si data commence par 1 . Je pense que cela fonctionne si data [0] et data [-1] sont identiques



1
votes

Utilisez des pandas, profitez des pandas count compte des valeurs non NaN. Créez des NaN en utilisant le masque puis groupez les modifications des valeurs de s.

[0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0]

Sortie:

import pandas as pd
l = [0,0,1,1,1,0,0,0,0,1,1,0,0]
s = pd.Series(l)
g = s.diff().ne(0).cumsum()
s.mask(s==0).groupby(g).transform('count').mask(g.duplicated(), 0).tolist()


0 commentaires

0
votes

Autre option sans dépendances:

La bonne vieille boucle while accédant par index (parfois plus rapide que numpy):

def count_same_adjacent_non_zeros(iterable):
  i, x, size = 0, 0, len(iterable)
  while i < size-1:
    if iterable[i] != iterable[i+1]:
      tmp = iterable[x:i+1]
      if not iterable[i] == 0:
        tmp = [len(tmp)] + [0 for _ in range(i-x)]
      for e in tmp: yield e
      x = i + 1
    i += 1
  for e in iterable[x:size]: yield e


print(list(count_same_adjacent_non_zeros(array)))

#=> [0, 0, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0]


0 commentaires