Skip to content

Commit

Permalink
Algorithm: Adds compare as a free function.
Browse files Browse the repository at this point in the history
  • Loading branch information
tgockel committed Oct 14, 2015
1 parent ac1d259 commit 0637e60
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 99 deletions.
156 changes: 154 additions & 2 deletions include/jsonv/algorithm.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** \file jsonv/algorithm.hpp
* A collection of algorithms a la \c <algorithm>.
*
* Copyright (c) 2014 by Travis Gockel. All rights reserved.
* Copyright (c) 2014-2015 by Travis Gockel. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
* as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
Expand All @@ -13,20 +13,172 @@
#define __JSONV_ALGORITHM_HPP_INCLUDED__

#include <jsonv/config.hpp>
#include <jsonv/value.hpp>

#include <functional>

namespace jsonv
{

class path;
class value;

/** \addtogroup Algorithm
* \{
* A collection of useful free functions a la \c <algorithm>.
**/

/** Traits describing how to perform various aspects of comparison. This implementation for comparison is strict and is
* ultimately the one used by \c value::compare.
*
* \see compare
**/
struct JSONV_PUBLIC compare_traits
{
/** Compare two kinds \a a and \a b. This should return 0 if the types are the same or if they are directly
* comparable (such as \c kind::integer and \c kind::decimal) -- if you return 0 for non-comparable types, you risk
* getting a \c kind_error thrown.
**/
static int compare_kinds(kind a, kind b)
{
int va = kindval(a);
int vb = kindval(b);
return va == vb ? 0 : va < vb ? -1 : 1;
}

/** Compare two boolean values. **/
static int compare_booleans(bool a, bool b)
{
return a == b ? 0
: a ? 1
: -1;
}

/** Compare two integer values. **/
static int compare_integers(std::int64_t a, std::int64_t b)
{
return a == b ? 0
: a < b ? -1
: 1;
}

/** Compare two decimal values. **/
static int compare_decimals(double a, double b)
{
return (std::abs(a - b) < (std::numeric_limits<double>::denorm_min() * 10.0)) ? 0
: (a < b) ? -1
: 1;
}

/** Compare two string values. **/
static int compare_strings(const std::string& a, const std::string& b)
{
return a.compare(b);
}

/** Compare two strings used for the keys of objects. **/
static int compare_object_keys(const std::string& a, const std::string& b)
{
return a.compare(b);
}

/** Compare two objects \e before comparing the values. The \c compare function will only check the contents of an
* object if this function returns 0.
**/
static int compare_objects_meta(const value&, const value&)
{
return 0;
}

private:
static int kindval(kind k)
{
switch (k)
{
case jsonv::kind::null:
return 0;
case jsonv::kind::boolean:
return 1;
case jsonv::kind::integer:
case jsonv::kind::decimal:
return 2;
case jsonv::kind::string:
return 3;
case jsonv::kind::array:
return 4;
case jsonv::kind::object:
return 5;
default:
return -1;
}
}
};

/** Compare the values \a a and \a b using the comparison \a traits.
*
* \tparam TCompareTraits A type which should be compatible with the public signatures on the \c compare_traits class.
**/
template <typename TCompareTraits>
int compare(const value& a, const value& b, const TCompareTraits& traits)
{
if (&a == &b)
return 0;

if (int kindcmp = traits.compare_kinds(a.kind(), b.kind()))
return kindcmp;

switch (a.kind())
{
case jsonv::kind::null:
return 0;
case jsonv::kind::boolean:
return traits.compare_booleans(a.as_boolean(), b.as_boolean());
case jsonv::kind::integer:
// b might be a decimal type, but if they are both integers, compare directly
if (b.kind() == jsonv::kind::integer)
return traits.compare_integers(a.as_integer(), b.as_integer());
// fall through
case jsonv::kind::decimal:
return traits.compare_decimals(a.as_decimal(), b.as_decimal());
case jsonv::kind::string:
return traits.compare_strings(a.as_string(), b.as_string());
case jsonv::kind::array:
{
auto aiter = a.begin_array();
auto biter = b.begin_array();
for ( ; aiter != a.end_array() && biter != b.end_array(); ++aiter, ++biter)
if (int cmp = compare(*aiter, *biter, traits))
return cmp;
return aiter == a.end_array() ? biter == b.end_array() ? 0 : -1
: 1;
}
case jsonv::kind::object:
{
if (int objmetacmp = traits.compare_objects_meta(a, b))
return objmetacmp;

auto aiter = a.begin_object();
auto biter = b.begin_object();
for ( ; aiter != a.end_object() && biter != b.end_object(); ++aiter, ++biter)
{
if (int cmp = traits.compare_object_keys(aiter->first, biter->first))
return cmp;
if (int cmp = compare(aiter->second, biter->second, traits))
return cmp;
}
return aiter == a.end_object() ? biter == b.end_object() ? 0 : -1
: 1;
}
default:
return -1;
}
}

/** Compare the values \a a and \a b with strict comparison traits.
*
* \see value::compare
**/
JSONV_PUBLIC int compare(const value& a, const value& b);

/** Run a function over the values in the \a input. The behavior of this function is different, depending on the \c kind
* of \a input. For scalar kinds (\c kind::integer, \c kind::null, etc), \a func is called once with the value. If
* \a input is \c kind::array, \c func is called for every value in the array and the output will be an array with each
Expand Down
22 changes: 22 additions & 0 deletions src/jsonv/algorithm_compare.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** \file
*
* Copyright (c) 2015 by Travis Gockel. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
* as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
* version.
*
* \author Travis Gockel (travis@gockelhut.com)
**/
#include <jsonv/algorithm.hpp>
#include <jsonv/value.hpp>

namespace jsonv
{

int compare(const value& a, const value& b)
{
return compare(a, b, compare_traits());
}

}
14 changes: 0 additions & 14 deletions src/jsonv/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,18 +187,4 @@ value::array_iterator value::erase(const_array_iterator first, const_array_itera
return array_iterator(this, static_cast<size_type>(fdist));
}

namespace detail
{

int array_impl::compare(const array_impl& other) const
{
auto self_iter = _values.begin();
auto othr_iter = other._values.begin();
for (; self_iter != _values.end() && othr_iter != other._values.end(); ++self_iter, ++othr_iter)
if (int cmp = self_iter->compare(*othr_iter))
return cmp;
return self_iter == _values.end() ? othr_iter == other._values.end() ? 0 : -1 : 1;
}

}
}
2 changes: 0 additions & 2 deletions src/jsonv/array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ class JSONV_LOCAL array_impl :

