Có một cách đơn giản để loại bỏ nhiều khoảng trắng trong một chuỗi?


390

Giả sử chuỗi này:

The   fox jumped   over    the log.

Trở thành:

The fox jumped over the log.

Đơn giản nhất (1-2 dòng) để đạt được điều này, mà không cần tách và đi vào danh sách là gì?


22
Sự ác cảm của bạn đối với danh sách là gì? Chúng là một phần không thể thiếu của ngôn ngữ và "" .join (list_of_words) là một trong những thành ngữ cốt lõi để tạo danh sách các chuỗi thành một chuỗi được phân tách bằng dấu cách.
PaulMcG

3
@ Tom / @ Paul: Đối với các chuỗi đơn giản, (chuỗi) tham gia sẽ đơn giản và ngọt ngào. Nhưng nó trở nên phức tạp hơn nếu có khoảng trắng khác mà người ta KHÔNG muốn làm phiền ... trong trường hợp đó là giải pháp "trong khi" hoặc regex sẽ là tốt nhất. Tôi đã đăng bên dưới một chuỗi tham gia sẽ là "chính xác", với kết quả kiểm tra được định thời gian cho ba cách thực hiện việc này.
pythonlarry

Câu trả lời:


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

20
Giải pháp này chỉ xử lý các ký tự không gian duy nhất. Nó sẽ không thay thế một tab hoặc các ký tự khoảng trắng khác được xử lý bởi \ s like trong giải pháp của nsr81.
Taylor Leese

2
Điều đó đúng, string.splitcũng xử lý tất cả các loại khoảng trắng.
Josh Lee

6
Tôi thích cái này vì nó chỉ tập trung vào nhân vật không gian và không ảnh hưởng đến các nhân vật như '\ n'.
hhsaffar

2
Vâng đúng. Nhưng trước đó dải () nên được thực hiện. Nó sẽ loại bỏ không gian từ cả hai đầu.
Hardik Patel

17
Bạn có thể sử dụng re.sub(' {2,}', ' ', 'The quick brown fox')để ngăn chặn sự thay thế dự phòng của không gian đơn với không gian đơn .
AneesAhmed777

541

foo là chuỗi của bạn:

" ".join(foo.split())

Được cảnh báo mặc dù điều này loại bỏ "tất cả các ký tự khoảng trắng (dấu cách, tab, dòng mới, trả về, formfeed)" (nhờ hhsaffar , xem bình luận). Tức là, "this is \t a test\n"cuối cùng sẽ có hiệu quả như "this is a test".


19
Không có sự chia tách và đi vào danh sách ... Hãy
Gumbo

72
Tôi bỏ qua "Không chia tách và đi vào danh sách ..." bởi vì tôi vẫn nghĩ đó là câu trả lời tốt nhất.
Taylor Leese

1
Điều này loại bỏ không gian dấu. Nếu bạn muốn giữ chúng làm: văn bản [0: 1] + "" .join (văn bản [1: -1] .split ()) + văn bản [-1]
user984003

Nhanh hơn 6 lần so với giải pháp re.sub ().
nerdfever.com

1
@ AstraUvarova-Saturn'sstar Tôi đã mô tả nó.
nerdfever.com

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

hoặc là

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

vì khoảng trắng trước dấu phẩy được liệt kê dưới dạng peeve vật nuôi trong PEP 8 , như được đề cập bởi người dùng Martin Thoma trong các bình luận.


2
Tôi có xu hướng thay đổi regex đó thành r"\s\s+"để nó không cố gắng thay thế các không gian đơn lẻ.
Ben Trống

19
Nếu bạn muốn hành vi đó, tại sao không chỉ "\s{2,}"thay vì một cách giải quyết vì không biết hành vi regex vừa phải nâng cao?
Chris Lutz

2
hãy nhớ rằng sub () không thay đổi chuỗi đầu vào s, nhưng trả về giá trị mới.
gcb

