বেন্টলির কোডিং চ্যালেঞ্জ: কে সর্বাধিক ঘন শব্দ


18

এটি সম্ভবত ক্লাসিক কোডিং চ্যালেঞ্জগুলির মধ্যে একটি যা 1986 সালে কিছুটা অনুরণন পেয়েছিল, যখন কলামিস্ট জন বেন্টলে ডোনাল্ড নথকে এমন একটি প্রোগ্রাম লিখতে বলেছিলেন যা কোনও ফাইলে সবচেয়ে ঘন ঘন শব্দ খুঁজে পায়। নুথ তার সাক্ষরিত প্রোগ্রামিং কৌশলটি চিত্রিত করার জন্য 8-পৃষ্ঠাগুলি দীর্ঘ প্রোগ্রামে হ্যাশ চেষ্টা করে একটি দ্রুত সমাধান প্রয়োগ করেছিলেন। বেল ল্যাবসের ডগলাস ম্যাকিল্রোই নথের সমাধানটিকে বাইবেলের একটি সম্পূর্ণ পাঠ্য প্রক্রিয়া করতে সক্ষম না হিসাবে সমালোচনা করেছিলেন এবং এক-লাইনারের সাথে জবাব দিয়েছিলেন, এটি ততটা দ্রুত নয়, তবে কাজটি সম্পন্ন করে:

tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed 10q

1987 সালে, আরও একটি সমাধান সহ একটি ফলোআপ নিবন্ধ প্রকাশিত হয়েছিল, এবার প্রিন্সটনের একজন অধ্যাপক। কিন্তু এটি এমনকি একটি বাইবেলের ফলাফলও ফিরে আসতে পারে না!

সমস্যার বিবরণ

মূল সমস্যার বর্ণনা:

একটি পাঠ্য ফাইল এবং একটি পূর্ণসংখ্যার কে দেওয়া হয়েছে, আপনি ফাইলের সবচেয়ে সাধারণ শব্দগুলি (এবং তাদের সংখ্যার সংখ্যা) হ্রাসকারী ফ্রিকোয়েন্সিতে মুদ্রণ করতে হবে।

অতিরিক্ত সমস্যার ব্যাখ্যা:

  • নথ একটি শব্দটিকে লাতিন বর্ণের একটি স্ট্রিং হিসাবে সংজ্ঞায়িত করেছিলেন: [A-Za-z]+
  • অন্য সমস্ত চরিত্র উপেক্ষা করা হয়
  • বড় হাতের এবং ছোট হাতের অক্ষর সমতুল্য হিসাবে বিবেচিত হয় ( WoRd== word)
  • ফাইলের আকার বা শব্দের দৈর্ঘ্যের কোনও সীমা নেই
  • একটানা শব্দের মধ্যে দূরত্ব নির্বিচারে বড় হতে পারে
  • দ্রুততম প্রোগ্রামটি হ'ল সর্বনিম্ন মোট সিপিইউ সময় ব্যবহার করে (মাল্টিথ্রেডিং সম্ভবত সাহায্য করবে না)

নমুনা পরীক্ষার কেস

পরীক্ষা 1: জেমস জয়েসের ইউলিসেস 64৪ বার (MB৯ এমবি ফাইল) জড়িত।

  • প্রকল্প গুটেনবার্গ থেকে ইউলিসেস ডাউনলোড করুন :wget http://www.gutenberg.org/files/4300/4300-0.txt
  • এটি 64 বার সংঘবদ্ধ করুন: for i in {1..64}; do cat 4300-0.txt >> ulysses64; done
  • সর্বাধিক ঘন শব্দটি 968832 উপস্থিতি সহ "" "।

পরীক্ষা 2: বিশেষভাবে উত্পন্ন এলোমেলো পাঠ্য giganovel(প্রায় 1 গিগাবাইট)।

  • পাইথন 3 জেনারেটর স্ক্রিপ্ট এখানে
  • পাঠ্যে 148391 স্বতন্ত্র শব্দ রয়েছে যা প্রাকৃতিক ভাষার সাথে একইভাবে প্রদর্শিত হয়।
  • সর্বাধিক ঘন শব্দ: "ই" (11309 উপস্থিতি) এবং "আইহিট" (11290 উপস্থিতি)।

সাধারণতা পরীক্ষা: নির্বিচারে বৃহত শূন্যতা সহ বড় শব্দ।

রেফারেন্স বাস্তবায়ন

এই সমস্যার জন্য রোসটা কোড সন্ধান করার পরে এবং বুঝতে পেরেছি যে অনেকগুলি বাস্তবায়ন অবিশ্বাস্যরূপে ধীর (শেল স্ক্রিপ্টের চেয়ে ধীর!), আমি এখানে কয়েকটি ভাল বাস্তবায়ন পরীক্ষা করেছি । নীচে ulysses64সময় জটিলতার সাথে একত্রে সম্পাদনা করা হল:

                                     ulysses64      Time complexity
C++ (prefix trie + heap)             4.145          O((N + k) log k)
Python (Counter)                     10.547         O(N + k log Q)
AWK + sort                           20.606         O(N + Q log Q)
McIlroy (tr + sort + uniq)           43.554         O(N log N)

আপনি কি এটা মারতে পারেন?

পরীক্ষামূলক

স্ট্যান্ডার্ড ইউনিক্স timeকমান্ড ("ব্যবহারকারী" সময়) দিয়ে ম্যাকবুক প্রো 2017 13 ব্যবহার করে পারফরম্যান্সটি মূল্যায়ন করা হবে possible

এখন পর্যন্ত র‌্যাঙ্কিং

রেফারেন্স প্রোগ্রাম সহ সময়:

                                              k=10                  k=100K
                                     ulysses64      giganovel      giganovel
C++ (trie) by ShreevatsaR            0.671          4.227          4.276
C (trie + bins) by Moogie            0.704          9.568          9.459
C (trie + list) by Moogie            0.767          6.051          82.306
C++ (hash trie) by ShreevatsaR       0.788          5.283          5.390
C (trie + sorted list) by Moogie     0.804          7.076          x
Rust (trie) by Anders Kaseorg        0.842          6.932          7.503
J by miles                           1.273          22.365         22.637
C# (trie) by recursive               3.722          25.378         24.771
C++ (trie + heap)                    4.145          42.631         72.138
APL (Dyalog Unicode) by Adám         7.680          x              x
Python (dict) by movatica            9.387          99.118         100.859
Python (Counter)                     10.547         102.822        103.930
Ruby (tally) by daniero              15.139         171.095        171.551
AWK + sort                           20.606         213.366        222.782
McIlroy (tr + sort + uniq)           43.554         715.602        750.420

সম্মিলিত র‌্যাঙ্কিং * (%, সেরা সম্ভাব্য স্কোর - 300):

#     Program                         Score  Generality
 1  C++ (trie) by ShreevatsaR           300     Yes
 2  C++ (hash trie) by ShreevatsaR      368      x
 3  Rust (trie) by Anders Kaseorg       465     Yes
 4  C (trie + bins) by Moogie           552      x
 5  J by miles                         1248     Yes
 6  C# (trie) by recursive             1734      x
 7  C (trie + list) by Moogie          2182      x
 8  C++ (trie + heap)                  3313      x
 9  Python (dict) by movatica          6103     Yes
10  Python (Counter)                   6435     Yes
11  Ruby (tally) by daniero           10316     Yes
12  AWK + sort                        13329     Yes
13  McIlroy (tr + sort + uniq)        40970     Yes

* তিনটি পরীক্ষার প্রত্যেকের সেরা প্রোগ্রামের তুলনায় সময়ের পারফরম্যান্সের যোগফল।

এখন পর্যন্ত সেরা প্রোগ্রাম: এখানে (দ্বিতীয় সমাধান)


স্কোরটি কি ইউলিসিসের সময় মাত্র? এটি নিহিত বলে মনে হয় তবে এটি স্পষ্টভাবে বলা হয় না
পোস্ট রক গার্ফ হান্টার

@ শ্রুতচিলিজম ওজাইক, আপাতত, হ্যাঁ। তবে আপনার প্রথম পরীক্ষার ক্ষেত্রে নির্ভর করা উচিত নয় কারণ বড় টেস্টের কেস অনুসরণ করতে পারে। ইউলিসেস 64৪ এর পুনরাবৃত্তি হওয়ার সুস্পষ্ট অসুবিধা রয়েছে: ফাইলের ১/6464 এর পরে কোনও নতুন শব্দ উপস্থিত হয় না। সুতরাং, এটি খুব ভাল পরীক্ষার কেস নয়, তবে বিতরণ করা (বা পুনরুত্পাদন) করা সহজ।
অ্যান্ড্রি মাকুখা

3
আমি বোঝাতে চাইছি আপনি যে গোপনীয় পরীক্ষাগুলির কথা বলছিলেন তা আগে। আপনি যদি এখনই হ্যাশগুলি পোস্ট করেন তবে আপনি যখন আসল পাঠ্য প্রকাশ করেন আমরা নিশ্চিত করতে পারি যে এটি উত্তরের সাথে ন্যায্য এবং আপনি রাজা তৈরি করছেন না। যদিও আমি মনে করি ইউলিসিসের জন্য হ্যাশটি কিছুটা কার্যকর।
পোস্ট রক গারফ হান্টার

1
@ স্প এটি আমার বোধগম্য: উদাহরণস্বরূপ দুটি শব্দ ই এবং জি হবে
মুগি

1
@ অ্যান্ড্রি মাকুখা আহ, ধন্যবাদ সেগুলি কেবল বাগ ছিল; আমি তাদের ঠিক করেছি।
অ্যান্ডারস কাসের্গ

উত্তর:


5

[সি]

নিম্নলিখিতটি আমার ২.৮ গিগাহার্টজ জিয়ন ডাব্লু ৩৩৩০ তে টেস্ট 1 এর জন্য 1.6 সেকেন্ডের নীচে চলেছে। উইন্ডোজ on-এ MinGW.org GCC-6.3.0-1 ব্যবহার করে নির্মিত:

এটি ইনপুট হিসাবে দুটি আর্গুমেন্ট লাগে (পাঠ্য ফাইলের পথে এবং তালিকার জন্য বেশিরভাগ ঘন শব্দের জন্য k সংখ্যার জন্য)

