Add more options for sorting.

User can now choose between lexicographic, integer and decimal sorting.
For decimal sorting there are two further options: decimal point ('.')
or decimal comma (',').

When doing integer/decimal sort, the parsing is not as strict as
before. E.g during integer sorting the program will interpret "123abc"
as 123.

Performance of integer sorting has been improved by 30%.

The implementation of sorting is delegated to classes which implement
the new "ISorter" interface. Unfortunately due to template issues most
of the code had to go in the header file.
This commit is contained in:
Andreas Jönsson 2015-05-17 19:18:43 +02:00
parent 4d8e731d3e
commit d0bafb7fba
9 changed files with 318 additions and 150 deletions

View File

@ -749,131 +749,16 @@ generic_string stringJoin(const std::vector<generic_string>& strings, const gene
return joined;
}
long long stollStrict(const generic_string& input)
generic_string stringTakeWhileAdmissable(const generic_string& input, const generic_string& admissable)
{
if (input.empty())
// Find first non-admissable character in "input", and remove everything after it.
size_t idx = input.find_first_not_of(admissable);
if (idx == std::string::npos)
{
throw std::invalid_argument("Empty input.");
return input;
}
else
{
// Check minus characters.
const int minuses = std::count(input.begin(), input.end(), TEXT('-'));
if (minuses > 1)
{
throw std::invalid_argument("More than one minus sign.");
}
else if (minuses == 1 && input[0] != TEXT('-'))
{
throw std::invalid_argument("Minus sign must be first.");
}
// Check for other characters which are not allowed.
if (input.find_first_not_of(TEXT("-0123456789")) != std::string::npos)
{
throw std::invalid_argument("Invalid character found.");
}
return std::stoll(input);
return input.substr(0, idx);
}
}
bool allLinesAreNumericOrEmpty(const std::vector<generic_string>& lines)
{
for (const generic_string& line : lines)
{
try
{
if (!line.empty())
{
stollStrict(line);
}
}
catch (std::invalid_argument&)
{
return false;
}
catch (std::out_of_range&)
{
return false;
}
}
return true;
}
std::vector<generic_string> repeatString(const generic_string& text, const size_t count)
{
std::vector<generic_string> output;
output.reserve(count);
for (size_t i = 0; i < count; ++i)
{
output.push_back(text);
}
assert(output.size() == count);
return output;
}
std::vector<generic_string> lexicographicSort(std::vector<generic_string> input, bool isDescending)
{
std::sort(input.begin(), input.end(), [isDescending](generic_string a, generic_string b)
{
if (isDescending)
{
return a.compare(b) > 0;
}
else
{
return a.compare(b) < 0;
}
});
return input;
}
std::vector<generic_string> numericSort(std::vector<generic_string> input, bool isDescending)
{
// Pre-condition: all strings in "input" are either empty or convertible to int with stoiStrict.
// Note that empty lines are filtered out and added back manually to the output at the end.
std::vector<long long> nonEmptyInputAsNumbers;
size_t nofEmptyLines = 0;
nonEmptyInputAsNumbers.reserve(input.size());
for (const generic_string& line : input)
{
if (line.empty())
{
++nofEmptyLines;
}
else
{
nonEmptyInputAsNumbers.push_back(stollStrict(line));
}
}
assert(nonEmptyInputAsNumbers.size() + nofEmptyLines == input.size());
std::sort(nonEmptyInputAsNumbers.begin(), nonEmptyInputAsNumbers.end(), [isDescending](long long a, long long b)
{
if (isDescending)
{
return a > b;
}
else
{
return a < b;
}
});
std::vector<generic_string> output;
output.reserve(input.size());
const std::vector<generic_string> empties = repeatString(TEXT(""), nofEmptyLines);
if (!isDescending)
{
output.insert(output.end(), empties.begin(), empties.end());
}
for (const long long& sortedNumber : nonEmptyInputAsNumbers)
{
output.push_back(std::to_wstring(sortedNumber));
}
if (isDescending)
{
output.insert(output.end(), empties.begin(), empties.end());
}
assert(output.size() == input.size());
return output;
}

View File

@ -190,11 +190,6 @@ generic_string stringToUpper(generic_string strToConvert);
generic_string stringReplace(generic_string subject, const generic_string& search, const generic_string& replace);
std::vector<generic_string> stringSplit(const generic_string& input, const generic_string& delimiter);
generic_string stringJoin(const std::vector<generic_string>& strings, const generic_string& separator);
long long stollStrict(const generic_string& input);
bool allLinesAreNumericOrEmpty(const std::vector<generic_string>& lines);
std::vector<generic_string> repeatString(const generic_string& text, const size_t count);
std::vector<generic_string> numericSort(std::vector<generic_string> input, bool isDescending);
std::vector<generic_string> lexicographicSort(std::vector<generic_string> input, bool isDescending);
generic_string stringTakeWhileAdmissable(const generic_string& input, const generic_string& admissable);
#endif //M30_IDE_COMMUN_H

View File

@ -0,0 +1,210 @@
// This file is part of Notepad++ project
// Copyright (C)2003 Don HO <don.h@free.fr>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// Note that the GPL places important restrictions on "derived works", yet
// it does not provide a detailed definition of that term. To avoid
// misunderstandings, we consider an application to constitute a
// "derivative work" for the purpose of this license if it does any of the
// following:
// 1. Integrates source code from Notepad++.
// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable
// installer, such as those produced by InstallShield.
// 3. Links to a library or executes a program that does any of the above.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#ifndef M30_IDE_SORTERS_H
#define M30_IDE_SORTERS_H
// Base interface for line sorting.
class ISorter
{
private:
bool _isDescending;
protected:
bool isDescending() const
{
return _isDescending;
}
public:
ISorter(bool isDescending)
{
_isDescending = isDescending;
};
virtual ~ISorter() { };
virtual std::vector<generic_string> sort(std::vector<generic_string> lines) = 0;
};
// Implementation of lexicographic sorting of lines.
class LexicographicSorter : public ISorter
{
public:
LexicographicSorter(bool isDescending) : ISorter(isDescending) { };
std::vector<generic_string> sort(std::vector<generic_string> lines) override
{
const bool descending = isDescending();
std::sort(lines.begin(), lines.end(), [descending](generic_string a, generic_string b)
{
if (descending)
{
return a.compare(b) > 0;
}
else
{
return a.compare(b) < 0;
}
});
return lines;
}
};
// Convert each line to a number (somehow - see stringToNumber) and then sort.
template<typename T_Num>
class NumericSorter : public ISorter
{
public:
NumericSorter(bool isDescending) : ISorter(isDescending) { };
std::vector<generic_string> sort(std::vector<generic_string> lines) override
{
// Note that empty lines are filtered out and added back manually to the output at the end.
std::vector<std::pair<size_t, T_Num>> nonEmptyInputAsNumbers;
std::vector<generic_string> empties;
nonEmptyInputAsNumbers.reserve(lines.size());
for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex)
{
generic_string line = prepareStringForConversion(lines[lineIndex]);
if (considerStringEmpty(line))
{
empties.push_back(line);
}
else
{
try
{
nonEmptyInputAsNumbers.push_back(make_pair(lineIndex, convertStringToNumber(line)));
}
catch (...)
{
throw lineIndex;
}
}
}
assert(nonEmptyInputAsNumbers.size() + empties.size() == lines.size());
const bool descending = isDescending();
std::sort(nonEmptyInputAsNumbers.begin(), nonEmptyInputAsNumbers.end(), [descending](std::pair<size_t, T_Num> a, std::pair<size_t, T_Num> b)
{
if (descending)
{
return a.second > b.second;
}
else
{
return a.second < b.second;
}
});
std::vector<generic_string> output;
output.reserve(lines.size());
if (!isDescending())
{
output.insert(output.end(), empties.begin(), empties.end());
}
for (const std::pair<size_t, T_Num>& sortedNumber : nonEmptyInputAsNumbers)
{
output.push_back(lines[sortedNumber.first]);
}
if (isDescending())
{
output.insert(output.end(), empties.begin(), empties.end());
}
assert(output.size() == lines.size());
return output;
}
protected:
bool considerStringEmpty(const generic_string& input)
{
// String has something else than just whitespace.
return input.find_first_not_of(TEXT(" \t\r\n")) == std::string::npos;
}
// Prepare the string for conversion to number.
virtual generic_string prepareStringForConversion(const generic_string& input) = 0;
// Should convert the input string to a number of the correct type.
// If unable to convert, throw either std::invalid_argument or std::out_of_range.
virtual T_Num convertStringToNumber(const generic_string& input) = 0;
};
// Converts lines to long long before sorting.
class IntegerSorter : public NumericSorter<long long>
{
public:
IntegerSorter(bool isDescending) : NumericSorter<long long>(isDescending) { };
protected:
virtual generic_string prepareStringForConversion(const generic_string& input)
{
return stringTakeWhileAdmissable(input, TEXT(" \t\r\n0123456789"));
}
long long convertStringToNumber(const generic_string& input) override
{
return std::stoll(input);
}
};
// Converts lines to double before sorting (assumes decimal comma).
class DecimalCommaSorter : public NumericSorter<double>
{
public:
DecimalCommaSorter(bool isDescending) : NumericSorter<double>(isDescending) { };
protected:
generic_string prepareStringForConversion(const generic_string& input) override
{
generic_string admissablePart = stringTakeWhileAdmissable(input, TEXT(" \t\r\n0123456789,"));
return stringReplace(admissablePart, TEXT(","), TEXT("."));
}
double convertStringToNumber(const generic_string& input) override
{
return std::stod(input);
}
};
// Converts lines to double before sorting (assumes decimal dot).
class DecimalDotSorter : public NumericSorter<double>
{
public:
DecimalDotSorter(bool isDescending) : NumericSorter<double>(isDescending) { };
protected:
generic_string prepareStringForConversion(const generic_string& input) override
{
return stringTakeWhileAdmissable(input, TEXT(" \t\r\n0123456789."));
}
double convertStringToNumber(const generic_string& input) override
{
return std::stod(input);
}
};
#endif //M30_IDE_SORTERS_H

