MFC   VC++   Visual Studio   Visual Studio   プログラミング

数字を数値としてソート

 リストコントロールなどでアイテムをソートする場合、文字列としてソートしたり数字としてソートしたりすると思います。
 その時、文字列としてソートする場合、比較関数内ではCStringのCompareやCompareNoCaseなどを使い、数字としてソートする場合、一旦intなどへ変換してから数値比較すると思います。
 しかし、文字列の中に数字が入っていた場合、数字は文字として認識されソートされるため、数値順に並ばなかったりします。(例:Sample1.txt Sample10.txt Sample2.txt Sample3.txt)
 エクスプローラーなどでは、きちんと数字部分は数値として認識されソートされるため、それと同じようにソートされるように比較関数を考えてみました。

 まず最初に、比較する二つの文字列は、関数によってLPARAMだったりCStringだったりするため、ここでは使い回しが出来るように、普通にCStringとして宣言しておきます。
 その都度、リストから文字列を取得したり、ポインタから文字列を取得したり、CStringにする変換処理を入れてください。

 ソートフラグも状況によって取得の仕方が違うため、最後の一行の「SortFlag」は独自に書き換えてください。
 ソート後の昇順降順の入れ替えなども独自に行ってください。

 コードは以下のようになります。
 やり方としては、文字列を先頭から比較していき、数字が出たら数値比較をし、文字の違いが出たらその時点で比較完了という流れです。
 一応、全角数字も数値として扱います。
 全角文字を対象にしない場合、strNumWを除外してください。
 Unicode仕様です。マルチバイトの場合、少し改良が必要です。

   CString str1, str2;    // 比較するための二つの変数(状況によって変換)
    int iSort = 0;        // ソート結果を入れる変数

    int iLen1 = str1.GetLength();
    int iLen2 = str2.GetLength();
    int iLen = min(iLen1, iLen2);    // 文字列長の短い方に合わせるため

    CString strRes1, strRes2;        // 数字部分を受け取るための変数
    const CString strNumH = _T("0123456789");        // 半角数字を用意
    const CString strNumW = _T("0123456789");        // 全角数字を用意
    int iFlag;        // 同じ位置に数字がある場合はその開始位置を、違う場合or無い場合は-1を入れるための変数
    iFlag = (str1.FindOneOf(strNumH + strNumW) == str2.FindOneOf(strNumH + strNumW))    // 数字が最初に出てくる位置が同じかどうか
         ? str1.FindOneOf(strNumH + strNumW) : -1;

    // 数字の開始位置が同じで、それまでの文字列も同じだった場合
    if (iFlag != -1 && str1.Left(iFlag) == str2.Left(iFlag))    // 数字部分で比較
    {
        do
        {
            // 数字分以降を抽出
            str1 = str1.Mid(iFlag);
            str2 = str2.Mid(iFlag);

            // 数字部分のみを抽出
            strRes1 = str1.SpanIncluding(strNumH + strNumW);
            strRes2 = str2.SpanIncluding(strNumH + strNumW);

            if (strRes1 == strRes2)        // 抽出した数字が同じだった場合、もう一度それより後ろについて繰り返す
            {
                str1 = str1.Mid(strRes1.GetLength()); ← 2015/01/01 修正
                str2 = str2.Mid(strRes1.GetLength()); ← 2015/01/01 修正
                iFlag = (str1.FindOneOf(strNumH + strNumW) == str2.FindOneOf(strNumH + strNumW)) ? str1.FindOneOf(strNumH + strNumW) : -1;
            }
            else        // 数字に違いが出た場合、数値として比較してループ終了
            {
                iSort = (_tstoi(strRes1) < _tstoi(strRes2)) ? -1 : 1;
                break;
            }
        } while (iFlag != -1);
    }
    else    // 数字が出てくる前に文字列が違う場合、普通にCStringの比較関数を使用
    {
        iSort = lParam1.Compare(lParam2);
    }

    // 比較した部分が同じで、文字列長が違う場合の処理
    if (iSort == 0 && iLen1 != iLen2)
        iSort = (iLen1 < iLen2) ? -1 : 1;

    return (SortFlag ? iSort * -1 : iSort);        // 「SortFlag」は昇順降順の状況を示す値が入る


以上、独自の方法で数字を数値としてソートするようにしたのですが、要領が悪く、アイテムが多くなると処理が遅くなりそうなので、あとはそれぞれ改良してみてください。
ランキングへ     posted by 遠雷 | Comment(0) | MFC

16進表記の文字列を数値に変換

 16進表記(0xFF45A/45FA)の文字列を数値に変換するには「strtol」などありますが、これだとlong値への変換になるため、大きな数値(LONG_MAXより上)への変換が出来ません。
 そこで、今回はULONGLONGへ変換できる自作関数を紹介します。

 一応、MFCについてのブログなので、MFCを使って書いていきます。
 関数は、16進表記の文字列をCStringで受け取り、数値に変換したあとULONGLONG値を返すようにします。
 やり方は、一文字ずつlong値に変換し、それらを桁位置で16のべき乗したものをULONGLONG値へインクリメントしていくだけです。
ULONGLONG CXXXX::StrToUll(CString strNum)
{
TCHAR* strEnd; // 変換不可能用
long num[16]; // 16桁分用意
ULONGLONG ullSize = 0; // 0で初期化
int iLen = strNum.GetLength();
for(int i=0; i<iLen; i++)
{
num[i] = _tcstol(strNum.Mid(i, 1), &strEnd, 16);
ullSize += num[i] * (ULONGLONG)pow(16.0, (iLen - (i + 1)));
}

return ullSize;
}
 注意点として、「pow」を使っているので「math.h」をインクルードし、変更不可能文字がある場合や、桁数が多い場合のエラー処理などが入っていないため、関数を呼ぶ前にチェックして下さい。

 使い方の一例を書くと、
	CString str(_T("ffAb456"));

CString strCheck;
strCheck = str.SpanIncluding(_T("0123456789ABCDEFabcdef"));
if((strCheck != str) || (str.GetLength() > 16))
{
MessageBox(_T("エラーです"));
return;
}
ULONGLONG ulSize = StrToUll(str);
となり、「ulSize」は「268088406」になります。

 上記の例では変更不可能文字のチェックに「SpanIncluding」を使っていますが、別の方法として「SpanExcluding」を使うとか、「MakeUpper」を使い大文字にしてからチェックするとか、その辺は好みや状況で変更して下さい。
 その他に「StrToUll」関数の「strEnd」を使ってエラー処理する方法もあります。
 あと、文字列の長さから桁数が多い場合もエラーにするようにしてあります。

 ここで、16進数の表記で「0xFF55」のように「0x」がついている場合は、前もって「0x」を取り除くなど状況に応じて各自工夫してみて下さい。
ランキングへ     posted by 遠雷 | Comment(2) | MFC

CMFCToolBar上のコンボボックス その2

 前回、コンボボックスのスタイルが「CBS_DROPDOWNLIST」の場合の値の取得/操作の方法を紹介しましたが、今回は、「CBS_DROPDOWN」での値の取得、操作方法を紹介します。

 基本的には前回のものとほぼ同じなのですが、値を取得/操作するタイミングは、ほとんどの場合、任意のタイミングだと思うので、その方法について紹介します。

 値取得/操作する方法として、コンボボックスのポインタを取得して、そのポインタから値取得/操作するようにします。
 そのため、コンボボックスのポインタを取得する関数を用意します。(ここでは、「GetComboBox」)
 その関数内で以下のように処理し、コンボボックスのポインタを返すようにします。
CMFCToolBarComboBoxButton* CXXX::GetComboBox(void)
{
    CMFCToolBarComboBoxButton* pSrcCombo = NULL;
    CObList listButtons;
    if(CMFCToolBar::GetCommandButtons(ID_COMBOBOXBUTTON, listButtons) > 0)
    {
        for(POSITION posCombo = listButtons.GetHeadPosition(); pSrcCombo == NULL && posCombo != NULL;)
        {
            CMFCToolBarComboBoxButton* pCombo =
                DYNAMIC_DOWNCAST(CMFCToolBarComboBoxButton, listButtons.GetNext(posCombo));
            if(pCombo != NULL)
                pSrcCombo = pCombo;
        }
    }

    return pSrcCombo;
}
 その後、返ってきたポインタを使って値の取得/操作をします。
 以下に値を取得する方法を掲載します。
    CString strName;
    CMFCToolBarComboBoxButton* pSrcCombo = GetComboBox();
    if(pSrcCombo != NULL)
        pSrcCombo->GetEditCtrl()->GetWindowText(strName);
 ここで注意点として、スタイルが「CBS_DROPDOWNLIST」のときに値を取得するには「pSrcCombo->GetItem()」としましたが、
スタイルが「CBS_DROPDOWN」のときは、「pSrcCombo->GetWindowText(strName)」でもなく、「pSrcCombo->GetEditCtrl()->GetWindowText(strName)」のように、子のエディットコントロールから取得します。
ランキングへ     posted by 遠雷 | Comment(0) | MFC