Який найпростіший спосіб ініціалізувати std :: vector з жорстко кодованими елементами?


611

Я можу створити масив та ініціалізувати його так:

int a[] = {10, 20, 30};

Як створити std::vectorі ініціалізувати його настільки ж елегантно?

Найкращий спосіб мене знає:

std::vector<int> ints;

ints.push_back(10);
ints.push_back(20);
ints.push_back(30);

Чи є кращий спосіб?


1
якщо ви не збираєтесь змінювати розмір ints після ініціалізації, подумайте про використання масиву tr1.
зр.

@zr, ти мені цікавий ... якщо мені потрібен фіксований розмір, чи не можу я сам використовувати звичайні старі масиви? Дивлячись на масив tr1 прямо зараз ...
Агнел Куріан

2
tr1::arrayкорисно, тому що звичайні масиви не забезпечують інтерфейс контейнерів STL
Мануель

Змінено заголовок, щоб зробити це явно питанням C ++ 03. Здавалося, простіше, ніж пройти і виправити всі відповіді, щоб мати сенс за допомогою нового стандарту C ++.
ТЕД

Відповіді:


548

Одним із методів було б використання масиву для ініціалізації вектора

static const int arr[] = {16,2,77,29};
vector<int> vec (arr, arr + sizeof(arr) / sizeof(arr[0]) );

7
@Agnel Він буде добре працювати без staticабо const, однак вони обидва роблять більш чіткими щодо того, як це слід використовувати, і дозволяють компілятору робити додаткові оптимізації.
Якобі

68
Я не спростував цього, але мене спокусило. В основному тому, що це майже нічого не економить від просто використання ініціалізованого масиву. Однак це справді вина C ++, а не ваша.
ТЕД

2
Чи можете ви пояснити, чому ви використовуєте ці параметри при визначенні вектора vec.
DomX23

13
sizeof (масив) - одне з небагатьох винятків, що дозволяє отримати загальний розмір елементів масиву, а НЕ розмір вказівника arr. Тому в основному він використовує вектор (pointer_to_first_element, pointer_to_first_element + size_in_bytes_of_the_whole_array / size_of_one_element), тобто: вектор (pointer_to_first_element, pointer_after_final_element). Тип вже заданий за допомогою <int>, тому вектор знає, скільки коштує один елемент. Пам’ятайте, що ітератори можуть трактуватися як покажчики, тому ви в основному використовуєте конструктор векторів (ітератор початок, кінець ітератора)
Johnny Pauling

11
@ TED: Іноді потрібно змінити отриманий вектор. Наприклад, вам може знадобитися завжди мати деякі параметри за замовчуванням, а іноді до них додавати кілька налаштованих.
DarkWanderer

641

Якщо ваш компілятор підтримує C ++ 11, ви можете просто зробити:

std::vector<int> v = {1, 2, 3, 4};

Це доступно в GCC з версії 4.4 . На жаль, VC ++ 2010, схоже, відстає в цьому відношенні.

Крім того, бібліотека Boost.Assign використовує не макро магію, щоб дозволити наступне:

#include <boost/assign/list_of.hpp>
...
std::vector<int> v = boost::assign::list_of(1)(2)(3)(4);

Або:

#include <boost/assign/std/vector.hpp>
using namespace boost::assign;
...
std::vector<int> v;
v += 1, 2, 3, 4;

Але майте на увазі, що це має певні накладні витрати (в основному, це list_ofконструкція std::dequeпід кришкою), тож для критичного для продуктивності коду вам краще зробити так, як каже Yacoby.


Оскільки вектори змінюють розмір, було б нормально ініціалізувати його як порожнє? Як і в конструкторі: this->vect = {};?
Azurespot

3
@Azurespot Ви можете просто ініціалізувати його, і він буде порожнім:std::vector<T> vector;
Лука

2
На всякий випадок, коли хтось може бути цікавим std::vector<int> v = {1, 2, 3, 4};, вектори initializer list constructorбудуть викликані для подібної ініціалізації, її документ можна знайти в C++ 11розділі .
simomo

103

Якщо можете, скористайтеся сучасним способом C ++ [11,14,17, ...]:

std::vector<int> vec = {10,20,30};