এটি কেবল শব্দের অক্ষরে একটি গাছের শাখা তৈরি করে, তারপরে পাতার অক্ষরে এটি একটি পাল্টা বাড়ায়। তারপরে বর্তমান পাতার কাউন্টারটি সর্বাধিক ঘন ঘন শব্দের তালিকার ক্ষুদ্রতম ঘন ঘন শব্দের চেয়ে বড় কিনা তা পরীক্ষা করে দেখুন। (তালিকার আকার হ'ল কমান্ড লাইন আর্গুমেন্টের মাধ্যমে নির্ধারিত সংখ্যা) যদি তা হয় তবে পাতার বর্ণ দ্বারা প্রতিনিধিত্ব করা শব্দটিকে সর্বাধিক ঘন ঘন একটি হিসাবে প্রচার করুন। আর কোনও অক্ষর না পড়া পর্যন্ত এই সমস্ত পুনরাবৃত্তি হয় After যার পরে সর্বাধিক ঘন শব্দের তালিকাটি প্রায়শই ঘন ঘন শব্দের তালিকা থেকে সর্বাধিক ঘন শব্দের সন্ধানের জন্য অকার্যকর পুনরাবৃত্ত অনুসন্ধানের মাধ্যমে আউটপুট হয়।

এটি বর্তমানে প্রক্রিয়াজাতকরণের সময় আউটপুট দিতে ডিফল্ট হয় তবে অন্যান্য সাবমিশনের সাথে সঙ্গতি রাখার উদ্দেশ্যে উত্স কোডে টিমিং সংজ্ঞাটি অক্ষম করে।

এছাড়াও, আমি এটি একটি ওয়ার্ক কম্পিউটার থেকে জমা দিয়েছি এবং টেস্ট 2 পাঠ্য ডাউনলোড করতে সক্ষম হইনি। এটি পরিবর্তন ছাড়াই এই পরীক্ষা 2 এর সাথে কাজ করা উচিত, তবে MAX_LETTER_INSTANCES মান বাড়ানোর প্রয়োজন হতে পারে।

// comment out TIMING if using external program timing mechanism
#define TIMING 1

// may need to increase if the source text has many unique words
#define MAX_LETTER_INSTANCES 1000000

// increase this if needing to output more top frequent words
#define MAX_TOP_FREQUENT_WORDS 1000

#define false 0
#define true 1
#define null 0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef TIMING
#include <sys/time.h>
#endif

struct Letter
{
    char mostFrequentWord;
    struct Letter* parent;
    char asciiCode;
    unsigned int count;
    struct Letter* nextLetters[26];
};
typedef struct Letter Letter;

int main(int argc, char *argv[]) 
{
#ifdef TIMING
    struct timeval tv1, tv2;
    gettimeofday(&tv1, null);
#endif

    int k;
    if (argc !=3 || (k = atoi(argv[2])) <= 0 || k> MAX_TOP_FREQUENT_WORDS)
    {
        printf("Usage:\n");
        printf("      WordCount <input file path> <number of most frequent words to find>\n");
        printf("NOTE: upto %d most frequent words can be requested\n\n",MAX_TOP_FREQUENT_WORDS);
        return -1;
    }

    long  file_size;
    long dataLength;
    char* data;

    // read in file contents
    FILE *fptr;
    size_t read_s = 0;  
    fptr = fopen(argv[1], "rb");
    fseek(fptr, 0L, SEEK_END);
    dataLength = ftell(fptr);
    rewind(fptr);
    data = (char*)malloc((dataLength));
    read_s = fread(data, 1, dataLength, fptr);
    if (fptr) fclose(fptr);

    unsigned int chr;
    unsigned int i;

    // working memory of letters
    Letter* letters = (Letter*) malloc(sizeof(Letter) * MAX_LETTER_INSTANCES);
    memset(&letters[0], 0, sizeof( Letter) * MAX_LETTER_INSTANCES);

    // the index of the next unused letter
    unsigned int letterMasterIndex=0;

    // pesudo letter representing the starting point of any word
    Letter* root = &letters[letterMasterIndex++];

    // the current letter in the word being processed
    Letter* currentLetter = root;
    root->mostFrequentWord = false;
    root->count = 0;

    // the next letter to be processed
    Letter* nextLetter = null;

    // store of the top most frequent words
    Letter* topWords[MAX_TOP_FREQUENT_WORDS];

    // initialise the top most frequent words
    for (i = 0; i<k; i++)
    {
        topWords[i]=root;
    }

    unsigned int lowestWordCount = 0;
    unsigned int lowestWordIndex = 0;
    unsigned int highestWordCount = 0;
    unsigned int highestWordIndex = 0;

    // main loop
    for (int j=0;j<dataLength;j++)
    {
        chr = data[j]|0x20; // convert to lower case

        // is a letter?
        if (chr > 96 && chr < 123)
        {
            chr-=97; // translate to be zero indexed
            nextLetter = currentLetter->nextLetters[chr];

            // this is a new letter at this word length, intialise the new letter
            if (nextLetter == null)
            {
                nextLetter = &letters[letterMasterIndex++];
                nextLetter->parent = currentLetter;
                nextLetter->asciiCode = chr;
                currentLetter->nextLetters[chr] = nextLetter;
            }

            currentLetter = nextLetter;
        }
        // not a letter so this means the current letter is the last letter of a word (if any letters)
        else if (currentLetter!=root)
        {

            // increment the count of the full word that this letter represents
            ++currentLetter->count;

            // ignore this word if already identified as a most frequent word
            if (!currentLetter->mostFrequentWord)
            {
                // update the list of most frequent words
                // by replacing the most infrequent top word if this word is more frequent
                if (currentLetter->count> lowestWordCount)
                {
                    currentLetter->mostFrequentWord = true;
                    topWords[lowestWordIndex]->mostFrequentWord = false;
                    topWords[lowestWordIndex] = currentLetter;
                    lowestWordCount = currentLetter->count;

                    // update the index and count of the next most infrequent top word
                    for (i=0;i<k; i++)
                    {
                        // if the topword  is root then it can immediately be replaced by this current word, otherwise test
                        // whether the top word is less than the lowest word count
                        if (topWords[i]==root || topWords[i]->count<lowestWordCount)
                        {
                            lowestWordCount = topWords[i]->count;
                            lowestWordIndex = i;
                        }
                    }
                }
            }

            // reset the letter path representing the word
            currentLetter = root;
        }
    }

    // print out the top frequent words and counts
    char string[256];
    char tmp[256];

    while (k > 0 )
    {
        highestWordCount = 0;
        string[0]=0;
        tmp[0]=0;

        // find next most frequent word
        for (i=0;i<k; i++)
        {
            if (topWords[i]->count>highestWordCount)
            {
                highestWordCount = topWords[i]->count;
                highestWordIndex = i;
            }
        }

        Letter* letter = topWords[highestWordIndex];

        // swap the end top word with the found word and decrement the number of top words
        topWords[highestWordIndex] = topWords[--k];

        if (highestWordCount > 0)
        {
            // construct string of letters to form the word
            while (letter != root)
            {
                memmove(&tmp[1],&string[0],255);
                tmp[0]=letter->asciiCode+97;
                memmove(&string[0],&tmp[0],255);
                letter=letter->parent;
            }

            printf("%u %s\n",highestWordCount,string);
        }
    }

    free( data );
    free( letters );

#ifdef TIMING   
    gettimeofday(&tv2, null);
    printf("\nTime Taken: %f seconds\n", (double) (tv2.tv_usec - tv1.tv_usec)/1000000 + (double) (tv2.tv_sec - tv1.tv_sec));
#endif
    return 0;
}

পরীক্ষা 1 এর জন্য এবং শীর্ষ 10 ঘন ঘন শব্দের জন্য এবং সময় সাধ্যের সাথে এটি মুদ্রণ করা উচিত:

 968832 the
 528960 of
 466432 and
 421184 a
 322624 to
 320512 in
 270528 he
 213120 his
 191808 i
 182144 s

 Time Taken: 1.549155 seconds

চিত্তাকর্ষক! মনে হয় তালিকার ব্যবহার এটি সবচেয়ে খারাপ ক্ষেত্রে ও (এনকে) করে, তাই এটি কে = 100,000 সহ জিগানোভেলের জন্য রেফারেন্স সি ++ প্রোগ্রামের চেয়ে ধীর হয়ে যায়। তবে কে << এন এর পক্ষে এটি স্পষ্ট বিজয়ী।
অ্যান্ড্রি মাকুখা

1
@ অ্যান্ড্রি মাকুখা ধন্যবাদ! আমি কিছুটা অবাক হয়েছিলাম যে এই জাতীয় বাস্তবায়নটি দুর্দান্ত গতি পেয়েছিল। আমি তালিকাটি বাছাই করে কে এর বৃহত্তর মানগুলির জন্য এটি আরও ভাল করে তুলতে পারি। (তালিকার ক্রমটি ধীরে ধীরে পরিবর্তিত হবে কারণ বাছাই খুব ব্যয়বহুল হওয়া উচিত নয়) তবে এটি জটিলতা যুক্ত করে এবং সম্ভবত কে এর নিম্ন মানের জন্য গতিকে প্রভাবিত করবে। পরীক্ষা
মোগি

হ্যাঁ, আমিও অবাক হয়েছি। এটি হতে পারে কারণ রেফারেন্স প্রোগ্রামটি প্রচুর ফাংশন কল ব্যবহার করে এবং সংকলক এটি সঠিকভাবে অনুকূল করতে ব্যর্থ হয়।
অ্যান্ড্রি মাকুখা

আর একটি কার্যকারিতা বেনিফিট সম্ভবত lettersঅ্যারের semistatic বরাদ্দ থেকে আসে , যখন রেফারেন্স বাস্তবায়ন বৃক্ষ নোডগুলি গতিশীলভাবে বরাদ্দ করে।
অ্যান্ড্রি মাকুখা

mmap-ing হওয়া উচিত দ্রুত (~ আমার লিনাক্স ল্যাপটপে 5%): #include<sys/mman.h>, <sys/stat.h>, <fcntl.h>, ফাইলের সাথে পড়া প্রতিস্থাপন int d=open(argv[1],0);struct stat s;fstat(d,&s);dataLength=s.st_size;data=mmap(0,dataLength,1,1,d,0);এবং মন্তব্য আউটfree(data);
ngn

4

মরিচা

আমার কম্পিউটারে, এটি মুগির সি "উপসর্গের গাছ + বিনগুলি" সি সমাধানের চেয়ে প্রায় 42% দ্রুত (10.64 গুলি বনাম 18.24 এস) গিগানোভেল 100000 চালায়। এছাড়াও এর কোনও পূর্বনির্ধারিত সীমা নেই (সি সমাধানের বিপরীতে যা শব্দের দৈর্ঘ্য, অনন্য শব্দের, পুনরাবৃত্ত শব্দ ইত্যাদির সীমা নির্ধারণ করে)।

src/main.rs

use memmap::MmapOptions;
use pdqselect::select_by_key;
use std::cmp::Reverse;
use std::default::Default;
use std::env::args;
use std::fs::File;
use std::io::{self, Write};
use typed_arena::Arena;

#[derive(Default)]
struct Trie<'a> {
    nodes: [Option<&'a mut Trie<'a>>; 26],
    count: u64,
}

fn main() -> io::Result<()> {
    // Parse arguments
    let mut args = args();
    args.next().unwrap();
    let filename = args.next().unwrap();
    let size = args.next().unwrap().parse().unwrap();

    // Open input
    let file = File::open(filename)?;
    let mmap = unsafe { MmapOptions::new().map(&file)? };

    // Build trie
    let arena = Arena::new();
    let mut num_words = 0;
    let mut root = Trie::default();
    {
        let mut node = &mut root;
        for byte in &mmap[..] {
            let letter = (byte | 32).wrapping_sub(b'a');
            if let Some(child) = node.nodes.get_mut(letter as usize) {
                node = child.get_or_insert_with(|| {
                    num_words += 1;
                    arena.alloc(Default::default())
                });
            } else {
                node.count += 1;
                node = &mut root;
            }
        }
        node.count += 1;
    }

    // Extract all counts
    let mut index = 0;
    let mut counts = Vec::with_capacity(num_words);
    let mut stack = vec![root.nodes.iter()];
    'a: while let Some(frame) = stack.last_mut() {
        while let Some(child) = frame.next() {
            if let Some(child) = child {
                if child.count != 0 {
                    counts.push((child.count, index));
                    index += 1;
                }
                stack.push(child.nodes.iter());
                continue 'a;
            }
        }
        stack.pop();
    }

    // Find frequent counts
    select_by_key(&mut counts, size, |&(count, _)| Reverse(count));
    // Or, in nightly Rust:
    //counts.partition_at_index_by_key(size, |&(count, _)| Reverse(count));

    // Extract frequent words
    let size = size.min(counts.len());
    counts[0..size].sort_by_key(|&(_, index)| index);
    let mut out = Vec::with_capacity(size);
    let mut it = counts[0..size].iter();
    if let Some(mut next) = it.next() {
        index = 0;
        stack.push(root.nodes.iter());
        let mut word = vec![b'a' - 1];
        'b: while let Some(frame) = stack.last_mut() {
            while let Some(child) = frame.next() {
                *word.last_mut().unwrap() += 1;
                if let Some(child) = child {
                    if child.count != 0 {
                        if index == next.1 {
                            out.push((word.to_vec(), next.0));
                            if let Some(next1) = it.next() {
                                next = next1;
                            } else {
                                break 'b;
                            }
                        }
                        index += 1;
                    }
                    stack.push(child.nodes.iter());
                    word.push(b'a' - 1);
                    continue 'b;
                }
            }
            stack.pop();
            word.pop();
        }
    }
    out.sort_by_key(|&(_, count)| Reverse(count));

    // Print results
    let stdout = io::stdout();
    let mut stdout = io::BufWriter::new(stdout.lock());
    for (word, count) in out {
        stdout.write_all(&word)?;
        writeln!(stdout, " {}", count)?;
    }

    Ok(())
}

