it-swarm.com.de

Geben Sie bei einer Zahl von Millionen Zahlen alle sich wiederholenden 3-stelligen Zahlen zurück

Ich hatte vor ein paar Monaten ein Interview mit einer Hedgefonds-Gesellschaft in New York und leider bekam ich das Praktikumsangebot als Daten-/Software-Ingenieur nicht. (Sie fragten auch nach der Lösung, in Python zu sein.)

Das erste Interviewproblem habe ich ziemlich vermasselt ...

Frage: Geben Sie .__ eine Zahl von Millionen Zahlen ein (z. B. Pi). eine Funktion/ein Programm, die alle sich wiederholenden 3-stelligen Nummern und die Anzahl von .__ zurückgibt. Wiederholung größer als 1

Beispiel: Wenn die Zeichenfolge 123412345123456 lautet, gibt die Funktion/das Programm Folgendes zurück:

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

Sie haben mir nicht die Lösung gegeben, nachdem ich das Interview nicht bestanden habe, aber sie sagten mir, dass die zeitliche Komplexität für die Lösung konstant bei 1000 lag, da alle möglichen Ergebnisse zwischen:

000 -> 999

Jetzt, wo ich darüber nachdenke, glaube ich nicht, dass es möglich ist, einen Algorithmus mit konstanter Zeit zu entwickeln. Ist es? 

135
ezzzCash

Sie sind leichtfertig davongekommen, wahrscheinlich möchten Sie nicht für einen Hedgefonds arbeiten, bei dem die Quants die grundlegenden Algorithmen nicht verstehen :-)

In O(1) gibt es no Möglichkeiten, eine Datenstruktur beliebiger Größe zu verarbeiten, wenn Sie, wie in diesem Fall, jedes Element mindestens einmal besuchen müssen. Das beste, auf das Sie hoffen können, ist in diesem Fall O(n), wobei n die Länge der Zeichenfolge ist.

Abgesehen davon ist ein nominaler O(n) Algorithmus wirdO(1) für eine feste Eingabegröße. Technisch gesehen waren sie hier möglicherweise korrekt. In der Regel wird die Komplexitätsanalyse jedoch nicht so eingesetzt.

Mir scheint, Sie hätten sie auf verschiedene Weise beeindrucken können.

Erstens, indem Sie ihnen mitteilen, dass es nicht möglich ist, dies in O(1) zu tun, es sei denn, Sie verwenden die oben angegebene "verdächtige" Argumentation.

Zweitens, indem Sie Ihre Elite-Fähigkeiten unter Beweis stellen, indem Sie Pythonic-Code bereitstellen, wie zum Beispiel:

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])

Dies gibt aus:

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

natürlich können Sie das Ausgabeformat beliebig ändern.

Und schließlich, indem Sie ihnen sagen, gibt es mit ziemlicher Sicherheit no ein Problem mit einer O(n) -Lösung, da der obige Code Ergebnisse für eine Zeichenfolge mit einer Million Ziffern in weniger als einer halben Sekunde liefert . Es scheint auch ziemlich linear zu skalieren, da eine Zeichenfolge mit 10.000.000 Zeichen 3,5 Sekunden und eine Zeichenfolge mit 100.000.000 Zeichen 36 Sekunden dauert.

Und wenn sie brauchen besser als das, gibt es Möglichkeiten, diese Art von Sachen zu parallelisieren, die es erheblich beschleunigen können.

Natürlich nicht innerhalb eines single Python -Interpreters aufgrund der GIL, aber Sie könnten den String in so etwas wie (Überlappung durch vv angegeben ist erforderlich, um dies zuzulassen) aufteilen ordnungsgemäße Bearbeitung der Grenzbereiche):

    vv
123412  vv
    123451
        5123456

Sie können diese ausfarmen, um die Mitarbeiter zu trennen und die Ergebnisse anschließend zu kombinieren.

Das Aufteilen von Eingaben und Kombinieren von Ausgaben überfordert wahrscheinlich jede Speicherung mit kleinen Zeichenfolgen (und möglicherweise sogar mit Zeichenfolgen mit einer Million Stellen), kann aber bei viel größeren Datenmengen durchaus einen Unterschied bewirken. Mein übliches Mantra von "messen, nicht raten" gilt hier natürlich.


Dieses Mantra gilt auch für andere Möglichkeiten, wie das Umgehen von Python und die Verwendung einer anderen Sprache, die möglicherweise schneller ist.

Beispielsweise verarbeitet der folgende C-Code, der auf derselben Hardware wie der frühere Code Python ausgeführt wird, in 0,6 Sekunden eine Zahl von hundert Millionen Zeichen, ungefähr so ​​viel Zeit wie der Code _.Python Code verarbeitet eins Million. Mit anderen Worten viel schneller:

#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;
}
168
paxdiablo

Konstante Zeit ist nicht möglich. Alle 1 Million Ziffern müssen mindestens einmal betrachtet werden, so dass eine zeitliche Komplexität von O (n) gilt, wobei in diesem Fall n = 1 Million ist.