1
@moose - Đó là một tối ưu hóa dễ đọc hơn hiệu suất. \s+sẽ khiến dòng này đọc "thay thế một hoặc nhiều khoảng trắng bằng một khoảng trắng", thay vì "thay thế hai hoặc nhiều khoảng trắng bằng một khoảng trắng". Cái trước ngay lập tức khiến tôi dừng lại và nghĩ "Tại sao lại thay thế một không gian bằng một không gian? Điều đó thật ngớ ngẩn." Đối với tôi, đó là một mùi mã (rất nhỏ). Tôi thực sự không mong đợi có sự khác biệt về hiệu năng giữa cả hai, vì dù sao nó cũng sẽ được sao chép thành một chuỗi mới, và phải dừng lại và kiểm tra bất kể không gian được sao chép từ đâu .
Ben Trống

8
Tôi khuyên bạn không nên \s\s+vì điều này sẽ không bình thường hóa một nhân vật TAB trở lại không gian bình thường. SPACE + TAB được thay thế theo cách này.
vdboor

51

Sử dụng biểu thức chính quy với "\ s" và thực hiện các chuỗi đơn giản.split () cũng sẽ xóa các khoảng trắng khác - như dòng mới, trả về vận chuyển, tab. Trừ khi điều này là mong muốn, để chỉ làm nhiều không gian , tôi trình bày các ví dụ này.

Tôi đã sử dụng 11 đoạn, 1000 từ, 6665 byte Lorem Ipsum để có các bài kiểm tra thời gian thực tế và sử dụng các khoảng trắng thừa có độ dài ngẫu nhiên trong suốt:

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

Về cơ bản, một lớp lót sẽ thực hiện một dải của bất kỳ khoảng trắng đầu / cuối nào và nó giữ một khoảng trắng ở đầu / cuối (nhưng chỉ MỘT ;-).

# 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

LƯU Ý: " whilePhiên bản" đã tạo một bản sao của original_string, như tôi tin rằng một khi được sửa đổi trong lần chạy đầu tiên, các lần chạy liên tiếp sẽ nhanh hơn (nếu chỉ một chút). Khi điều này thêm thời gian, tôi đã thêm bản sao chuỗi này vào hai bản kia để thời gian chỉ cho thấy sự khác biệt trong logic. Hãy nhớ rằng chính stmttrên các timeittrường hợp sẽ chỉ được thực hiện một lần ; cách ban đầu tôi đã làm điều này, whilevòng lặp hoạt động trên cùng một nhãn original_string, do đó, lần chạy thứ hai, sẽ không có gì để làm. Cách thức thiết lập bây giờ, gọi một hàm, sử dụng hai nhãn khác nhau, đó không phải là vấn đề. Tôi đã thêm các assertbáo cáo cho tất cả các công nhân để xác minh rằng chúng tôi thay đổi điều gì đó mỗi lần lặp (đối với những người có thể không rõ ràng). Ví dụ, thay đổi điều này và nó phá vỡ:

# 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

Đối với chuỗi tầm thường, có vẻ như vòng lặp while là nhanh nhất, theo sau là chuỗi phân tách / nối Pythonic và regex kéo lên phía sau.

Đối với các chuỗi không tầm thường , dường như có thêm một chút để xem xét. 32-bit 2.7? Đó là regex để giải cứu! 2.7 64 bit? Một whilevòng lặp là tốt nhất, bởi một lề tốt. 32-bit 3.2, đi với "đúng" join. 64-bit 3.3, đi một whilevòng lặp. Lần nữa.

Cuối cùng, người ta có thể cải thiện hiệu suất nếu / ở đâu / khi cần , nhưng tốt nhất là luôn nhớ câu thần chú :

  1. Lam cho no hoạt động
  2. Làm cho đúng
  3. Làm nhanh

IANAL, YMMV, Emptor Caveat!


1
Tôi sẽ thích nếu bạn đã thử nghiệm đơn giản ' '.join(the_string.split())vì đây là trường hợp sử dụng thông thường nhưng tôi muốn nói lời cảm ơn vì công việc của bạn!
wedi

@wedi: Theo các nhận xét khác (như từ Gumbo ; user984003 , mặc dù giải pháp của cô ấy / anh ấy được cho là và sẽ không hoạt động "trong mọi trường hợp"), loại giải pháp này không tuân thủ yêu cầu của người hỏi. Người ta có thể sử dụng .split ('') và comp / gen, nhưng có hairier để xử lý các khoảng trắng chì / dấu.
trăn