Cargo.toml

[package]
name = "frequent"
version = "0.1.0"
authors = ["Anders Kaseorg <andersk@mit.edu>"]
edition = "2018"

[dependencies]
memmap = "0.7.0"
typed-arena = "1.4.1"
pdqselect = "0.1.0"

[profile.release]
lto = true
opt-level = 3

ব্যবহার

cargo build --release
time target/release/frequent ulysses64 10

1
চমত্কার! তিনটি সেটিংস জুড়েই খুব ভাল পারফরম্যান্স। জাস্ট সম্পর্কে ক্যারল নিকোলসের সাম্প্রতিক আলাপটি দেখার মাঝে আমি আক্ষরিক অর্থেই ছিলাম :) কিছুটা অস্বাভাবিক বাক্য গঠন, তবে আমি ভাষাটি শিখতে আগ্রহী: সি ++ পরবর্তী ভাষা পরবর্তী ভাষাগুলির মধ্যে একমাত্র ভাষা বলে মনে হচ্ছে বিকাশকারীর জীবনকে আরও সহজ করে তুলতে গিয়ে অনেক কর্মক্ষমতা ত্যাগ করুন।
অ্যান্ড্রি মাকুখা

খুব দ্রুত! আমি অভিভূত! আমি অবাক হয়েছি যদি সি (ট্রি + বিন) এর জন্য আরও ভাল সংকলক বিকল্পটি একই রকম ফল দেয়?
মোগি

@ মুগি আমি ইতিমধ্যে আপনার সাথে পরীক্ষা করে দেখছি -O3, এবং -Ofastকোনও পরিমাপযোগ্য পার্থক্য রাখে না।
Anders Kaseorg

@ মুগি, আমি আপনার কোডটি এর মতো সংকলন করছি gcc -O3 -march=native -mtune=native program.c
Andriy Makukha

@ অ্যান্ড্রি মাকুখা আহ। আপনি যে ফলাফলগুলি পেয়ে যাচ্ছেন তার মধ্যে গতির বড় পার্থক্য ব্যাখ্যা করবে: আপনি ইতিমধ্যে অপ্টিমাইজেশন পতাকা প্রয়োগ করছেন। আমি মনে করি না অনেক বড় কোড অপ্টিমাইজেশন বাকি আছে। আমি অন্যদের পরামর্শ মতো মানচিত্র ব্যবহার করে পরীক্ষা করতে পারছি না কারণ মিংডু মারা যাওয়ার কোনও প্রয়োগ নেই ... এবং কেবল ৫% বৃদ্ধি দেবে। আমি মনে করি আমাকে অ্যান্ডারসের দুর্দান্ত এন্ট্রি দিতে হবে। সাবাশ!
মোগি

3

এপিএল (ডায়ালগ ইউনিকোড)

উইন্ডোজ 10 এ 2.৪-বিট ডায়ালগ এপিএল ১.0.০ ব্যবহার করে আমার ২.6 গিগাহার্টজ আই 7-4720HQ এ নিম্নলিখিতগুলি 8 সেকেন্ডের মধ্যে চলেছে:

⎕{m[⍺↑⍒⊢/m←{(⊂⎕UCS⊃⍺),≢⍵}⌸(⊢⊆⍨96∘<∧<∘123)83⎕DR 819⌶80 ¯1⎕MAP⍵;]}⍞

এটি প্রথমে ফাইলের নামের জন্য অনুরোধ জানায়, তারপরে কে। দ্রষ্টব্য যে চলমান সময়ের একটি উল্লেখযোগ্য অংশ (প্রায় 1 সেকেন্ড) কেবলমাত্র ফাইলটি পড়ছে।

এটির জন্য, আপনি নিম্নলিখিতটি কার্যকর করতে সক্ষম হবেন dyalog(দশ দশ ঘন ঘন শব্দের জন্য):

⎕{m[⍺↑⍒⊢/m←{(⊂⎕UCS⊃⍺),≢⍵}⌸(⊢⊆⍨96∘<∧<∘123)83⎕DR 819⌶80 ¯1⎕MAP⍵;]}⍞
/tmp/ulysses64
10
⎕OFF

এটি মুদ্রণ করা উচিত:

 the  968832
 of   528960
 and  466432
 a    421184
 to   322624
 in   320512
 he   270528
 his  213120
 i    191808
 s    182144

খুব সুন্দর! এটি পাইথনকে মারধর করে। এটি পরে সবচেয়ে ভাল কাজ করেছে export MAXWS=4096M। আমার ধারণা, এটি হ্যাশ টেবিল ব্যবহার করে? কারণ কর্মক্ষেত্রের আকার 2 জিবি হ্রাস করা এটি পুরো 2 সেকেন্ডের দ্বারা ধীর করে দেয়।
অ্যান্ড্রি মাকুখা

@AndriyMakukha হ্যাঁ, অনুযায়ী একটি হ্যাশ টেবিল ব্যবহার এই , এবং আমি নিশ্চিত প্রশংসনীয় খুব অভ্যন্তরীণভাবে আছে।
অ্যাডম

এটি ও (এন লগ এন) কেন? পাইথনের মতো দেখতে (আমার কাছে সমস্ত বারের অনন্য শব্দের পুনঃস্থাপনের সময়) বা এডাব্লুকে (কেবলমাত্র অনন্য শব্দের বাছাই করা) সমাধানের জন্য সমাধান। আপনি যদি ম্যাকিল্রয়ের শেল স্ক্রিপ্টের মতো সমস্ত শব্দকে বাছাই না করেন তবে এটি ও (এন লগ এন) হওয়া উচিত নয়।
অ্যান্ড্রি মাকুখা

@ অ্যান্ড্রি মাকুখা এটি সমস্ত বিষয়কে গ্রেড করে। আমাদের পারফরম্যান্স লোকটি আমাকে যা লিখেছিল তা এখানে : সময় জটিলতা হ'ল (এন লগ এন), যদি আপনি হ্যাশ টেবিলগুলি সম্পর্কে কিছু তাত্ত্বিকভাবে সন্দেহজনক জিনিস বিশ্বাস না করেন, তবে এটি ও (এন) হয়।
অ্যাডম

ঠিক আছে, আমি যখন আপনার কোডটি 8, 16 এবং 32 ইউলিসিসের বিরুদ্ধে চালাই, তখন এটি ঠিক রৈখিকভাবে ধীর হয়ে যায়। হতে পারে আপনার পারফরম্যান্স লোকটির হ্যাশ টেবিলগুলির সময় জটিলতার বিষয়ে তার দৃষ্টিভঙ্গিগুলি পুনর্বিবেচনা করা দরকার :) এছাড়াও, এই কোডটি বড় পরীক্ষার ক্ষেত্রে কাজ করে না। এটি ফিরে আসে WS FULL, যদিও আমি কাজের জায়গাটি 6 গিগাবাইটে বাড়িয়েছি।
অ্যান্ড্রি মাকুখা

2

[সি] উপসর্গ গাছ + বিন

দ্রষ্টব্য: ব্যবহৃত সংকলক প্রোগ্রাম কার্যকর করার গতিতে একটি উল্লেখযোগ্য প্রভাব ফেলে! আমি জিসিসি (MinGW.org GCC-8.2.0-3) 8.2.0 ব্যবহার করেছি। -অফেষ্ট স্যুইচটিব্যবহার করার সময় , প্রোগ্রামটি সাধারণত সংকলিত প্রোগ্রামের চেয়ে প্রায় 50% গতিতে চলে।

অ্যালগরিদম জটিলতা

আমি তখন থেকে বুঝতে পেরেছি যে আমি যে বিন সাজানোর কাজটি করছি তা পাইগনহোস্ট সাজানোর একটি রূপ যার অর্থ আমি এই সমাধানটির বিগ ও জটিলতা অর্জন করতে পারি।

আমি এটি হিসাবে গণনা:

Worst Time complexity: O(1 + N + k)
Worst Space complexity: O(26*M + N + n) = O(M + N + n)

Where N is the number of words of the data
and M is the number of letters of the data
and n is the range of pigeon holes
and k is the desired number of sorted words to return
and N<=M

গাছ নির্মানের জটিলতা গাছের আক্রমণের সমতুল্য তাই যেহেতু যে কোনও স্তরে যাওয়ার সঠিক নোড হ'ল হ'ল (1) (যেহেতু প্রতিটি অক্ষর সরাসরি একটি নোডের সাথে ম্যাপ করা হয় এবং আমরা সর্বদা প্রতিটি বর্ণের জন্য গাছের এক স্তরকেই অতিক্রম করি)

কবুতর হোল বাছাই হ'ল ও (এন + এন) যেখানে এন মূল মানগুলির ব্যাপ্তি, তবে এই সমস্যার জন্য আমাদের সকল মানকে বাছাই করতে হবে না, কেবলমাত্র কে সংখ্যা তাই সবচেয়ে খারাপ ক্ষেত্রে ও (এন + কে) হবে।

একত্রিত হয়ে ও (1 + এন + কে) ফলন করে।

গাছের নির্মাণের জন্য স্পেস কমপ্লেক্সিটি হ'ল ডেটাতে এম সংখ্যার সাথে একটি শব্দ থাকে এবং প্রতিটি নোডে 26 টি নোড থাকে (অর্থাত্ বর্ণমালার অক্ষরের জন্য) যদি ডেটাতে 26 টি নোড থাকে তবে সবচেয়ে খারাপ পরিস্থিতি ঘটে tree এইভাবে হে (26 * এম) = ও (এম)

কবুতরের হোল বাছাইয়ের জন্য ও (এন + এন) এর স্পেস জটিলতা রয়েছে

একত্রিত হয়ে ও (26 * এম + এন + এন) = হে (এম + এন + এন)

অ্যালগরিদম

এটি ইনপুট হিসাবে দুটি আর্গুমেন্ট লাগে (পাঠ্য ফাইলের পথে এবং তালিকার জন্য বেশিরভাগ ঘন শব্দের জন্য k সংখ্যার জন্য)

আমার অন্যান্য এন্ট্রিগুলির উপর ভিত্তি করে, এই সংস্করণটিতে আমার অন্যান্য সমাধানগুলির তুলনায় কে-এর ক্রমবর্ধমান মানগুলির সাথে খুব সামান্য সময় ব্যয় র‌্যাম্প রয়েছে। তবে কে এর কম মানগুলির জন্য লক্ষণীয়ভাবে ধীরে ধীরে তবে কে এর বৃহত্তর মানগুলির জন্য এটি আরও দ্রুত হওয়া উচিত।

এটি শব্দের বর্ণগুলিতে একটি গাছের শাখা তৈরি করে, তারপরে পাতার অক্ষরে এটি একটি পাল্টা বৃদ্ধি করে। তারপরে শব্দটি একই আকারের শব্দের একটি বাক্যে যুক্ত করে (প্রথমে বিন থেকে শব্দটি সরিয়ে নেওয়ার পরে এটি ইতিমধ্যে উপস্থিত ছিল)। আর কোনও অক্ষর না পড়া পর্যন্ত এটি সমস্ত পুনরাবৃত্তি করে After

এটি বর্তমানে প্রক্রিয়াজাতকরণের সময় আউটপুট দিতে ডিফল্ট হয় তবে অন্যান্য সাবমিশনের সাথে সঙ্গতি রাখার উদ্দেশ্যে উত্স কোডে টিমিং সংজ্ঞাটি অক্ষম করে।

// comment out TIMING if using external program timing mechanism
#define TIMING 1

// may need to increase if the source text has many unique words
#define MAX_LETTER_INSTANCES 1000000

// may need to increase if the source text has many repeated words
#define MAX_BINS 1000000

// assume maximum of 20 letters in a word... adjust accordingly
#define MAX_LETTERS_IN_A_WORD 20