Старий спосіб перекидання масиву змінної довжини або використання sizeof()його справді жахливий для очей і зовсім непотрібний з точки зору розумових накладних витрат. Гидота.


2
Справедливо кажучи, це спочатку було питання C ++ 03, але я сподіваюся, що люди / компанії приймуть нові стандарти. C ++ все ще потребує реалізації масиву змінної довжини (VLA) у стандартній бібліотеці, аналогічно тому, що доступне в Eigen та Boost.
Адам Еріксон

На жаль, у деяких випадках такий підхід є проблематичним, наприклад, open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1467 . Гидота.
Гонки легкості на орбіті

Якщо "список-ініціалізація сукупності з одного і того ж типу" є вашою справою, ймовірно, у вашій кодовій базі є більші проблеми ... Я не можу придумати жодної програми, яка б виправдовувала проблеми налагодження.
Адам Еріксон

77

У C ++ 0x ви зможете зробити це так само, як ви це зробили з масивом, але не в поточному стандарті.

Маючи лише мовну підтримку, ви можете використовувати:

int tmp[] = { 10, 20, 30 };
std::vector<int> v( tmp, tmp+3 ); // use some utility to avoid hardcoding the size here

Якщо ви можете додати інші бібліотеки, ви можете спробувати boost :: assignment:

vector<int> v = list_of(10)(20)(30);

Щоб уникнути жорсткого кодування розміру масиву:

// option 1, typesafe, not a compile time constant
template <typename T, std::size_t N>
inline std::size_t size_of_array( T (&)[N] ) {
   return N;
}
// option 2, not typesafe, compile time constant
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

// option 3, typesafe, compile time constant
template <typename T, std::size_t N>
char (&sizeof_array( T(&)[N] ))[N];    // declared, undefined
#define ARRAY_SIZE(x) sizeof(sizeof_array(x))

Звичайно, я не сказав, але у мене все-таки виникає питання: коли розмір масиву не є константним часом компіляції? Тобто, у яких випадках ви б використали перше рішення у своєму другому фрагменті на відміну від третього?
Мануель

4
@Manuel, розмір масиву є частиною типу, і як такий він є постійною часом компіляції. Тепер варіант 1 використовує константи часу компіляції 'N' як значення повернення для функції. Повернення функції - це не час компіляції, а значення часу виконання, навіть якщо воно, ймовірно, буде накреслене як постійне значення в місці виклику. Різниця полягає в тому, що ти не можеш робити: в int another[size_of_array(array)]той час як ти можеш це зробити int another[ARRAY_SIZE(array)].
Девід Родрігес - дрибес

1
У варіанті 3: Я насправді не розумію, що ви маєте на увазі під "оголошеним, невизначеним"? Так що змінна не займе додаткової пам'яті?
To1ne

1
@ To1ne, що насправді є декларацією функції, а не змінною. Причина або її визначення полягає в тому, що ми насправді не хочемо функції для чогось іншого, крім sizeofвиразу, яке не потребує визначення. Хоча ви насправді можете надати визначення, для правильного його виконання знадобиться статичне виділення масиву та повернення посилання на нього, і наступним питанням буде те, що матиме сенс як значення для масиву? (Також зауважте, що це означає один масив на комбінацію типів / розмірів екземплярів функції!) Оскільки це не є розумним для використання, я б краще уникнути його.
Девід Родрігес - дрибес

1
@mhd: Ви не можете побудувати порожній масив на мові. 'int arr [0] = {};' недійсний код C ++. Але ви праві, що якщо ви хочете ініціалізувати порожній вектор та не порожній вектор, вам доведеться використовувати різні конструкції. Оскільки C ++ 11 це не проблема, оскільки ви можете використовувати конструктор списку ініціалізаторів
David Rodríguez - dribeas

61

В C ++ 11:

#include <vector>
using std::vector;
...
vector<int> vec1 { 10, 20, 30 };
// or
vector<int> vec2 = { 10, 20, 30 };

Використання boost list_of:

#include <vector>
#include <boost/assign/list_of.hpp>
using std::vector;
...
vector<int> vec = boost::assign::list_of(10)(20)(30);

Використання прискореного призначення:

#include <vector>
#include <boost/assign/std/vector.hpp>
using std::vector;
...
vector<int> vec;
vec += 10, 20, 30;

