Jumping On The Clouds - Revisited¶
Title: Jumping On The Clouds - Revisited
Link: https://www.hackerrank.com/challenges/jumping-on-the-clouds-revisited/
Skipping The Obvious¶
Given that we have already gone for the obvious solution in all previous challenges, we are going to go directly for an iterator-based solution to spare us some lines of text and code.
The challenge demands that we read a number of int
values, to be stored in an std::vector<int>
, and a constant k
that will determine the next jump inside the array of int
values.
For each jump the initial e
(energy value) of 100
will be decremented by 1
, with an extra decrement of 2
if the position we landed on contains a true
, non zero, value. The jump trip is over when the new position is the initial position.
#include <algorithm> // std::copy_n
#include <iostream> // std::cout/cin
#include <iterator> // std::istream/ostream_iterator
#include <vector> // std::vector
template <typename I, typename O>
auto
final_energy(I first, O out, int n, int k, int e) {
auto cloud = first;
do {
auto i = std::distance(first, cloud); // >= 0
cloud = std::next(first, (i + k) % n); // next is at (i + k) % n
e -= 1 + (*cloud * 2); // jump - 1, on non-zero cloud == 1 .. +2
} while(cloud != first);
*out++ = e;
}
///////////////////////////////////////////////////////////////////////////////
// Main
///////////////////////////////////////////////////////////////////////////////
int
main(int, char *[]) {
constexpr auto e = 100; // starting energy level
auto in = std::istream_iterator<int>{std::cin}; // input iterator
auto in_last = std::istream_iterator<int>{}; // input iterator end
auto out = std::ostream_iterator<int>{std::cout, "\n"}; // output iterator
for(; in != in_last; in++) { // resync "in" after copy_n operation
auto n = *in++, k = *in++; // input parameters
auto c = std::vector<bool>{}; // storage
std::copy_n(in, n, std::back_inserter(c)); // copy input
final_energy(c.begin(), out, n, k, e);
}
}
As we did previously, we prepare our solution to solve any number of inputs by checking for end-if-input with std::istream_iterator<int>{}
.
Once the input is gathered the problem is easily solved with a do-while
loop, instead of the usual while
and that is because there is always a first jump. Action that could actually take us to the beginning. Notice that we only need the first
iterator, because the jump calculation formula, (i + k) % n
ensures that we never get a new position greater than n
.
Mimicking std::istream_iterator<T¶
The happy hour idea to improve the solution of this problem comes from looking at the STL. In the problem we use
auto in = std::istream_iterator<int>{std::cin}; // input iterator
auto in_last = std::istream_iterator<int>{}; // input iterator end
We can quickly make an analogy: we traverse the array (std::vector<int>
) until we hit the end of the trip. The fact that the end is the initial position shall not distract us. We can create an iterator that executes the jumps for us until it comes back to the original position. Our iterator will simply take the iterator from the std::vector<int>
that determines the initial position and jump to the next.
Rather than hardcoding the jump in our iterator, it will accept a function to calculate the position. The only parameter the function has to take is the actual position.
template <typename It, typename T = int>
struct JumpingIterator {
// Iterator tags
using iterator_category = std::random_access_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using reference = value_type &;
using pointer = value_type *;
const It m_itfirst;
It m_itcur;
using FunctionMove = std::function<int(int)>;
FunctionMove m_fmove;
bool m_end = false;
JumpingIterator(It first, const FunctionMove &fmove)
: m_itfirst{first}, m_itcur{first}, m_fmove(fmove) {}
JumpingIterator() : m_end{true} {}
That is our iterator basic definition with member attributes and constructors. The wrapped-iterator itself is a template parameter, since we do not know in advance what will come to us.
auto operator *() { return *m_itcur; }
auto &operator ++() { // Prefix increment
if (not m_end) {
auto d = std::distance(m_itfirst, m_itcur); // >= 0
m_itcur = std::next(m_itfirst, m_fmove(d));
m_end = m_itfirst == m_itcur;
}
return *this;
}
The *
dereferencing operator will simply return the value of the current position. The actual job is done in the prefix increment ++
operator, where the current distance to the start is calculated and the jump is executed using the function m_fmove
that was given during construction. To ensure no extra jumps happen, the m_end
flag is checked and calculated also here.
template <typename I, typename FMove, typename FEnergy>
auto
minus_energy(I first, FMove fmove, FEnergy fenergy) {
return std::accumulate(
JumpingIterator<I>(first, fmove), // first
JumpingIterator<I>{}, // last
0, // init
fenergy // binary op
);
}
With such a powerful iterator in the hand we can resort to using std::accumulate
to calculate how much energy will be deducted after all jumps have been performed. And once again the formula is a function parameter, fenergy
. We do not want to write code in the solution that can come as a parameter.
We have taken the typename O
template parameter out of the equation. And this is because it feels redundant to also pass the e
, initial energy level, as a parameter. Our solution function gives us the energy that will be deducted and we can directly deduct it from the initial energy and output the result, all in the main
loop.
auto n = *in++, k = *in++; // input parameters
auto fenergy = [](auto acc, auto x) { return acc - (1 + (x * 2)); };
auto fmove = [&n, &k](auto x) { return (x + k) % n; };
auto c = std::vector<bool>{}; // storage
std::copy_n(in, n, std::back_inserter(c)); // copy input
*out++ = e + minus_energy(c.begin(), fmove, fenergy); // solve
Our JumpingIterator
is ready and passes all tests. We have marked it as an
std::random_access_iterator_tag
because it jumps randomly inside the array range. Granted, it is not a random jump, it is a jump determined by the constant k
and the range length n
, but it fits the description of a RandomAccessIterator.
Here is the complete code.
#include <algorithm> // std::copy_n
#include <functional> // std::function
#include <iostream> // std::cout/cin
#include <iterator> // std::istream/ostream_iterator
#include <numeric> // std::accumulate
#include <vector> // std::vector
template <typename It, typename T = int>
struct JumpingIterator {
// Iterator tags
using iterator_category = std::random_access_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using reference = value_type &;
using pointer = value_type *;
const It m_itfirst;
It m_itcur;
using FunctionMove = std::function<int(int)>;
FunctionMove m_fmove;
bool m_end = false;
JumpingIterator(It first, const FunctionMove &fmove)
: m_itfirst{first}, m_itcur{first}, m_fmove(fmove) {}
JumpingIterator() : m_end{true} {}
auto operator ->() { return &(*m_itcur); }
auto operator *() { return *m_itcur; }
auto &operator ++() { // Prefix increment
if (not m_end) {
auto d = std::distance(m_itfirst, m_itcur); // >= 0
m_itcur = std::next(m_itfirst, m_fmove(d));
m_end = m_itfirst == m_itcur;
}
return *this;
}
// Postfix increment
auto operator ++(int) { JumpingIterator tmp = *this; ++(*this); return tmp; }
auto operator ==(const JumpingIterator& o) const {
return m_end ? o.m_end : (not o.m_end and (m_itcur == o.m_itcur));
}
auto operator !=(const JumpingIterator& o) const { return not (*this == o); }
};
template <typename I, typename FMove, typename FEnergy>
auto
minus_energy(I first, FMove fmove, FEnergy fenergy) {
return std::accumulate(
JumpingIterator<I>(first, fmove), // first
JumpingIterator<I>{}, // last
0, // init
fenergy // binary op
);
}
///////////////////////////////////////////////////////////////////////////////
// Main
///////////////////////////////////////////////////////////////////////////////
int
main(int, char *[]) {
constexpr auto e = 100; // starting energy level
auto in = std::istream_iterator<int>{std::cin}; // input iterator
auto in_last = std::istream_iterator<int>{}; // input iterator end
auto out = std::ostream_iterator<int>{std::cout, "\n"}; // output iterator
for(; in != in_last; in++) { // resync "in" after copy_n operation
auto n = *in++, k = *in++; // input parameters
auto fenergy = [](auto acc, auto x) { return acc - (1 + (x * 2)); };
auto fmove = [&n, &k](auto x) { return (x + k) % n; };
auto c = std::vector<bool>{}; // storage
std::copy_n(in, n, std::back_inserter(c)); // copy input
*out++ = e + minus_energy(c.begin(), fmove, fenergy); // solve
}
}
Adding SFINAE¶
Yes, once again we are going to do some dirty work. Someone has to do it! And we do it because we have, for example, define as parameters the functions that move the iterator and calculate energy deduction. Hence the need to ensure that they take the right number and type of arguments and deliver the expected result.
In the case of the iterator the check does not happen in the struct
definition itself, but in the constructor. This is the earliest moment at which we can pass the fmove
function, and the check has to therefore take place there.
template <typename F, typename = enable_if_fmove<F>>
JumpingIterator(It first, const F &fmove)
: m_itfirst{first}, m_itcur{first}, m_fmove(fmove) {}
The other checks are all made when the templates parameters are defined. We have had to resort to long strings of beloved decltype
/ std::declval<T>
married couple checks. Applying, for example std::is_integral
to the result of std::invoke_result_t
to check if the output type of the fmove
and fenergy
parameters is a match would not work even if it seems optimal.
The full code is presented here.
#include <algorithm> // std::copy_n
#include <functional> // std::function
#include <iostream> // std::cout/cin
#include <iterator> // std::istream/ostream_iterator
#include <numeric> // std::accumulate
#include <vector> // std::vector
#include <type_traits> // std::enable_if, std::is_integral, std::void_t
// SFINAE to check for It being an Input iterator and T and integer-like
template <typename T, typename Tag>
constexpr bool is_it_tag_v =
std::is_base_of_v<Tag, typename std::iterator_traits<T>::iterator_category>;
template <typename I>
constexpr bool is_input_v = is_it_tag_v<I, std::input_iterator_tag>;
template <typename It, typename T>
using enable_if_iter_int =
std::enable_if_t<is_input_v<It> and std::is_integral_v<T>>;
using FunctionMove = std::function<int(int)>;
template<typename, typename = void>
constexpr bool is_fmove_v = false;
template<typename F>
constexpr bool is_fmove_v<F,
std::void_t<
decltype(
std::declval<FunctionMove>()(0) ==
std::declval<std::invoke_result_t<F, int>>()
)>> = true;
template<typename F>
using enable_if_fmove = std::enable_if_t<is_fmove_v<F>>;
template <
typename It, typename T = int, typename = enable_if_iter_int<It, T>>
struct JumpingIterator {
// Iterator tags
using iterator_category = std::random_access_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using reference = value_type &;
using pointer = value_type *;
const It m_itfirst;
It m_itcur;
FunctionMove m_fmove;
bool m_end = false;
template <typename F, typename = enable_if_fmove<F>>
JumpingIterator(It first, const F &fmove)
: m_itfirst{first}, m_itcur{first}, m_fmove(fmove) {}
JumpingIterator() : m_end{true} {}
auto operator ->() { return &(*m_itcur); }
auto operator *() { return *m_itcur; }
auto &operator ++() { // Prefix increment
if (not m_end) {
m_itcur = std::next(
m_itfirst,
m_fmove(std::distance(m_itfirst, m_itcur))
);
m_end = m_itfirst == m_itcur;
}
return *this;
}
// Postfix increment
auto operator ++(int) { JumpingIterator tmp = *this; ++(*this); return tmp; }
auto operator ==(const JumpingIterator& o) const {
return m_end ? o.m_end : (not o.m_end and (m_itcur == o.m_itcur));
}
auto operator !=(const JumpingIterator& o) const { return not (*this == o); }
};
// SFINAE for the solution function
using FunctionEnergy = std::function<int(int, int)>;
template<typename, typename = void>
constexpr bool is_fenergy_v = false;
template<typename F>
constexpr bool is_fenergy_v<F,
std::void_t<
decltype(
std::declval<FunctionEnergy>()(0, 0) ==
std::declval<std::invoke_result_t<F, int, int>>()
)>> = true;
template <typename I, typename FMove, typename FEnergy>
using enable_if_iter_fmove_fenergy = std::enable_if_t<
is_input_v<I> and is_fmove_v<FMove> and is_fenergy_v<FEnergy>>;
template <typename I, typename FMove, typename FEnergy,
typename = enable_if_iter_fmove_fenergy<I, FMove, FEnergy>>
auto
minus_energy(I first, FMove fmove, FEnergy fenergy) {
return std::accumulate(
JumpingIterator<I>(first, fmove), // first
JumpingIterator<I>{}, // last
0, // init
fenergy // binary op
);
}
///////////////////////////////////////////////////////////////////////////////
// Main
///////////////////////////////////////////////////////////////////////////////
int
main(int, char *[]) {
constexpr auto e = 100; // starting energy level
auto in = std::istream_iterator<int>{std::cin}; // input iterator
auto in_last = std::istream_iterator<int>{}; // input iterator end
auto out = std::ostream_iterator<int>{std::cout, "\n"}; // output iterator
for(; in != in_last; in++) { // resync "in" after copy_n operation
auto n = *in++, k = *in++; // input parameters
auto fenergy = [](auto acc, auto x) { return acc - (1 + (x * 2)); };
auto fmove = [&n, &k](auto x) { return (x + k) % n; };
auto c = std::vector<bool>{}; // storage
std::copy_n(in, n, std::back_inserter(c)); // copy input
*out++ = e + minus_energy(c.begin(), fmove, fenergy); // solve
}
}