상위 10 개 검색어를 찾는 알고리즘


115

저는 현재 인터뷰를 준비하고 있는데, 이전 인터뷰에서 다음과 같은 질문을 받았던 것이 생각났습니다.

"Google에서 상위 10 개 검색어를 지속적으로 표시하도록 일부 소프트웨어를 설계하라는 요청을 받았습니다. 현재 Google에서 검색중인 검색어의 끝없는 실시간 스트림을 제공하는 피드에 대한 액세스 권한이 부여되었습니다. 어떤 알고리즘과 데이터 구조를 설명하십시오. 이를 구현하는 데 사용할 수 있습니다. 두 가지 변형을 디자인해야합니다.

(i) 항상 상위 10 개 검색어를 표시합니다 (예 : 피드 읽기를 시작한 이후).

(ii) 매시간 업데이트되는 지난달의 상위 10 개 검색어 만 표시합니다.

근사치를 사용하여 상위 10 개 목록을 얻을 수 있지만 선택을 정당화해야합니다. "
나는이 인터뷰에서 폭격을가했지만이를 구현하는 방법을 아직 모릅니다.

첫 번째 부분은 무한 목록의 지속적으로 증가하는 하위 시퀀스에서 가장 자주 사용되는 항목 10 개를 요청합니다. 선택 알고리즘을 조사했지만이 문제를 해결할 온라인 버전을 찾을 수 없었습니다.

두 번째 부분은 유한 목록을 사용하지만 처리되는 데이터 양이 많기 때문에 한 달 전체의 검색어를 메모리에 저장하고 매시간 히스토그램을 계산할 수 없습니다.

문제는 상위 10 개 목록이 지속적으로 업데이트되고 있다는 사실로 인해 더욱 어려워 지므로 어떻게 든 슬라이딩 윈도우에서 상위 10 개를 계산해야합니다.

어떤 아이디어?


11
@BlueRaja-어리석은 인터뷰 질문이 아니라 OP 부분에 대한 잘못된 해석입니다. 무한 목록에서 가장 빈번한 항목을 요청하는 것이 아니라 무한 목록의 유한 하위 시퀀스에서 가장 빈번한 항목을 요청하는 것입니다. 비유를 계속하려면what is the most frequent item in the subsequence [2; 2; 3; 3; 3; 4; 4; 4; 4; 5; 5] of your sequence?
IVlad

3
@BlueRaja-확실히 어려운 질문이지만 왜 어리석은지는 모르겠습니다. 거대한 데이터 세트를 가진 회사가 직면하는 상당히 전형적인 문제를 대표하는 것 같습니다. @IVlad-귀하의 제안에 따라 수정했습니다.
del

답변:


47

음, 모든 주파수를 저장하는 데 엄청난 비용이 드는 엄청난 양의 데이터처럼 보입니다. 데이터의 양이 너무 커서 모든 것을 저장할 수 없을 때 데이터 스트림 알고리즘 의 영역으로 들어갑니다 .

이 분야에서 유용한 책 : Muthukrishnan- "데이터 스트림 : 알고리즘 및 응용 프로그램"

위에서 선택한 문제에 대한 밀접한 관련 참조 : Manku, Motwani- "데이터 스트림에 대한 대략적인 주파수 카운트"[pdf]

그건 그렇고, Stanford의 Motwani (편집)는 매우 중요한 "Randomized Algorithms" 책 의 저자였습니다 . 이 책의 11 장에서는이 문제를 다룹니다 . 편집 : 죄송합니다, 잘못된 참조입니다. 특정 장이 다른 문제에 있습니다. 확인한 후 온라인으로 제공 되는 Muthukrishnan의 책 5.1.2 섹션 을 권장 합니다.

좋은 인터뷰 질문입니다.


2
+1 매우 흥미로운 내용입니다. 사이트에 "읽기"항목에 태그를 지정할 수있는 방법이 있어야합니다. 공유해 주셔서 감사합니다.
Ramadheer Singh

@Gollum : 북마크에 읽을 폴더가 있습니다. 그냥 할 수 있습니다. 나는 그 링크가 내 링크가 추가되고 있다는 것을 알고 있습니다. :)
Cam

+1. 스트리밍 알고리즘은 정확히 여기에서 주제이며 Muthu의 책 (지금까지 작성된 유일한 책인 AFAIK)은 훌륭합니다.
ShreevatsaR

1
+1. 관련 : en.wikipedia.org/wiki/Online_algorithm . BTW, Motwani 그래서 아마도, 최근 사망 했다 작가가 더 정확하다.

아주 이상한. 나는 그 책에서 그를 알고 있었지만, 그는 분명히이 때문에 더 유명했을 것임에 틀림 없다 : "Motwani는 PageRank 알고리즘에 대한 영향력있는 초기 논문의 공동 저자 중 한 명 (래리 페이지, 세르게이 브린, 테리 위노 그라드와 함께)이었다. Google 검색 기술의 기초입니다. "( en.wikipedia.org/wiki/Rajeev_Motwani )
Dimitris Andreou

55

주파수 추정 개요

고정 된 양의 스토리지를 사용하여 이러한 스트림에 대한 주파수 추정치를 제공 할 수있는 잘 알려진 알고리즘이 있습니다. 하나는 Misra와 Gries (1982)의 Frequent 입니다. n 개의 항목 목록에서 k-1 개의 카운터를 사용하여 n / k 회 이상 발생하는 모든 항목을 찾습니다 . 이것은 Boyer and Moore 's Majority 알고리즘 (Fischer-Salzberg, 1982) 의 일반화입니다 . 여기서 k 는 2입니다. Manku 및 Motwani의 LossyCounting (2002) 및 Metwally의 SpaceSaving (2005) 알고리즘은 공간 요구 사항이 비슷하지만 특정 조건에서 더 정확한 추정치를 제공 할 수 있습니다. 정황.

기억해야 할 중요한 점은 이러한 알고리즘이 주파수 추정값 만 제공 할 수 있다는 것입니다. 특히 Misra-Gries 추정치는 (n / k) 항목으로 실제 빈도를 과소 계산할 수 있습니다.

항목 이 50 % 이상 발생하는 경우 에만 항목을 확실하게 식별 할 수있는 알고리즘이 있다고 가정합니다 . 이 알고리즘에 N 개의 고유 항목 스트림을 공급 한 다음 총 2N-1 개 항목에 대해 한 항목 x 의 또 다른 N-1 복사본 을 추가 합니다. 알고리즘이 x 가 전체의 50 %를 초과 한다고 알려 주면 첫 번째 스트림에 있었을 것입니다. 그렇지 않은 경우 x 는 초기 스트림에 없습니다. 알고리즘이이 결정을 내리려면 초기 스트림 (또는 길이에 비례하는 요약)을 저장해야합니다! 따라서 우리는 그러한 "정확한"알고리즘에 필요한 공간이 Ω ( N ) 임을 스스로 증명할 수 있습니다 .

대신 여기에 설명 된 이러한 빈도 알고리즘은 임계 값을 초과하는 항목을 식별하는 추정치를 제공하며, 특정 마진 아래로 떨어지는 일부 항목도 식별합니다. 예를 들어 단일 카운터를 사용하는 Majority 알고리즘은 항상 결과를 제공합니다. 스트림의 50 %를 초과하는 항목이 있으면 검색됩니다. 그러나 한 번만 발생하는 항목을 제공 할 수도 있습니다. 데이터를 두 번째로 전달하지 않으면 알 수 없습니다 (다시 단일 카운터를 사용하지만 해당 항목 만 검색).

빈번한 알고리즘

다음은 Misra-Gries의 Frequent 알고리즘에 대한 간단한 설명입니다 . Demaine (2002)과 다른 사람들이 알고리즘을 최적화했지만 이것이 요점을 제공합니다.

임계 비율 지정, 1 / k ; n / k 회 이상 발생하는 모든 항목이 검색 됩니다. 빈지도 (예 : 빨강-검정 나무)를 만듭니다. 키는 검색어가되고 값은 해당 검색어에 대한 카운터가됩니다.

  1. 스트림의 각 항목을 살펴보십시오.
  2. 용어가 맵에 있으면 연관된 카운터를 증가 시키십시오.
  3. 그렇지 않고 맵이 k-1 항목 미만이면 카운터가 1 인 용어를 맵에 추가하십시오.
  4. 그러나 맵에 이미 k-1 개의 항목이있는 경우 모든 항목에서 카운터를 줄입니다. 이 과정에서 카운터가 0에 도달하면 맵에서 제거하십시오.

고정 된 양의 스토리지 (고정 크기 맵만)로 무한한 양의 데이터를 처리 할 수 ​​있습니다. 필요한 스토리지 양은 관심 임계 값에만 의존하며 스트림 크기는 중요하지 않습니다.

검색 횟수 계산

이 컨텍스트에서 아마도 1 시간의 검색을 버퍼링하고 해당 시간의 데이터에 대해이 프로세스를 수행합니다. 이 시간의 검색 로그를 두 번째 통과 할 수 있으면 첫 번째 통과에서 식별 된 상위 "후보"의 정확한 발생 수를 얻을 수 있습니다. 또는 하나의 패스를 만들고 모든 후보자를보고하는 것은 괜찮습니다. 거기에 있어야하는 모든 항목이 포함되어 있고 추가 항목은 다음 시간에 사라지는 소음 일뿐입니다.

관심 임계 값을 실제로 초과하는 모든 후보는 요약으로 저장됩니다. 한 달 분량의 이러한 요약을 보관하고 매시간 가장 오래된 것을 버리면 가장 일반적인 검색어에 대한 근사치를 얻을 수 있습니다.


이 솔루션이 필터 역할을하여 관심있는 검색어 수를 줄일 수 있다고 생각합니다. 용어가지도에 포함되면지도에서 벗어나더라도 실제 통계를 추적하기 시작합니다. 그런 다음 데이터에 대한 두 번째 패스를 건너 뛰고 수집 한 제한된 통계에서 정렬 된 상위 10 개를 생성 할 수 있습니다.
Dolph

카운터를 줄임으로써 트리에서 덜 검색된 용어를 잘라내는 우아한 방법을 좋아합니다. 그러나지도가 "가득 차면"도착하는 모든 새로운 검색어에 대해 감소 단계가 필요하지 않을까요? 일단 이런 일이 발생하면 카운터가 충분히 증가 할 기회를 갖기 전에 새로운 검색어가지도에서 빠르게 제거되지 않을까요?
del