Звичайний STL:

#include <vector>
using std::vector;
...
static const int arr[] = {10,20,30};
vector<int> vec (arr, arr + sizeof(arr) / sizeof(arr[0]) );

Звичайний STL з загальними макросами:

#include <vector>
#define ARRAY_SIZE(ar) (sizeof(ar) / sizeof(ar[0])
#define ARRAY_END(ar) (ar + ARRAY_SIZE(ar))
using std::vector;
...
static const int arr[] = {10,20,30};
vector<int> vec (arr, ARRAY_END(arr));

Звичайний STL з макросом векторного ініціалізатора:

#include <vector>
#define INIT_FROM_ARRAY(ar) (ar, ar + sizeof(ar) / sizeof(ar[0])
using std::vector;
...
static const int arr[] = {10,20,30};
vector<int> vec INIT_FROM_ARRAY(arr);

2
C ++ 11 також підтримує std::beginі std::endмасив, так що вектор також може бути ініціалізований static const int arr[] = {10,20,30}; vector<int> vec(begin(arr), end(arr));.
Jaege

54

Просто думав, що я кину мою 0,02 долара. Я схильний заявити про це:

template< typename T, size_t N >
std::vector<T> makeVector( const T (&data)[N] )
{
    return std::vector<T>(data, data+N);
}

десь у заголовку утиліти, і тоді все, що потрібно:

const double values[] = { 2.0, 1.0, 42.0, -7 };
std::vector<double> array = makeVector(values);

Але я не можу чекати C ++ 0x. Я застряг, бо мій код також повинен збиратися у Visual Studio. Бу


1
Цей прийом можна також використовувати для перевантаження функції для прийняття масиву з набраним розміром.
Андрес Ріофріо

4
Чи можете ви пояснити const T (&data)[N]частину? Як визначається розмір масиву у вашому дзвінку makeVector(values)?
Патрик

36

До C ++ 11:

Спосіб 1 =>

vector<int> v(arr, arr + sizeof(arr)/sizeof(arr[0]));
vector<int>v;

Спосіб 2 =>

 v.push_back(SomeValue);

С ++ 11 внизу також можливий

vector<int>v = {1, 3, 5, 7};

28

Починаючи з:

int a[] = {10, 20, 30}; //i'm assuming a is just a placeholder

Якщо у вас немає компілятора C ++ 11 і ви не хочете використовувати boost:

const int a[] = {10, 20, 30};
const std::vector<int> ints(a,a+sizeof(a)/sizeof(int)); //make it const if you can

Якщо у вас немає компілятора C ++ 11 і ви можете використовувати boost:

#include <boost/assign.hpp>
const std::vector<int> ints = boost::assign::list_of(10)(20)(30);

Якщо у вас є компілятор C ++ 11:

const std::vector<int> ints = {10,20,30};

22

Для векторної ініціалізації -

vector<int> v = {10,20,30}

можна зробити, якщо у вас є компілятор c ++ 11.

В іншому випадку, ви можете мати масив даних, а потім використовувати цикл.

int array[] = {10,20,30}
for(unsigned int i=0; i<sizeof(array)/sizeof(array[0]); i++)
{
     v.push_back(array[i]);
}

Крім них, існують різні інші способи, описані вище, використовуючи деякий код. На мою думку, ці способи легко запам’ятати і швидко записати.



16

Я будую власне рішення, використовуючи va_arg. Це рішення сумісне з C ++ 98.

#include <cstdarg>
#include <iostream>
#include <vector>

template <typename T>
std::vector<T> initVector (int len, ...)
{
  std::vector<T> v;
  va_list vl;
  va_start(vl, len);
  for (int i = 0; i < len; ++i)
    v.push_back(va_arg(vl, T));
  va_end(vl);
  return v;
}

int main ()
{
  std::vector<int> v = initVector<int> (7,702,422,631,834,892,104,772);
  for (std::vector<int>::const_iterator it = v.begin() ; it != v.end(); ++it)
    std::cout << *it << std::endl;
  return 0;
}

Демо


14

Якщо ваш компілятор підтримує макроси Variadic (що справедливо для більшості сучасних компіляторів), ви можете використовувати наступний макрос, щоб перетворити ініціалізацію вектора в однолінійний:

#define INIT_VECTOR(type, name, ...) \
static const type name##_a[] = __VA_ARGS__; \
vector<type> name(name##_a, name##_a + sizeof(name##_a) / sizeof(*name##_a))

За допомогою цього макросу ви можете визначити ініціалізований вектор з таким кодом:

INIT_VECTOR(int, my_vector, {1, 2, 3, 4});

Це створило б новий вектор вкладених назв my_vector з елементами 1, 2, 3, 4.


13

Якщо ви не хочете використовувати boost, але хочете насолоджуватися синтаксисом, як

std::vector<int> v;
v+=1,2,3,4,5;

просто включіть цю частину коду

template <class T> class vector_inserter{
public:
    std::vector<T>& v;
    vector_inserter(std::vector<T>& v):v(v){}
    vector_inserter& operator,(const T& val){v.push_back(val);return *this;}
};
template <class T> vector_inserter<T> operator+=(std::vector<T>& v,const T& x){
    return vector_inserter<T>(v),x;
}

1
Я не зміг зрозуміти, як користуватися цим кодом, але це виглядає цікаво.
Даніель Бакмастер

Це так, як сказано в коментарі вище. Просто перевантаження + = і оператор комами. Введення круглих дужок для ясності: ((((v+=1),2),3),4),5) Ось так це працює: По-перше, vector<T> += Tповертає vector_inserter дозволяє називати його, viякий інкапсулює початковий вектор, потім vi,Tдодає T до вихідного вектора, який viінкапсулює і повертає його сам, щоб ми могли vi,Tповторити.
Piti Ongmongkolkul

цей код не працював правильно на gcc 4.2.1, я думаю, через повернення посилання на локальну змінну всередині оператора + =, але ідея є чудовою. Я відредагував код, і з'явиться ще один конструктор копій. потоку зараз -> + = -> ctor -> кома -> копія -> dtor -> кома ...... -> кома -> dtor.
Євген

Я б, мабуть, перевантажив << замість + =. Принаймні << вже має невиразні правила побічних ефектів через зміщення бітів і cout
Speed8ump

11

В C ++ 11:

static const int a[] = {10, 20, 30};
vector<int> vec (begin(a), end(a));

21
Якщо ви вже використовуєте C ++ 11, ви можете також скористатися прямим підходом - vector<int> arr = {10, 20, 30};.
Бернхард Баркер

Насправді я мав вхідний int [] (деякий C lib) і хотів просунутися у вектор (C ++ lib). Ця відповідь допомогла, решта не ;-)
Туманність

10

ви можете зробити це за допомогою boost :: призначити.

vector<int> values;  
values += 1,2,3,4,5,6,7,8,9;

детальніше тут


19
Я давно не бачив гіршого випадку, коли оператор перевантажує зловживання. Чи +=дотримується там 1,2,3,4 .. до кінця значень, або додає 1 до 1-го елемента, 2 до 2-го елемента, 3 до 3-го елемента (як такий синтаксис повинен бути у MATLAB- like languages)
bobobobo

10

Пізніша копія питання має ця відповідь на Вікторі SEHR . Для мене він компактний, візуально привабливий (виглядає так, що ви 'заштовхуєте' значення у), не вимагає c ++ 11 або стороннього модуля і уникає використання додаткової (записаної) змінної. Нижче описано, як я використовую його з кількома змінами. Я можу перейти до розширення функції вектора та / або va_arg у майбутньому інтерваді.


// Based on answer by "Viktor Sehr" on Stack Overflow
// https://stackoverflow.com/a/8907356
//
template <typename T>
class mkvec {
public:
    typedef mkvec<T> my_type;
    my_type& operator<< (const T& val) {
        data_.push_back(val);
        return *this;
    }
    my_type& operator<< (const std::vector<T>& inVector) {
        this->data_.reserve(this->data_.size() + inVector.size());
        this->data_.insert(this->data_.end(), inVector.begin(), inVector.end());
        return *this;
    }
    operator std::vector<T>() const {
        return data_;
    }
private:
    std::vector<T> data_;
};

std::vector<int32_t>    vec1;
std::vector<int32_t>    vec2;

