【问题标题】:Complex algorithm to extract numbers/number range from a string从字符串中提取数字/数字范围的复杂算法
【发布时间】:2013-09-12 12:14:00
【问题描述】:

我正在研究一种算法,我正在尝试以下输出:

给定值/输入:

char *Var = "1-5,10,12,15-16,25-35,67,69,99-105"; int size = 29;

这里的"1-5" 描述了一个范围值,即它将被理解为"1,2,3,4,5",而只有"," 是个人价值观。

我正在编写一个算法,其中最终输出应该是这样的,它将给出完整的输出范围:

int list[]=1,2,3,4,5,10,12,15,16,25,26,27,28,29,30,31,32,33,34,35,67,69,99,100,101,102,103,104,105;

如果有人熟悉这个问题,那么我们将不胜感激。 提前致谢!

我最初的代码方法是:

if(NULL != strchr((char *)grp_range, '-'))
{
    int_u8 delims[] = "-";
    result = (int_u8 *)strtok((char *)grp_range, (char *)delims);

    if(NULL != result)
    {
        start_index = strtol((char*)result, (char **)&end_ptr, 10);
        result = (int_u8 *)strtok(NULL, (char *)delims);
    }

    while(NULL != result)
    {
        end_index = strtol((char*)result, (char**)&end_ptr, 10);
        result = (int_u8 *)strtok(NULL, (char *)delims);
    }

    while(start_index <= end_index)
    {
        grp_list[i++] = start_index;
        start_index++;
    }
}

else if(NULL != strchr((char *)grp_range, ','))
{
    int_u8 delims[] = ",";
    result = (unison_u8 *)strtok((char *)grp_range, (char *)delims);

    while(result != NULL)
    {
        grp_list[i++] = strtol((char*)result, (char**)&end_ptr, 10);
        result = (int_u8 *)strtok(NULL, (char *)delims);
    }
}

但只有当我有“0-5”或“0,10,15”时它才有效。我期待着让它变得更加通用。

【问题讨论】:

  • 是的!我自己试过了,但逻辑似乎太复杂了。
  • 永远不要使用非常量 char * 指向字符串文字。这是 C 还是 C++?答案会大不相同。
  • “是!”首先展示合理的代码/方法
  • @chris 感谢您的意见。但是你能不能看看你能不能为此想出一个算法?这是相当复杂的。
  • 感谢大家的回答!有很多正确的答案,但我会接受一个适合我的。请原谅我昨天粗鲁的回答,当然这里有很多聪明人。再次感谢! @AndrewBarber

标签: c++ c string strtok strtol


【解决方案1】:

这里有一个 C++ 解决方案供你学习。

#include <vector>
#include <string>
#include <sstream>
#include <iostream>
using namespace std;

int ConvertString2Int(const string& str)
{
    stringstream ss(str);
    int x;
    if (! (ss >> x))
    {
        cerr << "Error converting " << str << " to integer" << endl;
        abort();
    }
    return x;
}

vector<string> SplitStringToArray(const string& str, char splitter)
{
    vector<string> tokens;
    stringstream ss(str);
    string temp;
    while (getline(ss, temp, splitter)) // split into new "lines" based on character
    {
        tokens.push_back(temp);
    }
    return tokens;
}

vector<int> ParseData(const string& data)
{
    vector<string> tokens = SplitStringToArray(data, ',');

    vector<int> result;
    for (vector<string>::const_iterator it = tokens.begin(), end_it = tokens.end(); it != end_it; ++it)
    {
        const string& token = *it;
        vector<string> range = SplitStringToArray(token, '-');
        if (range.size() == 1)
        {
            result.push_back(ConvertString2Int(range[0]));
        }
        else if (range.size() == 2)
        {
            int start = ConvertString2Int(range[0]);
            int stop = ConvertString2Int(range[1]);
            for (int i = start; i <= stop; i++)
            {
                result.push_back(i);
            }
        }
        else
        {
            cerr << "Error parsing token " << token << endl;
            abort();
        }
    }

    return result;
}

int main()
{
    vector<int> result = ParseData("1-5,10,12,15-16,25-35,67,69,99-105");
    for (vector<int>::const_iterator it = result.begin(), end_it = result.end(); it != end_it; ++it)
    {
        cout << *it << " ";
    }
    cout << endl;
}

活生生的例子