1
@del-이 알고리즘은 지정된 임계 값 빈도를 초과하는 용어를 찾기위한 것이며 반드시 가장 일반적인 용어를 찾기위한 것은 아닙니다. 가장 일반적인 용어가 지정된 임계 값 아래로 떨어지면 일반적으로 찾을 수 없습니다. 새로운 용어를 "너무 빨리"제거하는 것에 대한 귀하의 우려가이 경우와 관련이있을 수 있습니다. 이것을 보는 한 가지 방법은 인기가있는 실제 "신호"가 있다는 것입니다. "노이즈"에서 눈에 띄게 두드러 질 것입니다. 그러나 때로는 신호를 찾을 수 없으며 무작위 검색 정적 만 있습니다.
erickson

@erickson-맞아요-이 알고리즘의 가정은 상위 10 개 단어가 측정 창에 균일하게 분포되어 있다는 것입니다. 그러나 측정 창을 충분히 작게 (예 : 1 시간) 유지하는 한 이는 유효한 가정 일 것입니다.
del

1
@erickson, 균일 한 배포가 요구 사항은 아니지만 이것이보다 현실적인 배포 (power-law, Zipf)에서 어떻게 작동하는지 궁금합니다. N 개의 고유 한 단어가 있다고 가정하고 용량 K의 빨강-검정 트리를 유지하면서 K가 가장 빈번한 용어로 끝날 것이라고 가정합니다. (N-K) 단어의 누적 빈도가 가장 빈번한 K 단어의 누적 빈도보다 크면 마지막 트리에 쓰레기가 포함됩니다. 동의하십니까?
Dimitris Andreou

19

이것은 제가 현재 진행하고있는 연구 프로젝트 중 하나입니다. 요구 사항은 거의 귀하의 요구 사항이며 문제를 해결하기 위해 멋진 알고리즘을 개발했습니다.

입력

입력은 끝없는 영어 단어 또는 구문 스트림입니다 (우리는이를라고 함 tokens).

출력

  1. 지금까지 본 토큰 중 상위 N 개를 출력합니다 (우리가 본 모든 토큰에서!)
  2. 지난 날 또는 지난주와 같이 기록 창에서 상위 N 개 토큰을 출력합니다.

이 연구의 응용은 트위터 나 페이스 북에서 화제가되는 주제 나 주제의 트렌드를 찾는 것입니다. 웹 사이트를 크롤링하는 크롤러가있어 시스템에 입력되는 단어 스트림을 생성합니다. 그러면 시스템은 전체적으로 또는 역사적으로 최고 빈도의 단어 또는 구문을 출력합니다. 지난 몇 주 동안 "월드컵"이라는 문구가 트위터에 여러 번 나타날 것이라고 상상해보십시오. "Paul the octopus"도 마찬가지입니다. :)

정수로 문자열

시스템에는 각 단어에 대한 정수 ID가 있습니다. 인터넷에는 거의 무한한 단어가 있지만 많은 단어가 쌓이면 새로운 단어를 찾을 가능성이 점점 낮아집니다. 이미 4 백만 개의 서로 다른 단어를 찾았고 각각에 대해 고유 한 ID를 할당했습니다. 이 전체 데이터 세트는 해시 테이블로 메모리에로드 될 수 있으며 대략 300MB 메모리를 사용합니다. (우리는 자체 해시 테이블을 구현했습니다. Java의 구현에는 엄청난 메모리 오버 헤드가 필요합니다.)

그러면 각 구문을 정수 배열로 식별 할 수 있습니다.

정수에 대한 정렬 및 비교가 문자열보다 훨씬 빠르기 때문에 이것은 중요 합니다.

데이터 보관

시스템은 모든 토큰에 대한 아카이브 데이터를 유지합니다. 기본적으로 (Token, Frequency). 그러나 데이터를 저장하는 테이블이 너무 커서 테이블을 물리적으로 분할해야합니다. 일단 파티션 구성표는 토큰의 ngram을 기반으로합니다. 토큰이 한 단어이면 1 그램입니다. 토큰이 두 단어 구문 인 경우 2 그램입니다. 그리고 이것은 계속됩니다. 대략 4 그램에 10 억 개의 레코드가 있으며 테이블 크기는 약 60GB입니다.

들어오는 스트림 처리

시스템은 메모리가 완전히 활용 될 때까지 들어오는 문장을 흡수합니다 (예, MemoryManager가 필요합니다). N 개의 문장을 가져와 메모리에 저장 한 후 시스템은 일시 중지하고 각 문장을 단어와 구문으로 토큰 화하기 시작합니다. 각 토큰 (단어 또는 구)이 계산됩니다.

매우 빈번한 토큰의 경우 항상 메모리에 보관됩니다. 빈도가 낮은 토큰의 경우 ID를 기준으로 정렬되고 (문자열을 정수 배열로 변환하는 것을 기억하십시오) 디스크 파일로 직렬화됩니다.

(그러나 문제에 대해서는 단어 만 계산하므로 모든 단어 빈도 맵을 메모리에만 저장할 수 있습니다. 신중하게 설계된 데이터 구조는 400 만 개의 다른 단어에 대해 300MB 메모리 만 사용합니다. 일부 힌트 : ASCII 문자를 사용하여 문자열을 나타냄), 이것은 매우 수용 가능합니다.

