【问题标题】:Sorting characters in a string first by frequency and then alphabetically首先按频率对字符串中的字符进行排序,然后按字母顺序排序
【发布时间】:2014-01-10 20:51:52
【问题描述】:

给定一个字符串,我试图计算字符串中每个字母的出现次数,然后将它们的出现频率从高到低排序。然后,对于出现次数相似的字母,我必须按字母顺序对它们进行排序。

这是我目前能够做到的:

  • 我创建了一个大小为 26 的 int 数组,对应于字母表中的 26 个字母,其中各个值表示它在句子中出现的次数
  • 我将这个数组的内容推入了一个由intchar 组成的对向量vint 表示频率,char 表示实际字母)
  • 我使用std::sort(v.begin(), v.end()); 对这个向量对进行了排序

在显示频率计数时,我只是使用了一个从最后一个索引开始的 for 循环来显示从最高到最低的结果。但是,对于那些频率相似的字母,我遇到了问题,因为我需要它们按字母顺序显示。我尝试使用嵌套 for 循环,内循环从最低索引开始,并使用条件语句检查其频率是否与外循环相同。这似乎可行,但我的问题是我似乎无法弄清楚如何控制这些循环以避免冗余输出。要理解我的意思,请查看此示例输出:

Enter a string: hello world

Pushing the array into a vector pair v:
d = 1
e = 1
h = 1
l = 3
o = 2
r = 1
w = 1


Sorted first according to frequency then alphabetically:
l = 3
o = 2
d = 1
e = 1
h = 1
r = 1
w = 1
d = 1
e = 1
h = 1
r = 1
d = 1
e = 1
h = 1
d = 1
e = 1
d = 1
Press any key to continue . . .

如您所见,如果不是因为不正确的 for 循环带来的冗余输出,那本来还可以。

如果您能就我的问题提出更有效或更好的实现,那么我将非常感激,只要它们不太复杂或太高级,因为我只是一个 C++ 初学者。

如果你需要看我的代码,这里是:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    cout<<"Enter a string: ";
    string input;
    getline(cin, input);

    int letters[26]= {0};

    for (int x = 0; x < input.length(); x++) {
        if (isalpha(input[x])) {
            int c = tolower(input[x] - 'a');
            letters[c]++;
        }
    }

    cout<<"\nPushing the array into a vector pair v: \n";
    vector<pair<int, char> > v;

    for (int x = 0; x < 26; x++) {
        if (letters[x] > 0) {
            char c = x + 'a';
            cout << c << " = " << letters[x] << "\n";
            v.push_back(std::make_pair(letters[x], c));
        }
    }

    // Sort the vector of pairs.
    std::sort(v.begin(), v.end());

    // I need help here!
    cout<<"\n\nSorted first according to frequency then alphabetically: \n";
    for (int x = v.size() - 1 ; x >= 0; x--) {
        for (int y = 0; y < x; y++) {
            if (v[x].first == v[y].first) {
                cout << v[y].second<< " = " << v[y].first<<endl;
            }
        }
        cout << v[x].second<< " = " << v[x].first<<endl;
    }

    system("pause");
    return 0;
}

【问题讨论】:

  • 您可以通过使用自定义 比较器 运行您的排序一步来解决此问题(请参阅en.cppreference.com/w/cpp/algorithm/sort 以获取示例)。
  • 你也可以使用map&lt;char, int&gt;
  • @Gabriel L.,但即使我使用地图,我仍然无法直接对其值进行排序,对吗?谢谢!

标签: c++ sorting vector character frequency


【解决方案1】:

你可以简化很多,分两步:

  1. 先用一个map统计字符串中每个字符出现的次数:

    std::unordered_map<char, unsigned int> count;
    
    for( char character : string )
        count[character]++;
    
  2. 使用该地图的值作为比较标准:

    std::sort( std::begin( string ) , std::end( string ) , 
               [&]( char lhs , char rhs )
               {
                   return count[lhs] < count[rhs];
               }
             ); 
    

Here 是在 ideone 上运行的一个工作示例。

【讨论】:

  • 此示例按字符出现正确计数,但此后不按字母顺序排序!
  • 这段代码有一个错误,我不知道排序到底在哪里失败,但是试试这个字符串“AZBIPTOFTJCJJIK” 输出是 JJJJITTIAZBPOFCK 看到两个不连续的地方插入了“I”字符!