View File

@ -274,8 +274,29 @@ BEGIN
MENUITEM "Duplicate Current Line", IDM_EDIT_DUP_LINE
MENUITEM "Split Lines", IDM_EDIT_SPLIT_LINES
MENUITEM "Join Lines", IDM_EDIT_JOIN_LINES
MENUITEM "Sort Lines in Ascending Order", IDM_EDIT_SORTLINES_ASCENDING
MENUITEM "Sort Lines in Descending Order", IDM_EDIT_SORTLINES_DESCENDING
POPUP "Sort Lines"
BEGIN
POPUP "Lexicographic"
BEGIN
MENUITEM "Ascending", IDM_EDIT_SORTLINES_LEXICOGRAPHIC_ASCENDING
MENUITEM "Descending", IDM_EDIT_SORTLINES_LEXICOGRAPHIC_DESCENDING
END
POPUP "Integer"
BEGIN
MENUITEM "Ascending", IDM_EDIT_SORTLINES_INTEGER_ASCENDING
MENUITEM "Descending", IDM_EDIT_SORTLINES_INTEGER_DESCENDING
END
POPUP "Decimal (comma)"
BEGIN
MENUITEM "Ascending", IDM_EDIT_SORTLINES_DECIMALCOMMA_ASCENDING
MENUITEM "Descending", IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING
END
POPUP "Decimal (dot)"
BEGIN
MENUITEM "Ascending", IDM_EDIT_SORTLINES_DECIMALDOT_ASCENDING
MENUITEM "Descending", IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING
END
END
MENUITEM "Move Up Current Line", IDM_EDIT_LINE_UP
MENUITEM "Move Down Current Line", IDM_EDIT_LINE_DOWN
MENUITEM "Remove Empty Lines", IDM_EDIT_REMOVEEMPTYLINES

View File