한편, 시스템에서 생성 된 디스크 파일을 찾은 다음 병합을 시작하면 활성화되는 다른 프로세스가 있습니다. 디스크 파일이 정렬되어 있으므로 병합은 병합 정렬과 같은 유사한 프로세스를 수행합니다. 임의의 디스크 탐색을 너무 많이 피하고 싶기 때문에 여기에서도 일부 디자인을주의해야합니다. 아이디어는 읽기 (병합 프로세스) / 쓰기 (시스템 출력)를 동시에 피하고 병합 프로세스가 다른 디스크에 쓰는 동안 하나의 디스크에서 읽도록하는 것입니다. 이것은 잠금을 구현하는 것과 유사합니다.

하루의 끝

하루가 끝나면 시스템에는 빈도가 메모리에 저장되는 빈도가 높은 토큰이 많이 있고 여러 디스크 파일에 저장되는 빈도가 낮은 토큰이 많이 있습니다 (각 파일이 정렬 됨).

시스템은 메모리 내 맵을 디스크 파일로 플러시합니다 (정렬). 이제 문제는 정렬 된 디스크 파일 집합을 병합하는 것입니다. 유사한 프로세스를 사용하여 마지막에 정렬 된 디스크 파일 하나를 얻습니다.

그런 다음 마지막 작업은 정렬 된 디스크 파일을 아카이브 데이터베이스에 병합하는 것입니다. 아카이브 데이터베이스의 크기에 따라 알고리즘이 충분히 크면 아래와 같이 작동합니다.

   for each record in sorted disk file
        update archive database by increasing frequency
        if rowcount == 0 then put the record into a list
   end for

   for each record in the list of having rowcount == 0
        insert into archive database
   end for

직감적으로 언젠가는 삽입 횟수가 점점 줄어들 것입니다. 점점 더 많은 작업이 업데이트에만 있습니다. 그리고이 업데이트는 인덱스에 의해 불이익을받지 않습니다.

이 전체 설명이 도움이되기를 바랍니다. :)


이해가 안 돼요. 단어의 정수 ID에서 어떤 종류의 의미있는 정렬 또는 비교를 수행 할 수 있습니까? 숫자는 임의적이지 않습니까?
Dimitris Andreou

또한 단어의 빈도를 세는 것은 Google의 MapReduce 논문 ( labs.google.com/papers/mapreduce.html ) 의 첫 번째 예이며 몇 줄로 확장 가능하게 해결합니다. 데이터를 Google app angine으로 이동하고 MapReduce ( code.google.com/p/appengine-mapreduce )를 수행 할 수도 있습니다.
Dimitris Andreou

@Dimitris Andreou : 정수 정렬은 문자열에서 더 빠를 것입니다. 두 정수를 비교하는 것이 두 문자열을 비교하는 것보다 빠르기 때문입니다.
SiLent SoNG 2010

@Dimitris Andreou : Google의 mapreduce는이 문제를 해결하기위한 훌륭한 분산 접근 방식입니다. 아! 링크를 제공해 주셔서 감사합니다. 예, 여러 기계를 사용하여 정렬하는 것이 좋습니다. 좋은 접근입니다.
SiLent SoNG 2010

@Dimitris Andreou : 지금까지는 단일 기계 정렬 방식 만 고려했습니다. 배포를 정렬하는 데 좋은 아이디어입니다.
SiLent SoNG 2010

4

이진 검색 트리 와 결합 된 해시 테이블을 사용할 수 있습니다 . 각 검색어가 검색된 횟수를 알려주 는 사전을 구현하십시오 .<search term, count>

분명히 상위 10 개를 얻기 위해 매시간 전체 해시 테이블을 반복하는 것은 매우 나쁩니다. 그러나 이것은 우리가 말하는 구글이기 때문에 상위 10 개가 모두 10,000 개 이상의 히트를 얻을 것이라고 가정 할 수 있습니다 (아마도 훨씬 더 많은 수일 것입니다). 따라서 검색어 수가 10,000 개를 초과 할 때마다 BST에 삽입하십시오. 그런 다음 매시간 BST에서 처음 10 개만 가져 오면 비교적 적은 항목이 포함됩니다.

이것은 역대 최고 10의 문제를 해결합니다.


정말 까다로운 부분은 월별 보고서에서 한 용어가 다른 용어의 자리를 차지하는 것을 다루는 것입니다 (예를 들어, "스택 오버플로"는 지난 2 개월 동안 5 만 건의 히트를 기록 할 수 있지만 지난 달에는 10,000 건만, "amazon"은 40 건일 수 있습니다.) 지난 2 개월 동안은 000, 지난달에는 3 만. 월별 보고서에서 "아마존"이 "스택 오버플로"앞에 오기를 원합니다.) 이를 위해 모든 주요 검색어 (전체 검색 횟수 10,000 개 이상)에 대해 해당 검색어가 매일 몇 번 검색되었는지 알려주는 30 일 목록을 저장합니다. 이 목록은 FIFO 대기열처럼 작동합니다. 첫 번째 날을 제거하고 매일 (또는 매 시간마다 새 항목을 삽입하지만, 더 많은 정보를 저장해야 할 수 있으며 이는 더 많은 메모리 / 공간을 의미합니다. 메모리가 문제가되지 않는 경우 수행하십시오. 그렇지 않으면 "근사치"로 이동합니다.

좋은 출발 인 것 같습니다. 그런 다음 10,000 개를 초과하는 히트가 있지만 오랫동안 많이 사용하지 않은 용어 및 이와 유사한 항목을 정리하는 것에 대해 걱정할 수 있습니다.


3

사례 i)

