3

My question is similar to this, but I have two strings (as char *) and the task is to replace strnicmp function (avaible only for MS VC) with something like boost::iequals.

Note strnicmp is not stricmp - it only compares first n characters.

Is there any solution simplier than this:

void foo(const char *s1, const char *s2) 
{
    ...

    std::string str1 = s1;
    std::string str2 = s2;
    int n = 7;

    if (boost::iequals(str1.substr(0, n), str2)) {
        ...
    }
}
Community
  • 1
  • 1
maverik
  • 5,508
  • 3
  • 35
  • 55
  • Thanks to all. I prefer answers from `Xeo` and `unwind`. I know `James Kanze` from c.l.c++ and respect him as a professional, but in this case if I have `char *` strings I prefer to write C-style function instead of converting them to the `std::string`. But the answer of `James` would be interesting in case of `std::string`s, especially if your compiler supports lamda-expressions so you can replace `EqIgnoreCase` with lambda :) All get +1. – maverik Apr 14 '11 at 10:43
  • 1
    `boost::iequals` works on ranges, you don't need to convert all the way to `std::string`. – dalle Apr 14 '11 at 10:49
  • Can't figure out how to use `boost::iequals` with range. Can you provide one-line example? – maverik Apr 14 '11 at 10:59
  • 1
    `boost::iequals(boost:iterator_range(s1, s1+n), boost::iterator_range(s2, s2+n))`. Assuming both strings are at least as long as `n`, otherwise reduce `n` first. – Steve Jessop Apr 14 '11 at 11:34
  • 2
    Also take a look at `boost::istarts_with`, http://www.boost.org/doc/html/boost/algorithm/istarts_with.html – dalle Apr 14 '11 at 11:56

6 Answers6

6

If it's really necessary, write your own function:

bool mystrnicmp(char const* s1, char const* s2, int n){
  for(int i=0; i < n; ++i){
    unsigned char c1 = static_cast<unsigned char>(s1[i]);
    unsigned char c2 = static_cast<unsigned char>(s2[i]);
    if(tolower(c1) != tolower(c2))
      return false;
    if(c1 == '\0' || c2 == '\0')
      break;
  }
  return true;
}
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    And the `static_cast` necessary to avoid undefined behavior. – James Kanze Apr 14 '11 at 10:20
  • Undefined behavior if called with e.g. `mystrnicmp("foo", "foo", 50)`. – unwind Apr 14 '11 at 10:21
  • @unwind: Should be as safe as it gets now, since `strlen` also relies on the null-terminator. – Xeo Apr 14 '11 at 10:35
  • 1
    You don't really need to check both characters against 0, because you've already established that they're case-insensitive-equal. Might harm code clarity to only check one, though. – Steve Jessop Apr 14 '11 at 10:50
4

For case insensitivity, you need a custom comparison function (or functor):

struct EqIgnoreCase
{
    bool operator()( char lhs, char rhs ) const
    {
        return ::tolower( static_cast<unsigned char>( lhs ) )
            == ::tolower( static_cast<unsigned char>( rhs ) );
    }
};

If I understand correctly, you're checking for a prefix. The simplest way to do this is:

bool
isPrefix( std::string const& s1, std::string const& s2 )
{
    return s1.size() <= s2.size()
        && std::equals( s1.begin(), s1.end(), s2.begin(), EqIgnoreCase() );
}

(Note the check of the sizes. s1 can't be a prefix of s2 if it it longer than s2. And of course, std::equals will encounter undefined behavior if called with s1 longer than s2.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • ah. crap, mine's the same... I'll leave up anyway I guess given I'm using the `const char*` as the iterators for `std::equal`... – Nim Apr 14 '11 at 10:44
2

For a function defined in terms of C strings (character pointers) going "up" to STL strings seems horribly inefficient, but maybe that's totally premature thinking on my part.

I would consider a straight C solution "simpler", but again that depends on one's perspective.

#include <ctype.h>

void foo(const char *s1, const char *s2)
{
  size_t i, n = 7;

  for(i = 0; i < n; i++)
  {
    if(tolower(s1[i]) != tolower(s2[i]))
      return;
    if(s[i] == '\0' && s2[i] == '\0')
      break;
  }
  /* Strings are equal, do the work. */
  ...
}

This assumes that if both strings end before the length of the prefix has been exhausted, it's a match.

Of course the above assumes ASCII strings where tolower() makes sense.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • To clarify a little bit: all the code is written in C++ and heavily uses STL and boost. This function uses `strnicmp` becase the data structure we get from 3d party library contains `char *` fields – maverik Apr 14 '11 at 10:18
1

I suggest to write the function yourselfs, like this:

bool strnicmp2(const char *s, const char *t, size_t n) {
    while (n > 0 && *s && *t && tolower(*s) == tolower(*t)) {
        ++s;
        ++t;
        --n;
    }
    return n == 0 || !*s || !*t;
}
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
1

something like this ought to work..

#include <iostream>
#include <string>
#include <cctype>
#include <cstring>
#include <algorithm>

struct isequal
{
  bool operator()(int l, int r) const
  {
    return std::tolower(l) == std::tolower(r);
  }
};

bool istrncmp(const char* s1, const char* s2, size_t n)
{
  size_t ls1 = std::strlen(s1);
  size_t ls2 = std::strlen(s2);
  // this is strict, but you can change
  if (ls1 < n || ls2 < n)
    return false;

  return std::equal(s1, s1 + n, s2, isequal()); 
}

int main(void)
{
  std::cout << istrncmp("fooB", "fooA", 3) << std::endl;
  std::cout << istrncmp("fooB", "fooA", 5) << std::endl;
  std::cout << istrncmp("fooB", "f1oA", 3) << std::endl;
  return 0;
}
Nim
  • 33,299
  • 2
  • 62
  • 101
1

I don't know if this counts as simpler or not, but it has fewer lines and speed should be pretty good.

#include <boost/iterator/transform_iterator.hpp>
#include <algorithm>
#include <cctype>

bool equal_insensitive_n( char const *a, char const *b, size_t n ) {
    n = std::min( n, std::min( ::strlen( a ) + 1, ::strlen( b ) + 1 ) );
    #define tilc(S) boost::make_transform_iterator( (S), ::tolower )
    return std::equals( tilc(a), tilc(a) + n, tilc(b) );
    #undef tilc
}
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421