@ -35,6 +35,7 @@
#include "VerticalFileSwitcher.h"
#include "documentMap.h"
#include "functionListPanel.h"
#include "Sorters.h"
void Notepad_plus::macroPlayback(Macro macro)
@ -344,8 +345,14 @@ void Notepad_plus::command(int id)
}
break;
case IDM_EDIT_SORTLINES_ASCENDING:
case IDM_EDIT_SORTLINES_DESCENDING:
case IDM_EDIT_SORTLINES_LEXICOGRAPHIC_ASCENDING:
case IDM_EDIT_SORTLINES_LEXICOGRAPHIC_DESCENDING:
case IDM_EDIT_SORTLINES_INTEGER_ASCENDING:
case IDM_EDIT_SORTLINES_INTEGER_DESCENDING:
case IDM_EDIT_SORTLINES_DECIMALCOMMA_ASCENDING:
case IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING:
case IDM_EDIT_SORTLINES_DECIMALDOT_ASCENDING:
case IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING:
{
// default: no selection
size_t fromLine = 0;
@ -374,8 +381,44 @@ void Notepad_plus::command(int id)
toLine = lineRange.second;
}
bool isDescending = id == IDM_EDIT_SORTLINES_LEXICOGRAPHIC_DESCENDING ||
id == IDM_EDIT_SORTLINES_INTEGER_DESCENDING ||
id == IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING ||
id == IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING;
_pEditView->execute(SCI_BEGINUNDOACTION);
_pEditView->sortLines(fromLine, toLine, id == IDM_EDIT_SORTLINES_DESCENDING);
std::unique_ptr<ISorter> pSorter;
if (id == IDM_EDIT_SORTLINES_LEXICOGRAPHIC_DESCENDING || id == IDM_EDIT_SORTLINES_LEXICOGRAPHIC_ASCENDING)
{
pSorter = std::unique_ptr<ISorter>(new LexicographicSorter(isDescending));
}
else if (id == IDM_EDIT_SORTLINES_INTEGER_DESCENDING || id == IDM_EDIT_SORTLINES_INTEGER_ASCENDING)
{
pSorter = std::unique_ptr<ISorter>(new IntegerSorter(isDescending));
}
else if (id == IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING || id == IDM_EDIT_SORTLINES_DECIMALCOMMA_ASCENDING)
{
pSorter = std::unique_ptr<ISorter>(new DecimalCommaSorter(isDescending));
}
else
{
pSorter = std::unique_ptr<ISorter>(new DecimalDotSorter(isDescending));
}
try
{
_pEditView->sortLines(fromLine, toLine, pSorter.get());
}
catch (size_t& failedLineIndex)
{
generic_string lineNo = std::to_wstring(1 + fromLine + failedLineIndex);
_nativeLangSpeaker.messageBox("SortingError",
_pPublicInterface->getHSelf(),
TEXT("Unable to perform numeric sort due to line $STR_REPLACE$."),
TEXT("Sorting Error"),
MB_OK | MB_ICONINFORMATION | MB_APPLMODAL,
0,
lineNo.c_str()); // We don't use intInfo since it would require casting size_t -> int.
}
_pEditView->execute(SCI_ENDUNDOACTION);
if (hasSelection) // there was 1 selection, so we restore it
@ -2598,8 +2641,14 @@ void Notepad_plus::command(int id)
case IDM_EDIT_RTL :
case IDM_EDIT_LTR :
case IDM_EDIT_BEGINENDSELECT:
case IDM_EDIT_SORTLINES_ASCENDING:
case IDM_EDIT_SORTLINES_DESCENDING:
case IDM_EDIT_SORTLINES_LEXICOGRAPHIC_ASCENDING:
case IDM_EDIT_SORTLINES_LEXICOGRAPHIC_DESCENDING:
case IDM_EDIT_SORTLINES_INTEGER_ASCENDING:
case IDM_EDIT_SORTLINES_INTEGER_DESCENDING:
case IDM_EDIT_SORTLINES_DECIMALCOMMA_ASCENDING:
case IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING:
case IDM_EDIT_SORTLINES_DECIMALDOT_ASCENDING:
case IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING:
case IDM_EDIT_BLANKLINEABOVECURRENT:
case IDM_EDIT_BLANKLINEBELOWCURRENT:
case IDM_VIEW_FULLSCREENTOGGLE :

View File

