sort array of files by natural number order

Become a member of the CGSociety

Connect, Share, and Learn with our Large Growing CG Art Community. It's Free!

THREAD CLOSED
 
Thread Tools Search this Thread Display Modes
Old 11 November 2013   #1
sort array of files by natural number order

as the title suggests is there a way to do this? I've solved this problem before in C#, but I hope I don't have to re-write code in maxscript to solve this problem here because I am not nearly good in maxscript as I am in C#. But for you pros out there I can drop the C# code here.

basically
theFiles = getFiles (path + "\\*_crop.png") --collect all .png files in the dir.


returns
--\Images\0_crop.png"
--\Images\10_crop.png"
--\Images\11_crop.png"
--\Images\12_crop.png"
--\Images\1_crop.png"
--\Images\2_crop.png"
--\Images\3_crop.png"
--\Images\4_crop.png"
--\Images\5_crop.png"
--\Images\6_crop.png"
--\Images\7_crop.png"
--\Images\8_crop.png"
--\Images\9_crop.png"

this is the wrong order... is there a way to fix this?
 
Old 11 November 2013   #2
what is your c# sorting code?
 
Old 11 November 2013   #3
WARNING: C#
USAGE:
string[] files = Directory.GetFiles(path, "*.png");
NumericComparer ns = new NumericComparer();
Array.Sort(files, ns);


	public class NumericComparer : IComparer
	{
		public NumericComparer()
		{}
		
		public int Compare(object x, object y)
		{
			if((x is string) && (y is string))
			{
				return StringLogicalComparer.Compare((string)x, (string)y);
			}
			return -1;
		}
	}

	// emulates StrCmpLogicalW, but not fully
	public class StringLogicalComparer
	{
		public static int Compare(string s1, string s2)
		{
			//get rid of special cases
			if ((s1 == null) && (s2 == null)) return 0;
			else if (s1 == null) return -1;
			else if (s2 == null) return 1;

			if ((s1.Equals(string.Empty) && (s2.Equals(string.Empty)))) return 0;
			else if (s1.Equals(string.Empty)) return -1;
			else if (s2.Equals(string.Empty)) return -1;

			//WE style, special case
			bool sp1 = Char.IsLetterOrDigit(s1, 0);
			bool sp2 = Char.IsLetterOrDigit(s2, 0);
			if (sp1 && !sp2) return 1;
			if (!sp1 && sp2) return -1;

			int i1 = 0, i2 = 0; //current index
			int r = 0; // temp result
			while (true)
			{
				bool c1 = Char.IsDigit(s1, i1);
				bool c2 = Char.IsDigit(s2, i2);
				if (!c1 && !c2)
				{
					bool letter1 = Char.IsLetter(s1, i1);
					bool letter2 = Char.IsLetter(s2, i2);
					if ((letter1 && letter2) || (!letter1 && !letter2))
					{
						if (letter1 && letter2)
						{
							r = Char.ToLower(s1[i1]).CompareTo(Char.ToLower(s2[i2]));
						}
						else
						{
							r = s1[i1].CompareTo(s2[i2]);
						}
						if (r != 0) return r;
					}
					else if (!letter1 && letter2) return -1;
					else if (letter1 && !letter2) return 1;
				}
				else if (c1 && c2)
				{
					r = CompareNum(s1, ref i1, s2, ref i2);
					if (r != 0) return r;
				}
				else if (c1)
				{
					return -1;
				}
				else if (c2)
				{
					return 1;
				}
				i1++;
				i2++;
				if ((i1 >= s1.Length) && (i2 >= s2.Length))
				{
					return 0;
				}
				else if (i1 >= s1.Length)
				{
					return -1;
				}
				else if (i2 >= s2.Length)
				{
					return -1;
				}
			}
		}

		private static int CompareNum(string s1, ref int i1, string s2, ref int i2)
		{
			int nzStart1 = i1, nzStart2 = i2; // nz = non zero
			int end1 = i1, end2 = i2;

			ScanNumEnd(s1, i1, ref end1, ref nzStart1);
			ScanNumEnd(s2, i2, ref end2, ref nzStart2);
			int start1 = i1; i1 = end1 - 1;
			int start2 = i2; i2 = end2 - 1;

			int nzLength1 = end1 - nzStart1;
			int nzLength2 = end2 - nzStart2;

			if (nzLength1 < nzLength2) return -1;
			else if (nzLength1 > nzLength2) return 1;

			for (int j1 = nzStart1, j2 = nzStart2; j1 <= i1; j1++, j2++)
			{
				int r = s1[j1].CompareTo(s2[j2]);
				if (r != 0) return r;
			}
			// the nz parts are equal
			int length1 = end1 - start1;
			int length2 = end2 - start2;
			if (length1 == length2) return 0;
			if (length1 > length2) return -1;
			return 1;
		}

		//lookahead
		private static void ScanNumEnd(string s, int start, ref int end, ref int nzStart)
		{
			nzStart = start;
			end = start;
			bool countZeros = true;
			while (Char.IsDigit(s, end))
			{
				if (countZeros && s[end].Equals('0'))
				{
					nzStart++;
				}
				else countZeros = false;
				end++;
				if (end >= s.Length) break;
			}
		}

	}
 
