1use std::collections::HashMap;
4use std::hash::Hash;
5use std::mem;
6
7use crate::*;
8
9pub fn map_keyed<T, K, U>(
25 list: impl Into<MaybeDyn<Vec<T>>> + 'static,
26 mut map_fn: impl FnMut(T) -> U + 'static,
27 key_fn: impl Fn(&T) -> K + 'static,
28) -> ReadSignal<Vec<U>>
29where
30 T: PartialEq + Clone + 'static,
31 K: Eq + Hash,
32 U: Clone,
33{
34 let list = list.into();
35 let mut items = Vec::new();
37
38 let mut mapped: Vec<U> = Vec::new();
39 let mut mapped_tmp: Vec<Option<U>> = Vec::new();
40
41 let mut disposers: Vec<Option<NodeHandle>> = Vec::new();
42 let mut disposers_tmp: Vec<Option<NodeHandle>> = Vec::new();
43
44 let _list = list.clone();
46 let mut update = move || {
47 let new_items = _list.get_clone();
48 if new_items.is_empty() {
49 for dis in mem::take(&mut disposers) {
51 dis.unwrap().dispose();
52 }
53 mapped = Vec::new();
54 } else if items.is_empty() {
55 mapped.reserve(new_items.len());
57 disposers.reserve(new_items.len());
58
59 for new_item in new_items.iter().cloned() {
60 let map_fn = &mut map_fn;
61 let mapped = &mut mapped;
62 let new_disposer = create_child_scope(move || mapped.push(map_fn(new_item)));
63 disposers.push(Some(new_disposer));
64 }
65 } else {
66 mapped_tmp.clear();
67 mapped_tmp.resize(new_items.len(), None);
68
69 disposers_tmp.clear();
70 disposers_tmp.resize_with(new_items.len(), || None);
71
72 let min_len = usize::min(items.len(), new_items.len());
74 let start = items
75 .iter()
76 .zip(new_items.iter())
77 .position(|(a, b)| a != b)
78 .unwrap_or(min_len);
79 debug_assert!(
80 (items.get(start).is_none() && new_items.get(start).is_none())
81 || (items.get(start) != new_items.get(start)),
82 "start is the first index where items[start] != new_items[start]"
83 );
84
85 let mut end = items.len();
87 let mut new_end = new_items.len();
88 while end > start && new_end > start && items[end - 1] == new_items[new_end - 1] {
89 end -= 1;
90 new_end -= 1;
91 mapped_tmp[new_end] = Some(mapped[end].clone());
92 disposers_tmp[new_end] = disposers[end].take();
93 }
94 debug_assert!(
95 if end != 0 && new_end != 0 {
96 (end == items.len() && new_end == new_items.len())
97 || (items[end - 1] != new_items[new_end - 1])
98 } else {
99 true
100 },
101 "end and new_end are the last indexes where items[end - 1] != new_items[new_end - 1]"
102 );
103
104 let mut new_indices = HashMap::with_capacity(new_end - start);
107
108 let mut new_indices_next = vec![None; new_end - start];
111 for j in (start..new_end).rev() {
112 let item = &new_items[j];
113 let key = key_fn(item);
114 let i = new_indices.get(&key);
115 new_indices_next[j - start] = i.copied();
116 new_indices.insert(key, j);
117 }
118
119 for i in start..end {
122 let item = &items[i];
123 let key = key_fn(item);
124 if let Some(j) = new_indices.get(&key).copied() {
125 mapped_tmp[j] = Some(mapped[i].clone());
127 disposers_tmp[j] = disposers[i].take();
128 new_indices_next[j - start].and_then(|j| new_indices.insert(key, j));
129 } else {
130 disposers[i].take().unwrap().dispose();
132 }
133 }
134
135 for j in start..new_items.len() {
138 if matches!(mapped_tmp.get(j), Some(Some(_))) {
139 if j >= mapped.len() {
141 debug_assert_eq!(mapped.len(), j);
142 mapped.push(mapped_tmp[j].clone().unwrap());
143 disposers.push(disposers_tmp[j].take());
144 } else {
145 mapped[j] = mapped_tmp[j].clone().unwrap();
146 disposers[j] = disposers_tmp[j].take();
147 }
148 } else {
149 let mut tmp = None;
151 let new_item = new_items[j].clone();
152 let new_disposer = create_child_scope(|| tmp = Some(map_fn(new_item)));
153 if mapped.len() > j {
154 mapped[j] = tmp.unwrap();
155 disposers[j] = Some(new_disposer);
156 } else {
157 mapped.push(tmp.unwrap());
158 disposers.push(Some(new_disposer));
159 }
160 }
161 }
162 }
163
164 mapped.truncate(new_items.len());
166 disposers.truncate(new_items.len());
167
168 debug_assert!([mapped.len(), disposers.len()]
170 .iter()
171 .all(|l| *l == new_items.len()));
172 items = new_items;
173
174 mapped.clone()
175 };
176 let scope = use_current_scope();
177 create_memo(on(list, move || scope.run_in(&mut update)))
178}
179
180pub fn map_indexed<T, U>(
196 list: impl Into<MaybeDyn<Vec<T>>> + 'static,
197 mut map_fn: impl FnMut(T) -> U + 'static,
198) -> ReadSignal<Vec<U>>
199where
200 T: PartialEq + Clone + 'static,
201 U: Clone,
202{
203 let list = list.into();
204 let mut items = Vec::new();
206 let mut mapped = Vec::new();
207 let mut disposers: Vec<NodeHandle> = Vec::new();
208
209 let _list = list.clone();
211 let mut update = move || {
212 let new_items = _list.get_clone();
213
214 if new_items.is_empty() {
215 for dis in mem::take(&mut disposers) {
217 dis.dispose();
218 }
219 items = Vec::new();
220 mapped = Vec::new();
221 } else {
222 if new_items.len() > items.len() {
224 let new_count = new_items.len() - items.len();
225 mapped.reserve(new_count);
226 disposers.reserve(new_count);
227 }
228
229 for (i, new_item) in new_items.iter().cloned().enumerate() {
230 let item = items.get(i);
231 let eqs = item != Some(&new_item);
233
234 if item.is_none() || eqs {
235 let mut tmp = None;
236 let new_disposer = create_child_scope(|| tmp = Some(map_fn(new_item)));
237 if item.is_none() {
238 mapped.push(tmp.unwrap());
239 disposers.push(new_disposer);
240 } else if eqs {
241 mapped[i] = tmp.unwrap();
242 let prev = mem::replace(&mut disposers[i], new_disposer);
243 prev.dispose();
244 }
245 }
246 }
247
248 if new_items.len() < items.len() {
249 for _i in new_items.len()..items.len() {
250 disposers.pop().unwrap().dispose();
251 }
252 }
253
254 mapped.truncate(new_items.len());
256
257 debug_assert!([mapped.len(), disposers.len()]
259 .iter()
260 .all(|l| *l == new_items.len()));
261 items = new_items;
262 }
263
264 mapped.clone()
266 };
267 let scope = use_current_scope();
268 create_memo(on(list, move || scope.run_in(&mut update)))
269}
270
271#[cfg(test)]
272mod tests {
273 use std::cell::Cell;
274 use std::rc::Rc;
275
276 use super::*;
277
278 #[test]
279 fn keyed() {
280 let _ = create_root(|| {
281 let a = create_signal(vec![1, 2, 3]);
282 let mapped = map_keyed(a, |x| x * 2, |x| *x);
283 assert_eq!(mapped.get_clone(), vec![2, 4, 6]);
284
285 a.set(vec![1, 2, 3, 4]);
286 assert_eq!(mapped.get_clone(), vec![2, 4, 6, 8]);
287
288 a.set(vec![2, 2, 3, 4]);
289 assert_eq!(mapped.get_clone(), vec![4, 4, 6, 8]);
290 });
291 }
292
293 #[test]
294 fn keyed_recompute_everything() {
295 let _ = create_root(|| {
296 let a = create_signal(vec![1, 2, 3]);
297 let mapped = map_keyed(a, |x| x * 2, |x| *x);
298 assert_eq!(mapped.get_clone(), vec![2, 4, 6]);
299
300 a.set(vec![4, 5, 6]);
301 assert_eq!(mapped.get_clone(), vec![8, 10, 12]);
302 });
303 }
304
305 #[test]
307 fn keyed_clear() {
308 let _ = create_root(|| {
309 let a = create_signal(vec![1, 2, 3]);
310 let mapped = map_keyed(a, |x| x * 2, |x| *x);
311
312 a.set(Vec::new());
313 assert_eq!(mapped.get_clone(), Vec::<i32>::new());
314 });
315 }
316
317 #[test]
319 fn keyed_use_previous_computation() {
320 let _ = create_root(|| {
321 let a = create_signal(vec![1, 2, 3]);
322 let counter = Rc::new(Cell::new(0));
323 let mapped = map_keyed(
324 a,
325 {
326 let counter = Rc::clone(&counter);
327 move |_| {
328 counter.set(counter.get() + 1);
329 counter.get()
330 }
331 },
332 |x| *x,
333 );
334 assert_eq!(mapped.get_clone(), vec![1, 2, 3]);
335
336 a.set(vec![1, 2]);
337 assert_eq!(mapped.get_clone(), vec![1, 2]);
338
339 a.set(vec![1, 2, 4]);
340 assert_eq!(mapped.get_clone(), vec![1, 2, 4]);
341
342 a.set(vec![1, 2, 3, 4]);
343 assert_eq!(mapped.get_clone(), vec![1, 2, 5, 4]);
344 });
345 }
346
347 #[test]
348 fn keyed_call_cleanup_on_remove() {
349 let _ = create_root(|| {
350 let a = create_signal(vec![1, 2, 3]);
351 let counter = Rc::new(Cell::new(0));
352 let _mapped = map_keyed(
353 a,
354 {
355 let counter = Rc::clone(&counter);
356 move |_| {
357 let counter = Rc::clone(&counter);
358 on_cleanup(move || {
359 counter.set(counter.get() + 1);
360 });
361 }
362 },
363 |x| *x,
364 );
365 assert_eq!(counter.get(), 0, "no cleanup yet");
366
367 a.set(vec![1, 2]);
368 assert_eq!(counter.get(), 1);
369
370 a.set(vec![1, 2, 3]);
371 assert_eq!(counter.get(), 1);
372
373 a.set(vec![1, 3]);
374 assert_eq!(counter.get(), 2);
375 });
376 }
377
378 #[test]
379 fn keyed_call_cleanup_on_remove_all() {
380 let _ = create_root(|| {
381 let a = create_signal(vec![1, 2, 3]);
382 let counter = Rc::new(Cell::new(0));
383 let _mapped = map_keyed(
384 a,
385 {
386 let counter = Rc::clone(&counter);
387 move |_| {
388 let counter = Rc::clone(&counter);
389 on_cleanup(move || {
390 counter.set(counter.get() + 1);
391 })
392 }
393 },
394 |x| *x,
395 );
396 assert_eq!(counter.get(), 0, "no cleanup yet");
397
398 a.set(vec![]);
399 assert_eq!(counter.get(), 3);
400 });
401 }
402
403 #[test]
404 fn indexed() {
405 let _ = create_root(|| {
406 let a = create_signal(vec![1, 2, 3]);
407 let mapped = map_indexed(a, |x| x * 2);
408 assert_eq!(mapped.get_clone(), vec![2, 4, 6]);
409
410 a.set(vec![1, 2, 3, 4]);
411 assert_eq!(mapped.get_clone(), vec![2, 4, 6, 8]);
412
413 a.set(vec![2, 2, 3, 4]);
414 assert_eq!(mapped.get_clone(), vec![4, 4, 6, 8]);
415 });
416 }
417
418 #[test]
420 fn indexed_clear() {
421 let _ = create_root(|| {
422 let a = create_signal(vec![1, 2, 3]);
423 let mapped = map_indexed(a, |x| x * 2);
424
425 a.set(Vec::new());
426 assert_eq!(mapped.get_clone(), Vec::<i32>::new());
427 });
428 }
429
430 #[test]
432 fn indexed_react() {
433 let _ = create_root(|| {
434 let a = create_signal(vec![1, 2, 3]);
435 let mapped = map_indexed(a, |x| x * 2);
436
437 let counter = create_signal(0);
438 create_effect(move || {
439 counter.set(counter.get_untracked() + 1);
440 mapped.track();
441 });
442
443 assert_eq!(counter.get(), 1);
444 a.set(vec![1, 2, 3, 4]);
445 assert_eq!(counter.get(), 2);
446 });
447 }
448
449 #[test]
451 fn indexed_use_previous_computation() {
452 let _ = create_root(|| {
453 let a = create_signal(vec![1, 2, 3]);
454 let counter = Rc::new(Cell::new(0));
455 let mapped = map_indexed(a, {
456 let counter = Rc::clone(&counter);
457 move |_| {
458 counter.set(counter.get() + 1);
459 counter.get()
460 }
461 });
462 assert_eq!(mapped.get_clone(), vec![1, 2, 3]);
463
464 a.set(vec![1, 2]);
465 assert_eq!(mapped.get_clone(), vec![1, 2]);
466
467 a.set(vec![1, 2, 4]);
468 assert_eq!(mapped.get_clone(), vec![1, 2, 4]);
469
470 a.set(vec![1, 3, 4]);
471 assert_eq!(mapped.get_clone(), vec![1, 5, 4]);
472 });
473 }
474
475 #[test]
476 fn indexed_call_cleanup_on_remove() {
477 let _ = create_root(|| {
478 let a = create_signal(vec![1, 2, 3]);
479 let counter = Rc::new(Cell::new(0));
480 let _mapped = map_indexed(a, {
481 let counter = Rc::clone(&counter);
482 move |_| {
483 let counter = Rc::clone(&counter);
484 on_cleanup(move || {
485 counter.set(counter.get() + 1);
486 });
487 }
488 });
489 assert_eq!(counter.get(), 0, "no cleanup yet");
490
491 a.set(vec![1, 2]);
492 assert_eq!(counter.get(), 1);
493
494 a.set(vec![1, 2, 3]);
495 assert_eq!(counter.get(), 1);
496
497 a.set(vec![1, 3]);
498 assert_eq!(counter.get(), 3);
499 });
500 }
501
502 #[test]
503 fn indexed_call_cleanup_on_remove_all() {
504 let _ = create_root(|| {
505 let a = create_signal(vec![1, 2, 3]);
506 let counter = Rc::new(Cell::new(0));
507 let _mapped = map_indexed(a, {
508 let counter = Rc::clone(&counter);
509 move |_| {
510 let counter = Rc::clone(&counter);
511 on_cleanup(move || {
512 counter.set(counter.get() + 1);
513 })
514 }
515 });
516 assert_eq!(counter.get(), 0, "no cleanup yet");
517
518 a.set(vec![]);
519 assert_eq!(counter.get(), 3);
520 });
521 }
522
523 #[test]
525 fn issue_739_keyed_should_not_track_nested_signals() {
526 let _ = create_root(|| {
527 let items = create_signal(vec![create_signal(1), create_signal(2)]);
528 map_keyed(items, |x| on_cleanup(move || x.dispose()), |x| x.get());
529 items.set(items.get_clone()[1..].to_vec());
530 });
531 }
532
533 #[test]
535 fn issue_739_indexed_should_not_track_nested_signals() {
536 let _ = create_root(|| {
537 let items = create_signal(vec![create_signal(()), create_signal(())]);
538 map_indexed(items, |x| on_cleanup(move || x.dispose()));
539 items.set(items.get_clone()[1..].to_vec());
540 });
541 }
542}