bool empty() const;

int compare(const array_impl& other) const;

public:
array_type _values;
};
Expand Down
2 changes: 1 addition & 1 deletion src/jsonv/detail.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** \file
*
* Copyright (c) 2012 by Travis Gockel. All rights reserved.
* Copyright (c) 2012-2015 by Travis Gockel. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
* as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
Expand Down
14 changes: 0 additions & 14 deletions src/jsonv/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,19 +172,5 @@ value::size_type object_impl::size() const
return _values.size();
}

int object_impl::compare(const object_impl& other) const
{
auto self_iter = _values.begin();
auto othr_iter = other._values.begin();
for (; self_iter != _values.end() && othr_iter != other._values.end(); ++self_iter, ++othr_iter)
{
if (int cmp = self_iter->first.compare(othr_iter->first))
return cmp;
if (int cmp = self_iter->second.compare(othr_iter->second))
return cmp;
}
return self_iter == _values.end() ? othr_iter == other._values.end() ? 0 : -1 : 1;
}

}
}
2 changes: 0 additions & 2 deletions src/jsonv/object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ class JSONV_LOCAL object_impl :
bool empty() const;

value::size_type size() const;

int compare(const object_impl& other) const;

public:
map_type _values;
Expand Down
67 changes: 3 additions & 64 deletions src/jsonv/value.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** \file
*
* Copyright (c) 2012 by Travis Gockel. All rights reserved.
* Copyright (c) 2012-2015 by Travis Gockel. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
* as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
Expand Down Expand Up @@ -407,13 +407,6 @@ bool value::as_boolean() const
return _data.boolean;
}

static int decimal_compare(const double& x, const double& y)
{
return (std::abs(x - y) < (std::numeric_limits<double>::denorm_min() * 10.0)) ? 0
: (x < y) ? -1
: 1;
}

bool value::operator==(const value& other) const
{
if (this == &other && kind_valid(kind()))
Expand All @@ -434,65 +427,11 @@ bool value::operator !=(const value& other) const
return compare(other) != 0;
}

static int kindval(kind k)
{
switch (k)
{
case jsonv::kind::null:
return 0;
case jsonv::kind::boolean:
return 1;
case jsonv::kind::integer:
case jsonv::kind::decimal:
return 2;
case jsonv::kind::string:
return 3;
case jsonv::kind::array:
return 4;
case jsonv::kind::object:
return 5;
default:
return -1;
}
}

static int compare_kinds(kind a, kind b)
{
int va = kindval(a);
int vb = kindval(b);
return va == vb ? 0 : va < vb ? -1 : 1;
}

int value::compare(const value& other) const
{
if (this == &other)
return 0;
using jsonv::compare;

if (int kindcmp = compare_kinds(kind(), other.kind()))
return kindcmp;

switch (kind())
{
case jsonv::kind::null:
return 0;
case jsonv::kind::boolean:
return as_boolean() == other.as_boolean() ? 0 : as_boolean() ? 1 : -1;
case jsonv::kind::integer:
// other might be a decimal type, but if they are both integers, compare directly
if (other.kind() == jsonv::kind::integer)
return as_integer() == other.as_integer() ? 0 : as_integer() < other.as_integer() ? -1 : 1;
// fall through
case jsonv::kind::decimal:
return decimal_compare(as_decimal(), other.as_decimal());
case jsonv::kind::string:
return as_string().compare(other.as_string());
case jsonv::kind::array:
return _data.array->compare(*other._data.array);
case jsonv::kind::object:
return _data.object->compare(*other._data.object);
default:
return -1;
}
return compare(*this, other);
}

bool value::operator< (const value& other) const
Expand Down

0 comments on commit 0637e60

Please sign in to comment.