Ottenere un array secondario da un array esistente


335

Ho un array X di 10 elementi. Vorrei creare un nuovo array contenente tutti gli elementi di X che iniziano all'indice 3 e finiscono nell'indice 7. Certo che posso facilmente scrivere un ciclo che lo farà per me, ma vorrei mantenere il mio codice il più pulito possibile . Esiste un metodo in C # che può farlo per me?

Qualcosa di simile (pseudo codice):

Array NewArray = oldArray.createNewArrayFromRange(int BeginIndex , int EndIndex)

Array.Copynon si adatta alle mie esigenze . Ho bisogno che gli elementi nel nuovo array siano cloni. Array.copyè solo un memcpyequivalente C-Style , non è quello che sto cercando.



7
@Kirtan - quel "dup" vuole specificamente IEnumerable <T> - che è diverso e ha diverse soluzioni ottimali; IMO
Marc Gravell

Quindi, le due righe necessarie per dichiarare il nuovo array e chiamare .Copy () non sono "codice pulito"?
Ed S.

2
@Ed Swangren - non se hai bisogno di farlo nel mezzo di un'espressione incatenata, no ;-p
Marc Gravell

2
La risposta di ShaggyUk è probabilmente quella corretta: stackoverflow.com/questions/943635/...
Dykam

Risposte:


469

È possibile aggiungerlo come metodo di estensione:

public static T[] SubArray<T>(this T[] data, int index, int length)
{
    T[] result = new T[length];
    Array.Copy(data, index, result, 0, length);
    return result;
}
static void Main()
{
    int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int[] sub = data.SubArray(3, 4); // contains {3,4,5,6}
}

Aggiorna la clonazione (che non era ovvio nella domanda originale). Se vuoi davvero un clone profondo; qualcosa di simile a:

public static T[] SubArrayDeepClone<T>(this T[] data, int index, int length)
{
    T[] arrCopy = new T[length];
    Array.Copy(data, index, arrCopy, 0, length);
    using (MemoryStream ms = new MemoryStream())
    {
        var bf = new BinaryFormatter();
        bf.Serialize(ms, arrCopy);
        ms.Position = 0;
        return (T[])bf.Deserialize(ms);
    }
}

Tuttavia, ciò richiede che gli oggetti siano serializzabili ( [Serializable]o ISerializable). Si potrebbe facilmente sostituire qualsiasi altro serializzatore a seconda dei casi - XmlSerializer, DataContractSerializer, protobuf-net, etc.

Si noti che il clone profondo è complicato senza serializzazione; in particolare, ICloneableè difficile fidarsi nella maggior parte dei casi.


1
(ovviamente usare un indice finale piuttosto che una lunghezza è una semplice modifica; ho pubblicato "così com'è" perché è l'uso più "tipico")
Marc Gravell

1
Quindi ... duro; non è così .... probabilmente avresti bisogno di usare la serializzazione per ottenere qualcosa di simile
Marc Gravell

1
vedere la mia risposta per alcuni supplenti e un collegamento a diverse implementazioni. la parte di farlo in un sub array è davvero piuttosto banale, quello che vuoi davvero è il bit di clonazione e questa è una domanda complessa e piuttosto aperta che dipende interamente dalle tue aspettative su quale comportamento "corretto" dovrebbe essere .
ShuggyCoUk

2
Questo è carino. Ed è particolarmente utile sottolineare che ICloneable è inaffidabile, perché oh, lo è mai.
Marcus Griep,

1
Grazie per aver sottolineato i problemi con la clonazione profonda in C #. È davvero un peccato, poiché la copia profonda è un'operazione fondamentale .
Dimitri C.

317

Puoi usare Array.Copy(...) per copiare nel nuovo array dopo averlo creato, ma non credo che esista un metodo che crei il nuovo array e copi un intervallo di elementi.

Se stai utilizzando .NET 3.5 potrebbe usare LINQ:

var newArray = array.Skip(3).Take(5).ToArray();

ma sarà un po 'meno efficiente.

Vedi questa risposta a una domanda simile per le opzioni per situazioni più specifiche.


+1 Mi piace anche questa variazione. Jon, puoi espandere il motivo per cui questo è considerato meno efficiente?
Ian Roke,

@Jon: Per rispondere alla domanda, non sarebbe "Take (5)"? @Ian: l'approccio Array.Copy non coinvolge un enumeratore e molto probabilmente sarà una semplice copia ...
Marc Gravell

@Marc: Sì davvero. Troppa domanda scrematura :)
Jon Skeet,

