web-dev-qa-db-fra.com

Étant donné une chaîne d'un million de nombres, retourne tous les nombres à 3 chiffres répétés

J'ai eu une interview avec une société de fonds spéculatifs à New York il y a quelques mois et, malheureusement, je n'ai pas reçu l'offre de stage en tant qu'ingénieur en informatique/logiciel. (Ils ont également demandé à la solution d'être en Python.)

J'ai pas mal foiré le premier problème d'interview ...

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

Par exemple: si la chaîne était: 123412345123456, la fonction/programme renverrait:

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

Ils ne m'ont pas donné la solution après l'échec de l'entretien, mais ils m'ont dit que la complexité temporelle de la solution était constante, car tous les résultats possibles se situent entre:

000 -> 999

Maintenant que j'y réfléchis, je ne pense pas qu'il soit possible de concevoir un algorithme à temps constant. Est ce 

135
ezzzCash

Le temps constant n'est pas possible. Tous les 1 million de chiffres doivent être examinés au moins une fois, il s'agit donc d'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 représentant le nombre d'occurrences de chaque nombre possible de 3 chiffres. Faites avancer les chiffres d’un chiffre à l’autre, le premier index == 0, le dernier index == 999997 et incrémentez le tableau [nombre à 3 chiffres] pour créer un histogramme (nombre d’occurrences pour chaque nombre possible de 3 chiffres). Puis, affichez le contenu du tableau avec un nombre> 1.

79
rcgldr

La solution simple O(n) consisterait à 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 rechercherait à travers 1 million de chiffres 1000 fois.

En parcourant 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 minutage montre qu'itérer une seule fois sur l'index est deux fois plus rapide que d'utiliser count.

14
Daniel

Un million est petit pour la réponse que je donne ci-dessous. Attendez-vous à ce que vous deviez pouvoir exécuter la solution dans l'interview, sans pause, puis procédez comme suit:

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 __== '__main__':
    import random

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

J'espère que l'intervieweur cherchera à utiliser les collections standard.Counter de la bibliothèque standard.

Version d'exécution parallèle

J'ai écrit un blog post à ce sujet avec plus d'explications.

13
Paddy3118

Voici une implémentation NumPy de l'algorithme "consensus" O(n): parcourez tous les triplets et la corbeille au fur et à mesure. Le binning est fait en rencontrant le mot "385", en ajoutant un à bin [3, 8, 5] qui est une opération O(1). Les bacs sont disposés dans un cube 10x10x10. 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 pure Python de @ Daniel sur les grands ensembles de données. Exemple de sortie:

# 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
10
Paul Panzer

Je voudrais résoudre le problème comme suit:

def find_numbers(str_num):
    final_dict = {}
    buffer = {}
    for idx in range(len(str_num) - 3):
        num = int(str_num[idx:idx + 3])
        if num not in buffer:
            buffer[num] = 0
        buffer[num] += 1
        if buffer[num] > 1:
            final_dict[num] = buffer[num]
    return final_dict

Appliqué à votre exemple de chaîne, cela donne:

>>> find_numbers("123412345123456")
{345: 2, 234: 3, 123: 3}

Cette solution est exécutée dans O(n), n étant la longueur de la chaîne fournie et, je suppose, la meilleure solution.

3
pho7

Selon ma compréhension, vous ne pouvez pas avoir la solution dans un temps constant. Il faudra au moins un passage sur le nombre de millions de chiffres (en supposant que ce soit une chaîne). Vous pouvez avoir une itération mobile de 3 chiffres sur les chiffres du nombre de longueurs en millions et augmenter la valeur de la clé de hachage de 1 si elle existe déjà ou créer une nouvelle clé de hachage (initialisée par la valeur 1) si elle n'existe pas déjà dans le dictionnaire.

Le code ressemblera à ceci:

def calc_repeating_digits(number):

    hash = {}

    for i in range(len(str(number))-2):

        current_three_digits = number[i:i+3]
        if current_three_digits in hash.keys():
            hash[current_three_digits] += 1

        else:
            hash[current_three_digits] = 1

    return hash

Vous pouvez filtrer jusqu'aux clés dont la valeur d’élément est supérieure à 1.

2
Abhishek Arora

Comme mentionné dans une autre réponse, vous ne pouvez pas utiliser cet algorithme en temps constant, car vous devez examiner au moins n chiffres. Le temps linéaire est le plus rapide que vous puissiez obtenir.

Cependant, l'algorithme peut être réalisé dans O(1) space . Il vous suffit de stocker les nombres de chaque numéro à 3 chiffres, vous avez donc besoin d'un tableau de 1 000 entrées. Vous pouvez ensuite diffuser le numéro dans.

Je suppose que l'intervieweur s'est mal exprimé lorsqu'il vous a donné la solution ou que vous avez mal compris le "temps constant" lorsqu'il a dit "espace constant"

2
Cort Ammon

Voici ma réponse:

from timeit import timeit
from collections import Counter
import types
import random

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


def f_counter(text):
    c = Counter()
    for i in range(len(text)-2):
        ss = text[i:i+3]
        c.update([ss])
    return (i for i in c.items() if i[1] > 1)

def f_dict(text):
    d = {}
    for i in range(len(text)-2):
        ss = text[i:i+3]
        if ss not in d:
            d[ss] = 0
        d[ss] += 1
    return ((i, d[i]) for i in d if d[i] > 1)

def f_array(text):
    a = [[[0 for _ in range(10)] for _ in range(10)] for _ in range(10)]
    for n in range(len(text)-2):
        i, j, k = (int(ss) for ss in text[n:n+3])
        a[i][j][k] += 1
    for i, b in enumerate(a):
        for j, c in enumerate(b):
            for k, d in enumerate(c):
                if d > 1: yield (f'{i}{j}{k}', d)


for n in (1E1, 1E3, 1E6):
    n = int(n)
    data = setup_data(n)
    print(f'n = {n}')
    results = {}
    for name, func in list(globals().items()):
        if not name.startswith('f_') or not isinstance(func, types.FunctionType):
            continue
        print("{:16s}{:16.8f} ms".format(name[2:], timeit(
            'results[name] = f(**data)', globals={'f':func, 'data':data, 'results':results, 'name':name}, number=10)*100))
    for r in results:
        print('{:10}: {}'.format(r, sorted(list(results[r]))[:5]))

La méthode de recherche de tableau est très rapide (même plus rapide que la méthode numpy de @ paul-panzer!). Bien sûr, il triche car il n’est pas terminé techniquement après l’achèvement, car il renvoie un générateur. De plus, il n'est pas nécessaire de vérifier chaque itération si la valeur existe déjà, ce qui aidera probablement beaucoup.

n = 10
counter               0.10595780 ms
dict                  0.01070654 ms
array                 0.00135370 ms
f_counter : []
f_dict    : []
f_array   : []
n = 1000
counter               2.89462101 ms
dict                  0.40434612 ms
array                 0.00073838 ms
f_counter : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
f_dict    : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
f_array   : [('008', 2), ('009', 3), ('010', 2), ('016', 2), ('017', 2)]
n = 1000000
counter            2849.00500992 ms
dict                438.44007806 ms
array                 0.00135370 ms
f_counter : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
f_dict    : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
f_array   : [('000', 1058), ('001', 943), ('002', 1030), ('003', 982), ('004', 1042)]
1
Turksarama

Image comme réponse:

IMAGE AS ANSWER

On dirait une fenêtre coulissante.

1
天杀包子神

Voici ma solution:

from collections import defaultdict
string = "103264685134845354863"
d = defaultdict(int)
for elt in range(len(string)-2):
    d[string[elt:elt+3]] += 1
d = {key: d[key] for key in d.keys() if d[key] > 1}

Avec un peu de créativité dans la boucle for (et une liste de recherche supplémentaire avec True/False/None par exemple), vous devriez être en mesure de vous débarrasser de la dernière ligne, car vous voulez uniquement créer des clés dans le dict que nous avons visité une fois jusqu'à ce point. .J'espère que ça aide :)

1
econ

-Dire du point de vue de C .- Vous pouvez avoir un résultat de tableau entier sur 3-d [10] [10] [10]; - Aller du 0ème au n-4ème emplacement, où n étant le taille du tableau de chaînes .- Sur chaque emplacement, vérifiez les valeurs actuelle, suivante et suivante .- Incrémentez le cntr en tant que résultats [actuel] [suivant] [suivant] [suivant] ++; - Imprimer les valeurs de 

results[1][2][3]
results[2][3][4]
results[3][4][5]
results[4][5][6]
results[5][6][7]
results[6][7][8]
results[7][8][9]

-Il est temps O(n), il n'y a pas de comparaisons impliquées .- Vous pouvez exécuter des trucs parallèles ici en partitionnant le tableau et en calculant les correspondances autour des partitions.

0
Suresh