Извлечь одно число из строки с условием

приведенный ниже код извлекает все числа из строки и даже объединяет их. Но мне нужно извлечь только одно целое число с правилами:
1- число состоит из одной или двух цифр (плюс десятичная часть, если она существует).
2- если за номером следует " или inch или in , извлеките его и игнорируйте остальные числа в строке.
3- если вышеуказанное условие (2) не найдено, то извлечь первые числа и игнорировать остальные числа в строке.

Текущая строка ожидаемый результат INSPECT-8'' Водопроводная труба 12 дюймов 8 ПРОВЕРЬТЕ - 8,5" Вода 12 8,5 INSPECT- 4 Вода 5,5 дюйма 5,5 PM-6,5 дюйма от H44 6,5 ПМ-36в трубе М1Т 36 Труба ПМ-36 от М2Т 36 Труба ПМ-18"*12" от М1Т 18 ПМ-36 От 5" М1Т 5 Труба ПМ-123 от МТ
Public Function GetNumeric(CellRef As String)
    Dim StringLength As Long, i As Long, Result As Variant
    StringLength = Len(CellRef)
    For i = 1 To StringLength
      If IsNumeric(Mid(CellRef, i, 1)) Then
         Result = Result & Mid(CellRef, i, 1)
      End If
    Next i
    GetNumeric = Result
End Function

Почему нет совпадения в последнем примере, но есть совпадение в предпоследнем?

JvdV 11.04.2023 11:15

@JvdV, в последнем образце совпадений нет, потому что число должно состоять только из одного или двух символов.

Waleed 11.04.2023 11:34

Верно, тогда последнее правило немного вводило в заблуждение.

JvdV 11.04.2023 11:38
Преобразование HTML-таблицы в профессиональный документ Excel
Преобразование HTML-таблицы в профессиональный документ Excel
Это самый простой способ создания Excel из HTML-таблицы.
Импорт excel в laravel в базу данных
Импорт excel в laravel в базу данных
Здравствуйте, дорогой читатель, в этой статье я расскажу практическим и быстрым способом, как импортировать файл Excel в вашу базу данных с помощью...
1
3
235
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Я думаю, что ответ уже содержится в предложенных вами требованиях Вам нужно добавить оператор if в качестве проверки, чтобы сделать это, или оператор case, я рекомендую определить дополнительную переменную для проверки, прежде чем добавлять к результатам. Определите это как символ вашей строки, которую вы перебираете. Пример:

Dim Check as string

Check = Mid(CellRef, i, 1)
If Check =  Chr(34) then GetNumeric = Result    

Работа с несколькими числами без дальнейшего определения приведет к неразрешимым ситуациям, например, в вашем предпоследнем примере нет указания, является ли 1 или 36 правильным.

Пожалуйста, не могли бы вы добавить полный код, а то он у меня не работает?

Waleed 11.04.2023 15:50

Извините, но это всего лишь идея для дальнейшего развития. Вы должны сделать проверку для каждого из желаемых показателей длины. И как я и другие здесь утверждали. Вы обнаружите проблемы с данными, которые недостаточно конкретны для получения желаемого результата.

Waly 11.04.2023 18:29
Ответ принят как подходящий

Возможно, создайте свой собственный UDF, используя регулярное выражение. Возможно что-то вроде:

Public Function RegexExtract(str, pat, Optional gFlag As Boolean = False, Optional pos As Integer = 0, Optional cse as Boolean = True) As String

Static RE As Object: If RE Is Nothing Then Set RE = CreateObject("vbscript.regexp")

RE.Pattern = pat
RE.Global = gFlag
RE.IgnoreCase = cse

If RE.Test(str) Then
    RegexExtract = RE.Execute(str)(pos)
Else
    RegexExtract = vbNullString
End If

End Function

Обратите внимание, что я создал необязательный глобальный флаг, который по умолчанию является ложным, который должен просто вытащить самое первое попадание в ячейку. Необязательная переменная pos предназначена для возврата определенного совпадения, если вы хотите каким-то образом вернуть другие числа, когда вы устанавливаете глобальный флаг в значение true. Также обратите внимание на использование флага case, для которого установлено значение true, чтобы по умолчанию не учитывать регистр.

Вы можете вызвать вышеуказанное как:

=IFERROR(--RegexExtract(A1,"\b\d\d?(?!\d)(?=\s*(?:""|''|in(?:ch)?\b)?)"),"")

Используемый шаблон означает:

  • \b\d\d? - граница слова с 1 цифрой и 2-й необязательной;
  • (?!\d) - Отрицательный просмотр вперед, чтобы не утверждать больше цифр;
  • (?=\s*(?:"|''|in(?:ch)?\b)?) - Положительный просмотр вперед для утверждения позиции сопровождается 0+ (жадными) символами пробела и:
    • " - Двойная кавычка или;
    • '' - Две одинарные кавычки или;
    • in(?:ch)?\b - Буквально «in», за которым следует необязательный «ch» и граница слова, чтобы подтвердить, что буквы не являются частью большей подстроки, чтобы предотвратить ложные срабатывания.

РЕДАКТИРОВАТЬ1:

Согласно комментариям OP ниже; есть случай, когда может быть число процентов, которое не находится на 1-й позиции. Поскольку OP позволяет также сопоставлять число без дюймов, добавление здесь состоит в том, чтобы включить отрицательный просмотр вперед, который будет утверждать, что нет второго вхождения действительного шаблона:

\b\d\d?(?!\d|.*\b\d+\s*(?:""|''|in(?:ch)?\b))(?=\s*(?:""|''|in(?:ch)?\b)?)

Я полагаю, что это неявно то же самое, что и:

\b\d\d?(?!\d|.*\b\d+\s*(?:""|''|in(?:ch)?\b))

РЕДАКТИРОВАТЬ2:

Чтобы разрешить десятичные дроби, вы можете включить необязательную группу без захвата:

=IFERROR(--RegexExtract(A2,"\b\d\d?(?:\.\d+)?(?!\d|.*\b\d+\s*(?:""|''|in(?:ch)?\b))"),"")

Я пробовал также как =RegexExtract(A1,"\b\d\d?(?!\d)(?=\s*(?:""|''|in(?:ch)?\b)?)‌​") и он отлично работает, так что же нужно IFERROR?

Waleed 11.04.2023 14:02

Я играл со всеми необязательными аргументами и установил Optional gFlag As Boolean = True, Optional pos As Integer = 1, но вывод этих строк неверен 1 Water 3" Pipe и 4" Water 5, правильный результат должен быть (3 , 4 ) соответственно, но теперь это (3 , 5)

Waleed 11.04.2023 14:17

@waleed, значит, ты неправильно понял переменные. Вы должны сохранить необязательные значения по умолчанию, чтобы получить правильный номер.

JvdV 11.04.2023 14:27

Да, я сохранил необязательные значения по умолчанию, но в этом образце выводится неверный результат (1 Water 3" Pipe) он дает 1, а должен 3

Waleed 11.04.2023 14:39

Ах, я понимаю, что вы имеете в виду. Это присуще моему первому предположению, что вы хотите вернуть самый первый действительный результат. Это было бы правильно с текущим шаблоном. Вам нужно будет настроить шаблон, чтобы удовлетворить ваш текущий образец. Что-то, что я могу сделать позже сегодня вечером, когда я вернусь за компьютер.

JvdV 11.04.2023 15:32

Вот два решения, которые не требуют Regex.

Первое решение применяет ряд преобразований к строке, чтобы мы могли использовать Split для получения последовательности строк, некоторые из которых будут числами. Проблема здесь заключается в выборе правильных преобразований, чтобы можно было применить разделение для изоляции чисел. Иногда это может быть невозможно.

Второе решение просто анализирует строку до тех пор, пока не будет извлечена последовательность числовых символов, а затем возвращает эту числовую строку для дальнейшей обработки. Это, вероятно, лучшее решение в вашем случае.

Обратите внимание, что ни одно из решений не тестировалось для крайних случаев.

Учитывая, что вы пытаетесь проанализировать то, что кажется текстом произвольной формы, может быть много пограничных случаев.

Sub Test()

    Dim myC As Collection
    Set myC = New Collection
    With myC
    
        .Add "INSPECT - 8" & Chr$(34) & " Water 12 Pipe    8"
        .Add "INSPECT- 18" & Chr$(34) & " Water 12   18"
        .Add "PM-6in Pipe From M37 st 6"
        .Add "PM- 6 inch Pipe From H44    6"
        .Add "PM-36 Pipe From M-1T    36"
        .Add "PM-123 Pipe From MT"
        
    End With
    

    Dim myItem As Variant
    Dim myNumber As Long
    For Each myItem In myC
    
    'Option 1
'        If TryGetFirstNumber(myItem, myNumber) Then
'            Debug.Print myNumber
'        End If
     ' option 2
'        Debug.Print ParseFirstNumber(VBA.CStr(myItem))
    Next
    
End Sub

' Pass ByVal so we don't alter the original string
Public Function ApplyTransforms(ByVal ipString As String) As String

    ipString = VBA.LCase(ipString)
    ipString = VBA.Replace(ipString, "-", " ")
    ipString = VBA.Replace(ipString, VBA.Chr(34), " ")
    ipString = VBA.Replace(ipString, "in ", " ")
    ipString = VBA.Replace(ipString, "inch ", " ")
    
    ApplyTransforms = ipString
    
End Function

'The try function indicates success by the returned boolean value, the result of the success is returned Byref in parameter opNumber
Public Function TryGetFirstNumber(ByRef ipString As Variant, ByRef opNumber As Long, Optional ipLength As Long = 2) As Boolean

    
    Dim myArray As Variant
    myArray = VBA.Split(ApplyTransforms(ipString))
    
    Dim myItem As Variant
    For Each myItem In myArray
    
        If VBA.IsNumeric(myItem) Then
        
            If VBA.Len(myItem) <= ipLength Then
            
                opNumber = VBA.CLng(myItem)
                TryGetFirstNumber = True
                Exit Function
                
            End If
            
        End If
            
    Next
    
    TryGetFirstNumber = False
    
End Function


Public Function ParseFirstNumber(ByRef ipString As String) As String

    Dim myIndex As Long
    myIndex = 1
    
    Dim myLen As Long
    myLen = VBA.Len(ipString)
    
    Dim myNumber As String
    myNumber = vbNullString

    Do While myIndex <= myLen
        If VBA.InStr("0123456789", VBA.Mid$(ipString, myIndex, 1)) > 0 Then
            Exit Do
        End If
        myIndex = myIndex + 1
    Loop

    If myIndex > myLen Then
        ParseFirstNumber = myNumber
        Exit Function
    End If

    Do While VBA.InStr("0123456789", VBA.Mid$(ipString, myIndex, 1)) > 0
        myNumber = myNumber & VBA.Mid$(ipString, myIndex, 1)
        myIndex = myIndex + 1
        If myIndex > myLen Then
            ParseFirstNumber = myNumber
            Exit Function
        End If
    Loop
    
    ParseFirstNumber = myNumber
    
End Function

Я попробовал второе решение, оно работает со всеми моими образцами, кроме последнего "PM-123 Pipe From MT», результат должен быть пустым, так как я требовал, чтобы число состояло из одного или двух символов.

Waleed 11.04.2023 13:57

Как вы думаете, что означает термин «требуется дальнейшая обработка»?

freeflow 11.04.2023 14:28

хорошо понял, я добавил if с Len , теперь проблема в том, что он извлекает первое попадание независимо от его положения.

Waleed 11.04.2023 14:33

Правильный. Ни один из ваших примеров не показал этот пограничный случай. Как и прежде, может потребоваться дополнительная обработка.

freeflow 11.04.2023 14:47

Пожалуйста, (также) проверьте следующий способ. Он использует стандартный VBA и массив для обработки. Содержимое обработанного массива будет удалено в конце кода, поэтому он должен быть очень быстрым даже для больших диапазонов. Предполагается, что обрабатываемый диапазон начинается с "A1", заголовки, существующие в первой строке листа:

Sub extractInchesNoFromAllRange()
   Dim sh As Worksheet, lastR As Long, arr, i As Long
   Dim dblQ As Long, sQ As Long, strIn As Long, No As String
   
   Set sh = ActiveSheet
   lastR = sh.Range("A" & sh.rows.count).End(xlUp).row
   
   arr = sh.Range("A2:B" & lastR).Value2
   
   For i = 1 To UBound(arr)
        dblQ = InStr(arr(i, 1), """") 'check if a double quote caracters exists and return its postition if it does
        sQ = InStr(arr(i, 1), "''")      'check if two simple quote caracters exists and return its postition if it does
        strIn = InStr(arr(i, 1), "in") 'the same as above for "in" string
        
        No = "" 'reinitialize the variable to keep the extracted number (as string...)
        If dblQ > 0 Or sQ > 0 Then 'if doble quote exists:
            If IsNumeric(Mid(arr(i, 1), IIf(dblQ > 0, dblQ, sQ) - 1, 1)) Then 'if a number exists before the quote ch
                No = Mid(arr(i, 1), IIf(dblQ > 0, dblQ, sQ) - 1, 1)                      'extract first digit
                arr(i, 2) = extractNo(IIf(dblQ > 0, dblQ, sQ) - 2, CStr(arr(i, 1)), No, True) 'call the function which make extraction by (backward) iteration
            End If
        ElseIf strIn > 0 Then 'if "in" exists:
            If Mid(arr(i, 1), strIn + 2, 1) = " " Or Mid(arr(i, 1), strIn + 2, 2) = "ch" Or strIn + 1 = Len(arr(i, 1)) Then
                If Mid(arr(i, 1), strIn - 1, 1) = " " Then
                    arr(i, 2) = extractNo(strIn - 2, CStr(arr(i, 1)), No, True)
                Else
                    arr(i, 2) = extractNo(strIn - 1, CStr(arr(i, 1)), No, True)
                End If
            End If
        Else
            arr(i, 2) = extractNo(0, CStr(arr(i, 1)), "")
        End If
   Next i
   
   'drop the processed arran content back in its range:
   sh.Range("A2").Resize(UBound(arr), UBound(arr, 2)).Value2 = arr
End Sub

Function extractNo(pos As Long, str As String, No As String, Optional boolChar = False) As Variant
   Dim i As Long, boolNo As Boolean
   
   On Error GoTo WrongPatt
   If boolChar Then 'if one of the searched characters has been found:
        For i = pos To 1 Step -1
            If IsNumeric(Mid(str, i, 1)) Or Mid(str, i, 1) = "." Then
                No = CStr(Mid(str, i, 1)) & No
            Else
                extractNo = CDbl(No): Exit For
            End If
        Next i
  Else 'if no searched string has been found:
        For i = 1 To Len(str)
            If IsNumeric(Mid(str, i, 1)) Then
                boolNo = True
                No = No & Mid(str, i, 1)
            Else
                If boolNo Then Exit For
            End If
        Next i
        If Len(No) <= 2 And No <> "" Then
                extractNo = CLng(No)
        Else
                extractNo = ""
        End If
  End If
  Exit Function
  
WrongPatt:
   extractNo = "Wrong pttern"
End Function

Но приведенный выше код будет обрабатывать только строку шаблона, которую вы указали в своем вопросе. Если, например, будет больше символов двойных кавычек с другим назначением ** перед тем, у которого есть число перед ним), код обработает только первый найденный. У него могут возникнуть проблемы, если искомые строки являются первыми в строке и т. д. Его можно адаптировать для работы с большим количеством условий, но мы здесь не умеем читать мысли, чтобы охватывать такие не показанные случаи...

Хорошо, я вспомню, что я написал вчера вечером. Я попробовал ваше последнее редактирование, и оно работает отлично 👍, я надеюсь, что ST позволит принять два ответа одновременно. со своей стороны, я немного адаптировал ваш код, чтобы использовать дополнительный массив для хранения извлеченных чисел (на самом деле я позаимствовал эту идею из других ваших ответов)

Waleed 13.04.2023 08:18

@ Валид Рад, что смог помочь! Да, я удивлен удалением комментариев. Использование другого массива — лучшая идея, если обрабатываемый диапазон содержит много столбцов, но только для двух мне кажется, что уникальный массив лучше. КПД конденсатора примерно одинаков для обоих случаев.

FaneDuru 13.04.2023 08:35

Пожалуйста, ты в порядке? Давно не видел от вас ответа.

Waleed 30.04.2023 11:07

@ Валид, я в порядке. Я участвую в проекте, который не позволяет мне много свободного времени. А на прошлой неделе я провел свой отпуск в Тунисе.

FaneDuru 01.05.2023 18:23

Другие вопросы по теме