11
@Ian: l'approccio LINQ introduce due livelli di riferimento indiretto (gli iteratori), deve saltare esplicitamente gli elementi e non sa in anticipo quanto sarà grande l'array finale. Prendi in considerazione l'idea di prendere la seconda metà di un array da due milioni di elementi: un semplice approccio "crea array di destinazione, copia" copierà semplicemente il blocco richiesto senza toccare gli altri elementi e in una volta sola. L'approccio LINQ attraverserà l'array fino a raggiungere il punto iniziale, quindi inizierà a prendere valori, costruendo un buffer (aumentando le dimensioni del buffer e copiandolo periodicamente). Molto meno efficiente.
Jon Skeet,

se 5 è EndIndexm, la domanda corretta è array.Skip (3) .Take (5-3 + 1) .ToArray (); vale a dire. array.Skip (StartIndex) .Take (EndIndex-StartIndex + 1) .ToArray ();
Klaus78,

74

Hai preso in considerazione l'utilizzo ArraySegment?

http://msdn.microsoft.com/en-us/library/1hsbd92d.aspx


1
Probabilmente fa quello che vuoi, ma non supporta la sintassi dell'array predefinita, né supporta IEnumerable, quindi non è particolarmente pulito.
Alex Black,

5
Questo ha bisogno di più voti. Nella mia esperienza, la copia di ArraySegment è anche leggermente più veloce (dopo tutto uso array per dispositivi critici per la velocità) ..
nawfal

5
@AlexBlack Sembra a partire da .NET 4.5 , implementa IEnumerable<T>e una varietà di altre interfacce utili.
pswg,

1
Come useresti ArraySegmentper rispondere alla domanda originale?
Craig McQueen,

2
@CraigMcQueen - Prova il seguente approccio a linea singola:IList<T> newArray = (IList<T>)new ArraySegment<T>(oldArray, beginIndex, endIndex);
skia.heliou

36

Vedo che vuoi fare la clonazione, non solo copiare i riferimenti. In questo caso è possibile utilizzare .Selectper proiettare i membri dell'array sui loro cloni. Ad esempio, se i tuoi elementi fossero implementati, IClonablepotresti fare qualcosa del genere:

var newArray = array.Skip(3).Take(5).Select(eachElement => eachElement.Clone()).ToArray();

Nota: questa soluzione richiede .NET Framework 3.5.


Questo è più elegante.
smwikipedia,

Questo e 'esattamente quello che stavo cercando. Questo funziona per qualsiasi IEnumerable. Posso ottenere un IEnumerable, IList, IArray, ecc ... con il minimo sforzo, in linea se ho bisogno di. Se non ho bisogno della copia profonda, rimuovo semplicemente il file Select. Cadere Skipo Takemi permette di controllare l'intervallo. In alternativa, posso mescolarlo con SkipWhilee / o TakeWhile.
Mike,

33

Il seguente codice lo fa in una riga:

// Source array
string[] Source = new string[] { "A", "B", "C", "D" };
// Extracting a slice into another array
string[] Slice = new List<string>(Source).GetRange(2, 2).ToArray();

Linea singola e non è necessario aggiungere Linq. È il mio modo preferito.
Dimitris,

Tuttavia non clona la fonte ... ma è comunque un buon approccio
IG Pascual,

1
Dovrebbe clonare l'origine perché ToArray: (1) crea un nuovo array e (2) esegue Array.Copy. Alla fine Source e Slice sono due oggetti separati. L'approccio è corretto, tuttavia, preferisco Array.Copy: riferimentiource.microsoft.com/#mscorlib/system/collections/…
Krauss

13

In C # 8 hanno introdotto un nuovo Rangee un Indextipo

