Gibt es eine einfache Möglichkeit, mehrere Leerzeichen in einer Zeichenfolge zu entfernen?


390

Angenommen, diese Zeichenfolge:

The   fox jumped   over    the log.

Sich in etwas verwandeln:

The fox jumped over the log.

Was ist am einfachsten (1-2 Zeilen), um dies zu erreichen, ohne zu teilen und in Listen zu gehen?


22
Was ist Ihre Abneigung gegen Listen? Sie sind ein integraler Bestandteil der Sprache, und "" .join (list_of_words) ist eine der Kernsprachen, um eine Liste von Zeichenfolgen zu einer einzelnen durch Leerzeichen getrennten Zeichenfolge zu machen.
PaulMcG

3
@ Tom / @ Paul: Für einfache Strings wäre (String) Join einfach und süß. Aber es wird komplexer, wenn es andere Leerzeichen gibt, die man NICHT stören möchte ... in diesem Fall wären "while" - oder Regex-Lösungen am besten. Ich habe unten einen String-Join gepostet, der "korrekt" wäre, mit zeitgesteuerten Testergebnissen für drei Möglichkeiten, dies zu tun.
Pythonlarry

Antworten:


529
>>> import re
>>> re.sub(' +', ' ', 'The     quick brown    fox')
'The quick brown fox'

20
Diese Lösung verarbeitet nur Leerzeichen. Es würde keine Registerkarte oder andere Leerzeichen ersetzen, die von \ s wie in der Lösung von nsr81 behandelt werden.
Taylor Leese

2
Das stimmt, string.splitbehandelt auch alle Arten von Leerzeichen.
Josh Lee

6
Ich bevorzuge dieses, weil es sich nur auf das Leerzeichen konzentriert und keine Zeichen wie '\ n' betrifft.
Hhsaffar

2
Ja richtig. Aber vorher sollte strip () gemacht werden. Es werden Leerzeichen von beiden Enden entfernt.
Hardik Patel

17
Sie können dies verwenden re.sub(' {2,}', ' ', 'The quick brown fox'), um redundantes Ersetzen von Single-Space durch Single-Space zu verhindern .
AneesAhmed777

541

foo ist dein String:

" ".join(foo.split())

Seien Sie gewarnt, obwohl dadurch "alle Leerzeichen (Leerzeichen, Tabulator, Zeilenumbruch, Rückgabe, Formularvorschub)" entfernt werden (dank hhsaffar , siehe Kommentare). Dh "this is \t a test\n"wird effektiv als enden "this is a test".


19
"Ohne zu teilen und in Listen zu gehen ..."
Gumbo

72
Ich habe "Ohne zu teilen und in Listen zu gehen ..." ignoriert, weil ich immer noch denke, dass es die beste Antwort ist.
Taylor Leese

1
Dadurch werden nachgestellte Leerzeichen entfernt. Wenn Sie sie behalten möchten, tun Sie Folgendes: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003

6x schneller als die Lösung re.sub ().
nerdfever.com

1
@ AstraUvarova-Saturn'sstar Ich habe es profiliert.
nerdfever.com

85
import re
s = "The   fox jumped   over    the log."
re.sub("\s\s+" , " ", s)

oder

re.sub("\s\s+", " ", s)

da das Leerzeichen vor dem Komma in PEP 8 als Pet Peeve aufgeführt ist , wie von Benutzer Martin Thoma in den Kommentaren erwähnt.


2
Ich würde diesen regulären Ausdruck in ändern, r"\s\s+"damit er nicht versucht, bereits einzelne Leerzeichen zu ersetzen.
Ben Blank

19
Wenn Sie dieses Verhalten wollten, warum nicht einfach "\s{2,}"anstelle einer Problemumgehung, wenn Sie das mäßig fortgeschrittene Regex-Verhalten nicht kennen?
Chris Lutz

2
Denken Sie daran, dass sub () die Eingabezeichenfolge nicht ändert s, sondern den neuen Wert zurückgibt.
GCB