vec1 = mkvec<int32_t>() << 5 << 8 << 19 << 79;  
// vec1 = (5,8,19,79)
vec2 = mkvec<int32_t>() << 1 << 2 << 3 << vec1 << 10 << 11 << 12;  
// vec2 = (1,2,3,5,8,19,79,10,11,12)

7

Нижче методи можуть бути використані для ініціалізації вектора в c ++.

  1. int arr[] = {1, 3, 5, 6}; vector<int> v(arr, arr + sizeof(arr)/sizeof(arr[0]));

  2. vector<int>v; v.push_back(1); v.push_back(2); v.push_back(3); і так далі

  3. vector<int>v = {1, 3, 5, 7};

Третій дозволений лише на C ++ 11 і далі.


5

Тут є багато хороших відповідей, але оскільки я самостійно приїхав до себе, перш ніж прочитати це, я зрозумів, що все-таки кину свою тут ...

Ось метод, який я використовую для цього, який буде універсально працювати в компіляторах і платформах:

Створіть структуру або клас як контейнер для вашої колекції об’єктів. Визначте функцію перевантаження оператора для <<.

class MyObject;

struct MyObjectList
{
    std::list<MyObject> objects;
    MyObjectList& operator<<( const MyObject o )
    { 
        objects.push_back( o );
        return *this; 
    }
};

Ви можете створити функції, які приймають вашу структуру як параметр, наприклад:

someFunc( MyObjectList &objects );

Потім ви можете викликати цю функцію так:

someFunc( MyObjectList() << MyObject(1) <<  MyObject(2) <<  MyObject(3) );

Таким чином, ви можете створити та передати динамічно розмірну колекцію об'єктів функції в одну єдину чисту лінію!


4

Якщо ви хочете щось у тому ж загальному порядку, що і Boost :: присвоюйте, не створюючи залежності від Boost, наступне, принаймні, невиразно схоже:

template<class T>
class make_vector {
    std::vector<T> data;
public:
    make_vector(T const &val) { 
        data.push_back(val);
    }

    make_vector<T> &operator,(T const &t) {
        data.push_back(t);
        return *this;
    }

    operator std::vector<T>() { return data; }
};

template<class T> 
make_vector<T> makeVect(T const &t) { 
    return make_vector<T>(t);
}

Хоча я хочу, щоб синтаксис його використання був більш чистим, він все ще не особливо жахливий:

std::vector<int> x = (makeVect(1), 2, 3, 4);

4
typedef std::vector<int> arr;

arr a {10, 20, 30};       // This would be how you initialize while defining

Для компіляції використовуйте:

clang++ -std=c++11 -stdlib=libc++  <filename.cpp>

Питання C ++ 03 (не 11)
Майк П

1
Я думаю, що це не вказало 03, коли я відповів на це. Не пам’ятаю чудово, хоча. Однак це все ще корисна відповідь для того, хто шукає швидкого рішення.
shaveenk

4
// Before C++11
// I used following methods:

// 1.
int A[] = {10, 20, 30};                              // original array A

unsigned sizeOfA = sizeof(A)/sizeof(A[0]);           // calculate the number of elements

                                                     // declare vector vArrayA,
std::vector<int> vArrayA(sizeOfA);                   // make room for all
                                                     // array A integers
                                                     // and initialize them to 0 

for(unsigned i=0; i<sizeOfA; i++)
    vArrayA[i] = A[i];                               // initialize vector vArrayA


//2.
int B[] = {40, 50, 60, 70};                          // original array B

std::vector<int> vArrayB;                            // declare vector vArrayB
for (unsigned i=0; i<sizeof(B)/sizeof(B[0]); i++)
    vArrayB.push_back(B[i]);                         // initialize vArrayB

//3.
int C[] = {1, 2, 3, 4};                              // original array C

std::vector<int> vArrayC;                            // create an empty vector vArrayC
vArrayC.resize(sizeof(C)/sizeof(C[0]));              // enlarging the number of 
                                                     // contained elements
for (unsigned i=0; i<sizeof(C)/sizeof(C[0]); i++)
     vArrayC.at(i) = C[i];                           // initialize vArrayC


// A Note:
// Above methods will work well for complex arrays
// with structures as its elements.

4

Якщо масив:

int arr[] = {1, 2, 3};
int len = (sizeof(arr)/sizeof(arr[0])); // finding length of array
vector < int > v;
std:: v.assign(arr, arr+len); // assigning elements from array to vector 