// assume maximum of 10 letters for the string representation of the bin number... adjust accordingly
#define MAX_LETTERS_FOR_BIN_NAME 10

// maximum number of bytes of the output results
#define MAX_OUTPUT_SIZE 10000000

#define false 0
#define true 1
#define null 0
#define SPACE_ASCII_CODE 32

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef TIMING
#include <sys/time.h>
#endif

struct Letter
{
    //char isAWord;
    struct Letter* parent;
    struct Letter* binElementNext;
    char asciiCode;
    unsigned int count;
    struct Letter* nextLetters[26];
};
typedef struct Letter Letter;

struct Bin
{
  struct Letter* word;
};
typedef struct Bin Bin;


int main(int argc, char *argv[]) 
{
#ifdef TIMING
    struct timeval tv1, tv2;
    gettimeofday(&tv1, null);
#endif

    int k;
    if (argc !=3 || (k = atoi(argv[2])) <= 0)
    {
        printf("Usage:\n");
        printf("      WordCount <input file path> <number of most frequent words to find>\n\n");
        return -1;
    }

    long  file_size;
    long dataLength;
    char* data;

    // read in file contents
    FILE *fptr;
    size_t read_s = 0;  
    fptr = fopen(argv[1], "rb");
    fseek(fptr, 0L, SEEK_END);
    dataLength = ftell(fptr);
    rewind(fptr);
    data = (char*)malloc((dataLength));
    read_s = fread(data, 1, dataLength, fptr);
    if (fptr) fclose(fptr);

    unsigned int chr;
    unsigned int i, j;

    // working memory of letters
    Letter* letters = (Letter*) malloc(sizeof(Letter) * MAX_LETTER_INSTANCES);
    memset(&letters[0], null, sizeof( Letter) * MAX_LETTER_INSTANCES);

    // the memory for bins
    Bin* bins = (Bin*) malloc(sizeof(Bin) * MAX_BINS);
    memset(&bins[0], null, sizeof( Bin) * MAX_BINS);

    // the index of the next unused letter
    unsigned int letterMasterIndex=0;
    Letter *nextFreeLetter = &letters[0];

    // pesudo letter representing the starting point of any word
    Letter* root = &letters[letterMasterIndex++];

    // the current letter in the word being processed
    Letter* currentLetter = root;

    // the next letter to be processed
    Letter* nextLetter = null;

    unsigned int sortedListSize = 0;

    // the count of the most frequent word
    unsigned int maxCount = 0;

    // the count of the current word
    unsigned int wordCount = 0;

////////////////////////////////////////////////////////////////////////////////////////////
// CREATING PREFIX TREE
    j=dataLength;
    while (--j>0)
    {
        chr = data[j]|0x20; // convert to lower case

        // is a letter?
        if (chr > 96 && chr < 123)
        {
            chr-=97; // translate to be zero indexed
            nextLetter = currentLetter->nextLetters[chr];

            // this is a new letter at this word length, intialise the new letter
            if (nextLetter == null)
            {
                ++letterMasterIndex;
                nextLetter = ++nextFreeLetter;
                nextLetter->parent = currentLetter;
                nextLetter->asciiCode = chr;
                currentLetter->nextLetters[chr] = nextLetter;
            }

            currentLetter = nextLetter;
        }
        else
        {
            //currentLetter->isAWord = true;

            // increment the count of the full word that this letter represents
            ++currentLetter->count;

            // reset the letter path representing the word
            currentLetter = root;
        }
    }

////////////////////////////////////////////////////////////////////////////////////////////
// ADDING TO BINS

    j = letterMasterIndex;
    currentLetter=&letters[j-1];
    while (--j>0)
    {

      // is the letter the leaf letter of word?
      if (currentLetter->count>0)
      {
        i = currentLetter->count;
        if (maxCount < i) maxCount = i;

        // add to bin
        currentLetter->binElementNext = bins[i].word;
        bins[i].word = currentLetter;
      }
      --currentLetter;
    }

////////////////////////////////////////////////////////////////////////////////////////////
// PRINTING OUTPUT

    // the memory for output
    char* output = (char*) malloc(sizeof(char) * MAX_OUTPUT_SIZE);
    memset(&output[0], SPACE_ASCII_CODE, sizeof( char) * MAX_OUTPUT_SIZE);
    unsigned int outputIndex = 0;

    // string representation of the current bin number
    char binName[MAX_LETTERS_FOR_BIN_NAME];
    memset(&binName[0], SPACE_ASCII_CODE, MAX_LETTERS_FOR_BIN_NAME);


    Letter* letter;
    Letter* binElement;

    // starting at the bin representing the most frequent word(s) and then iterating backwards...
    for ( i=maxCount;i>0 && k>0;i--)
    {
      // check to ensure that the bin has at least one word
      if ((binElement = bins[i].word) != null)
      {
        // update the bin name
        sprintf(binName,"%u",i);

        // iterate of the words in the bin
        while (binElement !=null && k>0)
        {
          // stop if we have reached the desired number of outputed words
          if (k-- > 0)
          {
              letter = binElement;

              // add the bin name to the output
              memcpy(&output[outputIndex],&binName[0],MAX_LETTERS_FOR_BIN_NAME);
              outputIndex+=MAX_LETTERS_FOR_BIN_NAME;

              // construct string of letters to form the word
               while (letter != root)
              {
                // output the letter to the output
                output[outputIndex++] = letter->asciiCode+97;
                letter=letter->parent;
              }

              output[outputIndex++] = '\n';

              // go to the next word in the bin
              binElement = binElement->binElementNext;
          }
        }
      }
    }

    // write the output to std out
    fwrite(output, 1, outputIndex, stdout);
   // fflush(stdout);

   // free( data );
   // free( letters );
   // free( bins );
   // free( output );

#ifdef TIMING   
    gettimeofday(&tv2, null);
    printf("\nTime Taken: %f seconds\n", (double) (tv2.tv_usec - tv1.tv_usec)/1000000 + (double) (tv2.tv_sec - tv1.tv_sec));
#endif
    return 0;
}

সম্পাদনা: গাছ নির্মান এবং আউটপুট নির্মাণের অনুকূলকরণের অবধি পপুলেটিং বিনগুলি স্থগিত করে।

EDIT2: এখন গতি অপ্টিমাইজেশনের জন্য অ্যারে অ্যাক্সেসের পরিবর্তে পয়েন্টার গাণিতিক ব্যবহার করছে।


কি দারুন! 11 সেকেন্ডের মধ্যে 1 জিবি ফাইল থেকে 100,000 সর্বাধিক ঘন শব্দগুলি ... এটি এক ধরণের যাদু কৌশলগুলির মতো দেখায়।
অ্যান্ড্রি মাকুখা

কোন কৌশল নয় ... দক্ষ মেমরির ব্যবহারের জন্য কেবল সিপিইউ সময় ট্রেডিং। আমি আপনার ফলাফল দেখে অবাক হই ... আমার পুরানো পিসিতে এটি 60 সেকেন্ডের বেশি সময় নেয়। আমি লক্ষ করেছি যে II অপ্রয়োজনীয় তুলনা করছি এবং ফাইল প্রক্রিয়া না হওয়া পর্যন্ত বিনিং স্থগিত করতে পারে। এটি আরও দ্রুত করা উচিত। আমি শীঘ্রই এটি চেষ্টা করব এবং আমার উত্তর আপডেট করব।
মোগি

@ অ্যান্ড্রি মাকুখা সমস্ত শব্দ প্রক্রিয়া করার পরে এবং গাছটি তৈরি না হওয়া পর্যন্ত আমি এখন বিনগুলি পপুলেশন স্থগিত করেছি। এটি অপ্রয়োজনীয় তুলনা এবং বিন উপাদান ম্যানিপুলেশন এড়ায়। মুদ্রণটি উল্লেখযোগ্য পরিমাণে সময় নিচ্ছিল বলে আমিও আউটপুটটি নির্মাণের পদ্ধতিটি পরিবর্তন করেছি!
মোগি

আমার মেশিনে এই আপডেটটি কোনও লক্ষণীয় পার্থক্য করে না। তবে এটি ulysses64একবারে খুব দ্রুত পারফর্ম করেছিল , সুতরাং এটি এখনকার বর্তমান নেতা।
অ্যান্ড্রি মাকুখা

আমার পিসির সাথে তখন অবশ্যই একটি অনন্য সমস্যা হতে হবে :) এই নতুন আউটপুট অ্যালগরিদমটি ব্যবহার করার সময় আমি একটি 5 সেকেন্ড গতি লক্ষ্য করেছি
মুগি

2

জে

9!:37 ] 0 _ _ _

