137 votes

Étant donné une chaîne d'un million de chiffres, renvoyer tous les chiffres à 3 chiffres qui se répètent.

J'ai eu un entretien avec une société de fonds spéculatifs à New York il y a quelques mois et malheureusement, je n'ai pas obtenu l'offre de stage en tant qu'ingénieur en données/logiciels. (Ils ont également demandé que la solution soit en Python).

J'ai presque tout foiré sur le problème du premier entretien...

Question : Étant donné une chaîne d'un million de chiffres (Pi par exemple), écrivez une fonction/programme qui renvoie tous les nombres répétitifs à 3 chiffres et le nombre de de répétition supérieur à 1

Par exemple, si la chaîne de caractères était : 123412345123456 alors la fonction/le programme reviendrait :

123 - 3 times
234 - 3 times
345 - 2 times

Ils ne m'ont pas donné la solution après que j'ai échoué à l'entretien, mais ils m'ont dit que la complexité temporelle de la solution était constante de 1000 puisque tous les résultats possibles sont compris entre les deux :

000 --> 999

Maintenant que j'y pense, je ne pense pas qu'il soit possible de trouver un algorithme à temps constant. C'est possible ?

167voto

paxdiablo Points 341644

Vous vous en êtes tiré à bon compte, vous avez probablement Ne le fais pas. je veux travailler pour un fonds spéculatif où les quants ne comprennent pas les algorithmes de base :-)

Hay pas de manière de traiter une structure de données de taille arbitraire en O(1) si, comme dans ce cas, vous devez visiter chaque élément au moins une fois. Le site meilleur que vous pouvez espérer est O(n) dans ce cas, où n est la longueur de la chaîne.

Bien que, en passant, un nominal O(n) algorithme sera être O(1) pour une taille d'entrée fixe, donc, techniquement, ils peuvent avoir eu raison ici. Cependant, ce n'est généralement pas ainsi que les gens utilisent l'analyse de la complexité.

Il me semble que vous auriez pu les impressionner de plusieurs façons.

D'abord, en les informant que c'est no possible de le faire en O(1) sauf si vous utilisez le raisonnement "suspect" donné ci-dessus.

Deuxièmement, en montrant vos compétences d'élite en fournissant du code Pythonique tel que :

inpStr = '123412345123456'

# O(1) array creation.
freq = [0] * 1000

# O(n) string processing.
for val in [int(inpStr[pos:pos+3]) for pos in range(len(inpStr) - 2)]:
    freq[val] += 1

# O(1) output of relevant array values.
print ([(num, freq[num]) for num in range(1000) if freq[num] > 1])

Ces sorties :

[(123, 3), (234, 3), (345, 2)]

mais vous pouvez, bien sûr, modifier le format de sortie comme vous le souhaitez.

Et, finalement, en leur disant qu'il y a presque certainement pas de problème avec un O(n) puisque le code ci-dessus fournit des résultats pour une chaîne d'un million de caractères en moins d'une demi-seconde. L'échelle semble également être assez linéaire, puisqu'il faut 3,5 secondes pour une chaîne de 10 000 000 de caractères et 36 secondes pour une chaîne de 100 000 000 de caractères.

Et, s'ils besoin de Mieux encore, il existe des moyens de paralléliser ce genre de choses qui peuvent accélérer considérablement le processus.

Pas dans un simple L'interpréteur Python bien sûr, en raison de la GIL, mais vous pourriez diviser la chaîne en quelque chose comme (chevauchement indiqué par vv est nécessaire pour permettre un traitement correct des zones limites) :

    vv
123412  vv
    123451
        5123456

Vous pouvez confier ces tâches à des travailleurs distincts et combiner les résultats par la suite.

Le fractionnement de l'entrée et la combinaison de la sortie risquent d'annihiler toute économie avec les petites chaînes de caractères (et peut-être même les chaînes de caractères à un million de chiffres) mais, pour les ensembles de données beaucoup plus importants, cela peut faire une différence. Mon mantra habituel de "mesure, ne devine pas" s'applique ici, bien sûr.


Ce mantra s'applique également à otros des possibilités, telles que le contournement total de Python et l'utilisation d'un autre langage qui peut être plus rapide.

Par exemple, le code C suivant, qui s'exécute sur le même matériel que le code Python précédent, traite un fichier cent millions de chiffres en 0,6 seconde, soit à peu près le même temps que le code Python a traité un million. En d'autres termes, beaucoup plus rapide :

#include <stdio.h>
#include <string.h>

int main(void) {
    static char inpStr[100000000+1];
    static int freq[1000];

    // Set up test data.

    memset(inpStr, '1', sizeof(inpStr));
    inpStr[sizeof(inpStr)-1] = '\0';

    // Need at least three digits to do anything useful.

    if (strlen(inpStr) <= 2) return 0;

    // Get initial feed from first two digits, process others.

    int val = (inpStr[0] - '0') * 10 + inpStr[1] - '0';
    char *inpPtr = &(inpStr[2]);
    while (*inpPtr != '\0') {
        // Remove hundreds, add next digit as units, adjust table.

        val = (val % 100) * 10 + *inpPtr++ - '0';
        freq[val]++;
    }

    // Output (relevant part of) table.

    for (int i = 0; i < 1000; ++i)
        if (freq[i] > 1)
            printf("%3d -> %d\n", i, freq[i]);

    return 0;
}

