Konwertowanie ciągu na liczbę całkowitą bez użycia mnożenia C #

9

Czy istnieje sposób na konwersję ciągu na liczby całkowite bez użycia mnożenia. Implementacja int.Parse () również wykorzystuje mnożenie. Mam inne podobne pytania, w których można ręcznie przekonwertować ciąg na int, ale to także wymaga wielokrotnego pomnożenia liczby przez jej bazę 10. To było pytanie do wywiadu, które miałem w jednym z wywiadów i wydaje się, że nie mogę znaleźć na to odpowiedzi.

chaudryja
źródło
3
z (tekstem) czy bez (tytuł)?
d4zed
2
Czy możesz to wyjaśnić? W twoim tytule jest napisane „bez”, podczas gdy w tekście jest napisane „z” za pomocą ...
vonludi
1
Biorąc pod uwagę resztę treści pytania (np. „Ale wymaga to również pomnożenia liczby przez jej podstawę 10”), tytuł jest poprawny. Dodatkowo, jeśli dozwolone jest mnożenie, jest to banalne, więc nie ma sensu o to pytać.
John
7
ten artykuł sugeruje zastąpienie mnożenia przez dziesięć przesunięciami bitowymi i dodawaniem ( x<<1 + x<<3), ale wciąż jest to sposób na użycie mnożenia, więc trudno powiedzieć, czy to „w duchu” pytania ...
Simon MᶜKenzie
3
Nie for (int i = 0; i < 10; i++) result += number;liczy?
kanton7

Odpowiedzi:

12

Jeśli przyjmiesz system liczbowy liczby podstawowej 10 i podstawisz mnożenie przez przesunięcia bitów ( patrz tutaj ), może to być rozwiązanie dla dodatnich liczb całkowitych .

public int StringToInteger(string value)
{
    int number = 0;
    foreach (var character in value)
        number = (number << 1) + (number << 3) + (character - '0');

    return number;
}

Zobacz przykład na ideone .

Jedynym założeniem jest to, że postacie '0'do '9'leżą bezpośrednio obok siebie w zestawie znaków. Znaki cyfr są konwertowane na ich wartości całkowite za pomocą character - '0'.

Edytować:

W przypadku liczb całkowitych ujemnych ta wersja ( patrz tutaj ) działa.

public static int StringToInteger(string value)
{
    bool negative = false;
    int i = 0;

    if (value[0] == '-')
    {
        negative = true;
        ++i;
    }

    int number = 0;
    for (; i < value.Length; ++i)
    {
        var character = value[i];
        number = (number << 1) + (number << 3) + (character - '0');
    }

    if (negative)
        number = -number;
    return number;
}

Zasadniczo należy wziąć pod uwagę błędy, takie jak kontrole zerowe, problemy z innymi znakami nienumerycznymi itp.

Andre Kampling
źródło
6

To zależy. Czy mówimy o logicznej operacji mnożenia, czy o tym, jak to się faktycznie robi w sprzęcie?

Na przykład możesz przekonwertować ciąg szesnastkowy (lub ósemkowy lub dowolny inny mnożnik bazowy dwa) na liczbę całkowitą „bez mnożenia”. Możesz iść znak po znaku i zachować oring ( |) i bitshifting ( <<). Pozwala to uniknąć korzystania z *operatora.

Robienie tego samego z łańcuchami dziesiętnymi jest trudniejsze, ale nadal mamy prosty dodatek. Możesz użyć pętli z dodatkiem, aby zrobić to samo. Całkiem proste do zrobienia. Możesz też stworzyć własną „tabliczkę mnożenia” - mam nadzieję, że nauczyłeś się, jak pomnożyć liczby w szkole; możesz zrobić to samo z komputerem. I oczywiście, jeśli korzystasz z komputera dziesiętnego (zamiast binarnego), możesz wykonać „przesunięcie bitów”, podobnie jak w przypadku wcześniejszego ciągu szesnastkowego. Nawet w przypadku komputera binarnego można użyć szeregu przesunięć bitowych - (a << 1) + (a << 3)jest taki sam jak a * 2 + a * 8 == a * 10. Uważaj na liczby ujemne. Możesz wymyślić wiele sztuczek, aby uczynić to interesującym.

Oczywiście oba te elementy są tylko maskowaniem. Jest tak, ponieważ pozycyjne systemy numeryczne są z natury multiplikatywne . Tak działa ta konkretna reprezentacja numeryczna. Można mieć uproszczeń, które ukrywają ten fakt (tylko np liczb binarnych trzeba 0i 1tak zamiast mnożenia, można mieć prosty warunek - oczywiście, co tak naprawdę robi to nadal mnożenie, tylko przy użyciu tylko dwóch możliwych wejść i dwóch możliwych wyjść), ale zawsze tam jest, czai się. <<jest taki sam jak * 2, nawet jeśli sprzęt, który wykonuje operację, może być prostszy i / lub szybszy.

Aby całkowicie zrezygnować z mnożenia, należy unikać używania systemu pozycjonowania. Na przykład, cyfry rzymskie są addytywne (uwaga, że rzeczywiste cyframi rzymskimi nie używać zasad zwartym mamy dzisiaj - cztery byłoby IIIInie IV, i to czternaście może być napisany w jakiejkolwiek formie jak XIIII, IIIIX, IIXII, VVIIIIitd.). Konwersja takiego ciągu na liczbę całkowitą staje się bardzo łatwa - po prostu idź znak po znaku i dodawaj dalej. Jeśli postać jest X, dodaj dziesięć. Jeśli Vdodaj pięć. GdybyI, Dodaj jeden. Mam nadzieję, że zrozumiecie, dlaczego cyfry rzymskie były tak popularne; Pozycyjne systemy numeryczne są cudowne, gdy trzeba dużo powielać i dzielić. Jeśli zajmujesz się głównie dodawaniem i odejmowaniem, cyfry rzymskie działają świetnie i wymagają znacznie mniej nauki (a liczydło jest o wiele łatwiejsze do wykonania i użycia niż kalkulator pozycyjny!).