'input k' =: _2 {. ARGV
k =: ". k

lower =: a. {~ 97 + i. 26
words =: ((lower , ' ') {~ lower i. ]) (32&OR)&.(a.&i.) fread input
words =: ' ' , words
words =: -.&(s: a:) s: words
uniq =: ~. words
res =: (k <. # uniq) {. \:~ (# , {.)/.~ uniq&i. words
echo@(,&": ' ' , [: }.@": {&uniq)/"1 res

exit 0

এর সাথে স্ক্রিপ্ট হিসাবে চালান jconsole <script> <input> <k>। উদাহরণস্বরূপ, থেকে আউটপুট giganovelসঙ্গে k=100K:

$ time jconsole solve.ijs giganovel 100000 | head 
11309 e
11290 ihit
11285 ah
11260 ist
11255 aa
11202 aiv
11201 al
11188 an
11187 o
11186 ansa

real    0m13.765s
user    0m11.872s
sys     0m1.786s

উপলব্ধ সিস্টেমের মেমরির পরিমাণ ব্যতীত কোনও সীমা নেই।


ছোট পরীক্ষার ক্ষেত্রে খুব দ্রুত! নিস! তবে, ইচ্ছামত বড় শব্দের জন্য এটি আউটপুটে শব্দগুলি কেটে দেয়। আমি নিশ্চিত নই যে কোনও শব্দের অক্ষরের সংখ্যার সীমা আছে কিনা বা কেবল আউটপুটকে আরও সংক্ষিপ্ত করে তোলা উচিত।
অ্যান্ড্রি মাকুখা

@ অ্যান্ড্রি মাকুখা হ্যাঁ, ...প্রতি লাইনে আউটপুট কেটে যাওয়ার কারণে এটি ঘটে। আমি সমস্ত কাটা অক্ষম করতে শুরুতে একটি লাইন যুক্ত করেছি। এটি আরও বেশি মেমরি ব্যবহার করে যেহেতু আরও অনন্য শব্দ রয়েছে তাই এটি জিগানোভেলকে ধীর করে দেয়।
মাইল

গ্রেট! এখন এটি সাধারণতার পরীক্ষায় উত্তীর্ণ হয়। এবং এটি আমার মেশিনে গতি কমেনি। আসলে, একটি গৌণ গতি ছিল।
অ্যান্ড্রি মাকুখা

2

সি ++ (একটি লা নুথ)

আমি কৌতূহল ছিল কীভাবে নুথের প্রোগ্রামটি ভাড়া নিবে, তাই আমি তার (মূলত পাস্কাল) প্রোগ্রামটি সি ++ তে অনুবাদ করেছিলাম।

যদিও নুথের প্রাথমিক লক্ষ্যটি গতি ছিল না তবে তার WEB সাক্ষরিত প্রোগ্রামিংয়ের চিত্রটি তুলে ধরার জন্য, প্রোগ্রামটি আশ্চর্যরকমভাবে প্রতিযোগিতামূলক এবং এখানকার উত্তরগুলির তুলনায় একটি দ্রুত সমাধানের দিকে নিয়ে যায়। এখানে তার প্রোগ্রামটির আমার অনুবাদ (WEB প্রোগ্রামের সংশ্লিষ্ট "বিভাগ" সংখ্যার " {§24}" এর মত মন্তব্যে উল্লেখ করা হয়েছে ):

#include <iostream>
#include <cassert>

// Adjust these parameters based on input size.
const int TRIE_SIZE = 800 * 1000; // Size of the hash table used for the trie.
const int ALPHA = 494441;  // An integer that's approximately (0.61803 * TRIE_SIZE), and relatively prime to T = TRIE_SIZE - 52.
const int kTolerance = TRIE_SIZE / 100;  // How many places to try, to find a new place for a "family" (=bunch of children).

typedef int32_t Pointer;  // [0..TRIE_SIZE), an index into the array of Nodes
typedef int8_t Char;  // We only care about 1..26 (plus two values), but there's no "int5_t".
typedef int32_t Count;  // The number of times a word has been encountered.
// These are 4 separate arrays in Knuth's implementation.
struct Node {
  Pointer link;  // From a parent node to its children's "header", or from a header back to parent.
  Pointer sibling;  // Previous sibling, cyclically. (From smallest child to header, and header to largest child.)
  Count count;  // The number of times this word has been encountered.
  Char ch;  // EMPTY, or 1..26, or HEADER. (For nodes with ch=EMPTY, the link/sibling/count fields mean nothing.)
} node[TRIE_SIZE + 1];
// Special values for `ch`: EMPTY (free, can insert child there) and HEADER (start of family).
const Char EMPTY = 0, HEADER = 27;

const Pointer T = TRIE_SIZE - 52;
Pointer x;  // The `n`th time we need a node, we'll start trying at x_n = (alpha * n) mod T. This holds current `x_n`.
// A header can only be in T (=TRIE_SIZE-52) positions namely [27..TRIE_SIZE-26].
// This transforms a "h" from range [0..T) to the above range namely [27..T+27).
Pointer rerange(Pointer n) {
  n = (n % T) + 27;
  // assert(27 <= n && n <= TRIE_SIZE - 26);
  return n;
}

// Convert trie node to string, by walking up the trie.
std::string word_for(Pointer p) {
  std::string word;
  while (p != 0) {
    Char c = node[p].ch;  // assert(1 <= c && c <= 26);
    word = static_cast<char>('a' - 1 + c) + word;
    // assert(node[p - c].ch == HEADER);
    p = (p - c) ? node[p - c].link : 0;
  }
  return word;
}

// Increment `x`, and declare `h` (the first position to try) and `last_h` (the last position to try). {§24}
#define PREPARE_X_H_LAST_H x = (x + ALPHA) % T; Pointer h = rerange(x); Pointer last_h = rerange(x + kTolerance);
// Increment `h`, being careful to account for `last_h` and wraparound. {§25}
#define INCR_H { if (h == last_h) { std::cerr << "Hit tolerance limit unfortunately" << std::endl; exit(1); } h = (h == TRIE_SIZE - 26) ? 27 : h + 1; }

// `p` has no children. Create `p`s family of children, with only child `c`. {§27}
Pointer create_child(Pointer p, int8_t c) {
  // Find `h` such that there's room for both header and child c.
  PREPARE_X_H_LAST_H;
  while (!(node[h].ch == EMPTY and node[h + c].ch == EMPTY)) INCR_H;
  // Now create the family, with header at h and child at h + c.
  node[h]     = {.link = p, .sibling = h + c, .count = 0, .ch = HEADER};
  node[h + c] = {.link = 0, .sibling = h,     .count = 0, .ch = c};
  node[p].link = h;
  return h + c;
}

// Move `p`'s family of children to a place where child `c` will also fit. {§29}
void move_family_for(const Pointer p, Char c) {
  // Part 1: Find such a place: need room for `c` and also all existing children. {§31}
  PREPARE_X_H_LAST_H;
  while (true) {
    INCR_H;
    if (node[h + c].ch != EMPTY) continue;
    Pointer r = node[p].link;
    int delta = h - r;  // We'd like to move each child by `delta`
    while (node[r + delta].ch == EMPTY and node[r].sibling != node[p].link) {
      r = node[r].sibling;
    }
    if (node[r + delta].ch == EMPTY) break;  // There's now space for everyone.
  }

  // Part 2: Now actually move the whole family to start at the new `h`.
  Pointer r = node[p].link;
  int delta = h - r;
  do {
    Pointer sibling = node[r].sibling;
    // Move node from current position (r) to new position (r + delta), and free up old position (r).
    node[r + delta] = {.ch = node[r].ch, .count = node[r].count, .link = node[r].link, .sibling = node[r].sibling + delta};
    if (node[r].link != 0) node[node[r].link].link = r + delta;
    node[r].ch = EMPTY;
    r = sibling;
  } while (node[r].ch != EMPTY);
}

// Advance `p` to its `c`th child. If necessary, add the child, or even move `p`'s family. {§21}
Pointer find_child(Pointer p, Char c) {
  // assert(1 <= c && c <= 26);
  if (p == 0) return c;  // Special case for first char.
  if (node[p].link == 0) return create_child(p, c);  // If `p` currently has *no* children.
  Pointer q = node[p].link + c;
  if (node[q].ch == c) return q;  // Easiest case: `p` already has a `c`th child.
  // Make sure we have room to insert a `c`th child for `p`, by moving its family if necessary.
  if (node[q].ch != EMPTY) {
    move_family_for(p, c);
    q = node[p].link + c;
  }
  // Insert child `c` into `p`'s family of children (at `q`), with correct siblings. {§28}
  Pointer h = node[p].link;
  while (node[h].sibling > q) h = node[h].sibling;
  node[q] = {.ch = c, .count = 0, .link = 0, .sibling = node[h].sibling};
  node[h].sibling = q;
  return q;
}

// Largest descendant. {§18}
Pointer last_suffix(Pointer p) {
  while (node[p].link != 0) p = node[node[p].link].sibling;
  return p;
}

// The largest count beyond which we'll put all words in the same (last) bucket.
// We do an insertion sort (potentially slow) in last bucket, so increase this if the program takes a long time to walk trie.
const int MAX_BUCKET = 10000;
Pointer sorted[MAX_BUCKET + 1];  // The head of each list.

// Records the count `n` of `p`, by inserting `p` in the list that starts at `sorted[n]`.
// Overwrites the value of node[p].sibling (uses the field to mean its successor in the `sorted` list).
void record_count(Pointer p) {
  // assert(node[p].ch != HEADER);
  // assert(node[p].ch != EMPTY);
  Count f = node[p].count;
  if (f == 0) return;
  if (f < MAX_BUCKET) {
    // Insert at head of list.
    node[p].sibling = sorted[f];
    sorted[f] = p;
  } else {
    Pointer r = sorted[MAX_BUCKET];
    if (node[p].count >= node[r].count) {
      // Insert at head of list
      node[p].sibling = r;
      sorted[MAX_BUCKET] = p;
    } else {
      // Find right place by count. This step can be SLOW if there are too many words with count >= MAX_BUCKET
      while (node[p].count < node[node[r].sibling].count) r = node[r].sibling;
      node[p].sibling = node[r].sibling;
      node[r].sibling = p;
    }
  }
}

// Walk the trie, going over all words in reverse-alphabetical order. {§37}
// Calls "record_count" for each word found.
void walk_trie() {
  // assert(node[0].ch == HEADER);
  Pointer p = node[0].sibling;
  while (p != 0) {
    Pointer q = node[p].sibling;  // Saving this, as `record_count(p)` will overwrite it.
    record_count(p);
    // Move down to last descendant of `q` if any, else up to parent of `q`.
    p = (node[q].ch == HEADER) ? node[q].link : last_suffix(q);
  }
}

int main(int, char** argv) {
  // Program startup
  std::ios::sync_with_stdio(false);

  // Set initial values {§19}
  for (Char i = 1; i <= 26; ++i) node[i] = {.ch = i, .count = 0, .link = 0, .sibling = i - 1};
  node[0] = {.ch = HEADER, .count = 0, .link = 0, .sibling = 26};

  // read in file contents
  FILE *fptr = fopen(argv[1], "rb");
  fseek(fptr, 0L, SEEK_END);
  long dataLength = ftell(fptr);
  rewind(fptr);
  char* data = (char*)malloc(dataLength);
  fread(data, 1, dataLength, fptr);
  if (fptr) fclose(fptr);

  // Loop over file contents: the bulk of the time is spent here.
  Pointer p = 0;
  for (int i = 0; i < dataLength; ++i) {
    Char c = (data[i] | 32) - 'a' + 1;  // 1 to 26, for 'a' to 'z' or 'A' to 'Z'
    if (1 <= c && c <= 26) {
      p = find_child(p, c);
    } else {
      ++node[p].count;
      p = 0;
    }
  }
  node[0].count = 0;

  walk_trie();

  const int max_words_to_print = atoi(argv[2]);
  int num_printed = 0;
  for (Count f = MAX_BUCKET; f >= 0 && num_printed <= max_words_to_print; --f) {
    for (Pointer p = sorted[f]; p != 0 && num_printed < max_words_to_print; p = node[p].sibling) {
      std::cout << word_for(p) << " " << node[p].count << std::endl;
      ++num_printed;
    }
  }

  return 0;
}

নথের প্রোগ্রাম থেকে পার্থক্য:

  • আমি Knuth এর 4 অ্যারে মিলিত link, sibling, countএবং chএকটি একটি অ্যারের মধ্যেstruct Node (এটা সহজ এই ভাবে বুঝতে এটি)।
  • আমি সাক্ষরতা-প্রোগ্রামিং (ডাব্লুইইবি-স্টাইল) বিভাগগুলির পাঠ্যগত ট্রান্সকোসিয়েশনকে আরও প্রচলিত ফাংশন কলগুলিতে (এবং কয়েকজন ম্যাক্রো) পরিবর্তন করেছি।
  • আমাদের স্ট্যান্ডার্ড পাস্কালের অদ্ভুত আই / ও কনভেনশন / বিধিনিষেধ ব্যবহার করার দরকার নেই, সুতরাং ব্যবহার করে freadএবংdata[i] | 32 - 'a' অন্যান্য উত্তর এখানে হিসাবে, পাসকাল কার্যসংক্রান্ত পরিবর্তে।
  • প্রোগ্রামটি চলাকালীন আমরা সীমা ছাড়িয়ে (স্থান ছাড়িয়ে যায়) ক্ষেত্রে, নুথের আসল প্রোগ্রামটি পরে শব্দগুলি বাদ দিয়ে এবং শেষে একটি বার্তা মুদ্রণের মাধ্যমে এটিকে মনোনিবেশ করে। (এটা বলা ঠিক নয় যে ম্যাকিলারোই "বাইবেলের সম্পূর্ণ পাঠ্য প্রক্রিয়া করতে না পেরে নূথের সমাধানের সমালোচনা করেছিলেন"; তিনি কেবল ইঙ্গিত করছিলেন যে মাঝে মাঝে ঘন ঘন শব্দগুলি কোনও পাঠ্যে খুব দেরীতে আসতে পারে, যেমন "যিশু শব্দ" "বাইবেলে, সুতরাং ত্রুটির শঙ্কা নিস্পাপ নয়)) আমি প্রোগ্রামটি সহজভাবে বন্ধ করার মত উচ্চস্বরে (এবং যাইহোক সহজ) দৃষ্টিভঙ্গি নিয়েছি।
  • প্রোগ্রামটি মেমোরির ব্যবহারটি নিয়ন্ত্রণ করতে একটি ধ্রুবক TRIE_SIZE ঘোষণা করে, যা আমি ধাক্কা দিয়েছি। (32767 এর ধ্রুবকটি মূল প্রয়োজনীয়তার জন্য বেছে নেওয়া হয়েছিল - "কোনও ব্যবহারকারীকে বিশ পৃষ্ঠার প্রযুক্তিগত কাগজে (প্রায় 50K বাইট ফাইলের মধ্যে প্রায় 100 টি ঘন ঘন শব্দগুলি খুঁজে পেতে সক্ষম হওয়া উচিত") এবং কারণ পাস্কেলটি পূর্ণসংখ্যার পূর্ণসংখ্যার সাথে ভালভাবে আচরণ করে " টাইপ করুন এবং এগুলি সর্বোত্তমভাবে প্যাক করে test পরীক্ষার ইনপুট এখন 20 মিলিয়ন গুণ বেশি বড় হওয়ায় আমাদের এটিকে 25x থেকে 800,000 বাড়াতে হয়েছিল)
  • স্ট্রিংগুলির চূড়ান্ত মুদ্রণের জন্য, আমরা কেবল ত্রি ট্রাই করতে পারি এবং একটি বোবা (সম্ভবত চতুষ্পদ এমনকি স্ট্রিং সংযোজন) করতে পারি।

এগুলি ছাড়াও, এটি হুবহু নুথের প্রোগ্রাম (তার হ্যাশ ট্রাই / প্যাকড ট্রাই ডেটা স্ট্রাকচার এবং বাল্টিট সাজ্ট ব্যবহার করে), এবং ইনপুটে সমস্ত অক্ষরকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফাঁকে ফোটার সময় (হুথ ট্রাই / প্যাকড ট্রাই ডাটা স্ট্রাকচার এবং বাল্টিট সাজ্ট) ব্যবহার করে এটি ঠিক একই কাজ করে; মনে রাখবেন যে এটি কোনও বাহ্যিক অ্যালগরিদম বা ডেটা স্ট্রাকচার লাইব্রেরি ব্যবহার করে না এবং সমান ফ্রিকোয়েন্সি শব্দগুলি বর্ণানুক্রমিকভাবে মুদ্রিত হবে।

টাইমিং

সংকলিত

clang++ -std=c++17 -O2 ptrie-walktrie.cc 

যখন এখানে বৃহত্তম টেস্টকেসটিতে চালিত হন ( giganovelঅনুরোধ করা 100,000 শব্দের সাথে) এবং এখন পর্যন্ত এখানে পোস্ট করা দ্রুততম প্রোগ্রামের তুলনায় আমি এটিকে কিছুটা হলেও ধারাবাহিকভাবে দ্রুত পেলাম:

target/release/frequent:   4.809 ±   0.263 [ 4.45.. 5.62]        [... 4.63 ...  4.75 ...  4.88...]
ptrie-walktrie:            4.547 ±   0.164 [ 4.35.. 4.99]        [... 4.42 ...   4.5 ...  4.68...]

(শীর্ষস্থানটি হ'ল এন্ডারস ক্যাসরগের জাস্ট সলিউশন; নীচের অংশটি উপরের প্রোগ্রামটি These এগুলি গড়, মিনিট, সর্বাধিক, মধ্যম এবং কোয়ার্টাইলস সহ 100 রানের সময়)

বিশ্লেষণ

এত দ্রুত কেন? এটি যে জাস্টের তুলনায় সি ++ তত দ্রুত নয় বা নথের প্রোগ্রামটি সবচেয়ে দ্রুত সম্ভব - প্রকৃতপক্ষে, ট্রাই-প্যাকিংয়ের (স্মৃতি সংরক্ষণের জন্য) নুথের প্রোগ্রাম সন্নিবেশগুলিতে (যেমন তিনি উল্লেখ করেছেন) ধীর। আমার সন্দেহ, কারণটি 2008 সালে নথের অভিযোগের সাথে সম্পর্কিত ছিল :

Fla৪-বিট পয়েন্টার সম্পর্কে একটি শিখা

4 গিগাবাইটের চেয়ে কম র‌্যাম ব্যবহার করা একটি প্রোগ্রাম সংকলন করার সময় 64৪-বিট পয়েন্টার থাকা একেবারে বোকামি। যখন এই জাতীয় নির্দেশকের মানগুলি কোনও কাঠামোর ভিতরে উপস্থিত হয়, তখন তারা কেবল অর্ধেক স্মৃতি নষ্ট করে না, তারা কার্যকরভাবে ক্যাশের অর্ধেক ফেলে দেয়।

উপরের প্রোগ্রামটি 32-বিট অ্যারে সূচকগুলি ব্যবহার করে (64-বিট পয়েন্টার নয়), সুতরাং "নোড" স্ট্রাক্টটি কম মেমরি দখল করে, তাই স্ট্যাকের উপর আরও নোড রয়েছে এবং কম ক্যাশে মিস হয়েছে। (আসলে, সেখানে ছিল কিছু কাজ যেমন এই x32 ABI- র , কিন্তু এটা মনে করা হয় একটি ভাল রাষ্ট্র নেই যদিও ধারণা স্পষ্টত দরকারী, যেমন দেখতে সাম্প্রতিক ঘোষণা এর V8 মধ্যে পয়েন্টার কম্প্রেশন । ওহ ভাল।) সুতরাং giganovel, এই প্রোগ্রামটি (প্যাকড) ট্রাইয়ের জন্য 12.8 এমবি ব্যবহার করে, তার ট্রাইয়ের জন্য জাস্ট প্রোগ্রামের 32.18 এমবি বনামgiganovel ) । আমরা 1000x ("গিগানোভেল" থেকে "টেরানোভেল" বলতে) স্কেল করতে পারি এবং এখনও 32-বিট সূচকগুলি অতিক্রম করতে পারি না, সুতরাং এটি যুক্তিসঙ্গত পছন্দ বলে মনে হয়।

দ্রুততম রূপ

আমরা গতিটির জন্য অনুকূলকরণ করতে পারি এবং প্যাকিংটি ফোরগো করতে পারি, তাই আমরা পয়েন্টারগুলির পরিবর্তে সূচকগুলি সহ মরিচা সমাধান হিসাবে (প্যাকড না হওয়া) ট্রাই ব্যবহার করতে পারি। এটি এমন কিছু দেয় যা দ্রুততর হয় এবং স্বতন্ত্র শব্দ, অক্ষর ইত্যাদির কোনও পূর্ব-নির্দিষ্ট সীমা থাকে না:

#include <iostream>
#include <cassert>
#include <vector>
#include <algorithm>

typedef int32_t Pointer;  // [0..node.size()), an index into the array of Nodes
typedef int32_t Count;
typedef int8_t Char;  // We'll usually just have 1 to 26.
struct Node {
  Pointer link;  // From a parent node to its children's "header", or from a header back to parent.
  Count count;  // The number of times this word has been encountered. Undefined for header nodes.
};
std::vector<Node> node; // Our "arena" for Node allocation.

std::string word_for(Pointer p) {
  std::vector<char> drow;  // The word backwards
  while (p != 0) {
    Char c = p % 27;
    drow.push_back('a' - 1 + c);
    p = (p - c) ? node[p - c].link : 0;
  }
  return std::string(drow.rbegin(), drow.rend());
}

// `p` has no children. Create `p`s family of children, with only child `c`.
Pointer create_child(Pointer p, Char c) {
  Pointer h = node.size();
  node.resize(node.size() + 27);
  node[h] = {.link = p, .count = -1};
  node[p].link = h;
  return h + c;
}

// Advance `p` to its `c`th child. If necessary, add the child.
Pointer find_child(Pointer p, Char c) {
  assert(1 <= c && c <= 26);
  if (p == 0) return c;  // Special case for first char.
  if (node[p].link == 0) return create_child(p, c);  // Case 1: `p` currently has *no* children.
  return node[p].link + c;  // Case 2 (easiest case): Already have the child c.
}

int main(int, char** argv) {
  auto start_c = std::clock();

  // Program startup
  std::ios::sync_with_stdio(false);

  // read in file contents
  FILE *fptr = fopen(argv[1], "rb");
  fseek(fptr, 0, SEEK_END);
  long dataLength = ftell(fptr);
  rewind(fptr);
  char* data = (char*)malloc(dataLength);
  fread(data, 1, dataLength, fptr);
  fclose(fptr);

  node.reserve(dataLength / 600);  // Heuristic based on test data. OK to be wrong.
  node.push_back({0, 0});
  for (Char i = 1; i <= 26; ++i) node.push_back({0, 0});

  // Loop over file contents: the bulk of the time is spent here.
  Pointer p = 0;
  for (long i = 0; i < dataLength; ++i) {
    Char c = (data[i] | 32) - 'a' + 1;  // 1 to 26, for 'a' to 'z' or 'A' to 'Z'
    if (1 <= c && c <= 26) {
      p = find_child(p, c);
    } else {
      ++node[p].count;
      p = 0;
    }
  }
  ++node[p].count;
  node[0].count = 0;

  // Brute-force: Accumulate all words and their counts, then sort by frequency and print.
  std::vector<std::pair<int, std::string>> counts_words;
  for (Pointer i = 1; i < static_cast<Pointer>(node.size()); ++i) {
    int count = node[i].count;
    if (count == 0 || i % 27 == 0) continue;
    counts_words.push_back({count, word_for(i)});
  }
  auto cmp = [](auto x, auto y) {
    if (x.first != y.first) return x.first > y.first;
    return x.second < y.second;
  };
  std::sort(counts_words.begin(), counts_words.end(), cmp);
  const int max_words_to_print = std::min<int>(counts_words.size(), atoi(argv[2]));
  for (int i = 0; i < max_words_to_print; ++i) {
    auto [count, word] = counts_words[i];
    std::cout << word << " " << count << std::endl;
  }

  return 0;
}

এই প্রোগ্রামটি এখানে সমাধানগুলির চেয়ে বাছাই করার জন্য অনেকগুলি কিছু করার পরেও giganovelতার ট্রাইয়ের জন্য কেবল 12.2MB ব্যবহার করে (এবং ) দ্রুততর করে তোলে be উল্লিখিত আগের সময়ের সাথে তুলনা করে এই প্রোগ্রামের (শেষ লাইন) সময়সীমা:

target/release/frequent:   4.809 ±   0.263 [ 4.45.. 5.62]        [... 4.63 ...  4.75 ...  4.88...]
ptrie-walktrie:            4.547 ±   0.164 [ 4.35.. 4.99]        [... 4.42 ...   4.5 ...  4.68...]
itrie-nolimit:             3.907 ±   0.127 [ 3.69.. 4.23]        [... 3.81 ...   3.9 ...   4.0...]

মরিচায় অনুবাদ হলে এটি (বা হ্যাশ-ট্রাই প্রোগ্রাম) কী পছন্দ করবে তা দেখার জন্য আমি আগ্রহী । :-)

অধিকতর বিস্তারিত

  1. এখানে ব্যবহৃত ডেটা স্ট্রাকচার সম্পর্কে: "প্যাকিং" চেষ্টাগুলির ব্যাখ্যা টিএওসিপি-র খণ্ড ৩ য় বিভাগের Digital.৩ (ডিজিটাল অনুসন্ধান, অর্থাত্ চেষ্টা করা) অনুশীলন এবং টেক্স-এর হাইফেনেশন সম্পর্কে নুথের ছাত্র ফ্র্যাঙ্ক লিয়াংয়ের থিসিসেও বর্ণিত হয়েছে : কম হাই-ফেন-এ-টিওন শব্দটি কম-পুট-এর দ্বারা লিখেছেন

  2. বেন্টলির কলাম, নুথের প্রোগ্রাম এবং ম্যাকিলারয়ের পর্যালোচনা (যার কেবলমাত্র একটি ছোট অংশই ইউনিক্স দর্শনের বিষয়ে ছিল) এর পূর্ববর্তী এবং পরবর্তী কলামগুলির আলোকে আরও পরিষ্কার করা হয়েছে এবং সংকলক, টিএওসিপি এবং টেক্স সহ নুথের পূর্বের অভিজ্ঞতা।

  3. প্রোগ্রামিং শৈলীতে এক্সারসাইজগুলিতে একটি সম্পূর্ণ বই রয়েছে , এই নির্দিষ্ট প্রোগ্রামের জন্য বিভিন্ন পদ্ধতির ইত্যাদি দেখায় etc.

আমার উপরের পয়েন্টগুলিতে একটি অসম্পূর্ণ ব্লগ পোস্ট রয়েছে; এটি শেষ হয়ে গেলে এই উত্তরটি সম্পাদনা করতে পারে। এদিকে, নুথের জন্মদিন উপলক্ষে (10 জানুয়ারী) যাইহোক, এই উত্তর এখানে পোস্ট করা। :-)


অসাধারণ! অবশেষে কেউ নাথের সমাধানটি পোস্ট করেছিলেন (আমি তা করার ইচ্ছা করেছি, তবে পাস্কেলে) দুর্দান্ত বিশ্লেষণ এবং অভিনয় দিয়েছিলাম যা আগের কয়েকটি সেরা পোস্টকে পরাজিত করে না, তবে অন্য সি ++ প্রোগ্রামের সাথে গতির জন্য একটি নতুন রেকর্ডও স্থাপন করে! ওয়ান্ডারফুল।
অ্যান্ড্রি মাকুখা

আমার কাছে কেবলমাত্র দুটি মন্তব্য: 1) আপনার দ্বিতীয় প্রোগ্রামটি Segmentation fault: 11ইচ্ছামত বড় শব্দ এবং ফাঁক দিয়ে পরীক্ষার ক্ষেত্রে ব্যর্থ হয় ; ২) যদিও এটি অনুভব হতে পারে যে আমি ম্যাকিল্রয়ের "সমালোচনা" এর প্রতি সহানুভূতি জানাই, তবে আমি ভাল করেই জানি যে নুথের উদ্দেশ্য কেবল তাঁর সাক্ষরিত প্রোগ্রামিং কৌশলটি প্রদর্শন করা ছিল, যখন ম্যাকিলরোয় ইঞ্জিনিয়ারিং দৃষ্টিকোণ থেকে এটির সমালোচনা করেছিলেন। ম্যাকিলরোই পরে স্বীকার করেছেন যে এটি করা মোটামুটি বিষয় নয়।
অ্যান্ড্রি মাকুখা

@ অ্যান্ড্রি মাকুখা ওহ উফ, এটাই পুনরাবৃত্তি হয়েছিল word_for; এটি এখন ঠিক করুন। হ্যাঁ ম্যাকিল্রয়, ইউনিক্স পাইপের উদ্ভাবক হিসাবে, ছোট সরঞ্জামগুলি রচনা করার ইউনিক্স দর্শনের সুসমাচারের সুযোগ নিয়েছিলেন । এটি নথের হতাশাজনকভাবে (যদি আপনি তার প্রোগ্রামগুলি পড়ার চেষ্টা করছেন) একত্রীক পদ্ধতির তুলনায় এটি একটি ভাল দর্শন, তবে অন্য প্রসঙ্গে এটি কিছুটা অন্যায়ও ছিল: আজ ইউনিক্সের পথটি বহুলভাবে পাওয়া যায়, তবে 1986 সালে সীমাবদ্ধ ছিল বেল ল্যাবস, বার্কলে, ইত্যাদিতে ("তার ফার্ম ব্যবসায়ের সেরা প্রিফাবগুলি তৈরি করে")
শ্রীভাতসার

কাজ করে! নতুন রাজার অভিনন্দন :- পি ইউনিক্স এবং নূথের জন্য, তিনি সিস্টেমটিকে খুব বেশি পছন্দ করেন বলে মনে হয় নি, কারণ বিভিন্ন সরঞ্জামের মধ্যে খুব একটা unityক্য ছিল এবং (উদাহরণস্বরূপ অনেক সরঞ্জাম আলাদা আলাদাভাবে রেজিক্স সংজ্ঞায়িত করেছেন)।
আন্দ্রে মাকুখা

1

পাইথন ঘ

একটি সহজ অভিধানের সাথে এই প্রয়োগটি Counterআমার সিস্টেমে ব্যবহার করা যেকোনটির থেকে কিছুটা দ্রুত ।

def words_from_file(filename):
    import re

    pattern = re.compile('[a-z]+')

    for line in open(filename):
        yield from pattern.findall(line.lower())


def freq(textfile, k):
    frequencies = {}

    for word in words_from_file(textfile):
        frequencies[word] = frequencies.get(word, 0) + 1

    most_frequent = sorted(frequencies.items(), key=lambda item: item[1], reverse=True)

    for i, (word, frequency) in enumerate(most_frequent):
        if i == k:
            break

        yield word, frequency


from time import time

start = time()
print('\n'.join('{}:\t{}'.format(f, w) for w,f in freq('giganovel', 10)))
end = time()
print(end - start)

1
আমি কেবলমাত্র আমার সিস্টেমে জিগানোভেল দিয়ে পরীক্ষা করতে পারতাম এবং এতে বেশ দীর্ঘ সময় লাগে (~ 90 সেকেন্ড)। gutenbergproject আইনগত কারণে জার্মানিতে অবরুদ্ধ করা হয়েছে ...
movatica

মজাদার. এটি হয় পদ্ধতিতে heapqকোনও কার্যকারিতা যুক্ত করে না Counter.most_commonবা অভ্যন্তরীণভাবেও enumerate(sorted(...))ব্যবহার heapqকরে।
অ্যান্ড্রি মাকুখা

আমি পাইথন 2 দিয়ে পরীক্ষা করেছি এবং অভিনয়টি একই রকম হয়েছিল, তাই আমার ধারণা, বাছাই করা ঠিক তত দ্রুত কাজ করে, যেমনটি Counter.most_common
অ্যান্ড্রি মাকুখা

হ্যাঁ, সম্ভবত এটি আমার সিস্টেমে কেবল ঝাঁকুনি ছিল ... কমপক্ষে এটি ধীর হয় না :) তবে রেজেক্স অনুসন্ধানগুলি অক্ষরগুলির দ্বারা পুনরাবৃত্ত হওয়ার চেয়ে অনেক দ্রুত। এটি বেশ কার্যকরভাবে প্রয়োগ করা হয়েছে বলে মনে হচ্ছে।
মোভাটিকা

