Simpler way to set multiple array slots to one value
Asked Answered
R

10

21

I'm coding in C++, and I have the following code:

int array[30];
array[9] = 1;
array[5] = 1;
array[14] = 1;

array[8] = 2;
array[15] = 2;
array[23] = 2;
array[12] = 2;
//...

Is there a way to initialize the array similar to the following?

int array[30];
array[9,5,14] = 1;
array[8,15,23,12] = 2;
//...

Note: In the actual code, there can be up to 30 slots that need to be set to one value.

Ruddle answered 4/10, 2013 at 0:38 Comment(3)
if they aren't contiguous it doesn't get much better than this, you could write a function that takes the array and the indiciesMezcaline
There's a GCC extension for this I think. Other than that, there's always array[9] = array[5] = array[14] = 1; I guess.Eleanor
you can also do int number[3] = { 5, 7, 2 };Volnak
M
32

This function will help make it less painful.

void initialize(int * arr, std::initializer_list<std::size_t> list, int value) {
    for (auto i : list) {
        arr[i] = value;
    }
}

Call it like this.

initialize(array,{9,5,14},2);
Mezcaline answered 4/10, 2013 at 0:45 Comment(13)
Was about to post pretty much the same thing (but with templates).Propjet
@Propjet too often SO is about typing speed (though I actually really suck at typing :) )Mezcaline
Not that it's a competition or anything.Propjet
This is exactly what I need, just when I compile, I get the error: error: 'std::initializer_list' has not been declared along with syntax errors due to that. Any ideas?Ruddle
@RandomPerson323 do you have the -std=c++11 flag, what compiler are you usingMezcaline
And be sure to #include <initializer_list>. (My demo: coliru.stacked-crooked.com/a/9e7e4ed2354a9714 )Propjet
I did not set any flag (do you mean in the makefile?), and I am compiling for the nds with devkitPro. When I added the include, the error changed:Ruddle
Now I get syntax errors. This is the first one: error: expected constructor, destructor, or type conversion before '(' tokenRuddle
@RandomPerson323 I'm not familiar with devkitpro, but it says right there you need -std=c++11 for this to work it sounds like your compilers out of date since all the good compilers have full c++11 supportMezcaline
@RandomPerson323 it would be helpful if you edited your question to include exactly what you have, my code compiles fineMezcaline
I added the flag, and fixed a stupid error I accidentally had, and now it compiles :).Ruddle
@RandomPerson323 nice, if possible I recommend you update your compiler to a current one with full c++11 supportMezcaline
let us continue this discussion in chatRuddle
P
9

A variant of aaronman's answer:

template <typename T>
void initialize(T array[], const T& value)
{
}

template <size_t index, size_t... indices, typename T>
void initialize(T array[], const T& value)
{
    array[index] = value;
    initialize<indices...>(array, value);
}

int main()
{
    int array[10];

    initialize<0,3,6>(array, 99);

    std::cout << array[0] << " " << array[3] << " " << array[6] << std::endl;
}

Example: Click here

Prague answered 4/10, 2013 at 1:11 Comment(2)
I like this one best for simplicity, altho coding mine was fun ;)Theressathereto
I like this one the best. Due to the way template expansion works, it's closest to the 'manual' way. Since it's all compiler time indexing, there is even the possibility of memset being used for larger contiguous chunks of identical values.Comatulid
C
7

Just for the fun of it I created a somewhat different approach which needs a bit of infrastructure allowing initialization like so:

double array[40] = {};
"9 5 14"_idx(array) = 1;
"8 15 23 12"_idx(array) = 2;

If the digits need to be separated by commas, there is a small change needed. In any case, here is the complete code:

#include <algorithm>
#include <iostream>
#include <sstream>
#include <iterator>

