Что нового

_FileDeleteDuplicateLines - Удаление дубликатов в файле

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
AutoIt: 3.3.8.1
Версия: 1.0

Категория: Строки, Данные, Файловая система

Описание: Удаление дублирующихся строк в файле.

Код/Пример:
Код:
#include <Array.au3>

$sFileName = 'Test.txt'
$sFilePath = @DesktopDir
$sFile = $sFilePath & '\' & $sFileName

$aDups = _FileDeleteDuplicateLines($sFile, 0, 1)

Switch @error
	Case 1
		MsgBox(48, 'Ошибка', StringFormat('Файл <%s> не найден.', $sFileName))
	Case 2
		MsgBox(48, 'Ошибка', StringFormat('Ошибка при попытке записи в Файл <%s>.', $sFileName))
	Case Else
		If IsArray($aDups) Then
			MsgBox(64, 'Results', StringFormat('В файле <%s> удалено дублирующихся строк: %i', $sFileName, $aDups[0]))
			_ArrayDisplay($aDups, 'Duplicate Lines')
		Else
			MsgBox(64, 'Results', StringFormat('В файле <%s> нет дублирующихся строк.', $sFileName))
		EndIf
EndSwitch

Функция:
Код:
; #FUNCTION# ====================================================================================================
; Name...........:	_FileDeleteDuplicateLines
; Description....:	Deletes duplicated lines in file
; Syntax.........:	_FileDeleteDuplicateLines($sFile [, $iCaseSense = 0 [, $iUseDict = 0 ]])
; Parameters.....:	$sFile - File to delete the duplicates in.
;					$iCaseSense - [Optional] Defines if the search for duplicates will be case sensitive (default is 0 - not case sensitive).
;					$iUseDict - [Optional] If this parameter is 1 (default is 0), then Scripting.Dictionary object is used to find and delete the duplicates (much faster).
;											Note: This method works only on Win 2000 and above.
;					
; Return values..:	Success - Return an array of deleted lines ([0] is the number of total deleted duplicates).
;					Failure - Set @error as following:
;														-1 - If $iUseDict = 1 and @OSTYPE = 'WIN32_WINDOWS'.
;														1 - $sFile does not exists.
;														2 - Unable to write the file.
; Author.........:	G.Sandler (CreatoR)
; Modified.......:	
; Remarks........:	
; Related........:	
; Link...........:	
; Example........:	
; ===============================================================================================================
Func _FileDeleteDuplicateLines($sFile, $iCaseSense = 0, $iUseDict = 0)
	If Not FileExists($sFile) Then
		Return SetError(1, 0, 0)
	EndIf
	
	If $iUseDict And @OSTYPE = 'WIN32_WINDOWS' Then
		Return SetError(-1, 0, 0)
	EndIf
	
	Local $aDups, $sFRead, $aFSplit, $sFContent, $oDict, $hFOpen
	
	$sFRead = FileRead($sFile)
	$aFSplit = StringSplit(StringStripCR($sFRead), @LF)
	
	Dim $aDups[$aFSplit[0]+1]
	
	If $iUseDict Then
		$oDict = ObjCreate('Scripting.Dictionary')
		
		If $iCaseSense = 1 Then
			$oDict.CompareMode = 0 ;Binary mode (case sensitive)
		Else
			$oDict.CompareMode = 1 ;Text mode (not case sensitive)
		EndIf
		
		For $i = 1 To $aFSplit[0]
			If Not $oDict.Exists($aFSplit[$i]) Then
				$oDict.Add($aFSplit[$i], $aFSplit[$i])
				$sFContent &= $aFSplit[$i] & @CRLF
			Else
				$aDups[0] += 1
				$aDups[$aDups[0]] = $aFSplit[$i]
			EndIf
		Next
	Else
		$sFContent = @CRLF
		
		If $iCaseSense = 0 Then
			$iCaseSense = 2
		EndIf
		
		For $i = 1 To $aFSplit[0]
			If Not StringInStr($sFContent, @CRLF & $aFSplit[$i] & @CRLF, $iCaseSense) Then
				$sFContent &= $aFSplit[$i] & @CRLF
			Else
				$aDups[0] += 1
				$aDups[$aDups[0]] = $aFSplit[$i]
			EndIf
		Next
		
		$sFContent = StringTrimLeft($sFContent, 2)
	EndIf
	
	If $aDups[0] = 0 Then
		Return 0
	EndIf
	
	ReDim $aDups[$aDups[0]+1]
	
	$sFContent = StringTrimRight($sFContent, 2)
	
	$hFOpen = FileOpen($sFile, 2)
	
	If $hFOpen = -1 Then
		Return SetError(2, 0, 0)
	EndIf
	
	FileWrite($hFOpen, $sFContent)
	FileClose($hFOpen)
	
	Return $aDups
