Commit e917a28a authored by csharp@chromium.org's avatar csharp@chromium.org

Create StringOrdinal to allow placement of strings in sorted lists

The StringOrdinal class is intended to allow sorted lists of strings to be treated as floating point numbers, and can be used to create a value to be sorted into any index location, allowing an element to change it's index without any other elements having to change their index value.

BUG=94662
TEST=Unit tests created in chrome/common/string_ordinal_unittest.cc


Review URL: http://codereview.chromium.org/8236002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@107566 0039d316-1c4b-4281-b951-d872f2087c98
parent 2d7b0872
......@@ -210,6 +210,8 @@
'common/spellcheck_common.cc',
'common/spellcheck_common.h',
'common/spellcheck_messages.h',
'common/string_ordinal.cc',
'common/string_ordinal.h',
'common/switch_utils.cc',
'common/switch_utils.h',
'common/thumbnail_score.cc',
......
......@@ -1878,6 +1878,7 @@
'common/net/gaia/oauth_request_signer_unittest.cc',
'common/random_unittest.cc',
'common/service_process_util_unittest.cc',
'common/string_ordinal_unittest.cc',
'common/switch_utils_unittest.cc',
'common/thumbnail_score_unittest.cc',
'common/time_format_unittest.cc',
......
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/string_ordinal.h"
#include <algorithm>
#include <cstddef>
#include "base/basictypes.h"
#include "base/logging.h"
namespace {
// Constants for StringOrdinal digits.
const char kZeroDigit = 'a';
const char kMinDigit = 'b';
const char kMidDigit = 'n';
const char kMaxDigit = 'z';
const int kMidDigitValue = kMidDigit - kZeroDigit;
const int kMaxDigitValue = kMaxDigit - kZeroDigit;
const int kRadix = kMaxDigitValue + 1;
COMPILE_ASSERT(kMidDigitValue == 13, kMidDigitValue_incorrect);
COMPILE_ASSERT(kMaxDigitValue == 25, kMaxDigitValue_incorrect);
COMPILE_ASSERT(kRadix == 26, kRadix_incorrect);
// Helper Functions
// Returns the length that string value.substr(0, length) would be with
// trailing zeros removed.
size_t GetLengthWithoutTrailingZeros(const std::string& value, size_t length) {
DCHECK(!value.empty());
size_t end_position = value.find_last_not_of(kZeroDigit, length - 1);
// If no non kZeroDigit is found then the string is a string of all zeros
// digits so we return 0 as the correct length.
if (end_position == std::string::npos)
return 0;
return end_position + 1;
}
// Return the digit value at position i, padding with kZeroDigit if required.
int GetPositionValue(const std::string& str, size_t i) {
return (i < str.length()) ? (str[i] - kZeroDigit) : 0;
}
// Add kMidDigitValue to the value at position index. This returns false if
// adding the half results in an overflow past the first digit, otherwise it
// returns true. This is used by ComputeMidpoint.
bool AddHalf(size_t position, std::string& value) {
DCHECK_GT(position, 0U);
DCHECK_LT(position, value.length());
// We can't perform this operation directly on the string because
// overflow can occur and mess up the values.
int new_position_value = value[position] + kMidDigitValue;
if (new_position_value <= kMaxDigit) {
value[position] = new_position_value;
} else {
value[position] = new_position_value - kRadix;
++value[position - 1];
for (size_t i = position - 1; value[i] > kMaxDigit; --i) {
if (i == 0U) {
// If the first digit is too large we have no previous digit
// to increase, so we fail.
return false;
}
value[i] -= kRadix;
++value[i - 1];
}
}
return true;
}
// Drops off the last digit of value and then all trailing zeros iff that
// doesn't change its ordering as greater than |start|.
void DropUnneededDigits(const std::string& start, std::string* value) {
CHECK_GT(*value, start);
size_t drop_length = GetLengthWithoutTrailingZeros(*value, value->length());
// See if the value can have its last digit removed without affecting
// the ordering.
if (drop_length > 1) {
// We must drop the trailing zeros before comparing |shorter_value| to
// |start| because if we don't we may have |shorter_value|=|start|+|a|*
// where |shorter_value| > |start| but not when it drops the trailing |a|s
// to become a valid StringOrdinal value.
int truncated_length = GetLengthWithoutTrailingZeros(*value,
drop_length - 1);
if (truncated_length != 0 && value->compare(0, truncated_length, start) > 0)
drop_length = truncated_length;
}
value->resize(drop_length);
}
// Compute the midpoint string that is between |start| and |end|.
std::string ComputeMidpoint(const std::string& start,
const std::string& end) {
size_t max_size = std::max(start.length(), end.length()) + 1;
std::string midpoint(max_size, kZeroDigit);
bool add_half = false;
for (size_t i = 0; i < max_size; ++i) {
int char_value = GetPositionValue(start, i);
char_value += GetPositionValue(end, i);
midpoint[i] += (char_value / 2);
if (add_half) {
// AddHalf only returns false if (midpoint[0] > kMaxDigit), which
// implies the midpoint of two strings in (0, 1) is >= 1, which is a
// contradiction.
CHECK(AddHalf(i, midpoint));
}
add_half = (char_value % 2 == 1);
}
DCHECK(!add_half);
return midpoint;
}
// Create a StringOrdinal that is lexigraphically greater than |start| and
// lexigraphically less than |end|. The returned StringOrdinal will be roughly
// between |start| and |end|.
StringOrdinal CreateStringOrdinalBetween(const StringOrdinal& start,
const StringOrdinal& end) {
CHECK(start.IsValid());
CHECK(end.IsValid());
CHECK(start.LessThan(end));
const std::string& start_string = start.ToString();
const std::string& end_string = end.ToString();
DCHECK_LT(start_string, end_string);
std::string midpoint = ComputeMidpoint(start_string, end_string);
DropUnneededDigits(start_string, &midpoint);
DCHECK_GT(midpoint, start_string);
DCHECK_LT(midpoint, end_string);
StringOrdinal midpoint_ordinal(midpoint);
DCHECK(midpoint_ordinal.IsValid());
return midpoint_ordinal;
}
// Returns true iff the input string matches the format [a-z]*[b-z].
bool IsValidStringOrdinal(const std::string& value) {
if (value.empty())
return false;
for (size_t i = 0; i < value.length(); ++i) {
if (value[i] < kZeroDigit || value[i] > kMaxDigit)
return false;
}
return value[value.length() - 1] != kZeroDigit;
}
} // namespace
StringOrdinal::StringOrdinal(const std::string& string_ordinal)
: string_ordinal_(string_ordinal),
is_valid_(IsValidStringOrdinal(string_ordinal_)) {
}
StringOrdinal::StringOrdinal() : string_ordinal_(""),
is_valid_(false) {
}
bool StringOrdinal::IsValid() const {
return is_valid_;
}
bool StringOrdinal::LessThan(const StringOrdinal& other) const {
CHECK(IsValid());
CHECK(other.IsValid());
return string_ordinal_ < other.string_ordinal_;
}
bool StringOrdinal::Equal(const StringOrdinal& other) const {
CHECK(IsValid());
CHECK(other.IsValid());
return string_ordinal_ == other.string_ordinal_;
}
StringOrdinal StringOrdinal::CreateBetween(const StringOrdinal& other) const {
CHECK(IsValid());
CHECK(other.IsValid());
CHECK(!Equal(other));
if (LessThan(other)) {
return CreateStringOrdinalBetween(*this, other);
} else {
return CreateStringOrdinalBetween(other, *this);
}
}
StringOrdinal StringOrdinal::CreateBefore() const {
CHECK(IsValid());
// Create the smallest valid StringOrdinal of the appropriate length
// to be the minimum boundary.
const size_t length = string_ordinal_.length();
std::string start(length, kZeroDigit);
start[length - 1] = kMinDigit;
if (start == string_ordinal_) {
start[length - 1] = kZeroDigit;
start += kMinDigit;
}
// Even though |start| is already a valid StringOrdinal that is less
// than |*this|, we don't return it because we wouldn't have much space in
// front of it to insert potential future values.
return CreateBetween(StringOrdinal(start));
}
StringOrdinal StringOrdinal::CreateAfter() const {
CHECK(IsValid());
// Create the largest valid StringOrdinal of the appropriate length to be
// the maximum boundary.
std::string end(string_ordinal_.length(), kMaxDigit);
if (end == string_ordinal_)
end += kMaxDigit;
// Even though |end| is already a valid StringOrdinal that is greater than
// |*this|, we don't return it because we wouldn't have much space after
// it to insert potential future values.
return CreateBetween(StringOrdinal(end));
}
std::string StringOrdinal::ToString() const {
CHECK(IsValid());
return string_ordinal_;
}
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_COMMON_STRING_ORDINAL_H_
#define CHROME_COMMON_STRING_ORDINAL_H_
#pragma once
#include <string>
// A StringOrdinal represents a specially-formatted string that can be used
// for ordering. The StringOrdinal class has an unbounded dense strict total
// order, which mean for any StringOrdinals a, b and c:
//
// - a < b and b < c implies a < c (transitivity);
// - exactly one of a < b, b < a and a = b holds (trichotomy);
// - if a < b, there is a StringOrdinal x such that a < x < b (density);
// - there are StringOrdinals x and y such that x < a < y (unboundedness).
//
// This means that when StringOrdinal is used for sorting a list, if
// any item changes its position in the list, only its StringOrdinal value
// has to change to represent the new order, and all the other older values
// can stay the same.
class StringOrdinal {
public:
// Creates a StringOrdinal from the given string. It may be valid or invalid.
explicit StringOrdinal(const std::string& string_ordinal);
// Creates an invalid StringOrdinal.
StringOrdinal();
bool IsValid() const;
// All remaining functions can only be called if IsValid() holds.
// It is an error to call them if IsValid() is false.
// Order-related Functions
// Returns true iff |*this| < |other|.
bool LessThan(const StringOrdinal& other) const;
// Returns true iff |*this| == |other| (i.e. |*this| < |other| and
// |other| < |*this| are both false).
bool Equal(const StringOrdinal& other) const;
// Given |*this| != |other|, returns a StringOrdinal x such that
// min(|*this|, |other|) < x < max(|*this|, |other|). It is an error
// to call this function when |*this| == |other|.
StringOrdinal CreateBetween(const StringOrdinal& other) const;
// Returns a StringOrdinal |x| such that |x| < |*this|.
StringOrdinal CreateBefore() const;
// Returns a StringOrdinal |x| such that |*this| < |x|.
StringOrdinal CreateAfter() const;
// It is guaranteed that a StringOrdinal constructed from the returned
// string will be valid.
std::string ToString() const;
// Use of copy constructor and default assignment for this class is allowed.
private:
// The string representation of the StringOrdinal.
std::string string_ordinal_;
// The validity of the StringOrdinal (i.e., is it of the format [a-z]*[b-z]),
// created to cache validity to prevent frequent recalculations.
bool is_valid_;
};
#endif // CHROME_COMMON_STRING_ORDINAL_H_
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/string_ordinal.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
TEST(StringOrdinalTest, IsValid) {
EXPECT_TRUE(StringOrdinal("b").IsValid());
EXPECT_TRUE(StringOrdinal("ab").IsValid());
EXPECT_FALSE(StringOrdinal("aa").IsValid());
EXPECT_FALSE(StringOrdinal().IsValid());
}
TEST(StringOrdinalTest, LessThan) {
StringOrdinal small("b");
StringOrdinal middle("n");
StringOrdinal big("z");
EXPECT_TRUE(small.LessThan(middle));
EXPECT_TRUE(small.LessThan(big));
EXPECT_TRUE(middle.LessThan(big));
EXPECT_FALSE(big.LessThan(small));
EXPECT_FALSE(big.LessThan(middle));
EXPECT_FALSE(middle.LessThan(small));
}
// Tests the CreateBetween StringOrdinal function by calling
// on the small_value with the large_value as the parameter and
// vice-versa.
void TestCreateBetween(StringOrdinal small_value,
StringOrdinal large_value,
const std::string& expected_result) {
StringOrdinal result = small_value.CreateBetween(large_value);
EXPECT_EQ(expected_result, result.ToString());
result = large_value.CreateBetween(small_value);
EXPECT_EQ(expected_result, result.ToString());
}
TEST(StringOrdinalTest, CreateBetweenSingleDigit) {
TestCreateBetween(StringOrdinal("b"), StringOrdinal("d"), "c");
TestCreateBetween(StringOrdinal("b"), StringOrdinal("e"), "c");
TestCreateBetween(StringOrdinal("b"), StringOrdinal("f"), "d");
TestCreateBetween(StringOrdinal("c"), StringOrdinal("d"), "cn");
}
TEST(StringOrdinalTest, CreateBetweenDifferentLengths) {
TestCreateBetween(StringOrdinal("b"), StringOrdinal("bb"), "ban");
TestCreateBetween(StringOrdinal("b"), StringOrdinal("db"), "c");
TestCreateBetween(StringOrdinal("bz"), StringOrdinal("c"), "bzn");
TestCreateBetween(StringOrdinal("baaaaaab"), StringOrdinal("d"), "c");
TestCreateBetween(StringOrdinal("baaaaaac"), StringOrdinal("d"), "c");
TestCreateBetween(StringOrdinal("b"), StringOrdinal("daaaaaaab"), "c");
}
TEST(StringOrdinalTest, CreateBetweenOverflow) {
TestCreateBetween(StringOrdinal("ab"), StringOrdinal("bb"), "ao");
TestCreateBetween(StringOrdinal("bb"), StringOrdinal("cb"), "bo");
TestCreateBetween(StringOrdinal("bbb"), StringOrdinal("bcb"), "bbo");
TestCreateBetween(StringOrdinal("aab"), StringOrdinal("zzz"), "n");
TestCreateBetween(StringOrdinal("yyy"), StringOrdinal("zzz"), "zml");
TestCreateBetween(StringOrdinal("yab"), StringOrdinal("zzz"), "z");
TestCreateBetween(StringOrdinal("aaz"), StringOrdinal("zzz"), "n");
TestCreateBetween(StringOrdinal("nnnnz"), StringOrdinal("mmmmz"), "n");
}
TEST(StringOrdinalTest, CreateAfter) {
StringOrdinal result = StringOrdinal("y").CreateAfter();
EXPECT_EQ("yn", result.ToString());
result = StringOrdinal("zy").CreateAfter();
EXPECT_EQ("zyn", result.ToString());
result = StringOrdinal("zzzy").CreateAfter();
EXPECT_EQ("zzzyn", result.ToString());
result = StringOrdinal("yy").CreateAfter();
EXPECT_EQ("zl", result.ToString());
result = StringOrdinal("yz").CreateAfter();
EXPECT_EQ("z", result.ToString());
result = StringOrdinal("z").CreateAfter();
EXPECT_EQ("zm", result.ToString());
}
TEST(StringOrdinalTest, CreateBefore) {
StringOrdinal result = StringOrdinal("b").CreateBefore();
EXPECT_EQ("an", result.ToString());
result = StringOrdinal("bb").CreateBefore();
EXPECT_EQ("ao", result.ToString());
result = StringOrdinal("bc").CreateBefore();
EXPECT_EQ("ao", result.ToString());
result = StringOrdinal("bd").CreateBefore();
EXPECT_EQ("ap", result.ToString());
}
TEST(StringOrdinalTest, ToString) {
StringOrdinal index("b");
EXPECT_EQ(index.ToString(), "b");
index = StringOrdinal("aab");
EXPECT_EQ(index.ToString(), "aab");
index = StringOrdinal("zzz");
EXPECT_EQ(index.ToString(), "zzz");
}
} // namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment