【问题标题】:String and character mapping question for the guru's out there大师的字符串和字符映射问题
【发布时间】:2011-01-15 08:15:50
【问题描述】:

这是一个让我难过的问题(明智的解决方案):

给定一个 str S,应用字符映射 Cm = {a=(m,o,p),d=(q,u),...} 并使用 C 或 C++ 打印出所有可能的组合。

字符串可以是任意长度,并且字符映射的数量是不同的,并且不会有任何映射映射到另一个映射(从而避免循环依赖)。

例如:字符串abba 与映射a=(e,o), d=(g,h), b=(i) 将打印:

abba,ebba,obba,abbe,abbo,ebbe,ebbo,obbe,obbo,aiba,aiia,abia,eiba,eiia,...... 

【问题讨论】:

  • 不,不是功课,帮助团队在紧迫的期限内完成现实生活中的可交付成果。我是一个双 E 嵌入式人,不熟悉数据结构等......任何帮助将不胜感激,甚至指向特定方向。想递归,但感觉不对。
  • @Gio:好的,映射的最大长度是多少?它们是固定的,即 2 个可能的映射吗?就像你上面的例子一样?
  • 最大字符串长度 = 32,最大映射数 = 8,每个映射的最大字符数 = 4。显然,这不是一成不变的,因为涉及到营销人员......叹息。因此,算法线索将不胜感激。我们可以根据需要处理内存问题或其他调整。去开会了。
  • 您准备好获得数十亿的字符串了吗?如果字符串中的每个字符都有一个备用字符,那么一个 32 个字符的字符串将生成超过 40 亿个字符串。
  • 好吧,我的程序在 2.2GHz Core2 duo 上在 6.1 秒内生成 6.12 亿个字符串,输入 'aeo dgh bi' abdabdabdabdabdabdabd

标签: c++ c string mapping character


【解决方案1】:

绝对有可能,不是很困难...但这肯定会产生很多字符串。

首先要说明的是,您事先知道它将生成多少个字符串,因此很容易进行一些完整性检查:)

第二个:听起来递归解决方案很容易(就像许多遍历问题一样)。

class CharacterMapper
{
public:
  CharacterMapper(): mGenerated(), mMapped()
  {
    for (int i = -128, max = 128; i != max; ++i)
      mMapped[i].push_back(i); // 'a' is mapped to 'a' by default
  }

  void addMapped(char origin, char target)
  {
    std::string& m = mMapped[origin];
    if (m.find(target) == std::string::npos) m.push_back(target);
  } // addMapped

  void addMapped(char origin, const std::string& target)
  {
    for (size_t i = 0, max = target.size(); i != max; ++i) this->addMapped(origin, target[i]);
  } // addMapped

  void execute(const std::string& original)
  {
    mGenerated.clear();
    this->next(original, 0);
    this->sanityCheck(original);
    this->print(original);
  }

private:
  void next(std::string original, size_t index)
  {
    if (index == original.size())
    {
      mGenerated.push_back(original);
    }
    else
    {
      const std::string& m = mMapped[original[index]];
      for (size_t i = 0, max = m.size(); i != max; ++i)
        this->next( original.substr(0, index) + m[i] + original.substr(index+1), index+1 );
    }
  } // next

  void sanityCheck(const std::string& original)
  {
    size_t total = 1;
    for (size_t i = 0, max = original.size(); i != max; ++i)
      total *= mMapped[original[i]].size();

    if (total != mGenerated.size())
      std::cout << "Failure: should have found " << total << " words, found " << mGenerated.size() << std::endl;
  }

  void print(const std::string& original) const
  {
    typedef std::map<char, std::string>::const_iterator map_iterator;
    typedef std::vector<std::string>::const_iterator vector_iterator;

    std::cout << "Original: " << original << "\n";

    std::cout << "Mapped: {";
    for (map_iterator it = mMapped.begin(), end = mMapped.end(); it != end; ++it)
      if (it->second.size() > 1) std::cout << "'" << it->first << "': '" << it->second.substr(1) << "'";
    std::cout << "}\n";

    std::cout << "Generated:\n";
    for (vector_iterator it = mGenerated.begin(), end = mGenerated.end(); it != end; ++it)
      std::cout << "  " << *it << "\n";
  }

