Oddzielanie liczb od tekstu, czyli jak znaleść liczby w tablicy char.

0

Witam,
mam tekst w tablicy char. Tekst zawiera liczby (mogą być ułamki dziesiętne). Czy da się coś zrobić, żeby program znalazł mi te liczby i każdą z osobna zapisał do zmiennej typu float?
Tak mniej więcej wygląda tekst:

v 0.04356 6.3245325 8.3245

I chcę te trzy liczby zapisać do zmiennych.
Z góry dziękuję.

0

Da się coś zrobić. Poczytaj o stringstream

1
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <clocale>

void alhpa_to_space( char & c )
{
    if ( ::isalpha( c ) ) {
        c = ' ';
    }
}

int main()
{
    std::string line = "v 0.04356 6.3245325 8.3245";
    std::for_each(line.begin(), line.end(), alhpa_to_space);

    std::stringstream stream(line);
    std::vector<float> nums;

    std::copy(std::istream_iterator<float>(stream), std::istream_iterator<float>(),
              std::back_inserter(nums));

    std::copy(nums.begin(), nums.end(),
              std::ostream_iterator<float> (std::cout, "; "));

    return 0;
}

W razie wątpliwości pytaj.

0
code_killer napisał(a)
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <clocale>

void alhpa_to_space( char & c )
{
    if ( ::isalpha( c ) ) {
        c = ' ';
    }
}

int main()
{
    std::string line = "v 0.04356 6.3245325 8.3245";
    std::for_each(line.begin(), line.end(), alhpa_to_space);

    std::stringstream stream(line);
    std::vector<float> nums;

    std::copy(std::istream_iterator<float>(stream), std::istream_iterator<float>(),
              std::back_inserter(nums));

    std::copy(nums.begin(), nums.end(),
              std::ostream_iterator<float> (std::cout, "; "));

    return 0;
}

W razie wątpliwości pytaj.

Na mam wątpliwości :)
Przyznaję, nie rozumiem tego kodu. Byłbym niezwykle wdzięczny gdyby ktoś krótko skomentował to, co w tym kodzie się dzieje.
Te oddzielone liczby chcę zapisać do zmiennych typu float.
Pozdrawiam!

1
  1. stworzył stringa który wygląda tak jak twój
  2. dla każdego znaku który jest literą zmienił go na spację -> ' '
  3. stworzył kontener stringstream z STL
  4. za pośrednictwem funkcji copy iterował po każdej pozostałej liczbie pobieracjąc ją i zapisująć do kontenera typu vector<float>
  5. wypisał wszystkie pobrane liczby które znajdowały się w tym kodzie na ekran
0
MJay napisał(a)
  1. stworzył stringa który wygląda tak jak twój
  2. dla każdego znaku który jest literą zmienił go na spację -> ' '
  3. stworzył kontener stringstream z STL
  4. za pośrednictwem funkcji copy iterował po każdej pozostałej liczbie pobieracjąc ją i zapisująć do kontenera typu vector<float>
  5. wypisał wszystkie pobrane liczby które znajdowały się w tym kodzie na ekran

Dobra, rozumiem, jednak nadal nie rozumiem jak tą liczbę wstawić do zmiennej ;)

0

Może mnóż każdą cyfrę przez 10^(-x), gdzie x oznacza miejsce na którym ma stać cyfra i zsumuj je wszystkie?

1

Może ujmnę to tak, pewnie oczekujesz, że efekcie końcowym wartości zostaną zapisane do zmiennych mniej więcej tak:

1. float value_0 = 0.43.. ;
2. float value_1 = 6.32.. ;
3. float value_2 = 8.32.. ;

Brzmi to kusząco, ale zauważ, że pisząc tak, z góry zakładasz ile powstanie wyników.
Pytanie: Co jesli twoja linia zawierać będzie nie 3 wartości a właśnie 2 lub 5?

Możesz oczywiście utworzyć tablicę:

float values[100] = { 0.0f };
// ...
values[0] = 0.43..;
values[1] = 6.32..;
values[2] = 8.32..;

