PageRenderTime 84ms CodeModel.GetById 45ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Libraries/CoreNodes/List.cs

https://github.com/adlai/Dynamo
C# | 1245 lines | 490 code | 108 blank | 647 comment | 68 complexity | 7b9e80d8fa090b0042e20f0cb7a2d1d2 MD5 | raw file
  1. #region
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using Autodesk.DesignScript.Runtime;
  7. #endregion
  8. namespace DSCore
  9. {
  10. /// <summary>
  11. /// Methods for creating and manipulating Lists.
  12. /// </summary>
  13. public static class List
  14. {
  15. /// <summary>
  16. /// An Empty List.
  17. /// </summary>
  18. /// <returns name="list">Empty list.</returns>
  19. /// <search>empty list, emptylist</search>
  20. public static IList Empty
  21. {
  22. get { return new ArrayList(); }
  23. }
  24. /// <summary>
  25. /// Creates a new list containing all unique items in the given list.
  26. /// </summary>
  27. /// <param name="list">List to filter duplicates out of.</param>
  28. /// <returns name="list">Filtered list.</returns>
  29. /// <search>removes,duplicates,remove duplicates</search>
  30. public static IList UniqueItems(IList list)
  31. {
  32. return list.Cast<object>().Distinct(DistinctComparer.Instance).ToList();
  33. }
  34. /// <summary>
  35. /// Determines if the given list contains the given item.
  36. /// </summary>
  37. /// <param name="list">List to search in.</param>
  38. /// <param name="item">Item to look for.</param>
  39. /// <returns name="bool">Whether list contains the given item.</returns>
  40. /// <search>item,search</search>
  41. public static bool ContainsItem(IList list, object item)
  42. {
  43. return list.Contains(item);
  44. }
  45. /// <summary>
  46. /// Creates a new list containing the items of the given list but in reverse order.
  47. /// </summary>
  48. /// <param name="list">List to be reversed.</param>
  49. /// <returns name="list">New list.</returns>
  50. /// <search>flip</search>
  51. public static IList Reverse(IList list)
  52. {
  53. return list.Cast<object>().Reverse().ToList();
  54. }
  55. /// <summary>
  56. /// Creates a new list containing the given items.
  57. /// </summary>
  58. /// <param name="items">Items to be stored in the new list.</param>
  59. public static IList __Create(IList items)
  60. {
  61. return items;
  62. }
  63. /// <summary>
  64. /// Build sublists from a list using DesignScript range syntax.
  65. /// </summary>
  66. /// <param name="list">The list from which to create sublists.</param>
  67. /// <param name="ranges">
  68. /// The index ranges of the sublist elements.
  69. /// Ex. \"{0..3,5,2}\"
  70. /// </param>
  71. /// <param name="offset">
  72. /// The offset to apply to the sublist.
  73. /// Ex. the range \"0..3\" with an offset of 2 will yield
  74. /// {0,1,2,3}{2,3,4,5}{4,5,6,7}...
  75. /// </param>
  76. /// <param name="keepIncomplete">
  77. /// Determines if ranges where some indices are out of bounds are kept.
  78. /// If true (default): All ranges are kept, out of bounds indices are ommitted.
  79. /// If false: Any ranges with out of bounds indices are ommitted.</param>
  80. /// <returns name="lists">Sublists of the given list.</returns>
  81. /// <search>sublists,build sublists</search>
  82. public static IList Sublists(IList list, IList ranges, int offset)
  83. {
  84. var result = new ArrayList();
  85. int len = list.Count;
  86. if (offset <= 0)
  87. throw new ArgumentException("Must be greater than zero.", "offset");
  88. for (int start = 0; start < len; start += offset)
  89. {
  90. var row = new List<object>();
  91. foreach (object item in ranges)
  92. {
  93. List<int> subrange;
  94. if (item is ICollection)
  95. subrange = ((IList)item).Cast<int>().Select(Convert.ToInt32).ToList();
  96. else
  97. subrange = new List<int> { Convert.ToInt32(item) };
  98. row.AddRange(
  99. subrange.Where(idx => start + idx < len).Select(idx => list[start + idx]));
  100. }
  101. if (row.Count > 0)
  102. result.Add(row);
  103. }
  104. return result;
  105. }
  106. /// <summary>
  107. /// Sorts a list using the built-in natural ordering.
  108. /// </summary>
  109. /// <param name="list">List to be sorted.</param>
  110. /// <returns name="list">Sorted list.</returns>
  111. /// <search>sort,order</search>
  112. public static IList Sort(IList list)
  113. {
  114. return list.Cast<object>().OrderBy(x => x, new ObjectComparer()).ToList();
  115. }
  116. /// <summary>
  117. /// Returns the minimum value from a list.
  118. /// </summary>
  119. /// <param name="list">List to take the minimum value from.</param>
  120. /// <returns name="min">Minimum value from the list.</returns>
  121. /// <search>least,smallest,find min</search>
  122. public static object MinimumItem(IList list)
  123. {
  124. return list.Cast<object>().Min();
  125. }
  126. /// <summary>
  127. /// Returns the maximum value from a list.
  128. /// </summary>
  129. /// <param name="list">List to take the maximum value from.</param>
  130. /// <returns name="max">Maximum value from the list.</returns>
  131. /// <search>greatest,largest,biggest,find max</search>
  132. public static object MaximumItem(IList list)
  133. {
  134. return list.Cast<object>().Max();
  135. }
  136. /// <summary>
  137. /// Filters a sequence by lookng up corresponding indices in a separate list of
  138. /// booleans.
  139. /// </summary>
  140. /// <param name="list">List to filter.</param>
  141. /// <param name="mask">List of booleans representing a mask.</param>
  142. /// <returns name="in">Items whose mask index is true.</returns>
  143. /// <returns name="out">Items whose mask index is false.</returns>
  144. /// <search>filter,in,out,mask,dispatch</search>
  145. [MultiReturn(new[] { "in", "out" })]
  146. public static Dictionary<string, object> FilterByBoolMask(IList list, IList mask)
  147. {
  148. Tuple<ArrayList, ArrayList> result = FilterByMaskHelper(
  149. list.Cast<object>(),
  150. mask.Cast<object>());
  151. return new Dictionary<string, object>
  152. {
  153. { "in", result.Item1 },
  154. { "out", result.Item2 }
  155. };
  156. }
  157. private static Tuple<ArrayList, ArrayList> FilterByMaskHelper(
  158. IEnumerable<object> list, IEnumerable<object> mask)
  159. {
  160. var inList = new ArrayList();
  161. var outList = new ArrayList();
  162. foreach (var p in list.Zip(mask, (item, flag) => new { item, flag }))
  163. {
  164. if (p.flag is IList && p.item is IList)
  165. {
  166. Tuple<ArrayList, ArrayList> recur =
  167. FilterByMaskHelper(
  168. (p.item as IList).Cast<object>(),
  169. (p.flag as IList).Cast<object>());
  170. inList.Add(recur.Item1);
  171. outList.Add(recur.Item2);
  172. }
  173. else
  174. {
  175. if ((bool)p.flag)
  176. inList.Add(p.item);
  177. else
  178. outList.Add(p.item);
  179. }
  180. }
  181. return Tuple.Create(inList, outList);
  182. }
  183. /// <summary>
  184. /// Given a list, produces the first item in the list, and a new list containing all items
  185. /// except the first.
  186. /// </summary>
  187. /// <param name="list">List to be split.</param>
  188. /// <returns name="first">First item in the list.</returns>
  189. /// <returns name="rest">Rest of the list.</returns>
  190. /// <search>first,rest</search>
  191. [MultiReturn(new[] { "first", "rest" })]
  192. public static IDictionary Deconstruct(IList list)
  193. {
  194. return new Dictionary<string, object>
  195. {
  196. { "first", list[0] },
  197. { "rest", list.Cast<object>().Skip(1).ToList() }
  198. };
  199. }
  200. /// <summary>
  201. /// Adds an item to the beginning of a list.
  202. /// </summary>
  203. /// <param name="item">Item to be added.</param>
  204. /// <param name="list">List to add on to.</param>
  205. /// <returns name="list">New list.</returns>
  206. /// <search>insert,add,item,front</search>
  207. public static IList AddItemToFront([ArbitraryDimensionArrayImport] object item, IList list)
  208. {
  209. var newList = new ArrayList { item };
  210. newList.AddRange(list);
  211. return newList;
  212. }
  213. /// <summary>
  214. /// Adds an item to the end of a list.
  215. /// </summary>
  216. /// <param name="item">Item to be added.</param>
  217. /// <param name="list">List to add on to.</param>
  218. /// <search>insert,add,item,end</search>
  219. public static IList AddItemToEnd(object item, IList list)
  220. {
  221. return new ArrayList(list) //Clone original list
  222. {
  223. item //Add item to the end of cloned list.
  224. };
  225. }
  226. /// <summary>
  227. /// Fetches an amount of items from the start of the list.
  228. /// </summary>
  229. /// <param name="list">List to take from.</param>
  230. /// <param name="amount">
  231. /// Amount of items to take. If negative, items are taken from the end of the list.
  232. /// </param>
  233. /// <returns name="list">List of extracted items.</returns>
  234. /// <search>get,sub,sublist</search>
  235. public static IList TakeItems(IList list, int amount)
  236. {
  237. IEnumerable<object> genList = list.Cast<object>();
  238. return (amount < 0 ? genList.Skip(list.Count + amount) : genList.Take(amount)).ToList();
  239. }
  240. /// <summary>
  241. /// Removes an amount of items from the start of the list.
  242. /// </summary>
  243. /// <param name="list">List to remove items from.</param>
  244. /// <param name="amount">
  245. /// Amount of items to remove. If negative, items are removed from the end of the list.
  246. /// </param>
  247. /// <returns name="list">List of remaining items.</returns>
  248. /// <search>drop,remove</search>
  249. public static IList DropItems(IList list, int amount)
  250. {
  251. IEnumerable<object> genList = list.Cast<object>();
  252. return (amount < 0 ? genList.Take(list.Count + amount) : genList.Skip(amount)).ToList();
  253. }
  254. /// <summary>
  255. /// Shifts indices in the list to the right by the given amount.
  256. /// </summary>
  257. /// <param name="list">List to be shifted.</param>
  258. /// <param name="amount">
  259. /// Amount to shift indices by. If negative, indices will be shifted to the left.
  260. /// </param>
  261. /// <returns name="list">Shifted list.</returns>
  262. /// <search>shift</search>
  263. public static IList ShiftIndices(IList list, int amount)
  264. {
  265. if (amount == 0)
  266. return list;
  267. IEnumerable<object> genList = list.Cast<object>();
  268. return
  269. (amount < 0
  270. ? genList.Skip(-amount).Concat(genList.Take(-amount))
  271. : genList.Skip(list.Count - amount).Concat(genList.Take(list.Count - amount)))
  272. .ToList();
  273. }
  274. /// <summary>
  275. /// Gets an item from the given list that's located at the specified index.
  276. /// </summary>
  277. /// <param name="list">List to fetch an item from.</param>
  278. /// <param name="index">Index of the item to be fetched.</param>
  279. /// <returns name="item">Item in the list at the given index.</returns>
  280. /// <search>get,item,index,fetch</search>
  281. public static object GetItemAtIndex(IList list, int index)
  282. {
  283. return list[index];
  284. }
  285. /// <summary>
  286. /// Gets a single sub-list from the given list, based on starting index, ending index,
  287. /// and a step amount.
  288. /// </summary>
  289. /// <param name="list">List to take a slice of.</param>
  290. /// <param name="start">Index to start the slice from.</param>
  291. /// <param name="end">Index to end the slice at.</param>
  292. /// <param name="step">
  293. /// Amount the indices of the items are separate by in the original list.
  294. /// </param>
  295. /// <returns name="items">Items in the slice of the given list.</returns>
  296. /// <search>list,sub,sublist</search>
  297. public static IList Slice(IList list, int? start = null, int? end = null, int step = 1)
  298. {
  299. if (step == 0)
  300. throw new ArgumentException("Cannot slice a list with step of 0.", @"step");
  301. int _start = start ?? (step < 0 ? -1 : 0);
  302. int _end = end ?? (step < 0 ? -list.Count - 1 : list.Count);
  303. _start = _start >= 0 ? _start : _start + list.Count;
  304. if (_start < 0)
  305. _start = 0;
  306. _end = _end >= 0 ? _end : _end + list.Count;
  307. if (_end > list.Count)
  308. _end = list.Count;
  309. IList result = new ArrayList();
  310. if (step > 0)
  311. {
  312. if (_start > _end)
  313. return result;
  314. for (int i = _start; i < _end; i += step)
  315. result.Add(list[i]);
  316. }
  317. else
  318. {
  319. if (_end > _start)
  320. return result;
  321. for (int i = _start; i > _end; i += step)
  322. result.Add(list[i]);
  323. }
  324. return result;
  325. }
  326. /// <summary>
  327. /// Removes an item from the given list at the specified index.
  328. /// </summary>
  329. /// <param name="list">List to remove an item or items from.</param>
  330. /// <param name="indices">Index or indices of the item(s) to be removed.</param>
  331. /// <returns name="list">List with items removed.</returns>
  332. /// <search>index,indices,cull</search>
  333. public static IList RemoveItemAtIndex(IList list, int[] indices)
  334. {
  335. return list.Cast<object>().Where((_, i) => !indices.Contains(i)).ToList();
  336. }
  337. /// <summary>
  338. /// Removes items from the given list at indices that are multiples
  339. /// of the given value, after the given offset.
  340. /// </summary>
  341. /// <param name="list">List to remove items from/</param>
  342. /// <param name="n">Indices that are multiples of this argument will be removed.</param>
  343. /// <param name="offset">
  344. /// Amount of items to be ignored from the start of the list.
  345. /// </param>
  346. /// <returns name="list">List with items removed.</returns>
  347. /// <search>nth,remove,cull,every</search>
  348. public static IList DropEveryNthItem(IList list, int n, int offset = 0)
  349. {
  350. return list.Cast<object>().Where((_, i) => (i + 1 - offset)%n != 0).ToList();
  351. }
  352. /// <summary>
  353. /// Fetches items from the given list at indices that are multiples
  354. /// of the given value, after the given offset.
  355. /// </summary>
  356. /// <param name="list">List to take items from.</param>
  357. /// <param name="n">
  358. /// Indices that are multiples of this number (after the offset)
  359. /// will be fetched.
  360. /// </param>
  361. /// <param name="offset">
  362. /// Amount of items to be ignored from the start of the list.
  363. /// </param>
  364. /// <returns name="items">Items from the list.</returns>
  365. /// <search>fetch,take,every,nth</search>
  366. public static IList TakeEveryNthItem(IList list, int n, int offset = 0)
  367. {
  368. return list.Cast<object>().Where((_, i) => (i + 1 - offset)%n == 0).ToList();
  369. }
  370. /// <summary>
  371. /// Determines if the given list is empty.
  372. /// </summary>
  373. /// <param name="list">List to check for items.</param>
  374. /// <returns name="bool">Whether the list is empty.</returns>
  375. /// <search>test,is,empty</search>
  376. public static bool IsEmpty(IList list)
  377. {
  378. return list.Count == 0;
  379. }
  380. /// <summary>
  381. /// Gets the number of items stored in the given list.
  382. /// </summary>
  383. /// <param name="list">List to get the item count of.</param>
  384. /// <returns name="count">List length.</returns>
  385. /// <search>listlength,list length,count</search>
  386. public static int Count(IList list)
  387. {
  388. return list.Count;
  389. }
  390. /// <summary>
  391. /// Concatenates all given lists into a single list.
  392. /// </summary>
  393. /// <param name="lists">Lists to join into one.</param>
  394. /// <returns name="list">Joined list.</returns>
  395. /// <search>join lists</search>
  396. public static IList Join(params IList[] lists)
  397. {
  398. var result = new ArrayList();
  399. foreach (IList list in lists)
  400. result.AddRange(list);
  401. return result;
  402. }
  403. /// <summary>
  404. /// Gets the first item in a list.
  405. /// </summary>
  406. /// <param name="list">List to get the first item from.</param>
  407. /// <returns name="item">First item in the list.</returns>
  408. /// <search>get,fetch,first,item</search>
  409. public static object FirstItem(IList list)
  410. {
  411. return list[0];
  412. }
  413. /// <summary>
  414. /// Removes the first item from the given list.
  415. /// </summary>
  416. /// <param name="list">List to get the rest of.</param>
  417. /// <returns name="rest">Rest of the list.</returns>
  418. /// <search>get,fetch,rest</search>
  419. public static IList RestOfItems(IList list)
  420. {
  421. return list.Cast<object>().Skip(1).ToList();
  422. }
  423. /// <summary>
  424. /// Chop a list into a set of lists each containing the given amount of items.
  425. /// </summary>
  426. /// <param name="list">List to chop up.</param>
  427. /// <param name="subLength">Length of each new sub-list.</param>
  428. /// <returns name="lists">List of lists.</returns>
  429. /// <search>sublists,build sublists</search>
  430. public static IList Chop(IList list, int subLength)
  431. {
  432. if (list.Count < subLength)
  433. return list;
  434. var finalList = new ArrayList();
  435. var currList = new ArrayList();
  436. int count = 0;
  437. foreach (object v in list)
  438. {
  439. count++;
  440. currList.Add(v);
  441. if (count == subLength)
  442. {
  443. finalList.Add(currList);
  444. currList = new ArrayList();
  445. count = 0;
  446. }
  447. }
  448. if (currList.Count > 0)
  449. finalList.Add(currList);
  450. return finalList;
  451. }
  452. /// <summary>
  453. /// List elements along each diagonal in the matrix from the top left to the lower right.
  454. /// </summary>
  455. /// <param name="list">A flat list</param>
  456. /// <param name="subLength">Length of each new sub-list.</param>
  457. /// <returns name="diagonals">Lists of elements along matrix diagonals.</returns>
  458. /// <search>diagonal,right,matrix</search>
  459. public static IList DiagonalRight([ArbitraryDimensionArrayImport] IList list, int subLength)
  460. {
  461. object[] flatList;
  462. try
  463. {
  464. flatList = list.Cast<ArrayList>().SelectMany(i => i.Cast<object>()).ToArray();
  465. }
  466. catch
  467. {
  468. flatList = list.Cast<object>().ToArray();
  469. }
  470. if (flatList.Count() < subLength)
  471. return list;
  472. var finalList = new List<List<object>>();
  473. var startIndices = new List<int>();
  474. //get indices along 'side' of array
  475. for (int i = subLength; i < flatList.Count(); i += subLength)
  476. startIndices.Add(i);
  477. startIndices.Reverse();
  478. //get indices along 'top' of array
  479. for (int i = 0; i < subLength; i++)
  480. startIndices.Add(i);
  481. foreach (int start in startIndices)
  482. {
  483. int index = start;
  484. var currList = new List<object>();
  485. while (index < flatList.Count())
  486. {
  487. var currentRow = (int)System.Math.Ceiling((index + 1)/(double)subLength);
  488. currList.Add(flatList[index]);
  489. index += subLength + 1;
  490. //ensure we are skipping a row to get the next index
  491. var nextRow = (int)System.Math.Ceiling((index + 1)/(double)subLength);
  492. if (nextRow > currentRow + 1 || nextRow == currentRow)
  493. break;
  494. }
  495. finalList.Add(currList);
  496. }
  497. return finalList;
  498. }
  499. /// <summary>
  500. /// List elements along each diagonal in the matrix from the top right to the lower left.
  501. /// </summary>
  502. /// <param name="list">A flat list.</param>
  503. /// <param name="rowLength">Length of each new sib-list.</param>
  504. /// <returns name="diagonals">Lists of elements along matrix diagonals.</returns>
  505. /// <search>diagonal,left,matrix</search>
  506. public static IList DiagonalLeft(IList list, int rowLength)
  507. {
  508. object[] flatList;
  509. try
  510. {
  511. flatList = list.Cast<ArrayList>().SelectMany(i => i.Cast<object>()).ToArray();
  512. }
  513. catch (Exception)
  514. {
  515. flatList = list.Cast<object>().ToArray();
  516. }
  517. if (flatList.Count() < rowLength)
  518. return list;
  519. var finalList = new List<List<object>>();
  520. var startIndices = new List<int>();
  521. //get indices along 'top' of array
  522. for (int i = 0; i < rowLength; i++)
  523. startIndices.Add(i);
  524. //get indices along 'side' of array
  525. for (int i = rowLength - 1 + rowLength; i < flatList.Count(); i += rowLength)
  526. startIndices.Add(i);
  527. foreach (int start in startIndices)
  528. {
  529. int index = start;
  530. var currList = new List<object>();
  531. while (index < flatList.Count())
  532. {
  533. var currentRow = (int)System.Math.Ceiling((index + 1)/(double)rowLength);
  534. currList.Add(flatList.ElementAt(index));
  535. index += rowLength - 1;
  536. //ensure we are skipping a row to get the next index
  537. var nextRow = (int)System.Math.Ceiling((index + 1)/(double)rowLength);
  538. if (nextRow > currentRow + 1 || nextRow == currentRow)
  539. break;
  540. }
  541. finalList.Add(currList);
  542. }
  543. return finalList;
  544. }
  545. /// <summary>
  546. /// Swaps rows and columns in a list of lists.
  547. /// </summary>
  548. /// <param name="lists">A list of lists to be transposed.</param>
  549. /// <returns name="lists">A list of transposed lists.</returns>
  550. /// <search>transpose,flip matrix,matrix,swap,rows,columns</search>
  551. public static IList Transpose(IList lists)
  552. {
  553. if (lists.Count == 0 || !lists.Cast<object>().Any(x => x is IList))
  554. return lists;
  555. IEnumerable<IList> ilists = lists.Cast<IList>();
  556. int maxLength = ilists.Max(subList => subList.Count);
  557. List<ArrayList> transposedList =
  558. Enumerable.Range(0, maxLength).Select(i => new ArrayList()).ToList();
  559. foreach (IList sublist in ilists)
  560. {
  561. for (int i = 0; i < sublist.Count; i++)
  562. transposedList[i].Add(sublist[i]);
  563. }
  564. return transposedList;
  565. }
  566. /// <summary>
  567. /// Creates a list containing the given item the given number of times.
  568. /// </summary>
  569. /// <param name="item">The item to repeat.</param>
  570. /// <param name="amount">The number of times to repeat.</param>
  571. /// <returns name="list">List of repeated items.</returns>
  572. /// <search>repeat,repeated,duplicate</search>
  573. public static IList OfRepeatedItem([ArbitraryDimensionArrayImport] object item, int amount)
  574. {
  575. return Enumerable.Repeat(item, amount).ToList();
  576. }
  577. /// <summary>
  578. /// Creates a new list by concatenining copies of a given list.
  579. /// </summary>
  580. /// <param name="list">List to repeat.</param>
  581. /// <param name="amount">Number of times to repeat.</param>
  582. /// <returns name="list">List of repeated lists.</returns>
  583. /// <search>repeat,repeated,duplicate</search>
  584. public static IList Cycle(IList list, int amount)
  585. {
  586. var result = new ArrayList();
  587. while (amount > 0)
  588. {
  589. result.AddRange(list);
  590. amount--;
  591. }
  592. return result;
  593. }
  594. // Here for backwards compatibility until we have a good way to migrate
  595. // changes in DSFunction nodes. --SJE
  596. [IsVisibleInDynamoLibrary(false)]
  597. public static IList Repeat(IList list, int amount)
  598. {
  599. return Cycle(list, amount);
  600. }
  601. /// <summary>
  602. /// Retrieves the last item in a list.
  603. /// </summary>
  604. /// <param name="list">List to get the last item of.</param>
  605. /// <returns name="last">Last item in the list.</returns>
  606. /// <search>get,fetch,last,item</search>
  607. public static object LastItem(IList list)
  608. {
  609. if (list.Count == 0)
  610. throw new ArgumentException("Cannot get the last item in an empty list.", "list");
  611. return list[list.Count - 1];
  612. }
  613. /// <summary>
  614. /// Shuffles a list, randomizing the order of its items.
  615. /// </summary>
  616. /// <param name="list">List to shuffle.</param>
  617. /// <returns name="list">Randomized list.</returns>
  618. /// <search>random,randomize,shuffle,jitter</search>
  619. public static IList Shuffle(IList list)
  620. {
  621. var rng = new Random();
  622. return list.Cast<object>().OrderBy(_ => rng.Next()).ToList();
  623. }
  624. /// <summary>
  625. /// Produces all permutations of the given length of a given list.
  626. /// </summary>
  627. /// <param name="list">List to permute.</param>
  628. /// <param name="length">Length of each permutation.</param>
  629. /// <returns name="perm">Permutations of the list of the given length.</returns>
  630. /// <search>permutation,permutations</search>
  631. public static IList Permutations(IList list, int? length = null)
  632. {
  633. return
  634. GetPermutations(list.Cast<object>(), length ?? list.Count)
  635. .Select(x => x.ToList())
  636. .ToList();
  637. }
  638. /// <summary>
  639. /// Produces all combinations of the given length of a given list.
  640. /// </summary>
  641. /// <param name="list">List to generate combinations of.</param>
  642. /// <param name="length">Length of each combination.</param>
  643. /// <param name="replace">
  644. /// Whether or not items are removed once selected for combination, defaults
  645. /// to false.
  646. /// </param>
  647. /// <returns name="comb">Combinations of the list of the given length.</returns>
  648. /// <search>combo</search>
  649. public static IList Combinations(IList list, int length, bool replace = false)
  650. {
  651. return
  652. GetCombinations(list.Cast<object>(), length, replace)
  653. .Select(x => x.ToList())
  654. .ToList();
  655. }
  656. #region Combinatorics Helpers
  657. private static IEnumerable<T> Singleton<T>(T t)
  658. {
  659. yield return t;
  660. }
  661. private static IEnumerable<IEnumerable<T>> GetCombinations<T>(
  662. IEnumerable<T> items, int count, bool replace)
  663. {
  664. int i = 0;
  665. IList<T> enumerable = items as IList<T> ?? items.ToList();
  666. foreach (T item in enumerable)
  667. {
  668. if (count == 1)
  669. yield return Singleton(item);
  670. else
  671. {
  672. IEnumerable<IEnumerable<T>> combs =
  673. GetCombinations(enumerable.Skip(replace ? i : i + 1), count - 1, replace);
  674. foreach (var result in combs)
  675. yield return Singleton(item).Concat(result);
  676. }
  677. ++i;
  678. }
  679. }
  680. private static IEnumerable<IEnumerable<T>> GetPermutations<T>(
  681. IEnumerable<T> items, int count)
  682. {
  683. int i = 0;
  684. IList<T> enumerable = items as IList<T> ?? items.ToList();
  685. foreach (T item in enumerable)
  686. {
  687. if (count == 1)
  688. yield return Singleton(item);
  689. else
  690. {
  691. IEnumerable<IEnumerable<T>> perms =
  692. GetPermutations(
  693. enumerable.Take(i).Concat(enumerable.Skip(i + 1)),
  694. count - 1);
  695. foreach (var result in perms)
  696. yield return Singleton(item).Concat(result);
  697. }
  698. ++i;
  699. }
  700. }
  701. /// <summary>
  702. /// Flattens a nested list of lists by a certain amount.
  703. /// </summary>
  704. /// <param name="list">List to flatten.</param>
  705. /// <param name="amt">Layers of nesting to remove.</param>
  706. public static IList Flatten(IList list, int amt)
  707. {
  708. return Flatten(list, amt, new List<object>());
  709. }
  710. private static IList Flatten(IList list, int amt, IList acc)
  711. {
  712. if (amt == 0)
  713. {
  714. foreach (object item in list)
  715. acc.Add(item);
  716. }
  717. else
  718. {
  719. foreach (object item in list)
  720. {
  721. if (item is IList)
  722. acc = Flatten(item as IList, amt - 1, acc);
  723. else
  724. acc.Add(item);
  725. }
  726. }
  727. return acc;
  728. }
  729. #endregion
  730. #region UniqueItems Comparer
  731. private class DistinctComparer : IEqualityComparer<object>
  732. {
  733. internal static readonly IEqualityComparer<object> Instance = new DistinctComparer();
  734. bool IEqualityComparer<object>.Equals(object x, object y)
  735. {
  736. return Eq(x as dynamic, y as dynamic);
  737. }
  738. // Bypass hash code check, use Equals instead.
  739. public int GetHashCode(object obj)
  740. {
  741. return -1;
  742. }
  743. private static bool Eq(double x, double y)
  744. {
  745. return x.Equals(y);
  746. }
  747. private static bool Eq(IList x, IList y)
  748. {
  749. return x.Cast<object>().Zip(y.Cast<object>(), Equals).All(b => b);
  750. }
  751. }
  752. #endregion
  753. /* Disabled Higher-order functions
  754. /// <summary>
  755. /// Flattens a nested list of lists into a single list containing no
  756. /// sub-lists.
  757. /// </summary>
  758. /// <param name="list">List to flatten.</param>
  759. public static IList FlattenCompletely(IList list)
  760. {
  761. throw new NotImplementedException();
  762. }
  763. /// <summary>
  764. /// Flattens a nested list of lists by a certain amount.
  765. /// </summary>
  766. /// <param name="list">List to flatten.</param>
  767. /// <param name="amt">Layers of nesting to remove.</param>
  768. /// s
  769. public static IList Flatten(IList list, int amt)
  770. {
  771. throw new NotImplementedException();
  772. }
  773. /// <summary>
  774. /// Returns the minimum value from a list using a key projection. The minimum
  775. /// value is the item in the list that the key projection produces the smallest
  776. /// value for.
  777. /// </summary>
  778. /// <param name="list">List to take the minimum value from.</param>
  779. /// <param name="keyProjector">
  780. /// Function that consumes an item from the list and produces an orderable value.
  781. /// </param>
  782. public static object MinimumItemByKey(IList list, Delegate keyProjector)
  783. {
  784. if (list.Count == 0)
  785. throw new ArgumentException("Cannot take the minimum value of an empty list.", "list");
  786. object min = list[0];
  787. var minProjection = (IComparable)keyProjector.DynamicInvoke(min);
  788. foreach (var item in list.Cast<object>().Skip(1))
  789. {
  790. var projection = (IComparable)keyProjector.DynamicInvoke(item);
  791. if (projection.CompareTo(minProjection) < 0)
  792. {
  793. min = item;
  794. minProjection = projection;
  795. }
  796. }
  797. return min;
  798. }
  799. /// <summary>
  800. /// Returns the maximum value from a list using a key projection. The maximum
  801. /// value is the item in the list that the key projection produces the largest
  802. /// value for.
  803. /// </summary>
  804. /// <param name="list">List to take the maximum value from.</param>
  805. /// <param name="keyProjector">
  806. /// Function that consumes an item from the list and produces an orderable value.
  807. /// </param>
  808. public static object MaximumItemByKey(IList list, Delegate keyProjector)
  809. {
  810. if (list.Count == 0)
  811. throw new ArgumentException("Cannot take the maximum value of an empty list.", "list");
  812. object max = list[0];
  813. var maxProjection = (IComparable)keyProjector.DynamicInvoke(max);
  814. foreach (var item in list.Cast<object>().Skip(1))
  815. {
  816. var projection = (IComparable)keyProjector.DynamicInvoke(item);
  817. if (projection.CompareTo(maxProjection) > 0)
  818. {
  819. max = item;
  820. maxProjection = projection;
  821. }
  822. }
  823. return max;
  824. }
  825. /// <summary>
  826. /// Creates a new list containing all the items of an old list for which
  827. /// the given predicate function returns True.
  828. /// </summary>
  829. /// <param name="list">List to be filtered.</param>
  830. /// <param name="predicate">
  831. /// Function to be applied to all items in the list. All items that make the
  832. /// predicate produce True will be stored in the output list.
  833. /// </param>
  834. public static IList Filter(IList list, Delegate predicate)
  835. {
  836. return list.Cast<object>().Where(x => (bool)predicate.DynamicInvoke(x)).ToList();
  837. }
  838. //TODO: This could be combined with Filter into a multi-output node
  839. /// <summary>
  840. /// Creates a new list containing all the items of an old list for which
  841. /// the given predicate function returns False.
  842. /// </summary>
  843. /// <param name="list">List to be filtered.</param>
  844. /// <param name="predicate">
  845. /// Function to be applied to all items in the list. All items that make the
  846. /// predicate produce False will be stored in the output list.
  847. /// </param>
  848. public static IList FilterOut(IList list, Delegate predicate)
  849. {
  850. return list.Cast<object>().Where(x => !(bool)predicate.DynamicInvoke(x)).ToList();
  851. }
  852. /// <summary>
  853. /// Sorts a list using a key projection. A projection is created for each item,
  854. /// and that value is used to order the original items.
  855. /// </summary>
  856. /// <param name="list">List to be sorted.</param>
  857. /// <param name="keyProjector">
  858. /// Function that consumes an item from the list and produces an orderable value.
  859. /// </param>
  860. /// <returns></returns>
  861. public static IList SortByKey(IList list, Delegate keyProjector)
  862. {
  863. return list.Cast<object>().OrderBy(x => keyProjector.DynamicInvoke(x)).ToList();
  864. }
  865. /// <summary>
  866. /// Sorts a list using a comparison function. Given two items from the list, the comparison
  867. /// function determines which item should appear first in the sorted list.
  868. /// </summary>
  869. /// <param name="list">List to be sorted.</param>
  870. /// <param name="comparison">
  871. /// Function that consumes two items from the list and produces a value determining the order
  872. /// of the two items as follows: a value less than zero if the first item should appear
  873. /// before the second, zero if the values are considered the same, and a value greater than
  874. /// zero if the second item should appear before the first.
  875. /// </param>
  876. public static IList SortByComparison(IList list, Delegate comparison)
  877. {
  878. var rtn = list.Cast<object>().ToList();
  879. rtn.Sort((x, y) => (int)comparison.DynamicInvoke(x, y));
  880. return rtn;
  881. }
  882. /// <summary>
  883. /// Reduces a list of values into a new value using a reduction function.
  884. /// </summary>
  885. /// <param name="list">List to be reduced.</param>
  886. /// <param name="seed">
  887. /// Starting value for the reduction. If the list being reduced is
  888. /// empty, this will immediately be returned.
  889. /// </param>
  890. /// <param name="reducer">
  891. /// A function that consumes an item in the list and a reduction state. It must produce
  892. /// a new reduction state by combining the item with the current reduction state.
  893. /// </param>
  894. public static TState Reduce<T, TState>(IEnumerable<T> list, TState seed, Func<T, TState, TState> reducer)
  895. {
  896. return list.Aggregate(seed, (a, x) => reducer(x, a));
  897. }
  898. /// <summary>
  899. /// Produces a new list by applying a projection function to each item of the input list(s) and
  900. /// storing the result.
  901. /// </summary>
  902. /// <param name="projection">
  903. /// Function that consumes an item from each input list and produces a value that is stored
  904. /// in the output list.
  905. /// </param>
  906. /// <param name="lists">Lists to be combined/mapped into a new list.</param>
  907. public static IList<object> Map(Function.MapDelegate projection, params IEnumerable<object>[] lists)
  908. {
  909. if (!lists.Any())
  910. throw new ArgumentException("Need at least one list to map.");
  911. IEnumerable<List<object>> argList = lists[0].Select(x => new List<object> { x });
  912. foreach (var pair in
  913. lists.Skip(1).SelectMany(list => list.Zip(argList, (o, objects) => new { o, objects })))
  914. pair.objects.Add(pair.o);
  915. return argList.Select(x => projection(x.ToArray())).ToList();
  916. }
  917. /// <summary>
  918. /// Produces a new list by applying a projection function to all combinations of items from the
  919. /// input lists and storing the result.
  920. /// </summary>
  921. /// <param name="projection">
  922. /// Function that consumes an item from each input list and produces a value that is stored
  923. /// in the output list.
  924. /// </param>
  925. /// <param name="lists">Lists to take the cartesion product of.</param>
  926. public static IList<object> CartesianProduct(
  927. Function.MapDelegate projection,
  928. params IEnumerable<object>[] lists)
  929. {
  930. if (!lists.Any())
  931. throw new ArgumentException("Need at least one list to map.");
  932. throw new NotImplementedException();
  933. }
  934. /// <summary>
  935. /// Applies a function to each item of the input list(s). Does not accumulate results.
  936. /// </summary>
  937. /// <param name="action">
  938. /// Function that consumed an item from each input list. Return value is ignored.
  939. /// </param>
  940. /// <param name="lists">Lists to be iterated over.</param>
  941. public static void ForEach(Function.MapDelegate action, params IEnumerable<object>[] lists)
  942. {
  943. if (!lists.Any())
  944. throw new ArgumentException("Need at least one list to iterate over.");
  945. IEnumerable<List<object>> argList = lists[0].Select(x => new List<object> { x });
  946. foreach (var pair in
  947. lists.Skip(1).SelectMany(list => list.Zip(argList, (o, objects) => new { o, objects })))
  948. pair.objects.Add(pair.o);
  949. foreach (var args in argList)
  950. action(args.ToArray());
  951. }
  952. /// <summary>
  953. /// Determines if the given predicate function returns True when applied to all of the
  954. /// items in the given list.
  955. /// </summary>
  956. /// <param name="predicate">
  957. /// Function to be applied to all items in the list, returns a boolean value.
  958. /// </param>
  959. /// <param name="list">List to be tested.</param>
  960. public static bool TrueForAllItems(IList list, Delegate predicate)
  961. {
  962. return list.Cast<object>().All(x => (bool)predicate.DynamicInvoke(x));
  963. }
  964. /// <summary>
  965. /// Determines if the given predicate function returns True when applied to any of the
  966. /// items in the given list.
  967. /// </summary>
  968. /// <param name="predicate">
  969. /// Function to be applied to all items in the list, returns a boolean value.
  970. /// </param>
  971. /// <param name="list">List to be tested.</param>
  972. public static bool TrueForAnyItems(IList list, Delegate predicate)
  973. {
  974. return list.Cast<object>().Any(x => (bool)predicate.DynamicInvoke(x));
  975. }
  976. /// <summary>
  977. /// Applies a key mapping function to each item in the given list, and produces
  978. /// a new list of lists where each sublist contains items for which the key
  979. /// mapping function produced the same result.
  980. /// </summary>
  981. /// <param name="list">List to group into sublists.</param>
  982. /// <param name="keyProjector">Function that produces grouping values.</param>
  983. public static IList GroupByKey(IList list, Delegate keyProjector)
  984. {
  985. return
  986. list.Cast<object>()
  987. .GroupBy(x => keyProjector.DynamicInvoke(x))
  988. .Select(x => x.ToList() as IList)
  989. .ToList();
  990. }
  991. /// <summary>
  992. /// Applies a function to each item in the given list, and constructs a new
  993. /// list containing the results of each function application.
  994. /// </summary>
  995. /// <param name="list">List to map.</param>
  996. /// <param name="mapFunc">Function to apply to each element.</param>
  997. public static IList Map(IList list, Delegate mapFunc)
  998. {
  999. return list.Cast<object>().Select(x => mapFunc.DynamicInvoke(x)).ToList();
  1000. }
  1001. private static IEnumerable<object> GetArgs(IEnumerable<IList> lists, int index)
  1002. {
  1003. return lists.Where(argList => index < argList.Count).Select(x => x[index]);
  1004. }
  1005. /// <summary>
  1006. /// Combines multiple lists into a single list by taking items at corresponding
  1007. /// indices and applying a function to them, and using the results to construct
  1008. /// a new list.
  1009. /// </summary>
  1010. /// <param name="combinator">Function to apply to an element from each list.</param>
  1011. /// <param name="lists">Lists to combine.</param>
  1012. public static IList Combine(Delegate combinator, params IList[] lists)
  1013. {
  1014. var result = new ArrayList();
  1015. int i = 0;
  1016. while (true)
  1017. {
  1018. var args = GetArgs(lists, i).ToArray();
  1019. if (!args.Any()) break;
  1020. result.Add(combinator.DynamicInvoke(args));
  1021. i++;
  1022. }
  1023. return result;
  1024. }
  1025. /// <summary>
  1026. /// Reduces multiple lists into a single value by taking items at corresponding
  1027. /// indices and applying a function to them and a "reduced value". The result
  1028. /// is then used as a new reduced value for the next application. When all lists
  1029. /// are empty, the final reduced value is returned.
  1030. /// </summary>
  1031. /// <param name="reductor">
  1032. /// Reductor function: consumes an item from each list and a reduced value, and
  1033. /// produces a new reduced value.
  1034. /// </param>
  1035. /// <param name="initial">The initial reduced value.</param>
  1036. /// <param name="lists">Lists to reduce.</param>
  1037. public static object Reduce(Delegate reductor, object initial, params IList[] lists)
  1038. {
  1039. int i = 0;
  1040. while (true)
  1041. {
  1042. var args = GetArgs(lists, i);
  1043. if (!args.Any()) break;
  1044. initial = reductor.DynamicInvoke(args.Concat(new[] { initial }).ToArray());
  1045. i++;
  1046. }
  1047. return initial;
  1048. }
  1049. */
  1050. }
  1051. /// <summary>
  1052. /// Implements Compare function for two objects using following rule.
  1053. /// 1. Numbers are assumed to be smallest, then bool, string and pointers.
  1054. /// 2. If the two objects are IComparable and of the same type, then use
  1055. /// it's native comparison mechanism.
  1056. /// 3. If both inputs are value type, but one of them is bool, bool is bigger
  1057. /// 4. Otherwise Convert them all to double and compare.
  1058. /// 5. Else If only one is value type, then value type object is smaller
  1059. /// 6. Else If only one is string, then the string is smaller than other
  1060. /// 7. Else don't know how to compare, so best campare them based on HashCode.
  1061. /// </summary>
  1062. internal class ObjectComparer : IComparer<object>
  1063. {
  1064. public int Compare(object x, object y)
  1065. {
  1066. Type xType = x.GetType();
  1067. Type yType = y.GetType();
  1068. //Same type and are comparable, use it's own compareTo method.
  1069. if (xType == yType && typeof(IComparable).IsAssignableFrom(xType))
  1070. return ((IComparable)x).CompareTo(y);
  1071. //Both are value type, can be converted to Double, use double comparison
  1072. if (xType.IsValueType && yType.IsValueType)
  1073. {
  1074. //Bool is bigger than other value type.
  1075. if (xType == typeof(bool))
  1076. return 1;
  1077. //Other value type is smaller than bool
  1078. if (yType == typeof(bool))
  1079. return -1;
  1080. return Convert.ToDouble(x).CompareTo(Convert.ToDouble(y));
  1081. }
  1082. //Value Type object will be smaller, if x is value type it is smaller
  1083. if (xType.IsValueType)
  1084. return -1;
  1085. //if y is value type x is bigger
  1086. if (yType.IsValueType)
  1087. return 1;
  1088. //Next higher order object is string
  1089. if (xType == typeof(string))
  1090. return -1;
  1091. if (yType == typeof(string))
  1092. return 1;
  1093. //No idea, return based on hash code.
  1094. return x.GetHashCode() - y.GetHashCode();
  1095. }
  1096. }
  1097. }