모든 검색어에 대한 해시 테이블과 해시 테이블과 별도로 정렬 된 상위 10 개 목록을 유지합니다. 검색이 발생할 때마다 해시 테이블에서 적절한 항목을 증가시키고 해당 항목이 이제 상위 10 개 목록의 10 번째 항목으로 전환되어야하는지 확인하십시오.

상위 10 개 목록에 대한 O (1) 조회 및 해시 테이블에 대한 최대 O (log (n)) 삽입 (자체 균형 이진 트리에 의해 관리되는 충돌 가정).

사례 ii) 거대한 해시 테이블과 작은 목록을 유지하는 대신 해시 테이블과 모든 항목의 정렬 된 목록을 유지합니다. 검색이 이루어질 때마다 해당 용어가 해시 테이블에서 증가하고 정렬 된 목록에서 해당 용어가 다음 용어로 전환되어야하는지 여부를 확인할 수 있습니다. 자체 균형 이진 트리는이를 위해 잘 작동 할 수 있습니다. 우리는 또한 빠르게 쿼리 할 수 ​​있어야하기 때문입니다 (나중에 자세히 설명).

또한 FIFO 목록 (대기열) 형식의 '시간'목록도 유지합니다. 각 '시간'요소에는 특정 시간 내에 수행 된 모든 검색 목록이 포함됩니다. 예를 들어 영업 시간 목록은 다음과 같습니다.

Time: 0 hours
      -Search Terms:
          -free stuff: 56
          -funny pics: 321
          -stackoverflow: 1234
Time: 1 hour
      -Search Terms:
          -ebay: 12
          -funny pics: 1
          -stackoverflow: 522
          -BP sucks: 92

그런 다음 매시간 : 목록의 길이가 720 시간 이상인 경우 (즉, 30 일 동안의 시간) 목록의 첫 번째 요소를보고 각 검색어에 대해 해시 테이블의 해당 요소를 적절한 양만큼 줄입니다. . 그 후 목록에서 첫 번째 시간 요소를 삭제하십시오.

721 시라고 가정 해 보겠습니다. 목록 (위)의 첫 번째 시간을 볼 준비가되었습니다. 해시 테이블에서 무료 항목을 56, 재미있는 사진을 321 등으로 줄인 다음 다시 볼 필요가 없기 때문에 목록에서 0 시간을 완전히 제거합니다.

빠른 쿼리를 허용하는 모든 용어의 정렬 된 목록을 유지하는 이유는 720 시간 전의 검색어를 살펴보면 매시간마다 상위 10 개 목록이 정렬되어 있는지 확인해야하기 때문입니다. 예를 들어 해시 테이블에서 'free stuff'를 56만큼 줄이면 이제 목록에서 어디에 속하는지 확인합니다. 자체 균형 이진 트리이기 때문에 모든 것이 O (log (n)) 시간 내에 잘 수행 될 수 있습니다.


편집 : 공간에 대한 정확도 희생 ...

두 번째 목록에서와 같이 첫 번째 목록에 큰 목록을 구현하는 것도 유용 할 수 있습니다. 그런 다음 두 경우 모두에 다음 공간 최적화를 적용 할 수 있습니다. 크론 작업을 실행 하여 목록에서 상위 x 개 항목을 제외하고 모두 제거 합니다. 이렇게하면 공간 요구 사항이 줄어들고 결과적으로 목록에 대한 쿼리가 더 빨라집니다. 물론 대략적인 결과가 나오 겠지만 이것은 허용됩니다. x 는 사용 가능한 메모리를 기반으로 애플리케이션을 배포하기 전에 계산할 수 있으며 더 많은 메모리를 사용할 수있게되면 동적으로 조정할 수 있습니다.


2

거친 생각 ...

역대 최고 10 위

  • 각 용어에 대한 개수가 저장된 해시 컬렉션 사용 (용어 삭제 등)
  • 진행중인 상위 10 개 항목을 포함하는 정렬 된 배열, 용어 개수가 배열의 최소 개수보다 같거나 클 때마다이 배열에 추가되는 용어 / 개수

매월 업데이트되는 월별 상위 10 개 :

  • 시작 모듈로 744 (한 달 동안의 시간) 이후 경과 된 시간에 색인 된 배열을 사용합니다.이 배열 항목은이 시간 슬롯 동안 발생한 각 용어에 대한 개수가 저장되는 해시 수집으로 구성됩니다. 시간 슬롯 카운터가 변경 될 때마다 항목이 재설정됩니다.
  • 시간 슬롯에 인덱싱 된 배열의 통계는 현재 시간 슬롯 카운터가 변경 될 때마다 (최대 한 시간에 한 번) 시간 슬롯에 인덱싱 된이 배열의 내용을 복사하고 병합하여 수집해야합니다.

어 ... 말이 되나? 실생활 에서처럼 생각하지 않았습니다.