Dzięki czemu masz możliwość dopisania wartości dla elementu czwartego, czy piątego (gdzie pozostałe elementy mają wartość domyślna czyli 0.0f).

values[3] = 42.43..;
values[4] = 9.32..;

Ale wtedy i tak problem nie zostanie rozwiązany, bo program się wyłoży, gdy w lini znajdzie się 101 wartości.

values[100] = 13.0f; // wyjście poza zakres tablicy - skok na główę do pustego basenu :-/

A na dodatek przez większość czasu program będzie zużywał więcej pamieci - np. gdy inny user będzie wprowadzał po 2-3 wartości w lini. Wtedy pozostałe 98-97 pól tablicy będzie niewykorzystane. Tego typu rozwiązanie jest nieoptymalne, dlatego warto zainteresować się wektorem, który zachowuje się jak tablica, która w razie zaistaniałych potrzeb wykonuje dodatkowa allokacja pamięci.

Aby odwołać się do pierwszej wartości wystarczy, że zrobisz np. tak:

std::cout << nums[0] << std::endl;
0

Sorry, że odświeżam ale mam jeszcze jeden problem: czy da się jakoś sprawdzić ile tych liczb zostało odczytanych?

0

Jeśli używasz kodu @code_killer'a to się da. Po prostu sprawdzasz wielkość wektora.

1

Wygląda jak parsowanie obj :D

Dalej szalejesz z OpenGL :) ?

Masz mój stary kod:

Plik jWfObj.h

#ifndef __JWFOBJ_H
#define __JWFOBJ_H
#include <fstream>
#include <GL/glu.h>
#include <GL/gl.h>
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>

using namespace std;
class WfObjFile
{
	public:
		void open (string filename);
		~WfObjFile();
		struct Vertex3d
		{
			double x,y,z;
		};
		struct Vertex2d
		{
			double x,y;
		};
		struct Face
		{
			Vertex3d v[3];
			Vertex2d t[3];
			Vertex3d n[3];
		};
		void glRender();
		unsigned int vl,vnl,vtl,fl;
		int GetM() {return m;};
		Face *faces;
	private:
		Vertex3d *v,*vn;
		Vertex2d *vt;
		int i,j,k,l,m,t;
		ifstream plik;
		string linia,temp;
		GLuint list;
};
#endif

Plik jWfObj.cpp

#include "jWfObj.h"

