Split

2010-07-02 17:44

Tytuł dzisiejszej notki nie ma nic wspólnego z miejscowością wypoczynkową na południu Chorwacji, chociaż pewnie obecne temperatury nasuwają takie skojarzenia :) Zamiast tego chodzi o split łańcucha znaków, czyli bardzo często potrzebną w praktyce operację na stringach.
Danymi dla niej są najczęściej dwa napisy, zaś wynikiem jest tablica podciągów pierwszego z nich, powstała poprzez podzielenie go względem wystąpień drugiego (tzw. separatora). Ponieważ jak zwykle przykład będzie mówił najwięcej, niniejszym podaję nawet kilka:

  1. Split("Ala ma kota", " ") == { "Ala", "ma", "kota" };
  2. Split("1, 2, 3, 4", ",") == { "1", " 2", " 3", "4" };
  3. Split("<point x='23' y='14' z='-3' />", " ")
  4.     == { "<point", "x='23'", "y='14'", "z='-3'", "/>" }

Zwłaszcza ostatni pokazuje, że split jest rzeczywiście użyteczną funkcją, mogącą ułatwiać parsowanie formatów tekstowych (zwłaszcza, jeśli daje się ją zastosować wielokrotnie). Warto więc mieć takową w swoim języku/bibliotece. Jak więc przedstawia się jej dostępność na różnych platformach?

Tradycyjnie w .NET i Javie jest dobrze, a nawet lepiej. W obu przypadkach funkcja (a właściwie metoda) Split/split klasy String dodaje nawet trochę więcej możliwości niż to opisałem wyżej. I tak w .NET można podać więcej niż jeden oddzielacz, natomiast w Javie domyślnie może być nim również wyrażenie regularne.
Niektóre języki skryptowe i skryptopodobne też mają się w tym względnie całkiem dobrze. W Pythonie jest metoda split w klasie napisu, natomiast PHP ma funkcję explode, która mimo innej nazwy działa bardzo podobnie.

Ale nie wszędzie funkcja typu split jest od razu dostępna; niekiedy trzeba ją sobie samemu napisać. Przykładem języka, gdzie może być to koniecznie, jest Lua oraz – jakżeby inaczej – C++ :) Ze względu na użyteczność splita często znajdowałem się w sytuacji, gdzie konieczne/wygodne było jego napisanie. Po kilku(nastu?) takich przypadkach doszedłem wreszcie do czegoś podobnego do poniższego kodu:
typedef std::vector StringArray;
StringArray Split(const std::string& text, const std::string& delim)
{
StringArray res;
if (delim.empty()) { res.push_back(text); return res; }

std::string::size_type i = 0, j;
while (i < text.length() && (j = text.find(delim, i)) != std::string::npos) { res.push_back (text.substr(i, j - i)); i = j + delim.length(); } res.push_back (text.substr(i)); return res; }[/cpp] Na koniec zwrócę jeszcze uwagę na to, że czasami trzeba ostrożnie postępować z rezultatem splitu. Zdecydowana większość wersji tej operacji dopuszcza, by w wynikowej tablicy występowały puste ciągi. Odpowiadają one kilku kolejnym wystąpieniom separatora lub jego obecnością na początku bądź końcu ciągu. Jeśli nie są one nam potrzebne (a rzadko są), to należy je zignorować lub usunąć.

Tags: , , , , ,
Author: Xion, posted under Programming »


5 comments for post “Split”.
  1. Liosan:
    July 2nd, 2010 o 19:52

    Operując na C-stringach, często (oczywiście nie zawsze) przydaje się split in-situ – czyli nie kopiujemy zawartości tablicy znaków, a jedynie zastępujemy wszystkie wystąpienia delimitera :) No i jeszcze zwykle zwracam tablicę wskaźników tychże stringów, no ale to raczej dodatek.

  2. moriturius:
    July 3rd, 2010 o 10:06

    Jeśli chodzi o końcową uwagę o dwóch delimiterach pod rząd generujących puste stringi to w Javie jest fajnie bo można ustawić regex jako delimiter i ja zazwyczaj robie coś w stylu: “\s+”.
    No i zanim splituje string to jeszcze trimm() na nim i wówczas mam pewność, że nie ma pustych elementów.

    Zawsze mnie jednak zastanawiało czy zaprzęganie wyrażeń regularnych do tego to nie jest przypadkiem armata na muchę…

  3. lmmilewski:
    July 4th, 2010 o 17:03

    W C++ są dwa standardowe sposoby, których ja używam

    Pierwszy łatwo przewidzieć – boost http://www.boost.org/doc/libs/1_43_0/doc/html/string_algo/usage.html#id
    1761650

    Drugi jest ciekawszy, ale działa tylko w przypadku prostych delimiterów. Można stworzyć stringstream z łańcucha i wykorzystać funkcję getline :-)

    W Lua można zrobić split przy pomocy string.gsub np. tak:
    string.gsub(“witaj swiecie”, “[^%s]+”, function(x) print(x) end);

    Funkcję z ostatniego warunku warto zrobić jako local przed wywołaniem gsub ze względów wydajnościowych

  4. Reg:
    July 4th, 2010 o 19:35

    Łojej, jak możesz zwracać z funkcji coś takiego jak wektor stringów przez wartość! Przecież to angażuje dodatkowe kopiowanie całego wektora, co najmniej raz!!!

    Sama idea Split jest słuszna i fajnie, że zwróciłeś uwagę na to zagadnienie. Przydałaby się tylko jeszcze wersja inkrementacyjna, która zamiast budować wektor stringów to z kolejnymi wywołaniami jakiejś funkcji czy metody zwracałaby kolejne stringi.

  5. Xion:
    July 4th, 2010 o 20:53

    Czasami aż się zastanawiam, co jest takiego trudnego w zaimplementowaniu RVO w tak oczywistych przypadkach… No ale co ja mogę wiedzieć o budowie kompilatorów :)

    Co do funkcji inkrementacyjnej, to strtok() ze standardowej biblioteki C robi właśnie coś takiego, tyle że ograniczeniem jest jednoznakowy separator (ale za to można podać kilka separatorów naraz).

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.