1
@moose - Es ist eine Optimierung der Lesbarkeit als eine Leistungsoptimierung. \s+würde dazu führen, dass in der Zeile " ein oder mehrere Leerzeichen durch ein Leerzeichen ersetzen" und nicht " zwei oder mehr Leerzeichen durch ein Leerzeichen ersetzen " steht. Ersteres lässt mich sofort innehalten und denken: "Warum ein Leerzeichen durch ein Leerzeichen ersetzen? Das ist albern." Für mich ist das ein (sehr geringer) Code-Geruch. Ich würde eigentlich nicht erwarten , dass es überhaupt einen Unterschied in der Leistung sein zwischen den beiden, wie es geht in eine neue Zeichenfolge zu kopieren sowieso, und muss aufhören und Test unabhängig davon , wo der Raum kopiert wird aus .
Ben Blank

8
Ich würde davon abraten, \s\s+da dies ein TAB-Zeichen nicht auf ein normales Leerzeichen normalisiert. Ein SPACE + TAB wird auf diese Weise ersetzt.
Vdboor

51

Wenn Sie reguläre Ausdrücke mit "\ s" verwenden und einfache string.split () ausführen, werden auch andere Leerzeichen entfernt - wie Zeilenumbrüche, Zeilenumbrüche und Tabulatoren. Es sei denn , dies gewünscht ist, um nur zu tun mehrere Räume , präsentiere ich diese Beispiele.

Ich habe 11 Absätze, 1000 Wörter, 6665 Bytes Lorem Ipsum verwendet , um realistische Zeittests zu erhalten, und zusätzliche Leerzeichen mit zufälliger Länge verwendet:

original_string = ''.join(word + (' ' * random.randint(1, 10)) for word in lorem_ipsum.split(' '))

Der Einzeiler macht im Wesentlichen einen Streifen von allen führenden / nachfolgenden Leerzeichen und bewahrt einen führenden / nachfolgenden Leerzeichen (aber nur EINEN ;-).

# setup = '''

import re

def while_replace(string):
    while '  ' in string:
        string = string.replace('  ', ' ')

    return string

def re_replace(string):
    return re.sub(r' {2,}' , ' ', string)

def proper_join(string):
    split_string = string.split(' ')

    # To account for leading/trailing spaces that would simply be removed
    beg = ' ' if not split_string[ 0] else ''
    end = ' ' if not split_string[-1] else ''

    # versus simply ' '.join(item for item in string.split(' ') if item)
    return beg + ' '.join(item for item in split_string if item) + end

original_string = """Lorem    ipsum        ... no, really, it kept going...          malesuada enim feugiat.         Integer imperdiet    erat."""

assert while_replace(original_string) == re_replace(original_string) == proper_join(original_string)

#'''

# while_replace_test
new_string = original_string[:]

new_string = while_replace(new_string)

assert new_string != original_string

# re_replace_test
new_string = original_string[:]

new_string = re_replace(new_string)

assert new_string != original_string

# proper_join_test
new_string = original_string[:]

new_string = proper_join(new_string)

assert new_string != original_string

HINWEIS: Die " whileVersion" hat eine Kopie der original_string, wie ich glaube, einmal beim ersten Lauf modifizierten, aufeinanderfolgenden Läufe schneller gemacht (wenn auch nur ein bisschen). Da dies Zeit hinzufügt, habe ich diese Zeichenfolgenkopie zu den beiden anderen hinzugefügt, sodass die Zeiten nur den Unterschied in der Logik zeigten. Beachten Sie, dass das Haupt stmtauf timeitInstanzen wird nur einmal durchgeführt werden ; Auf die ursprüngliche Art und Weise, wie ich das gemacht habe, arbeitete die whileSchleife auf demselben Label original_string, daher gab es beim zweiten Durchlauf nichts zu tun. Die Art und Weise, wie es jetzt eingerichtet ist, eine Funktion mit zwei verschiedenen Bezeichnungen aufzurufen, ist kein Problem. Ich habe assertallen Mitarbeitern Aussagen hinzugefügt , um zu überprüfen, ob wir bei jeder Iteration etwas ändern (für diejenigen, die möglicherweise zweifelhaft sind). ZB ändern Sie dies und es bricht:

# while_replace_test
new_string = original_string[:]

new_string = while_replace(new_string)

assert new_string != original_string # will break the 2nd iteration

while '  ' in original_string:
    original_string = original_string.replace('  ', ' ')

Tests run on a laptop with an i5 processor running Windows 7 (64-bit).

timeit.Timer(stmt = test, setup = setup).repeat(7, 1000)

test_string = 'The   fox jumped   over\n\t    the log.' # trivial

Python 2.7.3, 32-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001066 |   0.001260 |   0.001128 |   0.001092
     re_replace_test |   0.003074 |   0.003941 |   0.003357 |   0.003349
    proper_join_test |   0.002783 |   0.004829 |   0.003554 |   0.003035

Python 2.7.3, 64-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001025 |   0.001079 |   0.001052 |   0.001051
     re_replace_test |   0.003213 |   0.004512 |   0.003656 |   0.003504
    proper_join_test |   0.002760 |   0.006361 |   0.004626 |   0.004600

Python 3.2.3, 32-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001350 |   0.002302 |   0.001639 |   0.001357
     re_replace_test |   0.006797 |   0.008107 |   0.007319 |   0.007440
    proper_join_test |   0.002863 |   0.003356 |   0.003026 |   0.002975

Python 3.3.3, 64-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001444 |   0.001490 |   0.001460 |   0.001459
     re_replace_test |   0.011771 |   0.012598 |   0.012082 |   0.011910
    proper_join_test |   0.003741 |   0.005933 |   0.004341 |   0.004009

test_string = lorem_ipsum
# Thanks to http://www.lipsum.com/
# "Generated 11 paragraphs, 1000 words, 6665 bytes of Lorem Ipsum"

Python 2.7.3, 32-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.342602 |   0.387803 |   0.359319 |   0.356284
     re_replace_test |   0.337571 |   0.359821 |   0.348876 |   0.348006
    proper_join_test |   0.381654 |   0.395349 |   0.388304 |   0.388193    

Python 2.7.3, 64-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.227471 |   0.268340 |   0.240884 |   0.236776
     re_replace_test |   0.301516 |   0.325730 |   0.308626 |   0.307852
    proper_join_test |   0.358766 |   0.383736 |   0.370958 |   0.371866    

Python 3.2.3, 32-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.438480 |   0.463380 |   0.447953 |   0.446646
     re_replace_test |   0.463729 |   0.490947 |   0.472496 |   0.468778
    proper_join_test |   0.397022 |   0.427817 |   0.406612 |   0.402053    

Python 3.3.3, 64-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.284495 |   0.294025 |   0.288735 |   0.289153
     re_replace_test |   0.501351 |   0.525673 |   0.511347 |   0.508467
    proper_join_test |   0.422011 |   0.448736 |   0.436196 |   0.440318

Für die triviale Zeichenfolge scheint eine while-Schleife die schnellste zu sein, gefolgt von der Aufteilung / Verknüpfung der Pythonic-Zeichenfolge und der Regex, die die Rückseite nach oben zieht.

Für nicht triviale Zeichenfolgen scheint es ein bisschen mehr zu geben. 32-Bit 2,7? Es ist Regex zur Rettung! 2,7 64-Bit? Eine whileSchleife ist mit einem anständigen Abstand am besten. 32-Bit 3.2, gehen Sie mit dem "richtigen" join. 64-Bit 3.3, machen Sie eine whileSchleife. Nochmal.

Am Ende kann man die Leistung verbessern, wenn / wo / wenn nötig , aber es ist immer am besten, sich an das Mantra zu erinnern :

  1. Bring es zum Laufen
  2. Richtig machen
  3. Mach schnell

IANAL, YMMV, Vorbehalt Emptor!


1
Ich hätte es vorgezogen, wenn Sie das Einfache getestet hätten, ' '.join(the_string.split())da dies der übliche Anwendungsfall ist, aber ich möchte mich für Ihre Arbeit bedanken!
Mi

