Erstellen Sie eine Zeichenfolge mit n Zeichen


144

Gibt es in Java eine Möglichkeit, eine Zeichenfolge mit einer bestimmten Nummer eines bestimmten Zeichens zu erstellen? In meinem Fall müsste ich eine Zeichenfolge mit 10 Leerzeichen erstellen. Mein aktueller Code lautet:

StringBuffer outputBuffer = new StringBuffer(length);
for (int i = 0; i < length; i++){
   outputBuffer.append(" ");
}
return outputBuffer.toString();

Gibt es einen besseren Weg, um das Gleiche zu erreichen? Insbesondere möchte ich etwas, das schnell ist (in Bezug auf die Ausführung).


1
Wenn Sie häufig etwas tun, schreiben Sie einfach eine Funktion: String CharacterRepeat (Char c, int Length) {...}, die das tut, was Sie dort für jedes Zeichen und jede Länge tun. Dann rufen Sie es einfach an, wenn Sie es brauchen.
Austin Fitzpatrick

12
Sie möchten StringBuilder anstelle von StringBuffer verwenden

Fügen Sie am Anfang die Größe des Puffers hinzu, ist einfach zu berechnen und erleichtert die Dinge im Speichermanagement!. StringBuilder outputBuffer = neuer StringBuilder (repeat * base.length ());
Victor


Wenn Sie ein einzelnes Leerzeichen anhängen möchten, verwenden Sie append(' ')stattdessen ... es erfordert etwas weniger Rechenaufwand ...
Erk

Antworten:


36

Die for-Schleife wird vom Compiler optimiert. In solchen Fällen wie Ihrem müssen Sie sich nicht selbst um die Optimierung kümmern. Vertrauen Sie dem Compiler.

Übrigens, wenn es eine Möglichkeit gibt, eine Zeichenfolge mit n Leerzeichen zu erstellen, wird sie genauso codiert, wie Sie es gerade getan haben.


1
Auch wenn dies nicht optimiert wurde - wie oft wird dies in Ihrem Programm erstellt - wenn häufig in einer statischen Variablen oder einem anderen Cache
gespeichert

Übrigens durchläuft Array.fill () nur das Array. / Ich brauche definitiv mehr Punkte, um andere Beiträge zu kommentieren :)
Kalkin

@Mark Length ist variabel, daher erscheint es albern, es zu speichern. Was würde ich tun, eine haben static Dictionary<int, String>?
C. Ross

1
Speichern Sie die längste Zeichenfolge, die Sie jemals haben möchten, und verwenden Sie dann " ".substring(0, 10);
Folgendes

169

Wahrscheinlich der kürzeste Code, der Stringausschließlich die API verwendet:

String space10 = new String(new char[10]).replace('\0', ' ');

System.out.println("[" + space10 + "]");
// prints "[          ]"

Als Methode, ohne direkt zu instanziieren char:

import java.nio.CharBuffer;

/**
 * Creates a string of spaces that is 'spaces' spaces long.
 *
 * @param spaces The number of spaces to add to the string.
 */
public String spaces( int spaces ) {
  return CharBuffer.allocate( spaces ).toString().replace( '\0', ' ' );
}

Rufen Sie auf mit:

System.out.printf( "[%s]%n", spaces( 10 ) );

4
Obwohl ich den Einzeiler mag, denke ich, dass die Lösung von FrustratedWithFormsDes die bessere ist, wenn es um die Ausführung geht, da sie es vermeidet, jeden auf \ 0 zu prüfen und einfach den Platz zuzuweisen.
Bouncner

1
Getestet ist dies immer schneller als die Schleife, ziemlich konstant 50000-100000 Nanosekunden schneller, was ziemlich bedeutsam sein könnte (0,1 Millisekunden). Die Schleife wird schneller, wenn die Anzahl der Iterationen zunimmt, obwohl die Gesamtzeit immer noch hoch ist. Dies gilt auch für die Erstellung unterschiedlicher Größen von Leerzeichenfolgen.
kmecpp

Ich habe es in meiner Antwort hier verwendet. stackoverflow.com/questions/852665/…
maytham-ɯɐɥʇʎɐɯ

66

Hmm jetzt, wo ich darüber nachdenke, vielleicht Arrays.fill:

char[] charArray = new char[length];
Arrays.fill(charArray, ' ');
String str = new String(charArray);

Natürlich gehe ich davon aus, dass die fillMethode dasselbe tut wie Ihr Code, daher wird sie wahrscheinlich ungefähr die gleiche Leistung erbringen, aber zumindest sind dies weniger Zeilen.


2
Nach dem Überprüfen der Quelle scheint dies tatsächlich genau das zu tun, was das OP gepostet hat: Zeile 806 von docjar.com/html/api/java/util/Arrays.java.html
Pops

2
@ Lord Torgamus Wurde die Frage des OP bearbeitet? Weil er in der Version, die ich sehe, eine Schleife in StringBuffer.append () durchführt und die Version von Frustrated eine Füllung ausführt (die natürlich Schleifen durchführt, um dem Array Zeichenzuweisungen zu geben). Überhaupt nicht dasselbe.
CPerkins

@ CPerkins, fair genug, ich war nicht klar. Mein Punkt ist, dass beide eine zeichenweise Einfügung innerhalb einer for-Schleife durchführen.
Pops

1
@ Lord Torgamus - Einverstanden. Schade, dass der JVM nicht einfach ein Memset machen kann. Preis zahlen wir für breite Zeichen.
CPerkins

2
@Pops Es ist erwähnenswert, dass die Arrays.fill()Funktion , wenn sie konsistent für diese Aufgabe in Ihrem Code und (Standard- und externem) Bibliothekscode verwendet wird, ein guter Kandidat für die Hotspot-Kompilierung ist und eine bessere Chance hat, Ihren CPU-Cache zu finden. Zukünftige JVMs können es als intrinsische Funktion zuweisen . Das Rollen Ihrer eigenen schneidet Sie von all dieser Leistungsgüte ab ...
SusanW

65

Ich empfehle dringend, die Schleife nicht von Hand zu schreiben. Das werden Sie im Laufe Ihrer Programmierkarriere immer wieder tun. Leute, die Ihren Code lesen - das schließt Sie ein - müssen immer Zeit investieren, auch wenn es nur einige Sekunden sind, um die Bedeutung der Schleife zu verstehen.

Verwenden Sie stattdessen eine der verfügbaren Bibliotheken wieder, die Code bereitstellen, der genau das tut, wie StringUtils.repeatin Apache Commons Lang :

StringUtils.repeat(' ', length);

Auf diese Weise müssen Sie sich auch nicht um die Leistung kümmern, sodass alle wichtigen Details StringBuilder, Compiler-Optimierungen usw. verborgen bleiben. Wenn sich die Funktion als langsam herausstellen würde, wäre dies ein Fehler in der Bibliothek.

Mit Java 11 wird es noch einfacher:

" ".repeat(length);

5
Wenn StringUtils nicht bereits im Projekt verwendet wird, kann das Hinzufügen einer weiteren Abhängigkeit für eine so einfache Aufgabe ein Overkill sein.
Erich Kitzmüller

1
Wenn es sich als langsam herausstellte, können Sie auch Ihren eigenen Fix für die Funktion einreichen.
wird

26

In Java 8 können Sie Folgendes verwenden String.join:

String.join("", Collections.nCopies(n, s));

13

Wenn Sie nur Leerzeichen möchten, wie wäre es dann mit:

String spaces = (n==0)?"":String.format("%"+n+"s", "");

was zu abs (n) Leerzeichen führt;


6
Wie ist das für die Geschwindigkeit? Ich bin unter dem Eindruck Format ist relativ langsam. Es muss die Zeichenfolge analysieren, bevor sie schließlich erstellt werden kann.
C. Ross

11

seit Java 11 :

" ".repeat(10);

seit Java 8 :

generate(() -> " ").limit(10).collect(joining());

wo:

import static java.util.stream.Collectors.joining;
import static java.util.stream.Stream.generate;

9

Seit Java 11 können Sie einfach verwenden String.repeat(count), um Ihr Problem zu lösen.

Gibt eine Zeichenfolge zurück, deren Wert die wiederholte Verkettung dieser Zeichenfolge countist.

Wenn diese Zeichenfolge leer oder countNull ist, wird die leere Zeichenfolge zurückgegeben.

Anstelle einer Schleife würde Ihr Code also einfach so aussehen:

" ".repeat(length);

8

Ich denke, dies ist der weniger mögliche Code, der die Guava Joiner-Klasse verwendet:

Joiner .on (""). Join ( Collections.nCopies (10, ""));


Die kürzeste Codezeile, aber das Hinzufügen einer großen Bibliothek nur für diese einfache Aufgabe, macht keinen Sinn. Ich kann selbst eine kleinere JAR mit einer einzigen Methode erstellen, pubic String n(int n) { ... }die noch "weniger" Code zulässt. n(10)Aber auch dies macht keinen Sinn.
Isapir

@isapir Dies ist eine großartige Antwort für Leute, die bereits Guava
nasch

1
@nasch Guave war nicht Teil der Frage. Aber im Jahr 2018 gibt es wirklich keinen Grund, Guavas Joiner zu verwenden, wenn Sie String.join () verwenden können, das in Java 8 hinzugefügt wurde. Siehe stackoverflow.com/a/43011939/968244
isapir

@isapir Nein, Guave war eine Antwort. Die Antworten auf StackOverflow haben ein breiteres Publikum als nur die Person, die die Frage stellt. Es ist also in Ordnung, wenn einige Antworten für die fragende Person nicht die besten sind. Und der Kommentar zu String.join () ist großartig, obwohl ich nicht sicher bin, ob er auf allen Versionen von Android verfügbar ist, bei denen Java eine herausragende Rolle spielt.
Nasch

7

Sie können die Standardfunktion String.formatzum Generieren von N Leerzeichen verwenden. Beispielsweise:

String.format("%5c", ' ');

Erstellt eine Zeichenfolge mit 5 Leerzeichen.

oder

int count = 15;
String fifteenSpacebars = String.format("%" + count + "c", ' ');

Erstellt eine Zeichenfolge mit 15 Leertasten.

Wenn Sie möchten, dass sich ein anderes Symbol wiederholt, müssen Sie Leerzeichen durch das gewünschte Symbol ersetzen:

int count = 7;
char mySymbol = '#';
System.out.println(String.format("%" + count + "c", ' ').replaceAll("\\ ", "\\" + mySymbol));

Ausgabe:

#######

6

Mein Beitrag basiert auf dem Algorithmus zur schnellen Potenzierung.

/**
 * Repeats the given {@link String} n times.
 * 
 * @param str
 *            the {@link String} to repeat.
 * @param n
 *            the repetition count.
 * @throws IllegalArgumentException
 *             when the given repetition count is smaller than zero.
 * @return the given {@link String} repeated n times.
 */
public static String repeat(String str, int n) {
    if (n < 0)
        throw new IllegalArgumentException(
                "the given repetition count is smaller than zero!");
    else if (n == 0)
        return "";
    else if (n == 1)
        return str;
    else if (n % 2 == 0) {
        String s = repeat(str, n / 2);
        return s.concat(s);
    } else
        return str.concat(repeat(str, n - 1));
}

Ich habe den Algorithmus gegen zwei andere Ansätze getestet:

  • Regelmäßige for-Schleife String.concat()zum Verketten von Zeichenfolgen
  • Regelmäßige for-Schleife mit a StringBuilder

Testcode (Verkettung mit einer for-Schleife String.concat(), die für große zu langsam wird n, daher habe ich sie nach der 5. Iteration weggelassen).

/**
 * Test the string concatenation operation.
 * 
 * @param args
 */
public static void main(String[] args) {
    long startTime;
    String str = " ";

    int n = 1;
    for (int j = 0; j < 9; ++j) {
        n *= 10;
        System.out.format("Performing test with n=%d\n", n);

        startTime = System.currentTimeMillis();
        StringUtil.repeat(str, n);
        System.out
                .format("\tStringUtil.repeat() concatenation performed in    %d milliseconds\n",
                        System.currentTimeMillis() - startTime);

        if (j <5) {
            startTime = System.currentTimeMillis();
            String string = "";
            for (int i = 0; i < n; ++i)
                string = string.concat(str);
            System.out
                    .format("\tString.concat() concatenation performed in        %d milliseconds\n",
                            System.currentTimeMillis() - startTime);
        } else
            System.out
                    .format("\tString.concat() concatenation performed in        x milliseconds\n");
        startTime = System.currentTimeMillis();
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < n; ++i)
            b.append(str);
        b.toString();
        System.out
                .format("\tStringBuilder.append() concatenation performed in %d milliseconds\n",
                        System.currentTimeMillis() - startTime);
    }
}