【解决方案2】:

如果您想要最高频率然后最低字母,一种简单的方法是存储频率的负值,然后在排序后将其取反。更有效的方法是更改​​用于排序的函数,但这有点棘手:

struct sort_helper {
   bool operator()(std::pair<int,char> lhs, std::pair<int,char> rhs) const{
     return std::make_pair(-lhs.first,lhs.second)<std::make_pair(-rhs.first,rhs.second);
   }
};
std::sort(vec.begin(),vec.end(),sort_helper());

【讨论】:

    【解决方案3】:

    (代表 OP 发布。)

    感谢 Stack Overflow 上出色的人们的回复,我终于能够解决我的问题。这是我的最终代码,以防万一有人感兴趣或将来可能被困在同一条船上的人参考:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    struct Letters
    {
        Letters() : freq(0){}
        Letters(char letter,int freq) {
            this->freq = freq;
            this->letter = letter;
        }
        char letter;
        int freq;
    };
    
    bool Greater(const Letters& a, const Letters& b)
    {
        if(a.freq == b.freq)
            return a.letter < b.letter;
    
        return a.freq > b.freq;
    }
    
    int main () {
    
        cout<<"Enter a string: ";
        string input;
        getline(cin, input);
    
        vector<Letters> count;
        int letters[26]= {0};
    
        for (int x = 0; x < input.length(); x++) {
            if (isalpha(input[x])) {
                int c = tolower(input[x] - 'a');
                letters[c]++;
            }
        }
    
        for (int x = 0; x < 26; x++) {
            if (letters[x] > 0) {
                char c = x + 'a';
                count.push_back(Letters(c, letters[x]));
            }
        }
    
        cout<<"\nUnsorted list..\n";
        for (int x = 0 ; x < count.size(); x++) {
            cout<<count[x].letter<< " = "<< count[x].freq<<"\n";
        }
    
        std::sort(count.begin(),count.end(),Greater);
    
        cout<<"\nSorted list according to frequency then alphabetically..\n";
        for (int x = 0 ; x < count.size(); x++) {
            cout<<count[x].letter<< " = "<< count[x].freq<<"\n";
        }
    
        system("pause");
        return 0;
    }
    

    示例输出:

    Enter a string: hello world
    
    Unsorted list..
    d = 1
    e = 1
    h = 1
    l = 3
    o = 2
    r = 1
    w = 1
    
    Sorted list according to frequency then alphabetically..
    l = 3
    o = 2
    d = 1
    e = 1
    h = 1
    r = 1
    w = 1
    Press any key to continue . . .
    

    我基本上只是遵循@OliCharlesworth 的建议,并通过本指南的帮助实现了一个自定义比较器:A Function Pointer as Comparison Function

    虽然我很确定我的代码仍然可以提高效率,但我仍然对结果感到非常满意。

    【讨论】:

      【解决方案4】:
      // CODE BY VIJAY JANGID in C language
      // Using arrays, Time complexity - ( O(N) * distinct characters ) 
      // Efficient answer
      
      #include <stdio.h>
      
      int main() {
      
          int iSizeFrequencyArray= 58;
          //  122 - 65 = 57  for A to z
          int frequencyArray[iSizeFrequencyArray]; 
      
          int iIndex = 0;
      
          // Initializing frequency to zero for all
          for (iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++) {
              frequencyArray[iIndex] = 0;
          }
      
          int iMyStringLength = 1000;
          char chMyString[iMyStringLength];
      
          // take input for the string
          scanf("%s", &chMyString);
      
          // calculating length
          int iSizeMyString;
          while(chMyString[++iSizeMyString]);
      
          // saving each character frequency in the freq. array
          for (iIndex = 0; iIndex < iSizeMyString; iIndex++) {
              int currentChar = chMyString[iIndex];
              frequencyArray[currentChar - 65]++;
          }
      
          /* // To print the frequency of each alphabet
          for (iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++) {
              char currentChar = iIndex + 65;
              printf("\n%c - %d", currentChar, frequencyArray[iIndex ]);
          }
          */
      
          int lowestDone = 0, lowest = 0, highestSeen = 0;
      
          for( iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++ ) {
              if(frequencyArray[iIndex] > highestSeen) {
                  highestSeen = frequencyArray[iIndex];
              }
          }
      
          // assigning sorted values to the current array
          while (lowest != highestSeen) {
      
              // calculating lowest frequency
              for( iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++ ) {
      
                  if( frequencyArray[iIndex] > lowestDone &&
                     frequencyArray[iIndex] < lowest) {
                      lowest = frequencyArray[iIndex]; // taking lowest value
                  }
              }
      
              // printing that frequency
              for( iIndex =0; iIndex < iSizeFrequencyArray; iIndex++ ) {
      
                  // print that work for that times
                  if(frequencyArray[iIndex] == lowest){
                      char currentChar = iIndex + 65;
                      int iIndex3;
                      for(iIndex3 = 0; iIndex3 < lowest; iIndex3++){
                          printf("%c", currentChar);
                      }
                  }
              }
      
              // now that is done, move to next lowest
              lowestDone = lowest;
      
              // reset to highest value, to get the next lowest one
              lowest = highestSeen+1;
      
          }
      
          return 0;
      
      }
      

      说明:

      1. 首先创建数组来存储重复大小 (112 - 65) 以存储从 A 到 z 的 asci 字符。
      2. 通过在每次出现时递增来存储每个字符的频率。
      3. 现在找到最高频率。
      4. 运行一个循环,条件为(最低!= 最高),其中最低 = 0。
      5. 现在在每次迭代中打印频率等于最低的字符。它们将自动按字母顺序排列。
      6. 最后找到下一个更高的频率并打印,以此类推。
      7. 当最低到达最高时,然后打破循环。

      【讨论】:

        【解决方案5】:

        按照@Manu343726 的建议,使用unordered_map 来计算字符数是个好主意。但是,为了产生您的排序输出,还需要另一个步骤。

        我的解决方案也在C++11 中并使用lambda expression。这样,您既不需要定义自定义结构,也不需要定义比较函数。代码差不多完成了,我只是跳过阅读输入:

        #include <unordered_map>
        #include <iostream>
        #include <set>
        
        int main() {
            string input = "hello world";
        
            unordered_map<char, unsigned int> count;
            for (char character : input)
                if (character >= 'a' && character <= 'z')
                    count[character]++;
        
            cout << "Unsorted list:" << endl;
            for (auto const &kv : count)
                cout << kv.first << " = " << kv.second << endl;
        
            using myPair = pair<char, unsigned int>;
            auto comp = [](const myPair& a, const myPair& b) {
                return (a.second > b.second || a.second == b.second && a.first < b.first);
            };
            set<myPair, decltype(comp)> sorted(comp);
            for(auto const &kv : count)
                sorted.insert(kv);
        
            cout << "Sorted list according to frequency then alphabetically:" << endl;
            for (auto const &kv : sorted)
                cout << kv.first << " = " << kv.second << endl;
        
            return 0;
        }
        

        输出:

        未排序的列表:
        r = 1
        h = 1
        e = 1
        d = 1
        o = 2
        w = 1
        l = 3
        按频率排序列表,然后按字母顺序排列:
        l = 3
        o = 2
        d = 1
        e = 1
        h = 1
        r = 1
        w = 1

        注意 1:与其将 unordered_map 中的每个元素插入到 set 中,不如使用函数 std::transformstd:copy 更有效,但我的代码至少很短。

        注意 2:与其使用自定义排序的 set 来保持您想要的顺序,不如使用成对的向量并最终对其进行一次排序可能更有效,但您的解决方案已经与此类似。

        Code on Ideone

        【讨论】:

          【解决方案6】:
          #include<stdio.h>
          
          // CODE BY AKSHAY BHADERIYA
          
          char iFrequencySort (char iString[]);
          void vSort (int arr[], int arr1[], int len);
          
          int
          main ()
          {
            int iLen, iCount;
            char iString[100], str[100];
            printf ("Enter a string : ");
            scanf ("%s", iString);
          
            iFrequencySort (iString);
          
            return 0;
          }
          
          
          char
          iFrequencySort (char iString[])
          {
            int iFreq[100] = { 0 };
            int iI, iJ, iK, iAsc, iLen1 = 0, iLen = 0;
          
            while (iString[++iLen]);
          
            int iOccurrence[94];
            int iCharacter[94];
          
            for (iI = 0; iI < iLen; iI++)
              {               //frequency of the characters
                iAsc = (int) iString[iI];
                iFreq[iAsc - 32]++;
              }
          
          
          
            for (iI = 0, iJ = 0; iI < 94; iI++)
              {               //the characters and occurrence arrays
                if (iFreq[iI] != 0)
              {
                iCharacter[iJ] = iI;
                iOccurrence[iJ] = iFreq[iI];
                iJ++;
              }
              }
            iLen1 = iJ;
          
            vSort (iOccurrence, iCharacter, iLen1);   //sorting both arrays
          
            /*letter array consists only the index of iFreq array.
               Converting it to the ASCII value of corresponding character */
            for (iI = 0; iI < iLen1; iI++)
              {
                iCharacter[iI] += 32;
              }
            iK = 0;
            for (iI = 0; iI < iLen1; iI++)
              {               //characters into original string
                for (iJ = 0; iJ < iOccurrence[iI]; iJ++)
              {
                iString[iK++] = (char) iCharacter[iI];
              }
              }
            printf ("%s", iString);
          }
          
          void
          vSort (int iOccurrence[], int iCharacter[], int len)
          {
            int iI, iJ, iTemp;
            for (iI = 0; iI < len - 1; iI++)
              {
                for (iJ = iI + 1; iJ < len; iJ++)
              {
                if (iOccurrence[iI] > iOccurrence[iJ])
                  {
                    iTemp = iOccurrence[iI];
                    iOccurrence[iI] = iOccurrence[iJ];
                    iOccurrence[iJ] = iTemp;
          
                    iTemp = iCharacter[iI];
                    iCharacter[iI] = iCharacter[iJ];
                    iCharacter[iJ] = iTemp;
                  }
              }
              }
          }
          

          【讨论】:

            【解决方案7】:

            给出一个答案,一个被接受。我想给出一个额外的答案,显示此任务的标准方法。

            通常需要先对事物进行计数,然后再取回它们的排名或某些最高值或其他信息。

            最常见的解决方案之一是为此使用所谓的关联容器,特别是std::map,甚至更好的是std::unordered_map。这是因为我们需要一个键值,上面描述的方式是一个字母和一个关联的值,这里是这个字母的计数。钥匙是独一无二的。同一个字母不能超过一个。这当然没有任何意义。

            关联容器通过键值访问元素非常有效。

            好的,有 2 个。 std::mapstd::unordered_map。一种使用树以排序方式存储键,另一种使用快速散列算法访问键值。由于我们稍后对排序的键不感兴趣,但对出现的排序计数感兴趣,我们可以选择std::unordred_map。另一个好处是,这将使用提到的哈希算法快速访问密钥。

            地图还有一个巨大的优势。有一个索引运算符[],它对于键值看起来非常快。如果找到,它将返回对与键关联的值的引用。如果没有找到,它将创建一个键并使用默认值(在我们的例子中为 0)初始化它的值。然后计算任意键就像map[key]++ 一样简单。

            但是,后来,我们在这里经常听到:但它必须按计数排序。这当然不起作用,因为我的计数有重复的值,并且地图只能包含唯一的键值。所以,不可能。

            解决方案是使用第二个关联容器std::multiset,它可以有更多相同的键和自定义排序运算符,我们可以在其中根据值进行排序。在这里,我们不是将键和值存储为 2 个元素,而是将两个值存储为 std::pair。我们按对的第二部分排序。

            首先我们不能使用std::multi:set,因为我们需要唯一的键(在本例中是字母)。

            上述方法为我们提供了极大的灵活性和易用性。我们基本上可以用这个算法计算任何东西

            例如可以看下面的简洁代码:

            #include <iostream>
            #include <string>
            #include <utility>
            #include <set>
            #include <unordered_map>
            #include <type_traits>
            #include <cctype>
            
            // ------------------------------------------------------------
            // Create aliases. Save typing work and make code more readable
            using Pair = std::pair<char, unsigned int>;
            
            // Standard approach for counter
            using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;
            
            // Sorted values will be stored in a multiset
            struct Comp { bool operator ()(const Pair& p1, const Pair& p2) const { return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second; } };
            using Rank = std::multiset<Pair, Comp>;
            // ------------------------------------------------------------
            
            
            // --------------------------------------------------------------------------------------
            // Compact function to calculate the frequency of charcters and then get their rank
            Rank getRank(std::string& text) {
            
                // Definition of our counter
                Counter counter{};
            
                // Iterate over all charcters in text and count their frequency
                for (const char c : text) if (std::isalpha(c)) counter[char(std::tolower(c))]++;
                
                // Return ranks,sorted by frequency and then sorted by character
                return { counter.begin(), counter.end() };
            }
            // --------------------------------------------------------------------------------------
            // Test, driver code
            int main() {
                // Get a string from the user
                if (std::string text{}; std::getline(std::cin, text))
            
                    // Calculate rank and show result
                    for (const auto& [letter, count] : getRank(text))
                        std::cout << letter << " = " << count << '\n';
            }
            

            请查看使用的最少语句。非常优雅。


            但我们经常看到数组被用作关联容器。它们还有一个索引(一个键)和一个值。缺点可能是未使用密钥的空间开销。此外,这只适用于已知量级的东西。例如 26 个字母。其他国家的字母可能有更多或更少的字母。那么这种解决方案就没有那么灵活了。反正也经常用也OK。

            因此,您的解决方案可能稍微复杂一些,但当然仍然有效。

            让我再举一个例子来获取任何容器的最高值。在这里您将看到,这样的解决方案是多么灵活。

            对不起,它有点高级。 . .

            #include <iostream>
            #include <utility>
            #include <unordered_map>
            #include <queue>
            #include <vector>
            #include <iterator>
            #include <type_traits>
            #include <string>
            
            
            // Helper for type trait We want to identify an iterable container ----------------------------------------------------
            template <typename Container>
            auto isIterableHelper(int) -> decltype (
                std::begin(std::declval<Container&>()) != std::end(std::declval<Container&>()),     // begin/end and operator !=
                ++std::declval<decltype(std::begin(std::declval<Container&>()))&>(),                // operator ++
                void(*std::begin(std::declval<Container&>())),                                      // operator*
                void(),                                                                             // Handle potential operator ,
                std::true_type{});
            template <typename T>
            std::false_type isIterableHelper(...);
            
            // The type trait -----------------------------------------------------------------------------------------------------
            template <typename Container>
            using is_iterable = decltype(isIterableHelper<Container>(0));
            
            // Some Alias names for later easier reading --------------------------------------------------------------------------
            template <typename Container>
            using ValueType = std::decay_t<decltype(*std::begin(std::declval<Container&>()))>;
            template <typename Container>
            using Pair = std::pair<ValueType<Container>, size_t>;
            template <typename Container>
            using Counter = std::unordered_map<ValueType<Container>, size_t>;
            template <typename Container>
            using UnderlyingContainer = std::vector<Pair<Container>>;
            
            // Predicate Functor
            template <class Container> struct LessForSecondOfPair {
                bool operator () (const Pair<Container>& p1, const Pair<Container>& p2) { return p1.second < p2.second; }
            };
            template <typename Container>
            using MaxHeap = std::priority_queue<Pair<Container>, UnderlyingContainer<Container>, LessForSecondOfPair<Container>>;
            
            
            // Function to get most frequent used number in any Container ---------------------------------------------------------
            template <class Container>
            auto topFrequent(const Container& data) {
            
                if constexpr (is_iterable<Container>::value) {
            
                    // Count all occurences of data
                    Counter<Container> counter{};
                    for (const auto& d : data) counter[d]++;
            
                    // Build a Max-Heap
                    MaxHeap<Container> maxHeap(counter.begin(), counter.end());
            
                    // Return most frequent number
                    return maxHeap.top().first;
                }
                else
                    return data;
            }
            // Test
            int main() {
                std::vector testVector{ 1,2,2,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,6,7 };
                std::cout << "Most frequent is: " << topFrequent(testVector) << "\n";
            
                double cStyleArray[] = { 1.1, 2.2, 2.2, 3.3, 3.3, 3.3 };
                std::cout << "Most frequent is: " << topFrequent(cStyleArray) << "\n";
            
                std::string s{ "abbcccddddeeeeeffffffggggggg" };
                std::cout << "Most frequent is: " << topFrequent(s) << "\n";
            
                double value = 12.34;
                std::cout << "Most frequent is: " << topFrequent(value) << "\n";
            
                return 0;
            }
            

            【讨论】:

              猜你喜欢
              • 2020-02-06
              • 1970-01-01
              • 1970-01-01
              • 2021-12-28
              • 2021-02-21
              • 1970-01-01
              • 2013-06-29
              • 2022-01-01
              • 1970-01-01
              相关资源
              最近更新 更多