Jak powtórzyć ciąg zmienną liczbę razy w C ++?


127

Chcę wstawić spacje „n” (lub dowolny ciąg) na początku ciągu w C ++. Czy istnieje bezpośredni sposób na zrobienie tego przy użyciu ciągów std :: strings lub char *?

Np. W Pythonie możesz to po prostu zrobić

>>> "." * 5 + "lolcat"
'.....lolcat'
c++ 

Ktoś udzieli odpowiedzi za pomocą QString?
Akiva

Odpowiedzi:



39

Nie ma bezpośredniego idiomatycznego sposobu na powtarzanie ciągów w C ++ jako odpowiednik operatora * w Pythonie lub operatora x w Perlu. Jeśli powtarzasz pojedynczy znak, konstruktor dwuargumentowy (jak sugerowały poprzednie odpowiedzi) działa dobrze:

std::string(5, '.')

To jest wymyślony przykład tego, jak można użyć strumienia ostringstream do powtórzenia ciągu n razy:

#include <sstream>

std::string repeat(int n) {
    std::ostringstream os;
    for(int i = 0; i < n; i++)
        os << "repeat";
    return os.str();
}

W zależności od implementacji może to być nieco bardziej wydajne niż zwykłe konkatenowanie łańcucha n razy.


17

Użyj jednej z form string :: insert:

std::string str("lolcat");
str.insert(0, 5, '.');

Spowoduje to wstawienie „.....” (pięć kropek) na początku ciągu (pozycja 0).


15
OP poprosił o powtórzenie łańcucha, a nie znaku.
Brent

@Brent OP zażądał obu - „n” spacji (lub dowolnego ciągu) ”, a następnie przejdzie do zademonstrowania ich intencji za pomocą pojedynczej kropki jako ciągu. Angielski nie jest pierwszym językiem wielu ludzi, więc czasami trzeba odgadnąć ich dokładne wymagania, a po analizie pytanie brzmi tak naprawdę, jak to zrobić za pomocą jednej postaci. Przykro mi, że moja odpowiedź okazała się na tyle nieprzydatna, że ​​musiałeś ją przegłosować.
camh

13

Wiem, że to stare pytanie, ale szukałem tego samego i znalazłem prostsze rozwiązanie. Wygląda na to, że cout ma tę funkcję wbudowaną w cout.fill (), zobacz link do pełnego wyjaśnienia

http://www.java-samples.com/showtutorial.php?tutorialid=458

cout.width(11);
cout.fill('.');
cout << "lolcat" << endl;

wyjścia

.....lolcat

6
tylko kropki: zmień ostatnią linię na ...cout << "" << endl;
musefan

9

Dla celów przykładu dostarczonych przez OP std :: konstruktor struny jest wystarczająca: std::string(5, '.'). Jeśli jednak ktoś szuka funkcji do wielokrotnego powtarzania std :: string:

std::string repeat(const std::string& input, unsigned num)
{
    std::string ret;
    ret.reserve(input.size() * num);
    while (num--)
        ret += input;
    return ret;
}

8

Jak wspomniał Commodore Jaeger, nie sądzę, aby żadna z pozostałych odpowiedzi faktycznie odpowiadała na to pytanie; pytanie dotyczy tego, jak powtórzyć ciąg, a nie znak.

Chociaż odpowiedź udzielona przez Commodore jest poprawna, jest dość nieefektywna. Oto szybsza implementacja, chodzi o zminimalizowanie operacji kopiowania i alokacji pamięci przez pierwsze wykładnicze zwiększenie ciągu:

#include <string>
#include <cstddef>

std::string repeat(std::string str, const std::size_t n)
{
    if (n == 0) {
        str.clear();
        str.shrink_to_fit();
        return str;
    } else if (n == 1 || str.empty()) {
        return str;
    }
    const auto period = str.size();
    if (period == 1) {
        str.append(n - 1, str.front());
        return str;
    }
    str.reserve(period * n);
    std::size_t m {2};
    for (; m < n; m *= 2) str += str;
    str.append(str.c_str(), (n - (m / 2)) * period);
    return str;
}