int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
var slice = a[i1..i2]; // { 3, 4, 5 }

12
string[] arr = { "Parrot" , "Snake" ,"Rabbit" , "Dog" , "cat" };

arr = arr.ToList().GetRange(0, arr.Length -1).ToArray();

8

Basandosi sulla risposta di Marc ma aggiungendo il comportamento di clonazione desiderato

public static T[] CloneSubArray<T>(this T[] data, int index, int length)
    where T : ICloneable
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Clone();            
    return result;
}

E se implementare ICloneable è troppo simile a un duro lavoro, è una cosa riflessiva che utilizza la libreria Copyable di Håvard Stranden per fare il lavoro pesante richiesto.

using OX.Copyable;

public static T[] DeepCopySubArray<T>(
    this T[] data, int index, int length)
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Copy();            
    return result;
}

Si noti che l'implementazione OX.Copyable funziona con uno dei seguenti:

Affinché la copia automatica funzioni, tuttavia, una delle seguenti istruzioni deve contenere ad esempio:

  • Il suo tipo deve avere un costruttore senza parametri o
  • Deve essere un copiabile o
  • Deve avere un IInstanceProvider registrato per il suo tipo.

Quindi questo dovrebbe coprire quasi ogni situazione che hai. Se stai clonando oggetti in cui il grafico secondario contiene elementi come connessioni db o handle di file / stream, hai ovviamente dei problemi, ma è vero per qualsiasi copia profonda generalizzata.

Se vuoi usare un altro approccio di copia profonda invece questo articolo ne elenca diversi altri, quindi suggerirei di non provare a scrivere il tuo.


La prima è probabilmente la soluzione desiderata, in quanto chiede la clonazione. Si noti che con il metodo Copia, probabilmente non è nemmeno necessario verificare la presenza di null, poiché si tratta di un metodo di estensione, se il metodo stesso fa già quella cosa. Vale la pena provare.
Dykam,

Sì, ho notato il controllo null ma non volevo confondere l'OP nel caso in cui non avesse letto la fonte.
ShuggyCoUk, l'

2
Solo un sidenote: l'ultima versione di Copyable su GitHub non richiede che gli oggetti abbiano un costruttore senza parametri. :) Vedi github.com/havard/copyable
Håvard S

8

Puoi farlo abbastanza facilmente;

    object[] foo = new object[10];
    object[] bar = new object[7];   
    Array.Copy(foo, 3, bar, 0, 7);  

No, la barra sarà comunque nulla. Array.Copy non crea magicamente un nuovo array, soprattutto perché la barra non viene passata con ref o out.
Zr40,

2
oh ye hey, hai ragione, l'ho fatto in fretta, ma hey, forse quando la tua critica alla scrittura dovresti mettere la correzione, la critica costruttiva è molto più utile per tutti. quindi prima di array.copy fai una "barra = nuovo oggetto [7];"
RandomNickName42

4

Penso che il codice che stai cercando sia:

Array.Copy(oldArray, 0, newArray, BeginIndex, EndIndex - BeginIndex)


Penso di aver fatto un buon amico qui ... stessa risposta come te;) e ho votato parecchio !! hah !! Comunque, bei tempi bei tempi.
RandomNickName42

3

In alternativa alla copia dei dati è possibile creare un wrapper che consente di accedere a una parte dell'array originale come se fosse una copia della parte dell'array. Il vantaggio è che non si ottiene un'altra copia dei dati in memoria e lo svantaggio è un leggero sovraccarico quando si accede ai dati.

public class SubArray<T> : IEnumerable<T> {

   private T[] _original;
   private int _start;

   public SubArray(T[] original, int start, int len) {
      _original = original;
      _start = start;
      Length = len;
   }

   public T this[int index] {
      get {
         if (index < 0 || index >= Length) throw new IndexOutOfRangeException();
         return _original[_start + index];
      }
   }

   public int Length { get; private set; }

   public IEnumerator<T> GetEnumerator() {
      for (int i = 0; i < Length; i++) {
        yield return _original[_start + i];
      }
   }