@wedi: Ví dụ: ' '.join(p for p in s.split(' ') if p)<- vẫn bị mất khoảng trắng chì / dấu, nhưng chiếm nhiều khoảng trắng. Để giữ chúng, phải làm như thế nào parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '')!
pythonlarry

Cảm ơn @pythonlarry cho câu thần chú! và yêu các bài kiểm tra chi tiết! Tôi tò mò muốn biết liệu suy nghĩ hoặc quan điểm của bạn đã thay đổi về điều này kể từ khi nó được 6 năm?
JayRizzo

Phiên bản thiếu sử dụng máy phát điện
Lee

42

Tôi phải đồng ý với nhận xét của Paul McGuire. Với tôi,

' '.join(the_string.split())

là rất thích hợp để đánh bại một regex.

Các phép đo của tôi (Linux và Python 2.5) cho thấy phép chia tách sau đó nhanh hơn gần năm lần so với thực hiện "re.sub (...)" và vẫn nhanh hơn ba lần nếu bạn biên dịch lại biểu thức chính quy một lần và thực hiện thao tác nhiều lần. Và đó là bằng bất kỳ biện pháp nào dễ hiểu hơn - nhiều Pythonic hơn.


Điều này loại bỏ không gian dấu. Nếu bạn muốn giữ chúng làm: văn bản [0: 1] + "" .join (văn bản [1: -1] .split ()) + văn bản [-1]
user984003

4
một regrec đơn giản là tốt hơn nhiều để đọc. không bao giờ tối ưu hóa cho hiệu suất trước khi bạn cần.
gcb

@gcb: Tại sao không? Điều gì xảy ra nếu bạn đang mong đợi một kịch bản thông lượng cao (ví dụ vì nhu cầu cao)? Tại sao không triển khai một cái gì đó mà bạn mong đợi sẽ ít tốn tài nguyên hơn từ việc di chuyển trong kịch bản đó?
Hassan Baig

1
@HassanBaig nếu bạn đã có yêu cầu về hiệu suất, thì nó không thực sự tối ưu hóa sớm, phải không? Quan điểm của tôi là khi bạn không cần phải ám ảnh về hiệu suất, nó luôn luôn tốt hơn để nhắm đến khả năng đọc.
gcb

14

Tương tự như các giải pháp trước, nhưng cụ thể hơn: thay thế hai hoặc nhiều khoảng trắng bằng một:

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

11

Một linh hồn đơn giản

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

6

Bạn cũng có thể sử dụng kỹ thuật tách chuỗi trong Khung dữ liệu Pandas mà không cần sử dụng .apply (..), rất hữu ích nếu bạn cần thực hiện thao tác nhanh chóng trên một số lượng lớn chuỗi. Đây là một dòng:

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

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

Điều này sẽ loại bỏ tất cả các tab, dòng mới và nhiều khoảng trắng với khoảng trắng đơn.


Nhưng nếu bạn có các ký tự khoảng trắng (không in được) không nằm trong phạm vi của bạn như '\ x00' đến '\ x0020' thì mã sẽ không loại bỏ chúng.
Muskovets

5

Tôi đã thử phương pháp sau và thậm chí nó còn hoạt động với trường hợp cực đoan như:

str1='          I   live    on    earth           '

' '.join(str1.split())

Nhưng nếu bạn thích một biểu thức chính quy, nó có thể được thực hiện như sau:

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

Mặc dù một số tiền xử lý phải được thực hiện để loại bỏ dấu cách và không gian kết thúc.


3

Điều này cũng có vẻ hoạt động:

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

Trong đó biến sđại diện cho chuỗi của bạn.


2

Trong một số trường hợp, mong muốn thay thế các lần xuất hiện liên tiếp của mọi ký tự khoảng trắng bằng một thể hiện duy nhất của ký tự đó . Bạn sẽ sử dụng một biểu thức chính quy với phản hồi để làm điều đó.