Możemy również zdefiniować an, operator*aby uzyskać coś bliższego wersji Pythona:

#include <utility>

std::string operator*(std::string str, std::size_t n)
{
    return repeat(std::move(str), n);
}

Na moim komputerze jest to około 10x szybsze niż implementacja podana przez Commodore i około 2x szybsze niż naiwne rozwiązanie typu „dodaj n - 1 razy” .


Twoja implementacja nie „minimalizuje kopiowania”. Pamiętaj, że +=wewnątrz twojej pętli for również jest coś w rodzaju pętli, która wykonuje str.size()iteracje. str.size()rośnie w każdej iteracji pętli zewnętrznej, więc po każdej iteracji zewnętrznej pętla wewnętrzna musi wykonać więcej iteracji. Twoja i naiwna implementacja „kopiuj n razy” w sumie oba n * periodznaki kopiowania . Twoja implementacja wykonuje tylko jedną alokację pamięci z powodu początkowej reserve. Chyba sprofilowałeś swoją implementację na raczej małą stri dużą n, ale nie też na dużą stri małą n.
Florian Kaufmann

@FlorianKaufmann Nie jestem pewien, dlaczego zdecydowałeś się zaatakować moją odpowiedź. Ale przez „zminimalizowanie kopiowania” mam na myśli „operacje kopiowania”. Chodzi o to, że kopiowanie małej liczby dużych bloków jest bardziej wydajne (z różnych powodów) niż kopiowanie dużej liczby małych bloków. Potencjalnie unikam dodatkowej alokacji ciągu wejściowego zamiast naiwnej metody.
Daniel

2
To był komentarz stwierdzający, że nie wierzę w twoje twierdzenie, że twoje rozwiązanie jest znacznie lepsze pod względem wydajności niż rozwiązanie naiwne. W moich pomiarach, w porównaniu do naiwnego rozwiązania, twój kod jest szybszy z małymi ciągami i wieloma powtórzeniami, ale wolniejszy z długimi ciągami i kilkoma powtórzeniami. Czy możesz podać linki wyjaśniające bardziej szczegółowo różne powody, dla których kopiowanie kilku dużych bloków ma wyższą wydajność niż kopiowanie wielu małych bloków? Przychodzi mi na myśl przewidywanie gałęzi. Jeśli chodzi o pamięć podręczną procesora, nie jestem pewien, który wariant jest preferowany.
Florian Kaufmann

@FlorianKaufmann Nie widzę znaczącej różnicy między dużymi stri małymi npodejściami. Uważam, że jest to bardziej związane z ogólnym potokiem niż z prognozowaniem gałęzi jako takim, należy również rozważyć kwestie związane z dopasowaniem danych . Powinieneś zadać nowe pytanie, aby wyjaśnić, dlaczego jest to bardziej przyjazne dla procesora / pamięci, jestem pewien, że zyskałoby duże zainteresowanie i otrzymał lepszą odpowiedź niż mogę udzielić tutaj.
Daniel

1
@FlorianKaufmann: Na x86, rep movsbjest jednym z najbardziej wydajnych sposobów kopiowania, przynajmniej w przypadku średnich i dużych kopii. Jego implementacja z mikrokodowaniem ma prawie stałe obciążenie rozruchowe (zarówno na AMD, jak i na Intelu), np. Na Sandybridge, ~ 15 do 40 cykli plus 4 cykle na linię pamięci podręcznej 64B (w najlepszym przypadku) . W przypadku małych kopii pętla SSE jest najlepsza, ponieważ nie ma narzutu związanego z uruchamianiem. Ale wtedy jest to uzależnione od błędnych prognoz branży.
Peter Cordes


5

ITNOA

Możesz do tego użyć funkcji C ++.

 std::string repeat(const std::string& input, size_t num)
 {
    std::ostringstream os;
    std::fill_n(std::ostream_iterator<std::string>(os), num, input);
    return os.str();
 }

1
Co u licha oznacza „ITNOA”? Nie mogę znaleźć żadnego odniesienia do tego w Internecie.
Folling
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.