   IEnumerator IEnumerable.GetEnumerator() {
      return GetEnumerator();
   }

}

Uso:

int[] original = { 1, 2, 3, 4, 5 };
SubArray<int> copy = new SubArray<int>(original, 2, 2);

Console.WriteLine(copy.Length); // shows: 2
Console.WriteLine(copy[0]); // shows: 3
foreach (int i in copy) Console.WriteLine(i); // shows 3 and 4

@Robert: No, non lo è. Prova invece a usare un ArraySegment e vedi che non puoi né accedere agli articoli per indice, né scorrere gli articoli.
Guffa,

2

Array.ConstrainedCopy funzionerà.

public static void ConstrainedCopy (
    Array sourceArray,
    int sourceIndex,
    Array destinationArray,
    int destinationIndex,
    int length
)

2
Che copia solo i dati; non creerà il nuovo array ecc .; e se l'array è nuovo, potremmo usare Array.Copy che è più efficiente (non sono necessari ulteriori controlli / rollback).
Marc Gravell

Esatto, ma la creazione di un nuovo array è solo una riga di codice e non è richiesto alcun nuovo metodo. Sono d'accordo che anche Array.Copy funzionerà.
Crauscher,

1

Non esiste un singolo metodo che farà quello che vuoi. Sarà necessario rendere disponibile un metodo clone per la classe nell'array. Quindi, se LINQ è un'opzione:

Foo[] newArray = oldArray.Skip(3).Take(5).Select(item => item.Clone()).ToArray();

class Foo
{
    public Foo Clone()
    {
        return (Foo)MemberwiseClone();
    }
}

1

Che ne dici di usare Array.ConstrainedCopy :

int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8};
int[] ArrayTwo = new int[5];
Array.ConstrainedCopy(ArrayOne, 3, ArrayTwo, 0, 7-3);

Di seguito è il mio post originale. Non funzionerà

È possibile utilizzare Array.CopyTo :

int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8};
int[] ArrayTwo = new int[5];
ArrayOne.CopyTo(ArrayTwo,3); //starts copy at index=3 until it reaches end of
                             //either array

1

Cosa ne pensi di questo:

public T[] CloneCopy(T[] array, int startIndex, int endIndex) where T : ICloneable
{
    T[] retArray = new T[endIndex - startIndex];
    for (int i = startIndex; i < endIndex; i++)
    {
        array[i - startIndex] = array[i].Clone();
    }
    return retArray;

}

È quindi necessario implementare l'interfaccia ICloneable su tutte le classi su cui è necessario utilizzarlo, ma dovrebbe farlo.


1

Non sono sicuro di quanto sia veramente profondo, ma:

MyArray.ToList<TSource>().GetRange(beginningIndex, endIndex).ToArray()

È un po 'sovraccarico, ma potrebbe ritagliare un metodo non necessario.


1

C # 8 ha fornito la funzione chiamata Range per ottenere l'indice di subarry dall'inizio alla fine. puoi usarlo in questo modo.

Index i1 = 3; // number 3 from beginning  
Index i2 = ^4; // number 4 from end  
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };  
var slice = a[i1..i2]; // { 3, 4, 5 }

Questa è una pazzesca pythonic s ** t. Lo adoro.
Ch3shire

Sì, è davvero una bella funzionalità
vivek nuna

0

Per quanto riguarda la clonazione, non credo che la serializzazione chiami i tuoi costruttori. Questo potrebbe spezzare gli invarianti di classe se stai facendo cose interessanti nei ctor.

Sembra che la scommessa più sicura siano i metodi di clone virtuale che chiamano i costruttori di copie.

protected MyDerivedClass(MyDerivedClass myClass) 
{
  ...
}

public override MyBaseClass Clone()
{
  return new MyDerivedClass(this);
}

Se la serializzazione chiama i costruttori dipende dal serializzatore specifico. Alcuni lo fanno, altri no. Ma quelli che in genere non offrono il supporto per la richiamata consentono di eseguire qualsiasi correzione richiesta.
Marc Gravell

Ciò evidenzia un altro punto di attrito della serializzazione: è necessario fornire costruttori predefiniti.
Hans Malherbe,