Erstellen Sie für eine einfache O(n) - Lösung ein Array der Größe 1000, das die Anzahl der Vorkommen jeder möglichen 3-stelligen Zahl darstellt. Vorwärts um jeweils 1 Stelle, erster Index == 0, letzter Index == 999997 und Inkrementieren des Arrays [3-stellige Nummer], um ein Histogramm zu erstellen (Anzahl der Vorkommen für jede mögliche 3-stellige Nummer). Geben Sie dann den Inhalt des Arrays mit einer Anzahl> 1 aus.

79
rcgldr

Die einfache O(n) - Lösung wäre, jede dreistellige Zahl zu zählen:

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

Dies würde 1000 Mal alle 1 Million Ziffern durchsuchen.

Durchlauf der Ziffern nur einmal:

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)

Das Timing zeigt, dass das einmalige Durchlaufen des Index doppelt so schnell ist wie die Verwendung von count.

14
Daniel

Eine Million ist klein für die Antwort, die ich unten gebe. Erwarten Sie nur, dass Sie die Lösung im Interview ohne Pause ausführen können müssen, dann funktioniert Folgendes in weniger als zwei Sekunden und liefert das erforderliche Ergebnis:

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)

Hoffentlich würde der Interviewer nach den Standardbibliotheken suchen. CounterCounter.

Parallelausführung

Ich habe einen blog post dazu mit mehr Erklärung geschrieben.

13
Paddy3118

Hier ist eine NumPy-Implementierung des Algorithmus "Consensus" O(n): Gehen Sie durch alle Drillinge und bin dabei. Das Binning wird durchgeführt, indem man beim Treffen auf "385" stößt und eins zu bin [3, 8, 5] hinzufügt, was eine O(1) - Operation ist. Bins sind in einem 10x10x10-Würfel angeordnet. Da das Binning vollständig vektorisiert ist, gibt es keine Schleife im 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:]))

Wenig überraschend ist NumPy bei großen Datenmengen etwas schneller als die reine Python-Lösung von @ Daniel's. Beispielausgabe:

# 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

Ich würde das Problem wie folgt lösen:

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

Auf Ihre Beispielzeichenfolge angewendet ergibt sich Folgendes:

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

Diese Lösung läuft in O(n), wobei n die Länge der bereitgestellten Zeichenfolge ist und, denke ich, das Beste ist, das Sie bekommen können.

3
pho7

Nach meinem Verständnis können Sie die Lösung nicht in einer konstanten Zeit haben. Es dauert mindestens einen Durchlauf über die Millionenzahl (vorausgesetzt, es handelt sich um eine Zeichenkette). Sie können eine dreistellige Roll-Iteration über die Ziffern der Millionenlängennummer durchführen und den Wert des Hash-Schlüssels um 1 erhöhen, wenn er bereits vorhanden ist, oder einen neuen Hash-Schlüssel erstellen (mit Wert 1 initialisiert), wenn er nicht bereits existiert das Wörterbuch.

Der Code sieht ungefähr so ​​aus:

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

Sie können bis zu den Schlüsseln filtern, deren Artikelwert größer als 1 ist.

2
Abhishek Arora

Wie in einer anderen Antwort erwähnt, können Sie diesen Algorithmus nicht in einer konstanten Zeit ausführen, da Sie mindestens n Ziffern betrachten müssen. Die lineare Zeit ist die schnellste, die Sie bekommen können.

Der Algorithmus kann jedoch in O(1) space ausgeführt werden. Sie müssen nur die Zähler für jede dreistellige Zahl speichern, so dass Sie ein Array mit 1000 Einträgen benötigen. Sie können die Nummer dann streamen.

Meine Vermutung ist, dass entweder der Interviewer einen Fehler gemacht hat, als er Ihnen die Lösung gab, oder Sie haben "konstante Zeit" verloren, als sie "konstanten Raum" sagten.

2
Cort Ammon

Hier ist meine Antwort:

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]))

Die Array-Lookup-Methode ist sehr schnell (sogar schneller als die numpy-Methode von @ paul-panzer!). Natürlich schummelt es, da es nach dem Abschluss nicht technisch fertig ist, weil es einen Generator zurückgibt. Es muss auch nicht jede Iteration überprüft werden, wenn der Wert bereits vorhanden ist, was wahrscheinlich sehr hilfreich ist.

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

Bild als Antwort:

IMAGE AS ANSWER

Sieht aus wie ein Schiebefenster.

1
天杀包子神

Hier ist meine Lösung:

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}

Mit etwas Kreativität in for-Schleife (und zusätzlicher Nachschlageliste mit True/False/None zum Beispiel) sollten Sie die letzte Zeile loswerden können, da Sie nur Schlüssel in einem Diktat erstellen möchten, das wir bis dahin einmal besucht haben .Ich hoffe es hilft :)

1
econ

-Telling aus der Perspektive von C .- Sie können ein int 3-d-Array-Ergebnis haben [10] [10] [10]; - Gehen Sie von der 0. Position zur n-4ten Position, wobei n die vierte ist Größe des String-Arrays .- Überprüfen Sie an jedem Ort den aktuellen, nächsten und nächsten. .- Erhöhen Sie die CPU als resutls [current] [next] [next's next] ++; - Print die Werte von 

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]

-Es ist O(n) Zeit, es sind keine Vergleiche erforderlich- Sie können hier Paralleles ausführen, indem Sie das Array partitionieren und die Übereinstimmungen um die Partitionen berechnen.

0
Suresh