template <int Size, typename T = int>
class assign
{
    int  d_indices[Size];
    int* d_end;
    T*   d_array;
    void operator=(assign const&) = delete;
public:
    assign(char const* base, std::size_t n)
        : d_end(std::copy(std::istream_iterator<int>(
                      std::istringstream(std::string(base, n)) >> std::skipws),
                          std::istream_iterator<int>(), this->d_indices))
        , d_array()
    {
    }
    assign(assign<Size>* as, T* a)
        : d_end(std::copy(as->begin(), as->end(), this->d_indices))
        , d_array(a) {
    }
    assign(assign const& o)
        : d_end(std::copy(o.begin(), o.end(), this->d_indices))
        , d_array(o.d_array)
    {
    }
    int const* begin() const { return this->d_indices; }
    int const* end() const   { return this->d_end; }
    template <typename A>
    assign<Size, A> operator()(A* array) {
        return assign<Size, A>(this, array);
    }
    void operator=(T const& value) {
        for (auto it(this->begin()), end(this->end()); it != end; ++it) {
            d_array[*it] = value;
        }
    }
};

assign<30> operator""_idx(char const* base, std::size_t n)
{
    return assign<30>(base, n);
}

int main()
{
    double array[40] = {};
    "1 3 5"_idx(array) = 17;
    "4 18 7"_idx(array) = 19;
    std::copy(std::begin(array), std::end(array),
              std::ostream_iterator<double>(std::cout, " "));
    std::cout << "\n";
}
Clinquant answered 4/10, 2013 at 1:19 Comment(5)
Although this would not be as readably, it would definitely save space.Ruddle
This is slightly disturbing +1Mezcaline
Lol, yes, this it crazy. One question: Would this be more efficient (At running the program, not typing), and if so, by how much?Ruddle
@Dietmar Kühl Interesting. I must play around with the literal operator sometime. Can you see any problems with my answer?Theressathereto
@RandomPerson323: The implementation in the answer uses a fairly heavy-weight run-time approach to parsing the string. It could be done at compile-time, emitting constant expression, I think but it wouldn't necessarily be faster. Personally, I'm not quite satisfied with the notation and my main reason for posting it was to show an alternative which may lead to something useful.Sanjuanitasank
T
5

I just had a play around for the sake of fun / experimentation (Note my concerns at the bottom of the answer):

It's used like this:

smartAssign(array)[0][8]       = 1;
smartAssign(array)[1][4][2]    = 2;
smartAssign(array)[3]          = 3;
smartAssign(array)[5][9][6][7] = 4;

Source code:

#include <assert.h> //Needed to test variables
#include <iostream>
#include <cstddef>

template <class ArrayPtr, class Value>
class SmartAssign
{
    ArrayPtr m_array;

public:
    class Proxy
    {
        ArrayPtr m_array;
        size_t m_index;
        Proxy* m_prev;

        Proxy(ArrayPtr array, size_t index)
            : m_array(array)
            , m_index(index)
            , m_prev(nullptr)
        { }

        Proxy(Proxy* prev, size_t index)
            : m_array(prev->m_array)
            , m_index(index)
            , m_prev(prev)
        { }

        void assign(Value value)
        {
            m_array[m_index] = value;            
            for (auto prev = m_prev; prev; prev = prev->m_prev) {
                m_array[prev->m_index] = value;
            }
        }

    public:
        void operator=(Value value)
        {
            assign(value);
        }

        Proxy operator[](size_t index)
        {
          return Proxy{this, index};
        }

        friend class SmartAssign;
    };

    SmartAssign(ArrayPtr array)
        : m_array(array)
    {
    }


    Proxy operator[](size_t index)
    {
        return Proxy{m_array, index};
    }
};

template <class T>
SmartAssign<T*, T> smartAssign(T* array)
{
    return SmartAssign<T*, T>(array);
}

int main()
{
    int array[10];

    smartAssign(array)[0][8]       = 1;
    smartAssign(array)[1][4][2]    = 2;
    smartAssign(array)[3]          = 3;
    smartAssign(array)[5][9][6][7] = 4;

    for (auto i : array) {
        std::cout << i << "\n";
    }

    //Now to test the variables
    assert(array[0] == 1 && array[8] == 1);
    assert(array[1] == 2 && array[4] == 2 && array[2] == 2);
    assert(array[3] == 3);
    assert(array[5] == 4 && array[9] == 4 && array[6] == 4 && array[7] == 4);
}