  std::vector<std::string> mGenerated;
  std::map<char, std::string> mMapped;
}; // class CharacterMapper


int main(int argc, char* argv[])
{
  CharacterMapper mapper;
  mapper.addMapped('a', "eo");
  mapper.addMapped('d', "gh");
  mapper.addMapped('b', "i");
  mapper.execute("abba");
}

这是输出:

Original: abba
Mapped: {'a': 'eo''b': 'i''d': 'gh'}
Generated:
  abba
  abbe
  abbo
  abia
  abie
  abio
  aiba
  aibe
  aibo
  aiia
  aiie
  aiio
  ebba
  ebbe
  ebbo
  ebia
  ebie
  ebio
  eiba
  eibe
  eibo
  eiia
  eiie
  eiio
  obba
  obbe
  obbo
  obia
  obie
  obio
  oiba
  oibe
  oibo
  oiia
  oiie
  oiio

是的,相当冗长,但有很多不直接参与计算(初始化、检查、打印)。核心方法是next,它实现了递归。

【讨论】:

    【解决方案2】:

    编辑:这应该是最快和最简单的算法。有些人可能会争论风格或便携性;我认为这对于嵌入式类型的东西来说是完美的,而且我已经在它上面花了足够长的时间。我把原件留在下面。

    这使用一个数组进行映射。符号位用于表示一个映射循环的结束,因此如果要使用完整的unsigned 范围,数组类型必须大于映射类型。

    在 2.2GHz Core2 上生成 231M 串/秒或约 9.5 个周期/串。测试条件和用法如下。

    #include <iostream>
    using namespace std;
    
    int const alphabet_size = CHAR_MAX+1;
    typedef int map_t; // may be char or short, small performance penalty
    int const sign_bit = 1<< CHAR_BIT*sizeof(map_t)-1;
    typedef map_t cmap[ alphabet_size ];
    
    void CreateMap( char *str, cmap &m ) {
        fill( m, m+sizeof(m)/sizeof(*m), 0 );
        char *str_end = strchr( str, 0 ) + 1;
        str_end[-1] = ' '; // space-terminated strings
        char prev = ' ';
        for ( char *pen = str; pen != str_end; ++ pen ) {
            if ( * pen == ' ' ) {
                m[ prev ] |= sign_bit;
                prev = 0;
            }
            m[ * pen ] = * pen;
            if ( prev != ' ' ) swap( m[prev], m[ *pen ] );
            prev = *pen;
        }
        for ( int mx = 0; mx != sizeof(m)/sizeof(*m); ++ mx ) {
            if ( m[mx] == 0 ) m[mx] = mx | sign_bit;
        }
    }
    
    bool NextMapping( char *s, char *s_end, cmap &m ) {
        for ( char *pen = s; pen != s_end; ++ pen ) {
            map_t oldc = *pen, newc = m[ oldc ];
            * pen = newc & sign_bit-1;
            if ( newc >= 0 ) return true;
        }
        return false;
    }
    
    int main( int argc, char **argv ) {
        uint64_t cnt = 0;
        cmap m;
        CreateMap( argv[1], m );
        char *s = argv[2], *s_end = strchr( s, 0 );
        do {
            ++ cnt;
        } while ( NextMapping( s, s_end, m ) );
        cerr << cnt;
        return 0;
    }
    

    原文:

    没有我想要的那么短或健壮,但这里有一些东西。

    • 要求输入字符串始终包含每个替换集中按字母顺序排列的第一个字母
    • 执行maptool 'aeo dgh bi' abbd
    • 输出按逆字典顺序排列
    • 性能约为 22 个周期/串(100M 串/秒,2.2 GHz Core2)
      • 但我的平台正试图巧妙地使用 strings,从而减慢速度
      • 如果我将其改为使用 char* 字符串,它会以 142M 字符串/秒(~15.5 个周期/字符串)的速度运行
    • 应该可以更快地使用char[256] 映射表和另一个char[256] 指定哪些字符结束循环。

    地图数据结构是一个链接到循环列表中的节点数组。

    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    enum { alphabet_size = UCHAR_MAX+1 };
    
    struct MapNode {
         MapNode *next;
         char c;
         bool last;
         MapNode() : next( this ), c(0), last(false) {}
    };
    
    void CreateMap( string s, MapNode (&m)[ alphabet_size ] ) {
        MapNode *mprev = 0;
        replace( s.begin(), s.end(), ' ', '\0' );
        char *str = const_cast<char*>(s.c_str()), *str_end = str + s.size() + 1;
        for ( char *pen = str; pen != str_end; ++ pen ) {
            if ( mprev == 0 ) sort( pen, pen + strlen( pen ) );
            if ( * pen == 0 ) {
                if ( mprev ) mprev->last = true;
                mprev = 0;
                continue;
            }
            MapNode &mnode = m[ * pen ];
            if ( mprev ) swap( mprev->next, mnode.next ); // link node in
            mnode.c = * pen; // tell it what char it is
            mprev = &mnode;
        }
           // make it easier to tell that a node isn't in any map
        for ( MapNode *mptr = m; mptr != m + alphabet_size; ++ mptr ) {
            if ( mptr->next == mptr ) mptr->next = 0;
        }
    }
    
    bool NextMapping( string &s, MapNode (&m)[ alphabet_size ] ) {
        for ( string::iterator it = s.begin(); it != s.end(); ++ it ) {
            MapNode &mnode = m[ * it ];
            if ( mnode.next ) {
                * it = mnode.next->c;
                if ( ! mnode.last ) return true;
            }
        }
        return false;
    }
    
    int main( int argc, char **argv ) {
        MapNode m[ alphabet_size ];
        CreateMap( argv[1], m );
        string s = argv[2];
        do {
            cerr << s << endl;
        } while ( NextMapping( s, m ) );
     return 0;
    }
    

    【讨论】:

      【解决方案3】:

      我要解决的方法是创建一个与字符串长度相同的索引数组,全部初始化为零。然后我们把这个索引数组当作一个计数器来枚举我们的源字符串的所有可能的映射。 0 索引将字符串中的该位置映射到该字符的第一个映射,1 映射到第二个,等等。我们可以通过递增数组中的最后一个索引来按顺序遍历它们,当我们继续到下一个位置时达到该位置的最大映射数。

      要使用您的示例,我们有映射

      'a' => 'e', 'o'
      'b' => 'i'
      

      对于输入字符串“abba”,我们需要一个四元素数组作为索引:

      [0,0,0,0] => "abba"
      [0,0,0,1] => "abbe"
      [0,0,0,2] => "abbo"
      [0,0,1,0] => "abia"
      [0,0,1,1] => "abie"
      [0,0,1,2] => "abio"
      [0,1,0,0] => "aiba"
      [0,1,0,1] => "aibe"
      [0,1,0,2] => "aibo"
      [0,1,1,0] => "aiia"
      [0,1,1,1] => "aiie"
      [0,1,1,2] => "aiio"
      [1,0,0,0] => "ebba"
      [1,0,0,1] => "ebbe"
      [1,0,0,2] => "ebbo"
      [1,0,1,0] => "ebia"
      [1,0,1,1] => "ebie"
      [1,0,1,2] => "ebio"
      [1,1,0,0] => "eiba"
      [1,1,0,1] => "eibe"
      [1,1,0,2] => "eibo"
      [1,1,1,0] => "eiia"
      [1,1,1,1] => "eiie"
      [1,1,1,2] => "eiio"
      [2,0,0,0] => "obba"
      [2,0,0,1] => "obbe"
      [2,0,0,2] => "obbo"
      [2,0,1,0] => "obia"
      [2,0,1,1] => "obie"
      [2,0,1,2] => "obio"
      [2,1,0,0] => "oiba"
      [2,1,0,1] => "oibe"
      [2,1,0,2] => "oibo"
      [2,1,1,0] => "oiia"
      [2,1,1,1] => "oiie"
      [2,1,1,2] => "oiio"
      

      在我们开始生成这些字符串之前,我们需要在某个地方存储它们,这在 C 中意味着我们正在 将不得不分配内存。幸运的是,我们已经知道这些字符串的长度,我们可以算出 我们将要生成的字符串数量 - 它只是每个位置的映射数量的乘积。

      虽然您可以return them in an array,但我更喜欢使用 当我找到它们时回调以返回它们。

      #include <string.h>
      #include <stdlib.h>
      int each_combination( 
          char const * source, 
          char const * mappings[256],
          int (*callback)(char const *, void *), 
          void * thunk
      ) {
        if (mappings == NULL || source == NULL || callback == NULL )
        {
          return -1;
        }
        else
        {
          size_t i;
          int rv;
          size_t num_mappings[256] = {0};
          size_t const source_len = strlen(source);
          size_t * const counter = calloc( source_len, sizeof(size_t) );
          char * const scratch = strdup( source );
      
          if ( scratch == NULL || counter == NULL )
          {
            rv = -1;
            goto done;
          }
      
          /* cache the number of mappings for each char */
          for (i = 0; i < 256; i++)
            num_mappings[i] = 1 + (mappings[i] ? strlen(mappings[i]) : 0);
      
          /* pass each combination to the callback */
          do {
            rv = callback(scratch, thunk);
            if (rv != 0) goto done;
      
            /* increment the counter */
            for (i = 0; i < source_len; i++)
            {
              counter[i]++;
              if (counter[i] == num_mappings[(unsigned char) source[i]])
              {
                /* carry to the next position */
                counter[i] = 0;
                scratch[i] = source[i];
                continue;
              }
              /* use the next mapping for this character */
              scratch[i] = mappings[(unsigned char) source[i]][counter[i]-1];
              break;
            }
          } while(i < source_len);
      
      done:
          if (scratch) free(scratch);
          if (counter) free(counter);
          return rv;
        }
      }
      #include <stdio.h>
      int print_each( char const * s, void * name)
      {
          printf("%s:%s\n", (char const *) name, s);
          return 0;
      }
      int main(int argc, char ** argv)
      {
        char const * mappings[256] = { NULL };
        mappings[(unsigned char) 'a'] = "eo";
        mappings[(unsigned char) 'b'] = "i";
      
        each_combination( "abba", mappings, print_each, (void *) "abba");
        each_combination( "baobab", mappings, print_each, (void *) "baobab");
      
        return 0;
      }
      

      【讨论】:

      • 虽然它有效,但看到到处都是mallocfree 总是让我感到畏缩,尤其是当没有以相同的方法配对时......另外(但这是说英语的人的典型表现) 一些char 可能有一个负值(对于扩展ASCII 中的重音字符)。
      • 感谢您打电话给我使用有符号字符作为数组索引。解决了这个问题。至于返回分配的内存 - 我更喜欢让调用者分配内存,但在这种情况下,计算要分配多少内存是计算的重要部分,期望调用者弄清楚这一点似乎很愚蠢。
      • 其实,知道吗?我将用回调重写它,因为我更喜欢它。
      【解决方案4】:

      您本质上想要进行深度优先搜索 (DFS) 或任何其他遍历有向无环词图 (DAWG)。我会尽快发布一些代码。

      【讨论】:

        【解决方案5】:

        这里有一个指向 sn-ps 存档的链接,Permute2.c。字符串排列还有另一种变体(我想您可以过滤掉那些不在地图中的变体!)请参阅“snippets”存档中的此处...

        希望这会有所帮助, 最好的祝福, 汤姆。

        【讨论】:

          【解决方案6】:

          简单的递归置换,使用字符映射[256]

          char *map [256];
          
          /* permute the ith char in s */
          perm (char *s, int i)
          {
             if (!s) return;
          
             /* terminating condition */
             if (s[i] == '\0') {
                /* add "s" to a string array if we want to store the permutations */
                printf("%s\n", s);
                return;
             }
          
             char c = s[i];
             char *m = map [c];
             // printf ("permuting at [%c]: %s\n", c, m);
             int j=0;
             /* do for the first char, then use map chars */
             do {
                perm (s, i+1);
                s[i] = m[j];
             } while (m[j++] != '\0');
             /* restore original char here, used for mapping */
             s[i] = c;
          
             return;
          }
          
          int main ()
          {
             /* map table initialization */
             map['a'] = "eo\0";
             map['b'] = "i\0";
             map['d'] = "gh\0";
          
             /* need modifyable sp, as we change chars in position, sp="abba" will not work! */
             char *sp = malloc (10);
             strncpy (sp, "abba\0", 5);
          
             perm (sp, 0);
             return 0;
          }
          

          【讨论】:

            猜你喜欢
            • 2013-10-20
            • 2013-09-18
            • 2020-09-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-10
            • 2021-11-15
            相关资源
            最近更新 更多