import 'dart:math'; import 'collection.dart'; /// A set of helper functions for working with arrays. class Arr { /// Create a new instance of the Arr class. const Arr._(); /// Determine whether the given value is array accessible. static bool accessible(dynamic value) { return value is List || value is Map; } /// Add an element to an array using "dot" notation if it doesn't exist. static void add(Map array, String key, dynamic value) { if (!has(array, key)) { set(array, key, value); } } /// Collapse an array of arrays into a single array. static List collapse(Iterable> array) { return array.expand((element) => element).toList(); } /// Cross join the given arrays, returning all possible permutations. static List> crossJoin(List> arrays) { if (arrays.isEmpty) return []; if (arrays.length == 1) return arrays[0].map((e) => [e]).toList(); final result = >[]; final firstArray = arrays[0]; final remainingArrays = arrays.sublist(1); final subPermutations = crossJoin(remainingArrays); for (var item in firstArray) { for (var subPerm in subPermutations) { result.add([item, ...subPerm]); } } return result; } /// Divide an array into two arrays. One with keys and the other with values. static Map> divide(Map array) { return { 'keys': array.keys.toList(), 'values': array.values.toList(), }; } /// Flatten a multi-dimensional array into a single level. static List flatten(Iterable array, [int depth = -1]) { final result = []; for (var item in array) { if (item is Iterable && depth != 0) { result.addAll(flatten(item, depth - 1)); } else { result.add(item as T); } } return result; } /// Remove one or many array items from a given array using "dot" notation. static void forget(Map array, dynamic keys) { final keysList = keys is String ? [keys] : keys as List; for (var key in keysList) { if (key.contains('.')) { final segments = key.split('.'); _forgetNested(array, segments); } else { array.remove(key); } } } static void _forgetNested(Map array, List segments) { var current = array; final lastSegment = segments.last; segments = segments.sublist(0, segments.length - 1); for (var segment in segments) { if (!current.containsKey(segment) || current[segment] is! Map) { return; } current = current[segment] as Map; } current.remove(lastSegment); } /// Get an item from an array using "dot" notation. static T? get(dynamic array, String? key, [T? defaultValue]) { if (array == null || key == null) { return defaultValue; } if (!key.contains('.')) { if (array is Map) { return array.containsKey(key) ? array[key] as T : defaultValue; } if (array is List && int.tryParse(key) != null) { final index = int.parse(key); return index >= 0 && index < array.length ? array[index] as T : defaultValue; } return defaultValue; } final segments = key.split('.'); var current = array; for (var segment in segments) { if (current is! Map && current is! List) { return defaultValue; } if (current is List) { final index = int.tryParse(segment); if (index == null || index < 0 || index >= current.length) { return defaultValue; } current = current[index]; } else { final map = current as Map; if (!map.containsKey(segment)) { return defaultValue; } current = map[segment]; } } return current as T? ?? defaultValue; } /// Check if an item or items exist in an array using "dot" notation. static bool has(dynamic array, dynamic keys) { if (array == null) { return false; } final keysList = keys is String ? [keys] : keys as List; for (var key in keysList) { if (key.contains('.')) { final segments = key.split('.'); var current = array; for (var segment in segments) { if (current is! Map && current is! List) { return false; } if (current is List) { final index = int.tryParse(segment); if (index == null || index < 0 || index >= current.length) { return false; } current = current[index]; } else { final map = current as Map; if (!map.containsKey(segment)) { return false; } current = map[segment]; } } } else { if (array is Map && !array.containsKey(key)) { return false; } if (array is List) { final index = int.tryParse(key); if (index == null || index < 0 || index >= array.length) { return false; } } } } return true; } /// Determines if an array is associative. static bool isAssoc(dynamic array) { if (array is! Map) { return false; } return array.keys.any((key) => key is! int); } /// Get a subset of the items from the given array. static Map only( Map array, List keys, ) { return Map.fromEntries( array.entries.where((entry) => keys.contains(entry.key)), ); } /// Pluck an array of values from an array. static List pluck( Iterable> array, String key, [ String? value, ]) { if (value == null) { return array.map((item) => item[key] as T).toList(); } return array .map((item) => MapEntry(item[key] as String, item[value] as T)) .fold>({}, (map, entry) { map[entry.key] = entry.value; return map; }) .values .toList(); } /// Push an item onto the beginning of an array. static void prepend(List array, T value, [String? key]) { if (key != null) { array.insert(0, {key: value}); } else { array.insert(0, value); } } /// Get a value from the array, and remove it. static T? pull(Map array, String key, [T? defaultValue]) { final value = get(array, key, defaultValue); forget(array, key); return value; } /// Get one or a specified number of random values from an array. static List random(List array, [int? number]) { if (array.isEmpty) { return []; } if (number == null) { return [array[Random().nextInt(array.length)]]; } if (number <= 0) { throw ArgumentError('Number must be greater than 0'); } if (number > array.length) { throw ArgumentError('Number cannot be greater than array length'); } final shuffled = List.from(array)..shuffle(); return shuffled.take(number).toList(); } /// Set an array item to a given value using "dot" notation. static void set(Map array, String key, dynamic value) { if (!key.contains('.')) { array[key] = value; return; } final segments = key.split('.'); var current = array; for (var i = 0; i < segments.length - 1; i++) { final segment = segments[i]; if (!current.containsKey(segment) || current[segment] is! Map) { current[segment] = {}; } current = current[segment] as Map; } current[segments.last] = value; } /// Shuffle the given array and return the result. static List shuffle(List array) { final shuffled = List.from(array); shuffled.shuffle(); return shuffled; } /// Convert a flattened "dot" notation array to an expanded array. static Map undot(Map array) { final results = {}; for (var entry in array.entries) { if (entry.key.contains('.')) { set(results, entry.key, entry.value); } else { results[entry.key] = entry.value; } } return results; } /// Filter the array using the given callback. static List where(List array, bool Function(T) callback) { return array.where(callback).toList(); } /// If the given value is not an array and not null, wrap it in one. static List wrap(dynamic value) { if (value == null) { return []; } if (value is List) { return value; } if (value is List) { return value.cast(); } if (value is T) { return [value]; } throw ArgumentError( 'Cannot wrap value of type ${value.runtimeType} as List<$T>'); } }