0

La clonazione di elementi in un array non è qualcosa che può essere fatto in modo universale. Vuoi una clonazione approfondita o una semplice copia di tutti i membri?

Andiamo per l'approccio del "miglior sforzo": clonazione di oggetti utilizzando l'interfaccia ICloneable o serializzazione binaria:

public static class ArrayExtensions
{
  public static T[] SubArray<T>(this T[] array, int index, int length)
  {
    T[] result = new T[length];

    for (int i=index;i<length+index && i<array.Length;i++)
    {
       if (array[i] is ICloneable)
          result[i-index] = (T) ((ICloneable)array[i]).Clone();
       else
          result[i-index] = (T) CloneObject(array[i]);
    }

    return result;
  }

  private static object CloneObject(object obj)
  {
    BinaryFormatter formatter = new BinaryFormatter();

    using (MemoryStream stream = new MemoryStream())
    {
      formatter.Serialize(stream, obj);

      stream.Seek(0,SeekOrigin.Begin);

      return formatter.Deserialize(stream);
    }
  }
}

Questa non è una soluzione perfetta, perché semplicemente non ce n'è nessuna che funzionerà per qualsiasi tipo di oggetto.


Non sarebbe qualcosa come il risultato [i-index] = (T) ...?
Donald Byrd,

si :) E non solo. Il limite del loop è errato. Lo aggiusterò. Grazie!
Philippe Leybaert,

0

Puoi prendere lezioni fatte da Microsoft:

internal class Set<TElement>
{
    private int[] _buckets;
    private Slot[] _slots;
    private int _count;
    private int _freeList;
    private readonly IEqualityComparer<TElement> _comparer;

    public Set()
        : this(null)
    {
    }

    public Set(IEqualityComparer<TElement> comparer)
    {
        if (comparer == null)
            comparer = EqualityComparer<TElement>.Default;
        _comparer = comparer;
        _buckets = new int[7];
        _slots = new Slot[7];
        _freeList = -1;
    }

    public bool Add(TElement value)
    {
        return !Find(value, true);
    }

    public bool Contains(TElement value)
    {
        return Find(value, false);
    }

    public bool Remove(TElement value)
    {
        var hashCode = InternalGetHashCode(value);
        var index1 = hashCode % _buckets.Length;
        var index2 = -1;
        for (var index3 = _buckets[index1] - 1; index3 >= 0; index3 = _slots[index3].Next)
        {
            if (_slots[index3].HashCode == hashCode && _comparer.Equals(_slots[index3].Value, value))
            {
                if (index2 < 0)
                    _buckets[index1] = _slots[index3].Next + 1;
                else
                    _slots[index2].Next = _slots[index3].Next;
                _slots[index3].HashCode = -1;
                _slots[index3].Value = default(TElement);
                _slots[index3].Next = _freeList;
                _freeList = index3;
                return true;
            }
            index2 = index3;
        }
        return false;
    }

    private bool Find(TElement value, bool add)
    {
        var hashCode = InternalGetHashCode(value);
        for (var index = _buckets[hashCode % _buckets.Length] - 1; index >= 0; index = _slots[index].Next)
        {
            if (_slots[index].HashCode == hashCode && _comparer.Equals(_slots[index].Value, value))
                return true;
        }
        if (add)
        {
            int index1;
            if (_freeList >= 0)
            {
                index1 = _freeList;
                _freeList = _slots[index1].Next;
            }
            else
            {
                if (_count == _slots.Length)
                    Resize();
                index1 = _count;
                ++_count;
            }
            int index2 = hashCode % _buckets.Length;
            _slots[index1].HashCode = hashCode;
            _slots[index1].Value = value;
            _slots[index1].Next = _buckets[index2] - 1;
            _buckets[index2] = index1 + 1;
        }
        return false;
    }

    private void Resize()
    {
        var length = checked(_count * 2 + 1);
        var numArray = new int[length];
        var slotArray = new Slot[length];
        Array.Copy(_slots, 0, slotArray, 0, _count);
        for (var index1 = 0; index1 < _count; ++index1)
        {
            int index2 = slotArray[index1].HashCode % length;
            slotArray[index1].Next = numArray[index2] - 1;
            numArray[index2] = index1 + 1;
        }
        _buckets = numArray;
        _slots = slotArray;
    }