Old 11 November 2013   #4
you can use your c# comparer of course but everything can be much easier
fn makeIComparersAssembly = 
(
	source = ""
	source += "using System;\n"
	source += "using System.Runtime.InteropServices;\n"
	source += "using System.Collections;\n"
	source += "namespace IComparers\n"
	source += "{\n"
	source += "	public sealed class StringLogicalComparer: IComparer\n"
	source += "	{\n"
	source += "		[DllImport(\"shlwapi.dll\", CharSet = CharSet.Unicode, ExactSpelling = true)]\n"
	source += "		static extern int StrCmpLogicalW(String x, String y);\n"
	source += "		public int Compare(object x, object y)\n"
	source += "		{\n"
	source += "			return StrCmpLogicalW((String)x, (String)y);\n"
	source += "		}\n"
	source += "	}\n"
	source += "}\n"

	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

	compilerParams.GenerateInMemory = on
	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
	compilerResults.CompiledAssembly
)
global StringLogicalComparer = (makeIComparersAssembly()).createInstance "IComparers.StringLogicalComparer"
	
/*-- TEST -- */
(
	a = dotnet.valuetodotnetobject #("test 1", "test 10", "test 2", "1 te1st", "1 te10st", "1 te2st", "10 test", "2 test") (dotnetclass "System.String[]")
	
	a.Sort a StringLogicalComparer
	print (a.Clone())
)
 
Old 11 November 2013   #5
using the native MXS methods we can do it with qsort. any volunteers to make a qsort sorting function?
 
Old 11 November 2013   #6
For this particular case something like this might also work:
(
 
 fn compare f1 f2 =
 (
 	name1 = getFilenameFile f1
 	name2 = getFilenameFile f2
 	
 	n1 = (replace name1 (name1.count-4) 5 "") as integer
 	n2 = (replace name2 (name2.count-4) 5 "") as integer
 
 	case of
 	(
 		(n1 < n2): -1
 		(n1 > n2):  1
 		default: 0
 	)
 )
 
 files = for j = 12 to 0 by -1 collect "C:\Images\\" + (j as string) + "_crop.png"
 qsort files compare
 files
 
 )
__________________
Jorge Rodríguez
PolyTools3D
 
Old 11 November 2013   #7
PolyTools3D: error here (n1 < n2): -1 /// no ""<"" function for undefined

denisT: this code result in theFiles unable to convert to type String

	if path != undefined do--if the user did not cancel
	(
		theFiles = getFiles (path + "\\*_crop.png") --collect all .png files in the dir.
		a = dotnet.valuetodotnetobject #(theFiles) (dotnetclass "System.String[]")
		a.Sort a StringLogicalComparer
		for f in theFiles do
		(
			print(f)
		)
	)


thankyou so much for the help! Ill keep experimenting...
 
Old 11 November 2013   #8
Originally Posted by Bixel: this code result in theFiles unable to convert to type String

 	if path != undefined do--if the user did not cancel
 	(
 		theFiles = getFiles (path + "\\*_crop.png") --collect all .png files in the dir.
 		a = dotnet.valuetodotnetobject #(theFiles) (dotnetclass "System.String[]")
 		a.Sort a StringLogicalComparer
 		for f in theFiles do
 		(
 			print(f)
 		)
 	)
 


the Sort method sorts "a" array. why do you expect to get theFiles array sorted?
"a" is a dotnet object copy of the theFiles mxs array.
 
Old 11 November 2013   #9
Originally Posted by Bixel:
PolyTools3D: error here (n1 < n2): -1 /// no ""<"" function for undefined

The function should work as long as the suffix length is the same, in this case 5 characters "_crop", as in the example you showed. So, 1ABCDE.png should work but 1ABCDEF.png will not.

I realized that if the suffix is always the same, you could also trim it, do a simple integer sort and then append it again.

Also, if you know the files will have no gaps in the numbering and have the same suffix, you could just collect the numbers and append the suffix for the given amount of files.

In any case, the C# function is much more efficient and works in general cases. I don’t think a pure mxs qsort function can beat C# in this case.

By the way, in the function Denis provided you are putting an array inside another array, that’s what might be giving you the error. Try replacing #(theFiles) by just theFiles.
__________________
Jorge Rodríguez
PolyTools3D
 
Old 11 November 2013   #10
denisT: sorry I did not know that #() is == to theFiles
PolyTools3D: thank you - I don't know maxscript that well. That solved the issue. Now I don't suppose there is a dotnet.dotnetobjecttovalue conversion to get 'a' back into theFiles? Maxscript does not allow for loops on 'a'.
 
Old 11 November 2013   #11
Originally Posted by Bixel: denisT: sorry I did not know that #() is == to theFiles
PolyTools3D: thank you - I don't know maxscript that well. That solved the issue. Now I don't suppose there is a dotnet.dotnetobjecttovalue conversion to get 'a' back into theFiles? Maxscript does not allow for loops on 'a'.

to go back to the string array use (as i showed it above) clone of .net array. max automatically cast it to a mxs string array

 mxsArray = netArray.Clone()
 

you can iterate through .net Array elements using mxs.

 a = dotnet.ValueToDotNetObject #(1,2,3) (dotnetclass "Int32[]")
 for i=0 to a.length-1 do print (a.GetValue i)
 

but it' will be slower than convert to mxs array at the beginning
 
Old 11 November 2013   #12
you can collect all files using .net, and directly put them into .net array:

a = (dotnetclass "System.IO.Directory").GetFiles (getDir #maxroot) "*.exe" (dotnetclass "System.IO.SearchOption").AllDirectories asdotnetobject:on
a.Sort a
a.Clone()

it might save a memory
 
Old 11 November 2013   #13
Smile

That did it! thankyou so much
 
Old 11 November 2013   #14
Thread automatically closed

This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.
__________________
CGTalk Policy/Legalities
Note that as CGTalk Members, you agree to the terms and conditions of using this website.
 
Thread Closed share thread



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
CGSociety
Society of Digital Artists
www.cgsociety.org

Powered by vBulletin
Copyright ©2000 - 2006,
Jelsoft Enterprises Ltd.
Minimize Ads
Forum Jump
Miscellaneous

All times are GMT. The time now is 07:03 AM.


Powered by vBulletin
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.