아 예, 언급하는 것을 잊었습니다. 월별 통계에 필요한 시간별 "복사 / 평 평화"는 실제로 상위 10 위에 사용 된 동일한 코드를 재사용 할 수 있습니다. 좋은 부작용입니다.


2

정확한 솔루션

첫째, 정확한 결과를 보장하지만 많은 메모리 (큰 맵)가 필요한 솔루션입니다.

"모든 시간"변형

쿼리를 키로, 개수를 값으로 사용하여 해시 맵을 유지합니다. 또한 지금까지 가장 빈번한 쿼리 10 개와 가장 빈번한 10 번째 횟수 (임계 값)의 목록을 유지합니다.

쿼리 스트림을 읽을 때 지속적으로지도를 업데이트합니다. 개수가 현재 임계 값을 초과 할 때마다 다음을 수행하십시오. "Top 10"목록에서 10 번째 쿼리를 제거하고 방금 업데이트 한 쿼리로 바꾸고 임계 값도 업데이트합니다.

"지난달"변형

동일한 "상위 10 개"목록을 유지하고 위와 동일한 방식으로 업데이트합니다. 또한 유사한 맵을 유지하되 이번에는 30 * 24 = 720 카운트 (시간당 하나씩)의 벡터를 값으로 저장합니다. 매 시간마다 모든 키에 대해 다음을 수행하십시오. 벡터에서 가장 오래된 카운터를 제거하고 끝에 새 카운터를 추가합니다 (0으로 초기화 됨). 벡터가 모두 0이면지도에서 키를 제거합니다. 또한 매시간 "Top 10"목록을 처음부터 계산해야합니다.

참고 : 예, 이번에는 1 개가 아닌 720 개의 정수를 저장하지만 키가 훨씬 적습니다 (모든 시간 변형은 정말 긴 꼬리를 가짐).

근사치

이러한 근사값이 올바른 솔루션을 보장하지는 않지만 메모리 소모가 적습니다.

  1. N 번째 쿼리를 모두 처리하고 나머지는 건너 뜁니다.
  2. (모든 시간 변형에만 해당) 맵에 최대 M 개의 키-값 쌍을 유지합니다 (M은 감당할 수있는 한 커야 함). 일종의 LRU 캐시입니다. 맵에없는 쿼리를 읽을 때마다 가장 최근에 사용 된 쿼리를 개수 1로 제거하고 현재 처리 된 쿼리로 바꿉니다.

근사치 1의 확률 적 접근 방식을 좋아합니다.하지만 근사치 2 (LRU 캐시)를 사용하면 처음에 인기가 많지 않은 용어가 나중에 인기를 얻게되면 어떻게 될까요? 개수가 매우 적기 때문에 추가 할 때마다 버려지지 않습니까?
del

@del 맞습니다. 두 번째 근사치는 특정 쿼리 스트림에서만 작동합니다. 덜 신뢰할 수 있지만 동시에 리소스가 덜 필요합니다. 참고 : 두 근사치를 결합 할 수도 있습니다.
Bolo

2

지난달 상위 10 개 검색어

같은 메모리 효율적인 인덱스 / 데이터 구조를 이용하여 단단히 포장 시도 (위키의 항목에서 시도 용어의 수 -) 약 메모리와 N 사이에 관계를 정의한다.

필요한 메모리를 사용할 수있는 경우 ( 가정 1 ) 정확한 월별 통계를 유지하고 매월 모든 시간 통계로 집계 할 수 있습니다.

또한 여기에는 '지난 달'을 고정 기간으로 해석하는 가정이 있습니다. 그러나 월별 윈도우가 슬라이딩하더라도 위의 절차는 원칙을 보여줍니다 (슬라이드는 주어진 크기의 고정 윈도우로 근사 할 수 있음).