Przy takich zadaniach istnieje wiele trafień i oczekiwań dotyczących tego, czego faktycznie oczekuje ankieter. Może po prostu chcą zobaczyć twoje procesy myślowe. Czy akceptujesz specyfikacje techniczne ( <<tak naprawdę to nie mnożenie)? Czy znasz teorię liczb i informatykę? Czy po prostu zagłębiasz się w kod, czy prosisz o wyjaśnienia? Czy traktujesz to jako zabawne wyzwanie, czy może kolejne śmieszne, nudne pytanie, które nie ma związku z twoją pracą? Nie możemy powiedzieć ci odpowiedzi, której szukał ankieter.

Ale mam nadzieję, że przynajmniej rzuciłem okiem na możliwe odpowiedzi :)

Luaan
źródło
Jeśli użyjemy systemu cyfr rzymskich, jak byś dodał, gdybyśmy mieli znaki takie jak B, T, S, R, G, A itd., Które nie są częścią systemu cyfr rzymskich. Innymi słowy, jak uzyskać int int?
Adheesh
@Adheesh Nie mam pojęcia, o co chcesz zapytać. Czy możesz podać przykład problemu, który Twoim zdaniem byłby problemem?
Luaan
Załóżmy, że mamy ciąg „12345” i chcemy przekonwertować go na wartość int. Co jeśli nie chcę używać pozycyjnego systemu numerycznego, a zamiast tego używać rzymskiego systemu liczbowego. Jak możemy to zrobić? (Nie chcę w ogóle używać mnożenia)
Adheesh
@Adheesh W systemie cyfr rzymskich ciąg nie byłby „12345”. To o to chodzi. Pozycyjne systemy numeryczne są z natury multiplikatywne. System cyfr rzymskich jest addytywny. Ciąg rzymski byłby czymś w rodzaju „MMMMMMMMMMMCCCCXXXXIIIII”. Idź znak po znaku i kontynuuj dodawanie liczb.
Luaan
@Adheesh Oczywiście w rzeczywistości nie byłby to tak długi nieporęczny bałagan. Zamiast „12345 metrów” można powiedzieć coś w stylu „XII kilograma i CCCXXXXIIIII metrów” lub coś w tym rodzaju. Stare systemy pomiarowe były przystosowane do ludzi, którzy nie mogli wiele policzyć - po prostu porównaj zakres wartości, które możesz przedstawić za pomocą jednostek imperialnych, do nowoczesnych jednostek, jeśli możesz policzyć tylko do dwunastu :)
Luaan
5

Biorąc pod uwagę, że jest to pytanie do wywiadu, wydajność może nie mieć wysokiego priorytetu. Dlaczego nie tylko:

private int StringToInt(string value)
{
    for (int i = int.MinValue; i <= int.MaxValue; i++)
        if (i.ToString() == value)
            return i;
    return 0; // All code paths must return a value.
}

Jeśli przekazany ciąg nie jest liczbą całkowitą, metoda zgłosi wyjątek przepełnienia.

nl-x
źródło
1
Genialne: P Upewnij się, że nie używasz go, jeśli ankieter nie ma natychmiastowej informacji zwrotnej: DI Wyobraź sobie, że mogą istnieć sposoby na poprawę wydajności z częściowymi dopasowaniami, przy użyciu tego samego podstawowego podejścia. Przynajmniej proste sprawdzenie liczb ujemnych pozwala zaoszczędzić połowę pętli; czek na nieparzystą / nawet kolejną połowę.
Luaan
@Luaan Chciałem to zrobić w jak najmniejszych liniach :) ...public int StringToInt(string value, int search_from = int.MinValue) => value == search_from.ToString() ? search_from : StringToInt (value, ++search_from);
nl-x
Niestety, to wybuchnie: D Ale to świetne rozwiązanie do pisania kodu na papierze - sprawia, że ​​jest to oczywiście poprawne i ciężko jest napisać źle. Możesz także użyć LIINQ - Enumerable.Range(int.MinValue, int.MaxValue).First(i => i.ToString() == stringValue.
Luaan
0

Każde zwielokrotnienie można zastąpić przez wielokrotne dodawanie. Możesz więc zastąpić dowolną wielokrotność w istniejącym algorytmie wersją, która używa tylko dodawania:

static int Multiply(int a, int b)
{
    bool isNegative = a > 0 ^ b > 0;
    int aPositive = Math.Abs(a);
    int bPositive = Math.Abs(b);
    int result = 0;
    for(int i = 0; i < aPositive; ++i)
    {
        result += bPositive;
    }
    if (isNegative) {
        result = -result;
    }
    return result;
}

Możesz pójść dalej i napisać specjalistyczny ciąg znaków do Int, korzystając z tego pomysłu, który minimalizuje liczbę dodatków (pominięto liczbę ujemną i obsługę błędów):

static int StringToInt(string v)
{
    const int BASE = 10;
    int result = 0;
    int currentBase = 1;
    for (int digitIndex = v.Length - 1; digitIndex >= 0; --digitIndex)
    {
        int digitValue = (int)Char.GetNumericValue(v[digitIndex]);
        int accum = 0;
        for (int i = 0; i < BASE; ++i)
        {
            if (i == digitValue)
            {
                result += accum;
            }
            accum += currentBase;
        }
        currentBase = accum;
    }
    return result;
}

Ale nie sądzę, żeby było to warte kłopotu, ponieważ wydajność nie wydaje się tutaj stanowić problemu.

David Brown
źródło