I've been practising my coding skills because I have an interview coming up. The HackerRank challenge has 16 test cases; the code passes 9 of them and the other 7 time out.
If you go to HackerRank Problems Data Structures you might be able to find Array Manipulation under Hard problems. I can't seem to provide a direct link.
Using Microsoft specific parallel extensions in C++ I have gotten execution time for test case 4 down from hours down to 89 seconds when built for release. Test case 4 is one of the test cases that times out on Hacker Rank. I might be able to squeeze another couple of seconds out by using parallel processing in the merge function as well.
Things I don't like about my solution:
- It is totally brute force
- I can't seem to run parallel on HackerRank; maybe I need to try OpenMP.
I'm open to all answers and comments.
Program Challenge Statement
Starting with a 1-indexed array of zeros and a list of operations, for each operation add a value to each the array element between two given indices, inclusive. Once all operations have been performed, return the maximum value in the array.
Example
Queries are interpreted as follows: a b k
1 5 3
4 8 7
6 9 1Add the values of k between the indices a and b inclusive:
index-> 1 2 3 4 5 6 7 8 9 10
[0,0,0, 0, 0,0,0,0,0, 0]
[3,3,3, 3, 3,0,0,0,0, 0]
[3,3,3,10,10,7,7,7,0, 0]
[3,3,3,10,10,8,8,8,1, 0]The largest value is 10 after all operations are performed.
Function Description
Complete the functionarrayManipulation
.
arrayManipulation
has the following parameters:
int n
- the number of elements in the arrayint queries[q][3]
- a two dimensional array of queries where eachqueries[i]
contains three integers,a
,b
, andk
.Returns
int
- the maximum value in the resultant arrayConstraints
- 3 < n < 10⁷
- 1 < m < 2 * 10⁵
- 1 < a < b < n
- 0 < k < 10⁹Sample Input
5 3
1 2 100
2 5 100
3 4 100Sample Output
200
Environment
Dell Precision 7740
Processor Intel(R) Core(TM) i7-9850H CPU @ 2.60GHz 2.59 GHz
Installed RAM 64.0 GB (63.8 GB usable)
System type 64-bit operating system, x64-based processor
Edition Windows 10 Pro Version 20H2
OS build 19042.1348
Visual Studio 2019.
Test Cases
Test Case 1
5
3
1 2 100
2 5 100
3 4 100
Test Case 2
10
3
1 5 3
4 8 7
6 9 1
Test Case 3
10
4
2 6 8
3 5 7
1 8 1
5 9 15
Test Case 4
This is the first 5 lines only, the complete test case is 100,002 lines. You can download the complete test case from my GitHub repository.
10000000
100000
1400906 9889280 90378
6581237 9872072 87106
4386373 9779851 52422
This test case really shows the scope of the problem, 100,000 outer loops, some inner loops with more the 7 million executions.
Test Output
PS C:\Users\PaulC\Documents\ProjectsNfwsi\CodeReview\HackerRankArrayManip\Release> HackerRankArrayManip.exe
How many test cases do you want to run?4
Test Case 1
Test Case 1 File read time 0.271 milliseconds
Test Case 1 result is 200 Execution time 2.0378 milliseconds
Test Case 2
Test Case 2 File read time 0.1541 milliseconds
Test Case 2 result is 10 Execution time 0.0132 milliseconds
Test Case 3
Test Case 3 File read time 0.1068 milliseconds
Test Case 3 result is 31 Execution time 0.0985 milliseconds
Test Case 4
Test Case 4 File read time 77.3068 milliseconds
Test Case 4 result is 2497169732 Execution time 89.1439 Seconds
PS C:\Users\PaulC\Documents\ProjectsNfwsi\CodeReview\HackerRankArrayManip\Release>
C++ Source File
#include <PPL.h>
#include<algorithm>
#include <chrono>
#include<execution>
#include <fstream>
#include <iostream>
#include <mutex>
#include <string>
#include <vector>
constexpr int MAX_TEST_CASE = 4;
/*
* Infrastructure to replace HackerRank input functions
*/
std::vector<int> convertInputLineToIntVector(std::string query_string)
{
constexpr int query_size = 3;
std::vector<int> query;
std::string::iterator intStart = query_string.begin();
std::string::iterator intEnd;
for (int i = 0; i < query_size; i++)
{
intEnd = std::find(intStart, query_string.end(), ' ');
int pos = intEnd - query_string.begin();
std::string tempInt(intStart, intEnd);
query.push_back(stoi(tempInt));
if (intEnd < query_string.end())
{
intStart = query_string.begin() + pos + 1;
}
}
return query;
}
std::vector<std::vector<int>> getIntVectors(std::ifstream* inFile)
{
std::vector<std::vector<int>> inputVector;
std::string string_vector_count;
getline(*inFile, string_vector_count);
int strings_count = stoi(string_vector_count);
for (int i = 0; i < strings_count; i++) {
std::string string_item;
getline(*inFile, string_item);
inputVector.push_back(convertInputLineToIntVector(string_item));
}
return inputVector;
}
int getInputLines(std::string inputFileName, int &vectorSize, std::vector<std::vector<int>>& queries)
{
std::string string_count_size;
std::ifstream inFile(inputFileName);
if (!inFile.is_open())
{
std::cerr << "Can't open " << inputFileName << " for input.\n";
std::cout << "Can't open " << inputFileName << " for input.\n";
return EXIT_FAILURE;
}
getline(inFile, string_count_size);
vectorSize = stoi(string_count_size);
queries = getIntVectors(&inFile);
return EXIT_SUCCESS;
}
void getTestCountAndFirstTestCase(int& testCount, int& firstTestCase)
{
do {
std::cout << "How many test cases do you want to run?";
std::cin >> testCount;
if (testCount < 0 || testCount > MAX_TEST_CASE)
{
std::cerr << "The number of test cases must be greater > 0 and less than " << " " << MAX_TEST_CASE << "\n";
}
} while (testCount < 0 || testCount > MAX_TEST_CASE);
if (testCount < MAX_TEST_CASE)
{
bool hasErrors = true;
do {
std::cout << "What test case file do you want to start with?";
std::cin >> firstTestCase;
if (firstTestCase < 0 || firstTestCase > MAX_TEST_CASE)
{
std::cerr << "The first test cases must be greater > 0 and less than " << " " << MAX_TEST_CASE << "\n";
hasErrors = true;
}
else
{
hasErrors = false;
}
if (!hasErrors && testCount + firstTestCase > MAX_TEST_CASE)
{
std::cerr << "The first test cases and the test count must be less than or equal to " << MAX_TEST_CASE << "\n";
hasErrors = true;
}
} while (hasErrors);
}
else
{
firstTestCase = 1;
}
}
/*
* Begin HackerRank Solution
*/
constexpr int IDX_FIRST_LOCATION = 0;
constexpr int IDX_LAST_LOCATION = 1;
constexpr int IDX_VALUE_TO_ADD = 2;
constexpr int MAX_THREADS = 16;
unsigned long mergeAndFindMax(std::vector<unsigned long> maxValues, std::vector<std::vector<unsigned long>> calculatedValues, const size_t executionCount)
{
unsigned long maximumValue = 0;
for (size_t i = 0; i < MAX_THREADS; i++)
{
std::vector<unsigned long>::iterator cvi = calculatedValues[i].begin();
std::vector<unsigned long>::iterator cvEnd = calculatedValues[i].end();
std::vector<unsigned long>::iterator mvi = maxValues.begin();
for ( ; mvi < maxValues.end() && cvi < cvEnd; mvi++, cvi++)
{
*mvi += *cvi;
if (*mvi > maximumValue)
{
maximumValue = *mvi;
}
}
if (i > executionCount)
{
break;
}
}
return maximumValue;
}
unsigned long arrayManipulation(const int n, const std::vector<std::vector<int>> queries)
{
std::vector<unsigned long> maximumValues(n, 0);
std::vector<std::vector<unsigned long>> calculatedValues(MAX_THREADS);
std::mutex m;
for_each(calculatedValues.begin(), calculatedValues.end(), [maximumValues](std::vector<unsigned long>& cvi) {cvi = maximumValues; });
int executionCount = 0;
Concurrency::parallel_for_each(queries.begin(), queries.end(),
[&m, &calculatedValues, &executionCount](std::vector<int> query)
{
std::lock_guard<std::mutex> guard(m);
size_t startLoc = query[IDX_FIRST_LOCATION];
size_t endLoc = query[IDX_LAST_LOCATION];
const unsigned long valueToAdd = query[IDX_VALUE_TO_ADD];
for_each(calculatedValues[executionCount % MAX_THREADS].begin() + (startLoc - 1),
calculatedValues[executionCount % MAX_THREADS].begin() + endLoc,
[valueToAdd](unsigned long& n) {n += valueToAdd; });
executionCount++;
});
return mergeAndFindMax(maximumValues, calculatedValues, executionCount);
}
int executeAndTimeTestCases(int testCaseCount, int firstTestCase)
{
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
for (int i = 0; i < testCaseCount; i++)
{
std::string testFileName = "TestCase" + std::to_string(firstTestCase) + ".txt";
int n = 0;
std::vector<std::vector<int>> queries;
std::cout << "Test Case " << firstTestCase << "\n";
auto fileReadStartTime = high_resolution_clock::now();
int exitStatus = getInputLines(testFileName, n, queries);
if (exitStatus != EXIT_SUCCESS)
{
return exitStatus;
}
auto fileReadEndTime = high_resolution_clock::now();
duration<double, std::milli> msReadTime = fileReadEndTime - fileReadStartTime;
std::cout << "Test Case " << firstTestCase << " File read time " << msReadTime.count() << " milliseconds\n";
auto executionStartTime = high_resolution_clock::now();
unsigned long result = arrayManipulation(n, queries);
auto executionEndTime = high_resolution_clock::now();
duration<double, std::milli> msExecution = executionEndTime - executionStartTime;
if (msExecution.count() > 1000.0)
{
std::cout << "Test Case " << firstTestCase << " result is " << result << " Execution time " << msExecution.count() / 1000.0 << " Seconds\n\n";
}
else
{
std::cout << "Test Case " << firstTestCase << " result is " << result << " Execution time " << msExecution.count() << " milliseconds\n\n";
}
firstTestCase++;
}
return EXIT_SUCCESS;
}
int main()
{
int testCaseCount = 0;
int firstTestCase = 0;
getTestCountAndFirstTestCase(testCaseCount, firstTestCase);
return executeAndTimeTestCases(testCaseCount, firstTestCase);
}
parallel_for_each
and then to lock the entire scope with a mutex? From my CPU usage, it does not look like much is happening in parallel although memory bandwidth could be an issue. \$\endgroup\$for_each()
and using this documentation. I've removed the mutex in my testing code thanks. That and Deduplicator's suggestion to change vectors to pass by reference rather than pass by value shaved off 6 seconds. \$\endgroup\$