Что нового

[Массивы] Сравнение двух массивов, поиск значений, подсчет одинаковых элементов

inx

Знающий
Сообщения
43
Репутация
12
Версия AutoIt: v3.3.10.2

Описание:
Есть текстовый файл - "база данных"
(разделители - @TAB, но не суть важно)
Содержимое вида:
номер, код, текст
(да, текст может отсутствовать)

Код:
99900008	код99900008	девять-девять-ноль-0008
99900009	код99900009
99900010	код99900010	девять-девять-ноль-0010
99900011	код99900011	девять-девять-ноль-0011
99900012	код99900012	девять-девять-ноль-0012

Есть второй файл, с содержимым вида:
код
Код:
99900009
99900009
99900009
99900011
99900008
99900011
99900008
99900008
99900008
99999999
99999999

Собственно нужно, сравнив второй с первым, следующий файл:
номер, код, текст, количество
(для отсутствующих в файле-базе надо также указать код (допустим "код00000000", описание - "Отсутствует!" и количество)

Код:
99900008 код99900008 девять-девять-ноль-0008 4
99900009 код99900009 Пусто! 3
99900011 код99900011 девять-девять-ноль-0011 2
99999999 код00000000 Отсутствует! 2

Примечания:
P.S.
Ну и вопрос: в файле-"базе" около 65000 строк, во втором файле в районе 100.
Как будет быстрее:
текстовые файлы, чтение в массивы и перебор
экселевские таблицы и то же самое
или как-то применить SQLite ?
 

Вложения

  • base+out.zip
    61.6 КБ · Просмотры: 12

ra4o

AutoIT Гуру
Сообщения
1,165
Репутация
246
Если excel, то всё равно в массив читать и дальше с массивом работать. Обработка файла в 65000 строк занимает несколько секунд. (в моём случае было около 5-7 секунд).
 
A

Alofa

Гость
inx
1) В файле-базе каждый номер соответствует определенной фамилии?
2) В файле-базе номера могут повторяться?
3) Список в файле-базе каким-то образом отсортирован?
4) Номера во втором файле каким-то образом отсортированы?
 
Автор
I

inx

Знающий
Сообщения
43
Репутация
12
Alofa сказал(а):
inx
1) В файле-базе каждый номер соответствует определенной фамилии?
2) В файле-базе номера могут повторяться?
3) Список в файле-базе каким-то образом отсортирован?
4) Номера во втором файле каким-то образом отсортированы?
1. Да.
2. Не должны, но могут (это вручную исправим, файл-база в принципе экспорт из Excel и часто меняться не будет)
3. Отсортирован по первому столбцу, по возрастанию.
4. Во втором файле номера могут быть в любом порядке, но второй файл с небольшим количеством строк, до 100.

В первом сообщении чуть подкорректировал задачу, суть осталась та же.
Также приложил два сгенерированных файла для проверки.
Код ниже писал на скорую руку, но вроде работает на приложенных файлах правильно, но довольно медленно (с файлом-базой), нужны комментарии, предложения и критика :smile:

Код:
#include <Array.au3>
#include <File.au3>
#include <Excel.au3>

Global $BaseFile = "base.txt"
Global $OutFile = "out.txt"
Global $arrayOutFile
Global $arrayBase
Global $aID_and_Counts[0]

Local $hFileOpen = FileOpen($BaseFile)
_FileReadToArray($hFileOpen, $arrayBase, 0) ; по @CRLF
FileClose($hFileOpen)
_ArrayDisplay($arrayBase, "$arrayBase 1d")

$arrayBase = _Array_1d_to_2d($arrayBase)

_ArrayDisplay($arrayBase, "$arrayBase 2d")

Local $hFileOpen = FileOpen($OutFile)
_FileReadToArray($hFileOpen, $arrayOutFile, 0) ; по @CRLF
FileClose($hFileOpen)
_ArrayDisplay($arrayOutFile, "$arrayOutFile 1d")

For $i = 0 To UBound($arrayOutFile) - 1
	$sSearch = $arrayOutFile[$i]
	Local $aiResult = _ArrayFindAll($arrayOutFile, $sSearch, 0, 0, 1, 2)
	$counts = UBound($aiResult)
	$iID = _ArraySearch($arrayBase, $sSearch)
	$delim = "__" ; Пока для наглядности так...
	If @error Then
		_ArrayAdd($aID_and_Counts, $arrayOutFile[$aiResult[0]] & $delim & "000000" & $delim & "Отсутствует!" & $delim & $counts) ; Если не нашли
	Else
		$sID = $arrayBase[$iID][1] ; Получаем из второй колонки код
		$sNAME = $arrayBase[$iID][2] ; Получаем из третьей колонки текст
		_ArrayAdd($aID_and_Counts, $arrayOutFile[$aiResult[0]] & $delim & $sID & $delim & $sNAME & $delim & $counts)
	EndIf