http://ideone.com/2W99Tt

【讨论】:

  • 哇!干得好伙计。谢谢,让我把它转换成 C 并检查一下。
  • @LetsCode 为什么要将其转换为 C?你的问题被标记为 C++
  • @LetsCode 玩得开心。如果您尝试转换它,您会发现 C 和 C++ 完全不同。
  • 对于一个非常简单的问题有很多行代码(但基本方法很好)。
  • @NeilKirk 首先将所有内容保存在一个 std::istringstream 中,并传递对它的引用。 (或者相反,在任何地方都使用std::string::const_iterator 对,并且只转换为std::istringstream 进行数字转换。)通过相同地处理单个数字和范围形式。但是给我的印象是有很多行的部分原因是您实际上提供了一个完整的程序,包括包含和所有内容。这实际上是一个很好的解决方案。
【解决方案2】:

这是我的提升方法:

这不会给你一个整数数组,而是一个整数向量

使用的算法:(没什么新意)

  • 使用,分割字符串

  • 使用-分割单个字符串

  • 建立一个范围lowhigh

  • 借助此范围将其推入向量

代码:-

#include<iostream>
#include<vector>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>


int main(){

    std::string line("1-5,10,12,15-16,25-35,67,69,99-105");
    std::vector<std::string> strs,r;
    std::vector<int> v;
    int low,high,i;
    boost::split(strs,line,boost::is_any_of(","));

for (auto it:strs)
{
    boost::split(r,it,boost::is_any_of("-"));

    auto x = r.begin();
    low = high =boost::lexical_cast<int>(r[0]);
    x++;
    if(x!=r.end())
        high = boost::lexical_cast<int>(r[1]);
    for(i=low;i<=high;++i)
      v.push_back(i);
}

for(auto x:v)
  std::cout<<x<<" ";

    return 0;

}

【讨论】:

  • 这可能是我实际使用的解决方案。但是,我的工具包中已经有相当于 boost::split 的东西很久了(早在 Boost 出现之前)。这让问题变得太简单了。
  • 我的问题做得很好!谢谢你的努力。欣赏!
【解决方案3】:

您的问题似乎是误解了 strtok 的工作原理。看看这个。

#include <string.h>
#include <stdio.h>

int main()
{
        int i, j;
        char delims[] = " ,";
        char str[] = "1-5,6,7";
        char *tok;
        char tmp[256];
        int rstart, rend;

        tok = strtok(str, delims);

        while(tok != NULL) {
                for(i = 0; i < strlen(tok); ++i) {
                        //// range
                        if(i != 0 && tok[i] == '-') {
                                strncpy(tmp, tok, i); 
                                rstart = atoi(tmp);
                                strcpy(tmp, tok + i + 1); 
                                rend = atoi(tmp);
                                for(j = rstart; j <= rend; ++j)
                                        printf("%d\n", j); 
                                i = strlen(tok) + 1;
                        }   
                        else if(strchr(tok, '-') == NULL)
                                printf("%s\n", tok);
                }   

                tok = strtok(NULL, delims);
        }   

        return 0;
}

【讨论】:

  • 这个确切的例子当然会崩溃。 strtok: 说不吧。
  • 那么你的编译器正在做一些不寻常的事情。 (由于各种历史原因,我知道 Sun CC 会这样做。)"1-5,6,7" 的类型是 char const[],大多数编译器会将其放入写保护内存中。
  • 你是对的,我必须将它从 char *var 更改为 char var[]。请参阅我上面的编辑,当我最初尝试时,我似乎没有遇到段错误。一旦我编写了完整的解决方案,它就失败了。
【解决方案4】:

不要搜索。一次一个字符地浏览文本。只要您看到数字,就将它们累积成一个值。如果数字后跟-,那么您正在查看一个范围,并且需要解析下一组数字以获取范围的上限并将所有值放入您的列表中。如果该值后面没有-,那么您只有一个值;把它放到你的列表中。

【讨论】:

  • 好主意!这就是我需要的。我会尝试并告诉你。
  • 这实际上是一个有趣的解决方案。一旦你得到第一个数字,如果下一个字符是-,跳过它并收集第二个。如果不是,则使用第一个数字作为起点和终点来确定范围。无论哪种方式,更高级别的逻辑只看到一个范围列表。
  • 是的。这是一种“基于堆栈”的方法,您只需要在堆栈上有一个数字——读取的最后一个整数。下一个字符定义会发生什么:, = 推入列表,- = 读取下一个,推入列表,二进制 0 = 结束。
  • 我真的很喜欢你的想法。它给了我灵感,让我以一种简单的方式看待这个问题。谢谢你的努力。欣赏!