(\s)\1{1,}phù hợp với bất kỳ ký tự khoảng trắng, theo sau là một hoặc nhiều lần xuất hiện của ký tự đó. Bây giờ, tất cả những gì bạn cần làm là chỉ định nhóm đầu tiên (\1 ) làm thay thế cho trận đấu.

Bao bọc điều này trong một chức năng:

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

Một cách khác:

>>> 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

Một dòng mã để loại bỏ tất cả các khoảng trắng thừa trước, sau và trong câu:

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

Giải trình:

  1. Chia toàn bộ chuỗi thành một danh sách.
  2. Lọc các phần tử trống từ danh sách.
  3. Tham gia lại các yếu tố còn lại * với một khoảng trắng

* Các yếu tố còn lại phải là từ hoặc từ có dấu chấm câu, v.v. Tôi đã không kiểm tra rộng rãi điều này, nhưng đây sẽ là điểm khởi đầu tốt. Tất cả là tốt nhất!


2

Giải pháp cho nhà phát triển Python:

import re

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

Đầu ra:
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

Tốc độ nhanh nhất bạn có thể nhận được cho các chuỗi do người dùng tạo là:

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

Việc đoản mạch làm cho nó nhanh hơn một chút so với câu trả lời toàn diện của pythonlarry . Hãy làm điều này nếu bạn đang theo đuổi hiệu quả và đang nghiêm túc tìm cách loại bỏ các khoảng trắng thừa của nhiều loại không gian duy nhất .


1

Khá ngạc nhiên - không ai đăng chức năng đơn giản sẽ nhanh hơn nhiều so với TẤT CẢ các giải pháp được đăng khác. Nó đi từ đây:

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)

Kết quả :

Đây là một chuỗi đầy không gian và vòi


0

Để xóa khoảng trắng, hãy xem xét hàng đầu, dấu và thêm khoảng trắng ở giữa các từ, sử dụng:

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

Các orgiao dịch đầu tiên liên quan đến không gian trắng hàng đầu, orgiao dịch thứ hai bắt đầu chuỗi trắng hàng đầu và giao dịch cuối cùng liên quan đến không gian trắng kéo dài.

Để chứng minh việc sử dụng, liên kết này sẽ cung cấp cho bạn một bài kiểm tra.

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

Điều này được sử dụng với chức năng re.split .


0

Tôi có phương pháp đơn giản mà tôi đã sử dụng ở trường đại học.

line = "I     have            a       nice    day."

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

Điều này sẽ thay thế mọi không gian đôi bằng một không gian duy nhất và sẽ thực hiện 1000 lần. Nó có nghĩa là bạn có thể có thêm 2000 không gian và vẫn sẽ hoạt động. :)


Đây là (thực tế) giống hệt với câu trả lời của Anakimi (được đăng hơn hai năm trước).
Peter Mortensen

0

Tôi đã có một phương pháp đơn giản mà không cần tách:

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
Điều này khác với câu trả lời của Anakimi như thế nào (được đăng hơn ba năm trước)? Nó không phải là một phiên bản phức tạp hơn sao?
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='') 

Kết quả:

Xóa tất cả không gian: Youcanselectbelowtrimsforremovingwhitespace !! BRAliakbar Xóa không gian hàng đầu: Bạn có thể chọn bên dưới để xóa khoảng trắng !! BR Aliakbar
Xóa các khoảng trắng ở cuối: Bạn có thể chọn các viền bên dưới để xóa khoảng trắng !! BR Aliakbar Xóa các khoảng trắng ở đầu và cuối: Bạn có thể chọn các ô bên dưới để xóa khoảng trắng !! BR Aliakbar Xóa nhiều hơn một khoảng trắng: Bạn có thể chọn các viền bên dưới để xóa khoảng trắng !! BR Aliakbar


-1

Tôi chưa đọc nhiều vào các ví dụ khác, nhưng tôi vừa tạo phương thức này để hợp nhất nhiều ký tự không gian liên tiếp.

Nó không sử dụng bất kỳ thư viện nào và trong khi nó tương đối dài về độ dài tập lệnh, thì nó không phải là một triển khai phức tạp:

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)))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.