Ergebnisse:

Performing test with n=10
    StringUtil.repeat() concatenation performed in    0 milliseconds
    String.concat() concatenation performed in        0 milliseconds
    StringBuilder.append() concatenation performed in 0 milliseconds
Performing test with n=100
    StringUtil.repeat() concatenation performed in    0 milliseconds
    String.concat() concatenation performed in        1 milliseconds
    StringBuilder.append() concatenation performed in 0 milliseconds
Performing test with n=1000
    StringUtil.repeat() concatenation performed in    0 milliseconds
    String.concat() concatenation performed in        1 milliseconds
    StringBuilder.append() concatenation performed in 1 milliseconds
Performing test with n=10000
    StringUtil.repeat() concatenation performed in    0 milliseconds
    String.concat() concatenation performed in        43 milliseconds
    StringBuilder.append() concatenation performed in 5 milliseconds
Performing test with n=100000
    StringUtil.repeat() concatenation performed in    0 milliseconds
    String.concat() concatenation performed in        1579 milliseconds
    StringBuilder.append() concatenation performed in 1 milliseconds
Performing test with n=1000000
    StringUtil.repeat() concatenation performed in    0 milliseconds
    String.concat() concatenation performed in        x milliseconds
    StringBuilder.append() concatenation performed in 10 milliseconds
Performing test with n=10000000
    StringUtil.repeat() concatenation performed in    7 milliseconds
    String.concat() concatenation performed in        x milliseconds
    StringBuilder.append() concatenation performed in 112 milliseconds
Performing test with n=100000000
    StringUtil.repeat() concatenation performed in    80 milliseconds
    String.concat() concatenation performed in        x milliseconds
    StringBuilder.append() concatenation performed in 1107 milliseconds
Performing test with n=1000000000
    StringUtil.repeat() concatenation performed in    1372 milliseconds
    String.concat() concatenation performed in        x milliseconds
    StringBuilder.append() concatenation performed in 12125 milliseconds

Fazit:

  • Für große n- verwenden Sie den rekursiven Ansatz
  • Für kleine n- für Schleife hat ausreichende Geschwindigkeit

4
Dies ist eine interessante Implementierung. Leider ist es entgegen Ihren Schlussfolgerungen für große n sehr ineffizient. Ich vermute, dies liegt an den vielen Speicherzuordnungen, die jedes Mal ausgeführt werden, wenn Sie Zeichenfolgen verketten. Versuchen Sie, es als Wrapper um eine rekursive Methode zu schreiben, die StringBuilder anstelle von String verwendet. Ich wette, Sie werden feststellen, dass die Ergebnisse viel besser sind.
Klitos Kyriacou

3
Es gibt viele Vorsichtsmaßnahmen, die Sie treffen müssen, wenn Sie so etwas mikro-Benchmarking durchführen, und ich glaube nicht, dass Sie eine davon treffen! Die vorherrschenden Leistungsfragen rund um diesen Code könnten leicht sein, ob er Hotspot-kompiliert ist, wie viel Müll er erzeugt usw. Ich wette, all diese ifAnweisungen werden die Verzweigungsvorhersage der CPU durcheinander bringen. Dies sollte wirklich mit JMH ( openjdk.java.net/projects/code-tools/jmh ) wiederholt werden , sonst ist es ein bisschen sinnlos.
SusanW

Beachten Sie, dass diese Implementierung O (n * log n) Komplexität hat, während StringBuilder O (n) ist :)
Leo Leontev


4

In Anbetracht dessen haben wir:

String c = "c"; // character to repeat, for empty it would be " ";
int n = 4; // number of times to repeat
String EMPTY_STRING = ""; // empty string (can be put in utility class)

Java 8 (Verwenden von Stream)

String resultOne = IntStream.range(0,n)
   .mapToObj(i->c).collect(Collectors.joining(EMPTY_STRING)); // cccc

Java 8 (Verwenden von nCopies)

String resultTwo = String.join(EMPTY_STRING, Collections.nCopies(n, c)); //cccc