Let me know what you think, I don't typically write much code like this, I'm sure someone will point out some problems somewhere ;)

I'm not a 100% certain of the lifetime of the proxy objects.

Theressathereto answered 4/10, 2013 at 2:2 Comment(0)
D
2

The best you can do if your indexes are unrelated is "chaining" the assignments:

array[9] = array[5] = array[14] = 1;

However if you have some way to compute your indexes in a deterministic way you could use a loop:

for (size_t i = 0; i < 3; ++i)
    array[transform_into_index(i)] = 1;

This last example also obviously applies if you have some container where your indexes are stored. So you could well do something like this:

const std::vector<size_t> indexes = { 9, 5, 14 };
for (auto i: indexes)
    array[i] = 1;
Deemphasize answered 4/10, 2013 at 0:42 Comment(6)
With only 30 values, I'm not sure if the overhead of calling a function transform_into_index() is worth a few less lines of code. Performance hit for nothing.Office
In C++, a function call is often not a performance hit at all. Also, premature optimization is the root of all evil.Propjet
@Office I for one am not sure the performance hit is a reason enough to write less readable code, especially since the function might well be inlined (and thus the call costs nothing ; the loop may in fact be unrolled and all indexes computed at compile-time, think constexpr).Deemphasize
But the code is not unreadable. Perhaps sorting the statements so that array[1] =, array[2]=... this is called loop unrolling and has advantages. Simple > Complicated. I stress again that if there were 300 values or something, then a loop would most definitely be needed.Office
@Office Well, "unreadable" is a matter of opinion. Repeating the same variable name 30 times is definitely unreadable (rather unmaintainable, actually) in my book.Deemphasize
From reading the values op posted, there does not appear to be any easy pattern of the indices ( I tried to find a simple transformation), which is why I suspect a performance hit because transform_into_index() is going to be complicated. Or impossible =).Office
C
2

Compilers which still doesn't support variadic template argument and universal initialization list, it can be a pain to realize, that some of the posted solution will not work

As it seems, OP only intends to work with arrays of numbers, valarray with variable arguments can actually solve this problem quite easily.

#include <valarray>     
#include <cstdarg>
#include <iostream>
#include <algorithm>
#include <iterator>
template <std::size_t size >
std::valarray<std::size_t>  selection( ... )
{
    va_list arguments; 
    std::valarray<std::size_t> sel(size);   
    //Skip the first element
    va_start ( arguments, size );
    va_arg ( arguments, int );
    for(auto &elem : sel)
        elem = va_arg ( arguments, int );
    va_end ( arguments );
    return sel;

}
int main ()
{
    //Create an array of 30 integers
    std::valarray<int> array(30);
    //The first argument is the count of indexes
    //followed by the indexes of the array to initialize
    array[selection<3>(9,5,14)] = 1;
    array[selection<4>(8,15,13, 12)] = 2;
    std::copy(std::begin(array), std::end(array),
              std::ostream_iterator<int>(std::cout, " "));
    return 0;
}
Cockatiel answered 4/10, 2013 at 7:0 Comment(9)
I liked this one! (though i hate va_lists) but i've tested the code on ideone ( ideone.com/tCma9B ) and doesn't works: it doesn't initialize the first index passed on selection.Fibrilliform
@PaperBirdMaster: The first index is the length rather than the Index, so it will not be initialized. As you might recollect, the way argument passing works, there should be someway for the recipient to know what is the data type and no of data passed. The first number in the list actually does it.Cockatiel
@PaperBirdMaster: I understand your predicament in using va_list, but unless we have something better initialization list or variadic template, that is the only viable approach.Cockatiel
oh! first number is the lenght, sorry for the misunderstanding :OFibrilliform
So, is std::valarray<int> array(30); to create the array?Ruddle
@RandomPerson323: Yes, that will create an array of 30 integersCockatiel
OK, when I get the time I will look into this.Ruddle
@PaperBirdMaster: I have modified the code, to remove the dependency on the length argumentCockatiel
@RandomPerson323: And my guess is, this solution would be comparable in terms of efficiency to the already posted once.Cockatiel
M
1