void WfObjFile::open(string filename)
{
    plik.open(filename.c_str());
	if (plik.is_open())
	{
	    static GLuint lis;
        list=++lis;
        //// sprawdzamy ile linijek zaczyna sie od potrzebnych nam naglowkow
        //// aby wiedziec ile pamieci zaalokowac
        vl=vnl=vtl=fl=0;
        while (!plik.eof())
        {
            plik >> linia;
            if (linia=="v") vl++;
            else if (linia=="vn") vnl++;
            else if (linia=="vt") vtl++;
            else if (linia=="f") fl++;
            getline(plik,linia);
        }
        plik.clear();
        plik.seekg(0, ios::beg);
        v=new Vertex3d[vl];
        vn=new Vertex3d[vnl];
        vt=new Vertex2d[vtl];
        faces=new Face[fl];
        /////-------------------------------------------------------------
        i=j=k=m=t=0;
        ///// teraz nastepuje wlasciwe wczytywanie pliku *.obj
        while (!plik.eof())
        {
            plik >> linia;
            if (linia=="#") continue;
            else if (linia=="v")
            {
                plik >> (v+i)->x >> (v+i)->y >> (v+i)->z;
                i++;
            }
            else if (linia=="vn")
            {
                plik >> (vn+j)->x >> (vn+j)->y >> (vn+j)->z;
                j++;
            }
            else if (linia=="vt")
            {
                plik >> (vt+k)->x >> (vt+k)->y;
                k++;
            }

            else if (linia=="f")
            {
                getline(plik,linia);
                temp="";
                l=0;
                for (unsigned int i=1;i<=linia.length();i++)
                {
                    if (linia.c_str()[i]>='0'&&linia.c_str()[i]<='9')
                        temp+=linia.c_str()[i];
                    else if (linia.c_str()[i]=='/')
                    {
                        l++;
                        t=atoi(temp.c_str())-1;
                        temp="";
                        if (l==1)
                        {
                            (faces+m)->v[0].x=(v+t)->x;
                            (faces+m)->v[0].y=(v+t)->y;
                            (faces+m)->v[0].z=(v+t)->z;
                        }
                        else if (l==2)
                        {
                            (faces+m)->t[0].x=(vt+t)->x;
                            (faces+m)->t[0].y=(vt+t)->y;
                        }
                        else if (l==4)
                        {
                            (faces+m)->v[1].x=(v+t)->x;
                            (faces+m)->v[1].y=(v+t)->y;
                            (faces+m)->v[1].z=(v+t)->z;
                        }
                        else if (l==5)
                        {
                            (faces+m)->t[1].x=(vt+t)->x;
                            (faces+m)->t[1].y=(vt+t)->y;
                        }
                        else if (l==7)
                        {
                            (faces+m)->v[2].x=(v+t)->x;
                            (faces+m)->v[2].y=(v+t)->y;
                            (faces+m)->v[2].z=(v+t)->z;
                        }
                        else if (l==8)
                        {
                            (faces+m)->t[2].x=(vt+t)->x;
                            (faces+m)->t[2].y=(vt+t)->y;
                        }
                    }
                    else
                    {
                        l++;
                        t=atoi(temp.c_str())-1;
                        temp="";
                        bool bul=false;
                        for (int ind=1;ind<=3;ind++)
                        if (l==ind*3)
                        {
                            (faces+m)->n[ind-1].x=(vn+t)->x;
                            (faces+m)->n[ind-1].y=(vn+t)->y;
                            (faces+m)->n[ind-1].z=(vn+t)->z;
                            if (ind==3)
                            {
                                m++;
                                bul=true;
                            }
                            if (bul) break;
                        }
                    }
                }
            }

        }
        /////------------------------------------------------------------
        plik.close();
        // zwalniamy pamiec po tablicach tymczasowych (faces zostawiamy,
        // bo przyda sie do renderowania :)
        delete [] v;
        delete [] vn;
        delete [] vt;
        glNewList(list,GL_COMPILE);
        glBegin(GL_TRIANGLES);
        for (int i=0;i<m;i++)
        {
            glNormal3d((faces+i)->n[0].x,(faces+i)->n[0].y,(faces+i)->n[0].z);
            glTexCoord2d((faces+i)->t[0].x,(faces+i)->t[0].y);
            glVertex3d((faces+i)->v[0].x,(faces+i)->v[0].y,(faces+i)->v[0].z);
            glNormal3d((faces+i)->n[1].x,(faces+i)->n[1].y,(faces+i)->n[1].z);
            glTexCoord2d((faces+i)->t[1].x,(faces+i)->t[1].y);
            glVertex3d((faces+i)->v[1].x,(faces+i)->v[1].y,(faces+i)->v[1].z);
            glNormal3d((faces+i)->n[2].x,(faces+i)->n[2].y,(faces+i)->n[2].z);
            glTexCoord2d((faces+i)->t[2].x,(faces+i)->t[2].y);
            glVertex3d((faces+i)->v[2].x,(faces+i)->v[2].y,(faces+i)->v[2].z);
        }
        glEnd();
        glEndList();
	}
	else cout << "Plik " << filename << " nie istnieje" << endl;
}
WfObjFile::~WfObjFile()
{
	delete [] faces;
}
void WfObjFile::glRender()
{
	glCallList(list);
}

Piękny nie jest - zdaję sobie sprawę, ale działa pod G++ :)

Ma to sporo ograniczeń, siatka to muszą być same trójkąty, musisz przy eksporcie zawsze oprócz vertexów eksportować koordynaty tekstur i wektory normalne. Klasa użyta w projekcie http://www.jason.gd/str/pokaz/Hand_Of_God - możesz tam sobie podpatrzeć jak wyglądają obj'ty obsługiwane przez klasę.