1
Collectors.joining(EMPTY_STRING)ist gleichbedeutend mitCollectors.joining()
PPartisan

2

RandomStringUtils bietet die Möglichkeit , eine Zeichenfolge aus der angegebenen Eingabegröße zu erstellen. Ich kann die Geschwindigkeit nicht kommentieren, aber es ist ein Einzeiler.

RandomStringUtils.random(5,"\t");

erstellt eine Ausgabe

\ t \ t \ t \ t \ t

vorzuziehen, wenn Sie \ 0 nicht in Ihrem Code sehen möchten .


2

Verwenden Sie StringUtils: StringUtils.repeat ('', 10)


1

In den meisten Fällen benötigen Sie nur Strings bis zu einer bestimmten Länge, z. B. 100 Leerzeichen. Sie können ein Array von Zeichenfolgen vorbereiten, bei dem die Indexnummer der Größe der mit Leerzeichen gefüllten Zeichenfolge entspricht, und die Zeichenfolge nachschlagen, wenn die erforderliche Länge innerhalb der Grenzen liegt, oder sie bei Bedarf erstellen, wenn sie außerhalb der Grenze liegt.



0

Ersetzen Sie einfach Ihren StringBuffer durch einen StringBuilder . Schwer zu schlagen.

Wenn Ihre Länge eine große Zahl ist, können Sie eine effizientere (aber ungeschicktere) Selbstanhängung implementieren und die Länge in jeder Iteration duplizieren:

 public static String dummyString(char c, int len) {
  if( len < 1 ) return "";
  StringBuilder sb = new StringBuilder(len).append(c);
  int remnant = len - sb.length();
  while(remnant  > 0) {
   if( remnant  >= sb.length() ) sb.append(sb);
   else sb.append(sb.subSequence(0, remnant));
   remnant  = len - sb.length();
  }
  return sb.toString();
 }

Sie können auch den Arrays.fill()Ansatz ausprobieren ( Antwort von FrustratedWithFormsDesigner ).


Können Sie einige der Variablen mit mehr als einem Zeichen benennen?
C. Ross

0

Sie können ersetzen StringBuffermit StringBuilder(letzteres nicht synchronisiert ist, kann ein schneller in einem einzigen Thread app)

Und Sie können die StringBuilderInstanz einmal erstellen , anstatt sie jedes Mal zu erstellen, wenn Sie sie benötigen.

Etwas wie das:

class BuildString {
     private final StringBuilder builder = new StringBuilder();
     public String stringOf( char c , int times ) {

         for( int i = 0 ; i < times ; i++  ) {
             builder.append( c );
         }
         String result = builder.toString();
         builder.delete( 0 , builder.length() -1 );
         return result;
      }

  }

Und benutze es so:

 BuildString createA = new BuildString();
 String empty = createA.stringOf( ' ', 10 );

Wenn Sie Ihre createAals Instanzvariable halten, können Sie Zeit beim Erstellen von Instanzen sparen.

Dies ist nicht threadsicher. Wenn Sie mehrere Threads haben, sollte jeder Thread eine eigene Kopie haben.


0

Kombinieren Sie für eine gute Leistung die Antworten von Aznilamir und von FrustratedWithFormsDesigner

private static final String BLANKS = "                       ";
private static String getBlankLine( int length )
{
    if( length <= BLANKS.length() )
    {
        return BLANKS.substring( 0, length );
    }
    else
    {
        char[] array = new char[ length ];
        Arrays.fill( array, ' ' );
        return new String( array );
    }
}

Passen Sie die Größe BLANKSan Ihre Anforderungen an. Meine spezifische BLANKSZeichenfolge ist ungefähr 200 Zeichen lang.


0

Haben Sie eine Methode wie diese. Hiermit werden die erforderlichen Leerzeichen am Ende des Gegebenen angehängt String, um ein Gegebenes Stringzur Länge einer bestimmten Länge zu machen.

public static String fillSpaces (String str) {

    // the spaces string should contain spaces exceeding the max needed
    String spaces = "                                                   ";
    return str + spaces.substring(str.length());
}

0

Wenn der String eine feste Größe haben soll, müssen Sie ihn entweder auffüllen oder abschneiden, um Daten zu tabellieren ...