I remember, for static initialization exist syntax like:

int array[30] = {
  [9] = 1, [8] = 2
}

And so on. This works in gcc, about another compilers - I do not know.

Martinemartineau answered 4/10, 2013 at 0:43 Comment(1)
This seems like it would be simpler, except sadly, the compiler I'm using gives me the error: sorry, unimplemented: non-trivial designated initializers not supported.Ruddle
K
1

Use overload operator << .

#include <iostream>
#include <iomanip>
#include <cmath>

// value and indexes wrapper
template< typename T,  std::size_t ... Ints> struct _s{ T value; };

//deduced value type
template< std::size_t ... Ints,  typename T>
constexpr inline   _s<T, Ints... >  _ ( T const& v )noexcept { return {v}; }


// stored array reference
template< typename T, std::size_t N>
struct _ref
{
    using array_ref = T (&)[N];

    array_ref ref;
};


//join _s and _ref with << operator.
template< 
        template< typename , std::size_t ... > class IC, 
        typename U, std::size_t N, std::size_t ... indexes
        >
constexpr _ref<U,N> operator << (_ref<U,N> r, IC<U, indexes...> ic ) noexcept
{
    using list = bool[];
    return (  (void)list{ false, (  (void)(r.ref[indexes] = ic.value), false) ... }) , r ;

    //return r;
}


//helper function, for creating _ref<T,N> from array.
template< typename T, std::size_t N>
constexpr inline _ref<T,N> _i(T (&array)[N] ) noexcept { return {array}; }



int main()
{

   int a[15] = {0};

   _i(a) << _<0,3,4,5>(7) << _<8,9, 14>( 6 ) ;


   for(auto x : a)std::cout << x << "  " ;  
   //       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14
  //result: 7  0  0  7  7  7  0  0  6  6  0  0  0  0  6


  double b[101]{0};

  _i(b) << _<0,10,20,30,40,50,60,70,80,90>(3.14) 
        << _<11,21,22,23,24,25>(2.71) 
        << _<5,15,25,45,95>(1.414) ;
}
Kendrick answered 4/10, 2013 at 9:34 Comment(2)
Although this would be slightly less readable (because of the underscore syntax), how wold it compare speed-wise to aarman's answer?Ruddle
I agree about less readable. aarman's answer is good, but it can't check array bound. My implement can't check array bound,too, but it can give warning at compile time.Kendrick
K
1
struct _i_t
{
    int * array;


    struct s
    {
        int* array;
        std::initializer_list<int> l;

        s const&   operator = (int value) const noexcept
        {
            for(auto i : l )
              array[i] = value;

            return *this;
        }
    };

    s operator []( std::initializer_list<int> i ) const noexcept
    {
        return s{array, i};
    }
};

template< std::size_t N>
constexpr _i_t   _i( int(&array)[N]) noexcept { return {array}; }

int main()
{
  int a[15] = {0};
  _i(a)[{1,3,5,7,9}] =  7;

  for(auto x : a)std::cout << x << ' ';


}
Kendrick answered 4/10, 2013 at 14:16 Comment(0)
O
0

Any fancy trickery you do will be unrolled by the compiler/assembler into exactly what you have. Are you doing this for readability reasons? If your array is already init, you can do:

array[8] = array[15] = array[23] = array[12] = 2;

But I stress my point above; it will be transformed into exactly what you have.

Office answered 4/10, 2013 at 0:44 Comment(1)
Yes, like you mentioned, I am doing this for readiblity/simplicity, rather than for efficiency.Ruddle

© 2022 - 2024 — McMap. All rights reserved.