第三十~三十一章:字符串轉換成整數,帶通配符的字符串匹配
前言
之前本一直想寫寫神經網絡算法和EM算法,但寫這兩個算法實在需要大段大段的時間,而平時上班,週末則跑去北大教室自習看書(順便以時間爲序,說下過去半年看過的自覺還不錯的數學史方面的書:《數理統計學簡史》《微積分概念發展史》《微積分的歷程:從牛頓到勒貝格》《數學恩仇錄》《數學與知識的探求》《古今數學思想》《素數之戀》),故一直未曾有時間寫。
然最近在負責一款在線編程挑戰平臺:http://hero.pongo.cn/(簡稱hero,通俗理解是中國的topcoder,當然,一直在不斷完善中,與一般OJ不同點在於,OJ側重爲參與ACM競賽者提供刷題練習的場所,而hero則着重爲企業招聘面試服務),在上面出了幾道編程面試題,有些題目看似簡單,但一coding,很多問題便立馬都在hero上給暴露出來了,故就從hero上的編程挑戰題切入,繼續更新本程序員編程藝術系列吧。
況且,前幾天與一朋友聊天,他說他認識的今年360招進來的三四十人應屆生包括他自己找工作時基本都看過我的博客,則更增加了更新此編程藝術系列的動力。
OK,本文講兩個問題:
- 第三十章、字符串轉換成整數,從確定思路,到寫出有瑕疵的代碼,繼而到microsoft & linux的atoi實現,再到第一份比較完整的代碼,最後以Net/OS中的實現結尾,看似很簡單的一個問題,其實非常不簡單;
- 第三十一章、字符串匹配問題
第三十章、字符串轉換成整數
先看題目:
輸入一個表示整數的字符串,把該字符串轉換成整數並輸出,例如輸入字符串"345",則輸出整數345。
給定函數原型int StrToInt(const char *str) ,完成函數StrToInt,實現字符串轉換成整數的功能,不得用庫函數atoi(即便准許使用,其對於溢出情況的處理也達不到題目的要求,詳情請參看下文第7節末)。
我們來一步一步分析(共9小節,重點在下文第8小節及後續內容),直至寫出第一份準確的代碼:
1、本題考查的實際上就是字符串轉換成整數的問題,或者說是要你自行實現atoi函數。那如何實現把表示整數的字符串正確地轉換成整數呢?以"345"作爲例子:
- 當我們掃描到字符串的第一個字符'3'時,由於我們知道這是第一位,所以得到數字3。
- 當掃描到第二個數字'4'時,而之前我們知道前面有一個3,所以便在後面加上一個數字4,那前面的3相當於30,因此得到數字:3*10+4=34。
- 繼續掃描到字符'5','5'的前面已經有了34,由於前面的34相當於340,加上後面掃描到的5,最終得到的數是:34*10+5=345。
因此,此題的思路便是:每掃描到一個字符,我們便把在之前得到的數字乘以10,然後再加上當前字符表示的數字。
2、思路有了,有一些細節需要注意,如zhedahht所說:
- “由於整數可能不僅僅之含有數字,還有可能以'+'或者'-'開頭,表示整數的正負。因此我們需要把這個字符串的第一個字符做特殊處理。如果第一個字符是'+'號,則不需要做任何操作;如果第一個字符是'-'號,則表明這個整數是個負數,在最後的時候我們要把得到的數值變成負數。
- 接着我們試着處理非法輸入。由於輸入的是指針,在使用指針之前,我們要做的第一件是判斷這個指針是不是爲空。如果試着去訪問空指針,將不可避免地導致程序崩潰。
- 另外,輸入的字符串中可能含有不是數字的字符。每當碰到這些非法的字符,我們就沒有必要再繼續轉換。
- 最後一個需要考慮的問題是溢出問題。由於輸入的數字是以字符串的形式輸入,因此有可能輸入一個很大的數字轉換之後會超過能夠表示的最大的整數而溢出。”
//copyright@zhedahht 2007
enum Status {kValid = 0, kInvalid};
int g_nStatus = kValid;
// Convert a string into an integer
int StrToInt(const char* str)
{
g_nStatus = kInvalid;
long long num = 0;
if(str != NULL)
{
const char* digit = str;
// the first char in the string maybe '+' or '-'
bool minus = false;
if(*digit == '+')
digit ++;
else if(*digit == '-')
{
digit ++;
minus = true;
}
// the remaining chars in the string
while(*digit != '\0')
{
if(*digit >= '0' && *digit <= '9')
{
num = num * 10 + (*digit - '0');
// overflow
if(num > std::numeric_limits<int>::max())
{
num = 0;
break;
}
digit ++;
}
// if the char is not a digit, invalid input
else
{
num = 0;
break;
}
}
if(*digit == '\0')
{
g_nStatus = kValid;
if(minus)
num = 0 - num;
}
}
return static_cast<int>(num);
}
兩個問題:
- 當輸入的字符串不是數字,而是字符的時候,比如“1a”,上述程序直接返回了0(而正確的結果應該是得到1):
// if the char is not a digit, invalid input else { num = 0; break; }
- 處理溢出時,有問題。因爲它遇到溢出情況時,直接返回了0:
// overflow if(num > std::numeric_limits<int>::max()) { num = 0; break; }
//copyright@SP_daiyq 2013/5/29
int StrToInt(const char* str)
{
int res = 0; // result
int i = 0; // index of str
int signal = '+'; // signal '+' or '-'
int cur; // current digit
if (!str)
return 0;
// skip backspace
while (isspace(str[i]))
i++;
// skip signal
if (str[i] == '+' || str[i] == '-')
{
signal = str[i];
i++;
}
// get result
while (str[i] >= '0' && str[i] <= '9')
{
cur = str[i] - '0';
// judge overlap or not
if ( (signal == '+') && (cur > INT_MAX - res*10) )
{
res = INT_MAX;
break;
}
else if ( (signal == '-') && (cur -1 > INT_MAX - res*10) )
{
res = INT_MIN;
break;
}
res = res * 10 + cur;
i++;
}
return (signal == '-') ? -res : res;
}
// judge overlap or not
if ( (signal == '+') && (cur > INT_MAX - res*10) )
{
res = INT_MAX;
break;
}
else if ( (signal == '-') && (cur -1 > INT_MAX - res*10) )
{
res = INT_MIN;
break;
}
接着上面的例子來,比如給定字符串" 10522545459",除去空格有11位,而MAX_INT,即2147483647是10位數,當掃描到最後一個字符‘9’的時候,程序會比較 9 和 2147483647 - 1052254545*10的大小。cur > INT_MAX - res*10
也就是說,對於字符串"10522545459", 當掃描到最後一個字符‘9’時,根據上文第1小節的字符串轉換成整數的思路:“每掃描到一個字符,我們便把在之前得到的數字乘以10,然後再加上當前字符表示的數字”,爲了得到最終的整數,我們得如此計算:1052254545*10 + 4,
然實際上當程序計算到1052254545*10時,
1052254545*10 >
2147483647
此時已經溢出了,若再執意計算,則程序邏輯將出錯,故此後也就不能再判斷字串的最後一位4是否大於2147483647%10了(耐不得煩想盡快看到最終正確代碼的讀者可以直接跳到下文第8節)。
//copyright@fuwutu 2013/5/29
int StrToInt(const char* str)
{
bool negative = false;
long long result = 0;
while (*str == ' ' || *str == '\t')
{
++str;
}
if (*str == '-')
{
negative = true;
++str;
}
else if (*str == '+')
{
++str;
}
while (*str != '\0')
{
int n = *str - '0';
if (n < 0 || n > 9)
{
break;
}
if (negative)
{
result = result * 10 - n;
if (result < -2147483648LL)
{
result = -2147483648LL;
}
}
else
{
result = result * 10 + n;
if (result > 2147483647LL)
{
result = 2147483647LL;
}
}
++str;
}
return result;
}
long long result = 0;
//atol函數
//Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.
long __cdecl atol(
const char *nptr
)
{
int c; /* current char */
long total; /* current total */
int sign; /* if ''-'', then negative, otherwise positive */
/* skip whitespace */
while ( isspace((int)(unsigned char)*nptr) )
++nptr;
c = (int)(unsigned char)*nptr++;
sign = c; /* save sign indication */
if (c == ''-'' || c == ''+'')
c = (int)(unsigned char)*nptr++; /* skip sign */
total = 0;
while (isdigit(c)) {
total = 10 * total + (c - ''0''); /* accumulate digit */
c = (int)(unsigned char)*nptr++; /* get next char */
}
if (sign == ''-'')
return -total;
else
return total; /* return result, negated if necessary */
}
isspace(int x)
{
if(x==' '||x=='/t'||x=='/n'||x=='/f'||x=='/b'||x=='/r')
return 1;
else
return 0;
}
isdigit(int x)
{
if(x<='9'&&x>='0')
return 1;
else
return 0;
}
//atoi調用上述的atol
int __cdecl atoi(
const char *nptr
)
{
//Overflow is not detected. Because of this, we can just use
return (int)atol(nptr);
}
但很遺憾的是,上述atoi標準代碼依然返回的是long:
long total; /* current total */
if (sign == ''-'')
return -total;
else
return total; /* return result, negated if necessary */
再者,下面這裏定義成long的total與10相乘,即total*10很容易溢出:
long total; /* current total */
total = 10 * total + (c - ''0''); /* accumulate digit */
- simple_strtol,把一個字符串轉換爲一個有符號長整數;
- simple_strtoll,把一個字符串轉換爲一個有符號長長整數;
- simple_strtoul,把一個字符串轉換爲一個無符號長整數;
- simple_strtoull,把一個字符串轉換爲一個無符號長長整數
//linux/lib/vsprintf.c
//Copyright (C) 1991, 1992 Linus Torvalds
//simple_strtol - convert a string to a signed long
long simple_strtol(const char *cp, char **endp, unsigned int base)
{
if (*cp == '-')
return -simple_strtoul(cp + 1, endp, base);
return simple_strtoul(cp, endp, base);
}
EXPORT_SYMBOL(simple_strtol);
然後,上面的strtol調下面的strtoul://simple_strtoul - convert a string to an unsigned long
unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base)
{
return simple_strtoull(cp, endp, base);
}
EXPORT_SYMBOL(simple_strtoul);
接着,上面的strtoul調下面的strtoull://simple_strtoll - convert a string to a signed long long
long long simple_strtoll(const char *cp, char **endp, unsigned int base)
{
if (*cp == '-')
return -simple_strtoull(cp + 1, endp, base);
return simple_strtoull(cp, endp, base);
}
EXPORT_SYMBOL(simple_strtoll);
最後,strtoull調_parse_integer_fixup_radix和_parse_integer來處理相關邏輯://simple_strtoull - convert a string to an unsigned long long
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)
{
unsigned long long result;
unsigned int rv;
cp = _parse_integer_fixup_radix(cp, &base);
rv = _parse_integer(cp, base, &result);
/* FIXME */
cp += (rv & ~KSTRTOX_OVERFLOW);
if (endp)
*endp = (char *)cp;
return result;
}
EXPORT_SYMBOL(simple_strtoull);
重頭戲來了。接下來,我們來看上面strtoull函數中的parse_integer_fixup_radix和_parse_integer兩段代碼。如鯊魚所說- “真正的處理邏輯主要是在_parse_integer裏面,關於溢出的處理,_parse_integer處理的很優美,
- 而_parse_integer_fixup_radix是用來自動根據字符串判斷進制的”。
//lib/kstrtox.c, line 39
//Convert non-negative integer string representation in explicitly given radix to an integer.
//Return number of characters consumed maybe or-ed with overflow bit.
//If overflow occurs, result integer (incorrect) is still returned.
unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p)
{
unsigned long long res;
unsigned int rv;
int overflow;
res = 0;
rv = 0;
overflow = 0;
while (*s) {
unsigned int val;
if ('0' <= *s && *s <= '9')
val = *s - '0';
else if ('a' <= _tolower(*s) && _tolower(*s) <= 'f')
val = _tolower(*s) - 'a' + 10;
else
break;
if (val >= base)
break;
/*
* Check for overflow only if we are within range of
* it in the max base we support (16)
*/
if (unlikely(res & (~0ull << 60))) {
if (res > div_u64(ULLONG_MAX - val, base))
overflow = 1;
}
res = res * base + val;
rv++;
s++;
}
*p = res;
if (overflow)
rv |= KSTRTOX_OVERFLOW;
return rv;
}
- 上頭出現了個unlikely,其實unlikely和likely經常出現在linux相關內核源碼中
likely表示value爲真的可能性更大,而unlikely表示value爲假的可能性更大,這兩個宏被定義成:if(likely(value)){ //等價於if(likely(value)) == if(value) } else{ }
//include/linux/compiler.h # ifndef likely # define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1)) # endif # ifndef unlikely # define unlikely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0)) # endif
- 呈現下div_u64的代碼:
//include/linux/math64.h //div_u64 static inline u64 div_u64(u64 dividend, u32 divisor) { u32 remainder; return div_u64_rem(dividend, divisor, &remainder); } //div_u64_rem static inline u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder) { *remainder = dividend % divisor; return dividend / divisor; }
//lib/kstrtox.c, line 23
const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)
{
if (*base == 0) {
if (s[0] == '0') {
if (_tolower(s[1]) == 'x' && isxdigit(s[2]))
*base = 16;
else
*base = 8;
} else
*base = 10;
}
if (*base == 16 && s[0] == '0' && _tolower(s[1]) == 'x')
s += 2;
return s;
}
2147483647 : 2147483647 2147483648 : -2147483648 10522545459 : 1932610867 -2147483648 : -2147483648 -2147483649 : -2147483647 -10522545459 : 1932610867
8、根據我們第1小節達成一致的字符串轉換成整數的思路:“每掃描到一個字符,我們便把在之前得到的數字乘以10,然後再加上當前字符表示的數字”,相信讀者已經覺察到,在掃描到最後一個字符的時候,如果之前得到的數比較大,此時若再讓其擴大10倍,相對來說是比較容易溢出的。
- 與其將n擴大10倍,,冒着溢出的風險, 再與MAX_INT進行比較(如果已經溢出, 則比較的結果沒有意義),
- 不如未雨綢繆先用n與MAX_INT/10進行比較: 若n>MAX_INT/10(當然同時還要考慮n=MAX_INT/10的情況), 說明最終得到的整數一定會溢出, 故此時可以當即進行溢出處理,直接返回最大值MAX_INT,從而也就免去了計算n*10這一步驟。
他的代碼如下,如有問題請指出:
//copyright@njnu_mjn 2013
int StrToDecInt(const char* str)
{
static const int MAX = (int)((unsigned)~0 >> 1);
static const int MIN = -(int)((unsigned)~0 >> 1) - 1;
unsigned int n = 0;
int sign = 1;
int c;
while (isspace(*str))
++str;
if (*str == '+' || *str == '-')
{
if (*str == '-')
sign = -1;
++str;
}
while (isdigit(*str))
{
c = *str - '0';
if (sign > 0 && (n > MAX/10 || (n == MAX/10 && c > MAX%10)))
{
n = MAX;
break;
}
else if (sign < 0 && (n > (unsigned)MIN/10
|| (n == (unsigned)MIN/10 && c > (unsigned)MIN%10)))
{
n = MIN;
break;
}
n = n * 10 + c;
++str;
}
return sign > 0 ? n : -n;
}
上述代碼從測試結果來看,暫未發現什麼問題 輸入 輸出
10522545459 : 2147483647
-10522545459 : -2147483648
咱們再來總結下上述代碼是如何處理溢出情況的。對於正數來說,它溢出的可能性有兩種:
- 一種是諸如2147483650,即n > MAX/10 的;
- 一種是諸如2147483649,即n == MAX/10 && c > MAX%10。
故咱們上面處理溢出情況的代碼便是:
c = *str - '0';
if (sign > 0 && (n > MAX/10 || (n == MAX/10 && c > MAX%10)))
{
n = MAX;
break;
}
else if (sign < 0 && (n > (unsigned)MIN/10
|| (n == (unsigned)MIN/10 && c > (unsigned)MIN%10)))
{
n = MIN;
break;
}
不過,即便如此,有些細節是改進的,如他自己所說:
- n的聲明及定義應該爲
int n = 0;
- 將MAX/10,MAX%10,(unsigned)MIN/10及(unsigned)MIN%10保存到變量中, 防止重複計算
//copyright@njnu_mjn 2013
int StrToDecInt(const char* str)
{
static const int MAX = (int)((unsigned)~0 >> 1);
static const int MIN = -(int)((unsigned)~0 >> 1) - 1;
static const int MAX_DIV = (int)((unsigned)~0 >> 1) / 10;
static const int MIN_DIV = (int)((((unsigned)~0 >> 1) + 1) / 10);
static const int MAX_R = (int)((unsigned)~0 >> 1) % 10;
static const int MIN_R = (int)((((unsigned)~0 >> 1) + 1) % 10);
int n = 0;
int sign = 1;
int c;
while (isspace(*str))
++str;
if (*str == '+' || *str == '-')
{
if (*str == '-')
sign = -1;
++str;
}
while (isdigit(*str))
{
c = *str - '0';
if (sign > 0 && (n > MAX_DIV || (n == MAX_DIV && c >= MAX_R)))
{
n = MAX;
break;
}
else if (sign < 0 && (n > MIN_DIV
|| (n == MIN_DIV && c >= MIN_R)))
{
n = MIN;
break;
}
n = n * 10 + c;
++str;
}
return sign > 0 ? n : -n;
}
輸入 輸出 10522545459 : 2147483647 -10522545459 : -2147483648 2147483648 : 2147483647 -2147483648 : -2147483648是否已是完美?如MJN君本人所說“我的實現與linux內核的atoi函數的實現, 都有一個共同的問題: 即使出錯, 函數也返回了一個值, 導致調用者誤認爲自己傳入的參數是正確的, 但是可能會導致程序的其他部分產生莫名的錯誤且很難調試”。
9、最後看下Nut/OS中atoi的實現,同時,本小節內容主要來自參考文獻條目9,即MJN的博客:
00077 #include <compiler.h>
00078 #include <stdlib.h>
00079
00084
00092 int atoi(CONST char *str)
00093 {
00094 return ((int) strtol(str, (char **) NULL, 10));
00095 }
上述代碼中strtol實現的思想跟上文第7節所述的MJN君的思路類似,也是除法代替乘法。加上測試函數後的具體代碼如下:
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#define CONST const
long mstrtol(CONST char *nptr, char **endptr, int base)
{
register CONST char *s;
register long acc, cutoff;
register int c;
register int neg, any, cutlim;
/*
* Skip white space and pick up leading +/- sign if any.
* If base is 0, allow 0x for hex and 0 for octal, else
* assume decimal; if base is already 16, allow 0x.
*/
s = nptr;
do {
c = (unsigned char) *s++;
} while (isspace(c));
if (c == '-') {
neg = 1;
c = *s++;
} else {
neg = 0;
if (c == '+')
c = *s++;
}
if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) {
c = s[1];
s += 2;
base = 16;
}
if (base == 0)
base = c == '0' ? 8 : 10;
/*
* Compute the cutoff value between legal numbers and illegal
* numbers. That is the largest legal value, divided by the
* base. An input number that is greater than this value, if
* followed by a legal input character, is too big. One that
* is equal to this value may be valid or not; the limit
* between valid and invalid numbers is then based on the last
* digit. For instance, if the range for longs is
* [-2147483648..2147483647] and the input base is 10,
* cutoff will be set to 214748364 and cutlim to either
* 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated
* a value > 214748364, or equal but the next digit is > 7 (or 8),
* the number is too big, and we will return a range error.
*
* Set any if any `digits' consumed; make it negative to indicate
* overflow.
*/
cutoff = neg ? LONG_MIN : LONG_MAX;
cutlim = cutoff % base;
cutoff /= base;
if (neg) {
if (cutlim > 0) {
cutlim -= base;
cutoff += 1;
}
cutlim = -cutlim;
}
for (acc = 0, any = 0;; c = (unsigned char) *s++) {
if (isdigit(c))
c -= '0';
else if (isalpha(c))
c -= isupper(c) ? 'A' - 10 : 'a' - 10;
else
break;
if (c >= base)
break;
if (any < 0)
continue;
if (neg) {
if ((acc < cutoff || acc == cutoff) && c > cutlim) {
any = -1;
acc = LONG_MIN;
errno = ERANGE;
} else {
any = 1;
acc *= base;
acc -= c;
}
} else {
if ((acc > cutoff || acc == cutoff) && c > cutlim) {
any = -1;
acc = LONG_MAX;
errno = ERANGE;
} else {
any = 1;
acc *= base;
acc += c;
}
}
}
if (endptr != 0)
*endptr = (char *) (any ? s - 1 : nptr);
return (acc);
}
int matoi2(CONST char *str)
{
return ((int) mstrtol(str, (char **) NULL, 10));
}
int mgetline(char* buf, size_t n) {
size_t idx = 0;
int c;
while (--n > 0 && (c = getchar()) != EOF && c != '\n') {
buf[idx++] = c;
}
buf[idx] = '\0';
return idx;
}
#define MAX_LINE 200
int main() {
char buf[MAX_LINE];
while (mgetline(buf, MAX_LINE) >= 0) {
if (strcmp(buf, "quit") == 0) break;
printf("matoi2=%d\n", matoi2(buf));
}
return 0;
}
同樣,MJN對上述實現測試了下,結果如下:
10522545459 matoi2=2147483647 -10522545459 matoi2=-2147483648
程序貌似對溢出的處理是正確的, 真的嗎? 再把測試數據換成"10522545454"(與"10522545459"的區別在於最後一個字符)
10522545454 matoi2=1932610862 -10522545454 matoi2=-1932610862
癥結就在於下面這段代碼:
if (neg) {
if ((acc < cutoff || acc == cutoff) && c > cutlim) {
any = -1;
acc = LONG_MIN;
errno = ERANGE;
} else {
any = 1;
acc *= base;
acc -= c;
}
} else {
if ((acc > cutoff || acc == cutoff) && c > cutlim) {
any = -1;
acc = LONG_MAX;
errno = ERANGE;
要想得到正確的輸出結果,需要改動兩個地方:
①其中這行:
if ((acc > cutoff || acc == cutoff) && c > cutlim)
應該改爲:
if ( acc > cutoff || (acc == cutoff) && c > cutlim) )
②與此同時,這行:
if ((acc < cutoff || acc == cutoff) && c > cutlim) {
改爲:
if (acc < cutoff || (acc == cutoff && c > cutlim)) {
爲何要這樣修改呢?細心的讀者相信還是會記得上文第8節中關於正數的兩種溢出情況的可能性:“對於正數來說,它溢出的可能性有兩種:
- 一種是諸如2147483650,即n > MAX/10 的;
- 一種是諸如2147483649,即n == MAX/10 && c > MAX%10。”
也就是說無論是"10522545459",還是"10522545454",都是屬於第1種情況,即“諸如2147483650,即n > MAX/10的”,此時直接返回MAX_INT即可,所以不需要也不能再去判斷n == MAX/10的情況。
這個處理思路類似於上文第8節處理溢出情況的代碼:
if (sign > 0 && (n > MAX/10 || (n == MAX/10 && c > MAX%10)))
{
n = MAX;
break;
}
else if (sign < 0 && (n > (unsigned)MIN/10
|| (n == (unsigned)MIN/10 && c > (unsigned)MIN%10)))
{
n = MIN;
break;
}
So,修改過後的代碼測試正常:
10522545459 matoi2=2147483647 -10522545459\ matoi2=-2147483648 10522545454 matoi2=2147483647 -10522545454 matoi2=-2147483648 quit
第三十一章、帶通配符的字符串匹配問題
字符串匹配問題,給定一串字符串,按照指定規則對其進行匹配,並將匹配的結果保存至output數組中,多個匹配項用空格間隔,最後一個不需要空格。
要求:
- 匹配規則中包含通配符?和*,其中?表示匹配任意一個字符,*表示匹配任意多個(>=0)字符。
- 匹配規則要求匹配最大的字符子串,例如a*d,匹配abbdd而非abbd,即最大匹配子串。
- 匹配後的輸入串不再進行匹配,從當前匹配後的字符串重新匹配其他字符串。
請實現函數:char* my_find(char input[], char rule[])
舉例說明
input:abcadefg
rule:a?c
output:abc
input :newsadfanewfdadsf
rule: new
output: new new
input :breakfastfood
rule: f*d
output:fastfood
注意事項:
- 自行實現函數my_find,勿在my_find函數裏夾雜輸出,且不準用C、C++庫,和Java的String對象;
- 請注意代碼的時間,空間複雜度,及可讀性,簡潔性;
- input=aaa,rule=aa時,返回一個結果aa,即可。
1、本題與上述第三十章的題不同,上題字符串轉換成整數更多考察對思維的全面性和對細節的處理,本題則更多的是編程技巧。閒不多說,直接上代碼:
//copyright@cao_peng 2013/4/23
int str_len(char *a) { //字符串長度
if (a == 0) {
return 0;
}
char *t = a;
for (;*t;++t)
;
return (int) (t - a);
}
void str_copy(char *a,const char *b,int len) { //拷貝字符串 a = b
for (;len > 0; --len, ++b,++a) {
*a = *b;
}
*a = 0;
}
char *str_join(char *a,const char *b,int lenb) { //連接字符串 第一個字符串被回收
char *t;
if (a == 0) {
t = (char *) malloc(sizeof(char) * (lenb + 1));
str_copy(t, b, lenb);
return t;
}
else {
int lena = str_len(a);
t = (char *) malloc(sizeof(char) * (lena + lenb + 2));
str_copy(t, a, lena);
*(t + lena) = ' ';
str_copy(t + lena + 1, b, lenb);
free(a);
return t;
}
}
int canMatch(char *input, char *rule) { // 返回最長匹配長度 -1表示不匹配
if (*rule == 0) { //已經到rule尾端
return 0;
}
int r = -1 ,may;
if (*rule == '*') {
r = canMatch(input, rule + 1); // *匹配0個字符
if (*input) {
may = canMatch(input + 1, rule); // *匹配非0個字符
if ((may >= 0) && (++may > r)) {
r = may;
}
}
}
if (*input == 0) { //到尾端
return r;
}
if ((*rule == '?') || (*rule == *input)) {
may = canMatch(input + 1, rule + 1);
if ((may >= 0) && (++may > r)) {
r = may;
}
}
return r;
}
char * my_find(char input[], char rule[]) {
int len = str_len(input);
int *match = (int *) malloc(sizeof(int) * len); //input第i位最多能匹配多少位 匹配不上是-1
int i,max_pos = - 1;
char *output = 0;
for (i = 0; i < len; ++i) {
match[i] = canMatch(input + i, rule);
if ((max_pos < 0) || (match[i] > match[max_pos])) {
max_pos = i;
}
}
if ((max_pos < 0) || (match[max_pos] <= 0)) { //不匹配
output = (char *) malloc(sizeof(char));
*output = 0; // \0
return output;
}
for (i = 0; i < len;) {
if (match[i] == match[max_pos]) { //找到匹配
output = str_join(output, input + i, match[i]);
i += match[i];
}
else {
++i;
}
}
free(match);
return output;
}
2、本題也可以直接寫出DP方程,如下代碼所示:
//copyright@chpeih 2013/4/23
char* my_find(char input[], char rule[])
{
//write your code here
int len1,len2;
for(len1 = 0;input[len1];len1++);
for(len2 = 0;rule[len2];len2++);
int MAXN = len1>len2?(len1+1):(len2+1);
int **dp;
//dp[i][j]表示字符串1和字符串2分別以i j結尾匹配的最大長度
//記錄dp[i][j]是由之前那個節點推算過來 i*MAXN+j
dp = new int *[len1+1];
for (int i = 0;i<=len1;i++)
{
dp[i] = new int[len2+1];
}
dp[0][0] = 0;
for(int i = 1;i<=len2;i++)
dp[0][i] = -1;
for(int i = 1;i<=len1;i++)
dp[i][0] = 0;
for (int i = 1;i<=len1;i++)
{
for (int j = 1;j<=len2;j++)
{
if(rule[j-1]=='*'){
dp[i][j] = -1;
if (dp[i-1][j-1]!=-1)
{
dp[i][j] = dp[i-1][j-1]+1;
}
if (dp[i-1][j]!=-1 && dp[i][j]<dp[i-1][j]+1)
{
dp[i][j] = dp[i-1][j]+1;
}
}else if (rule[j-1]=='?')
{
if(dp[i-1][j-1]!=-1){
dp[i][j] = dp[i-1][j-1]+1;
}else dp[i][j] = -1;
}
else
{
if(dp[i-1][j-1]!=-1 && input[i-1]==rule[j-1]){
dp[i][j] = dp[i-1][j-1]+1;
}else dp[i][j] = -1;
}
}
}
int m = -1;//記錄最大字符串長度
int *ans = new int[len1];
int count_ans = 0;//記錄答案個數
char *returnans = new char[len1+1];
int count = 0;
for(int i = 1;i<=len1;i++)
if (dp[i][len2]>m){
m = dp[i][len2];
count_ans = 0;
ans[count_ans++] = i-m;
}else if(dp[i][len2]!=-1 &&dp[i][len2]==m){
ans[count_ans++] = i-m;
}
if (count_ans!=0)
{
int len = ans[0];
for (int i = 0;i<m;i++)
{
printf("%c",input[i+ans[0]]);
returnans[count++] = input[i+ans[0]];
}
for (int j = 1;j<count_ans;j++)
{
printf(" ");
returnans[count++] = ' ';
len = ans[j];
for (int i = 0;i<m;i++)
{
printf("%c",input[i+ans[j]]);
returnans[count++] = input[i+ans[j]];
}
}
printf("\n");
returnans[count++] = '\0';
}
return returnans;
}
歡迎於本文評論下或hero上show your code。參考文獻及推薦閱讀
- http://zhedahht.blog.163.com/blog/static/25411174200731139971/;
- http://hero.pongo.cn/,本文大部分代碼都取自左邊hero上參與答題者提交的代碼,歡迎你也去挑戰;
- 字符串轉換成整數題目完整描述:http://hero.pongo.cn/Question/Details?ID=47&ExamID=45;
- 字符串匹配問題題目完整描述:http://hero.pongo.cn/Question/Details?ID=28&ExamID=28;
- linux3.8.4版本下的相關字符串整數轉換函數概覽:https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/lib/vsprintf.c?id=refs/tags/v3.9.4;
- 關於linux中的likely和unlikely:http://blog.21ic.com/user1/5593/archives/2010/68193.html;
- 如果你喜歡編程挑戰,除了topcoder和hero,你應該還多去leetcode上逛逛:http://leetcode.com/onlinejudge;
- atio函數的實現:http://blog.csdn.net/njnu_mjn/article/details/9099405;
- atoi函數的實現: linux內核atoi函數的測試:http://blog.csdn.net/njnu_mjn/article/details/9104143;
- Nut/OS中atoi函數的實現:http://www.ethernut.de/api/atoi_8c_source.html;
- 一讀者寫的hero上“字符串轉換成整數”一題的解題報告(測試正確):http://blog.csdn.net/u011070134/article/details/9116831;