Home > Enterprise >  Having a hard time figuring out logic behind array manipulation
Having a hard time figuring out logic behind array manipulation

Time:11-20

I am given a filled array of size WxH and need to create a new array by scaling both the width and the height by a power of 2. For example, 2x3 becomes 8x12 when scaled by 4, 2^2. My goal is to make sure all the old values in the array are placed in the new array such that 1 value in the old array fills up multiple new corresponding parts in the scaled array. For example:

old_array = [[1,2],
             [3,4]]

becomes

new_array = [[1,1,2,2],
             [1,1,2,2],
             [3,3,4,4],
             [3,3,4,4]]

when scaled by a factor of 2. Could someone explain to me the logic on how I would go about programming this?

CodePudding user response:

It's actually very simple. I use a vector of vectors for simplicity noting that 2D matrixes are not efficient. However, any 2D matrix class using [] indexing syntax can, and should be for efficiency, substituted.

#include <vector>
using std::vector;

int main()
{
    vector<vector<int>> vin{ {1,2},{3,4},{5,6} };
    size_t scaleW = 2;
    size_t scaleH = 3;
    vector<vector<int>> vout(scaleH * vin.size(), vector<int>(scaleW * vin[0].size()));
    for (size_t i = 0; i < vout.size(); i  )
        for (size_t ii = 0; ii < vout[0].size(); ii  )
            vout[i][ii] = vin[i / scaleH][ii / scaleW];

    auto x = vout[8][3];        // last element s/b 6

}

CodePudding user response:

Here is my take. It is very similar to @Tudor's but I figure between our two, you can pick what you like or understand best.

First, let's define a suitable 2D array type because C 's standard library is very lacking in this regard. I've limited myself to a rather simple struct, in case you don't feel comfortable with object oriented programming.

#include <vector>
// using std::vector

struct Array2d
{
  unsigned rows, cols;
  std::vector<int> data;
};

This print function should give you an idea how the indexing works:

#include <cstdio>
// using std::putchar, std::printf, std::fputs

void print(const Array2d& arr)
{
  std::putchar('[');
  for(std::size_t row = 0; row < arr.rows;   row) {
    std::putchar('[');
    for(std::size_t col = 0; col < arr.cols;   col)
      std::printf("%d, ", arr.data[row * arr.cols   col]);
    std::fputs("]\n ", stdout);
  }
  std::fputs("]\n", stdout);
}

Now to the heart, the array scaling. The amount of nesting is … bothersome.

Array2d scale(const Array2d& in, unsigned rowfactor, unsigned colfactor)
{
  Array2d out;
  out.rows = in.rows * rowfactor;
  out.cols = in.cols * colfactor;
  out.data.resize(std::size_t(out.rows) * out.cols);
  for(std::size_t inrow = 0; inrow < in.rows;   inrow) {
    for(unsigned rowoff = 0; rowoff < rowfactor;   rowoff) {
      std::size_t outrow = inrow * rowfactor   rowoff;
      for(std::size_t incol = 0; incol < in.cols;   incol) {
        std::size_t in_idx = inrow * in.cols   incol;
        int inval = in.data[in_idx];
        for(unsigned coloff = 0; coloff < colfactor;   coloff) {
          std::size_t outcol = incol * colfactor   coloff;
          std::size_t out_idx = outrow * out.cols   outcol;
          out.data[out_idx] = inval;
        }
      }
    }
  }
  return out;
}

Let's pull it all together for a little demonstration:

int main()
{
  Array2d in;
  in.rows = 2;
  in.cols = 3;
  in.data.resize(in.rows * in.cols);
  for(std::size_t i = 0; i < in.rows * in.cols;   i)
    in.data[i] = static_cast<int>(i);
  print(in);
  print(scale(in, 3, 2));
}

This prints

[[0, 1, 2, ]
 [3, 4, 5, ]
 ]
[[0, 0, 1, 1, 2, 2, ]
 [0, 0, 1, 1, 2, 2, ]
 [0, 0, 1, 1, 2, 2, ]
 [3, 3, 4, 4, 5, 5, ]
 [3, 3, 4, 4, 5, 5, ]
 [3, 3, 4, 4, 5, 5, ]
 ]

CodePudding user response:

To be honest, i'm incredibly bad at algorithms but i gave it a shot.
I am not sure if this can be done using only one matrix, or if it can be done in less time complexity.
Edit: You can estimate the number of operations this will make with W*H*S*S where Sis the scale factor, W is width and H is height of input matrix.

I used 2 matrixes m and r, where m is your input and r is your result/output. All that needs to be done is to copy each element from m at positions [i][j] and turn it into a square of elements with the same value of size scale_factor inside r.

Simply put:

int main()
{
    Matrix<int> m(2, 2);

    // initial values in your example
    m[0][0] = 1;
    m[0][1] = 2;
    m[1][0] = 3;
    m[1][1] = 4;
    m.Print();

    // pick some scale factor and create the new matrix
    unsigned long scale = 2;
    Matrix<int> r(m.rows*scale, m.columns*scale);

    // i know this is bad but it is the most
    //  straightforward way of doing this
    // it is also the only way i can think of :(
    for(unsigned long i1 = 0; i1 < m.rows; i1  )
        for(unsigned long j1 = 0; j1 < m.columns; j1  )
            for(unsigned long i2 = i1*scale; i2 < (i1 1)*scale; i2  )
                for(unsigned long j2 = j1*scale; j2 < (j1 1)*scale; j2  )
                    r[i2][j2] = m[i1][j1];

    // the output in your example
    std::cout << "\n\n";
    r.Print();

    return 0;
}

I do not think it is relevant for the question, but i used a class Matrix to store all the elements of the extended matrix. I know it is a distraction but this is still C and we have to manage memory. And what you are trying to achieve with this algorithm needs a lot of memory if the scale_factor is big so i wrapped it up using this:

template <typename type_t>
class Matrix
{
    private:
        type_t** Data;
    public:
        // should be private and have Getters but
        //  that would make the code larger...
        unsigned long rows;
        unsigned long columns;

        // 2d Arrays get big pretty fast with what you are
        //  trying to do.
        Matrix(unsigned long rows, unsigned long columns)
        {
            this->rows = rows;
            this->columns = columns;

            Data = new type_t*[rows];
            for(unsigned long i = 0; i < rows; i  )
                Data[i] = new type_t[columns];
        }

        // It is true, a copy constructor is needed
        //  as HolyBlackCat pointed out
        Matrix(const Matrix& m)
        {
            rows = m.rows;
            columns = m.columns;

            Data = new type_t*[rows];
            for(unsigned long i = 0; i < rows; i  )
            {
                Data[i] = new type_t[columns];
                for(unsigned long j = 0; j < columns; j  )
                    Data[i][j] = m[i][j];
            }
        }

        ~Matrix()
        {
            for(unsigned long i = 0; i < rows; i  )
                delete [] Data[i];
            delete [] Data;
        }

        void Print()
        {
            for(unsigned long i = 0; i < rows; i  )
            {
                for(unsigned long j = 0; j < columns; j  )
                    std::cout << Data[i][j] << " ";
                std::cout << "\n";
            }
        }

        type_t* operator [] (unsigned long row)
        {
            return Data[row];
        }
};

CodePudding user response:

First of all, having a suitable 2D matrix class is presumed but not the question. But I don't know the API of yours, so I'll illustrate with something typical:

struct coord {
    size_t x;  // x position or column count
    size_t y;  // y position or row count
};

template <typename T>
class Matrix2D {
       ⋮  // implementation details
public:
       ⋮  // all needed special members (ctors dtor, assignment)
    Matrix2D (coord dimensions);

    coord dimensions() const;  // return height and width
    const T& cell (coord position) const;  // read-only access
    T& cell (coord position);  // read-write access
    // handy synonym:
    const T& operator[](coord position) const { return cell(position); }
    T& operator[](coord position) { return cell(position); }
};

I just showed the public members I need: create a matrix with a given size, query the size, and indexed access to the individual elements.

So, given that, your problem description is:

template<typename T>
Matrix2D<T> scale_pow2 (const Matrix2D& input, size_t pow)
{
    const auto scale_factor= 1 << pow;
    const auto size_in = input.dimensions();
    Matrix2D<T> result ({size_in.x*scale_factor,size_in.y*scale_factor});
         ⋮ 
         ⋮  // fill up resultreturn result;
}

OK, so now the problem is precisely defined: what code goes in the big blank immediately above?

Each cell in the input gets put into a bunch of cells in the output. So you can either iterate over the input and write a clump of cells in the output all having the same value, or you can iterate over the output and each cell you need the value for is looked up in the input.

