5

i have a two dimensional array that has the following values due to an algorithm i created,

String [2,12] array = {
    {"ab","ab","ab","FREE","me","me","me","FREE","mo","mo","FREE","FREE"},
    {"so","so","FREE","no","no","FREE","to","to","to","FREE","do","do"}
};

and i wish to shuffle the data within the arrays randomly so that the data with the same values remain together but their location changes. please can someone help with this.

for example if the array is to be shuffled it should look like this,

 String [2,12] array = {
     {"me","me","me","FREE","so","so","FREE","mo","mo","FREE","do","do"},
     {"ab","ab","ab""FREE","to","to","to","FREE","no","no","FREE","FREE"}
 };
svick
  • 236,525
  • 50
  • 385
  • 514
  • Are the values with the same values (e.g. "ab" "ab" "ab" in your example) *always* next to each other in the source array? – Matthew Watson May 16 '13 at 12:46
  • Can you give a sample for this: *to shuffle the data within the arrays randomly so that the data with the same values remain together but their location changes.* – oleksii May 16 '13 at 12:46
  • yes the values are always next to each other in the source array –  May 16 '13 at 12:48
  • ...and they should also _remain_ together in the result? – John Willemse May 16 '13 at 12:52
  • This is a really complicated problem. I don't think you're going to find an easy solution. Since the items you are moving around occupy different "sizes" then fitting them all in after "shuffling" them sounds like it would be the equivalent to the [packing problem](http://en.wikipedia.org/wiki/Packing_problem) If you treat each set of adjacent identical items as a single unit, that unit's "size" will be the number of adjacent identical items. You would have to move these "units" around - so, a packing problem. – Matthew Watson May 16 '13 at 12:52
  • @user2227986 Your edit is confusing me; are you saying that it's OK for values to _jump_ between the main indices? That is the "me, me, me" from `array[0, ...]` can transfer over to `array[1, ...]` and vice-versa? EDIT: _AND_ that means that any jumps between them _must_ maintain the overall same size of `12`? So we can move two sets of 3 (6 total entries) from `array[0]` to `array[1]`, but then we must _also_ move _exactly_ 6 entries (say three sets of 2) back over to `array[0]`? – Chris Sinclair May 16 '13 at 13:00
  • yes they should remain together in the result and the values may jump between their main indices depending on how it is randomized and how the algorithm is built. –  May 16 '13 at 13:02
  • @user2227986 Does the result _have_ to be a `string [2,12]` type? Or can the random result be altered to a jagged array where `array[0]` is length 10, and `array[1]` is length 14? – Chris Sinclair May 16 '13 at 13:05
  • Does there have to be a FREE between each set of the same values? – Lily May 16 '13 at 13:06
  • And in addition, is it OK for groups to be combined? That is, given "FREE, ab, FREE, mo, FREE, FREE" is it OK to have a result of "ab, FREE, FREE, FREE, FREE, mo" where the 3 sets of "FREE" _happened_ to get random sorted adjacent to each other? – Chris Sinclair May 16 '13 at 13:11
  • Oh I understand what the OP means now, it's not keep the values together vertically but more horizontally... (sorry for being a bit slow on that one but it wasn't very clear before the example) – vc 74 May 16 '13 at 13:20
  • @vc74 Yet still let the values "jump" vertically, and I'm _guessing_ maintain the same size of the array. – Chris Sinclair May 16 '13 at 13:20
  • @ChrisSinclair, yikes, it's a totally different story then... – vc 74 May 16 '13 at 13:22
  • 4
    This problem is quite tricky. Since essentially you are describing a *constraint satisfaction problem* it would be really helpful if you actually *listed all the constraints* instead of just giving an example. – Eric Lippert May 16 '13 at 13:34
  • Actually the array is actually static in a 4 by 12 structure but for simplification i made it a 2 by 12, but as to if it can be jagged if it would make it easier for packing then yes. and @chrisSinclair no! the data is in contiguous spaces for example {"a","a","a"} can not be seperated and it applies to the rest. –  May 16 '13 at 13:47
  • @user2227986 I meant not separating the spaces, but combining them randomly. Given {"a", "b", "a"} can you get {"b", "a", "a"}? (all three are their own contiguous set, but we happen to randomly sort the two "a" sets next to each other) – Chris Sinclair May 16 '13 at 13:53
  • @ChrisSinclair not at all! –  May 16 '13 at 14:03
  • The constraints as I understand them : 1. The target array should be regular and have the same dimensions as the source array. 2. The number and count of each string in the source array should be equal to the destination array. 3. Internal sequences should be preserved. For example "ab","ab","ab" in the example above is a sequence. A sequence can be of length 1. – cvraman May 16 '13 at 16:55