78voto

rcgldr Points 1197

Le temps constant n'est pas possible. Les 1 million de chiffres doivent être examinés au moins une fois, ce qui représente une complexité temporelle de O(n), où n = 1 million dans ce cas.

Pour une solution simple O(n), créez un tableau de taille 1000 qui représente le nombre d'occurrences de chaque nombre à 3 chiffres possible. Avancez d'un chiffre à la fois, premier indice == 0, dernier indice == 999997, et incrémentez array[3 digit number] pour créer un histogramme (nombre d'occurrences pour chaque nombre à 3 chiffres possible). Ensuite, sortez le contenu du tableau avec des comptes > 1.

14voto

Daniel Points 5467

La solution simple O(n) serait de compter chaque nombre à 3 chiffres :

for nr in range(1000):
    cnt = text.count('%03d' % nr)
    if cnt > 1:
        print '%03d is found %d times' % (nr, cnt)

Cela permettrait de rechercher les 1 million de chiffres 1000 fois.

Traverser les chiffres une seule fois :

counts = [0] * 1000
for idx in range(len(text)-2):
    counts[int(text[idx:idx+3])] += 1

for nr, cnt in enumerate(counts):
    if cnt > 1:
        print '%03d is found %d times' % (nr, cnt)

Le chronométrage montre que le fait de n'itérer qu'une seule fois sur l'index est deux fois plus rapide que l'utilisation de la fonction count .

14voto

Paddy3118 Points 1770

Un million, c'est peu pour la réponse que je donne ci-dessous. En supposant seulement que vous devez être capable d'exécuter la solution pendant l'entretien, sans pause, alors ce qui suit fonctionne en moins de deux secondes et donne le résultat requis :

from collections import Counter

def triple_counter(s):
    c = Counter(s[n-3: n] for n in range(3, len(s)))
    for tri, n in c.most_common():
        if n > 1:
            print('%s - %i times.' % (tri, n))
        else:
            break

if __name__ == '__main__':
    import random

    s = ''.join(random.choice('0123456789') for _ in range(1_000_000))
    triple_counter(s)

Avec un peu de chance, l'intervieweur cherchera à utiliser la classe collections.Counter des bibliothèques standard.

Version d'exécution parallèle

J'ai écrit un article de blog sur ce sujet avec plus d'explications.

10voto

Paul Panzer Points 30707

Voici une implémentation NumPy de l'algorithme O(n) "consensus" : parcourir tous les triplets et les classer au fur et à mesure. Le classement est fait en ajoutant un à bin[3, 8, 5] dès que l'on rencontre "385", ce qui est une opération O(1). Les cases sont disposées dans un ordre 10x10x10 cube. Comme le binning est entièrement vectorisé, il n'y a pas de boucle dans le code.

def setup_data(n):
    import random
    digits = "0123456789"
    return dict(text = ''.join(random.choice(digits) for i in range(n)))

def f_np(text):
    # Get the data into NumPy
    import numpy as np
    a = np.frombuffer(bytes(text, 'utf8'), dtype=np.uint8) - ord('0')
    # Rolling triplets
    a3 = np.lib.stride_tricks.as_strided(a, (3, a.size-2), 2*a.strides)

    bins = np.zeros((10, 10, 10), dtype=int)
    # Next line performs O(n) binning
    np.add.at(bins, tuple(a3), 1)
    # Filtering is left as an exercise
    return bins.ravel()

def f_py(text):
    counts = [0] * 1000
    for idx in range(len(text)-2):
        counts[int(text[idx:idx+3])] += 1
    return counts

import numpy as np
import types
from timeit import timeit
for n in (10, 1000, 1000000):
    data = setup_data(n)
    ref = f_np(**data)
    print(f'n = {n}')
    for name, func in list(globals().items()):
        if not name.startswith('f_') or not isinstance(func, types.FunctionType):
            continue
        try:
            assert np.all(ref == func(**data))
            print("{:16s}{:16.8f} ms".format(name[2:], timeit(
                'f(**data)', globals={'f':func, 'data':data}, number=10)*100))
        except:
            print("{:16s} apparently crashed".format(name[2:]))

Sans surprise, NumPy est un peu plus rapide que la solution purement Python de @Daniel sur les grands ensembles de données. Exemple de résultat :

# n = 10
# np                    0.03481400 ms
# py                    0.00669330 ms
# n = 1000
# np                    0.11215360 ms
# py                    0.34836530 ms
# n = 1000000
# np                   82.46765980 ms
# py                  360.51235450 ms

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X