EndFunc

История версий:
v1.0 - Первая публичная версия

Источник: autoit-script.ru
Автор(ы): G.Sandler (CreatoR)
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
CreatoR,
А Вы это не забыли?
Код:
$oDictionary.CompareMode
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
Уточнение ;)
_FileDeleteDuplicateLines - Удаление дубликатов строк в файле
 

WSWR

AutoIT Гуру
Сообщения
941
Репутация
363
CreatoR
Интересно, а можно на таком принципе написать пример, показывающий количество каждой из уникальных строк в исходном файле? Вроде словаря?
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
В UDF RESH удаление дубликатов проще, вызов просто не добавляет новый ключ, если он уже существует.
Код:
Func _ArrayRemoveDuplicates(Const ByRef $aArray)
	If Not IsArray($aArray) Then Return SetError(1, 0, 0)
	Local $oSD = ObjCreate("Scripting.Dictionary")
	For $i In $aArray
		$oSD.Item($i) ; shown by wraithdu
	Next
	Return $oSD.Keys()
EndFunc
 
Автор
CreatoR

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
AZJIO [?]
вызов просто не добавляет новый ключ, если он уже существует
Так при удалении строк нужно строить строковую переменную, иначе придётся запускать ещё один цикл для записи. Т.ч проверка нужна в этом случае.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
CreatoR
Я проверил скорость на 3000 IP-шнеков и твой вариант с Exists оказался действительно быстрей в разы, именно за счёт несоздания второго цикла и вызова Keys(). А если использовать вывод массивом, то проигрывает на ~5%.

Вот тестовый пример
Код:
#include <Array.au3>

$sText = FileRead(@ScriptDir & '\тест.txt')
$timer1 = TimerInit()
$sText1 = _ArrayRemoveDuplicates($sText)
$timer1 = Round(TimerDiff($timer1), 2)

$timer2 = TimerInit()
$sText2 = _DeleteDuplicateLines($sText)
$timer2 = Round(TimerDiff($timer2), 2)

MsgBox(0, "Время выполнения", _
		'$timer1 : ' & $timer1 & @CRLF & _
		'$timer2 : ' & $timer2)
; MsgBox(0, 'Сообщение', $sText1)
; MsgBox(0, 'Сообщение', $sText2)

Func _ArrayRemoveDuplicates(Const ByRef $sTXT)
	Local $oDict = ObjCreate("Scripting.Dictionary")
	$oDict.CompareMode = 0
	$aFSplit = StringSplit(StringStripCR($sTXT), @LF)
	For $i In $aFSplit
		$oDict.Item($i)
	Next
	$sText = ''
	$aFSplit = $oDict.Keys()
	For $i In $aFSplit
		$sText &= $i & @CRLF
	Next
	Return $sText
EndFunc   ;==>_ArrayRemoveDuplicates

Func _DeleteDuplicateLines(Const ByRef $sTXT)
	Local $oDict = ObjCreate("Scripting.Dictionary")
	$oDict.CompareMode = 0
	$aFSplit = StringSplit(StringStripCR($sTXT), @LF)
	$sText = ''
	For $i = 1 To $aFSplit[0]
		If Not $oDict.Exists($aFSplit[$i]) Then
			$oDict.Add($aFSplit[$i], $aFSplit[$i])
			$sText &= $aFSplit[$i] & @CRLF
		EndIf
	Next
	Return $sText
EndFunc   ;==>_DeleteDuplicateLines

Ещё вопрос, почему функция сделана имено по работе с файлом? То есть заложено 2 операции, хотя открыть файл для многих не проблема. В таком случае лучше иметь составные функции, одна открывает/закрывает файл, а между ними вызов функции обработки данных, чтобы функцию обработки данных можно было использовать самостоятельно.
 