4

Досить зручно створювати векторний inline без визначення змінної під час написання тесту, наприклад:

assert(MyFunction() == std::vector<int>{1, 3, 4}); // <- this.

3

Пов’язано, ви можете використовувати наступне, якщо ви хочете, щоб вектор був повністю готовий до швидкого оператора (наприклад, негайно переходите до іншої функції):

#define VECTOR(first,...) \
   ([](){ \
   static const decltype(first) arr[] = { first,__VA_ARGS__ }; \
   std::vector<decltype(first)> ret(arr, arr + sizeof(arr) / sizeof(*arr)); \
   return ret;})()

Приклад функції

template<typename T>
void test(std::vector<T>& values)
{
    for(T value : values)
        std::cout<<value<<std::endl;
}

Приклад використання

test(VECTOR(1.2f,2,3,4,5,6));

хоч будьте уважні до decltype, переконайтесь, що перше значення - це явно те, що ви хочете.


3

Існує різні способи жорсткого коду вектора, я поділюся кількома способами:

  1. Ініціалізація натисканням значень по черзі
// Create an empty vector 
    vector<int> vect;  

    vect.push_back(10); 
    vect.push_back(20); 
    vect.push_back(30); 
  1. Ініціалізація як масиви
vector<int> vect{ 10, 20, 30 };
  1. Ініціалізація з масиву
    int arr[] = { 10, 20, 30 }; 
    int n = sizeof(arr) / sizeof(arr[0]); 

    vector<int> vect(arr, arr + n); 
  1. Ініціалізація з іншого вектора
    vector<int> vect1{ 10, 20, 30 }; 

    vector<int> vect2(vect1.begin(), vect1.end()); 

2

"Як створити вектор STL та ініціалізувати його, як зазначено вище? Який найкращий спосіб зробити це з мінімальними зусиллями набору тексту?"

Найпростіший спосіб ініціалізації вектора при ініціалізації вбудованого масиву - це використання списку ініціалізаторів, який був введений в C ++ 11 .

// Initializing a vector that holds 2 elements of type int.
Initializing:
std::vector<int> ivec = {10, 20};


// The push_back function is more of a form of assignment with the exception of course
//that it doesn't obliterate the value of the object it's being called on.
Assigning
ivec.push_back(30);

ivec має розмір 3 елементів після того, як буде призначено призначення (мічений вислів).


У подібних рядках я намагаюся ініціалізувати карту, std :: map <int, bool> catinfo = {{1, false}}; Але тоді отримайте цю помилку помилки: у C ++ 98 'catinfo' має бути ініціалізовано конструктором, а не '{...}'
pdk

2

B. Stroustrup описує хороший спосіб ланцюгових операцій у 16.2.10 Selfreference на сторінці 464 у випуску програми C ++ 11. Ланг. де функція повертає посилання, тут модифіковане на вектор. Таким чином, ви можете подобати ланцюжок, v.pb(1).pb(2).pb(3);але може бути занадто багато роботи для таких невеликих вигод.

#include <iostream>
#include <vector>

template<typename T>
class chain
{
private:
    std::vector<T> _v;
public:
    chain& pb(T a) {
        _v.push_back(a);
        return *this;
    };
    std::vector<T> get() { return _v; };
};

using namespace std;

int main(int argc, char const *argv[])
{
    chain<int> v{};

    v.pb(1).pb(2).pb(3);

    for (auto& i : v.get()) {
        cout << i << endl;
    }

    return 0;
}

1
2
3


Бібліотека броненосця робить це для матриці ініціалізації , але використовує оператор << замість імені функції: arma.sourceforge.net/docs.html#element_initialisation
Agnel Курьян

0

Найпростіший, ергономічний спосіб (із C ++ 11 або новіших версій):

auto my_ints = {1,2,3};

0

Якщо ви хочете мати його у власному класі:

#include <initializer_list>
Vector<Type>::Vector(std::initializer_list<Type> init_list) : _size(init_list.size()),
_capacity(_size),
_data(new Type[_size])
{
    int idx = 0;
    for (auto it = init_list.begin(); it != init_list.end(); ++it)
        _data[idx++] = *it;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.