Related
I have aproblem with testing my unzip method.
My unit test method is like:
[Fact]
public void UnzipData_CalledWithByteArrayParameter_ReturnsString()
{
_serializationService.CallerName = "";
byte[] array = new byte[] { 31, 139, 8, 0, 0, 0, 0, 0, 0, 11, 117, 205, 49, 15, 130, 48, 16, 134, 255, 255, 114, 43, 148, 220, 157, 165, 45, 55, 234, 204, 98, 216, 140, 3, 9, 23, 37, 209, 18, 67, 53, 38, 134, 255, 46, 168, 139, 3, 243, 247, 189, 121, 14, 47, 232, 159, 32, 148, 195, 110, 136, 73, 99, 170, 135, 78, 47, 32, 47, 136, 237, 85, 65, 160, 214, 211, 185, 141, 144, 195, 163, 31, 251, 52, 126, 174, 93, 155, 150, 137, 145, 130, 65, 103, 152, 26, 44, 165, 172, 4, 109, 65, 155, 210, 57, 79, 25, 178, 32, 206, 213, 114, 221, 235, 237, 174, 99, 210, 238, 175, 129, 105, 202, 191, 56, 175, 226, 219, 97, 13, 174, 12, 178, 97, 219, 80, 16, 14, 98, 185, 32, 31, 188, 117, 62, 67, 90, 133, 127, 205, 12, 31, 223, 154, 207, 196, 62, 247, 0, 0, 0 };
string result = _serializationService.UnzipData(array);
Assert.False(result.Length == 0);
Assert.True(result.Length > array.Length);
}
but result variable is null. Sut based on this answer:
public string UnzipData(byte[] bytes)
{
LoadDictionaries();
CallerName = _messageService.GetCallerName();
using (var msi = new MemoryStream(bytes))
using (var mso = new MemoryStream())
{
try
{
using (var gs = new GZipStream(msi, CompressionMode.Decompress))
{
CopyTo(gs, mso);
}
return Encoding.UTF8.GetString(mso.ToArray());
}
catch (Exception e)
{
_logger.Error(e, MessagesError[CallerName]);
return null;
}
}
}
and
public static void CopyTo(Stream src, Stream dest)
{
byte[] bytes = new byte[4096];
int cnt;
while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0)
{
dest.Write(bytes, 0, cnt);
}
}
The main problem is that I am not quite sure how to pass byte[] compressed by GZipStream data in my unit test.
As I belive, in test preparation I am not supposed to do something like this? Is not it breaking testability?
[Fact]
public void UnzipData_CalledWithByteArrayParameter_ReturnsString()
{
_serializationService.CallerName = "";
string someString = "fdfsdfsdfsdfsf";
byte[] array = _serializationService.ZipData(someString); //using compress method first????
string result = _serializationService.UnzipData(array);
Assert.False(result.Length == 0);
Assert.True(result.Length > array.Length);
}
Or maybe I can?
ZipData method:
public byte[] ZipData(string data)
{
LoadDictionaries();
CallerName = _messageService.GetCallerName();
var bytes = Encoding.UTF8.GetBytes(data);
using (var msi = new MemoryStream(bytes))
using (var mso = new MemoryStream())
{
try
{
using (var gs = new GZipStream(mso, CompressionMode.Compress))
{
CopyTo(msi, gs);
}
byte[] toReturn = mso.ToArray();
return toReturn;
}
catch (Exception e)
{
_logger.Error(e, MessagesError[CallerName]);
return null;
}
}
}
And compressing method returns me:
new byte[] { 31, 139, 8, 0, 0, 0, 0, 0, 0, 11, 125, 206, 61, 11, 194, 48, 16, 6, 224, 255, 114, 171, 77, 185, 94, 62, 74, 179, 234, 234, 34, 221, 196, 33, 144, 27, 2, 53, 197, 38, 138, 80, 250, 223, 141, 90, 16, 151, 194, 45, 7, 247, 222, 243, 158, 103, 8, 79, 176, 77, 5, 251, 49, 102, 142, 249, 56, 122, 30, 192, 206, 16, 221, 149, 193, 194, 193, 69, 7, 21, 120, 151, 223, 27, 97, 99, 4, 106, 129, 166, 39, 178, 100, 202, 212, 168, 100, 139, 170, 219, 33, 189, 196, 245, 244, 196, 183, 59, 167, 204, 254, 47, 3, 203, 182, 125, 61, 218, 240, 166, 129, 35, 151, 63, 143, 144, 66, 78, 159, 110, 63, 93, 9, 236, 4, 233, 30, 181, 45, 5, 164, 172, 165, 54, 70, 181, 155, 250, 154, 41, 250, 229, 5, 222, 237, 15, 25, 239, 0, 0, 0 }
from string:
[{\"ix\":1,\"ContentModel\":{\"name\":\"Dana\",\"date\":\"2016-05-06T22:26:26.0437049+02:00\",\"dateRequested\":\"2016-05-06\"}},{\"ix\":2,\"ContentModel\":{\"name\":\"Darlene\",\"visits\":1,\"date\":\"2014-09-25T05:22:33.3566479+02:00\",\"dateRequested\":\"2014-09-25\"}}]
UPDATED TEST
[Fact]
public void UnzipData_CalledWithByteArrayParameter_ReturnsString()
{
//string _jsonExample i converted in txtwizard.net/compression
string _jsonExample = "[{\"ix\":1,\"ContentModel\":{\"name\":\"Dana\",\"date\":\"2016-05-06T22:26:26.0437049+02:00\",\"dateRequested\":\"2016-05-06\"}},{\"ix\":2,\"ContentModel\":{\"name\":\"Darlene\",\"visits\":1,\"date\":\"2014-09-25T05:22:33.3566479+02:00\",\"dateRequested\":\"2014-09-25\"}}]";
var base64String = "H4sIAAAAAAAA/4WOPwvCMBDFv0tWm3K9/CnNqquLdDMOgdwQqCmaKIL0uxu1irgUbjjevXfvt79bFm6WmaaybD3GTDFvR09DkcopuiOVzbKNi86y4vEuvxWERnNQHHSPaFCXqUGKFmS3AjQAX/uOThdKmfxfzrJpqj79uNR/HijS6+c1pJDTzPzLIzl0HFUPyhQkIWqhtJbtIs+ce/IcHmtZn30RAQAA";
byte[] array = Convert.FromBase64String(base64String);
_serializationService.CallerName = "";
string result = _serializationService.UnzipData(array);
Assert.Equals(result, _jsonExample); //not equal, added extra backslashes
Assert.Equal("SomeMethod", _serializationService.CallerName);
_messageServiceMock.Verify(m => m.GetCallerName(It.IsAny<string>()), Times.Once);
}
Your source data in:-
byte[] array = new byte[] { 31, 139, 8, 0, 0, 0, 0, 0, 0, 11, 117, 205, 49, 15, 130, 48, 16, 134, 255, 255, 114, 43, 148, 220, 157, 165, 45, 55, 234, 204, 98, 216, 140, 3, 9, 23, 37, 209, 18, 67, 53, 38, 134, 255, 46, 168, 139, 3, 243, 247, 189, 121, 14, 47, 232, 159, 32, 148, 195, 110, 136, 73, 99, 170, 135, 78, 47, 32, 47, 136, 237, 85, 65, 160, 214, 211, 185, 141, 144, 195, 163, 31, 251, 52, 126, 174, 93, 155, 150, 137, 145, 130, 65, 103, 152, 26, 44, 165, 172, 4, 109, 65, 155, 210, 57, 79, 25, 178, 32, 206, 213, 114, 221, 235, 237, 174, 99, 210, 238, 175, 129, 105, 202, 191, 56, 175, 226, 219, 97, 13, 174, 12, 178, 97, 219, 80, 16, 14, 98, 185, 32, 31, 188, 117, 62, 67, 90, 133, 127, 205, 12, 31, 223, 154, 207, 196, 62, 247, 0, 0, 0 };
Is not valid Gzip data, and your method is throwing an InvalidDataException where you are returning null.
Otherwise, your method works fine with correct input data.
Using the core parts of your provided code to create a minimal example of the subject under test
public class Subject {
public byte[] ZipData(string data) {
//LoadDictionaries();
//CallerName = _messageService.GetCallerName();
var bytes = Encoding.UTF8.GetBytes(data);
using (var msi = new MemoryStream(bytes))
using (var mso = new MemoryStream()) {
try {
using (var gs = new GZipStream(mso, CompressionMode.Compress)) {
msi.CopyTo(gs);
}
byte[] toReturn = mso.ToArray();
return toReturn;
} catch (Exception e) {
// _logger.Error(e, MessagesError[CallerName]);
return Array.Empty<byte>();
}
}
}
public string UnzipData(byte[] bytes) {
//LoadDictionaries();
//CallerName = _messageService.GetCallerName();
using (var msi = new MemoryStream(bytes))
using (var mso = new MemoryStream()) {
try {
using (var gs = new GZipStream(msi, CompressionMode.Decompress)) {
gs.CopyTo(mso);
}
return Encoding.UTF8.GetString(mso.ToArray());
} catch (Exception e) {
//_logger.Error(e, MessagesError[CallerName]);
return string.Empty;
}
}
}
}
The following test behaves as expected when exercised
[TestClass]
public class MyTestClass {
[TestMethod]
public void UnzipData_CalledWithByteArrayParameter_ReturnsString() {
//Arrange
var _serializationService = new Subject();
string expected = "[{\"ix\":1,\"ContentModel\":{\"name\":\"Dana\",\"date\":\"2016-05-06T22:26:26.0437049+02:00\",\"dateRequested\":\"2016-05-06\"}},{\"ix\":2,\"ContentModel\":{\"name\":\"Darlene\",\"visits\":1,\"date\":\"2014-09-25T05:22:33.3566479+02:00\",\"dateRequested\":\"2014-09-25\"}}]";
byte[] array = _serializationService.ZipData(expected);
//Act
string actual = _serializationService.UnzipData(array);
//Assert
actual.Should().NotBeNull()
.And.Be(expected);
}
}
Note the changes made to the used functions. Mainly not returning null which can bring its own complications.
Your assertions were also in accurate since the string and byte array lengths wont match given the zip process.
In my opinion zipping the data as part of arranging the test does not break isolation since the zip is not what is being tested (technically).
You could easily have rewritten the zip code again manually in the test to ensure you have correct data to supply to the member under test but why repeat existing functionality.
That would not be very DRY.
The assumption here would be that the zip functionality would have also had its own isolated unit test. If its test failed then that member is already covered.
I'm trying to implement knapsack problem with 3 constraint with google-or tools.
Let's say i want to have an additional property called size for each of the item. So each item with have 3 property and i have to maximize the total value for the items.
KnapsackSolver k = new KnapsackSolver(KnapsackSolver.KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, "mybin");
long[, ,] profits = { {{ 10,20,30} ,{40,50,60}} };
long[,] weights = {{44,21}};
long[] capa = { 110 };
k.Init(profits, weights, capa);
But it is not going anywhere. can somebody please correct me.
See https://github.com/google/or-tools/blob/stable/examples/dotnet/csknapsack.cs
long[] profits = { 360, 83, 59, 130, 431, 67, 230, 52, 93,
125, 670, 892, 600, 38, 48, 147, 78, 256,
63, 17, 120, 164, 432, 35, 92, 110, 22,
42, 50, 323, 514, 28, 87, 73, 78, 15,
26, 78, 210, 36, 85, 189, 274, 43, 33,
10, 19, 389, 276, 312 };
long[,] weights = { { 7, 0, 30, 22, 80, 94, 11, 81, 70,
64, 59, 18, 0, 36, 3, 8, 15, 42,
9, 0, 42, 47, 52, 32, 26, 48, 55,
6, 29, 84, 2, 4, 18, 56, 7, 29,
93, 44, 71, 3, 86, 66, 31, 65, 0,
79, 20, 65, 52, 13 } };
long[] capacities = { 850 };
profits is a long[]
weights is long[,]
capacities is a long[].
I'm writing code for an embedded device using .Net Micro Framework, I have a set of calculated integers as shown by the link below which I need to access but I'd like it if this information could be minimized somehow, basically I have a list of numbers ranging from 150 - 4, each number has an adjacent list of numbers that it can be linked to, ex 150 can be linked to 149, 148, 146, 144, 142 etc, some numbers (150 to 4) share linked numbers 150 & 149 share 91 as an example as well as many others, so I'm thinking there must be someway of representing all these numbers were the memory footprint is reduced since there are a lot of share linked numbers, any suggestions would be great.
https://www.dropbox.com/s/qqn8097571r9s27/LinkedNumbers.txt
Example Data
150 = 149, 148, 146, 144, 142, 140, 138, 136, 134, 99, 93, 92, 91, 87
149 = 148, 147, 145, 143, 141, 139, 137, 135, 133, 131, 129, 103, 102, 97, 96, 93, 92, 91
148 = 147, 146, 145, 144, 142, 140, 138, 136, 134, 132, 99, 97, 94, 92, 91, 88, 87, 86
147 = 146, 145, 144, 143, 141, 139, 137, 135, 133, 131, 95, 91, 90, 89, 88
Store each row as a 19 byte bit sequence where a one represents an entry in your list and a zero represents a value not in your list.
For example, your first entry is:-
150 = 149, 148, 146, 144, 142, 140, 138, 136, 134, 99, 93, 92, 91, 87,
83, 79, 77, 65, 63, 59, 55, 54, 53, 51, 49, 48, 47, 46, 45, 44,
43, 42, 41, 40, 39, 38, 37, 35, 34, 33, 31, 29, 27, 25, 23, 22,
21, 19, 18, 17, 14, 13, 12, 11, 9, 8, 7, 6, 5, 4, 3, 2
can be store as:-
150 = 0,0,1,1,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0.....
where the first bit is represents 0, the second represents 1, etc.
You can improve the efficiency of the storage by decreasing the number of bits used for each row, but the computation of the bit to check becomes a bit more complex. Using this, the whole lot can be stored in 1416 bytes.
If the maximum decrement between two sequential numbers doesn't exceed, for instance, 64, then you can store each sequence as a list of 6-bit unsigned decrement values.
Example:
150 = 149, 148, 146, 144, 142, 140, 138, 136, 134, 99, 93, 92, 91, 87
Becomes a:
150 = 1, 1, 2, 2, 2, 2, 2, 2, 2, 35, 6, 1, 1, 4
I understand WP7 support limited number of language as Culture for localization BUT Silverlight itself support more language so my question is Can I make culture info take arguments of "any from supported language" and in resource file string write my own "needed" to support language in my app in this case it's Arabic language ??
in other words file resource.fr.resx and supply strings with Arabic data ?
Will it pass for certification?
For Windows Phone 7 i wrote a mapping function which maps the Arabic encoding[windows-1256] into default WP7 encoding.
public static string ConvertToArabic(string s)
{
short[] mapping = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 8364, 1662, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 1657, 8249, 338, 1670, 1688, 1672, 1711, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 1705, 8482, 1681, 8250, 339, 8204, 8205, 1722, 160, 1548, 162, 163, 164, 165, 166, 167, 168, 169, 1726, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 1563, 187, 188, 189, 190, 1567, 1729, 1569, 1570, 1571, 1572, 1573, 1574, 1575, 1576, 1577, 1578, 1579, 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1587, 1588, 1589, 1590, 215, 1591, 1592, 1593, 1594, 1600, 1601, 1602, 1603, 224, 1604, 226, 1605, 1606, 1607, 1608, 231, 232, 233, 234, 235, 1609, 1610, 238, 239, 1611, 1612, 1613, 1614, 244, 1615, 1616, 247, 1617, 249, 1618, 251, 252, 8206, 8207, 1746 };
string str = string.Empty;
for (int ix = 0; ix < s.Length; ++ix)
{
str = str + (char)mapping[s[ix]];
}
return str;
}
This worked for me perfectly.
Windows Phone 7 does not support Arabic character sets (But Windows Phone 8 will).
Some applications have worked around this with custom fonts but this approach will require lots of careful testing.
I have a list of elements which can be easily compared using Equals(). I have to shuffle the list, but the shuffle must satisfy one condition:
The i'th element shuffledList[i] must not equal elements at i +/- 1 nor elements at i +/- 2. The list should be considered circular; that is, the last element in the list is followed by the first, and vice versa.
Also, if possible, I would like to check if the shuffle is even possible.
Note:
I'm using c# 4.0.
EDIT:
Based on some responses, I will explain a little more:
The list wont have more than 200 elements, so there isn't a real need for good performance. If it takes 2 secs to calculate it it's not the best thing, but it's not the end of the world either. The shuffled list will be saved, and unless the real list changes, the shuffled list will be used.
Yes, it's a "controlled" randomness, but I expect that severals run on this method would return different shuffled lists.
I will make further edits after I try some of the responses below.
EDIT 2:
Two sample list that fails with Sehe's implementation (Both have solutions):
Sample1:
`List<int> list1 = new List<int>{0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,9,10};`
Possible solution:
List<int> shuffledList1 = new List<int>
{9,3,1,4,7,9,2,6,8,1,4,9,2,0,6,5,7,8,4,3,10,9,6,7,8,5,3,9,1,2,7,8}
Sample 2:
`List<int> list2 = new List<int> {0,1,1,2,2,2,3,3,4,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,8,8,9,9,9,9,10};`
Verify: I'm using this method, that it's not the most eficient nor elegant piece of code I've made, but it does it's work:
public bool TestShuffle<T>(IEnumerable<T> input)
{
bool satisfied = true;
int prev1 = 0; int prev2 = 0;
int next1 = 0; int next2 = 0;
int i = 0;
while (i < input.Count() && satisfied)
{
prev1 = i - 1; prev2 = i - 2; next1 = i + 1; next2 = i + 2;
if (i == 0)
{
prev1 = input.Count() - 1;
prev2 = prev1 - 1;
}
else if (i == 1)
prev2 = input.Count() - 1;
if (i == (input.Count() - 1))
{
next1 = 0;
next2 = 1;
}
if (i == (input.Count() - 2))
next2 = 0;
satisfied =
(!input.ElementAt(i).Equals(input.ElementAt(prev1)))
&& (!input.ElementAt(i).Equals(input.ElementAt(prev2)))
&& (!input.ElementAt(i).Equals(input.ElementAt(next1)))
&& (!input.ElementAt(i).Equals(input.ElementAt(next2)))
;
if (satisfied == false)
Console.WriteLine("TestShuffle fails at " + i);
i++;
}
return satisfied;
}
EDIT 3:
Another test input that fails sometimes:
List<int> list3 = new List<int>(){0,1,1,2,2,3,3,3,4,4,4,5,5,5,5,6,6,6,6,7,7,7,8,8,8,8,9,9,9,9,10,10};
Optimized version
To my disappointment, my optimized function only runs 7x faster than the LINQ 'straightforward' version. Unoptimized LINQ 1m43s Optimized 14.7s.
linux 32-bit
Mono 2.11 (C# 4.0) compiler with -optimize+,
1,000,000 TESTITERATIONS
VERBOSE not #define-d
What has been optimized:
assume arrays for input and output
work in-place on input array
'manually' analyze runs of identical values, instead of using GroupBy (using ValueRun struct)
have these ValueRun structs in an Array instead of Enumerable (List); sort/shuffle in-place
use unsafe blocks and pointers (no discernable difference...)
use modulo indexing instead of MAGIC Linq code
generate the output by iterative append instead of nested LINQ. This probably had the most effect. Actually, it would be even better when we could shortcut the ValueRuns that have a countruns collection is being ordered by this count, it seemed pretty easy to do; however, the transposed indexing (needed for the cyclic constraints) is complicating things. The gain of somehow applying this optimization anyway will be bigger with larger inputs and many unique values and some highly duplicated values.
Here is the code with optimized version. _Additional speed gain can be had by removing the seeding of RNGs; these are only there to make it possible to regression test the output.
[... old code removed as well ...]
Original response (partial)
If I'm getting you right, you are trying to devise a shuffle that prevents duplicates from ending up consecutive in the output (with a minimum interleave of 2 elements).
This is not solvable in the general case. Imagine an input of only identical elements :)
Update: Troubled state of affairs
Like I mention in the notes, I think I wasn't on the right track all the time. Either I should invoke graph theory (anyone?) or use a simple 'bruteforcey' algorithm instead, much a long Erick's suggestion.
Anyway, so you can see what I've been up to, and also what the problems are (enable the randomized samples to quickly see the problems):
#define OUTPUT // to display the testcase results
#define VERIFY // to selfcheck internals and verify results
#define SIMPLERANDOM
// #define DEBUG // to really traces the internals
using System;
using System.Linq;
using System.Collections.Generic;
public static class Q5899274
{
// TEST DRIVER CODE
private const int TESTITERATIONS = 100000;
public static int Main(string[] args)
{
var testcases = new [] {
new [] {0,1,1,2,2,2,3,3,4,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,8,8,9,9,9,9,10},
new [] {0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,9,10},
new [] { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41, 42, 42, 42, },
new [] {1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4},
}.AsEnumerable();
// // creating some very random testcases
// testcases = Enumerable.Range(0, 10000).Select(nr => Enumerable.Range(GROUPWIDTH, _seeder.Next(GROUPWIDTH, 400)).Select(el => _seeder.Next(-40, 40)).ToArray());
foreach (var testcase in testcases)
{
// _seeder = new Random(45); for (int i=0; i<TESTITERATIONS; i++) // for benchmarking/regression
{
try
{
var output = TestOptimized(testcase);
#if OUTPUT
Console.WriteLine("spread\t{0}", string.Join(", ", output));
#endif
#if VERIFY
AssertValidOutput(output);
#endif
} catch(Exception e)
{
Console.Error.WriteLine("Exception for input {0}:", string.Join(", ", testcase));
Console.Error.WriteLine("Sequence length {0}: {1} groups and remainder {2}", testcase.Count(), (testcase.Count()+GROUPWIDTH-1)/GROUPWIDTH, testcase.Count() % GROUPWIDTH);
Console.Error.WriteLine("Analysis: \n\t{0}", string.Join("\n\t", InternalAnalyzeInputRuns(testcase)));
Console.Error.WriteLine(e);
}
}
}
return 0;
}
#region Algorithm Core
const int GROUPWIDTH = 3; /* implying a minimum distance of 2
(GROUPWIDTH-1) values in between duplicates
must be guaranteed*/
public static T[] TestOptimized<T>(T[] input, bool doShuffle = false)
where T: IComparable<T>
{
if (input.Length==0)
return input;
var runs = InternalAnalyzeInputRuns(input);
#if VERIFY
CanBeSatisfied(input.Length, runs); // throws NoValidOrderingExists if not
#endif
var transpositions = CreateTranspositionIndex(input.Length, runs);
int pos = 0;
for (int run=0; run<runs.Length; run++)
for (int i=0; i<runs[run].runlength; i++)
input[transpositions[pos++]] = runs[run].value;
return input;
}
private static ValueRun<T>[] InternalAnalyzeInputRuns<T>(T[] input)
{
var listOfRuns = new List<ValueRun<T>>();
Array.Sort(input);
ValueRun<T> current = new ValueRun<T> { value = input[0], runlength = 1 };
for (int i=1; i<=input.Length; i++)
{
if (i<input.Length && input[i].Equals(current.value))
current.runlength++;
else
{
listOfRuns.Add(current);
if (i<input.Length)
current = new ValueRun<T> { value = input[i], runlength = 1 };
}
}
#if SIMPLERANDOM
var rng = new Random(_seeder.Next());
listOfRuns.ForEach(run => run.tag = rng.Next()); // this shuffles them
#endif
var runs = listOfRuns.ToArray();
Array.Sort(runs);
return runs;
}
// NOTE: suboptimal performance
// * some steps can be done inline with CreateTranspositionIndex for
// efficiency
private class NoValidOrderingExists : Exception { public NoValidOrderingExists(string message) : base(message) { } }
private static bool CanBeSatisfied<T>(int length, ValueRun<T>[] runs)
{
int groups = (length+GROUPWIDTH-1)/GROUPWIDTH;
int remainder = length % GROUPWIDTH;
// elementary checks
if (length<GROUPWIDTH)
throw new NoValidOrderingExists(string.Format("Input sequence shorter ({0}) than single group of {1})", length, GROUPWIDTH));
if (runs.Length<GROUPWIDTH)
throw new NoValidOrderingExists(string.Format("Insufficient distinct values ({0}) in input sequence to fill a single group of {1})", runs.Length, GROUPWIDTH));
int effectivewidth = Math.Min(GROUPWIDTH, length);
// check for a direct exhaustion by repeating a single value more than the available number of groups (for the relevant groupmember if there is a remainder group)
for (int groupmember=0; groupmember<effectivewidth; groupmember++)
{
int capacity = remainder==0? groups : groups -1;
if (capacity < runs[groupmember].runlength)
throw new NoValidOrderingExists(string.Format("Capacity exceeded on groupmember index {0} with capacity of {1} elements, (runlength {2} in run of '{3}'))",
groupmember, capacity, runs[groupmember].runlength, runs[groupmember].value));
}
// with the above, no single ValueRun should be a problem; however, due
// to space exhaustion duplicates could end up being squeezed into the
// 'remainder' group, which could be an incomplete group;
// In particular, if the smallest ValueRun (tail) has a runlength>1
// _and_ there is an imcomplete remainder group, there is a problem
if (runs.Last().runlength>1 && (0!=remainder))
throw new NoValidOrderingExists("Smallest ValueRun would spill into trailing incomplete group");
return true;
}
// will also verify solvability of input sequence
private static int[] CreateTranspositionIndex<T>(int length, ValueRun<T>[] runs)
where T: IComparable<T>
{
int remainder = length % GROUPWIDTH;
int effectivewidth = Math.Min(GROUPWIDTH, length);
var transpositions = new int[length];
{
int outit = 0;
for (int groupmember=0; groupmember<effectivewidth; groupmember++)
for (int pos=groupmember; outit<length && pos<(length-remainder) /* avoid the remainder */; pos+=GROUPWIDTH)
transpositions[outit++] = pos;
while (outit<length)
{
transpositions[outit] = outit;
outit += 1;
}
#if DEBUG
int groups = (length+GROUPWIDTH-1)/GROUPWIDTH;
Console.WriteLine("Natural transpositions ({1} elements in {0} groups, remainder {2}): ", groups, length, remainder);
Console.WriteLine("\t{0}", string.Join(" ", transpositions));
var sum1 = string.Join(":", Enumerable.Range(0, length));
var sum2 = string.Join(":", transpositions.OrderBy(i=>i));
if (sum1!=sum2)
throw new ArgumentException("transpositions do not cover range\n\tsum1 = " + sum1 + "\n\tsum2 = " + sum2);
#endif
}
return transpositions;
}
#endregion // Algorithm Core
#region Utilities
private struct ValueRun<T> : IComparable<ValueRun<T>>
{
public T value;
public int runlength;
public int tag; // set to random for shuffling
public int CompareTo(ValueRun<T> other) { var res = other.runlength.CompareTo(runlength); return 0==res? tag.CompareTo(other.tag) : res; }
public override string ToString() { return string.Format("[{0}x {1}]", runlength, value); }
}
private static /*readonly*/ Random _seeder = new Random(45);
#endregion // Utilities
#region Error detection/verification
public static void AssertValidOutput<T>(IEnumerable<T> output)
where T:IComparable<T>
{
var repl = output.Concat(output.Take(GROUPWIDTH)).ToArray();
for (int i=1; i<repl.Length; i++)
for (int j=Math.Max(0, i-(GROUPWIDTH-1)); j<i; j++)
if (repl[i].Equals(repl[j]))
throw new ArgumentException(String.Format("Improper duplicate distance found: (#{0};#{1}) out of {2}: value is '{3}'", j, i, output.Count(), repl[j]));
}
#endregion
}
Your requirements eliminate the real shuffling alternative: there is no randomness, or there is controlled randomness.
Here is one special approach
Sort the original list L
Find the mode M, and its frequency n
If n is even, n++.
Create k (= 5*n - 1) lists (prime to n, and 5 times n) L1 through Lk
(5 is chosen to avoid two previous elements and two next elements)
Assign all elements into the k lists in round robin fashion
Individually shuffle all k lists.
Collate the contents of k lists in the following order:
a. pick randomly +5 or -5 as x.
b. pick a random number j.
c. repeat k times:
i. add all contents from Lj.
ii. j <- (j + x) mod k
[5, 6, 7, 7, 8, 8, 9, 10, 12, 13, 13, 14, 14, 14, 17, 18, 18, 19, 19, 20, 21, 21, 21, 21, 24, 24, 26, 26, 26, 27, 27, 27, 29, 29, 30, 31, 31, 31, 31, 32, 32, 32, 33, 35, 35, 37, 38, 39, 40, 42, 43, 44, 44, 46, 46, 47, 48, 50, 50, 50, 50, 51, 52, 53, 54, 55, 56, 57, 57, 58, 60, 60, 60, 61, 62, 63, 63, 64, 64, 65, 65, 65, 68, 71, 71, 72, 72, 73, 74, 74, 74, 74, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 80, 81, 82, 86, 88, 88, 89, 89, 90, 91, 92, 92, 92, 93, 93, 94, 94, 95, 96, 99, 99, 100, 102, 102, 103, 103, 105, 106, 106, 107, 108, 113, 115, 116, 118, 119, 123, 124, 125, 127, 127, 127, 128, 131, 133, 133, 134, 135, 135, 135, 137, 137, 137, 138, 139, 141, 143, 143, 143, 145, 146, 147, 153, 156, 157, 158, 160, 164, 166, 170, 173, 175, 181, 181, 184, 185, 187, 188, 190, 200, 200, 215, 217, 234, 238, 240]
Frequency of mode = 4, so 19 slots (#0 - #18)
0: [7, 21, 32, 50, 65, 77, 93, 115, 137, 173]
1: [8, 21, 33, 51, 65, 78, 93, 116, 137, 175]
2: [8, 24, 35, 52, 65, 78, 94, 118, 138, 181]
3: [9, 24, 35, 53, 68, 78, 94, 119, 139, 181]
4: [10, 26, 37, 54, 71, 79, 95, 123, 141, 184]
5: [12, 26, 38, 55, 71, 79, 96, 124, 143, 185]
6: [13, 26, 39, 56, 72, 80, 99, 125, 143, 187]
7: [13, 27, 40, 57, 72, 81, 99, 127, 143, 188]
8: [14, 27, 42, 57, 73, 82, 100, 127, 145, 190]
9: [14, 27, 43, 58, 74, 86, 102, 127, 146, 200]
10: [14, 29, 44, 60, 74, 88, 102, 128, 147, 200]
11: [17, 29, 44, 60, 74, 88, 103, 131, 153, 215]
12: [18, 30, 46, 60, 74, 89, 103, 133, 156, 217]
13: [18, 31, 46, 61, 75, 89, 105, 133, 157, 234]
14: [19, 31, 47, 62, 76, 90, 106, 134, 158, 238]
15: [19, 31, 48, 63, 76, 91, 106, 135, 160, 240]
16: [5, 20, 31, 50, 63, 76, 92, 107, 135, 164]
17: [6, 21, 32, 50, 64, 77, 92, 108, 135, 166]
18: [7, 21, 32, 50, 64, 77, 92, 113, 137, 170]
Shuffling individual lists, and picking lists 5 slots apart (start randomly at #16):
16: [31, 135, 92, 76, 107, 5, 164, 63, 20, 50]
2: [52, 24, 35, 78, 181, 8, 138, 94, 118, 65]
7: [57, 143, 99, 81, 40, 13, 127, 72, 188, 27]
12: [46, 30, 60, 89, 133, 74, 156, 18, 103, 217]
17: [64, 50, 135, 92, 21, 32, 108, 77, 166, 6]
3: [9, 94, 181, 119, 24, 35, 139, 68, 53, 78]
8: [145, 27, 14, 57, 42, 100, 190, 82, 73, 127]
13: [89, 18, 75, 61, 157, 234, 133, 105, 31, 46]
18: [113, 21, 7, 92, 64, 32, 137, 50, 170, 77]
4: [71, 10, 37, 26, 123, 54, 184, 79, 95, 141]
9: [27, 74, 86, 14, 102, 146, 127, 43, 58, 200]
14: [62, 106, 158, 134, 19, 47, 238, 31, 76, 90]
0: [7, 77, 65, 21, 50, 93, 173, 115, 32, 137]
5: [96, 79, 26, 185, 12, 71, 124, 143, 55, 38]
10: [29, 14, 147, 60, 128, 88, 74, 44, 102, 200]
15: [106, 240, 63, 48, 91, 19, 160, 31, 76, 135]
1: [65, 33, 21, 51, 137, 8, 175, 93, 116, 78]
6: [143, 26, 13, 56, 99, 72, 39, 80, 187, 125]
11: [103, 88, 29, 60, 74, 44, 17, 153, 131, 215]
[31, 135, 92, 76, 107, 5, 164, 63, 20, 50, 52, 24, 35, 78, 181, 8, 138, 94, 118, 65, 57, 143, 99, 81, 40, 13, 127, 72, 188, 27, 46, 30, 60, 89, 133, 74, 156, 18, 103, 217, 64, 50, 135, 92, 21, 32, 108, 77, 166, 6, 9, 94, 181, 119, 24, 35, 139, 68, 53, 78, 145, 27, 14, 57, 42, 100, 190, 82, 73, 127, 89, 18, 75, 61, 157, 234, 133, 105, 31, 46, 113, 21, 7, 92, 64, 32, 137, 50, 170, 77, 71, 10, 37, 26, 123, 54, 184, 79, 95, 141, 27, 74, 86, 14, 102, 146, 127, 43, 58, 200, 62, 106, 158, 134, 19, 47, 238, 31, 76, 90, 7, 77, 65, 21, 50, 93, 173, 115, 32, 137, 96, 79, 26, 185, 12, 71, 124, 143, 55, 38, 29, 14, 147, 60, 128, 88, 74, 44, 102, 200, 106, 240, 63, 48, 91, 19, 160, 31, 76, 135, 65, 33, 21, 51, 137, 8, 175, 93, 116, 78, 143, 26, 13, 56, 99, 72, 39, 80, 187, 125, 103, 88, 29, 60, 74, 44, 17, 153, 131, 215]
It can be accomplished with a simple, two-step process. First use a Fisher-Yates (Knuth) shuffle in place. Then iterate over the list once, copying its elements to a new list. As you encounter an element, insert it into the first legal position in your new list. (This will be much more efficient with a linked list than with an array as your destination.) If there are no legal position to insert to, the instance of the problem is unsolvable. If you manage to fill your destination list, problem solved. This will take O(n) in the best case and O(n^2) in the worst.
permute valid 5 in list, if none unsolvable
remove permutation from list
create a cycle graph of that 5
order the list(remaining list) by count of valid positions in new graph (if you dont do this you can end up with a wrong non solvable, because putting items that can go on more positions increases the possible position count of items with less)
continue picking items in the list, add items to cycle graph at valid positions
if there is no valid position in graph or graph cannot be created continue on next
if graph created revert back to start iteration where graph has been created
continue creating other graphs
save all full graphs in a list
pick a random from that list
if list is empty unsolvable