Next

Local $aArrayUnique = _ArrayUnique($aID_and_Counts)
_ArrayDelete($aArrayUnique, 0)
_ArrayDisplay($aArrayUnique, "$aArray Unique")

;~ Преобразуем 1d массив в 2d
Func _Array_1d_to_2d($arr)
	Local $array2D[UBound($arr)][3]
	For $i = 0 To UBound($arr) - 1
		$aTemp = StringSplit($arr[$i], @TAB) ; Делим на элементы по "TAB"
		If UBound($aTemp) < 4 Then
;~ 			MsgBox (0, "YES", "Yes")
			ReDim $aTemp[3]
			_ArrayAdd($aTemp, "Пусто!") ; В случае если третий элемент (текст) пустой.
		EndIf
		For $j = 0 To UBound($array2D, 2) - 1
			$array2D[$i][$j] = $aTemp[$j + 1] ; Записываем элементы в 2d массив
		Next
	Next
	Return ($array2D)
EndFunc   ;==>_Array_1d_to_2d
 
A

Alofa

Гость
inx сказал(а):
... работает на приложенных файлах правильно, но довольно медленно ...

Так будет побыстрей (читайте комментарии):
Читаем "Out.txt" в массив --> Каждую стоку "Base.txt" сравниваем с массивом и вносим в него данные --> Записываем массив в "Результат.txt".
Код:
#include <File.au3>
#include <Array.au3>
$hTimer = TimerInit() ; Запускаем таймер

Global $N = 0, $FileOut[0][4]
Local $i = 1, $Draft

If _FileReadToArray(@ScriptDir & '\Out.txt', $Draft) = 0 Then Exit MsgBox(16, 'Ошибка "_FileReadToArray"', '@error = ' & @error)
If Not _ArraySort($Draft, 0, 1) Then Exit MsgBox(16, 'Ошибка "_ArraySort"', '@error = ' & @error) ; Сортируем массив

Do ; В массиве "$Draft[]" узнаем количество одноименных элементов и выводим информацию в "$FileOut[][]"
    $aiResult = _ArrayFindAll($Draft, $Draft[$i])
    $UB = UBound($aiResult)
    $Size = UBound($FileOut)
    ReDim $FileOut[$Size + 1][4]
    $FileOut[$Size][0] = $Draft[$i]
;~  If $UB > 1 Then $FileOut[$Size][1] = '[' &$UB& ']' ; Записываем количество дубликатов только если их больше 1 ...
    $FileOut[$Size][3] = '[' & $UB & ']' ; ... или записываем все
    $i += $UB
Until $i > $Draft[0]
If Not $FileOut[0][0] Then _ArrayDelete($FileOut, 0) ; Удаляем возможные пустые строки

;~ _ArrayDisplay($FileOut, '"$FileOut[][]"') ; Проверка массива "$FileOut[][]"

$FileBase = FileOpen(@ScriptDir & '\Base.txt')
While 1
    $sLine = FileReadLine($FileBase) ; Считываем строку текста из "Base.txt"
    If @error = -1 Then ExitLoop ; Условие штатного выхода из цикла
	If Not $sLine Then ContinueLoop ; Если попалась пустая строка - пропускаем текущий шаг цикла
    $aArray = StringRegExp($sLine, '(\d{7,9}?)\h*?(код\d{7,9})\h*(\S*)\h*?', 2) ; Разделяем стоку на три составляющие в массив
    If @error Then ContinueLoop
;~  _ArrayDisplay($aArray, '"$aArray[]"') ; Проверка результата поиска функции "StringRegExp" в строке "$sLine"

    For $i = 0 To UBound($FileOut) - 1 ; Ищем совпадения
        If $FileOut[$i][0] = $aArray[1] And $FileOut[$i][1] = '' Then ; Условие "$FileOut[$i][1] = ''" позволяет пропускать уже найденные элементы массива
            $FileOut[$i][1] = $aArray[2]
            If Not $aArray[3] Then
                $FileOut[$i][2] = 'Пусто!'
            Else
                $FileOut[$i][2] = $aArray[3]
            EndIf
			$N += 1
            ExitLoop
        EndIf
    Next
	If $N = UBound($FileOut) - 1 Then ExitLoop ; Если количество найденных элементов = количеству элементов массива => досрочное окончание поиска
