Dividir una lista en N partes de aproximadamente la misma longitud


150

¿Cuál es la mejor manera de dividir una lista en partes más o menos iguales? Por ejemplo, si la lista tiene 7 elementos y se divide en 2 partes, queremos obtener 3 elementos en una parte, y la otra debe tener 4 elementos.

Busco algo así como even_split(L, n)que las interrupciones Lenn partes.

def chunks(L, n):
    """ Yield successive n-sized chunks from L.
    """
    for i in range(0, len(L), n):
        yield L[i:i+n]

El código anterior proporciona fragmentos de 3, en lugar de 3 fragmentos. Simplemente podría transponer (iterar sobre esto y tomar el primer elemento de cada columna, llamar a esa parte uno, luego tomar el segundo y ponerlo en la parte dos, etc.), pero eso destruye el orden de los elementos.

Respuestas:


64

Este código está roto debido a errores de redondeo. ¡¡¡No lo uses!!!

assert len(chunkIt([1,2,3], 10)) == 10  # fails

Aquí hay uno que podría funcionar:

def chunkIt(seq, num):
    avg = len(seq) / float(num)
    out = []
    last = 0.0

    while last < len(seq):
        out.append(seq[int(last):int(last + avg)])
        last += avg

    return out

Pruebas:

>>> chunkIt(range(10), 3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
>>> chunkIt(range(11), 3)
[[0, 1, 2], [3, 4, 5, 6], [7, 8, 9, 10]]
>>> chunkIt(range(12), 3)
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]

9
Su ejemplo no funcionará para >>> chunkIt(range(8), 6)=>[[0], [1], [2, 3], [4], [5], [6], [7]]
nopper

1
@nopper, agregué un "if num == 1:" condicional para manejar ese caso límite.
paulie4

24
Nuevos visitantes: por favor, no use ni vote este código , está roto. por ejemplo, chunkIt(range(10), 9)debería devolver 9 partes, pero no lo hace.
wim

3
Este hilo de comentarios es realmente confuso ya que la respuesta ha sido editada varias veces. ¿Esta es una buena respuesta? ¿No es una buena respuesta?
conchoecia

66
@conchoecia No es una buena respuesta, sigue desplazándote hacia abajo. Esto solo se editó una vez hasta ahora, y solo fue una edición trivial (la sangría de 2 espacios cambió a 4). Lamentablemente, el OP "user248237dfsf" no se ha visto en el sitio durante más de 3 años, por lo que hay pocas esperanzas de que se cambie la respuesta aceptada.
wim

183

Puede escribirlo simplemente como un generador de listas:

def split(a, n):
    k, m = divmod(len(a), n)
    return (a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n))

Ejemplo:

>>> list(split(range(11), 3))
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10]]

Inserte n = min(n, len(a)) # don't create empty bucketsen la línea 1 para evitar crear cubos vacíos en escenarios como list(split(range(X, Y)))dondeX < Y
abanana

Como no puedo editar mi comentario, debo agregar que mi enmienda anterior podría generar una división por cero error si la lista está vacía, por lo que debe controlarse externamente o agregarse a la solución.
abanana

44
Fuera de N respuesta en SO, este es el único que ha pasado todas mis pruebas. gj!
avishayp

2
stackoverflow.com/a/37414115/210971 utiliza el mismo método, pero también funciona para la lista vacía y el contador 0 dividido.
LookAheadAtYourTypes

¡Hermoso! Además, se puede hacer que n funcione como batch_size intercambiando k y n en la declaración de devolución :)
haraprasadj

162

Esta es la razón de ser de numpy.array_split*:

>>> import numpy as np
>>> print(*np.array_split(range(10), 3))
[0 1 2 3] [4 5 6] [7 8 9]
>>> print(*np.array_split(range(10), 4))
[0 1 2] [3 4 5] [6 7] [8 9]
>>> print(*np.array_split(range(10), 5))
[0 1] [2 3] [4 5] [6 7] [8 9]

* crédito a Zero Piraeus en la habitación 6


1
¿Cuál es la *de printpara?
yuqli