이것은 일부 통계가 '항상'으로 계산된다는 점을 제외하고 라운드 로빈 데이터베이스 를 상기시킵니다 (모든 데이터가 유지되는 것은 아닙니다. 주어진 작업에서 손실되는 세부 사항은 오류를 일으킬 수있는 저 빈도 항목에 대한 정보입니다.

가정 1

한 달 동안 완벽한 스탯을 유지할 수 없다면 완벽한 스탯을 유지할 수있는 특정 기간 P를 찾을 수 있어야합니다. 예를 들어, 우리가 n 번째 달에 들어가는 어떤 기간 P에 대한 완벽한 통계를 가지고 있다고 가정합니다.
완벽한 통계는 기능을 정의합니다 f(search_term) -> search_term_occurance.

모든 n완벽한 통계 테이블을 메모리에 보관할 수 있다면 월별 통계 슬라이딩은 다음과 같이 계산할 수 있습니다.

  • 최신 기간에 대한 통계 추가
  • 가장 오래된 기간의 통계를 제거합니다 (따라서 n완벽한 통계 테이블 을 유지해야 함).

그러나 집계 수준 (월간)에서 상위 10 개만 유지하면 고정 기간의 전체 통계에서 많은 데이터를 삭제할 수 있습니다. 이것은 이미 고정 된 (기간 P에 대한 완벽한 통계 테이블에 대한 상한선 가정) 메모리 요구 사항을 수정 한 작업 절차를 제공합니다.

위 절차의 문제점은 슬라이딩 윈도우에 대해 상위 10 개 용어에 대한 정보 만 유지하면 (모든 시간에 대해 유사하게) 통계가 한 기간 동안 최고조에 달하는 검색어에 대해서는 정확하지만 표시되지 않을 수 있다는 것입니다. 시간이 지남에 따라 지속적으로 유입되는 검색어에 대한 통계입니다.

이는 상위 10 개 용어 (예 : 상위 100 개 용어)에 대한 정보를 유지하여 상쇄 할 수 있으며 상위 10 개 용어가 정확하기를 바랍니다.

추가 분석은 항목이 통계의 일부가되는 데 필요한 최소 발생 횟수 (최대 오류와 관련됨)와 관련 될 수 있다고 생각합니다.

(어떤 항목이 통계의 일부가되어야 하는지를 결정할 때 추세를 모니터링하고 추적 할 수도 있습니다. 예를 들어 각 기간에 대한 각 기간 P의 발생에 대한 선형 외삽을 통해 해당 용어가 한두 달 안에 중요해질 것임을 알 수 있습니다. 이미 추적을 시작할 수 있습니다. 유사한 원칙이 추적 된 풀에서 검색어를 제거하는 데 적용됩니다.)

위의 최악의 경우는 거의 똑같이 빈번한 용어가 많고 항상 변경되는 경우입니다 (예 : 100 개 용어 만 추적하는 경우 상위 150 개 용어가 똑같이 자주 발생하지만 상위 50 개 용어는 첫 달에 더 자주 발생하고 종종 나중에 통계가 올바르게 유지되지 않을 수 있습니다.)

또한 메모리 크기가 고정되지 않은 다른 접근 방식이있을 수 있습니다 (엄밀히 말해서 위의 경우도 마찬가지 임).이 방법은 발생 / 기간 (일, 월, 연도, 전체 시간) 측면에서 최소 중요성을 정의하여 통계. 이는 집계 중에 각 통계에서 최대 오류를 보장 할 수 있습니다 (라운드 로빈 다시 참조).


2

무엇의 적응에 대한 "시계 페이지 교체 알고리즘" (또한 "두 번째 기회"라고도 함)? 검색 요청이 균등하게 분산되면 매우 잘 작동한다고 상상할 수 있습니다 (즉, 대부분의 검색어가 연속으로 5mio 번 표시되지 않고 다시는 표시되지 않음을 의미합니다).

다음은 알고리즘의 시각적 표현입니다. 시계 페이지 교체 알고리즘


0

검색 용어의 수를 거대한 해시 테이블에 저장하십시오. 여기서 각각의 새로운 검색으로 인해 특정 요소가 1 씩 증가합니다. 상위 20 개 정도의 검색어를 추적하십시오. 11 위의 요소가 증가 할 때 위치를 # 10 *으로 바꿔야하는지 확인합니다 (상위 10 위를 정렬 할 필요는 없습니다. 관심있는 것은 10 위와 11 위를 구분하는 것입니다).

* 새로운 검색어가 11 위에 있는지 확인하기 위해 유사한 검사를 수행해야하므로이 알고리즘은 다른 검색어에도 적용됩니다.


해시 테이블의 크기를 제한하고 싶을 것입니다. 고유 한 검색 스트림을 얻으면 어떨까요? 정기적으로 검색되지만 드물게 검색되는 용어를 알아 차리지 않도록 방지해야합니다. 시간이 지남에 따라 상위 검색어가 될 수 있습니다. 특히 다른 모든 검색어가 "현재 이벤트"인 경우, 즉 지금은 많이 검색되지만 다음 주에는 많이 검색되지 않습니다. 실제로 이와 같은 고려 사항은 사용자가 원하는 근사치 일 수 있습니다. 그렇게하면 알고리즘이 더 많은 시간 / 공간을 비싸게 만들기 때문에 우리는 이런 종류의 것들을 포착하지 않을 것입니다.
cape1232 2010

나는 Google이 모든 것을 가지고 있다고 확신합니다. 일부는 정적으로 유지되지 않고 필요에 따라 계산됩니다.
Ether

0

때때로 가장 좋은 대답은 "모름"입니다.

더 깊이 찔러 볼게요. 내 첫 번째 본능은 결과를 Q에 공급하는 것입니다. 프로세스는 Q에 들어오는 항목을 지속적으로 처리합니다. 프로세스는

기간-> 개수

Q 항목이 처리 될 때마다 검색어를 조회하고 개수를 증가시키기 만하면됩니다.

동시에 맵의 상위 10 개 항목에 대한 참조 목록을 유지합니다.

현재 구현 된 항목의 경우 해당 개수가 상위 10 개 항목 중 가장 작은 항목 개수보다 큰지 확인합니다 (목록에 아직없는 경우). 그렇다면 가장 작은 항목을 항목으로 바꿉니다.

그게 효과가있을 것 같아요. 시간이 많이 걸리는 작업은 없습니다. 카운트 맵의 크기를 관리하는 방법을 찾아야합니다. 하지만 인터뷰 답변으로는 충분합니다.

그들은 당신이 생각할 수 있는지보고 싶어하는 해결책을 기대하지 않습니다. 당신은 그때 거기에 해결책을 쓸 필요가 없습니다 ....


12
데이터 구조는이라고 queue, Q:) 편지입니다.
IVlad 2010