0

@up dzięki, ale zrobiłem sobie swój :)
Tylko nadal potrzebuję wiedzieć ile tych liczb z ciągu znaków pobrałem. Do tej pory zrobiłem to tak że leciałem co znak i sprawdzałem co to jest. Tylko że teraz są dłuższe liczby i mi nie odczytuje.
Więc, tradycyjnie potrzebuję helpa :/

0

Mam kolejny problem :/
Muszę teraz wczytać linijki typu to:

f 17/1 2/2 14/3

Tylko że problem jest taki, że skrypt znajduje tylko 2 pierwsze liczby :(

0

Użyj wyrażeń regularnych i masz sprawę jasną :D

0

Sorry, ale w jaki sposób? Co mi to da?
No bo mam taką funkcję:

 void alhpa_to_space( char & c )
{
    if ( ::isalpha( c ) ) {
        c = ' ';
    }
}

I mój problem polega na tym, co zrobić żeby on łapał spacje i te ukośniki ("/").
EDIT:
Tak też nie działa:

 void alhpa_to_space( char & c )
{
    if ( ::isalpha( c ) ) {
        c = ' ';
    }
    else if ( ::isalpha( c ) ) {
        c = '/';
    }
}
</del> Jezu co ja popisałem. EDIT2: Albo chociaż powiedzcie mi dzięki czemu w tym kodzie: ```cpp void alhpa_to_space( char & c ) { if ( ::isalpha( c ) ) { c = ' '; } }

float znajdzliczbe(std::string line, int zwroc){
/*
oddziela liczby od tekstu i zwraca je.
zmienna wroc zawiera numer liczby ktora chcemy pobrac
*/
std::for_each(line.begin(), line.end(), alhpa_to_space);
std::stringstream stream(line);
std::vector<float> nums;
std::copy(std::istream_iterator<float>(stream), std::istream_iterator<float>(),
std::back_inserter(nums));
//std::cout << line << " Il elem: "<< nums.size() <<std::endl;
if(zwroc == -1){
std::cout << nums.size() << std::endl;
return nums.size();
}
else {
std::cout << nums[2] << std::endl;
return nums[zwroc];
}
}

Kompilator wie, że liczby oddzielone są spacją...
1

O obiektach typu stringstream staraj się myśleć niemal identycznie jak o instancji cin. Zauważ, że mając kod:

float a;
float b;
std::cin >> a >> b;

User może wprowadzić tekst "10 30 ".
Lecz nie ma obaw, bo strumień po prostu pomija białe znaki i zwraca dane skonwertowane na oczekiwany typ.

Co do twojego problemu to poniższy kod powinien ci pomóc:

#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <clocale>

void alpha_to_space( char & c )
{
    if ( ::isalpha( c ) ) {
        c = ' ';
    }
}

bool contains( const std::string & s, char c )
{
    for ( int i = 0; i < s.size(); ++i ) {
        if ( s[ i ] == c ) {
            return true;
        }
    }
    return false;
}

class Translator
{
public:
    Translator( const std::string & incorrect_chars_, char correct_char_ ) :
        incorrect_chars( incorrect_chars_ ), correct_char( correct_char_ ) {}

    void operator()( char & c ) {
        c = contains( incorrect_chars, c ) ? correct_char : c;
    }

private:
    std::string incorrect_chars;
    char correct_char;
};

int main()
{
    std::string line = "v /0.04356/ 6.3245325 8.3245";

    std::for_each(line.begin(), line.end(), alpha_to_space );
    std::for_each(line.begin(), line.end(), Translator(" /", ' '));

    std::stringstream stream(line);
    std::vector<float> nums;

    std::copy(std::istream_iterator<float>(stream), std::istream_iterator<float>(),
              std::back_inserter(nums));

    std::copy(nums.begin(), nums.end(),
              std::ostream_iterator<float> (std::cout, "; "));

    return 0;
}

0
St4rKiller070 napisał(a)

Sorry, ale w jaki sposób? Co mi to da?

Nie wiem jak biblioteka standardowa C++ stoi z wyrażeniami regularnymi, ale zawsze możesz do tego użyć zewnętrznej biblioteki :)

Wtedy cała zabawa w parsowanie pliku obj będzie się sprowadzała do sformułowania odpowiedniego wyrażenia regularnego, które Ci wyciągnie dane zgodne z wyrażeniem (wzorcem).
Np. korzystając z modułu re w Pythonie mógłbym zrobić takie wyrażenie:
wyrazenie="f ([0-9]*)/([0-9]*)/([0-9]*) ([0-9]*)/([0-9]*)/([0-9]*) ([0-9]*)/([0-9]*)/([0-9]*)"

W taki sposób je wykorzystać w skrypcie

import re
plik_obj="""# Blender3D v249 OBJ File: 
# www.blender3d.org
mtllib cube.mtl
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vn 0.000000 0.000000 -1.000000
vn -1.000000 -0.000000 -0.000000
vn -0.000000 -0.000000 1.000000
vn -0.000001 0.000000 1.000000
vn 1.000000 -0.000000 0.000000
vn 1.000000 0.000000 0.000001
vn 0.000000 1.000000 -0.000000
vn -0.000000 -1.000000 0.000000
usemtl Material
s off
f 5/1/1 1/2/1 8/3/1
f 1/2/1 4/4/1 8/3/1
f 3/1/2 7/2/2 8/4/2
f 3/1/2 8/4/2 4/3/2
f 2/1/3 6/2/3 3/3/3
f 6/2/4 7/4/4 3/3/4
f 1/1/5 5/2/5 2/3/5
f 5/2/6 6/4/6 2/3/6
f 5/1/7 8/2/7 7/4/7
f 5/1/7 7/4/7 6/3/7
f 1/1/8 2/2/8 3/4/8
f 1/1/8 3/4/8 4/3/8
"""
wyrazenie="f ([0-9]*)/([0-9]*)/([0-9]*) ([0-9]*)/([0-9]*)/([0-9]*) ([0-9]*)/([0-9]*)/([0-9]*)"
fejsy=re.findall(wyrazenie, plik_obj,re.U|re.I|re.M)
print fejsy

No i dostanę wynik:

[('5', '1', '1', '1', '2', '1', '8', '3', '1'), ('1', '2', '1', '4', '4', '1', '8', '3', '1'), ('3', '1', '2', '7', '2', '2', '8', '4', '2'), ('3', '1', '2', '8', '4', '2', '4', '3', '2'), ('2', '1', '3', '6', '2', '3', '3', '3', '3'), ('6', '2', '4', '7', '4', '4', '3', '3', '4'), ('1', '1', '5', '5', '2', '5', '2', '3', '5'), ('5', '2', '6', '6', '4', '6', '2', '3', '6'), ('5', '1', '7', '8', '2', '7', '7', '4', '7'), ('5', '1', '7', '7', '4', '7', '6', '3', '7'), ('1', '1', '8', '2', '2', '8', '3', '4', '8'), ('1', '1', '8', '3', '4', '8', '4', '3', '8')]

Można napisać zaawansowane wyrażenie, które się dopasowuje do ilości wierzchołków każdego face'a. Albo też można sprawdzać kilkoma wyrażeniami po kolei - dla czworokątów, trójkątów itd. po prostu będzie inna ilość takich zapisów: ([0-9])/([0-9])/([0-9]*)
Jednak jak się dobrze napisze wyrażenie (np. całą funkcjonalność zrobisz jednym wyrażeniem :D ) to można znacznie ograniczyć ilość kodu :)

ps. Ty eksportujesz bez koordynatów tekstur albo bez normalnych więc wyrażenie dla Twojego pliku obj to będzie: f ([0-9]*)/([0-9]*) ([0-9]*)/([0-9]*) ([0-9]*)/([0-9]*)

1 użytkowników online, w tym zalogowanych: 0, gości: 1