    internal int InternalGetHashCode(TElement value)
    {
        if (value != null)
            return _comparer.GetHashCode(value) & int.MaxValue;
        return 0;
    }

    internal struct Slot
    {
        internal int HashCode;
        internal TElement Value;
        internal int Next;
    }
}

e poi

public static T[] GetSub<T>(this T[] first, T[] second)
    {
        var items = IntersectIteratorWithIndex(first, second);
        if (!items.Any()) return new T[] { };


        var index = items.First().Item2;
        var length = first.Count() - index;
        var subArray = new T[length];
        Array.Copy(first, index, subArray, 0, length);
        return subArray;
    }

    private static IEnumerable<Tuple<T, Int32>> IntersectIteratorWithIndex<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        var firstList = first.ToList();
        var set = new Set<T>();
        foreach (var i in second)
            set.Add(i);
        foreach (var i in firstList)
        {
            if (set.Remove(i))
                yield return new Tuple<T, Int32>(i, firstList.IndexOf(i));
        }
    }

0

Questo è il modo ottimale, ho scoperto, per fare questo:

private void GetSubArrayThroughArraySegment() {
  int[] array = { 10, 20, 30 };
  ArraySegment<int> segment = new ArraySegment<int>(array,  1, 2);
  Console.WriteLine("-- Array --");
  int[] original = segment.Array;
  foreach (int value in original)
  {
    Console.WriteLine(value);
  }
  Console.WriteLine("-- Offset --");
  Console.WriteLine(segment.Offset);
  Console.WriteLine("-- Count --");
  Console.WriteLine(segment.Count);

  Console.WriteLine("-- Range --");
  for (int i = segment.Offset; i <= segment.Count; i++)
  {
    Console.WriteLine(segment.Array[i]);
  }
}

Spero che sia d'aiuto!


0

utilizzare il metodo di estensione:

public static T[] Slice<T>(this T[] source, int start, int end)
    {
        // Handles negative ends.
        if (end < 0)
        {
            end = source.Length + end;
        }
        int len = end - start;

        // Return new array.
        T[] res = new T[len];
        for (int i = 0; i < len; i++)
        {
            res[i] = source[i + start];
        }
        return res;
    }

e puoi usarlo

var NewArray = OldArray.Slice(3,7);

0

Codice da System.Private.CoreLib.dll:

public static T[] GetSubArray<T>(T[] array, Range range)
{
    if (array == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
    }
    (int Offset, int Length) offsetAndLength = range.GetOffsetAndLength(array.Length);
    int item = offsetAndLength.Offset;
    int item2 = offsetAndLength.Length;
    if (default(T) != null || typeof(T[]) == array.GetType())
    {
        if (item2 == 0)
        {
            return Array.Empty<T>();
        }
        T[] array2 = new T[item2];
        Buffer.Memmove(ref Unsafe.As<byte, T>(ref array2.GetRawSzArrayData()), ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), item), (uint)item2);
        return array2;
    }
    T[] array3 = (T[])Array.CreateInstance(array.GetType().GetElementType(), item2);
    Array.Copy(array, item, array3, 0, item2);
    return array3;
}



0

Non soddisfa i requisiti di clonazione, ma sembra più semplice di molte risposte:

Array NewArray = new ArraySegment(oldArray,BeginIndex , int Count).ToArray();

-1
public   static   T[]   SubArray<T>(T[] data, int index, int length)
        {
            List<T> retVal = new List<T>();
            if (data == null || data.Length == 0)
                return retVal.ToArray();
            bool startRead = false;
            int count = 0;
            for (int i = 0; i < data.Length; i++)
            {
                if (i == index && !startRead)
                    startRead = true;
                if (startRead)
                {

                    retVal.Add(data[i]);
                    count++;

                    if (count == length)
                        break;
                }
            }
            return retVal.ToArray();
        }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.