1

[সি] উপসর্গের ট্রি + লিংকযুক্ত তালিকা অনুসারে

এটি ইনপুট হিসাবে দুটি আর্গুমেন্ট লাগে (পাঠ্য ফাইলের পথে এবং তালিকার জন্য বেশিরভাগ ঘন শব্দের জন্য k সংখ্যার জন্য)

আমার অন্যান্য এন্ট্রিটির ভিত্তিতে, এই সংস্করণটি কে-এর বৃহত্তর মানগুলির জন্য অনেক দ্রুত তবে কে-এর নিম্ন মানেরগুলিতে পারফরম্যান্সের একটি স্বল্প ব্যয়ে।

এটি শব্দের বর্ণগুলিতে একটি গাছের শাখা তৈরি করে, তারপরে পাতার অক্ষরে এটি একটি পাল্টা বৃদ্ধি করে। তারপরে বর্তমান পাতার কাউন্টারটি সর্বাধিক ঘন ঘন শব্দের তালিকার ক্ষুদ্রতম ঘন ঘন শব্দের চেয়ে বড় কিনা তা পরীক্ষা করে দেখুন। (তালিকার আকার হ'ল কমান্ড লাইন আর্গুমেন্টের মাধ্যমে নির্ধারিত সংখ্যা) যদি তা হয় তবে পাতার বর্ণ দ্বারা প্রতিনিধিত্ব করা শব্দটিকে সর্বাধিক ঘন ঘন একটি হিসাবে প্রচার করুন। যদি ইতিমধ্যে খুব ঘন ঘন শব্দের শব্দ থাকে, তবে শব্দটির সংখ্যা এখন আরও বেশি হলে পরবর্তী সর্বাধিক ঘন ঘন অদলবদল করুন, এভাবে তালিকাটি সাজানো থাকবে। আর কোনও অক্ষর না পড়া পর্যন্ত এই সমস্ত পুনরাবৃত্তি হয় After এর পরে বেশিরভাগ ঘন শব্দের তালিকা আউটপুট হয়।

এটি বর্তমানে প্রক্রিয়াজাতকরণের সময় আউটপুট দিতে ডিফল্ট হয় তবে অন্যান্য সাবমিশনের সাথে সঙ্গতি রাখার উদ্দেশ্যে উত্স কোডে টিমিং সংজ্ঞাটি অক্ষম করে।

// comment out TIMING if using external program timing mechanism
#define TIMING 1

// may need to increase if the source text has many unique words
#define MAX_LETTER_INSTANCES 1000000

#define false 0
#define true 1
#define null 0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef TIMING
#include <sys/time.h>
#endif

struct Letter
{
    char isTopWord;
    struct Letter* parent;
    struct Letter* higher;
    struct Letter* lower;
    char asciiCode;
    unsigned int count;
    struct Letter* nextLetters[26];
};
typedef struct Letter Letter;

int main(int argc, char *argv[]) 
{
#ifdef TIMING
    struct timeval tv1, tv2;
    gettimeofday(&tv1, null);
#endif

    int k;
    if (argc !=3 || (k = atoi(argv[2])) <= 0)
    {
        printf("Usage:\n");
        printf("      WordCount <input file path> <number of most frequent words to find>\n\n");
        return -1;
    }

    long  file_size;
    long dataLength;
    char* data;

    // read in file contents
    FILE *fptr;
    size_t read_s = 0;  
    fptr = fopen(argv[1], "rb");
    fseek(fptr, 0L, SEEK_END);
    dataLength = ftell(fptr);
    rewind(fptr);
    data = (char*)malloc((dataLength));
    read_s = fread(data, 1, dataLength, fptr);
    if (fptr) fclose(fptr);

    unsigned int chr;
    unsigned int i;

    // working memory of letters
    Letter* letters = (Letter*) malloc(sizeof(Letter) * MAX_LETTER_INSTANCES);
    memset(&letters[0], 0, sizeof( Letter) * MAX_LETTER_INSTANCES);

    // the index of the next unused letter
    unsigned int letterMasterIndex=0;

    // pesudo letter representing the starting point of any word
    Letter* root = &letters[letterMasterIndex++];

    // the current letter in the word being processed
    Letter* currentLetter = root;

    // the next letter to be processed
    Letter* nextLetter = null;
    Letter* sortedWordsStart = null;
    Letter* sortedWordsEnd = null;
    Letter* A;
    Letter* B;
    Letter* C;
    Letter* D;

    unsigned int sortedListSize = 0;


    unsigned int lowestWordCount = 0;
    unsigned int lowestWordIndex = 0;
    unsigned int highestWordCount = 0;
    unsigned int highestWordIndex = 0;

    // main loop
    for (int j=0;j<dataLength;j++)
    {
        chr = data[j]|0x20; // convert to lower case

        // is a letter?
        if (chr > 96 && chr < 123)
        {
            chr-=97; // translate to be zero indexed
            nextLetter = currentLetter->nextLetters[chr];

            // this is a new letter at this word length, intialise the new letter
            if (nextLetter == null)
            {
                nextLetter = &letters[letterMasterIndex++];
                nextLetter->parent = currentLetter;
                nextLetter->asciiCode = chr;
                currentLetter->nextLetters[chr] = nextLetter;
            }

            currentLetter = nextLetter;
        }
        // not a letter so this means the current letter is the last letter of a word (if any letters)
        else if (currentLetter!=root)
        {

            // increment the count of the full word that this letter represents
            ++currentLetter->count;

            // is this word not in the top word list?
            if (!currentLetter->isTopWord)
            {
                // first word becomes the sorted list
                if (sortedWordsStart == null)
                {
                  sortedWordsStart = currentLetter;
                  sortedWordsEnd = currentLetter;
                  currentLetter->isTopWord = true;
                  ++sortedListSize;
                }
                // always add words until list is at desired size, or 
                // swap the current word with the end of the sorted word list if current word count is larger
                else if (sortedListSize < k || currentLetter->count> sortedWordsEnd->count)
                {
                    // replace sortedWordsEnd entry with current word
                    if (sortedListSize == k)
                    {
                      currentLetter->higher = sortedWordsEnd->higher;
                      currentLetter->higher->lower = currentLetter;
                      sortedWordsEnd->isTopWord = false;
                    }
                    // add current word to the sorted list as the sortedWordsEnd entry
                    else
                    {
                      ++sortedListSize;
                      sortedWordsEnd->lower = currentLetter;
                      currentLetter->higher = sortedWordsEnd;
                    }

                    currentLetter->lower = null;
                    sortedWordsEnd = currentLetter;
                    currentLetter->isTopWord = true;
                }
            }
            // word is in top list
            else
            {
                // check to see whether the current word count is greater than the supposedly next highest word in the list
                // we ignore the word that is sortedWordsStart (i.e. most frequent)
                while (currentLetter != sortedWordsStart && currentLetter->count> currentLetter->higher->count)
                {
                    B = currentLetter->higher;
                    C = currentLetter;
                    A = B != null ? currentLetter->higher->higher : null;
                    D = currentLetter->lower;

                    if (A !=null) A->lower = C;
                    if (D !=null) D->higher = B;
                    B->higher = C;
                    C->higher = A;
                    B->lower = D;
                    C->lower = B;

                    if (B == sortedWordsStart)
                    {
                      sortedWordsStart = C;
                    }

                    if (C == sortedWordsEnd)
                    {
                      sortedWordsEnd = B;
                    }
                }
            }

            // reset the letter path representing the word
            currentLetter = root;
        }
    }

    // print out the top frequent words and counts
    char string[256];
    char tmp[256];

    Letter* letter;
    while (sortedWordsStart != null )
    {
        letter = sortedWordsStart;
        highestWordCount = letter->count;
        string[0]=0;
        tmp[0]=0;

        if (highestWordCount > 0)
        {
            // construct string of letters to form the word
            while (letter != root)
            {
                memmove(&tmp[1],&string[0],255);
                tmp[0]=letter->asciiCode+97;
                memmove(&string[0],&tmp[0],255);
                letter=letter->parent;
            }

            printf("%u %s\n",highestWordCount,string);
        }
        sortedWordsStart = sortedWordsStart->lower;
    }

    free( data );
    free( letters );

#ifdef TIMING   
    gettimeofday(&tv2, null);
    printf("\nTime Taken: %f seconds\n", (double) (tv2.tv_usec - tv1.tv_usec)/1000000 + (double) (tv2.tv_sec - tv1.tv_sec));
#endif
    return 0;
}

এটা খুবই সাজানো না ফেরৎ ট = 100,000 জন্য আউটপুট: 12 eroilk 111 iennoa 10 yttelen 110 engyt
অ্যান্ড্রি মাকুখা

আমি মনে করি কারণ সম্পর্কে আমার ধারণা আছে। আমার ধারণা হ'ল বর্তমান শব্দের পরবর্তী সর্বোচ্চ শব্দটি কিনা তা যাচাই করার সময় তালিকায় আমার স্বাপ শব্দের পুনরাবৃত্তি করতে হবে। যখন আমার সময় হবে আমি চেক করব
মোগি

হুম ভাল এটি মনে হয় যে একটি পরিবর্তন করার সহজ ফিক্স যদি কাজ করে তবে এটি কে এর বৃহত মানগুলির জন্য অ্যালগোরিদমকে উল্লেখযোগ্যভাবে ধীর করে দেয়। আমাকে আরও চতুর সমাধানের কথা ভাবতে হতে পারে।
মোগি

1

সি শার্প

এই সর্বশেষতমনেট এসডিকে নিয়ে কাজ করা উচিত ।

using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using static System.Console;

class Node {
    public Node Parent;
    public Node[] Nodes;
    public int Index;
    public int Count;

    public static readonly List<Node> AllNodes = new List<Node>();

    public Node(Node parent, int index) {
        this.Parent = parent;
        this.Index = index;
        AllNodes.Add(this);
    }

    public Node Traverse(uint u) {
        int b = (int)u;
        if (this.Nodes is null) {
            this.Nodes = new Node[26];
            return this.Nodes[b] = new Node(this, b);
        }
        if (this.Nodes[b] is null) return this.Nodes[b] = new Node(this, b);
        return this.Nodes[b];
    }

    public string GetWord() => this.Index >= 0 
        ? this.Parent.GetWord() + (char)(this.Index + 97)
        : "";
}

class Freq {
    const int DefaultBufferSize = 0x10000;

    public static void Main(string[] args) {
        var sw = Stopwatch.StartNew();

        if (args.Length < 2) {
            WriteLine("Usage: freq.exe {filename} {k} [{buffersize}]");
            return;
        }

        string file = args[0];
        int k = int.Parse(args[1]);
        int bufferSize = args.Length >= 3 ? int.Parse(args[2]) : DefaultBufferSize;

        Node root = new Node(null, -1) { Nodes = new Node[26] }, current = root;
        int b;
        uint u;

        using (var fr = new FileStream(file, FileMode.Open))
        using (var br = new BufferedStream(fr, bufferSize)) {
            outword:
                b = br.ReadByte() | 32;
                if ((u = (uint)(b - 97)) >= 26) {
                    if (b == -1) goto done; 
                    else goto outword;
                }
                else current = root.Traverse(u);
            inword:
                b = br.ReadByte() | 32;
                if ((u = (uint)(b - 97)) >= 26) {
                    if (b == -1) goto done;
                    ++current.Count;
                    goto outword;
                }
                else {
                    current = current.Traverse(u);
                    goto inword;
                }
            done:;
        }

        WriteLine(string.Join("\n", Node.AllNodes
            .OrderByDescending(count => count.Count)
            .Take(k)
            .Select(node => node.GetWord())));

        WriteLine("Self-measured milliseconds: {0}", sw.ElapsedMilliseconds);
    }
}

এখানে একটি নমুনা আউটপুট।

C:\dev\freq>csc -o -nologo freq-trie.cs && freq-trie.exe giganovel 100000
e
ihit
ah
ist
 [... omitted for sanity ...]
omaah
aanhele
okaistai
akaanio
Self-measured milliseconds: 13619

প্রথমদিকে, আমি স্ট্রিং কীগুলির সাহায্যে একটি অভিধান ব্যবহার করার চেষ্টা করেছি, তবে এটি খুব ধীর ছিল। আমি মনে করি এটি কারণ। নেট স্ট্রিংগুলি অভ্যন্তরীণভাবে একটি 2-বাইট এনকোডিং দ্বারা প্রতিনিধিত্ব করা হয় যা এই অ্যাপ্লিকেশনটির জন্য একরকম অপব্যয়। সুতরাং আমি কেবল খাঁটি বাইট, এবং একটি কুরুচিযুক্ত গোটো-স্টাইলের রাষ্ট্রীয় মেশিনে স্যুইচ করেছি। কেস রূপান্তরটি কিছুটা অপারেটর। বিয়োগের পরে একক তুলনায় ক্যারেক্টার রেঞ্জ চেকিং করা হয়। আমি চূড়ান্ত সাজানোর জন্য কোন প্রয়াস ব্যয় করি নি যেহেতু আমি এটি রানটাইমের 0.1% এরও কম ব্যবহার করছি।

ফিক্স: অ্যালগরিদম মূলত সঠিক ছিল, তবে এটি শব্দের সমস্ত উপসর্গ গণনা করে মোট শব্দের অতিরিক্ত রিপোর্ট করেছিল। যেহেতু মোট শব্দ গণনা সমস্যার প্রয়োজন নয়, তাই আমি সেই আউটপুটটি সরিয়েছি। সমস্ত কে শব্দের আউটপুট করার জন্য, আমি আউটপুটও সামঞ্জস্য করেছি। আমি অবশেষে string.Join()একবারে পুরো তালিকাটি ব্যবহার এবং তারপরে স্থির করেছিলাম । আশ্চর্যজনকভাবে এটি আমার মেশিনে প্রায় এক সেকেন্ড দ্রুত যা প্রতি শব্দ 100k এর জন্য আলাদাভাবে লিখছে।


1
খুব চিত্তাকর্ষক! আমি আপনার বিটওয়াইজ tolowerএবং একক তুলনা কৌশল পছন্দ করি। তবে, আপনার প্রোগ্রামটি কেন প্রত্যাশার চেয়ে বেশি স্বতন্ত্র শব্দের প্রতিবেদন করে তা আমি বুঝতে পারি না। এছাড়াও, মূল সমস্যার বিবরণ অনুসারে, প্রোগ্রামটির সমস্ত কম শব্দকে ফ্রিকোয়েন্সি হ্রাস ক্রমে আউটপুট করা দরকার, সুতরাং আমি আপনার প্রোগ্রামটি শেষ পরীক্ষার দিকে গন্য করিনি, যার জন্য প্রায়শই ঘন ঘন শব্দগুলির আউটপুট প্রয়োজন to
অ্যান্ড্রি মাকুখা

@ অ্যান্ড্রি মাকুখা: আমি দেখতে পাচ্ছি যে আমি চূড়ান্ত গণনাতে এমন শব্দের উপসর্গগুলিও গণনা করছি। আমি সমস্ত আউটপুট লিখতে এড়িয়ে গেছি কারণ উইন্ডোতে কনসোল আউটপুট বেশ ধীর। আমি কি কোনও ফাইলে আউটপুট লিখতে পারি?
পুনরাবৃত্তিমূলক

দয়া করে এটি স্ট্যান্ডার্ড আউটপুট প্রিন্ট করুন। কে = 10 এর জন্য এটি যে কোনও মেশিনে দ্রুত হওয়া উচিত। আপনি কমান্ড লাইন থেকে আউটপুট কোনও ফাইলে পুনর্নির্দেশ করতে পারেন। এই মত
অ্যান্ড্রি মাকুখা

@ অ্যান্ড্রি মাকুখা: আমি বিশ্বাস করি আমি সমস্ত সমস্যার সমাধান করেছি। আমি বেশি রানটাইম ব্যয় ছাড়াই প্রয়োজনীয় সমস্ত আউটপুট উত্পাদন করার একটি উপায় খুঁজে পেয়েছি।
পুনরাবৃত্ত

এই আউটপুট দ্রুত জ্বলছে! খুব সুন্দর. অন্যান্য সমাধানের মতো আমিও আপনার প্রোগ্রামটি ফ্রিকোয়েন্সি গণনাগুলি মুদ্রণের জন্য পরিবর্তন করেছি।
অ্যান্ড্রি মাকুখা

1

সাথে রুবি ২..0.০-পূর্বরূপ ১ tally

রুবির সর্বশেষ সংস্করণে একটি নতুন পদ্ধতি বলা হয়েছে tally। থেকে রিলিজ নোট :

Enumerable#tallyযোগ করা হলো. এটি প্রতিটি উপাদানের উপস্থিতি গণনা করে।

["a", "b", "c", "b"].tally
#=> {"a"=>1, "b"=>2, "c"=>1}

এটি আমাদের জন্য প্রায় পুরো কাজটি সমাধান করে। আমাদের প্রথমে ফাইলটি পড়া এবং পরে সর্বোচ্চটি সন্ধান করা দরকার।

পুরো জিনিসটি এখানে:

k = ARGV.shift.to_i

pp ARGF
  .each_line
  .lazy
  .flat_map { @1.scan(/[A-Za-z]+/).map(&:downcase) }
  .tally
  .max_by(k, &:last)

সম্পাদনা: যুক্ত হয়েছে k একটি কমান্ড লাইন যুক্তি হিসাবে করা হয়েছে

এটি ruby k filename.rb input.txtরুবির 2.7.0-পূর্বরূপ 1 সংস্করণ ব্যবহার করে চালানো যেতে পারে । এটি রিলিজ নোট পৃষ্ঠাগুলির বিভিন্ন লিঙ্কগুলি থেকে ডাউনলোড করা যেতে পারে, বা rbenv ব্যবহার করে ইনস্টল করা যেতে পারেrbenv install 2.7.0-dev

পুরানো কম্পিউটারে আমার নিজের বেট-আপ চালানোর উদাহরণ:

$ time ruby bentley.rb 10 ulysses64 
[["the", 968832],
 ["of", 528960],
 ["and", 466432],
 ["a", 421184],
 ["to", 322624],
 ["in", 320512],
 ["he", 270528],
 ["his", 213120],
 ["i", 191808],
 ["s", 182144]]

real    0m17.884s
user    0m17.720s
sys 0m0.142s

1
আমি উত্স থেকে রুবি ইনস্টল। এটি আপনার মেশিনে প্রায় 15 গিগাবাইটের তুলনায় দ্রুত গতিতে চলে।
অ্যান্ড্রি মাকুখা
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.