Simpler way to set multiple array slots to one val

2019-04-03 08:50发布

问题:

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.

回答1:

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);


回答2:

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



回答3:

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";
}


回答4:

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.



回答5:

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;
}


回答6:

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;


回答7:

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) ;
}


回答8:

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 << ' ';


}


回答9:

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.



回答10:

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.