3
제가 인터뷰를했다면 "모르겠어 <그만>"이 확실히 최선의 답이 아닐 텐데요. 발을 생각하십시오. 모른다면 알아 내거나 적어도 시도해보세요.
Stephen

인터뷰에서 7 페이지 이력서에서 최대 절전 모드를 가진 사람을 5 번보고 ORM이 무엇인지 말하지 못하면 즉시 인터뷰를 종료합니다. 이드는 오히려 이력서에 적지 않고 그냥 "모르겠어요"라고 말해요. 아무도 모든 것을 알지 못합니다. @IVIad, 나는 ... 나는 C 개발자를 한 척하고 비트를 저장하려고했던)
hvgotcodes

0

한 가지 방법은 모든 검색에 대해 해당 검색어와 타임 스탬프를 저장하는 것입니다. 이렇게하면 특정 기간 동안 상위 10 개를 찾는 것은 단순히 주어진 기간 내의 모든 검색어를 비교하는 문제입니다.

알고리즘은 간단하지만 단점은 더 많은 메모리와 시간 소비입니다.


0

노드가 10 개인 Splay Tree 를 사용하는 것은 어떻 습니까? 트리에 포함되지 않은 값 (검색어)에 액세스하려고 할 때마다 리프를 버리고 대신 값을 삽입하고 액세스하십시오.

이것이면의 아이디어는 다른 대답 과 동일 합니다. 검색어가 균등 / 정기적으로 액세스된다는 가정하에이 솔루션은 매우 잘 수행되어야합니다.

편집하다

곧 다시 액세스 할 수있는 노드를 삭제하지 않기 위해 트리에 더 많은 검색어를 저장할 수도 있습니다 (다른 답변에서 제안한 솔루션에도 동일하게 적용됨). 저장하는 값이 많을수록 결과가 더 좋습니다.


0

내가 옳은지 이해하지 못한다면 Dunno. 내 솔루션은 힙을 사용하고 있습니다. 상위 10 개의 검색 항목 때문에 크기가 10 인 힙을 빌드합니다. 그런 다음이 힙을 새 검색으로 업데이트합니다. 새 검색 빈도가 힙 (최대 힙) 상단보다 크면 업데이트합니다. 주파수가 가장 작은 것을 버리십시오.

그러나 특정 검색의 빈도를 계산하는 방법은 다른 항목에 포함됩니다. 모두가 말했듯이 데이터 스트림 알고리즘 ....


0

cm-sketch를 사용하여 처음부터 모든 검색의 수를 저장하고, 상위 10 개에 대해 최소 힙 크기를 10으로 유지하십시오. 월별 결과를 위해 30 cm-sketch / hash-table 및 최소 힙을 유지하십시오. 지난 30, 29 .., 1 일부터 계산 및 업데이트. 하루가 지나면 마지막을 지우고 1 일로 사용합니다. 시간당 결과와 동일하게 60 개의 해시 테이블과 최소 힙을 유지하고 지난 60, 59, ... 1 분 동안 계산을 시작합니다. 1 분이 지나면 마지막을 지우고 1 분으로 사용합니다.

Montly 결과는 1 일 범위에서 정확하고, 시간별 결과는 1 분 범위에서 정확합니다.


0

고정 된 양의 메모리와 '무한한'(매우 큰 것을 생각해보십시오) 토큰 스트림이있을 때 문제는 보편적으로 해결 될 수 없습니다.

대략적인 설명 ...

그 이유를 알아 보려면 입력 스트림에서 N 토큰마다 특정 토큰 (즉, 단어) T가있는 토큰 스트림을 고려하십시오.

또한 메모리가 최대 M 개의 토큰에 대한 참조 (단어 ID 및 개수)를 보유 할 수 있다고 가정합니다.

이러한 조건으로 N이 충분히 크면 스트림이 T 사이에 다른 M 토큰을 포함 할 수있는 경우 토큰 T가 감지되지 않는 입력 스트림을 구성 할 수 있습니다.

이것은 top-N 알고리즘 세부 사항과는 무관합니다. 한계 M에만 의존합니다.

이것이 사실 인 이유를 확인하려면 두 개의 동일한 토큰 그룹으로 구성된 수신 스트림을 고려하십시오.

T a1 a2 a3 ... a-M T b1 b2 b3 ... b-M ...

여기서 a와 b는 모두 T와 같지 않은 유효한 토큰입니다.

이 스트림에서 T는 각 ai 및 bi에 대해 두 번 나타납니다. 그러나 시스템에서 플러시 될 정도로 거의 나타나지 않습니다.

빈 메모리로 시작하여 첫 번째 토큰 (T)은 메모리의 슬롯 (M으로 묶인)을 차지합니다. 그런 다음 a1은 슬롯을 소비하고 M이 소진되면 a- (M-1)까지 계속됩니다.

aM이 도착하면 알고리즘은 하나의 기호를 드롭해야하므로 T가되도록합니다. 다음 기호는 b-1이되어 a-1이 플러시되도록합니다.

따라서 T는 실제 카운트를 구축 할만큼 오랫동안 메모리에 상주하지 않습니다. 요컨대, 모든 알고리즘은 충분히 낮은 로컬 주파수이지만 높은 글로벌 주파수 (스트림 길이에 걸쳐)의 토큰을 놓칠 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.