WEnd

FileClose($FileBase)

; Вписываем в массив значения для "Отсутствующих"
For $i = 0 To UBound($FileOut) - 1
    If Not $FileOut[$i][1] Then
        $FileOut[$i][1] = 'код00000000'
        $FileOut[$i][2] = 'Отсутствует!'
    EndIf
Next

; Записываем массив в файл
$FileResult = FileOpen(@ScriptDir & '\Результат.txt', 2)
If $FileResult = -1 Then Exit MsgBox(16, 'Ошибка "$FileResult"', 'Невозможно создать файл для записи!' & @CRLF & @CRLF & 'Выход.')
For $i = 0 To UBound($FileOut) - 1
    FileWriteLine($FileResult, $FileOut[$i][0] & @TAB & $FileOut[$i][1] & @TAB & @TAB & $FileOut[$i][2] & @TAB & $FileOut[$i][3])
Next
FileClose($FileResult)

_ArrayDisplay($FileOut, 'Готово!  (Время обработки: ' & Round(TimerDiff($hTimer) / 1000, 3) & ' сек.)', Default, 32 + 64, Default, Default, Default, 0xFFFFB8)
;~ _ArrayDisplay($FileOut)

А так еще шустрее:
Читаем "Out.txt" в массив --> Каждое значение элемента массива ищем в "Base.txt", начиная с позиции предыдущего совпадения --> Записываем массив в "Результат.txt".
Код:
#include <File.au3>
#include <Array.au3>
$hTimer = TimerInit() ; Запускаем таймер

Local $FileOut[0][4], $i = 1, $Position = 1, $Draft

If _FileReadToArray(@ScriptDir & '\Out.txt', $Draft) = 0 Then Exit MsgBox(16, 'Ошибка "_FileReadToArray"', '@error = ' & @error)
If Not _ArraySort($Draft, 0, 1) Then Exit MsgBox(16, 'Ошибка "_ArraySort"', '@error = ' & @error) ; Сортируем массив

Do ; В массиве "$Draft[]" узнаем количество одноименных элементов и выводим информацию в "$FileOut[][]"
	$aiResult = _ArrayFindAll($Draft, $Draft[$i])
	$UB = UBound($aiResult)
	$Size = UBound($FileOut)
	ReDim $FileOut[$Size + 1][4]
	$FileOut[$Size][0] = $Draft[$i]
;~  If $UB > 1 Then $FileOut[$Size][1] = '[' &$UB& ']' ; Записываем количество дубликатов только если их больше 1 ...
	$FileOut[$Size][3] = '[' & $UB & ']' ; ... или записываем все
	$i += $UB
Until $i > $Draft[0]
If Not $FileOut[0][0] Then _ArrayDelete($FileOut, 0) ; Удаляем возможные пустые строки

;~ _ArrayDisplay($FileOut, '"$FileOut[][]"') ; Проверка массива "$FileOut[][]"

$FileBaseOpen = FileOpen(@ScriptDir & '\Base.txt')
$FileBase = FileRead($FileBaseOpen)

SetError(0, 0)
$i = 0
While $i <= UBound($FileOut) - 1 ; Ищем совпадения с определенной позиции в тексте
	$aArray = StringRegExp($FileBase, $FileOut[$i][0] & '\h*?(код\d{7,9})\h*(\S*)\h*?', 2, $Position) ; Находим и затем разделяем найденную стоку на три составляющие в массив
	If @error Then ; Если "StringRegExp" не вернула массив, тогда ...
		$Str = StringInStr($FileBase, $FileOut[$i][0], 2, -2, $Position) ; ... поиск в обратном направлении от той же позиции ...
		If $Str Then ; ... Если "StringInStr" вернула позицию, то повторяем "StringRegExp" уже от этой позиции
			$Position = $Str
			ContinueLoop
		Else ; Если совпадений нет, то вписываем в массив следующие значения:
			$FileOut[$i][1] = 'код00000000'
			$FileOut[$i][2] = 'Отсутствует!'
		EndIf
	Else ; Если "StringRegExp" вернула массив ...
		$Position = @extended
		$FileOut[$i][1] = $aArray[1]
		If Not $aArray[2] Then
			$FileOut[$i][2] = 'Пусто!'
		Else
			$FileOut[$i][2] = $aArray[2]
		EndIf
	EndIf
	$i += 1