2
Hola @yuqli, Convierte una lista de algo en argumentos individuales en una función. intente print(L)e `print (* L). Consulte también stackoverflow.com/a/36908/2184122 o busque "uso de asterisco en Python".
Robert Lugg

121

Mientras no quieras nada tonto como trozos continuos:

>>> def chunkify(lst,n):
...     return [lst[i::n] for i in xrange(n)]
... 
>>> chunkify(range(13), 3)
[[0, 3, 6, 9, 12], [1, 4, 7, 10], [2, 5, 8, 11]]

14
No diría que los trozos continuos son tontos. Quizás desee mantener los fragmentos ordenados (es decir, fragmento [0] <fragmento [1]), por ejemplo.
tixxit

1
Estaba bromeando. Pero si realmente no te importaba, de esta manera con la comprensión de la lista es agradable y conciso.
trabajo

3
Este es un subíndice con un paso de n
smci el

8
enviar esta salida a 'zip' le da su lista ordenada: zip(*chunkify(range(13), 3))resultados en[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)]
gens

2
Esta solución funciona bien, hasta que necesite el orden de la lista, permanezca igual.
s7anley

18

Cambiar el código para producir nfragmentos en lugar de fragmentos de n:

def chunks(l, n):
    """ Yield n successive chunks from l.
    """
    newn = int(len(l) / n)
    for i in xrange(0, n-1):
        yield l[i*newn:i*newn+newn]
    yield l[n*newn-newn:]

l = range(56)
three_chunks = chunks (l, 3)
print three_chunks.next()
print three_chunks.next()
print three_chunks.next()

lo que da:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
[18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
[36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]

Esto asignará los elementos adicionales al grupo final que no es perfecto pero está dentro de su especificación de "aproximadamente N partes iguales" :-) Con eso, quiero decir que 56 elementos serían mejores como (19,19,18) mientras que esto da (18,18,20).

Puede obtener la salida más equilibrada con el siguiente código:

#!/usr/bin/python
def chunks(l, n):
    """ Yield n successive chunks from l.
    """
    newn = int(1.0 * len(l) / n + 0.5)
    for i in xrange(0, n-1):
        yield l[i*newn:i*newn+newn]
    yield l[n*newn-newn:]

l = range(56)
three_chunks = chunks (l, 3)
print three_chunks.next()
print three_chunks.next()
print three_chunks.next()

que salidas:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
[19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37]
[38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]

Esto me da un resultado extraño. para p en trozos (rango (54), 3): print len ​​(p) devuelve 18, 18, 51 ...

Corregido, eso, era el rendimiento final.
paxdiablo

ver también una solución en link
Jakob Kroeker

Esta es la respuesta más útil para consideraciones prácticas. ¡Gracias!
mVChr

Cuando uso esto, for x in chunks(mylist,num): print xobtengo los fragmentos deseados, pero entre ellos obtengo una lista vacía. ¿Alguna idea de por qué? Es decir, obtengo muchos [], uno después de cada porción.
synaptik

12

Si divide los nelementos en kpartes aproximadas , puede hacer que las n % kpartes sean 1 elemento más grande que las otras partes para distribuir los elementos adicionales.

El siguiente código le dará la longitud de los fragmentos:

[(n // k) + (1 if i < (n % k) else 0) for i in range(k)]

Ejemplo: n=11, k=3resultados en[4, 4, 3]

Luego puede calcular fácilmente los índices de inicio para los fragmentos:

[i * (n // k) + min(i, n % k) for i in range(k)]

Ejemplo: n=11, k=3resultados en[0, 4, 8]

Usando el i+1fragmento th como límite, obtenemos que el ifragmento th de la lista lcon len nes

l[i * (n // k) + min(i, n % k):(i+1) * (n // k) + min(i+1, n % k)]

Como paso final, cree una lista de todos los fragmentos utilizando la comprensión de la lista:

[l[i * (n // k) + min(i, n % k):(i+1) * (n // k) + min(i+1, n % k)] for i in range(k)]

Ejemplo: n=11, k=3, l=range(n)resultados en[range(0, 4), range(4, 8), range(8, 11)]


6

Esto hará la división por una sola expresión:

>>> myList = range(18)
>>> parts = 5
>>> [myList[(i*len(myList))//parts:((i+1)*len(myList))//parts] for i in range(parts)]
[[0, 1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13], [14, 15, 16, 17]]

La lista en este ejemplo tiene el tamaño 18 y se divide en 5 partes. El tamaño de las partes difiere en no más de un elemento.



4

Aquí hay uno que agrega Nonepara hacer que las listas tengan la misma longitud

>>> from itertools import izip_longest
>>> def chunks(l, n):
    """ Yield n successive chunks from l. Pads extra spaces with None
    """
    return list(zip(*izip_longest(*[iter(l)]*n)))

>>> l=range(54)

>>> chunks(l,3)
[(0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51), (1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52), (2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53)]

>>> chunks(l,4)
[(0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52), (1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53), (2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, None), (3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, None)]

>>> chunks(l,5)
[(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50), (1, 6, 11, 16, 21, 26, 31, 36, 41, 46, 51), (2, 7, 12, 17, 22, 27, 32, 37, 42, 47, 52), (3, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53), (4, 9, 14, 19, 24, 29, 34, 39, 44, 49, None)]

4

Aquí está mi solución:

def chunks(l, amount):
    if amount < 1:
        raise ValueError('amount must be positive integer')
    chunk_len = len(l) // amount
    leap_parts = len(l) % amount
    remainder = amount // 2  # make it symmetrical
    i = 0
    while i < len(l):
        remainder += leap_parts
        end_index = i + chunk_len
        if remainder >= amount:
            remainder -= amount
            end_index += 1
        yield l[i:end_index]
        i = end_index

Produce

    >>> list(chunks([1, 2, 3, 4, 5, 6, 7], 3))
    [[1, 2], [3, 4, 5], [6, 7]]

4

Aquí hay un generador que puede manejar cualquier número positivo (entero) de fragmentos. Si el número de fragmentos es mayor que la longitud de la lista de entrada, algunos fragmentos estarán vacíos. Este algoritmo alterna entre fragmentos cortos y largos en lugar de segregarlos.

También he incluido un código para probar la ragged_chunksfunción.

''' Split a list into "ragged" chunks

    The size of each chunk is either the floor or ceiling of len(seq) / chunks

    chunks can be > len(seq), in which case there will be empty chunks

    Written by PM 2Ring 2017.03.30
'''

def ragged_chunks(seq, chunks):
    size = len(seq)
    start = 0
    for i in range(1, chunks + 1):
        stop = i * size // chunks
        yield seq[start:stop]
        start = stop

# test

def test_ragged_chunks(maxsize):
    for size in range(0, maxsize):
        seq = list(range(size))
        for chunks in range(1, size + 1):
            minwidth = size // chunks
            #ceiling division
            maxwidth = -(-size // chunks)
            a = list(ragged_chunks(seq, chunks))
            sizes = [len(u) for u in a]
            deltas = all(minwidth <= u <= maxwidth for u in sizes)
            assert all((sum(a, []) == seq, sum(sizes) == size, deltas))
    return True

if test_ragged_chunks(100):
    print('ok')

Podemos hacer esto un poco más eficiente exportando la multiplicación a la rangellamada, pero creo que la versión anterior es más legible (y SECA).

def ragged_chunks(seq, chunks):
    size = len(seq)
    start = 0
    for i in range(size, size * chunks + 1, size):
        stop = i // chunks
        yield seq[start:stop]
        start = stop

3

Echa un vistazo a numpy.split :

>>> a = numpy.array([1,2,3,4])
>>> numpy.split(a, 2)
[array([1, 2]), array([3, 4])]

55
Y numpy.array_split () es aún más adecuado porque se divide más o menos.
Yariv

11
Esto no funciona si el tamaño de la matriz no es divisible por el número de divisiones.
Dan

1
Esta es una respuesta incorrecta, su solución devuelve una lista de ndarrays, no una lista de listas
Chłop Z Lasu

3

Implementación usando el método numpy.linspace.

Simplemente especifique el número de partes en las que desea dividir la matriz. Las divisiones serán de un tamaño casi igual.

Ejemplo:

import numpy as np   
a=np.arange(10)
print "Input array:",a 
parts=3
i=np.linspace(np.min(a),np.max(a)+1,parts+1)
i=np.array(i,dtype='uint16') # Indices should be floats
split_arr=[]
for ind in range(i.size-1):
    split_arr.append(a[i[ind]:i[ind+1]]
print "Array split in to %d parts : "%(parts),split_arr

Da :

Input array: [0 1 2 3 4 5 6 7 8 9]
Array split in to 3 parts :  [array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8, 9])]

3

Mi solución, fácil de entender.

def split_list(lst, n):
    splitted = []
    for i in reversed(range(1, n + 1)):
        split_point = len(lst)//i
        splitted.append(lst[:split_point])
        lst = lst[split_point:]
    return splitted

Y la frase más corta en esta página (escrita por mi chica)

def split(l, n):
    return [l[int(i*len(l)/n):int((i+1)*len(l)/n-1)] for i in range(n)]

FYI: Tu one-liner está roto, produce resultados incorrectos. El otro funciona muy bien.
Paulo Freitas

2

Usando la comprensión de la lista:

def divide_list_to_chunks(list_, n):
    return [list_[start::n] for start in range(n)]

Esto no aborda el problema de hacer que todos los trozos sean iguales.
SuperBiasedMan

0

Otra forma sería algo como esto, la idea aquí es usar el mero, pero deshacerse de él None. En este caso tendremos todas las 'partes_pequeñas' formadas a partir de elementos en la primera parte de la lista, y 'partes_más grandes' de la parte posterior de la lista. La longitud de las 'partes más grandes' es len (small_parts) + 1. Necesitamos considerar x como dos sub-partes diferentes.

from itertools import izip_longest

import numpy as np

def grouper(n, iterable, fillvalue=None): # This is grouper from itertools
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

def another_chunk(x,num):
    extra_ele = len(x)%num #gives number of parts that will have an extra element 
    small_part = int(np.floor(len(x)/num)) #gives number of elements in a small part

    new_x = list(grouper(small_part,x[:small_part*(num-extra_ele)]))
    new_x.extend(list(grouper(small_part+1,x[small_part*(num-extra_ele):])))

    return new_x

La forma en que lo configuré devuelve una lista de tuplas:

>>> x = range(14)
>>> another_chunk(x,3)
[(0, 1, 2, 3), (4, 5, 6, 7, 8), (9, 10, 11, 12, 13)]
>>> another_chunk(x,4)
[(0, 1, 2), (3, 4, 5), (6, 7, 8, 9), (10, 11, 12, 13)]
>>> another_chunk(x,5)
[(0, 1), (2, 3, 4), (5, 6, 7), (8, 9, 10), (11, 12, 13)]
>>> 

0

Aquí hay otra variante que distribuye los elementos "restantes" de manera uniforme entre todos los fragmentos, uno a la vez hasta que no quede ninguno. En esta implementación, los fragmentos más grandes se producen al comienzo del proceso.

def chunks(l, k):
  """ Yield k successive chunks from l."""
  if k < 1:
    yield []
    raise StopIteration
  n = len(l)
  avg = n/k
  remainders = n % k
  start, end = 0, avg
  while start < n:
    if remainders > 0:
      end = end + 1
      remainders = remainders - 1
    yield l[start:end]
    start, end = end, end+avg

Por ejemplo, genere 4 fragmentos de una lista de 14 elementos:

>>> list(chunks(range(14), 4))
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10], [11, 12, 13]]
>>> map(len, list(chunks(range(14), 4)))
[4, 4, 3, 3]

0

Lo mismo que la respuesta del trabajo , pero tiene en cuenta listas con un tamaño más pequeño que el número de fragmentos.

def chunkify(lst,n):
    [ lst[i::n] for i in xrange(n if n < len(lst) else len(lst)) ]

si n (número de fragmentos) es 7 y lst (la lista para dividir) es [1, 2, 3] los fragmentos son [[0], [1], [2]] en lugar de [[0], [1 ], [2], [], [], [], []]


0

También puedes usar:

split=lambda x,n: x if not x else [x[:n]]+[split([] if not -(len(x)-n) else x[-(len(x)-n):],n)][0]

split([1,2,3,4,5,6,7,8,9],2)

[[1, 2], [3, 4], [5, 6], [7, 8], [9]]

0
def evenly(l, n):
    len_ = len(l)
    split_size = len_ // n
    split_size = n if not split_size else split_size
    offsets = [i for i in range(0, len_, split_size)]
    return [l[offset:offset + split_size] for offset in offsets]

Ejemplo:

l = [a for a in range(97)] Debe constar de 10 partes, cada una con 9 elementos, excepto la última.

Salida:

[[0, 1, 2, 3, 4, 5, 6, 7, 8],
 [9, 10, 11, 12, 13, 14, 15, 16, 17],
 [18, 19, 20, 21, 22, 23, 24, 25, 26],
 [27, 28, 29, 30, 31, 32, 33, 34, 35],
 [36, 37, 38, 39, 40, 41, 42, 43, 44],
 [45, 46, 47, 48, 49, 50, 51, 52, 53],
 [54, 55, 56, 57, 58, 59, 60, 61, 62],
 [63, 64, 65, 66, 67, 68, 69, 70, 71],
 [72, 73, 74, 75, 76, 77, 78, 79, 80],
 [81, 82, 83, 84, 85, 86, 87, 88, 89],
 [90, 91, 92, 93, 94, 95, 96]]

0

Supongamos que desea dividir una lista [1, 2, 3, 4, 5, 6, 7, 8] en 3 listas de elementos

como [[1,2,3], [4, 5, 6], [7, 8]] , donde si los últimos elementos restantes son inferiores a 3, se agrupan.

my_list = [1, 2, 3, 4, 5, 6, 7, 8]
my_list2 = [my_list[i:i+3] for i in range(0, len(my_list), 3)]
print(my_list2)

Salida: [[1,2,3], [4, 5, 6], [7, 8]]

Donde la longitud de una parte es 3. Reemplace 3 con su propio tamaño de trozo.


0

1>

import numpy as np

data # your array

total_length = len(data)
separate = 10
sub_array_size = total_length // separate
safe_separate = sub_array_size * separate

splited_lists = np.split(np.array(data[:safe_separate]), separate)
splited_lists[separate - 1] = np.concatenate(splited_lists[separate - 1], 
np.array(data[safe_separate:total_length]))

splited_lists # your output

2>

splited_lists = np.array_split(np.array(data), separate)

0
def chunk_array(array : List, n: int) -> List[List]:
    chunk_size = len(array) // n 
    chunks = []
    i = 0
    while i < len(array):
        # if less than chunk_size left add the remainder to last element
        if len(array) - (i + chunk_size + 1) < 0:
            chunks[-1].append(*array[i:i + chunk_size])
            break
        else:
            chunks.append(array[i:i + chunk_size])
            i += chunk_size
    return chunks

Aquí está mi versión (inspirada en la de Max)


-1

Redondear el espacio de línea y usarlo como índice es una solución más fácil de lo que propone amit12690.

function chunks=chunkit(array,num)

index = round(linspace(0,size(array,2),num+1));

chunks = cell(1,num);

for x = 1:num
chunks{x} = array(:,index(x)+1:index(x+1));
end
end

-1
#!/usr/bin/python


first_names = ['Steve', 'Jane', 'Sara', 'Mary','Jack','Bob', 'Bily', 'Boni', 'Chris','Sori', 'Will', 'Won','Li']

def chunks(l, n):
for i in range(0, len(l), n):
    # Create an index range for l of n items:
    yield l[i:i+n]

result = list(chunks(first_names, 5))
print result

Elegido de este enlace , y esto fue lo que me ayudó. Tenía una lista predefinida.


-1

Digamos que quiere dividir en 5 partes:

p1, p2, p3, p4, p5 = np.split(df, 5)

44
Esto no proporciona una respuesta a la pregunta, por ejemplo, ¿cómo la escribiría si no sabe de antemano que desea dividirla en cinco partes? Además, está (supongo) asumiendo un marco de datos numpy y tal vez un pandas. El OP pregunta por una lista genérica.
NickD

-1

He escrito código en este caso yo mismo:

def chunk_ports(port_start, port_end, portions):
    if port_end < port_start:
        return None

    total = port_end - port_start + 1

    fractions = int(math.floor(float(total) / portions))

    results = []

    # No enough to chuck.
    if fractions < 1:
        return None

    # Reverse, so any additional items would be in the first range.
    _e = port_end
    for i in range(portions, 0, -1):
        print "i", i

        if i == 1:
            _s = port_start
        else:
            _s = _e - fractions + 1

        results.append((_s, _e))

        _e = _s - 1

    results.reverse()

    return results

divide_ports (1, 10, 9) devolvería

[(1, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10)]

-1

este código funciona para mí (compatible con Python3):

def chunkify(tab, num):
    return [tab[i*num: i*num+num] for i in range(len(tab)//num+(1 if len(tab)%num else 0))]

ejemplo (para el tipo bytearray , pero también funciona para las listas ):

b = bytearray(b'\x01\x02\x03\x04\x05\x06\x07\x08')
>>> chunkify(b,3)
[bytearray(b'\x01\x02\x03'), bytearray(b'\x04\x05\x06'), bytearray(b'\x07\x08')]
>>> chunkify(b,4)
[bytearray(b'\x01\x02\x03\x04'), bytearray(b'\x05\x06\x07\x08')]

-1

Este proporciona trozos de longitud <= n,> = 0

def

 chunkify(lst, n):
    num_chunks = int(math.ceil(len(lst) / float(n))) if n < len(lst) else 1
    return [lst[n*i:n*(i+1)] for i in range(num_chunks)]

por ejemplo

>>> chunkify(range(11), 3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
>>> chunkify(range(11), 8)
[[0, 1, 2, 3, 4, 5, 6, 7], [8, 9, 10]]

-1

Intenté la mayoría de las soluciones, pero no funcionaron para mi caso, así que creo una nueva función que funciona para la mayoría de los casos y para cualquier tipo de matriz:

import math

def chunkIt(seq, num):
    seqLen = len(seq)
    total_chunks = math.ceil(seqLen / num)
    items_per_chunk = num
    out = []
    last = 0

    while last < seqLen:
        out.append(seq[last:(last + items_per_chunk)])
        last += items_per_chunk

    return out
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.