The latter is simpler since you don't need a nested loop (or pair of loops) to write a clump.

    for (coord outpos : /* ?? every cell of the output ?? */) {
        coord frompos {
            outpos.x >> pow,
            outpos.y >> pow };
        result[outpos] = input[frompos];
    }

Now that's simple!
Calculating the from position for a given output must match the way the scale was defined: you will have pow bits giving the position relative to this clump, and the higher bits will be the index of where that clump came from

Now, we want to set outpos to every legal position in the output matrix indexes. That's what I need. How to actually do that is another sub-problem and can be pushed off with top-down decomposition.

a bit more advanced

Maybe nested loops is the easiest way to get that done, but I won't put those directly into this code, pushing my nesting level even deeper. And looping 0..max is not the simplest thing to write in bare C without libraries, so that would just be distracting. And, if you're working with matrices, this is something you'll have a general need for, including (say) printing out the answer!

So here's the double-loop, put into its own code:

    struct all_positions {
        coord current {0,0};
        coord end;
        all_positions (coord end) : end{end} {}
        bool next() {
            if (  current.x < end.x)  return true; // not reached the end yet
            current.x = 0;  // reset to the start of the row
            if (  current.y < end.y)  return true;
            return false;  // I don't have a valid position now.
            }
    };

This does not follow the iterator/collection API that you could use in a range-based for loop. For information on how to do that, see my article on Code Project or use the Ranges stuff in the C 20 standard library.

Given this "old fashioned" iteration helper, I can write the loop as:

    all_positions scanner {output.dimensions};  // starts at {0,0}
    const auto& outpos= scanner.current;
    do {
           ⋮ 
       } while (scanner.next());

Because of the simple implementation, it starts at {0,0} and advancing it also tests at the same time, and it returns false when it can't advance any more. Thus, you have to declare it (gives the first cell), use it, then advance&test. That is, a test-at-the-end loop. A for loop in C checks the condition before each use, and advances at the end, using different functions. So, making it compatible with the for loop is more work, and surprisingly making it work with the ranged-for is not much more work. Separating out the test and advance the right way is the real work; the rest is just naming conventions.

As long as this is "custom", you can further modify it for your needs. For example, add a flag inside to tell you when the row changed, or that it's the first or last of a row, to make it handy for pretty-printing.

summary

You need a bunch of things working in addition to the little piece of code you actually want to write. Here, it's a usable Matrix class. Very often, it's prompting for input, opening files, handling command-line options, and that kind of stuff. It distracts from the real problem, so get that out of the way first.

Write your code (the real code you came for) in its own function, separate from any other stuff you also need in order to house it. Get it elsewhere if you can; it's not part of the lesson and just serves as a distraction. Worse, it may be "hard" in ways you are not prepared for (or to do well) as it's unrelated to the actual lesson being worked on.

Figure out the algorithm (flowchart, pseudocode, whatever) in a general way before translating that to legal syntax and API on the objects you are using. If you're just learning C , don't get bogged down in the formal syntax when you are trying to figure out the logic. Until you naturally start to think in C when doing that kind of planning, don't force it. Use whiteboard doodles, tinkertoys, whatever works for you.

Get feedback and review of the idea, the logic of how to make it happen, from your peers and mentors if available, before you spend time coding. Why write up an idea that doesn't work? Fix the logic, not the code.

Finally, sketch the needed control flow, functions and data structures you need. Use pseudocode and placeholder notes.

Then fill in the placeholders and replace the pseudo with the legal syntax. You already planned it out, so now you can concentrate on learning the syntax and library details of the programming language. You can concentrate on "how do I express (some tiny detail) in C " rather than keeping the entire program in your head. More generally, isolate a part that you will be learning; be learning/practicing one thing without worrying about the entire edifice.

To a large extent, some of those ideas translate to the code as well. Top-Down Design means you state things at a high level and then implement that elsewhere, separately. It makes code readable and maintainable, as well as easier to write in the first place. Functions should be written this way: the function explains how to do (what it does) as a list of details that are just one level of detail further down. Each of those steps then becomes a new function. Functions should be short and expressed at one semantic level of abstraction. Don't dive down into the most primitive details inside the function that explains the task as a set of simpler steps.

Good luck, and keep it up!

  • Related