【解决方案5】:

停下来想一想:你真正拥有的是一个逗号 分隔的范围列表,其中范围可以是单个 数字,或由'-' 分隔的一对数字。那么你 可能想要循环范围,使用递归下降 用于解析。 (这种事情最好由 istream,这就是我要使用的。)

std::vector<int> results;
std::istringstream parser( std::string( var ) );
processRange( results, parser );
while ( isSeparator( parser, ',' ) ) {
    processRange( results, parser );
}

与:

bool
isSeparator( std::istream& source, char separ )
{
    char next;
    source >> next;
    if ( source && next != separ ) {
        source.putback( next );
    }
    return source && next == separ;
}

void
processRange( std::vector<int>& results, std::istream& source )
{
    int first = 0;
    source >> first;
    int last = first;
    if ( isSeparator( source, '-' ) ) {
        source >> last;
    }
    if ( last < first ) {
        source.setstate( std::ios_base::failbit );
    } 
    if ( source ) {
        while ( first != last ) {
            results.push_back( first );
            ++ first;
        }
        results.push_back( first );
    }
}

isSeparator 函数实际上可能在 未来的其他项目,应保留在您的 工具箱。

【讨论】:

  • 具有两个不同范围说明符的范围列表是正确的。例如,10 真的意味着 10-10。
  • @AdamBurry 这就是我解析它的方式。其他解决方案也可能有效。
  • 感谢您的努力。欣赏!
【解决方案6】:

首先将整个字符串分成数字和范围(使用带有“,”分隔符的strtok()),将字符串保存在数组中,然后在数组中搜索“-”,如果它存在,而不是使用带有“%”的sscanf() d-%d" 格式,否则使用带有单个 "%d" 格式的 sscanf。

函数使用很容易用谷歌搜索。

【讨论】:

  • 不要使用strtok。总是有更好的方法。在这种情况下,在 C 语言中,strchr 将是一个不错的选择。当然,更好的选择是使用std::string,以及标准库中的函数。
  • 我知道 strtok() 会破坏字符串,但它是一个原始解决方案,没有任何改进和好的工具。同时更好的解决方案已经写好了。
【解决方案7】:

一种方法:

您需要一个能够识别 3 种标记的解析器:'、'、'-' 和数字。这提高了抽象级别,因此您可以在字符之上进行操作。

然后您可以解析令牌流以创建范围和常量列表。

然后您可以解析该列表以将范围转换为常量。

完成部分工作的一些代码:

#include <stdio.h>

// Prints a comma after the last digit. You will need to fix that up.
void print(int a, int b) {
  for (int i = a; i <= b; ++i) {
    printf("%d, ", i);
  }
}

int main() {
  enum { DASH, COMMA, NUMBER };
  struct token {
    int type;
    int value;
  };

  // Sample input stream. Notice the sentinel comma at the end.
  // 1-5,10,
  struct token tokStream[] = {
    { NUMBER, 1 },
    { DASH, 0 },
    { NUMBER, 5 },
    { COMMA, 0 },
    { NUMBER, 10 },
    { COMMA, 0 } };

  // This parser assumes well formed input. You have to add all the error
  // checking yourself.
  size_t i = 0;
  while (i < sizeof(tokStream)/sizeof(struct token)) {
    if (tokStream[i+1].type == COMMA) {
      print(tokStream[i].value, tokStream[i].value);
      i += 2;  // skip to next number
    }
    else { // DASH
      print(tokStream[i].value, tokStream[i+2].value);
      i += 4;  // skip to next number
    }
  }

  return 0;
}

【讨论】:

  • 确实可以在大约 5 分钟内使用 lex 和 yacc 编写一个解决方案。它也可能是最容易理解和维护的。
  • 感谢您的回复。您的解决方案有效,但我必须在一个不是我想要的结构中提供输入。感谢您的努力。欣赏!
猜你喜欢
  • 2022-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-14
  • 1970-01-01
  • 2015-03-29
  • 2012-06-15
  • 2017-02-17
相关资源
最近更新 更多