class Playground {
    private static String fixStrSize(String s, int n) {
        return String.format("%-" + n + "s", String.format("%." + n +"s", s));
    }

    public static void main(String[ ] args) {
        System.out.println("|"+fixStrSize("Hell",8)+"|");
        System.out.println("|"+fixStrSize("Hells Bells Java Smells",8)+"|");
    }
}

|Hell    |
|Hells Be|

Hervorragende Referenz hier .


0

Dies hat für mich ohne Verwendung externer Bibliotheken in Java 8 funktioniert

String sampleText = "test"
int n = 3;
String output = String.join("", Collections.nCopies(n, sampleText));
System.out.println(output);

Und die Ausgabe ist

testtesttest

0

int c = 10; String Leerzeichen = String.format ("%" + c + "c", ''); Dies wird Ihr Problem lösen.


-1

Eine einfache Methode wie unten kann ebenfalls verwendet werden

public static String padString(String str, int leng,char chr) {
        for (int i = str.length(); i <= leng; i++)
            str += chr;
        return str;
    }

Dies ist wesentlich langsamer.
SLaks

Sie können StringBuffer anstelle der Zeichenfolgenverkettung verwenden, falls die Zeichenfolgen, mit denen Sie sich befassen, groß sind. Das wird die Geschwindigkeit erheblich beeinflussen
Yashpal Singla

-2

Wie wäre es damit?

public String fillSpaces(int len) {
    /* the spaces string should contain spaces exceeding the max needed */  
    String spaces = "                                                   ";
    return spaces.substring(0,len);
}

EDIT: Ich habe einen einfachen Code geschrieben, um das Konzept zu testen und hier, was ich gefunden habe.

Methode 1: Hinzufügen eines einzelnen Leerzeichens in einer Schleife:

  public String execLoopSingleSpace(int len){
    StringBuilder sb = new StringBuilder();

    for(int i=0; i < len; i++) {
        sb.append(' ');
    }

    return sb.toString();
  }

Methode 2: 100 Leerzeichen und Schleife anhängen, dann Teilzeichenfolge:

  public String execLoopHundredSpaces(int len){
    StringBuilder sb = new StringBuilder("          ")
            .append("          ").append("          ").append("          ")
            .append("          ").append("          ").append("          ")
            .append("          ").append("          ").append("          ");

    for (int i=0; i < len/100 ; i++) {
        sb.append("          ")
            .append("          ").append("          ").append("          ")
            .append("          ").append("          ").append("          ")
            .append("          ").append("          ").append("          ");
    }

    return sb.toString().substring(0,len);
  }

Das Ergebnis ist, dass ich 12.345.678 Leerzeichen erschaffe:

C:\docs\Projects> java FillSpace 12345678
method 1: append single spaces for 12345678 times. Time taken is **234ms**. Length of String is 12345678
method 2: append 100 spaces for 123456 times. Time taken is **141ms**. Length of String is 12345678
Process java exited with code 0

und für 10.000.000 Plätze:

C:\docs\Projects> java FillSpace 10000000
method 1: append single spaces for 10000000 times. Time taken is **157ms**. Length of String is 10000000
method 2: append 100 spaces for 100000 times. Time taken is **109ms**. Length of String is 10000000
Process java exited with code 0

Das Kombinieren von direkter Zuordnung und Iteration nimmt immer weniger Zeit in Anspruch, durchschnittlich 60 ms weniger, wenn große Räume erstellt werden. Bei kleineren Größen sind beide Ergebnisse vernachlässigbar.

Aber bitte weiter kommentieren :-)


3
@ Aznilamir: Hm, wie machst du das für 10.000 Plätze?
Jayan

Die Idee ist, Schleife und direkte Zuweisung von 100 Leerzeichen zu kombinieren. Hier sind die Code-Schnipsel:
Aznilamir

Warum verwenden Sie mehrere Anhänge, um 100 Zeichen hinzuzufügen? Warum nicht ein 100 Zeichen anhängen?
Nycynik

-3

Ich kenne keine eingebaute Methode für das, wonach Sie fragen. Für eine kleine feste Länge wie 10 sollte Ihre Methode jedoch ausreichend schnell sein.


Wenn es nur eine kleine feste Länge wie 10 wäre.
C. Ross
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.