@ -127,8 +127,14 @@ WinMenuKeyDefinition winKeyDefs[] = {
{VK_SPACE, IDM_EDIT_FUNCCALLTIP, true, false, true, NULL},
{VK_R, IDM_EDIT_RTL, true, true, false, NULL},
{VK_L, IDM_EDIT_LTR, true, true, false, NULL},
{VK_NULL, IDM_EDIT_SORTLINES_ASCENDING, false, false, false, NULL},
{VK_NULL, IDM_EDIT_SORTLINES_DESCENDING, false, false, false, NULL},
{VK_NULL, IDM_EDIT_SORTLINES_LEXICOGRAPHIC_ASCENDING, false, false, false, NULL },
{VK_NULL, IDM_EDIT_SORTLINES_LEXICOGRAPHIC_DESCENDING, false, false, false, NULL },
{VK_NULL, IDM_EDIT_SORTLINES_INTEGER_ASCENDING, false, false, false, NULL },
{VK_NULL, IDM_EDIT_SORTLINES_INTEGER_DESCENDING, false, false, false, NULL },
{VK_NULL, IDM_EDIT_SORTLINES_DECIMALCOMMA_ASCENDING, false, false, false, NULL },
{VK_NULL, IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING, false, false, false, NULL },
{VK_NULL, IDM_EDIT_SORTLINES_DECIMALDOT_ASCENDING, false, false, false, NULL },
{VK_NULL, IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING, false, false, false, NULL },
{VK_RETURN, IDM_EDIT_BLANKLINEABOVECURRENT, true, true, false, NULL},
{VK_RETURN, IDM_EDIT_BLANKLINEBELOWCURRENT, true, true, true, NULL},
{VK_F, IDM_SEARCH_FIND, true, false, false, NULL},

View File

@ -29,6 +29,7 @@
#include "precompiledHeaders.h"
#include "ScintillaEditView.h"
#include "Parameters.h"
#include "Sorters.h"
#include "TCHAR.h"
@ -2947,7 +2948,7 @@ void ScintillaEditView::insertNewLineBelowCurrentLine()
execute(SCI_SETEMPTYSELECTION, execute(SCI_POSITIONFROMLINE, current_line + 1));
}
void ScintillaEditView::sortLines(size_t fromLine, size_t toLine, bool isDescending)
void ScintillaEditView::sortLines(size_t fromLine, size_t toLine, ISorter *pSort)
{
if (fromLine >= toLine)
{
@ -2968,23 +2969,16 @@ void ScintillaEditView::sortLines(size_t fromLine, size_t toLine, bool isDescend
}
}
assert(toLine - fromLine + 1 == splitText.size());
const bool isNumericSort = allLinesAreNumericOrEmpty(splitText);
std::vector<generic_string> sortedText;
if (isNumericSort)
{
sortedText = numericSort(splitText, isDescending);
}
else
{
sortedText = lexicographicSort(splitText, isDescending);
}
const std::vector<generic_string> sortedText = pSort->sort(splitText);
const generic_string joined = stringJoin(sortedText, getEOLString());
if (sortEntireDocument)
{
assert(joined.length() == text.length());
replaceTarget(joined.c_str(), startPos, endPos);
}
else
{
assert(joined.length() + getEOLString().length() == text.length());
replaceTarget((joined + getEOLString()).c_str(), startPos, endPos);
}
}

View File

@ -197,6 +197,8 @@ struct LanguageName {
int lexerID;
};
class ISorter;
class ScintillaEditView : public Window
{
friend class Finder;
@ -636,7 +638,7 @@ public:
};
void scrollPosToCenter(int pos);
generic_string getEOLString();
void sortLines(size_t fromLine, size_t toLine, bool isDescending);
void sortLines(size_t fromLine, size_t toLine, ISorter *pSort);
void changeTextDirection(bool isRTL);
bool isTextDirectionRTL() const;

View File

@ -112,8 +112,14 @@
#define IDM_EDIT_REMOVEEMPTYLINESWITHBLANK (IDM_EDIT + 56)
#define IDM_EDIT_BLANKLINEABOVECURRENT (IDM_EDIT + 57)
#define IDM_EDIT_BLANKLINEBELOWCURRENT (IDM_EDIT + 58)
#define IDM_EDIT_SORTLINES_ASCENDING (IDM_EDIT + 59)
#define IDM_EDIT_SORTLINES_DESCENDING (IDM_EDIT + 60)
#define IDM_EDIT_SORTLINES_LEXICOGRAPHIC_ASCENDING (IDM_EDIT + 59)
#define IDM_EDIT_SORTLINES_LEXICOGRAPHIC_DESCENDING (IDM_EDIT + 60)
#define IDM_EDIT_SORTLINES_INTEGER_ASCENDING (IDM_EDIT + 61)
#define IDM_EDIT_SORTLINES_INTEGER_DESCENDING (IDM_EDIT + 62)
#define IDM_EDIT_SORTLINES_DECIMALCOMMA_ASCENDING (IDM_EDIT + 63)
#define IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING (IDM_EDIT + 64)
#define IDM_EDIT_SORTLINES_DECIMALDOT_ASCENDING (IDM_EDIT + 65)
#define IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING (IDM_EDIT + 66)
// Menu macro
#define IDM_MACRO_STARTRECORDINGMACRO (IDM_EDIT + 18)