WEnd

FileClose($FileBase)

; Записываем массив в файл
; (Это можно было сделать и в предыдущем цикле)
$FileResult = FileOpen(@ScriptDir & '\Результат.txt', 2)
If $FileResult = -1 Then Exit MsgBox(16, 'Ошибка "$FileResult"', 'Невозможно создать файл для записи!' & @CRLF & @CRLF & 'Выход.')
For $i = 0 To UBound($FileOut) - 1
	FileWriteLine($FileResult, $FileOut[$i][0] & @TAB & $FileOut[$i][1] & @TAB & @TAB & $FileOut[$i][2] & @TAB & $FileOut[$i][3])
Next
FileClose($FileResult)

_ArrayDisplay($FileOut, 'Готово!  (Время обработки: ' & Round(TimerDiff($hTimer) / 1000, 3) & ' сек.)', Default, 32 + 64, Default, Default, Default, 0xFFFFB8)
;~ _ArrayDisplay($FileOut)
 
A

Alofa

Гость
Ну а SQLite, как и писал Garrett - вне конкуренции:
Вариант 3. (SQLite)
  • Для начала вашу базу необходимо переконвертировать в нужный формат.
    Это можно сделать так:
    Код:
    #include <SQLite.au3>
    #include <Array.au3>
    #include <File.au3>
    
    $FileBase = FileOpen(@ScriptDir & '\Base.txt')
    $Lines = _FileCountLines(@ScriptDir & '\Base.txt') ; Узнаем количество строк в "Base.txt"
    
    ; создаем файл "Base.db".
    _SQLite_Startup()
    If @error > 0 Then Exit MsgBox(16, "SQLite Ошибка", "SQLite.dll Не может быть загружен!")
    If Not FileExists('Base.db') Then
    	_SQLite_Open('Base.db')
    	If @error > 0 Then Exit MsgBox(16, 'SQLite Ошибка', 'Не возможно открыть базу!')
    	If Not _SQLite_Exec(-1, 'CREATE TABLE Таблица_Базы (Код_1, Код_2, ФИО);') = $SQLITE_OK Then
    		MsgBox(16, 'SQLite Ошибка', _SQLite_ErrMsg())
    		_Exit()
    	EndIf
    Else
    	MsgBox(16, '', 'База с таким именем уже существует!')
    	_Exit()
    EndIf
    
    ; Заполняем БД
    Local $i = 0
    While 1
    	$sLine = FileReadLine($FileBase) ; Считываем строку текста из "Base.txt"
    	If @error = -1 Then ExitLoop ; Условие выхода из цикла
    	If Not $sLine Then ContinueLoop ; Если попалась пустая строка - пропускаем текущий шаг цикла
    	$aArray = StringRegExp($sLine, '(\d{7,9}?)\h*?(код\d{7,9})\h*(\S*)\h*?', 2) ; Разделяем стоку на три составляющие в массив
    	If @error Then ContinueLoop
    	;   _ArrayDisplay($aArray, '"$aArray[]"') ; Проверка результата поиска функции "StringRegExp" в строке "$sLine"
    
    	If Not _SQLite_Exec(-1, "INSERT INTO Таблица_Базы VALUES ('" & $aArray[1] & "','" & $aArray[2] & "','" & $aArray[3] & "');") = $SQLITE_OK Then
    		MsgBox(16, 'SQLite Ошибка', _SQLite_ErrMsg())
    		_Exit()
    	EndIf
    	$i += 1
    	ConsoleWrite('Строка: ' & $i & ' [Из ' & $Lines & ']' & @CRLF)
    WEnd
    
    MsgBox(0, '', 'Готово!')
    
    ;показываем все что есть в БД
    Local $aResult, $iRows, $iColumns
    $iRval = _SQLite_GetTable2d(-1, 'SELECT * FROM Таблица_Базы;', $aResult, $iRows, $iColumns)
    If $iRval = $SQLITE_OK Then
    ;~	_SQLite_Display2DResult($aResult) ;показывает содержимое БД в консоли
    	_ArrayDisplay($aResult) ;показывает содержимое БД в array-листе
    Else
    	MsgBox(16, 'SQLite Ошибка: ' & $iRval, _SQLite_ErrMsg())
    EndIf
    
    _Exit()
    Func _Exit() ; Выход
    	FileClose($FileBase)
    	_SQLite_Close()
    	_SQLite_Shutdown()
    	Exit
    EndFunc

    Затем, собственно - сам поиск:
    Код:
    #include <SQLite.au3>
    #include <Array.au3>
    #include <File.au3>
    $hTimer = TimerInit() ; Запускаем таймер
    
    Global  $Draft, $Size, $N = 0, $FileOut[0][2], $i = 1, $aRow
    Global $DisplayVersion, $Publisher, $UninstallString
    
    If _FileReadToArray(@ScriptDir & '\Out.txt', $Draft) = 0 Then Exit MsgBox(16, 'Ошибка "_FileReadToArray"', '@error = ' & @error)
    If Not _ArraySort($Draft, 0, 1) Then Exit MsgBox(16, 'Ошибка "_ArraySort"', '@error = ' & @error) ; Сортируем массив
    
    Do ; В массиве "$Draft[]" узнаем количество одноименных элементов и выводим информацию в "$FileOut[][]"
        $aiResult = _ArrayFindAll($Draft, $Draft[$i])
        $UB = UBound($aiResult)
        $Size = UBound($FileOut)
        ReDim $FileOut[$Size + 1][2]
        $FileOut[$Size][0] = $Draft[$i]
    ;~  If $UB > 1 Then $FileOut[$Size][1] = '<' &$UB& '>' ; Записываем количество дубликатов только если их больше 1 ...
        $FileOut[$Size][1] = '<' & $UB & '>' ; ... или записываем все
        $i += $UB
    Until $i > $Draft[0]
    If Not $FileOut[0][0] Then _ArrayDelete($FileOut, 0) ; Удаляем возможные пустые строки
    
    ;~ _ArrayDisplay($FileOut, '"$FileOut[][]"') ; Проверка массива "$FileOut[][]"
    
    _SQLite_Startup() ; Загружаем "SQLite3.dll"
    If @error > 0 Then
    	MsgBox(16, 'SQLite Ошибка', 'SQLite.dll Не может быть загружен!')
    Else
    	$dbn=_SQLite_Open('Base.db') ; Открываем БД
    EndIf
    
    $FileResult = FileOpen(@ScriptDir & '\Результат.txt', 2) ; Открыть только для записи в конец файла, удалив предыдущее содержание
    If $FileResult = -1 Then Exit MsgBox(16, 'Ошибка "$FileResult"', 'Невозможно создать файл для записи!' & @CRLF & @CRLF & 'Выход.')
    
     ; Поиск и запись в "Результат.txt" 
    For $i=0 To $Size
    	$Err = _SQLite_GetTable2d($dbn, "SELECT Код_2, ФИО FROM Таблица_Базы WHERE Код_1='" & $FileOut[$i][0] & "'", $aRow, 2, 3) ; выполняет запрос
    	If Not $Err = $SQLITE_OK Then ; Если какой сбой - его код ошибки тоже записываем в файл.
    		FileWriteLine($FileResult, '<<<<<<<-Ошибка SQLite->> <<-Код ошибки: ' & _SQLite_ErrCode() & '->> <<-Сообщение об ошибке: ' & _SQLite_ErrMsg() & '->>>>>>>')
    		ContinueLoop
    	EndIf
    
    	If UBound($aRow)>1 Then
     		If Not $aRow[1][1] Then $aRow[1][1] = @TAB &'Пусто!'
    		FileWriteLine($FileResult, $FileOut[$i][0] & @TAB & $aRow[1][0] & @TAB & @TAB & $aRow[1][1] & @TAB & $FileOut[$i][1])
        Else
    		FileWriteLine($FileResult, $FileOut[$i][0] & @TAB & 'код00000000' & @TAB & @TAB & 'Отсутствует!' & @TAB & $FileOut[$i][1])
        EndIf
    Next
    
    FileClose($FileResult)
    
    MsgBox(64, 'Отчет', 'Готово!' &@CRLF& 'Время обработки: ' & Round(TimerDiff($hTimer) / 1000, 3) & ' сек.')
    Run ('notepad.exe "' & @ScriptDir & '\Результат.txt"')
    
    _Exit()
    Func _Exit() ; Выход
    	_SQLite_Close($dbn)
    	_SQLite_Shutdown()
    	Exit
    EndFunc
 
Верх