@wedi: Laut anderen Kommentaren (wie von Gumbo ; user984003 , obwohl ihre / seine Lösung vermutlich ist und nicht "in allen Fällen" funktioniert), entspricht diese Art von Lösung nicht der Anfrage des Fragestellers. Man kann .split ('') und ein comp / gen verwenden, wird aber haariger, um mit Blei / nachgestellten Leerzeichen umzugehen.
Pythonlarry

@wedi: ZB: ' '.join(p for p in s.split(' ') if p)<- verlor immer noch Lead / Trailing Spaces, machte aber mehrere Spaces aus. Um sie zu behalten, muss man gerne tun parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '')!
Pythonlarry

Danke @pythonlarry für das Mantra! und liebe den detaillierten Test! Ich bin gespannt, ob sich Ihre Gedanken oder Ansichten dazu seit 6 Jahren geändert haben.
JayRizzo

Fehlende Version, die Generatoren verwendet
Lee

42

Ich muss dem Kommentar von Paul McGuire zustimmen. Mir,

' '.join(the_string.split())

ist dem Auspeitschen einer Regex weitaus vorzuziehen.

Meine Messungen (Linux und Python 2.5) zeigen, dass der Split-Then-Join fast fünfmal schneller ist als "re.sub (...)" und immer noch dreimal schneller, wenn Sie den regulären Ausdruck einmal vorkompilieren und den Vorgang ausführen mehrmals. Und es ist in jeder Hinsicht leichter zu verstehen - viel mehr pythonisch.


Dadurch werden nachgestellte Leerzeichen entfernt. Wenn Sie sie behalten möchten, tun Sie Folgendes: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003

4
Ein einfacher regulärer Ausdruck ist viel besser zu lesen. Optimieren Sie niemals die Leistung, bevor Sie dies benötigen.
GCB

@gcb: Warum nicht? Was ist, wenn Sie ein Szenario mit hohem Durchsatz erwarten (z. B. aufgrund der hohen Nachfrage)? Warum nicht etwas bereitstellen, von dem Sie erwarten, dass es in diesem Szenario von Anfang an weniger ressourcenintensiv ist?
Hassan Baig

1
@ HassanBaig Wenn Sie bereits die Leistungsanforderungen haben, dann ist es nicht wirklich vorzeitige Optimierung, oder? Mein Punkt ist, wenn Sie noch nicht von der Leistung besessen sein müssen, ist es immer besser, die Lesbarkeit anzustreben.
GCB

14

Ähnlich wie bei den vorherigen Lösungen, jedoch spezifischer: Ersetzen Sie zwei oder mehr Leerzeichen durch eines:

>>> import re
>>> s = "The   fox jumped   over    the log."
>>> re.sub('\s{2,}', ' ', s)
'The fox jumped over the log.'

11

Eine einfache Lösung

>>> import re
>>> s="The   fox jumped   over    the log."
>>> print re.sub('\s+',' ', s)
The fox jumped over the log.

6

Sie können die Zeichenfolgenteilungstechnik auch in einem Pandas DataFrame verwenden, ohne .apply (..) verwenden zu müssen. Dies ist nützlich, wenn Sie den Vorgang für eine große Anzahl von Zeichenfolgen schnell ausführen müssen. Hier steht es in einer Zeile:

df['message'] = (df['message'].str.split()).str.join(' ')

6
import re
string = re.sub('[ \t\n]+', ' ', 'The     quick brown                \n\n             \t        fox')

Dadurch werden alle Registerkarten, neuen Linien und mehrere Leerzeichen mit einem Leerzeichen entfernt.


Wenn Sie jedoch Leerzeichen (nicht druckbare) Zeichen haben, die sich nicht in Ihrem Bereich befinden, z. B. '\ x00' bis '\ x0020', werden diese vom Code nicht entfernt.
Muskovets

5

Ich habe die folgende Methode ausprobiert und sie funktioniert sogar im Extremfall wie:

str1='          I   live    on    earth           '

' '.join(str1.split())