Автор
CreatoR

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
AZJIO [?]
почему функция сделана имено по работе с файлом?
Потому что у меня ещё с давних времён валялась эта функция, и работала очень медленно :smile:.

заложено 2 операции, хотя открыть файл для многих не проблема
Не проблема, но в любом случае для записи в файл и чтения с него, придётся перебирать цикл (если использовать удаление в массиве), а это опять же замедление.
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
CreatoR [?]
а это опять же замедление.
Почему замедление? Принцип такой:
Код:
Func _FileDeleteDuplicateLines(...)
	FileRead(...)
	$result = _DeleteDuplicateLines(...)
	FileWrite($result)
EndFunc

Func _DeleteDuplicateLines(...)
	...
EndFunc
 
Автор
CreatoR

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
AZJIO [?]
А что будет передаваться функциям, массив или строка?
В первом варианте (а исходя из замысла, оно так и есть) замедление неизбежно. Массив нужно построить (прочитав с файла), а потом собрать (для записи в файл).
 

AZJIO

Меценат
Меценат
Сообщения
2,874
Репутация
1,194
CreatoR [?]
А что будет передаваться функциям, массив или строка?
Строка, то есть замедление будет только на вызов функции, а это ничтожное замедление по сравнению с обработкой данных, а тем более записью на HDD.

На счёт возможности передачи массива, то есть усложнения функции, это конечно можно реализовать но мои попытки превращали функцию в сложно-запутанное, поэтому советовать не буду.

Вот пример запутанной функции, которую в итоге почти нигде не использую
Код:
; ===============================================================================================================================
; Описание ...: Поиск и удаление дубликатов в данных
; Синтаксис.........: _ArrayUnique2($data[, $flag=-1])
; Параметр1....: $data - данные, массив или строка с разделителем
; Параметр2 ....: $flag
;							Если массив, то $flag является индексом массива от которого производить поиск
;							Если строка, то $flag является разделителем, по умолчанию "|"
; Возвращает .: Успешно		- массив без дубликатов
;							Ошибка	- 0 и @error=1
; Автор ........: AZJIO
; Remarks .......: В данных не должно быть символа "[", такие данные исключаются из массива, даже
;						если не являются дубликатами, остальные спец-символы и буквы не вызывают ошибки
; ===============================================================================================================================
Func _ArrayUnique2($data, $flag = -1)
	Local $k, $i, $tmp
	Assign('/', 1, 1) ;для исключения пустых строк и не совпадения с локальными переменными
	If IsArray($data) Then
		If $flag = -1 Then $flag = 0
		$tmp = UBound($data) - 1
		If $flag > $tmp Then Return SetError(1, 0, 0)
		$k = 0
		For $i = $flag To $tmp
			Assign($data[$i] & '/', Eval($data[$i] & '/') + 1, 1)
			If Eval($data[$i] & '/') = 1 Then
				$data[$k] = $data[$i]
				$k += 1
			EndIf
		Next
		If $k = 0 Then Return SetError(1, 0, 0)
		ReDim $data[$k]
		Return $data
	Else
		If $flag = -1 Then $flag = '|'
		$data = StringSplit($data, $flag)
		If Not @error Then
			$k = 0
			For $i = 1 To $data[0]
				Assign($data[$i] & '/', Eval($data[$i] & '/') + 1, 1)
				If Eval($data[$i] & '/') = 1 Then
					$data[$k] = $data[$i]
					$k += 1
				EndIf
			Next
			If $k = 0 Then Return SetError(1, 0, 0)
			ReDim $data[$k]
			Return $data
		Else
			Return SetError(1, 0, 0)
		EndIf
	EndIf
EndFunc   ;==>_ArrayUnique2
 
Автор
CreatoR

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
AZJIO
Я не совсем понимаю что ты предлагаешь.

Функция в данной теме предназначена для файлов, для строк и массивов можно сделать другую, с другим алгоритмом.
Не вижу в чём проблема.
 

nowost

Знающий
Сообщения
178
Репутация
17
CreatoR , пробую обработать файл большого размера вашей функцией, 200мб примерно, в режиме словаря работает очень шустро. Есть только одно но, примерно на 95% обработки съедается примерно 3.5 Гига памяти и происходит вылет с ошибкой, что недостаточно памяти( Я так понимаю нужно исходный файл разбивать на более мелкие куски и с ними работать ? или возможно както оптимизировать вашу функцию ?
 
Верх