2 Answers2

3

I really enjoyed this question. Here is a sample solution. Code is commented. I have tested it on the sample input provided in the question. Documentation in Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SandboxConoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            const int rowCount = 2;
            const int columnCount = 12;
            int minCount = 1;
            int maxCount = 1;
            var sourceArray = new String[rowCount, columnCount]{
                    {"ab","ab","ab","FREE","me","me","me","FREE","mo","mo","FREE","FREE"},
                    {"so","so","FREE","no","no","FREE","to","to","to","FREE","do","do"}
                       };

            var destinationArray = new String[rowCount, columnCount];

            //Print Source Array
            PrintArrayData(sourceArray, rowCount, columnCount);
            Console.WriteLine("\n\n");

            //Data Structures that store data row wise.
            var sourceRowData = new Dictionary<int,List<StringCount>>();
            var destinationRowData = new Dictionary<int, List<StringCount>>();

            //Make sourceArray more consumable. Put it into sourceRowData. See Initialize Documentation 
            Initialize(sourceArray, rowCount, columnCount, ref maxCount, sourceRowData);

            //Data Structure that stores data by count (Occurences)
            var countIndexDictionary = new Dictionary<int, List<StringCount>>();
            for (int index = minCount; index <= maxCount; index++)
            {
                countIndexDictionary.Add(index, GetDataMatchingCount(index, sourceRowData));             
            }


            //Create Destination Row Data
            var random = new Random();
            for (int row = 0; row < rowCount; row++)
            {
                var destinationList = new List<StringCount>();

                //Count List contains the order of number of within a row. for source row 0 : 3,1,3,1,2,2
                var countList = sourceRowData[row].Select(p => p.count);

                //Randomize this order.
                var randomizedCountList = countList.OrderBy(x => random.Next()).ToList();
                foreach (var value in randomizedCountList)
                {
                    //For each number (count) on the list select a random element of the same count.
                    int indextoGet = random.Next(0,countIndexDictionary[value].Count - 1);

                    //Add it to the destination List
                    destinationList.Add(countIndexDictionary[value][indextoGet]);

                    //Rempve from that string from global count list
                    countIndexDictionary[value].RemoveAt(indextoGet);
                }
                destinationRowData.Add(row, destinationList);
            }

            //Create Destination Array from Destination Row Data            
            for (int row = 0; row < rowCount; row++)
            {
                int rowDataIndex = 0;
                int value = 1;
                for (int column = 0; column < columnCount; column++)
                {
                    if (destinationRowData[row][rowDataIndex].count >= value)
                        value++;

                    destinationArray[row, column] = destinationRowData[row][rowDataIndex].value;
                    if (value > destinationRowData[row][rowDataIndex].count)
                    {
                        value = 1;
                        rowDataIndex++;
                    }
                }
            }

            //Print Destination Array
            PrintArrayData(destinationArray, rowCount, columnCount);
        }

        /// <summary>
        /// Initializes Source Array and Massages data into a more consumable form
        /// Input :{"ab","ab","ab","FREE","me","me","me","FREE","mo","mo","FREE","FREE"},
        ///         {"so","so","FREE","no","no","FREE","to","to","to","FREE","do","do"}
        ///         
        /// Output : 0, {{ab,3},{FREE,1},{me,3},{FREE,1},{mo,2},{FREE,2}}
        ///          1, {{so,2},{FREE,1},{no,2},{FREE,1},{to,3},{FREE,1},{do,2}}
        /// </summary>
        /// <param name="sourceArray">Source Array</param>
        /// <param name="rowCount">Row Count</param>
        /// <param name="columnCount">Column Count</param>
        /// <param name="maxCount">Max Count of any String</param>
        /// <param name="sourceRowData"></param>
        public static void Initialize(string[,] sourceArray, int rowCount, int columnCount, ref int maxCount, Dictionary<int, List<StringCount>> sourceRowData)
        {
            for (int row = 0; row < rowCount; row++)
            {
                var list = new List<StringCount>();
                for (int column = 0; column < columnCount; column++)
                {
                    if (list.FirstOrDefault(p => p.value == sourceArray[row, column]) == null)
                        list.Add(new StringCount(sourceArray[row, column], 1));
                    else
                    {
                        var data = list.LastOrDefault(p => p.value == sourceArray[row, column]);
                        var currentValue = sourceArray[row, column];
                        var previousValue = sourceArray[row, column - 1];

                        if (previousValue == currentValue)
                            data.count++;
                        else
                            list.Add(new StringCount(sourceArray[row, column], 1));

                        if (data.count > maxCount)
                            maxCount = data.count;
                    }
                }
                sourceRowData.Add(row, list);
            }
        }

        /// <summary>
        /// Gets List of words with similar number of occurences.
        /// input : 2
        ///         0, {{ab,3},{FREE,1},{me,3},{FREE,1},{mo,2},{FREE,2}}
        ///         1, {{so,2},{FREE,1},{no,2},{FREE,1},{to,3},{FREE,1},{do,2}}
        /// 
        /// 
        /// output : 2,{{mo,2},{FREE,2},{so,2},{no,2},{do,2}} - Not necessarily in that order.
        /// </summary>
        /// <param name="count">Occurance Count</param>
        /// <param name="rowData">Source Row Data</param>
        /// <returns></returns>
        public static List<StringCount> GetDataMatchingCount(int count, Dictionary<int, List<StringCount>> rowData)
        {
            var stringCountList = new List<StringCount>();
            var random = new Random();
            var rowList = rowData.Where(p => p.Value.FirstOrDefault(q => q.count == count) != null).OrderBy(x => random.Next());
            foreach (var row in rowList)
            {
                stringCountList.AddRange(row.Value.Where(p => p.count == count).Reverse());
            }
            return stringCountList;
        }

        /// <summary>
        /// Prints Arrays
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rowCount"></param>
        /// <param name="columnCount"></param>
        public static void PrintArrayData(string[,] data,int rowCount,int columnCount) 
        {
            for (int row = 0; row < rowCount; row++)
            {
                for (int column = 0; column < columnCount; column++)
                {
                    Console.Write(data[row, column] + " ");
                }
                Console.WriteLine();
            }
        }
    }

    public class StringCount
    {
        /// <summary>
        /// Value of String
        /// </summary>
        public string value { get; set; }

        /// <summary>
        /// Count of The String
        /// </summary>
        public int count { get; set; }

        public StringCount(string _stringValue, int _count)
        {
            value = _stringValue;
            count = _count;
        }
    }
}
cvraman
  • 1,687
  • 1
  • 12
  • 18
  • Sadly, another requirement was that different sets with the same text don't randomly move adjacent to each other. That is "ab, ab, FREE, mo, FREE, FREE" cannot become "mo, FREE, FREE, FREE, ab, ab". +1 regardless because great work! Even works for cases where it's not possible to shift sets because the numbers are not divisible into 12 (e.g., a set of 7 + 5, and a set of 4+4+4; it's not possible to shift any of those and still keep a size of 12) – Chris Sinclair May 18 '13 at 13:53
  • Hmm.... did not consider that. So if sets cannot randomly move adjacent to each other, how are they supposed to move ? – cvraman May 18 '13 at 14:05
  • Randomly next to non-matching sets I guess. (might even introduce corner case conditions where such a move is not possible) Honestly, the constraints/requirements for this seem kind of contrived; I suspect there's simply an easier way of handling the application processes that avoids this random packing requirements altogether. – Chris Sinclair May 18 '13 at 14:15
  • @cvraman the way the syntax is if i wanted to change the row to 4. what else would be in need of change? Sorry i probably know its a dumb question but please just put me through in other to edit it properly to fit my solution –  May 20 '13 at 13:47
  • @user2227986 : You would be required to do 2 things in the above code: 1. Change the rowCount to 4. const int rowCount = 4; 2. Initialize sourceArray appropriately. Right now the sourceArray has 2 rows. You would be required to add 2 more. – cvraman May 20 '13 at 14:26
  • @cvraman: Okeyi i edit parts of your code and i understand a lot chunk of it but am trying to put a "FREE" in between every "mo" or "ab" kindly guide me –  May 30 '13 at 21:54
2

I quickly wrote some code for single-dimension array(though I can easily modify it to be capable for working with multi-dimensional one. ). So far I'm posting, and please let me know whether I'm on the right way.

UPDATE
Updated code: now it works with multidimensional arays(tested).
UPDATE2
I've found some bugs in my code and fixed them. So now it works except of requirement that same items can't be next to each other is not satisfied yet. But this problem got me and I gotta solve it till the end. Stay tuned ;)

 using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ShuffleTwoDimensionalArrayConsole
{
    public sealed class PackedItem
    {
        public string Value { get; private set; }
        public int Count { get; set; }

        public PackedItem(string value)
        {
            Value = value;
            Count = 1;
        }

        public string[] Expand()
        {
            string[] result = new string[Count];

            for (int i = 0; i < Count; i++)
            {
                result[i] = Value;
            }

            return result;
        }

        public override string ToString()
        {
            return string.Format("{0} - {1}", Value, Count);
        }
    }

    public static class Extensions
    {
        public static List<PackedItem> WithExcluded(this List<PackedItem> list, PackedItem item)
        {
            var list2 = list.ToList();
            list2.Remove(item);
            return list2;
        }

        public static List<PackedItem> WithIncluded(this List<PackedItem> list, PackedItem item)
        {
            var list2 = list.ToList();
            list2.Add(item);
            return list2;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string[,] input = new string[,]
            { 
                 { "ab","ab","ab","FREE","me","me","me","FREE","mo","mo","FREE","FREE"},
                 { "so","so","FREE","no","no","FREE","to","to","to","FREE","do","do"}
            };
            Console.WriteLine("Input:");
            Console.WriteLine(string.Join(", ", string.Join(", ", input.Cast<string>())));

            bool hasErrrors = false;
            int MAX_ITERATIONS = 10000;
            for (int i = 1; i <= MAX_ITERATIONS; i++)
            {
                try
                {
                    string[,] shuffled = Shuffle(input);

                    //Console.WriteLine("Shuffled:");
                    //Console.WriteLine(string.Join(", ", string.Join(", ", shuffled.Cast<string>())));
                    Verification.Verify(input, shuffled);
                    //Console.WriteLine("Verified");
                }
                catch (Exception exc)
                {
                    Console.WriteLine(exc.Message);
                    hasErrrors = true;
                }

                WriteProgress((1d * i) / MAX_ITERATIONS);
            }

            Console.WriteLine("Completed with {0}", (hasErrrors ? "errors" : "successfully"));
        }

        public static string[,] Shuffle(string[,] array)
        {
            List<PackedItem> packed = Pack(array);
            List<PackedItem> shuffled = Shuffle(packed);
            string[,] unpacked = Unpack(inputList: shuffled
                                        , rows: array.GetLength(0)
                                        , columns: array.GetLength(1));

            return unpacked;
        }

        private static List<PackedItem> Pack(string[,] array)
        {
            var list = new List<PackedItem>();



            for (int i = 0; i < array.GetLength(0); i++)
            {
                for (int j = 0; j < array.GetLength(1); j++)
                {
                    string s = array[i, j];

                    if (j == 0 || list.Count == 0)
                    {
                        list.Add(new PackedItem(s));
                        continue;
                    }

                    var last = list.Last();
                    if (s == last.Value)
                    {
                        last.Count += 1;
                        continue;
                    }
                    else
                    {
                        list.Add(new PackedItem(s));
                        continue;
                    }
                }
            }

            return list;
        }

        private static string[,] Unpack(List<PackedItem> inputList, int rows, int columns)
        {
            var list = inputList.ToList();
            string[,] result = new string[rows, columns];

            for (int i = 0; i < rows; i++)
            {
                List<PackedItem> packedRow = Pick(source: list, taken: new List<PackedItem>(), takeCount: columns);

                packedRow.ForEach(x => list.Remove(x));
                List<string> row = packedRow
                                    .Select(x => x.Expand())
                                    .Aggregate(seed: new List<string>(),
                                            func: (acc, source) => { acc.AddRange(source); return acc; });

                for (int j = 0; j < columns; j++)
                {
                    result[i, j] = row[j];
                }
            }

            return result;
        }

        private static List<PackedItem> Pick(List<PackedItem> source, List<PackedItem> taken, int takeCount)
        {
            if (taken.Sum(x => x.Count) == takeCount)
            {
                return taken;
            }
            foreach (var item in source.ToList())
            {
                var list = Pick(source.WithExcluded(item)
                                , taken.WithIncluded(item)
                                , takeCount);
                if (list != null)
                {
                    return list;
                }
            }
            return null;
        }

        private static bool HasAdjacent(List<PackedItem> taken)
        {
            PackedItem previous = null;
            foreach (var item in taken)
            {
                if (previous != null)
                {
                    if (previous.Value == item.Value)
                        return true;
                }
                previous = item;
            }
            return false;
        }

        private static List<PackedItem> Shuffle(List<PackedItem> list)
        {
            Random r = new Random();

            var result = list.ToList();

            for (int i = 0; i < list.Count; i++)
            {
                int a = r.Next(0, list.Count);
                int b = r.Next(0, list.Count);

                Swap(result, a, b);
            }

            return result;
        }

        private static void Swap(List<PackedItem> list, int a, int b)
        {
            var temp = list[b];
            list[b] = list[a];
            list[a] = temp;
        }

        private static void WriteProgress(double progress)
        {
            int oldTop = Console.CursorTop;
            int oldLeft = Console.CursorLeft;

            try
            {
                Console.CursorTop = 0;
                Console.CursorLeft = Console.WindowWidth - "xx.yy  %".Length;
                Console.WriteLine("{0:p}", progress);
            }
            finally
            {
                Console.CursorTop = oldTop;
                Console.CursorLeft = oldLeft;
            }
        }

        #region Verification

        private static class Verification
        {
            internal static void Verify(string[,] input, string[,] output)
            {
                VerifyCountsAreEqual(input, output);
                VerifySizesAreEquals(input, output);
                VerifyDoesNotHaveNulls(output);
                VerifyContainsSameItems(input, output);

                // TODO: get alrogith capable to pass next check
                // VerifyContainsNoAdjacentItems(input, output);
            }

            private static void VerifyContainsNoAdjacentItems(string[,] input, string[,] output)
            {
                var inputPacked = Pack(input);
                var outputPacked = Pack(output);

                if (inputPacked.Count() != outputPacked.Count())
                    throw new Exception("There are some adjacent items moved each other");

                foreach (var item in outputPacked)
                {
                    if (item.Count > 3)
                        Debugger.Break();
                    bool existsInOutput = inputPacked.Any(x => AreEqual(x, item));
                    if (!existsInOutput)
                    {
                        throw new Exception("There are some adjacent items moved each other");
                    }
                }
            }

            private static void VerifyContainsSameItems(string[,] input, string[,] output)
            {
                foreach (var item in Pack(input))
                {
                    bool contains = Contains(item, output);
                    if (!contains)
                    {
                        throw new Exception("output does not contain " + item);
                    }
                }
            }

            private static void VerifyCountsAreEqual(string[,] input, string[,] output)
            {
                if (input.Cast<string>().Count() != output.Cast<string>().Count())
                    throw new Exception("items count do not match");
            }

            private static void VerifyDoesNotHaveNulls(string[,] output)
            {
                if (output.Cast<string>().Any(x => x == null))
                {
                    throw new Exception("nulls found");
                }
            }

            private static void VerifySizesAreEquals(string[,] input, string[,] output)
            {
                int inputrows = input.GetLength(0);
                int inputcolumns = input.GetLength(1);

                int outputrows = output.GetLength(0);
                int outputcolumns = output.GetLength(1);

                if (inputrows != outputrows || inputcolumns != outputcolumns)
                    throw new Exception("sizes do not match");
            }

            private static bool Contains(PackedItem item, string[,] array)
            {
                int rows = array.GetLength(0);
                int columns = array.GetLength(1);

                int matchedCount = 0;
                for (int i = 0; i < rows; i++)
                {
                    for (int j = 0; j < columns; j++)
                    {
                        string value = array[i, j];
                        if (value == item.Value)
                        {
                            matchedCount++;
                            if (matchedCount == item.Count)
                            {
                                return true;
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else
                        {
                            matchedCount = 0;
                        }
                    }
                }

                return false;
            }

            private static bool AreEqual(PackedItem a, PackedItem b)
            {
                return a.Count == b.Count && a.Value == b.Value;
            }
        }

        #endregion
    }
}
alex.b
  • 4,547
  • 1
  • 31
  • 52