Wenn Sie jedoch einen regulären Ausdruck bevorzugen, können Sie Folgendes tun:

re.sub('\s+', ' ', str1)

Es muss zwar eine Vorverarbeitung durchgeführt werden, um den nachgestellten und endenden Speicherplatz zu entfernen.


3

Dies scheint auch zu funktionieren:

while "  " in s:
    s = s.replace("  ", " ")

Wo die Variable sIhre Zeichenfolge darstellt.


2

In einigen Fällen ist es wünschenswert, aufeinanderfolgende Vorkommen jedes Leerzeichen durch eine einzelne Instanz dieses Zeichens zu ersetzen . Sie würden dazu einen regulären Ausdruck mit Rückreferenzen verwenden.

(\s)\1{1,}Entspricht einem beliebigen Leerzeichen, gefolgt von einem oder mehreren Vorkommen dieses Zeichens. Jetzt müssen Sie nur noch die erste Gruppe ( \1) als Ersatz für das Match angeben .

Umhüllen Sie dies mit einer Funktion:

import re

def normalize_whitespace(string):
    return re.sub(r'(\s)\1{1,}', r'\1', string)
>>> normalize_whitespace('The   fox jumped   over    the log.')
'The fox jumped over the log.'
>>> normalize_whitespace('First    line\t\t\t \n\n\nSecond    line')
'First line\t \nSecond line'

2

Eine andere Alternative:

>>> import re
>>> str = 'this is a            string with    multiple spaces and    tabs'
>>> str = re.sub('[ \t]+' , ' ', str)
>>> print str
this is a string with multiple spaces and tabs

2

Eine Codezeile zum Entfernen aller zusätzlichen Leerzeichen vor, nach und innerhalb eines Satzes:

sentence = "  The   fox jumped   over    the log.  "
sentence = ' '.join(filter(None,sentence.split(' ')))

Erläuterung:

  1. Teilen Sie die gesamte Zeichenfolge in eine Liste auf.
  2. Filtern Sie leere Elemente aus der Liste.
  3. Verbinden Sie die verbleibenden Elemente * mit einem Leerzeichen

* Die restlichen Elemente sollten Wörter oder Wörter mit Satzzeichen usw. sein. Ich habe dies nicht ausführlich getestet, aber dies sollte ein guter Ausgangspunkt sein. Alles Gute!


2

Lösung für Python-Entwickler:

import re

text1 = 'Python      Exercises    Are   Challenging Exercises'
print("Original string: ", text1)
print("Without extra spaces: ", re.sub(' +', ' ', text1))

Ausgabe:
Original string: Python Exercises Are Challenging Exercises Without extra spaces: Python Exercises Are Challenging Exercises


1
def unPretty(S):
   # Given a dictionary, JSON, list, float, int, or even a string...
   # return a string stripped of CR, LF replaced by space, with multiple spaces reduced to one.
   return ' '.join(str(S).replace('\n', ' ').replace('\r', '').split())

1

Das schnellste, was Sie für benutzergenerierte Zeichenfolgen erhalten können, ist:

if '  ' in text:
    while '  ' in text:
        text = text.replace('  ', ' ')

Der Kurzschluss macht es etwas schneller als die umfassende Antwort von Pythonlarry . Entscheiden Sie sich für diese Option, wenn Sie nach Effizienz streben und streng darauf bedacht sind, zusätzliche Leerzeichen der Single-Space-Variante auszusortieren .


1

Sehr überraschend - niemand hat eine einfache Funktion veröffentlicht, die viel schneller ist als ALLE anderen veröffentlichten Lösungen. Hier kommt's:

def compactSpaces(s):
    os = ""
    for c in s:
        if c != " " or os[-1] != " ":
            os += c 
    return os


0
string = 'This is a             string full of spaces          and taps'
string = string.split(' ')
while '' in string:
    string.remove('')
string = ' '.join(string)
print(string)

Ergebnisse :

Dies ist eine Zeichenfolge voller Leerzeichen und Taps


0

Verwenden Sie Folgendes, um Leerzeichen zu entfernen, indem Sie führende, nachfolgende und zusätzliche Leerzeichen zwischen den Wörtern berücksichtigen:

(?<=\s) +|^ +(?=\s)| (?= +[\n\0])

Der erste orbefasst sich mit dem führenden Leerraum, der zweite ormit dem Beginn des String-führenden Leerraums und der letzte mit dem nachfolgenden Leerraum.

Zum Nachweis der Verwendung erhalten Sie über diesen Link einen Test.

https://regex101.com/r/meBYli/4

Dies ist mit der Funktion re.split zu verwenden.


0

Ich habe meine einfache Methode, die ich im College angewendet habe.

line = "I     have            a       nice    day."

end = 1000
while end != 0:
    line.replace("  ", " ")
    end -= 1

Dies ersetzt jedes doppelte Leerzeichen durch ein einzelnes Leerzeichen und führt dies 1000 Mal durch. Dies bedeutet, dass Sie 2000 zusätzliche Plätze haben können und trotzdem funktionieren. :) :)


Dies ist (praktisch) identisch mit Anakimis Antwort (mehr als zwei Jahre zuvor veröffentlicht).
Peter Mortensen

0

Ich habe eine einfache Methode ohne Aufteilung:

a = "Lorem   Ipsum Darum     Diesrum!"
while True:
    count = a.find("  ")
    if count > 0:
        a = a.replace("  ", " ")
        count = a.find("  ")
        continue
    else:
        break

print(a)

1
Wie unterscheidet sich das von Anakimis Antwort (mehr als drei Jahre zuvor veröffentlicht)? Ist es nicht nur eine kompliziertere Version?
Peter Mortensen

0
import re

Text = " You can select below trims for removing white space!!   BR Aliakbar     "
  # trims all white spaces
print('Remove all space:',re.sub(r"\s+", "", Text), sep='') 
# trims left space
print('Remove leading space:', re.sub(r"^\s+", "", Text), sep='') 
# trims right space
print('Remove trailing spaces:', re.sub(r"\s+$", "", Text), sep='')  
# trims both
print('Remove leading and trailing spaces:', re.sub(r"^\s+|\s+$", "", Text), sep='')
# replace more than one white space in the string with one white space
print('Remove more than one space:',re.sub(' +', ' ',Text), sep='') 

Ergebnis:

Entfernen Sie den gesamten Platz: Sie können die folgenden Kanten auswählen, um Leerzeichen zu entfernen !! BRAliakbar Führenden Bereich entfernen: Sie können die folgenden Bereiche auswählen, um Leerzeichen zu entfernen !! BR Aliakbar Nachgestellte
Leerzeichen entfernen: Sie können unten Verkleidungen auswählen, um Leerzeichen zu entfernen !! BR Aliakbar Entfernen Sie führende und nachfolgende Leerzeichen: Sie können unten Verkleidungen auswählen, um Leerzeichen zu entfernen !! BR Aliakbar Entfernen Sie mehr als ein Leerzeichen: Sie können unten Verkleidungen auswählen, um Leerzeichen zu entfernen !! BR Aliakbar


-1

Ich habe nicht viel in die anderen Beispiele gelesen, aber ich habe gerade diese Methode zum Konsolidieren mehrerer aufeinanderfolgender Leerzeichen erstellt.

Es werden keine Bibliotheken verwendet, und obwohl es in Bezug auf die Skriptlänge relativ lang ist, handelt es sich nicht um eine komplexe Implementierung:

def spaceMatcher(command):
    """
    Function defined to consolidate multiple whitespace characters in
    strings to a single space
    """
    # Initiate index to flag if more than one consecutive character
    iteration
    space_match = 0
    space_char = ""
    for char in command:
      if char == " ":
          space_match += 1
          space_char += " "
      elif (char != " ") & (space_match > 1):
          new_command = command.replace(space_char, " ")
          space_match = 0
          space_char = ""
      elif char != " ":
          space_match = 0
          space_char = ""
   return new_command

command = None
command = str(input("Please enter a command ->"))
print(spaceMatcher(command))
print(list(spaceMatcher(command)))
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.